1import { create } from "zustand";
2
3import {
4 getThemeDefinition,
5 type ThemeAppearance,
6 type ThemeHalves,
7 type ThemePreference,
8} from "../../themePalette";
9
10/**
11 * The theme editor lives above the router so a draft survives navigation: the
12 * palette is painted on the live app, and judging it means browsing the app
13 * while the editor stays open. Themes are referenced by id because the stored
14 * definitions change underneath an open session (an edit, an import).
15 */
16export type ThemeEditorSessionInput = {
17 /** Set when editing an installed theme; null creates a new one. */
18 editingThemeId: string | null;
19 /** Theme a new theme starts from: the active one, or a duplicate target. */
20 seedThemeId: string | null;
21 /** Prefilled name, used by duplicate. */
22 seedName: string | null;
23 initialAppearance: ThemeAppearance;
24};
25
26export type ThemeEditorSession = ThemeEditorSessionInput & {
27 /**
28 * Distinguishes two sessions that name the same themes, so asking for a
29 * fresh draft while the panel is already open still reseeds it.
30 */
31 id: number;
32};
33
34type ThemeEditorStore = {
35 session: ThemeEditorSession | null;
36 openThemeEditor: (session: ThemeEditorSessionInput) => void;
37 closeThemeEditor: () => void;
38};
39
40let nextSessionId = 0;
41
42export const useThemeEditorStore = create<ThemeEditorStore>((set) => ({
43 session: null,
44 openThemeEditor: (session) => set({ session: { ...session, id: ++nextSessionId } }),
45 closeThemeEditor: () => set({ session: null }),
46}));
47
48export function toggleThemeEditorForTheme(input: {
49 theme: ThemePreference;
50 themeHalves: ThemeHalves | null;
51 initialAppearance: ThemeAppearance;
52}): void {
53 const store = useThemeEditorStore.getState();
54 if (store.session) {
55 store.closeThemeEditor();
56 return;
57 }
58
59 const baseThemeId = getThemeDefinition(input.theme)?.id ?? null;
60 const activeThemeId = input.themeHalves?.[input.initialAppearance] ?? baseThemeId;
61 const seedThemeId = activeThemeId ? (getThemeDefinition(activeThemeId)?.id ?? null) : null;
62 store.openThemeEditor({
63 editingThemeId: null,
64 seedThemeId,
65 seedName: null,
66 initialAppearance: input.initialAppearance,
67 });
68}