feat: add Zustand stores with undo/redo and debounced persistence
- App store: theme, view routing, board list, settings - Board store: all card/column/label/checklist/attachment mutations - zundo temporal middleware for undo/redo (50 step limit) - Debounced saves (500ms) with immediate flush on close
This commit is contained in:
69
src/stores/app-store.ts
Normal file
69
src/stores/app-store.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { create } from "zustand";
|
||||
import type { AppSettings } from "@/types/settings";
|
||||
import type { BoardMeta } from "@/types/board";
|
||||
import { loadSettings, saveSettings, listBoards, ensureDataDirs } from "@/lib/storage";
|
||||
|
||||
export type View = { type: "board-list" } | { type: "board"; boardId: string };
|
||||
|
||||
interface AppState {
|
||||
settings: AppSettings;
|
||||
boards: BoardMeta[];
|
||||
view: View;
|
||||
initialized: boolean;
|
||||
|
||||
init: () => Promise<void>;
|
||||
setTheme: (theme: AppSettings["theme"]) => void;
|
||||
setView: (view: View) => void;
|
||||
refreshBoards: () => Promise<void>;
|
||||
addRecentBoard: (boardId: string) => void;
|
||||
}
|
||||
|
||||
function applyTheme(theme: AppSettings["theme"]): void {
|
||||
const root = document.documentElement;
|
||||
if (theme === "system") {
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
root.classList.toggle("dark", prefersDark);
|
||||
} else {
|
||||
root.classList.toggle("dark", theme === "dark");
|
||||
}
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>((set, get) => ({
|
||||
settings: { theme: "system", dataDirectory: null, recentBoardIds: [] },
|
||||
boards: [],
|
||||
view: { type: "board-list" },
|
||||
initialized: false,
|
||||
|
||||
init: async () => {
|
||||
await ensureDataDirs();
|
||||
const settings = await loadSettings();
|
||||
const boards = await listBoards();
|
||||
set({ settings, boards, initialized: true });
|
||||
applyTheme(settings.theme);
|
||||
},
|
||||
|
||||
setTheme: (theme) => {
|
||||
const settings = { ...get().settings, theme };
|
||||
set({ settings });
|
||||
saveSettings(settings);
|
||||
applyTheme(theme);
|
||||
},
|
||||
|
||||
setView: (view) => set({ view }),
|
||||
|
||||
refreshBoards: async () => {
|
||||
const boards = await listBoards();
|
||||
set({ boards });
|
||||
},
|
||||
|
||||
addRecentBoard: (boardId) => {
|
||||
const settings = get().settings;
|
||||
const recent = [
|
||||
boardId,
|
||||
...settings.recentBoardIds.filter((id) => id !== boardId),
|
||||
].slice(0, 10);
|
||||
const updated = { ...settings, recentBoardIds: recent };
|
||||
set({ settings: updated });
|
||||
saveSettings(updated);
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user