96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
"use client";
|
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
import type { AppSettings } from "@/lib/db/appSettings";
|
|
import { setBoolSetting, setNumberSetting, setColorSetting, setPaginationMode } from "@/app/actions/settings";
|
|
|
|
type BoolKey = "fadeTransitions" | "purgeFilesOnDelete" | "useRecycleBin";
|
|
type NumKey = "fadeDurationMs" | "trashRetentionDays" | "gridColumns" | "gridColumnsPortrait" | "supersededRetentionDays" | "coverPageSize";
|
|
type ColorKey = "accentPrimary" | "accentSecondary";
|
|
type PaginationModeKey = "paginationMode";
|
|
|
|
interface Ctx {
|
|
settings: AppSettings;
|
|
set(key: BoolKey, value: boolean): void;
|
|
set(key: NumKey, value: number): void;
|
|
set(key: ColorKey, value: string): void;
|
|
set(key: PaginationModeKey, value: AppSettings["paginationMode"]): void;
|
|
}
|
|
|
|
const SettingsCtx = createContext<Ctx | null>(null);
|
|
|
|
const NUM_KEYS = new Set<string>(["fadeDurationMs", "trashRetentionDays", "gridColumns", "gridColumnsPortrait", "supersededRetentionDays", "coverPageSize"]);
|
|
const COLOR_KEYS = new Set<string>(["accentPrimary", "accentSecondary"]);
|
|
const PAGINATION_MODE_KEYS = new Set<string>(["paginationMode"]);
|
|
|
|
const ACCENT_VARS: Record<ColorKey, [string, string]> = {
|
|
accentPrimary: ["--color-cyan", "--color-cyan-glow"],
|
|
accentSecondary: ["--color-violet", "--color-violet-glow"],
|
|
};
|
|
|
|
export function SettingsProvider({
|
|
initial,
|
|
children,
|
|
}: {
|
|
initial: AppSettings;
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [settings, setSettings] = useState<AppSettings>(initial);
|
|
|
|
// The server is the source of truth, but the parent layout passes the latest
|
|
// server values on every render. Keep our state in sync after server mutations.
|
|
useEffect(() => {
|
|
setSettings(initial);
|
|
}, [initial]);
|
|
|
|
useEffect(() => {
|
|
document.documentElement.dataset.fade = settings.fadeTransitions ? "on" : "off";
|
|
document.documentElement.style.setProperty("--fade-duration", `${settings.fadeDurationMs}ms`);
|
|
}, [settings.fadeTransitions, settings.fadeDurationMs]);
|
|
|
|
useEffect(() => {
|
|
const n = Math.max(2, Math.min(4, settings.gridColumns || 3));
|
|
document.documentElement.style.setProperty("--grid-cols", String(n));
|
|
}, [settings.gridColumns]);
|
|
|
|
useEffect(() => {
|
|
const n = Math.max(4, Math.min(10, settings.gridColumnsPortrait || 6));
|
|
document.documentElement.style.setProperty("--grid-cols-portrait", String(n));
|
|
}, [settings.gridColumnsPortrait]);
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement;
|
|
for (const [key, [base, glow]] of Object.entries(ACCENT_VARS) as [ColorKey, [string, string]][]) {
|
|
const value = settings[key];
|
|
if (value) {
|
|
root.style.setProperty(base, value);
|
|
root.style.setProperty(glow, value);
|
|
} else {
|
|
root.style.removeProperty(base);
|
|
root.style.removeProperty(glow);
|
|
}
|
|
}
|
|
}, [settings.accentPrimary, settings.accentSecondary]);
|
|
|
|
const set = useCallback((key: BoolKey | NumKey | ColorKey | PaginationModeKey, value: boolean | number | string) => {
|
|
setSettings((cur) => ({ ...cur, [key]: value }));
|
|
if (NUM_KEYS.has(key)) {
|
|
void setNumberSetting(key as NumKey, value as number);
|
|
} else if (COLOR_KEYS.has(key)) {
|
|
void setColorSetting(key as ColorKey, value as string);
|
|
} else if (PAGINATION_MODE_KEYS.has(key)) {
|
|
void setPaginationMode(value as AppSettings["paginationMode"]);
|
|
} else {
|
|
void setBoolSetting(key as BoolKey, value as boolean);
|
|
}
|
|
}, []) as Ctx["set"];
|
|
|
|
const value = useMemo<Ctx>(() => ({ settings, set }), [settings, set]);
|
|
return <SettingsCtx.Provider value={value}>{children}</SettingsCtx.Provider>;
|
|
}
|
|
|
|
export function useSettings() {
|
|
const ctx = useContext(SettingsCtx);
|
|
if (!ctx) throw new Error("useSettings must be used within SettingsProvider");
|
|
return ctx;
|
|
}
|