whiteboard-history.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. /**
  2. * Whiteboard History Store
  3. *
  4. * Lightweight in-memory store that saves snapshots of whiteboard elements
  5. * before destructive operations (clear, replace). Allows users to browse
  6. * and restore previous whiteboard states.
  7. *
  8. * History is per-session (not persisted to IndexedDB) to keep things simple.
  9. */
  10. import { create } from 'zustand';
  11. import type { PPTElement } from '@/lib/types/slides';
  12. import { elementFingerprint } from '@/lib/utils/element-fingerprint';
  13. export interface WhiteboardSnapshot {
  14. /** Deep copy of whiteboard elements at the time of capture */
  15. elements: PPTElement[];
  16. /** Timestamp when the snapshot was taken */
  17. timestamp: number;
  18. /** Cached fingerprint used for deduplication and no-op restore checks */
  19. fingerprint: string;
  20. }
  21. interface WhiteboardHistoryState {
  22. /** Stack of snapshots, newest last */
  23. snapshots: WhiteboardSnapshot[];
  24. /** Maximum number of snapshots to keep */
  25. maxSnapshots: number;
  26. // Actions
  27. /** Save a snapshot of the current whiteboard elements */
  28. pushSnapshot: (elements: PPTElement[]) => void;
  29. /** Get a snapshot by index */
  30. getSnapshot: (index: number) => WhiteboardSnapshot | null;
  31. /** Clear all history */
  32. clearHistory: () => void;
  33. }
  34. export const useWhiteboardHistoryStore = create<WhiteboardHistoryState>((set, get) => ({
  35. snapshots: [],
  36. maxSnapshots: 20,
  37. pushSnapshot: (elements) => {
  38. // Don't save empty snapshots
  39. if (!elements || elements.length === 0) return;
  40. const { snapshots } = get();
  41. const newFingerprint = elementFingerprint(elements);
  42. if (snapshots.some((s) => s.fingerprint === newFingerprint)) {
  43. return;
  44. }
  45. const snapshot: WhiteboardSnapshot = {
  46. elements: JSON.parse(JSON.stringify(elements)), // Deep copy
  47. timestamp: Date.now(),
  48. fingerprint: newFingerprint,
  49. };
  50. set((state) => {
  51. const newSnapshots = [...state.snapshots, snapshot];
  52. // Enforce limit: drop oldest snapshots first.
  53. if (newSnapshots.length > state.maxSnapshots) {
  54. return { snapshots: newSnapshots.slice(-state.maxSnapshots) };
  55. }
  56. return { snapshots: newSnapshots };
  57. });
  58. },
  59. getSnapshot: (index) => {
  60. const { snapshots } = get();
  61. return snapshots[index] ?? null;
  62. },
  63. clearHistory: () => set({ snapshots: [] }),
  64. }));