useClockStore.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { create } from "zustand";
  2. import type { ClockBlock, ClockRow, Signal } from "../assets/ClockTimeline/model";
  3. export const defaultClockSignal: Signal = {
  4. type: "virtual-signal",
  5. name: "signal-clock",
  6. subgroup: "pictographs",
  7. icon: "virtual-signal/signal-clock.png",
  8. order: "p[clock]",
  9. };
  10. interface ClockState {
  11. // Global Settings
  12. duration: number;
  13. pixelsPerTick: number;
  14. setDuration: (duration: number) => void;
  15. setZoom: (val: number) => void;
  16. clockSignal: Signal;
  17. setClockSignal: (signal: Signal | null) => void;
  18. // Normalized Data Models
  19. rows: Record<string, ClockRow>;
  20. rowOrder: string[]; // Maintains vertical rendering order
  21. blocks: Record<string, ClockBlock>;
  22. // UI State
  23. selectedBlockIds: Set<string>;
  24. alignmentTick: number | null;
  25. setAlignmentTick: (tick: number | null) => void;
  26. // --- Actions ---
  27. loadState: (state: Partial<ClockState>) => void;
  28. // Row Management
  29. addRow: (row: ClockRow) => void;
  30. updateRow: (id: string, patch: Partial<ClockRow>) => void;
  31. removeRow: (id: string) => void;
  32. // Block Management
  33. addBlocks: (newBlocks: ClockBlock[]) => void;
  34. updateBlock: (id: string, patch: Partial<ClockBlock>) => void;
  35. // High-performance batch update for dragging multiple blocks at once
  36. moveBlocks: (updates: Record<string, { start: number }>) => void;
  37. removeBlocks: (ids: string[]) => void;
  38. // Selection
  39. selectBlocks: (ids: Set<string> | ((prev: Set<string>) => Set<string>)) => void;
  40. clearSelection: () => void;
  41. }
  42. export const useClockStore = create<ClockState>((set) => ({
  43. duration: 256,
  44. pixelsPerTick: 4,
  45. clockSignal: defaultClockSignal, // Set your default signal here
  46. rows: {},
  47. rowOrder: [],
  48. blocks: {},
  49. selectedBlockIds: new Set(),
  50. alignmentTick: null,
  51. loadState: (state: Partial<ClockState>) => set(() => ({ ...state })),
  52. setDuration: (duration) => set({ duration }),
  53. setZoom: (val) => set({ pixelsPerTick: val }),
  54. setClockSignal: (clockSignal) => {
  55. if (clockSignal) set({ clockSignal });
  56. },
  57. setAlignmentTick: (tick) => set({ alignmentTick: tick }),
  58. addRow: (row) =>
  59. set((state) => ({
  60. rows: { ...state.rows, [row.id]: row },
  61. rowOrder: [...state.rowOrder, row.id],
  62. })),
  63. updateRow: (id, patch) =>
  64. set((state) => ({
  65. rows: {
  66. ...state.rows,
  67. [id]: { ...state.rows[id], ...patch },
  68. },
  69. })),
  70. removeRow: (id) =>
  71. set((state) => {
  72. const newRows = { ...state.rows };
  73. delete newRows[id];
  74. // Also clean up orphan blocks
  75. const newBlocks = Object.fromEntries(Object.entries(state.blocks).filter(([_, block]) => block.rowId !== id));
  76. return {
  77. rows: newRows,
  78. rowOrder: state.rowOrder.filter((rowId) => rowId !== id),
  79. blocks: newBlocks,
  80. };
  81. }),
  82. addBlocks: (newBlocks) =>
  83. set((state) => {
  84. const blockMap = { ...state.blocks };
  85. newBlocks.forEach((b) => {
  86. blockMap[b.id] = b;
  87. });
  88. return { blocks: blockMap };
  89. }),
  90. updateBlock: (id, patch) =>
  91. set((state) => ({
  92. blocks: {
  93. ...state.blocks,
  94. [id]: { ...state.blocks[id], ...patch },
  95. },
  96. })),
  97. moveBlocks: (updates) =>
  98. set((state) => {
  99. const nextBlocks = { ...state.blocks };
  100. for (const [id, patch] of Object.entries(updates)) {
  101. if (nextBlocks[id]) {
  102. nextBlocks[id] = { ...nextBlocks[id], ...patch };
  103. }
  104. }
  105. return { blocks: nextBlocks };
  106. }),
  107. removeBlocks: (ids) =>
  108. set((state) => {
  109. const newBlocks = { ...state.blocks };
  110. ids.forEach((id) => delete newBlocks[id]);
  111. const newSelection = new Set(state.selectedBlockIds);
  112. ids.forEach((id) => newSelection.delete(id));
  113. return { blocks: newBlocks, selectedBlockIds: newSelection };
  114. }),
  115. selectBlocks: (idsOrUpdater) =>
  116. set((state) => ({
  117. selectedBlockIds: typeof idsOrUpdater === "function" ? idsOrUpdater(state.selectedBlockIds) : idsOrUpdater,
  118. })),
  119. clearSelection: () => set({ selectedBlockIds: new Set() }),
  120. }));