/blog/

2021 0223 Secret control panels

For a couple of web projects, I’ve found myself wanting to expose debug functionality or experimental options on live sites. Sometimes I want a UI around functionality just for myself, like a drop down menu for enabling debug messages in the JavaScript console. Other times, I want to share an idea with others to solicit their opinions or advice, like whether link annotations might look good if enabled everywhere.

Still other times, well, you know that saying that every setting is a design failure? Sometimes my design failures are just too precious to me, and goddammit it’s my website so I’ll leave them there if I want to. 🙃

Plus, it’s good to keep a few secrets.

Here are a few examples.

Control panel on this site

me.micahrl.com controls

Screenshot of the control panel page on this site

At the time of this writing, it shows an option to display link annotations. I have experimented with annotations for PDFs, Wikipedia links, and the like, but I’m not sure whether I really like them.

To implement this, I just needed a few pieces:

  1. The checkbox itself
  2. An onclick() function that when checked/unchecked adds/removes an enabler class to the element that contains all my content
  3. CSS that shows link annotations only for elements that are children of the enabler class
  4. (Optional) A bit more JavaScript code to save the value to localStorage, so that it works on subsequent visits

Note that there is no server side code here; all of this is just stored to memory (and optionally to localStorage).

I don’t share the repo for this site, but the code as deployed is accessible from the browser’s “View Source” functionality, and quite readable (it’s not minified or anything, and short).

Update: JavaScript is now minified, so the code in question looks like this:

Theme setting code
var ThemeSetting = {

  /* Constants for easy reference later
    */
  localStorageKey: 'site-setting-theme',
  radioGroupClass: 'site-setting-theme-radio-group',
  darkColorSchemeClass: 'color-scheme-dark',
  lightColorSchemeClass: 'color-scheme-light',

  /* Convenience getters
    */
  get: function () {
    const lsvalue = localStorage.getItem(this.localStorageKey);
    return lsvalue ? lsvalue : "auto";
  },
  set: function (value) {
    return localStorage.setItem(this.localStorageKey, value);
  },
  radiogroups: function () {
    return document.getElementsByClassName(this.radioGroupClass);
  },
  inDarkMode: function () {
    if (this.get() == 'dark') {
      return true;
    } else if (this.get() == 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      return true;
    }
    return false;
  },

  /* Read the theme setting from localStorage and apply it to the page
    */
  apply: function () {
    var lightColorSchemeMediaValue = "all and (prefers-color-scheme: light)";
    var darkColorSchemeMediaValue = "all and (prefers-color-scheme: dark)";
    switch (this.get()) {
      case 'dark':
        lightColorSchemeMediaValue = "not all";
        darkColorSchemeMediaValue = "all";
        break;
      case 'light':
        lightColorSchemeMediaValue = "all";
        darkColorSchemeMediaValue = "not all";
        break;
    }
    for (elem of document.getElementsByClassName(this.lightColorSchemeClass)) {
      elem.media = lightColorSchemeMediaValue;
    }
    for (elem of document.getElementsByClassName(this.darkColorSchemeClass)) {
      elem.media = darkColorSchemeMediaValue;
    }
  },

  /* Set the page load state (including e.g. dark mode toggle buttons).
    * Should be called from the <body>'s onload event
    */
  setPageLoadState: function () {
    this.apply();
    if (!this.radiogroups()) {
      return;
    }
    for (radiogroup of this.radiogroups()) {
      for (elem of radiogroup.getElementsByTagName("input")) {
        if (elem.value == this.get()) {
          elem.checked = true;
        } else {
          elem.checked = false;
        }
      }
    }
  },

  /* Should be called from the `onclick` of each option in the dark mode toggle radio button group
    */
  setRadioGroup: function() {
    for (radiogroup of this.radiogroups()) {
      for (elem of radiogroup.getElementsByTagName("input")) {
        if (elem.checked) {
          this.set(elem.value);
        }
      }
    }
    this.apply();
  }
}

// Apply the theme immediately
ThemeSetting.apply();
// In the <body> onload, apply the theme again --
// this ensures that any page which has styles with the color scheme classes
// will have the correct styles applied even if this JavaScript is loaded and executed before those styles.
window.bodyOnloads.push(function () { ThemeSetting.apply(); })
// In the <body> onload, set the page load state (toggle buttons etc)
window.bodyOnloads.push(function () { ThemeSetting.setPageLoadState(); })

Control panel on keymap.click

keymap.click controls

Screenshot of the control panel page on keymap.click

Controls on keymap.click have options for the debug level, more cluttered navigation in the side bar, and a different color scheme.

This project uses React and NextJS, and I was already using React Context for app-side values. I added a few more for my new secret control panels items, and added a new NextJS page for the /controls route, and it worked just like the other controls already in my app.

This too has no server side code.

You can read the source on GitHub if you like.

Control panel on biblemunger.micahrl.com

biblemunger controls

Screenshot of the control panel page on biblemunger

Controls for biblemunger show font options. I think I picked the best fonts for the page, but I wanted to include an option (even if secret) to use a lovely digital restoration of the original 1611 King James Version typeface. It is very busy for the modern eye, but I just could not resist.

(Compare the screenshots on the biblemunger blog post with one of the kjv1611 typeface in action

Responses

Webmentions

Hosted on remote sites, and collected here via Webmention.io (thanks!).

Comments

Comments are hosted on this site and powered by Remark42 (thanks!).