Initial commit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"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;
|
||||
}
|
||||
Reference in New Issue
Block a user