feat: Refactor DashboardEditor and simulation store with peek methods for optimized block and element updates
This commit is contained in:
@@ -33,26 +33,30 @@ const DashboardEditor: React.FC = () => {
|
||||
setSelectedElement,
|
||||
addBlock,
|
||||
setBlocks,
|
||||
updateBlockPosition,
|
||||
updateElementPosition,
|
||||
updateBlockSize,
|
||||
updateElementSize,
|
||||
updateBlockStyle,
|
||||
updateBlockPositionType,
|
||||
updateBlockZIndex,
|
||||
addElement,
|
||||
updateElementStyle,
|
||||
updateElementPositionType,
|
||||
updateElementZIndex,
|
||||
updateElementData,
|
||||
updateGraphData,
|
||||
updateGraphTitle,
|
||||
updateGraphType,
|
||||
swapElements,
|
||||
removeBlock,
|
||||
removeElement,
|
||||
getBlockById,
|
||||
subscribe,
|
||||
updateBlock,
|
||||
// Peek methods
|
||||
peekAddBlock,
|
||||
peekRemoveBlock,
|
||||
peekAddElement,
|
||||
peekRemoveElement,
|
||||
peekUpdateBlockStyle,
|
||||
peekUpdateBlockSize,
|
||||
peekUpdateBlockPosition,
|
||||
peekUpdateBlockPositionType,
|
||||
peekUpdateBlockZIndex,
|
||||
peekUpdateElementStyle,
|
||||
peekUpdateElementSize,
|
||||
peekUpdateElementPosition,
|
||||
peekUpdateElementPositionType,
|
||||
peekUpdateElementZIndex,
|
||||
peekUpdateElementData,
|
||||
peekUpdateGraphData,
|
||||
peekUpdateGraphTitle,
|
||||
peekUpdateGraphType,
|
||||
peekSwapElements,
|
||||
} = simulationDashBoardStore();
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
@@ -83,9 +87,49 @@ const DashboardEditor: React.FC = () => {
|
||||
const currentBlock = blocks.find((b) => b.blockUuid === selectedBlock);
|
||||
const currentElement = currentBlock?.elements.find((el) => el.elementUuid === selectedElement);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("blocks: ", blocks);
|
||||
}, [blocks]);
|
||||
// Helper function to send updates to backend - only sends the specific block that changed
|
||||
const updateBackend = async (updatedBlock: Block) => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
try {
|
||||
const response = await upsetDashBoardBlocksApi({
|
||||
projectId,
|
||||
versionId: selectedVersion.versionId,
|
||||
blocks: [updatedBlock], // Send only the updated block
|
||||
});
|
||||
|
||||
if (response.data?.blocks) {
|
||||
// Update only the blocks that have success messages
|
||||
response.data.blocks.forEach((responseBlock: any) => {
|
||||
if (responseBlock.message === "Block updated successfully") {
|
||||
// Update the specific block in the store
|
||||
const { message, elements, ...blockData } = responseBlock;
|
||||
|
||||
// Process elements to remove their messages
|
||||
const cleanedElements =
|
||||
elements?.map((el: any) => {
|
||||
const { message: elementMessage, ...elementData } = el;
|
||||
return elementData;
|
||||
}) || [];
|
||||
|
||||
updateBlock(responseBlock.blockUuid, {
|
||||
...blockData,
|
||||
elements: cleanedElements,
|
||||
});
|
||||
} else if (responseBlock.message === "Block created successfully") {
|
||||
addBlock(responseBlock);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update backend:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get a specific block from peeked blocks
|
||||
const getBlockFromPeekedBlocks = (peekedBlocks: Block[], blockId: string): Block | undefined => {
|
||||
return peekedBlocks.find((b) => b.blockUuid === blockId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
@@ -96,31 +140,15 @@ const DashboardEditor: React.FC = () => {
|
||||
});
|
||||
}, [projectId, selectedVersion]);
|
||||
|
||||
const updateBackend = (blocks: Block[]) => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => {
|
||||
if (data.data?.blocks) {
|
||||
setBlocks(data.data.blocks);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = subscribe(() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
updateBackend(blocks);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [blocks, projectId, selectedVersion]);
|
||||
|
||||
useCallBackOnKey(
|
||||
() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
updateBackend(blocks);
|
||||
// For Ctrl+S, send all blocks
|
||||
upsetDashBoardBlocksApi({
|
||||
projectId,
|
||||
versionId: selectedVersion.versionId,
|
||||
blocks: blocks,
|
||||
});
|
||||
},
|
||||
"Ctrl+S",
|
||||
{ dependencies: [blocks, projectId, selectedVersion], noRepeat: true, allowOnInput: true }
|
||||
@@ -269,8 +297,8 @@ const DashboardEditor: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = (): void => {
|
||||
// Update state only on mouse up for elements
|
||||
const handleMouseUp = async (): Promise<void> => {
|
||||
// Update backend using peek methods, then update state from response
|
||||
if (draggingElement && selectedBlock && currentElement?.positionType === "absolute") {
|
||||
const blockElement = document.querySelector(`[data-block-id="${selectedBlock}"]`) as HTMLElement;
|
||||
const elementToDrag = document.querySelector(`[data-element-id="${draggingElement}"]`) as HTMLElement;
|
||||
@@ -280,11 +308,16 @@ const DashboardEditor: React.FC = () => {
|
||||
const x = parseFloat(computedStyle.left);
|
||||
const y = parseFloat(computedStyle.top);
|
||||
|
||||
updateElementPosition(selectedBlock, draggingElement, { x, y });
|
||||
// Use peek to get updated blocks and send only the affected block to backend
|
||||
const updatedBlocks = peekUpdateElementPosition(selectedBlock, draggingElement, { x, y });
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, selectedBlock);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update state only on mouse up for blocks
|
||||
// Update backend for block drag
|
||||
if (draggingBlock && currentBlock?.positionType && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) {
|
||||
const blockToDrag = document.querySelector(`[data-block-id="${draggingBlock}"]`) as HTMLElement;
|
||||
|
||||
@@ -293,11 +326,16 @@ const DashboardEditor: React.FC = () => {
|
||||
const x = parseFloat(computedStyle.left);
|
||||
const y = parseFloat(computedStyle.top);
|
||||
|
||||
updateBlockPosition(draggingBlock, { x, y });
|
||||
// Use peek to get updated blocks and send only the affected block to backend
|
||||
const updatedBlocks = peekUpdateBlockPosition(draggingBlock, { x, y });
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, draggingBlock);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update state only on mouse up for resizing
|
||||
// Update backend for element resize
|
||||
if (resizingElement && selectedBlock) {
|
||||
const elementToResize = document.querySelector(`[data-element-id="${resizingElement}"]`) as HTMLElement;
|
||||
if (elementToResize) {
|
||||
@@ -305,10 +343,16 @@ const DashboardEditor: React.FC = () => {
|
||||
const width = parseFloat(computedStyle.width);
|
||||
const height = parseFloat(computedStyle.height);
|
||||
|
||||
updateElementSize(selectedBlock, resizingElement, { width, height });
|
||||
// Use peek to get updated blocks and send only the affected block to backend
|
||||
const updatedBlocks = peekUpdateElementSize(selectedBlock, resizingElement, { width, height });
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, selectedBlock);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update backend for block resize
|
||||
if (resizingBlock) {
|
||||
const blockToResize = document.querySelector(`[data-block-id="${resizingBlock}"]`) as HTMLElement;
|
||||
if (blockToResize) {
|
||||
@@ -316,7 +360,12 @@ const DashboardEditor: React.FC = () => {
|
||||
const width = parseFloat(computedStyle.width);
|
||||
const height = parseFloat(computedStyle.height);
|
||||
|
||||
updateBlockSize(resizingBlock, { width, height });
|
||||
// Use peek to get updated blocks and send only the affected block to backend
|
||||
const updatedBlocks = peekUpdateBlockSize(resizingBlock, { width, height });
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, resizingBlock);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +391,19 @@ const DashboardEditor: React.FC = () => {
|
||||
return (
|
||||
<div ref={editorRef} className="dashboard-editor">
|
||||
{activeModule === "visualization" && (
|
||||
<ControlPanel editMode={editMode} setEditMode={setEditMode} addBlock={() => addBlock()} showDataModelPanel={showDataModelPanel} setShowDataModelPanel={setShowDataModelPanel} />
|
||||
<ControlPanel
|
||||
editMode={editMode}
|
||||
setEditMode={setEditMode}
|
||||
addBlock={async () => {
|
||||
const updatedBlocks = peekAddBlock();
|
||||
const newBlock = updatedBlocks[updatedBlocks.length - 1]; // Get the newly added block
|
||||
if (newBlock) {
|
||||
await updateBackend(newBlock);
|
||||
}
|
||||
}}
|
||||
showDataModelPanel={showDataModelPanel}
|
||||
setShowDataModelPanel={setShowDataModelPanel}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* BlockGrid */}
|
||||
@@ -350,8 +411,12 @@ const DashboardEditor: React.FC = () => {
|
||||
<div className="block-grid-container">
|
||||
<BlockGrid
|
||||
blocks={blocks}
|
||||
handleAddElement={(blockId, type, graphType) => {
|
||||
addElement(blockId, type, graphType);
|
||||
handleAddElement={async (blockId, type, graphType) => {
|
||||
const updatedBlocks = peekAddElement(blockId, type, graphType);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
editMode={editMode}
|
||||
selectedBlock={selectedBlock}
|
||||
@@ -367,7 +432,16 @@ const DashboardEditor: React.FC = () => {
|
||||
handleElementResizeStart={(elementId, event) => handleElementResizeStart(elementId, event, setResizingElement, setResizeStart)}
|
||||
handleBlockResizeStart={(blockId, event) => handleBlockResizeStart(blockId, event, setResizingBlock, setResizeStart)}
|
||||
handleSwapStart={(elementId, event) => handleSwapStart(elementId, event, setSwapSource, setShowSwapUI)}
|
||||
handleSwapTarget={(elementId, event) => handleSwapTarget(elementId, event, swapSource, selectedBlock, swapElements)}
|
||||
handleSwapTarget={async (elementId, event) => {
|
||||
if (!selectedBlock) return;
|
||||
handleSwapTarget(elementId, event, swapSource, selectedBlock, async (blockId, el1, el2) => {
|
||||
const updatedBlocks = peekSwapElements(blockId, el1, el2);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
});
|
||||
}}
|
||||
handleBlockDragStart={(blockId, event) => handleBlockDragStart(blockId, event, setDraggingBlock, setBlockDragOffset)}
|
||||
setShowElementDropdown={setShowElementDropdown}
|
||||
showElementDropdown={showElementDropdown}
|
||||
@@ -381,15 +455,52 @@ const DashboardEditor: React.FC = () => {
|
||||
blockEditorRef={blockEditorRef}
|
||||
currentBlock={currentBlock}
|
||||
selectedBlock={selectedBlock}
|
||||
updateBlockStyle={(blockId, style) => updateBlockStyle(blockId, style)}
|
||||
updateBlockSize={(blockId, size) => updateBlockSize(blockId, size)}
|
||||
updateBlockPosition={(blockId, position) => updateBlockPosition(blockId, position)}
|
||||
updateBlockPositionType={(blockId, positionType) => updateBlockPositionType(blockId, positionType)}
|
||||
updateBlockZIndex={(blockId, zIndex) => updateBlockZIndex(blockId, zIndex)}
|
||||
handleRemoveBlock={(blockId) => {
|
||||
updateBlockStyle={async (blockId, style) => {
|
||||
const updatedBlocks = peekUpdateBlockStyle(blockId, style);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateBlockSize={async (blockId, size) => {
|
||||
const updatedBlocks = peekUpdateBlockSize(blockId, size);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateBlockPosition={async (blockId, position) => {
|
||||
const updatedBlocks = peekUpdateBlockPosition(blockId, position);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateBlockPositionType={async (blockId, positionType) => {
|
||||
const updatedBlocks = peekUpdateBlockPositionType(blockId, positionType);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateBlockZIndex={async (blockId, zIndex) => {
|
||||
const updatedBlocks = peekUpdateBlockZIndex(blockId, zIndex);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
handleRemoveBlock={async (blockId) => {
|
||||
const block = getBlockById(blockId);
|
||||
if (!block) return;
|
||||
deleteDashBoardBlocksApi({ projectId: projectId!, versionId: selectedVersion!.versionId, blocks: [block] }).then((data) => {
|
||||
|
||||
try {
|
||||
const data = await deleteDashBoardBlocksApi({
|
||||
projectId: projectId!,
|
||||
versionId: selectedVersion!.versionId,
|
||||
blocks: [block],
|
||||
});
|
||||
|
||||
if (data.blocks.length > 0) {
|
||||
data.blocks.forEach((updatedBlock: any) => {
|
||||
if (updatedBlock.message === "Block deleted successfully") {
|
||||
@@ -397,7 +508,9 @@ const DashboardEditor: React.FC = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete block:", error);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -408,16 +521,76 @@ const DashboardEditor: React.FC = () => {
|
||||
currentElement={currentElement}
|
||||
selectedBlock={selectedBlock}
|
||||
selectedElement={selectedElement}
|
||||
updateElementStyle={(blockId, elementId, style) => updateElementStyle(blockId, elementId, style)}
|
||||
updateElementSize={(blockId, elementId, size) => updateElementSize(blockId, elementId, size)}
|
||||
updateElementPosition={(blockId, elementId, position) => updateElementPosition(blockId, elementId, position)}
|
||||
updateElementPositionType={(blockId, elementId, positionType) => updateElementPositionType(blockId, elementId, positionType)}
|
||||
updateElementZIndex={(blockId, elementId, zIndex) => updateElementZIndex(blockId, elementId, zIndex)}
|
||||
updateElementData={(blockId, elementId, updates) => updateElementData(blockId, elementId, updates)}
|
||||
updateGraphData={(blockId, elementId, newData) => updateGraphData(blockId, elementId, newData)}
|
||||
updateGraphTitle={(blockId, elementId, title) => updateGraphTitle(blockId, elementId, title)}
|
||||
updateGraphType={(blockId, elementId, type) => updateGraphType(blockId, elementId, type)}
|
||||
handleRemoveElement={(blockId, elementId) => removeElement(blockId, elementId)}
|
||||
updateElementStyle={async (blockId, elementId, style) => {
|
||||
const updatedBlocks = peekUpdateElementStyle(blockId, elementId, style);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateElementSize={async (blockId, elementId, size) => {
|
||||
const updatedBlocks = peekUpdateElementSize(blockId, elementId, size);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateElementPosition={async (blockId, elementId, position) => {
|
||||
const updatedBlocks = peekUpdateElementPosition(blockId, elementId, position);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateElementPositionType={async (blockId, elementId, positionType) => {
|
||||
const updatedBlocks = peekUpdateElementPositionType(blockId, elementId, positionType);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateElementZIndex={async (blockId, elementId, zIndex) => {
|
||||
const updatedBlocks = peekUpdateElementZIndex(blockId, elementId, zIndex);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateElementData={async (blockId, elementId, updates) => {
|
||||
const updatedBlocks = peekUpdateElementData(blockId, elementId, updates);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateGraphData={async (blockId, elementId, newData) => {
|
||||
const updatedBlocks = peekUpdateGraphData(blockId, elementId, newData);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateGraphTitle={async (blockId, elementId, title) => {
|
||||
const updatedBlocks = peekUpdateGraphTitle(blockId, elementId, title);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
updateGraphType={async (blockId, elementId, type) => {
|
||||
const updatedBlocks = peekUpdateGraphType(blockId, elementId, type);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
handleRemoveElement={async (blockId, elementId) => {
|
||||
const updatedBlocks = peekRemoveElement(blockId, elementId);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
await updateBackend(updatedBlock);
|
||||
}
|
||||
}}
|
||||
setSwapSource={setSwapSource}
|
||||
setShowSwapUI={setShowSwapUI}
|
||||
dataModelManager={dataModelManager}
|
||||
|
||||
@@ -115,7 +115,6 @@ export const handleBlockClick = (
|
||||
setSelectedBlock(blockId);
|
||||
setSelectedElement(null);
|
||||
setShowSwapUI(false);
|
||||
console.log('showElementDropdown: ', showElementDropdown);
|
||||
if (showElementDropdown && showElementDropdown !== blockId) {
|
||||
setShowElementDropdown(blockId);
|
||||
}
|
||||
@@ -135,8 +134,8 @@ export const handleElementClick = (
|
||||
event.stopPropagation();
|
||||
if (editMode) {
|
||||
setSelectedElement(elementId);
|
||||
setSelectedBlock(blockId);
|
||||
setShowSwapUI(false);
|
||||
// setSelectedBlock(blockId);
|
||||
// setShowSwapUI(false);
|
||||
// setShowElementDropdown(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ interface SimulationDashboardStore {
|
||||
setSelectedElement: (elementId: string | null) => void;
|
||||
|
||||
// Block operations
|
||||
addBlock: () => void;
|
||||
addBlock: (newBlock : Block) => void;
|
||||
removeBlock: (blockId: string) => void;
|
||||
updateBlock: (blockId: string, updates: Partial<Block>) => void;
|
||||
clearBlocks: () => void;
|
||||
@@ -53,6 +53,29 @@ interface SimulationDashboardStore {
|
||||
// Element swapping
|
||||
swapElements: (blockId: string, elementId1: string, elementId2: string) => void;
|
||||
|
||||
// Peek operations (get updated value without setting state)
|
||||
peekAddBlock: () => Block[];
|
||||
peekRemoveBlock: (blockId: string) => Block[];
|
||||
peekUpdateBlock: (blockId: string, updates: Partial<Block>) => Block[];
|
||||
peekUpdateBlockStyle: (blockId: string, newStyle: React.CSSProperties) => Block[];
|
||||
peekUpdateBlockSize: (blockId: string, size: Size) => Block[];
|
||||
peekUpdateBlockPosition: (blockId: string, position: Position) => Block[];
|
||||
peekUpdateBlockPositionType: (blockId: string, positionType: "relative" | "absolute" | "fixed") => Block[];
|
||||
peekUpdateBlockZIndex: (blockId: string, zIndex: number) => Block[];
|
||||
peekAddElement: (blockId: string, type: UIType, graphType?: GraphTypes, dataType?: DataType) => Block[];
|
||||
peekRemoveElement: (blockId: string, elementId: string) => Block[];
|
||||
peekUpdateElement: (blockId: string, elementId: string, updates: Partial<UIElement>) => Block[];
|
||||
peekUpdateElementStyle: (blockId: string, elementId: string, newStyle: ExtendedCSSProperties) => Block[];
|
||||
peekUpdateElementSize: (blockId: string, elementId: string, size: Size) => Block[];
|
||||
peekUpdateElementPosition: (blockId: string, elementId: string, position: Position) => Block[];
|
||||
peekUpdateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => Block[];
|
||||
peekUpdateElementZIndex: (blockId: string, elementId: string, zIndex: number) => Block[];
|
||||
peekUpdateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => Block[];
|
||||
peekUpdateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => Block[];
|
||||
peekUpdateGraphTitle: (blockId: string, elementId: string, title: string) => Block[];
|
||||
peekUpdateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => Block[];
|
||||
peekSwapElements: (blockId: string, elementId1: string, elementId2: string) => Block[];
|
||||
|
||||
// Helper functions
|
||||
getBlockById: (blockId: string) => Block | undefined;
|
||||
getElementById: (blockId: string, elementId: string) => UIElement | undefined;
|
||||
@@ -64,6 +87,11 @@ interface SimulationDashboardStore {
|
||||
|
||||
const subscribers = new Set<() => void>();
|
||||
|
||||
// Helper function to deep clone blocks
|
||||
const cloneBlocks = (blocks: Block[]): Block[] => {
|
||||
return JSON.parse(JSON.stringify(blocks));
|
||||
};
|
||||
|
||||
export const createSimulationDashboardStore = () => {
|
||||
return create<SimulationDashboardStore>()(
|
||||
immer((set, get) => ({
|
||||
@@ -96,7 +124,6 @@ export const createSimulationDashboardStore = () => {
|
||||
setSelectedBlock: (blockId) => {
|
||||
set((state) => {
|
||||
state.selectedBlock = blockId;
|
||||
// Clear element selection when selecting a block
|
||||
if (blockId) {
|
||||
state.selectedElement = null;
|
||||
}
|
||||
@@ -110,26 +137,8 @@ export const createSimulationDashboardStore = () => {
|
||||
},
|
||||
|
||||
// Block operations
|
||||
addBlock: () => {
|
||||
addBlock: (newBlock) => {
|
||||
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);
|
||||
});
|
||||
},
|
||||
@@ -292,7 +301,6 @@ export const createSimulationDashboardStore = () => {
|
||||
commonValue: "metric-1",
|
||||
};
|
||||
} else {
|
||||
// Default to single-machine
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "single-machine" as const,
|
||||
@@ -334,7 +342,7 @@ export const createSimulationDashboardStore = () => {
|
||||
break;
|
||||
|
||||
default:
|
||||
return; // Should not happen
|
||||
return;
|
||||
}
|
||||
|
||||
block.elements.push(newElement);
|
||||
@@ -471,7 +479,6 @@ export const createSimulationDashboardStore = () => {
|
||||
if (element && element.type === "graph") {
|
||||
element.graphType = graphType;
|
||||
element.graphTitle = `${graphType.charAt(0).toUpperCase() + graphType.slice(1)} Chart`;
|
||||
|
||||
element.graphData = defaultGraphData;
|
||||
}
|
||||
}
|
||||
@@ -498,6 +505,367 @@ export const createSimulationDashboardStore = () => {
|
||||
});
|
||||
},
|
||||
|
||||
// Peek operations - return updated blocks without setting state
|
||||
peekAddBlock: () => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
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",
|
||||
};
|
||||
blocks.push(newBlock);
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekRemoveBlock: (blockId) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
return blocks.filter((block) => block.blockUuid !== blockId);
|
||||
},
|
||||
|
||||
peekUpdateBlock: (blockId, updates) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
Object.assign(block, updates);
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateBlockStyle: (blockId, newStyle) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
block.style = { ...block.style, ...newStyle };
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateBlockSize: (blockId, size) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
block.size = size;
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateBlockPosition: (blockId, position) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
block.position = position;
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateBlockPositionType: (blockId, positionType) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
block.positionType = positionType;
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateBlockZIndex: (blockId, zIndex) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
block.zIndex = zIndex;
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekAddElement: (blockId, type, graphType, dataType?: DataType) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (!block) return blocks;
|
||||
|
||||
let newElement: UIElement;
|
||||
|
||||
const commonProps = {
|
||||
elementUuid: MathUtils.generateUUID(),
|
||||
positionType: "relative" as const,
|
||||
position: { x: 0, y: 0 },
|
||||
zIndex: 1,
|
||||
data: {
|
||||
key: MathUtils.generateUUID(),
|
||||
dataSource: "static" as const,
|
||||
staticValue: "",
|
||||
label: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case "label-value":
|
||||
newElement = {
|
||||
...commonProps,
|
||||
type: "label-value",
|
||||
title: "Label Value",
|
||||
dataSource: "machine-1",
|
||||
dataValue: "metric-1",
|
||||
style: {
|
||||
color: "#ffffff",
|
||||
fontSize: 14,
|
||||
textAlign: "left" as const,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "4px",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "center",
|
||||
labelColor: "#ffffff",
|
||||
valueColor: "#ffffff",
|
||||
},
|
||||
data: {
|
||||
...commonProps.data,
|
||||
staticValue: "Value",
|
||||
label: "Label",
|
||||
},
|
||||
size: { width: 200, height: 60 },
|
||||
};
|
||||
break;
|
||||
|
||||
case "graph":
|
||||
const baseGraphProps = {
|
||||
...commonProps,
|
||||
type: "graph" as const,
|
||||
graphType: graphType,
|
||||
graphTitle: "Graph Title",
|
||||
style: {
|
||||
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",
|
||||
},
|
||||
graphData: defaultGraphData,
|
||||
size: { width: 400, height: 200 },
|
||||
};
|
||||
|
||||
if (dataType === "multiple-machine") {
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "multiple-machine" as const,
|
||||
title: "Multi Machine Chart",
|
||||
dataSource: ["machine-1", "machine-2", "machine-3"],
|
||||
commonValue: "metric-1",
|
||||
};
|
||||
} else {
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "single-machine" as const,
|
||||
title: "Single Machine Chart",
|
||||
dataSource: "machine-1",
|
||||
dataValue: ["metric-1", "metric-2", "metric-3"],
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case "text":
|
||||
newElement = {
|
||||
...commonProps,
|
||||
type: "text",
|
||||
style: {
|
||||
color: "#ffffff",
|
||||
fontSize: 14,
|
||||
textAlign: "left" as const,
|
||||
},
|
||||
data: {
|
||||
...commonProps.data,
|
||||
staticValue: "Text",
|
||||
},
|
||||
size: { width: 200, height: 40 },
|
||||
};
|
||||
break;
|
||||
|
||||
case "icon":
|
||||
newElement = {
|
||||
...commonProps,
|
||||
type: "icon",
|
||||
style: {
|
||||
color: "#ffffff",
|
||||
fontSize: 14,
|
||||
textAlign: "center" as const,
|
||||
},
|
||||
size: { width: 40, height: 40 },
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
return blocks;
|
||||
}
|
||||
|
||||
block.elements.push(newElement);
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekRemoveElement: (blockId, elementId) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
block.elements = block.elements.filter((el) => el.elementUuid !== elementId);
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElement: (blockId, elementId, updates) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
Object.assign(element, updates);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElementStyle: (blockId, elementId, newStyle) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.style = { ...element.style, ...newStyle };
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElementSize: (blockId, elementId, size) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.size = size;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElementPosition: (blockId, elementId, position) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.position = position;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElementPositionType: (blockId, elementId, positionType) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.positionType = positionType;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElementZIndex: (blockId, elementId, zIndex) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.zIndex = zIndex;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateElementData: (blockId, elementId, updates) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = 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 };
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateGraphData: (blockId, elementId, newData) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.graphData = newData;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateGraphTitle: (blockId, elementId, title) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element) {
|
||||
element.graphTitle = title;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateGraphType: (blockId, elementId, graphType) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = 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;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekSwapElements: (blockId, elementId1, elementId2) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = 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;
|
||||
});
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
// Helper functions
|
||||
getBlockById: (blockId) => {
|
||||
return get().blocks.find((b) => b.blockUuid === blockId);
|
||||
|
||||
Reference in New Issue
Block a user