feat: Add simulation dashboard with interactive block and element editing capabilities.

This commit is contained in:
2025-12-20 14:05:27 +05:30
parent 5818583473
commit 578f10dfdb
8 changed files with 27 additions and 159 deletions

View File

@@ -1,7 +1,6 @@
import { useParams } from "react-router-dom";
import React, { useState, useRef, useEffect } from "react";
import ControlPanel from "./ControlPanel";
import SwapModal from "./SwapModal";
import { Block } from "../../types/exportedTypes";
import AnalyzerManager from "./AnalyzerManager";
@@ -9,7 +8,7 @@ import { useSceneContext } from "../../modules/scene/sceneContext";
import useModuleStore from "../../store/ui/useModuleStore";
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import { calculateMinBlockSize } from "./functions/block/calculateMinBlockSize";
import { handleElementDragStart, handleElementResizeStart, handleBlockResizeStart, handleSwapStart, handleSwapTarget, handleBlockClick, handleElementClick } from "./functions/eventHandlers";
import { handleElementDragStart, handleElementResizeStart, handleBlockResizeStart, handleBlockClick, handleElementClick } from "./functions/eventHandlers";
import BlockGrid from "./components/block/BlockGrid";
import BlockEditor from "./components/block/BlockEditor";
import ElementEditor from "./components/element/ElementEditor";
@@ -60,7 +59,7 @@ const DashboardEditor: React.FC = () => {
peekUpdateCommonValue,
peekUpdateDataValue,
peekUpdateDataSource,
peekSwapElements,
} = simulationDashBoardStore();
const [editMode, setEditMode] = useState(false);
@@ -73,8 +72,7 @@ const DashboardEditor: React.FC = () => {
width: number;
height: number;
} | null>(null);
const [showSwapUI, setShowSwapUI] = useState(false);
const [swapSource, setSwapSource] = useState<string | null>(null);
const [showElementDropdown, setShowElementDropdown] = useState<string | null>(null);
const [draggingBlock, setDraggingBlock] = useState<string | null>(null);
const [elementDragOffset, setElementDragOffset] = useState<Position>({ x: 0, y: 0 });
@@ -89,12 +87,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) => {
@@ -176,7 +174,7 @@ const DashboardEditor: React.FC = () => {
if (!isInsideEditor) {
setSelectedBlock(null);
setSelectedElement(null);
setShowSwapUI(false);
setSelectedElement(null);
return;
}
@@ -190,7 +188,7 @@ const DashboardEditor: React.FC = () => {
if (!isBlock && !isElement && !isButton && !isResizeHandle) {
setSelectedBlock(null);
setSelectedElement(null);
setShowSwapUI(false);
setSelectedElement(null);
setShowElementDropdown(null);
}
}
@@ -396,28 +394,14 @@ const DashboardEditor: React.FC = () => {
editMode={editMode}
selectedBlock={selectedBlock}
selectedElement={selectedElement}
showSwapUI={showSwapUI}
swapSource={swapSource}
calculateMinBlockSize={calculateMinBlockSize}
handleBlockClick={(blockId, event) => handleBlockClick(blockId, event, editMode, setSelectedBlock, setSelectedElement, setShowSwapUI, setShowElementDropdown, showElementDropdown)}
handleBlockClick={(blockId, event) => handleBlockClick(blockId, event, editMode, setSelectedBlock, setSelectedElement, setShowElementDropdown, showElementDropdown)}
handleElementClick={(blockId, elementId, event) => {
handleBlockClick(blockId, event, editMode, setSelectedBlock, setSelectedElement, setShowSwapUI, setShowElementDropdown, showElementDropdown);
handleElementClick(blockId, elementId, event, editMode, setSelectedElement, setSelectedBlock, setShowSwapUI, setShowElementDropdown);
handleElementClick(blockId, elementId, event, editMode, setSelectedElement, setSelectedBlock, setShowElementDropdown);
}}
handleElementDragStart={(elementId, event) => handleElementDragStart(elementId, event, currentElement, setDraggingElement, setElementDragOffset)}
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={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}
@@ -607,13 +591,10 @@ const DashboardEditor: React.FC = () => {
console.error("Failed to delete element:", error);
}
}}
setSwapSource={setSwapSource}
setShowSwapUI={setShowSwapUI}
/>
)}
</div>
{showSwapUI && <SwapModal setShowSwapUI={setShowSwapUI} setSwapSource={setSwapSource} />}
</div>
);
};

