Skip to content

Latest commit

 

History

History
344 lines (237 loc) · 4.84 KB

File metadata and controls

344 lines (237 loc) · 4.84 KB

Every shareable setting is self-contained and registered once. The registry is the single source of truth for UI generation, defaults, validation, and URL persistence.

Settings Registry

Every setting is described by a single object.

type SettingDefinition<T> = {
  id: string;
  label: string;

  type:
    | 'boolean'
    | 'number'
    | 'select'
    | 'color';

  defaultValue: T;

  serialize(value: T): string;
  deserialize(value: string | null): T;

  createControl(
    value: T,
    onChange: (value: T) => void
  ): HTMLElement;
};

Example:

const buttonPositionSetting: SettingDefinition<string> = {
  id: 'btn',

  label: 'Button Position',

  type: 'select',

  defaultValue: 'tl',

  serialize: value => value,

  deserialize: value => {
    if (
      value === 'tl' ||
      value === 'tr' ||
      value === 'bl' ||
      value === 'br'
    ) {
      return value;
    }

    return 'tl';
  },

  createControl(value, onChange) {
    const select = document.createElement('select');

    [
      ['tl', 'Top Left'],
      ['tr', 'Top Right'],
      ['bl', 'Bottom Left'],
      ['br', 'Bottom Right'],
    ].forEach(([id, label]) => {
      const option = document.createElement('option');
      option.value = id;
      option.textContent = label;
      select.append(option);
    });

    select.value = value;
    select.onchange = () => onChange(select.value);

    return select;
  },
};

Registry

All settings live in one registry.

export const SETTINGS = [
  buttonPositionSetting,
  // future settings...
] as const;

The panel is generated entirely from this registry.

for (const setting of SETTINGS) {
  createSettingRow(setting);
}

No hand-written UI per setting.


Runtime State

Current values are stored separately.

type SettingsState = Record<string, unknown>;

const settings: SettingsState = {};

Initialization:

for (const def of SETTINGS) {
  settings[def.id] = def.defaultValue;
}

URL Serialization Contract

Each setting owns exactly one query parameter.

Rule:

setting.id === query parameter name

Example:

?btn=tr

for

buttonPosition = "top-right"

Serialization

function settingsToSearchParams(): URLSearchParams {
  const params = new URLSearchParams();

  for (const def of SETTINGS) {
    const value = settings[def.id];

    if (value === def.defaultValue) {
      continue;
    }

    params.set(
      def.id,
      def.serialize(value)
    );
  }

  return params;
}

Default values are omitted.

So:

?btn=tl

becomes:

because tl is the default.

Cleaner URLs.


Deserialization

function loadSettings(
  params: URLSearchParams
) {
  for (const def of SETTINGS) {
    settings[def.id] =
      def.deserialize(
        params.get(def.id)
      );
  }
}

Each setting validates itself.

Invalid values fall back to defaults.

Example:

?btn=potato

becomes

buttonPosition = 'tl';

Camera Contract

Camera is special because it is not a UI setting.

We store it separately.

?cam=45,30,3.2

Meaning:

theta
phi
radius

Example:

?cam=45,30,2.7

This keeps camera state compact.


Model Contract

The model setting is registered in the settings registry like any other configurable setting.

?model=skull.obj

It uses the standard id: 'model' and its createControl renders a grid of buttons, one per registered model, each showing a stylised SVG thumbnail.

The model setting is treated specially only during serialization — it is handled by the explicit model parameter in serializeState() rather than the generic settings loop, to keep the URL format clean and avoid duplicate query parameters.


Full URL Example

?v=1
&model=hand.obj
&cam=45,30,2.5
&btn=br

Compact version:

?v=1&model=hand.obj&cam=45,30,2.5&btn=br

Adding a New Setting

Suppose later you add:

Background Color

You create:

const backgroundColorSetting = {
  id: 'bg',
  label: 'Background',

  type: 'color',

  defaultValue: '#ffffff',

  serialize: v => v,

  deserialize: v => v ?? '#ffffff',
};

Add it:

SETTINGS.push(backgroundColorSetting);

Immediately you get:

  • UI control
  • URL loading
  • URL saving
  • default handling
  • sharing support

No URL code changes.


Adding a New Model

To add a new model to the model selection setting:

  1. Place the model file in /public/models/.
  2. Add a ModelEntry to src/models/registry.ts with:
    • id — the filename (e.g. "hand.obj")
    • label — display name
    • thumbnail — inline SVG string for the button
  3. Optionally add credit metadata in src/models/credits.ts.

No registry or URL code changes needed — the model setting automatically picks up new entries from the models registry.