Chart theming: custom named themes + Theme Builder

This commit is contained in:
2026-06-12 18:15:54 +03:00
parent 44a601affd
commit b193464f55
32 changed files with 2220 additions and 70 deletions
+30
View File
@@ -17,12 +17,16 @@
import type { ComponentType } from 'react';
import type { ActiveModal, ModalName } from './types';
import { customThemeIdOf } from '@core/vega-themes';
import { AboutModal } from '../components/AboutModal';
import { ChartBuilderModal } from '../components/ChartBuilderModal';
import { DatasetsModal } from '../components/DatasetsModal';
import { DonateModal } from '../components/DonateModal';
import { ExtractModal } from '../components/ExtractModal';
import { ThemeBuilderModal } from '../components/ThemeBuilderModal';
import { useAppStore } from '../stores/AppStore';
import { useChartBuilderStore } from '../stores/ChartBuilderStore';
import { selectIsDraftDirty, useCustomThemeStore } from '../stores/CustomThemeStore';
import { useDatasetStore } from '../stores/DatasetStore';
import { useExtractStore } from '../stores/ExtractStore';
@@ -88,6 +92,32 @@ export const MODAL_REGISTRY: Partial<Record<ModalName, ModalConfig>> = {
init: (datasetId) => useChartBuilderStore.getState().init(datasetId ? Number(datasetId) : null),
},
// Opened from the chart-theme picker's "Edit themes…" entry. Re-seeds the open
// draft from its saved record on open (clean baseline), keeping whichever theme
// was last edited — or the active custom selection — in view. The snapshot is
// the dirty draft only, so browsing themes never trips a false discard prompt.
// Backdrop dismissal is off: config edits are real in-progress work.
themeBuilder: {
name: 'themeBuilder',
title: 'Theme Builder',
component: ThemeBuilderModal,
dismissOnBackdrop: false,
init: () => {
const store = useCustomThemeStore.getState();
const activeCustomId = customThemeIdOf(useAppStore.getState().chartTheme);
const id =
store.selectedId ??
(activeCustomId !== null && store.themes.some((t) => t.id === activeCustomId)
? activeCustomId
: (store.themes[0]?.id ?? null));
store.select(id);
},
getState: () => {
const s = useCustomThemeStore.getState();
return selectIsDraftDirty(s) ? { selectedId: s.selectedId, draft: s.draft } : null;
},
},
// Pure info modals — no state, no validity check, no discard prompt on close.
// `about` is URL-navigable (reload-restore); `donate` is not (spec §01E hash
// table omits both, but `about` is still a permanent, bookmark-worthy surface).
+2 -1
View File
@@ -12,7 +12,8 @@ export type ModalName =
| 'about' // About & Help (M6)
| 'donate' // Donate (M6)
| 'chartBuilder' // Visual no-JSON chart composition for a dataset (M4)
| 'extract'; // Extract inline spec data into a new dataset (M3)
| 'extract' // Extract inline spec data into a new dataset (M3)
| 'themeBuilder'; // Custom chart theme editor with a live preview gallery (spec §04)
// Settings is NOT a modal — preferences are distributed to per-pane disclosure
// popovers (spec §07; see components/SettingsPopover).