import type { IStatePersistenceConfig, IAppUIState } from '../../interfaces/appconfig.js'; /** * Manager for persisting and restoring UI state */ export class StateManager { private config: Required; private memoryStorage: Map = new Map(); constructor(config: IStatePersistenceConfig = { enabled: false }) { this.config = { enabled: config.enabled, storageKey: config.storageKey || 'dees-appui-state', storage: config.storage || 'localStorage', persist: { mainMenuCollapsed: true, secondaryMenuCollapsed: true, selectedView: true, secondaryMenuSelection: true, collapsedGroups: true, ...config.persist, }, }; } /** * Check if state persistence is enabled */ public isEnabled(): boolean { return this.config.enabled; } /** * Save current UI state */ public save(state: Partial): void { if (!this.config.enabled) return; const existingState = this.load() || {}; const newState: IAppUIState = { ...existingState, timestamp: Date.now(), }; // Only save what's configured if (this.config.persist.selectedView && state.currentViewId !== undefined) { newState.currentViewId = state.currentViewId; } if (this.config.persist.mainMenuCollapsed && state.mainMenuCollapsed !== undefined) { newState.mainMenuCollapsed = state.mainMenuCollapsed; } if (this.config.persist.secondaryMenuCollapsed && state.secondaryMenuCollapsed !== undefined) { newState.secondaryMenuCollapsed = state.secondaryMenuCollapsed; } if (this.config.persist.secondaryMenuSelection && state.secondaryMenuSelectedKey !== undefined) { newState.secondaryMenuSelectedKey = state.secondaryMenuSelectedKey; } if (this.config.persist.collapsedGroups && state.collapsedGroups !== undefined) { newState.collapsedGroups = state.collapsedGroups; } this.setItem(this.config.storageKey, JSON.stringify(newState)); } /** * Load persisted UI state */ public load(): IAppUIState | null { if (!this.config.enabled) return null; try { const data = this.getItem(this.config.storageKey); if (!data) return null; return JSON.parse(data) as IAppUIState; } catch (e) { console.warn('Failed to load UI state:', e); return null; } } /** * Clear persisted state */ public clear(): void { this.removeItem(this.config.storageKey); } /** * Check if state exists */ public hasState(): boolean { return this.getItem(this.config.storageKey) !== null; } /** * Get state age in milliseconds */ public getStateAge(): number | null { const state = this.load(); if (!state?.timestamp) return null; return Date.now() - state.timestamp; } /** * Update specific state properties */ public update(updates: Partial): void { const currentState = this.load() || {}; this.save({ ...currentState, ...updates }); } /** * Get the storage key being used */ public getStorageKey(): string { return this.config.storageKey; } // Storage abstraction methods private getItem(key: string): string | null { switch (this.config.storage) { case 'localStorage': try { return localStorage.getItem(key); } catch { return null; } case 'sessionStorage': try { return sessionStorage.getItem(key); } catch { return null; } case 'memory': return this.memoryStorage.get(key) || null; default: return null; } } private setItem(key: string, value: string): void { switch (this.config.storage) { case 'localStorage': try { localStorage.setItem(key, value); } catch (e) { console.warn('Failed to save to localStorage:', e); } break; case 'sessionStorage': try { sessionStorage.setItem(key, value); } catch (e) { console.warn('Failed to save to sessionStorage:', e); } break; case 'memory': this.memoryStorage.set(key, value); break; } } private removeItem(key: string): void { switch (this.config.storage) { case 'localStorage': try { localStorage.removeItem(key); } catch { // Ignore } break; case 'sessionStorage': try { sessionStorage.removeItem(key); } catch { // Ignore } break; case 'memory': this.memoryStorage.delete(key); break; } } }