Files
ITWorx-Pulse-Public/apps/web/src/operatorWorkspace.ts
T
2026-09-30 01:14:48 +00:00

98 lines
3.6 KiB
TypeScript

export type WorkspaceItem = {
id: string;
label: string;
path: string;
kind: 'favorite' | 'view' | 'recent';
updatedAt: string;
};
type WorkspaceState = {
favorites: WorkspaceItem[];
views: WorkspaceItem[];
recent: WorkspaceItem[];
};
const STORAGE_KEY = 'pulse.operator.workspace.v1';
export const WORKSPACE_EVENT = 'pulse-workspace-changed';
const emptyState = (): WorkspaceState => ({ favorites: [], views: [], recent: [] });
function validItem(value: unknown): value is WorkspaceItem {
if (!value || typeof value !== 'object') return false;
const item = value as Partial<WorkspaceItem>;
return typeof item.id === 'string' && typeof item.label === 'string' && typeof item.path === 'string'
&& ['favorite', 'view', 'recent'].includes(String(item.kind)) && typeof item.updatedAt === 'string';
}
export function readWorkspaceState(): WorkspaceState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return emptyState();
const parsed = JSON.parse(raw) as Partial<WorkspaceState>;
return {
favorites: Array.isArray(parsed.favorites) ? parsed.favorites.filter(validItem).slice(0, 24) : [],
views: Array.isArray(parsed.views) ? parsed.views.filter(validItem).slice(0, 24) : [],
recent: Array.isArray(parsed.recent) ? parsed.recent.filter(validItem).slice(0, 12) : [],
};
} catch {
return emptyState();
}
}
function writeWorkspaceState(state: WorkspaceState): void {
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch {
return;
}
window.dispatchEvent(new CustomEvent(WORKSPACE_EVENT));
}
function timestamp(): string { return new Date().toISOString(); }
function cleanPath(path: string): string {
if (!path.startsWith('/')) return '/';
return path.slice(0, 2048);
}
function cleanLabel(label: string): string { return label.trim().slice(0, 160) || 'Pulse'; }
export function isFavorite(path: string): boolean {
const clean = cleanPath(path);
return readWorkspaceState().favorites.some((item) => item.path === clean);
}
export function toggleFavorite(label: string, path: string): boolean {
const state = readWorkspaceState();
const clean = cleanPath(path);
const existing = state.favorites.some((item) => item.path === clean);
const favorite: WorkspaceItem = { id: 'favorite:' + clean, label: cleanLabel(label), path: clean, kind: 'favorite', updatedAt: timestamp() };
state.favorites = existing
? state.favorites.filter((item) => item.path !== clean)
: [favorite, ...state.favorites].slice(0, 24);
writeWorkspaceState(state);
return !existing;
}
export function saveCurrentView(label: string, path: string): void {
const state = readWorkspaceState();
const clean = cleanPath(path);
const item: WorkspaceItem = { id: 'view:' + clean, label: cleanLabel(label), path: clean, kind: 'view', updatedAt: timestamp() };
state.views = [item, ...state.views.filter((value) => value.path !== clean)].slice(0, 24);
writeWorkspaceState(state);
}
export function removeSavedView(path: string): void {
const state = readWorkspaceState();
const clean = cleanPath(path);
state.views = state.views.filter((item) => item.path !== clean);
writeWorkspaceState(state);
}
export function recordRecent(label: string, path: string): void {
const state = readWorkspaceState();
const clean = cleanPath(path);
if (clean === '/404') return;
const item: WorkspaceItem = { id: 'recent:' + clean, label: cleanLabel(label), path: clean, kind: 'recent', updatedAt: timestamp() };
state.recent = [item, ...state.recent.filter((value) => value.path !== clean)].slice(0, 12);
writeWorkspaceState(state);
}