mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Chart theming: custom named themes + Theme Builder
This commit is contained in:
@@ -172,21 +172,62 @@ swapping the expressive one (future custom themes).
|
||||
### Selectable chart themes
|
||||
|
||||
On top of the house pair, the user picks a **chart theme** (spec §04 → Chart
|
||||
theme) — `ChartThemeId = 'astrolabe' | 'stock' | <vega-themes preset id>`:
|
||||
theme) — `ChartThemeSelection = 'astrolabe' | 'stock' | <vega-themes preset id>
|
||||
| 'custom:<id>'`:
|
||||
|
||||
- `'astrolabe'` resolves via `chartConfigFor(uiTheme)` (follows light/dark);
|
||||
- `'stock'` resolves to `{}` — nothing injected, pure Vega-Lite defaults;
|
||||
- preset ids resolve to the `vega-themes` package's configs verbatim (the same
|
||||
presets as the Vega editor's theme dropdown; the package is already in the
|
||||
tree as a vega-embed dependency).
|
||||
tree as a vega-embed dependency);
|
||||
- `custom:<id>` resolves to a saved `CustomTheme` record's config (spec §09G).
|
||||
Selection is keyed by record **id**, not name, so a rename never invalidates
|
||||
the persisted preference; a missing record (themes hydrate async from
|
||||
IndexedDB; the record may be deleted) resolves to the house config rather
|
||||
than rendering unstyled, and deleting the actively-selected theme resets
|
||||
`AppStore.chartTheme` to `'astrolabe'` (CustomThemeStore.remove).
|
||||
|
||||
`chartConfigForSelection(selection, uiTheme)` is the only resolver. The choice
|
||||
lives in `AppStore.chartTheme`, persisted as `ui.chartTheme` by
|
||||
`orchestration/preferences.ts` (the `previewFitMode` pattern), and is surfaced
|
||||
by a `SelectControl` in the LivePreview header — **not** inside the
|
||||
PreviewSettings popover: `SelectControl` and `SettingsPopover` share the
|
||||
one-open-popover registry, so a select nested in the popover would close (and
|
||||
unmount) its own parent on open.
|
||||
`chartConfigForSelection(selection, uiTheme, customThemes)` is the only
|
||||
resolver; `chartThemeOptions(customThemes)` derives the full picker list
|
||||
(built-ins, customs, presets — memoize the call: it returns a fresh array). The
|
||||
choice lives in `AppStore.chartTheme`, persisted as `ui.chartTheme` by
|
||||
`orchestration/preferences.ts` (the `previewFitMode` pattern; persistence
|
||||
validates with `isChartThemeSelection`, which accepts `custom:<id>` on shape
|
||||
alone), and is surfaced by a `SelectControl` in the LivePreview header — **not**
|
||||
inside the PreviewSettings popover: `SelectControl` and `SettingsPopover` share
|
||||
the one-open-popover registry, so a select nested in the popover would close
|
||||
(and unmount) its own parent on open. The "Edit themes…" action row opens the
|
||||
Theme Builder without changing the selection (the VS Code theme-picker
|
||||
pattern); it closes the custom-themes block — after the built-ins, **before**
|
||||
the long preset roster — so it's visible without scrolling and sits next to
|
||||
the entries it manages.
|
||||
|
||||
### Custom themes & the Theme Builder
|
||||
|
||||
`CustomTheme` records (`core/custom-theme.ts`) persist in their own IndexedDB
|
||||
store through the standard stack: `infrastructure/theme-store.ts` (+ read-time
|
||||
`theme-migrations.ts`), `stores/CustomThemeStore.ts` (the themes array plus the
|
||||
builder's draft state), and `orchestration/theme-persistence.ts` (diffing
|
||||
write-through, wired after hydrate in `startup.ts`) — the exact dataset
|
||||
pattern, one tier each.
|
||||
|
||||
The Theme Builder modal (`ThemeBuilderModal`, registered as `themeBuilder`,
|
||||
xlarge shell, no backdrop dismissal) edits a **draft** held in the store:
|
||||
`{ name, configText }` plus `draftConfig` — the last text state that parsed.
|
||||
The gallery (`core/theme-preview-specs.ts`, fixed inline-data swatch specs)
|
||||
renders `draftConfig` per card through the shared `renderSpec` with the
|
||||
**canvas** renderer and a per-card debounce + chain-lock (the LivePreview
|
||||
serialization pattern, one lock per card) — so invalid JSON mid-edit never
|
||||
blanks the preview, and seven concurrent embeds never interleave on a node.
|
||||
`applyFontToConfig(config, family)` is the font control's transform: it sets
|
||||
the top-level `font` and rewrites every `font`/`*Font` string slot at any
|
||||
depth — explicit slots would otherwise keep overriding the new default.
|
||||
|
||||
Creation paths: the builder's "New theme" duplicates the currently selected
|
||||
chart theme's resolved config, and the editor's **Extract Config to New
|
||||
Theme** action (`runExtractConfigToTheme`, spec §03G) lifts a spec's `config`
|
||||
block into a theme, selects it, and removes the block — the spec-to-library
|
||||
direction of the same boundary the merge action crosses the other way.
|
||||
|
||||
Render-time precedence: vega-lite merges the injected config **under** the
|
||||
spec's own `config` (`mergeConfig(opt.config, spec.config)` — the spec wins
|
||||
|
||||
Reference in New Issue
Block a user