import { useState } from "react"; // Central registry of every localStorage key the app uses. One place to see them all, rename // safely, and avoid collisions — instead of `"siftlode.x"` string literals scattered across // files. Parametric (per-user) keys are functions. All keys are namespaced `siftlode.*`. export const LS = { theme: "siftlode.theme", sidebarLayout: "siftlode.sidebarLayout", hints: "siftlode.hints", lang: "siftlode.lang", filters: "siftlode.filters", // Mirror of the default saved view's filters, so it can be applied synchronously on load // (before the async saved-views query resolves). Kept in sync by SavedViewsWidget. defaultViewFilters: "siftlode.defaultViewFilters", page: "siftlode.page", perfMode: "siftlode.perfMode", channelFilter: "siftlode.channelFilter", channelsView: "siftlode.channelsView", settingsTab: "siftlode.settingsTab", statsTab: "siftlode.statsTab", adminUsersTab: "siftlode.adminUsersTab", navCollapsed: "siftlode.navCollapsed", filterCollapsed: "siftlode.filterCollapsed", playlist: "siftlode.playlist", plSort: "siftlode.plSort", notifHistory: "siftlode.notifications", notifSettings: "siftlode.notifSettings", onboardingDismissed: "siftlode.onboarding.dismissed", seenVersion: "siftlode.seenVersion", chatDock: (userId: number) => `siftlode.chatDock.${userId}`, } as const; // --- typed read/write helpers (do the JSON + try/catch once) ---------------------------- /** Read an object-shaped value merged over `defaults` (so missing/added keys are tolerated), * falling back to `defaults` on missing or corrupt data. */ export function readMerged(key: string, defaults: T): T { try { return { ...defaults, ...JSON.parse(localStorage.getItem(key) || "{}") }; } catch { return { ...defaults }; } } /** Read any JSON value (arrays/scalars), falling back on missing or corrupt data. */ export function readJSON(key: string, fallback: T): T { try { const raw = localStorage.getItem(key); return raw == null ? fallback : (JSON.parse(raw) as T); } catch { return fallback; } } /** Persist a value as JSON, swallowing quota/serialization errors. */ export function writeJSON(key: string, value: unknown): void { try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* ignore quota / serialization errors */ } } // --- reactive persisted string state ---------------------------------------------------- /** A `useState` whose string value is mirrored to localStorage, so it survives a reload (F5). * Validation is deferred to the caller (clamp at render) so it can be used before an * async-loaded option list is known, keeping hook order stable. Generalizes the per-component * "persisted active tab" pattern (Settings, Stats, admin pages, the channel filter…). */ export function usePersistedState( key: string, fallback = "", ): [string, (value: string) => void] { const [value, setValue] = useState(() => localStorage.getItem(key) ?? fallback); const set = (next: string) => { setValue(next); try { localStorage.setItem(key, next); } catch { /* ignore */ } }; return [value, set]; }