1"use client";
2
3
9import { create } from "zustand";
10import type { Block, BlockPatch, Presence } from "@/lib/types";
11
12interface BlockPatchState {
13
14 patches: Record<string, Record<string, BlockPatch>>;
15
16 upserts: Record<string, Record<string, Block>>;
17
18 deleted: Record<string, string[]>;
19
22 presence: Record<string, Record<string, Presence>>;
23 upsert: (pageId: string, blocks: Block[]) => void;
24 patch: (pageId: string, blockId: string, p: BlockPatch) => void;
25 delete: (pageId: string, blockIds: string[]) => void;
26
27 upsertPresence: (pageId: string, peer: Presence) => void;
28
29 removePresence: (pageId: string, userId: string) => void;
30
31 merged: (pageId: string, base: readonly Block[]) => Block[];
32
35 clearBlocks: (pageId: string) => void;
36
37 reset: (pageId: string) => void;
38}
39
40function applyPatch(block: Block, p: BlockPatch): Block {
41 return { ...block, ...p, properties: { ...block.properties, ...(p.properties ?? {}) } };
42}
43
44export const useBlockPatchStore = create<BlockPatchState>()((set, get) => ({
45 patches: {},
46 upserts: {},
47 deleted: {},
48 presence: {},
49
50 upsert: (pageId, blocks) => {
51 const cur = get().upserts[pageId] ?? {};
52 const next: Record<string, Block> = { ...cur };
53 for (const b of blocks) next[b.id] = b;
54 set({ upserts: { ...get().upserts, [pageId]: next } });
55 },
56
57 patch: (pageId, blockId, p) => {
58 const page = get().patches[pageId] ?? {};
59 set({
60 patches: { ...get().patches, [pageId]: { ...page, [blockId]: { ...page[blockId], ...p } } },
61 });
62 },
63
64 delete: (pageId, blockIds) => {
65 const cur = get().deleted[pageId] ?? [];
66 const page = get().upserts[pageId] ?? {};
67 const nextPage: Record<string, Block> = { ...page };
68 for (const id of blockIds) delete nextPage[id];
69 set({
70 deleted: { ...get().deleted, [pageId]: [...new Set([...cur, ...blockIds])] },
71 upserts: { ...get().upserts, [pageId]: nextPage },
72 });
73 },
74
75 upsertPresence: (pageId, peer) => {
76 const page = get().presence[pageId] ?? {};
77 set({ presence: { ...get().presence, [pageId]: { ...page, [peer.userId]: peer } } });
78 },
79
80 removePresence: (pageId, userId) => {
81 const page = get().presence[pageId];
82 if (page === undefined || page[userId] === undefined) return;
83 const next = { ...page };
84 delete next[userId];
85 set({ presence: { ...get().presence, [pageId]: next } });
86 },
87
88 merged: (pageId, base) => {
89 const { patches, upserts, deleted } = get();
90 const removed = new Set(deleted[pageId] ?? []);
91 const pagePatches = patches[pageId] ?? {};
92 const pageUpserts = upserts[pageId] ?? {};
93 const out: Block[] = [];
94 for (const b of base) {
95 if (removed.has(b.id)) continue;
96 out.push(pagePatches[b.id] ? applyPatch(b, pagePatches[b.id] as BlockPatch) : b);
97 }
98 for (const b of Object.values(pageUpserts)) {
99 if (removed.has(b.id)) continue;
100 out.push(pagePatches[b.id] ? applyPatch(b, pagePatches[b.id] as BlockPatch) : b);
101 }
102 return out;
103 },
104
105 clearBlocks: (pageId) => {
106 set({
107 patches: { ...get().patches, [pageId]: {} },
108 upserts: { ...get().upserts, [pageId]: {} },
109 deleted: { ...get().deleted, [pageId]: [] },
110 });
111 },
112
113 reset: (pageId) => {
114 set({
115 patches: { ...get().patches, [pageId]: {} },
116 upserts: { ...get().upserts, [pageId]: {} },
117 deleted: { ...get().deleted, [pageId]: [] },
118 presence: { ...get().presence, [pageId]: {} },
119 });
120 },
121}));
122