export const DESKTOP_RECENT_ROUTES_KEY = "ctms_desktop_recent_routes"; export const DESKTOP_FAVORITE_ROUTES_KEY = "ctms_desktop_favorite_routes"; export const DESKTOP_THEME_KEY = "ctms_desktop_theme"; export const DESKTOP_THEME_CHANGED_EVENT = "ctms:desktop-theme-changed"; export type DesktopThemePreference = "light" | "dark"; export interface DesktopRoutePreference { path: string; title: string; group?: string; updatedAt: string; } const MAX_RECENT_ROUTES = 8; const MAX_FAVORITE_ROUTES = 12; const DEFAULT_DESKTOP_THEME: DesktopThemePreference = "light"; const DESKTOP_THEME_ATTRIBUTE = "data-ctms-theme"; const isStorageAvailable = () => typeof window !== "undefined" && typeof window.localStorage !== "undefined"; const sanitizeRoutePreference = (value: Partial | null | undefined): DesktopRoutePreference | null => { const path = typeof value?.path === "string" ? value.path.trim() : ""; const title = typeof value?.title === "string" ? value.title.trim() : ""; if (!path.startsWith("/") || !title) return null; return { path, title: title.slice(0, 80), group: typeof value?.group === "string" ? value.group.slice(0, 40) : undefined, updatedAt: typeof value?.updatedAt === "string" ? value.updatedAt : new Date().toISOString(), }; }; const readRoutePreferences = (key: string): DesktopRoutePreference[] => { if (!isStorageAvailable()) return []; try { const raw = window.localStorage.getItem(key); if (!raw) return []; const parsed = JSON.parse(raw); if (!Array.isArray(parsed)) return []; return parsed .map((item) => sanitizeRoutePreference(item)) .filter(Boolean) as DesktopRoutePreference[]; } catch { return []; } }; const writeRoutePreferences = (key: string, routes: DesktopRoutePreference[]) => { if (!isStorageAvailable()) return; window.localStorage.setItem(key, JSON.stringify(routes)); }; const sanitizeDesktopThemePreference = (value: unknown): DesktopThemePreference => value === "dark" ? "dark" : DEFAULT_DESKTOP_THEME; export const readDesktopThemePreference = (): DesktopThemePreference => { if (!isStorageAvailable()) return DEFAULT_DESKTOP_THEME; return sanitizeDesktopThemePreference(window.localStorage.getItem(DESKTOP_THEME_KEY)); }; export const applyDesktopThemePreference = (theme: DesktopThemePreference = readDesktopThemePreference()): DesktopThemePreference => { const nextTheme = sanitizeDesktopThemePreference(theme); if (typeof document !== "undefined") { document.documentElement.setAttribute(DESKTOP_THEME_ATTRIBUTE, nextTheme); document.documentElement.style.colorScheme = nextTheme; } return nextTheme; }; export const setDesktopThemePreference = (theme: DesktopThemePreference): DesktopThemePreference => { const nextTheme = sanitizeDesktopThemePreference(theme); if (isStorageAvailable()) { window.localStorage.setItem(DESKTOP_THEME_KEY, nextTheme); } applyDesktopThemePreference(nextTheme); if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent(DESKTOP_THEME_CHANGED_EVENT, { detail: nextTheme })); } return nextTheme; }; export const readDesktopRecentRoutes = (): DesktopRoutePreference[] => readRoutePreferences(DESKTOP_RECENT_ROUTES_KEY); export const readDesktopFavoriteRoutes = (): DesktopRoutePreference[] => readRoutePreferences(DESKTOP_FAVORITE_ROUTES_KEY); export const recordDesktopRecentRoute = (route: Pick): DesktopRoutePreference[] => { const item = sanitizeRoutePreference({ ...route, updatedAt: new Date().toISOString() }); if (!item) return readDesktopRecentRoutes(); const next = [ item, ...readDesktopRecentRoutes().filter((existing) => existing.path !== item.path), ].slice(0, MAX_RECENT_ROUTES); writeRoutePreferences(DESKTOP_RECENT_ROUTES_KEY, next); return next; }; export const isDesktopFavoriteRoute = (path: string): boolean => readDesktopFavoriteRoutes().some((item) => item.path === path); export const toggleDesktopFavoriteRoute = ( route: Pick, ): DesktopRoutePreference[] => { const item = sanitizeRoutePreference({ ...route, updatedAt: new Date().toISOString() }); if (!item) return readDesktopFavoriteRoutes(); const current = readDesktopFavoriteRoutes(); const exists = current.some((existing) => existing.path === item.path); const next = exists ? current.filter((existing) => existing.path !== item.path) : [item, ...current].slice(0, MAX_FAVORITE_ROUTES); writeRoutePreferences(DESKTOP_FAVORITE_ROUTES_KEY, next); return next; };