From 0d3e7c0c127edb93074a50f11c73dd3a8a134168 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 14:03:56 +0530 Subject: [PATCH 1/4] feat: Add `BlockComponent` to render interactive simulation dashboard blocks with element management and resizing. --- .../SimulationDashboard/components/block/BlockComponent.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx b/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx index a4dab05..75e89c5 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx @@ -78,6 +78,11 @@ const BlockComponent: React.FC = ({ zIndex: block.zIndex || 1, cursor: isDraggable ? "move" : "pointer", }} + onMouseDown={(e) => { + if (isDraggable) { + handleBlockDragStart(block.blockUuid, e); + } + }} onClick={handleMouseDown} > {/* Add Element Button */} From deb9472073c2308065dafc948772726b7fa24ada Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 14:12:10 +0530 Subject: [PATCH 2/4] feat: Add initial implementation of Simulation Dashboard editor and its core components. --- .../SimulationDashboard/DashboardEditor.tsx | 55 ++++++++----------- .../components/block/BlockComponent.tsx | 9 +-- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/app/src/components/SimulationDashboard/DashboardEditor.tsx b/app/src/components/SimulationDashboard/DashboardEditor.tsx index b20296f..ccf732a 100644 --- a/app/src/components/SimulationDashboard/DashboardEditor.tsx +++ b/app/src/components/SimulationDashboard/DashboardEditor.tsx @@ -95,12 +95,12 @@ const DashboardEditor: React.FC = () => { const currentBlock = blocks.find((b) => b.blockUuid === selectedBlock); const currentElement = currentBlock?.elements.find((el) => el.elementUuid === selectedElement); - useEffect(()=>{ + useEffect(() => { if (!editMode) { setSelectedBlock(null); setSelectedElement(null); } - },[editMode]) + }, [editMode]); // Helper function to send updates to backend - only sends the specific block that changed const updateBackend = async (updatedBlock: Block) => { @@ -313,6 +313,8 @@ const DashboardEditor: React.FC = () => { }; const handleMouseUp = async (): Promise => { + let blockToUpdate: Block | undefined; + // 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; @@ -325,15 +327,10 @@ const DashboardEditor: React.FC = () => { // 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); - } + blockToUpdate = getBlockFromPeekedBlocks(updatedBlocks, selectedBlock); } - } - - // Update backend for block drag - if (draggingBlock && currentBlock?.positionType && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) { + } else if (draggingBlock && currentBlock?.positionType && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) { + // Update backend for block drag const blockToDrag = document.querySelector(`[data-block-id="${draggingBlock}"]`) as HTMLElement; if (blockToDrag) { @@ -343,15 +340,10 @@ const DashboardEditor: React.FC = () => { // 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); - } + blockToUpdate = getBlockFromPeekedBlocks(updatedBlocks, draggingBlock); } - } - - // Update backend for element resize - if (resizingElement && selectedBlock) { + } else if (resizingElement && selectedBlock) { + // Update backend for element resize const elementToResize = document.querySelector(`[data-element-id="${resizingElement}"]`) as HTMLElement; if (elementToResize) { const computedStyle = window.getComputedStyle(elementToResize); @@ -360,15 +352,10 @@ const DashboardEditor: React.FC = () => { // 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); - } + blockToUpdate = getBlockFromPeekedBlocks(updatedBlocks, selectedBlock); } - } - - // Update backend for block resize - if (resizingBlock) { + } else if (resizingBlock) { + // Update backend for block resize const blockToResize = document.querySelector(`[data-block-id="${resizingBlock}"]`) as HTMLElement; if (blockToResize) { const computedStyle = window.getComputedStyle(blockToResize); @@ -377,19 +364,25 @@ const DashboardEditor: React.FC = () => { // 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); - } + blockToUpdate = getBlockFromPeekedBlocks(updatedBlocks, resizingBlock); } } - // Reset all dragging states + // Reset all dragging states IMMEDIATELY to stop cursor following setDraggingElement(null); setResizingElement(null); setDraggingBlock(null); setResizingBlock(null); setResizeStart(null); + + // Perform updates if needed + if (blockToUpdate) { + // Optimistic update to prevent snap-back + updateBlock(blockToUpdate.blockUuid, blockToUpdate); + + // Send to backend + await updateBackend(blockToUpdate); + } }; if (draggingElement || draggingBlock || resizingElement || resizingBlock) { diff --git a/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx b/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx index 75e89c5..ae21157 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx @@ -53,13 +53,6 @@ const BlockComponent: React.FC = ({ const isSelected = selectedBlock === block.blockUuid; const isDraggable = editMode && (block.positionType === "absolute" || block.positionType === "fixed"); - const handleMouseDown = (event: React.MouseEvent) => { - if (isDraggable) { - handleBlockDragStart(block.blockUuid, event); - } - handleBlockClick(block.blockUuid, event); - }; - return (
= ({ handleBlockDragStart(block.blockUuid, e); } }} - onClick={handleMouseDown} + onClick={(e) => handleBlockClick(block.blockUuid, e)} > {/* Add Element Button */} {editMode && isSelected && ( From 8a5c78bc6da3c3e881bdbe75f989bd72f7dff6d6 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 14:26:48 +0530 Subject: [PATCH 3/4] feat: Add DashboardEditor component for managing simulation dashboards with block and element editing capabilities. --- .../SimulationDashboard/DashboardEditor.tsx | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/app/src/components/SimulationDashboard/DashboardEditor.tsx b/app/src/components/SimulationDashboard/DashboardEditor.tsx index ccf732a..0bd4efb 100644 --- a/app/src/components/SimulationDashboard/DashboardEditor.tsx +++ b/app/src/components/SimulationDashboard/DashboardEditor.tsx @@ -288,9 +288,28 @@ const DashboardEditor: React.FC = () => { const newWidth = Math.max(100, resizeStart.width + deltaX); const newHeight = Math.max(50, resizeStart.height + deltaY); - // Direct DOM manipulation - elementToResize.style.width = `${newWidth}px`; - elementToResize.style.height = `${newHeight}px`; + // Constrain element to block bounds + const blockElement = document.querySelector(`[data-block-id="${selectedBlock}"]`) as HTMLElement; + if (blockElement) { + const blockRect = blockElement.getBoundingClientRect(); + const elementLeft = elementToResize.offsetLeft; + const elementTop = elementToResize.offsetTop; + + // Calculate max allowed dimensions (block size - element position - padding) + const maxAllowedWidth = blockRect.width - elementLeft - 20; + const maxAllowedHeight = blockRect.height - elementTop - 20; + + const constrainedWidth = Math.min(newWidth, maxAllowedWidth); + const constrainedHeight = Math.min(newHeight, maxAllowedHeight); + + // Direct DOM manipulation + elementToResize.style.width = `${constrainedWidth}px`; + elementToResize.style.height = `${constrainedHeight}px`; + } else { + // Fallback if block not found (unlikely) + elementToResize.style.width = `${newWidth}px`; + elementToResize.style.height = `${newHeight}px`; + } } } else if (resizingBlock) { const blockToResize = document.querySelector(`[data-block-id="${resizingBlock}"]`) as HTMLElement; @@ -345,12 +364,14 @@ const DashboardEditor: React.FC = () => { } else if (resizingElement && selectedBlock) { // Update backend for element resize const elementToResize = document.querySelector(`[data-element-id="${resizingElement}"]`) as HTMLElement; - if (elementToResize) { + const blockElement = document.querySelector(`[data-block-id="${selectedBlock}"]`) as HTMLElement; + + if (elementToResize && blockElement) { const computedStyle = window.getComputedStyle(elementToResize); const width = parseFloat(computedStyle.width); const height = parseFloat(computedStyle.height); - // Use peek to get updated blocks and send only the affected block to backend + // Use peek to get updated blocks const updatedBlocks = peekUpdateElementSize(selectedBlock, resizingElement, { width, height }); blockToUpdate = getBlockFromPeekedBlocks(updatedBlocks, selectedBlock); } From 005e17c7b51ddb2f8913d0d0169a8dceca2934f8 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 14:49:23 +0530 Subject: [PATCH 4/4] feat: implement DashboardEditor component for managing simulation dashboards and add block size calculation utility. --- .../SimulationDashboard/DashboardEditor.tsx | 42 ++++++++++++++++--- .../functions/block/calculateMinBlockSize.ts | 11 ++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/app/src/components/SimulationDashboard/DashboardEditor.tsx b/app/src/components/SimulationDashboard/DashboardEditor.tsx index 0bd4efb..bb6ba7e 100644 --- a/app/src/components/SimulationDashboard/DashboardEditor.tsx +++ b/app/src/components/SimulationDashboard/DashboardEditor.tsx @@ -252,12 +252,20 @@ const DashboardEditor: React.FC = () => { if (blockElement && elementToDrag) { const blockRect = blockElement.getBoundingClientRect(); + const elementRect = elementToDrag.getBoundingClientRect(); const newX = e.clientX - blockRect.left - elementDragOffset.x; const newY = e.clientY - blockRect.top - elementDragOffset.y; - // Direct DOM manipulation - elementToDrag.style.left = `${Math.max(0, Math.min(blockRect.width - 50, newX))}px`; - elementToDrag.style.top = `${Math.max(0, Math.min(blockRect.height - 30, newY))}px`; + // Constrain element to stay within block bounds using actual element dimensions + // Add 20px padding on all sides to match the padding used in calculateMinBlockSize + const minX = 20; // Left padding + const minY = 20; // Top padding + const maxX = blockRect.width - elementRect.width - 20; // Right padding + const maxY = blockRect.height - elementRect.height - 20; // Bottom padding + + // Direct DOM manipulation with padding on all sides + elementToDrag.style.left = `${Math.max(minX, Math.min(maxX, newX))}px`; + elementToDrag.style.top = `${Math.max(minY, Math.min(maxY, newY))}px`; } } @@ -318,10 +326,32 @@ const DashboardEditor: React.FC = () => { const deltaY = e.clientY - resizeStart.y; const currentBlock = blocks.find((b) => b.blockUuid === resizingBlock); - const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 100, height: 50 }; - const newWidth = Math.max(minSize.width, resizeStart.width + deltaX); - const newHeight = Math.max(minSize.height, resizeStart.height + deltaY); + // Calculate minimum size based on ALL elements in the block + const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 100, height: 50 }; + const minWidth = minSize.width; + const minHeight = minSize.height; + + let newWidth = Math.max(minWidth, resizeStart.width + deltaX); + let newHeight = Math.max(minHeight, resizeStart.height + deltaY); + + // Constrain block to editor bounds if it has absolute or fixed positioning + if (currentBlock && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) { + const editorElement = editorRef.current; + if (editorElement) { + const editorRect = editorElement.getBoundingClientRect(); + const blockX = currentBlock.position?.x || 0; + const blockY = currentBlock.position?.y || 0; + + // Calculate max allowed dimensions based on position + const maxAllowedWidth = editorRect.width - blockX; + const maxAllowedHeight = editorRect.height - blockY; + + // Constrain width and height to editor bounds, but not below minimum + newWidth = Math.max(minWidth, Math.min(newWidth, maxAllowedWidth)); + newHeight = Math.max(minHeight, Math.min(newHeight, maxAllowedHeight)); + } + } // Direct DOM manipulation blockToResize.style.width = `${newWidth}px`; diff --git a/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts b/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts index fb63e7f..4a5cbe0 100644 --- a/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts +++ b/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts @@ -3,6 +3,7 @@ import type { Block } from "../../../../types/exportedTypes"; export const calculateMinBlockSize = (block: Block): Size => { let minWidth = 100; let minHeight = 50; + let stackedHeight = 0; // Track cumulative height for stacked elements block.elements.forEach((element) => { if (element.positionType === "absolute") { @@ -11,10 +12,18 @@ export const calculateMinBlockSize = (block: Block): Size => { minWidth = Math.max(minWidth, elementRight + 20); minHeight = Math.max(minHeight, elementBottom + 20); } else { + // For relative/static elements, they stack vertically + // Width: take the maximum minWidth = Math.max(minWidth, (element.size?.width || 200) + 40); - minHeight = Math.max(minHeight, (element.size?.height || 60) + 40); + // Height: sum them up since they stack + stackedHeight += element.size?.height || 60; } }); + // Add padding to stacked height and compare with minHeight from absolute elements + if (stackedHeight > 0) { + minHeight = Math.max(minHeight, stackedHeight + 40); + } + return { width: minWidth, height: minHeight }; };