diff --git a/app/src/components/SimulationDashboard/DashboardEditor.tsx b/app/src/components/SimulationDashboard/DashboardEditor.tsx index 3c470d9..ba4047d 100644 --- a/app/src/components/SimulationDashboard/DashboardEditor.tsx +++ b/app/src/components/SimulationDashboard/DashboardEditor.tsx @@ -92,7 +92,7 @@ const DashboardEditor: React.FC = () => { 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) => { @@ -210,12 +210,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`; } } @@ -246,9 +254,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; @@ -257,10 +284,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`; @@ -271,6 +320,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; @@ -283,15 +334,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) { @@ -301,32 +347,24 @@ 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 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 }); - 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); @@ -335,19 +373,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 203ec67..dc1df46 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockComponent.tsx @@ -45,13 +45,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 (
= ({ zIndex: block.zIndex || 1, cursor: isDraggable ? "move" : "pointer", }} - onClick={handleMouseDown} + onMouseDown={(e) => { + if (isDraggable) { + handleBlockDragStart(block.blockUuid, e); + } + }} + onClick={(e) => handleBlockClick(block.blockUuid, e)} > {/* Add Element Button */} {editMode && isSelected && ( 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 }; };