b491b6a146
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
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<DesktopRoutePreference> | 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, "path" | "title" | "group">): 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, "path" | "title" | "group">,
|
|
): 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;
|
|
};
|