feat: Refactor DashboardEditor and simulation store with peek methods for optimized block and element updates

This commit is contained in:
2025-12-17 10:40:57 +05:30
parent 0c51c22457
commit d94c7b4244
3 changed files with 639 additions and 99 deletions

View File

@@ -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}

View File

@@ -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);
}
};

View File

@@ -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);