feat: implement global settings system with localStorage persistence

Creates a new extendable settings store for application-wide preferences
that persist across browser sessions using Zustand's persist middleware.

Architecture:
- New settingsStore.ts using Zustand persist middleware
- Automatic localStorage synchronization
- Versioned schema (v1) for future migrations
- Clean, typed interface for easy extensibility

Changes:
- Created settingsStore for persistent global settings
- Migrated autoZoomEnabled from searchStore to settingsStore
- Updated GraphEditor to use settings store for auto-zoom
- Updated LeftPanel to use settings store for toggle control
- searchStore now focuses on ephemeral filter/search state

Settings Persistence:
- Storage key: 'constellation-settings'
- Automatically saves on any setting change
- Restores settings on page reload
- Same proven pattern as panelStore

Future Extensibility:
The settings store is designed to easily accommodate new settings:
- Theme preferences (light/dark mode)
- Default view options
- User interface preferences
- Any application-wide persistent settings

Benefits:
- User preferences persist across sessions
- Clean separation between ephemeral and persistent state
- Type-safe settings management
- Easy to extend for future features

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-10-16 14:30:49 +02:00
parent 58e04650c0
commit 084a3bb486
4 changed files with 48 additions and 12 deletions

View file

@ -25,6 +25,7 @@ import { useGraphWithHistory } from "../../hooks/useGraphWithHistory";
import { useDocumentHistory } from "../../hooks/useDocumentHistory";
import { useEditorStore } from "../../stores/editorStore";
import { useSearchStore } from "../../stores/searchStore";
import { useSettingsStore } from "../../stores/settingsStore";
import { useActiveDocument } from "../../stores/workspace/useActiveDocument";
import { useWorkspaceStore } from "../../stores/workspaceStore";
import { useCreateDocument } from "../../hooks/useCreateDocument";
@ -108,9 +109,11 @@ const GraphEditor = ({ onNodeSelect, onEdgeSelect, onAddNodeRequest, onExportReq
searchText,
visibleActorTypes,
visibleRelationTypes,
autoZoomEnabled,
} = useSearchStore();
// Settings for auto-zoom
const { autoZoomEnabled } = useSettingsStore();
// Track previous document ID to save viewport before switching
const prevDocumentIdRef = useRef<string | null>(null);

View file

@ -13,6 +13,7 @@ import { usePanelStore } from '../../stores/panelStore';
import { useGraphWithHistory } from '../../hooks/useGraphWithHistory';
import { useEditorStore } from '../../stores/editorStore';
import { useSearchStore } from '../../stores/searchStore';
import { useSettingsStore } from '../../stores/settingsStore';
import { createNode } from '../../utils/nodeUtils';
import { getIconComponent } from '../../utils/iconUtils';
import { getContrastColor } from '../../utils/colorUtils';
@ -87,12 +88,13 @@ const LeftPanel = forwardRef<LeftPanelRef, LeftPanelProps>(({ onDeselectAll, onA
setActorTypeVisible,
visibleRelationTypes,
setRelationTypeVisible,
autoZoomEnabled,
setAutoZoomEnabled,
clearFilters,
hasActiveFilters,
} = useSearchStore();
// Settings
const { autoZoomEnabled, setAutoZoomEnabled } = useSettingsStore();
// Initialize filter state when node/edge types change
useEffect(() => {
nodeTypes.forEach((nodeType) => {

View file

@ -7,7 +7,6 @@ import { create } from 'zustand';
* - Search text for filtering both actors (by label, description, or type) and relations (by label or type)
* - Filter by actor types (show/hide specific node types)
* - Filter by relation types (show/hide specific edge types)
* - Auto-zoom to filtered results (optional, enabled by default)
* - Results tracking
*/
@ -28,10 +27,6 @@ interface SearchStore {
toggleRelationType: (typeId: string) => void;
setAllRelationTypesVisible: (visible: boolean) => void;
// Auto-zoom to filtered results
autoZoomEnabled: boolean;
setAutoZoomEnabled: (enabled: boolean) => void;
// Clear all filters
clearFilters: () => void;
@ -43,7 +38,6 @@ export const useSearchStore = create<SearchStore>((set, get) => ({
searchText: '',
visibleActorTypes: {},
visibleRelationTypes: {},
autoZoomEnabled: true,
setSearchText: (text: string) =>
set({ searchText: text }),
@ -98,9 +92,6 @@ export const useSearchStore = create<SearchStore>((set, get) => ({
return { visibleRelationTypes: updated };
}),
setAutoZoomEnabled: (enabled: boolean) =>
set({ autoZoomEnabled: enabled }),
clearFilters: () =>
set((state) => {
// Reset all actor types to visible

View file

@ -0,0 +1,40 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
/**
* Settings Store - Global application settings with localStorage persistence
*
* Features:
* - Search and filter preferences
* - UI behavior settings
* - Auto-save to localStorage
* - Extendable for future settings
*/
interface SettingsState {
// Search & Filter Settings
autoZoomEnabled: boolean;
setAutoZoomEnabled: (enabled: boolean) => void;
// Future settings can be added here
// Example:
// theme: 'light' | 'dark';
// setTheme: (theme: 'light' | 'dark') => void;
}
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
// Search & Filter Settings
autoZoomEnabled: true,
setAutoZoomEnabled: (enabled: boolean) =>
set({ autoZoomEnabled: enabled }),
// Future settings implementations go here
}),
{
name: 'constellation-settings', // localStorage key
version: 1, // For future migrations if needed
}
)
);