113 lines
3.1 KiB
TypeScript
113 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { create } from "zustand";
|
|
|
|
export type ThemeMode = "system" | "light" | "dark";
|
|
|
|
type PersistedPreferences = {
|
|
theme: ThemeMode;
|
|
showStructure: boolean;
|
|
spellCheck: boolean;
|
|
flightMode: boolean;
|
|
};
|
|
|
|
const STORAGE_KEY = "mnote:preferences:v1";
|
|
|
|
const loadPersisted = (): PersistedPreferences | null => {
|
|
if (typeof window === "undefined") return null;
|
|
try {
|
|
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) return null;
|
|
const parsed = JSON.parse(raw) as Partial<PersistedPreferences>;
|
|
const theme = parsed.theme === "light" || parsed.theme === "dark" || parsed.theme === "system" ? parsed.theme : "system";
|
|
return {
|
|
theme,
|
|
showStructure: typeof parsed.showStructure === "boolean" ? parsed.showStructure : false,
|
|
spellCheck: typeof parsed.spellCheck === "boolean" ? parsed.spellCheck : false,
|
|
flightMode: typeof parsed.flightMode === "boolean" ? parsed.flightMode : false,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const persist = (value: PersistedPreferences) => {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
};
|
|
|
|
interface AppPreferencesState extends PersistedPreferences {
|
|
hydrated: boolean;
|
|
hydrate: () => void;
|
|
setTheme: (theme: ThemeMode) => void;
|
|
setShowStructure: (showStructure: boolean) => void;
|
|
setSpellCheck: (spellCheck: boolean) => void;
|
|
setFlightMode: (flightMode: boolean) => void;
|
|
}
|
|
|
|
export const useAppPreferencesStore = create<AppPreferencesState>((set, get) => ({
|
|
hydrated: false,
|
|
theme: "system",
|
|
showStructure: false,
|
|
spellCheck: false,
|
|
flightMode: false,
|
|
hydrate: () => {
|
|
const current = get();
|
|
if (current.hydrated) return;
|
|
const persisted = loadPersisted();
|
|
if (persisted) {
|
|
set({ ...persisted, hydrated: true });
|
|
} else {
|
|
set({ hydrated: true });
|
|
}
|
|
},
|
|
setTheme: (theme) =>
|
|
set((state) => {
|
|
const next = { ...state, theme };
|
|
persist({
|
|
theme: next.theme,
|
|
showStructure: next.showStructure,
|
|
spellCheck: next.spellCheck,
|
|
flightMode: next.flightMode,
|
|
});
|
|
return next;
|
|
}),
|
|
setShowStructure: (showStructure) =>
|
|
set((state) => {
|
|
const next = { ...state, showStructure };
|
|
persist({
|
|
theme: next.theme,
|
|
showStructure: next.showStructure,
|
|
spellCheck: next.spellCheck,
|
|
flightMode: next.flightMode,
|
|
});
|
|
return next;
|
|
}),
|
|
setSpellCheck: (spellCheck) =>
|
|
set((state) => {
|
|
const next = { ...state, spellCheck };
|
|
persist({
|
|
theme: next.theme,
|
|
showStructure: next.showStructure,
|
|
spellCheck: next.spellCheck,
|
|
flightMode: next.flightMode,
|
|
});
|
|
return next;
|
|
}),
|
|
setFlightMode: (flightMode) =>
|
|
set((state) => {
|
|
const next = { ...state, flightMode };
|
|
persist({
|
|
theme: next.theme,
|
|
showStructure: next.showStructure,
|
|
spellCheck: next.spellCheck,
|
|
flightMode: next.flightMode,
|
|
});
|
|
return next;
|
|
}),
|
|
}));
|