import { MathUtils } from "three"; import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; import { defaultGraphData } from "../../components/SimulationDashboard/data/defaultGraphData"; import { Block, UIElement, ExtendedCSSProperties, } from "../../types/exportedTypes"; interface SimulationDashboardStore { blocks: Block[]; selectedBlockId: string | null; selectedElementId: string | null; // Subscription management subscribe: (callback: () => void) => () => void; _notifySubscribers: () => void; saveBlocks: () => void; // Block operations addBlock: () => void; removeBlock: (blockId: string) => void; updateBlock: (blockId: string, updates: Partial) => void; clearBlocks: () => void; setBlocks: (blocks: Block[]) => void; // Block styling and positioning updateBlockStyle: (blockId: string, newStyle: React.CSSProperties) => void; updateBlockSize: (blockId: string, size: Size) => void; updateBlockPosition: (blockId: string, position: Position) => void; updateBlockPositionType: ( blockId: string, positionType: "relative" | "absolute" | "fixed" ) => void; updateBlockZIndex: (blockId: string, zIndex: number) => void; // Element operations addElement: (blockId: string, type: UIType, graphType?: GraphTypes) => void; removeElement: (blockId: string, elementId: string) => void; updateElement: ( blockId: string, elementId: string, updates: Partial ) => void; // Element styling and positioning updateElementStyle: ( blockId: string, elementId: string, newStyle: ExtendedCSSProperties ) => void; updateElementSize: (blockId: string, elementId: string, size: Size) => void; updateElementPosition: ( blockId: string, elementId: string, position: Position ) => void; updateElementPositionType: ( blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed" ) => void; updateElementZIndex: ( blockId: string, elementId: string, zIndex: number ) => void; // Element data operations updateElementData: ( blockId: string, elementId: string, updates: Partial ) => void; updateGraphData: ( blockId: string, elementId: string, newData: GraphDataPoint[] ) => void; updateGraphTitle: (blockId: string, elementId: string, title: string) => void; updateGraphType: ( blockId: string, elementId: string, graphType: GraphTypes ) => void; // Selection and hover management setSelectedBlock: (blockId: string | null) => void; setSelectedElement: (elementId: string | null) => void; // Element swapping swapElements: ( blockId: string, elementId1: string, elementId2: string ) => void; // Helper functions getBlockById: (blockId: string) => Block | undefined; getElementById: (blockId: string, elementId: string) => UIElement | undefined; getSelectedBlock: () => Block | undefined; getSelectedElement: () => UIElement | undefined; hasBlock: (blockId: string) => boolean; hasElement: (blockId: string, elementId: string) => boolean; } const subscribers = new Set<() => void>(); export const createSimulationDashboardStore = () => { return create()( immer((set, get) => ({ blocks: [], selectedBlockId: null, selectedElementId: null, subscribe: (callback: () => void) => { subscribers.add(callback); return () => { subscribers.delete(callback); }; }, _notifySubscribers: () => { subscribers.forEach((callback) => { try { callback(); } catch (error) { console.error("Error in store subscriber:", error); } }); }, saveBlocks: () => { get()._notifySubscribers(); }, // Block operations addBlock: () => { set((state) => { const newBlock: Block = { blockUuid: MathUtils.generateUUID(), style: { backgroundColor: "rgba(50, 50, 50, 0.8)", backdropFilter: "blur(10px)", padding: 10, borderRadius: 8, border: "1px solid rgba(255, 255, 255, 0.1)", position: "relative", minHeight: "200px", minWidth: "300px", }, elements: [], zIndex: 1, size: { width: 400, height: 300 }, position: { x: 0, y: 0 }, positionType: "relative", }; state.blocks.push(newBlock); }); }, removeBlock: (blockId) => { set((state) => { state.blocks = state.blocks.filter( (block) => block.blockUuid !== blockId ); if (state.selectedBlockId === blockId) { state.selectedBlockId = null; } }); }, updateBlock: (blockId, updates) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { Object.assign(block, updates); } }); }, clearBlocks: () => { set((state) => { state.blocks = []; state.selectedBlockId = null; state.selectedElementId = null; }); }, setBlocks: (blocks) => { set((state) => { state.blocks = blocks; }); }, // Block styling and positioning updateBlockStyle: (blockId, newStyle) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.style = { ...block.style, ...newStyle }; } }); }, updateBlockSize: (blockId, size) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.size = size; } }); }, updateBlockPosition: (blockId, position) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.position = position; } }); }, updateBlockPositionType: (blockId, positionType) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.positionType = positionType; } }); }, updateBlockZIndex: (blockId, zIndex) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.zIndex = zIndex; } }); }, // Element operations addElement: (blockId, type, graphType) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const newElement: UIElement = { elementUuid: MathUtils.generateUUID(), type: type, graphType, graphTitle: graphType ? `${ graphType.charAt(0).toUpperCase() + graphType.slice(1) } Chart` : undefined, style: type === "graph" ? { width: "100%", height: "100%", minHeight: "120px", color: "#ffffff", fontSize: 14, textAlign: "left" as const, backgroundColor: "rgba(0, 0, 0, 0.2)", borderRadius: "4px", padding: "8px", } : type === "label-value" ? { color: "#ffffff", fontSize: 14, textAlign: "left" as const, display: "flex", flexDirection: "column", gap: "4px", alignItems: "flex-start", justifyContent: "center", labelColor: "#ffffff", valueColor: "#ffffff", } : { color: "#ffffff", fontSize: 14, textAlign: "left" as const, }, positionType: "relative", position: { x: 0, y: 0 }, zIndex: 1, data: { key: MathUtils.generateUUID(), dataSource: "static", staticValue: type === "label-value" ? "Value" : type === "text" ? "Text" : "", label: type === "label-value" ? "Label" : undefined, }, graphData: type === "graph" ? defaultGraphData : undefined, size: type === "graph" ? { width: 400, height: 200 } : { width: 200, height: 60 }, }; block.elements.push(newElement); } }); }, removeElement: (blockId, elementId) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.elements = block.elements.filter( (el) => el.elementUuid !== elementId ); if (state.selectedElementId === elementId) { state.selectedElementId = null; } } }); }, updateElement: (blockId, elementId, updates) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { Object.assign(element, updates); } } }); }, // Element styling and positioning updateElementStyle: (blockId, elementId, newStyle) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.style = { ...element.style, ...newStyle }; } } }); }, updateElementSize: (blockId, elementId, size) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.size = size; } } }); }, updateElementPosition: (blockId, elementId, position) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.position = position; } } }); }, updateElementPositionType: (blockId, elementId, positionType) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.positionType = positionType; } } }); }, updateElementZIndex: (blockId, elementId, zIndex) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.zIndex = zIndex; } } }); }, // Element data operations updateElementData: (blockId, elementId, updates) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element?.data) { element.data = { ...element.data, ...updates }; } } }); }, updateGraphData: (blockId, elementId, newData) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.graphData = newData; } } }); }, updateGraphTitle: (blockId, elementId, title) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element) { element.graphTitle = title; } } }); }, updateGraphType: (blockId, elementId, graphType) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { const element = block.elements.find( (el) => el.elementUuid === elementId ); if (element && element.type === "graph") { element.graphType = graphType; element.graphTitle = `${ graphType.charAt(0).toUpperCase() + graphType.slice(1) } Chart`; element.graphData = defaultGraphData; } } }); }, // Selection and hover management setSelectedBlock: (blockId) => { set((state) => { state.selectedBlockId = blockId; // Clear element selection when selecting a block if (blockId) { state.selectedElementId = null; } }); }, setSelectedElement: (elementId) => { set((state) => { state.selectedElementId = elementId; }); }, // Element swapping swapElements: (blockId, elementId1, elementId2) => { set((state) => { const block = state.blocks.find((b) => b.blockUuid === blockId); if (block) { block.elements = block.elements.map((el) => { if (el.elementUuid === elementId1) { const targetElement = block.elements.find( (e) => e.elementUuid === elementId2 ); return targetElement ? { ...targetElement, elementUuid: elementId1 } : el; } if (el.elementUuid === elementId2) { const sourceElement = block.elements.find( (e) => e.elementUuid === elementId1 ); return sourceElement ? { ...sourceElement, elementUuid: elementId2 } : el; } return el; }); } }); }, // Helper functions getBlockById: (blockId) => { return get().blocks.find((b) => b.blockUuid === blockId); }, getElementById: (blockId, elementId) => { const block = get().blocks.find((b) => b.blockUuid === blockId); return block?.elements.find((el) => el.elementUuid === elementId); }, getSelectedBlock: () => { const { selectedBlockId, blocks } = get(); return selectedBlockId ? blocks.find((b) => b.blockUuid === selectedBlockId) : undefined; }, getSelectedElement: () => { const { selectedElementId, selectedBlockId, blocks } = get(); if (!selectedElementId || !selectedBlockId) return undefined; const block = blocks.find((b) => b.blockUuid === selectedBlockId); return block?.elements.find( (el) => el.elementUuid === selectedElementId ); }, hasBlock: (blockId) => { return get().blocks.some((b) => b.blockUuid === blockId); }, hasElement: (blockId, elementId) => { const block = get().blocks.find((b) => b.blockUuid === blockId); return ( block?.elements.some((el) => el.elementUuid === elementId) || false ); }, })) ); }; export type SimulationDashboardStoreType = ReturnType< typeof createSimulationDashboardStore >;