Files
dees-catalog/ts_web/elements/00group-appui/dees-appui-base/state.manager.ts

186 lines
4.6 KiB
TypeScript

import type { IStatePersistenceConfig, IAppUIState } from '../../interfaces/appconfig.js';
/**
* Manager for persisting and restoring UI state
*/
export class StateManager {
private config: Required<IStatePersistenceConfig>;
private memoryStorage: Map<string, string> = 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<IAppUIState>): 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<IAppUIState>): 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;
}
}
}