ebeee2d90a
The library sort setting used `size` for largest-first sorting while the ascending option used `sizeAsc`. That made the pair asymmetric and left the current settings model carrying a legacy-looking key. Rename the current descending key to `sizeDesc` in the type, menu, and sort logic. Stored `size` values are normalized to `sizeDesc` on read, so existing users keep the same largest-first behavior while new writes use the explicit key. Test Plan: - deno task build - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just build - git diff --check Refs: local review feedback
127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
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<Omit<UISettings, 'sort'> & { 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: <K extends keyof UISettings>(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<UISettings>(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<StoredUISettings>(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(<K extends keyof UISettings>(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<void> => {
|
|
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);
|
|
}
|
|
};
|