storebound/apps/desktop/src/prefs.ts

42 lines
1.9 KiB
TypeScript

// Per-device UI preferences (theme, motion, language, onboarding seen). Only conveniences live in localStorage;
// nothing about projects or accounts.
export type Theme = 'system' | 'light' | 'dark';
export type Motion = 'system' | 'reduce';
export type Lang = 'system' | 'en' | 'tr';
export interface Prefs { theme: Theme; motion: Motion; lang: Lang; onboarded: boolean }
export const DEFAULT_PREFS: Prefs = { theme: 'system', motion: 'system', lang: 'system', onboarded: false };
const KEY = 'storebound.prefs.v1';
export function parsePrefs(raw: string | null): Prefs {
try {
const v = JSON.parse(raw ?? '{}') as Partial<Prefs>;
return {
theme: v.theme === 'light' || v.theme === 'dark' ? v.theme : 'system',
motion: v.motion === 'reduce' ? 'reduce' : 'system',
lang: v.lang === 'en' || v.lang === 'tr' ? v.lang : 'system',
onboarded: v.onboarded === true,
};
} catch { return { ...DEFAULT_PREFS }; }
}
export function loadPrefs(): Prefs {
try { return parsePrefs(localStorage.getItem(KEY)); } catch { return { ...DEFAULT_PREFS }; }
}
export function savePrefs(p: Prefs): void {
try { localStorage.setItem(KEY, JSON.stringify(p)); } catch { /* private mode: keep it for this run only */ }
}
/** Puts the preferences on <html>: data-theme (light / dark, absent = follow the system), data-motion and lang. */
export function applyPrefs(p: Prefs, root: { dataset: DOMStringMap; lang?: string } = document.documentElement, systemLang = 'en'): void {
if (p.theme === 'system') delete root.dataset.theme; else root.dataset.theme = p.theme;
if (p.motion === 'reduce') root.dataset.motion = 'reduce'; else delete root.dataset.motion;
root.lang = resolveLang(p.lang, systemLang);
}
/** The language to show: the preference, else the system's when we have it (tr), else English. */
export function resolveLang(l: Lang, systemLang: string): 'en' | 'tr' {
if (l !== 'system') return l;
return /^tr\b/i.test(systemLang) ? 'tr' : 'en';
}