View File

@@ -1,26 +0,0 @@
import React from "react";
interface SwapModalProps {
setShowSwapUI: (show: boolean) => void;
setSwapSource: (source: string | null) => void;
}
const SwapModal: React.FC<SwapModalProps> = ({ setShowSwapUI, setSwapSource }) => {
return (
<div className="swap-modal">
<h4>Swap Elements</h4>
<p>Click on another element to swap positions with the selected element</p>
<button
onClick={() => {
setShowSwapUI(false);
setSwapSource(null);
}}
className="cancel-button"
>
Cancel Swap
</button>
</div>
);
};
export default SwapModal;

View File

@@ -10,16 +10,12 @@ interface BlockComponentProps {
editMode: boolean;
selectedBlock: string | null;
selectedElement: string | null;
showSwapUI: boolean;
swapSource: string | null;
calculateMinBlockSize: (block: Block) => { width: number; height: number };
handleBlockClick: (blockId: string, event: React.MouseEvent) => void;
handleElementClick: (blockId: string, elementId: string, event: React.MouseEvent) => void;
handleElementDragStart: (elementId: string, event: React.MouseEvent) => void;
handleElementResizeStart: (elementId: string, event: React.MouseEvent) => void;
handleBlockResizeStart: (blockId: string, event: React.MouseEvent) => void;
handleSwapStart: (elementId: string, event: React.MouseEvent) => void;
handleSwapTarget: (elementId: string, event: React.MouseEvent) => void;
setShowElementDropdown: (blockId: string | null) => void;
showElementDropdown: string | null;
blockRef: RefObject<HTMLDivElement>;
@@ -32,16 +28,12 @@ const BlockComponent: React.FC<BlockComponentProps> = ({
editMode,
selectedBlock,
selectedElement,
showSwapUI,
swapSource,
calculateMinBlockSize,
handleBlockClick,
handleElementClick,
handleElementDragStart,
handleElementResizeStart,
handleBlockResizeStart,
handleSwapStart,
handleSwapTarget,
setShowElementDropdown,
showElementDropdown,
blockRef,
@@ -109,13 +101,9 @@ const BlockComponent: React.FC<BlockComponentProps> = ({
blockId={block.blockUuid}
editMode={editMode}
selectedElement={selectedElement}
showSwapUI={showSwapUI}
swapSource={swapSource}
handleElementClick={handleElementClick}
handleElementDragStart={handleElementDragStart}
handleElementResizeStart={handleElementResizeStart}
handleSwapStart={handleSwapStart}
handleSwapTarget={handleSwapTarget}
/>
))}

View File

@@ -9,16 +9,12 @@ interface BlockGridProps {
editMode: boolean;
selectedBlock: string | null;
selectedElement: string | null;
showSwapUI: boolean;
swapSource: string | null;
calculateMinBlockSize: (block: Block) => { width: number; height: number };
handleBlockClick: (blockId: string, event: React.MouseEvent) => void;
handleElementClick: (blockId: string, elementId: string, event: React.MouseEvent) => void;
handleElementDragStart: (elementId: string, event: React.MouseEvent) => void;
handleElementResizeStart: (elementId: string, event: React.MouseEvent) => void;
handleBlockResizeStart: (blockId: string, event: React.MouseEvent) => void;
handleSwapStart: (elementId: string, event: React.MouseEvent) => void;
handleSwapTarget: (elementId: string, event: React.MouseEvent) => void;
handleBlockDragStart: (blockId: string, event: React.MouseEvent) => void;
setShowElementDropdown: (blockId: string | null) => void;
showElementDropdown: string | null;
@@ -31,16 +27,12 @@ const BlockGrid: React.FC<BlockGridProps> = ({
editMode,
selectedBlock,
selectedElement,
showSwapUI,
swapSource,
calculateMinBlockSize,
handleBlockClick,
handleElementClick,
handleElementDragStart,
handleElementResizeStart,
handleBlockResizeStart,
handleSwapStart,
handleSwapTarget,
setShowElementDropdown,
handleBlockDragStart,
showElementDropdown,
@@ -58,16 +50,12 @@ const BlockGrid: React.FC<BlockGridProps> = ({
editMode={editMode}
selectedBlock={selectedBlock}
selectedElement={selectedElement}
showSwapUI={showSwapUI}
swapSource={swapSource}
calculateMinBlockSize={calculateMinBlockSize}
handleBlockClick={handleBlockClick}
handleElementClick={handleElementClick}
handleElementDragStart={handleElementDragStart}
handleElementResizeStart={handleElementResizeStart}
handleBlockResizeStart={handleBlockResizeStart}
handleSwapStart={handleSwapStart}
handleSwapTarget={handleSwapTarget}
handleBlockDragStart={handleBlockDragStart}
setShowElementDropdown={setShowElementDropdown}
showElementDropdown={showElementDropdown}

View File

@@ -9,13 +9,9 @@ interface ElementComponentProps {
blockId: string;
editMode: boolean;
selectedElement: string | null;
showSwapUI: boolean;
swapSource: string | null;
handleElementClick: (blockId: string, elementId: string, event: React.MouseEvent) => void;
handleElementDragStart: (elementId: string, event: React.MouseEvent) => void;
handleElementResizeStart: (elementId: string, event: React.MouseEvent) => void;
handleSwapStart: (elementId: string, event: React.MouseEvent) => void;
handleSwapTarget: (elementId: string, event: React.MouseEvent) => void;
}
const ElementComponent: React.FC<ElementComponentProps> = ({
@@ -23,16 +19,11 @@ const ElementComponent: React.FC<ElementComponentProps> = ({
blockId,
editMode,
selectedElement,
showSwapUI,
swapSource,
handleElementClick,
handleElementDragStart,
handleElementResizeStart,
handleSwapStart,
handleSwapTarget,
}) => {
const isSelected = selectedElement === element.elementUuid;
const isSwapSource = swapSource === element.elementUuid;
const elementClasses = [
"element",
@@ -40,8 +31,6 @@ const ElementComponent: React.FC<ElementComponentProps> = ({
element.type === "graph" ? "graph" : "",
editMode ? "edit-mode" : "",
isSelected ? "selected" : "",
isSwapSource ? "swap-source" : "",
showSwapUI && !isSwapSource ? "swap-target" : "",
]
.filter(Boolean)
.join(" ");
@@ -64,11 +53,7 @@ const ElementComponent: React.FC<ElementComponentProps> = ({
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (showSwapUI && swapSource !== element.elementUuid) {
handleSwapTarget(element.elementUuid, e);
} else {
handleElementClick(blockId, element.elementUuid, e);
}
handleElementClick(blockId, element.elementUuid, e);
}}
onMouseDown={(e) => handleElementDragStart(element.elementUuid, e)}
>

View File

@@ -57,8 +57,6 @@ interface ElementDesignProps {
updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void;
updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void;
handleRemoveElement: (blockId: string, elementId: string) => void;
setSwapSource: (source: string | null) => void;
setShowSwapUI: (show: boolean) => void;
}
const ElementDesign: React.FC<ElementDesignProps> = ({
@@ -74,8 +72,6 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
updateElementZIndex,
updateGraphTitle,
updateGraphType,
setSwapSource,
setShowSwapUI,
}) => {
const [color, setColor] = useState("#000000");
@@ -396,7 +392,7 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
value={color}
onChange={(e) => {
updateElementStyle(selectedBlock, selectedElement, {
backgroundColor: color,
backgroundColor: hexToRgba(e.target.value),
});
setColor(e.target.value);
}}

View File

@@ -30,8 +30,6 @@ interface ElementEditorProps {
updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void;
updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void;
handleRemoveElement: (blockId: string, elementId: string) => void;
setSwapSource: (source: string | null) => void;
setShowSwapUI: (show: boolean) => void;
}
const ElementEditor: React.FC<ElementEditorProps> = ({
@@ -53,8 +51,6 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
updateDataValue,
updateDataSource,
handleRemoveElement,
setSwapSource,
setShowSwapUI,
}) => {
const { simulationDashBoardStore, productStore } = useSceneContext();
const { selectedProduct, getProductById, getEventByModelUuid } = productStore();
@@ -160,7 +156,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
window.removeEventListener("pointerup", onPointerUp);
try {
(panel as Element).releasePointerCapture?.((e as any).pointerId);
} catch (err) {}
} catch (err) { }
};
window.addEventListener("pointermove", onPointerMove);
@@ -519,8 +515,6 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
updateDataValue={updateDataValue}
updateDataSource={updateDataSource}
handleRemoveElement={handleRemoveElement}
setSwapSource={setSwapSource}
setShowSwapUI={setShowSwapUI}
/>
)}
{selectType === "data" && (
@@ -553,12 +547,12 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
value={
element.dataBinding?.dataSource
? {
id: element.dataBinding.dataSource as string,
label:
getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ??
(element.dataBinding.dataSource === "global" ? "Global" : ""),
icon: <DeviceIcon />,
}
id: element.dataBinding.dataSource as string,
label:
getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ??
(element.dataBinding.dataSource === "global" ? "Global" : ""),
icon: <DeviceIcon />,
}
: null
}
onChange={(value) => {
@@ -577,13 +571,13 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
value={
element.dataBinding?.dataValue
? {
id: element.dataBinding.dataValue as string,
label:
getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)
.flatMap((section) => section.items)
.find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "",
icon: <ParametersIcon />,
}
id: element.dataBinding.dataValue as string,
label:
getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)
.flatMap((section) => section.items)
.find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "",
icon: <ParametersIcon />,
}
: null
}
onChange={(value) => {

View File

@@ -24,23 +24,6 @@ export const handleElementDragStart = (
}
};
export const handleBlockDragStart = (
blockId: string,
event: React.MouseEvent,
setDraggingBlock: (blockId: string | null) => void,
setBlockDragOffset: (offset: Position) => void // Change to specific offset
): void => {
setDraggingBlock(blockId);
const element = event.currentTarget as HTMLElement;
const rect = element.getBoundingClientRect();
setBlockDragOffset({
x: event.clientX - rect.left,
y: event.clientY - rect.top,
});
event.preventDefault();
event.stopPropagation();
};
export const handleElementResizeStart = (
elementId: string,
event: React.MouseEvent,
@@ -81,24 +64,7 @@ export const handleBlockResizeStart = (
}
};
export const handleSwapStart = (elementId: string, event: React.MouseEvent, setSwapSource: (source: string | null) => void, setShowSwapUI: (show: boolean) => void): void => {
event.stopPropagation();
setSwapSource(elementId);
setShowSwapUI(true);
};
export const handleSwapTarget = (
elementId: string,
event: React.MouseEvent,
swapSource: string | null,
selectedBlock: string | null,
swapElements: (blockId: string, elementId1: string, elementId2: string) => void
): void => {
event.stopPropagation();
if (swapSource && swapSource !== elementId && selectedBlock) {
swapElements(selectedBlock, swapSource, elementId);
}
};
export const handleBlockClick = (
blockId: string,
@@ -106,7 +72,6 @@ export const handleBlockClick = (
editMode: boolean,
setSelectedBlock: (blockId: string | null) => void,
setSelectedElement: (elementId: string | null) => void,
setShowSwapUI: (show: boolean) => void,
setShowElementDropdown: (blockId: string | null) => void,
showElementDropdown: string | null
): void => {
@@ -114,7 +79,6 @@ export const handleBlockClick = (
if (editMode) {
setSelectedBlock(blockId);
setSelectedElement(null);
setShowSwapUI(false);
if (showElementDropdown && showElementDropdown !== blockId) {
setShowElementDropdown(blockId);
}
@@ -128,14 +92,12 @@ export const handleElementClick = (
editMode: boolean,
setSelectedElement: (elementId: string | null) => void,
setSelectedBlock: (blockId: string | null) => void,
setShowSwapUI: (show: boolean) => void,
setShowElementDropdown: (blockId: string | null) => void
): void => {
event.stopPropagation();
if (editMode) {
setSelectedBlock(blockId);
setSelectedElement(elementId);
// setSelectedBlock(blockId);
// setShowSwapUI(false);
// setShowElementDropdown(null);
setShowElementDropdown(null);
}
};