asdf98 commited on
Commit
87679d6
·
verified ·
1 Parent(s): d851ebe

feat: add theme state to store; apply data-theme to document; persist in localStorage

Browse files
Files changed (1) hide show
  1. src/store.tsx +9 -1
src/store.tsx CHANGED
@@ -3,6 +3,8 @@ import { invoke } from '@tauri-apps/api/core';
3
  import { listen } from '@tauri-apps/api/event';
4
  import { RefImage, ContextMenuState, AnnotationPath, Palette, TextNote } from './types';
5
 
 
 
6
  interface AppState {
7
  textNotes: TextNote[]; setTextNotes: React.Dispatch<React.SetStateAction<TextNote[]>>;
8
  images: RefImage[]; setImages: React.Dispatch<React.SetStateAction<RefImage[]>>;
@@ -26,6 +28,7 @@ interface AppState {
26
  isHighlighter: boolean; setIsHighlighter: React.Dispatch<React.SetStateAction<boolean>>;
27
  showMinimap: boolean; setShowMinimap: React.Dispatch<React.SetStateAction<boolean>>;
28
  showGrid: boolean; setShowGrid: React.Dispatch<React.SetStateAction<boolean>>;
 
29
  undo: () => void; redo: () => void;
30
  currentScreen: 'hub' | 'board'; setCurrentScreen: React.Dispatch<React.SetStateAction<'hub' | 'board'>>;
31
  updateSelectedNodes: (dx: number, dy: number, explicitTargetId: string) => void;
@@ -64,6 +67,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
64
  const [isHighlighter, setIsHighlighter] = useState(false);
65
  const [showMinimap, setShowMinimap] = useState(false);
66
  const [showGrid, setShowGrid] = useState(true);
 
67
  const [history, setHistory] = useState<any[]>([]);
68
  const [historyIndex, setHistoryIndex] = useState(-1);
69
  const [isUndoing, setIsUndoing] = useState(false);
@@ -80,13 +84,17 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
80
  useEffect(() => { panRef.current = pan; }, [pan]);
81
  useEffect(() => { zoomRef.current = zoom; }, [zoom]);
82
 
 
83
  useEffect(() => {
84
  if (localStorage.getItem('refstudio.showMinimap') === 'true') setShowMinimap(true);
85
  const grid = localStorage.getItem('refstudio.showGrid');
86
  if (grid !== null) setShowGrid(grid === 'true');
 
 
87
  }, []);
88
  useEffect(() => { localStorage.setItem('refstudio.showMinimap', String(showMinimap)); }, [showMinimap]);
89
  useEffect(() => { localStorage.setItem('refstudio.showGrid', String(showGrid)); }, [showGrid]);
 
90
 
91
  useEffect(() => {
92
  const un = listen<any>('board://image_added', (event) => {
@@ -121,7 +129,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
121
  const redo = useCallback(() => { if (historyIndex < history.length - 1) { setIsUndoing(true); const s = history[historyIndex + 1]; setTextNotes(s.textNotes); setImages(s.images); setAnnotations(s.annotations); setPalettes(s.palettes); setZoom(s.zoom); setPan(s.pan); if (s.valueMirrorIds) setValueMirrorIds(s.valueMirrorIds); setHistoryIndex(historyIndex + 1); } }, [history, historyIndex]);
122
  const updateSelectedNodes = useCallback((dx: number, dy: number, explicitTargetId: string) => { const ids = new Set([...selectedNodeIds, explicitTargetId]); setImages(prev => { const gids = new Set<string>(); prev.forEach(i => { if (ids.has(i.id) && i.groupId) gids.add(i.groupId); }); return prev.map(i => (ids.has(i.id) || (i.groupId && gids.has(i.groupId))) ? { ...i, x: i.x + dx, y: i.y + dy } : i); }); setTextNotes(prev => { const gids = new Set<string>(); prev.forEach(i => { if (ids.has(i.id) && i.groupId) gids.add(i.groupId); }); return prev.map(i => (ids.has(i.id) || (i.groupId && gids.has(i.groupId))) ? { ...i, x: i.x + dx, y: i.y + dy } : i); }); }, [selectedNodeIds]);
123
 
124
- return <AppContext.Provider value={{ textNotes, setTextNotes, images, setImages, annotations, setAnnotations, palettes, setPalettes, zoom, setZoom, pan, setPan, isSettingsOpen, setIsSettingsOpen, isBrowserOpen, setIsBrowserOpen, isLibraryOpen, setIsLibraryOpen, selectedNodeIds, setSelectedNodeIds, globalDesaturate, setGlobalDesaturate, contextMenu, setContextMenu, isAlwaysOnTop, setIsAlwaysOnTop, bgOpacity, setBgOpacity, isClickThrough, setIsClickThrough, isAnnotationMode, setIsAnnotationMode, annotationColor, setAnnotationColor, annotationSize, setAnnotationSize, isEraser, setIsEraser, isHighlighter, setIsHighlighter, showMinimap, setShowMinimap, showGrid, setShowGrid, undo, redo, currentScreen, setCurrentScreen, updateSelectedNodes, focusedImageId, setFocusedImageId, valueMirrorIds, setValueMirrorIds, isZoomLensActive, setIsZoomLensActive, isWhisperBrowser, setIsWhisperBrowser, activeProjectId, setActiveProjectId, boardTitle, setBoardTitle }}>{children}</AppContext.Provider>;
125
  };
126
 
127
  export const useAppStore = () => { const ctx = useContext(AppContext); if (!ctx) throw new Error('useAppStore must be used within AppProvider'); return ctx; };
 
3
  import { listen } from '@tauri-apps/api/event';
4
  import { RefImage, ContextMenuState, AnnotationPath, Palette, TextNote } from './types';
5
 
6
+ export type ThemeId = 'dark-canvas' | 'warm-studio' | 'midnight';
7
+
8
  interface AppState {
9
  textNotes: TextNote[]; setTextNotes: React.Dispatch<React.SetStateAction<TextNote[]>>;
10
  images: RefImage[]; setImages: React.Dispatch<React.SetStateAction<RefImage[]>>;
 
28
  isHighlighter: boolean; setIsHighlighter: React.Dispatch<React.SetStateAction<boolean>>;
29
  showMinimap: boolean; setShowMinimap: React.Dispatch<React.SetStateAction<boolean>>;
30
  showGrid: boolean; setShowGrid: React.Dispatch<React.SetStateAction<boolean>>;
31
+ theme: ThemeId; setTheme: React.Dispatch<React.SetStateAction<ThemeId>>;
32
  undo: () => void; redo: () => void;
33
  currentScreen: 'hub' | 'board'; setCurrentScreen: React.Dispatch<React.SetStateAction<'hub' | 'board'>>;
34
  updateSelectedNodes: (dx: number, dy: number, explicitTargetId: string) => void;
 
67
  const [isHighlighter, setIsHighlighter] = useState(false);
68
  const [showMinimap, setShowMinimap] = useState(false);
69
  const [showGrid, setShowGrid] = useState(true);
70
+ const [theme, setTheme] = useState<ThemeId>('dark-canvas');
71
  const [history, setHistory] = useState<any[]>([]);
72
  const [historyIndex, setHistoryIndex] = useState(-1);
73
  const [isUndoing, setIsUndoing] = useState(false);
 
84
  useEffect(() => { panRef.current = pan; }, [pan]);
85
  useEffect(() => { zoomRef.current = zoom; }, [zoom]);
86
 
87
+ // Persist preferences
88
  useEffect(() => {
89
  if (localStorage.getItem('refstudio.showMinimap') === 'true') setShowMinimap(true);
90
  const grid = localStorage.getItem('refstudio.showGrid');
91
  if (grid !== null) setShowGrid(grid === 'true');
92
+ const savedTheme = localStorage.getItem('refstudio.theme') as ThemeId | null;
93
+ if (savedTheme && ['dark-canvas', 'warm-studio', 'midnight'].includes(savedTheme)) setTheme(savedTheme);
94
  }, []);
95
  useEffect(() => { localStorage.setItem('refstudio.showMinimap', String(showMinimap)); }, [showMinimap]);
96
  useEffect(() => { localStorage.setItem('refstudio.showGrid', String(showGrid)); }, [showGrid]);
97
+ useEffect(() => { localStorage.setItem('refstudio.theme', theme); document.documentElement.setAttribute('data-theme', theme); }, [theme]);
98
 
99
  useEffect(() => {
100
  const un = listen<any>('board://image_added', (event) => {
 
129
  const redo = useCallback(() => { if (historyIndex < history.length - 1) { setIsUndoing(true); const s = history[historyIndex + 1]; setTextNotes(s.textNotes); setImages(s.images); setAnnotations(s.annotations); setPalettes(s.palettes); setZoom(s.zoom); setPan(s.pan); if (s.valueMirrorIds) setValueMirrorIds(s.valueMirrorIds); setHistoryIndex(historyIndex + 1); } }, [history, historyIndex]);
130
  const updateSelectedNodes = useCallback((dx: number, dy: number, explicitTargetId: string) => { const ids = new Set([...selectedNodeIds, explicitTargetId]); setImages(prev => { const gids = new Set<string>(); prev.forEach(i => { if (ids.has(i.id) && i.groupId) gids.add(i.groupId); }); return prev.map(i => (ids.has(i.id) || (i.groupId && gids.has(i.groupId))) ? { ...i, x: i.x + dx, y: i.y + dy } : i); }); setTextNotes(prev => { const gids = new Set<string>(); prev.forEach(i => { if (ids.has(i.id) && i.groupId) gids.add(i.groupId); }); return prev.map(i => (ids.has(i.id) || (i.groupId && gids.has(i.groupId))) ? { ...i, x: i.x + dx, y: i.y + dy } : i); }); }, [selectedNodeIds]);
131
 
132
+ return <AppContext.Provider value={{ textNotes, setTextNotes, images, setImages, annotations, setAnnotations, palettes, setPalettes, zoom, setZoom, pan, setPan, isSettingsOpen, setIsSettingsOpen, isBrowserOpen, setIsBrowserOpen, isLibraryOpen, setIsLibraryOpen, selectedNodeIds, setSelectedNodeIds, globalDesaturate, setGlobalDesaturate, contextMenu, setContextMenu, isAlwaysOnTop, setIsAlwaysOnTop, bgOpacity, setBgOpacity, isClickThrough, setIsClickThrough, isAnnotationMode, setIsAnnotationMode, annotationColor, setAnnotationColor, annotationSize, setAnnotationSize, isEraser, setIsEraser, isHighlighter, setIsHighlighter, showMinimap, setShowMinimap, showGrid, setShowGrid, theme, setTheme, undo, redo, currentScreen, setCurrentScreen, updateSelectedNodes, focusedImageId, setFocusedImageId, valueMirrorIds, setValueMirrorIds, isZoomLensActive, setIsZoomLensActive, isWhisperBrowser, setIsWhisperBrowser, activeProjectId, setActiveProjectId, boardTitle, setBoardTitle }}>{children}</AppContext.Provider>;
133
  };
134
 
135
  export const useAppStore = () => { const ctx = useContext(AppContext); if (!ctx) throw new Error('useAppStore must be used within AppProvider'); return ctx; };