import { useCallback, useEffect, useState } from 'react'; import { load } from '@tauri-apps/plugin-store'; import { GameFilter, GameSort } from '../lib/types'; import { SETTINGS_FILE, SETTINGS_FILE_OPTIONS, UI_SETTINGS_KEY } from '../lib/store'; export type Density = 'compact' | 'normal' | 'large'; export type CoverAspect = 'box' | 'square' | 'banner'; export type BackgroundStyle = 'flat' | 'gradient' | 'animated'; export interface UISettings { accent: string; bg: BackgroundStyle; density: Density; aspect: CoverAspect; sort: GameSort; filter: GameFilter; } type StoredGameSort = GameSort | 'size'; type StoredUISettings = Partial & { sort: StoredGameSort }>; export const ACCENT_OPTIONS = [ { value: '#3b82f6', label: 'Blue' }, { value: '#22d3ee', label: 'Cyan' }, { value: '#a855f7', label: 'Violet' }, { value: '#22c55e', label: 'Green' }, { value: '#f59e0b', label: 'Amber' }, { value: '#ef4444', label: 'Red' }, ] as const; export const BG_OPTIONS: ReadonlyArray<{ value: BackgroundStyle; label: string }> = [ { value: 'flat', label: 'Flat' }, { value: 'gradient', label: 'Gradient' }, { value: 'animated', label: 'Animated' }, ]; export const DENSITY_OPTIONS: ReadonlyArray<{ value: Density; label: string }> = [ { value: 'compact', label: 'Compact' }, { value: 'normal', label: 'Normal' }, { value: 'large', label: 'Large' }, ]; export const ASPECT_OPTIONS: ReadonlyArray<{ value: CoverAspect; label: string }> = [ { value: 'box', label: 'Box-art' }, { value: 'square', label: 'Square' }, { value: 'banner', label: 'Banner' }, ]; export const DEFAULT_SETTINGS: UISettings = { accent: '#3b82f6', bg: 'gradient', density: 'normal', aspect: 'square', sort: 'status', filter: 'local', }; const sanitize = (raw: StoredUISettings | undefined): UISettings => ({ accent: raw?.accent ?? DEFAULT_SETTINGS.accent, bg: raw?.bg ?? DEFAULT_SETTINGS.bg, density: raw?.density ?? DEFAULT_SETTINGS.density, aspect: raw?.aspect ?? DEFAULT_SETTINGS.aspect, sort: sanitizeSort(raw?.sort), filter: raw?.filter ?? DEFAULT_SETTINGS.filter, }); const sanitizeSort = (sort: StoredGameSort | undefined): GameSort => ( sort === 'size' ? 'sizeDesc' : sort ?? DEFAULT_SETTINGS.sort ); export interface UseSettings { settings: UISettings; set: (key: K, value: UISettings[K]) => void; ready: boolean; } /** * Loads UI preferences from the Tauri persistent store once on mount and * writes every change back through it. Components only see a synchronous * `settings` snapshot; persistence is fire-and-forget. */ export const useSettings = (): UseSettings => { const [settings, setSettings] = useState(DEFAULT_SETTINGS); const [ready, setReady] = useState(false); useEffect(() => { let cancelled = false; const init = async () => { try { const store = await load(SETTINGS_FILE, SETTINGS_FILE_OPTIONS); const saved = await store.get(UI_SETTINGS_KEY); if (!cancelled) { setSettings(sanitize(saved ?? undefined)); } } catch (err) { console.error('Failed to load UI settings:', err); } finally { if (!cancelled) setReady(true); } }; void init(); return () => { cancelled = true; }; }, []); const set = useCallback((key: K, value: UISettings[K]) => { setSettings(prev => { const next = { ...prev, [key]: value }; void persist(next); return next; }); }, []); return { settings, set, ready }; }; const persist = async (settings: UISettings): Promise => { try { const store = await load(SETTINGS_FILE, SETTINGS_FILE_OPTIONS); await store.set(UI_SETTINGS_KEY, settings); } catch (err) { console.error('Failed to persist UI settings:', err); } };