Files
Dwinzo_Demo/app/src/components/SimulationDashboard/DashboardEditor.tsx

412 lines
20 KiB
TypeScript
Raw Normal View History

import { useParams } from "react-router-dom";
import React, { useState, useRef, useEffect } from "react";
import { dataModelManager } from "./data/dataModel";
import ControlPanel from "./ControlPanel";
import SwapModal from "./SwapModal";
import DataModelPanel from "./components/models/DataModelPanel";
import { useSceneContext } from "../../modules/scene/sceneContext";
import useModuleStore from "../../store/ui/useModuleStore";
import { calculateMinBlockSize } from "./functions/block/calculateMinBlockSize";
import { handleElementDragStart, handleElementResizeStart, handleBlockResizeStart, handleSwapStart, handleSwapTarget, handleBlockClick, handleElementClick } from "./functions/eventHandlers";
import BlockGrid from "./components/block/BlockGrid";
import ElementDropdown from "./components/element/ElementDropdown";
import BlockEditor from "./components/block/BlockEditor";
import ElementEditor from "./components/element/ElementEditor";
import useCallBackOnKey from "../../utils/hooks/useCallBackOnKey";
import { handleBlockDragStart } from "./functions/block/handleBlockDragStart";
import { getDashBoardBlocksApi } from "../../services/visulization/dashBoard/getDashBoardBlocks";
import { upsetDashBoardBlocksApi } from "../../services/visulization/dashBoard/upsertDashBoardBlocks";
const DashboardEditor: React.FC = () => {
const { simulationDashBoardStore, versionStore } = useSceneContext();
const { selectedVersion } = versionStore();
const { projectId } = useParams();
const { activeModule } = useModuleStore();
const {
blocks,
setBlocks,
updateBlockPosition,
updateElementPosition,
updateBlockSize,
updateElementSize,
addBlock,
addElement,
updateBlockStyle,
updateBlockPositionType,
updateBlockZIndex,
updateElementStyle,
updateElementPositionType,
updateElementZIndex,
updateElementData,
updateGraphData,
updateGraphTitle,
updateGraphType,
swapElements,
subscribe,
} = simulationDashBoardStore();
const [selectedBlock, setSelectedBlock] = useState<string | null>(null);
const [selectedElement, setSelectedElement] = useState<string | null>(null);
const [editMode, setEditMode] = useState(false);
const [draggingElement, setDraggingElement] = useState<string | null>(null);
const [resizingElement, setResizingElement] = useState<string | null>(null);
const [resizingBlock, setResizingBlock] = useState<string | null>(null);
const [resizeStart, setResizeStart] = useState<{
x: number;
y: number;
width: number;
height: number;
} | null>(null);
const [showSwapUI, setShowSwapUI] = useState(false);
const [swapSource, setSwapSource] = useState<string | null>(null);
const [dataModel, setDataModel] = useState<DataModel>(dataModelManager.getDataSnapshot());
const [showDataModelPanel, setShowDataModelPanel] = useState(false);
const [showElementDropdown, setShowElementDropdown] = useState<string | null>(null);
const [dropDownPosition, setDropDownPosition] = useState({ top: 0, left: 0 });
const [draggingBlock, setDraggingBlock] = useState<string | null>(null);
const [elementDragOffset, setElementDragOffset] = useState<Position>({ x: 0, y: 0 });
const [blockDragOffset, setBlockDragOffset] = useState<Position>({ x: 0, y: 0 });
const editorRef = useRef<HTMLDivElement>(null);
const blockEditorRef = useRef<HTMLDivElement>(null);
const elementEditorRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const blockRef = useRef<HTMLDivElement>(null);
const currentBlock = blocks.find((b) => b.blockUuid === selectedBlock);
const currentElement = currentBlock?.elements.find((el) => el.elementUuid === selectedElement);
useEffect(() => {
if (!projectId || !selectedVersion) return;
// getDashBoardBlocksApi(projectId, selectedVersion.versionId).then((data) => {
// if (data.data && data.data.blocks) {
// console.log("data: ", data);
// setBlocks(data.data.blocks);
// }
// });
}, [projectId, selectedVersion]);
useEffect(() => {
const unsubscribe = subscribe(() => {
if (!projectId || !selectedVersion) return;
// upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => {
// console.log("data: ", data);
// });
});
return () => {
unsubscribe();
};
}, [blocks, projectId, selectedVersion]);
useCallBackOnKey(
() => {
console.log(blocks);
},
"Ctrl+S",
{ dependencies: [blocks], noRepeat: true, allowOnInput: true }
);
// Subscribe to data model changes
useEffect(() => {
const handleDataChange = (): void => {
setDataModel(dataModelManager.getDataSnapshot());
};
const keys = dataModelManager.getAvailableKeys();
const subscriptions: Array<[string, () => void]> = [];
keys.forEach((key) => {
const callback = () => handleDataChange();
dataModelManager.subscribe(key, callback);
subscriptions.push([key, callback]);
});
const interval = setInterval(() => {
const currentKeys = dataModelManager.getAvailableKeys();
const newKeys = currentKeys.filter((key) => !keys.includes(key));
newKeys.forEach((key) => {
const callback = () => handleDataChange();
dataModelManager.subscribe(key, callback);
subscriptions.push([key, callback]);
});
}, 1000);
return () => {
subscriptions.forEach(([key, callback]) => {
dataModelManager.unsubscribe(key, callback);
});
clearInterval(interval);
};
}, []);
// Click outside handler
useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
const target = event.target as Node;
const isInsideBlockEditor = blockEditorRef.current?.contains(target);
const isInsideElementEditor = elementEditorRef.current?.contains(target);
const isInsideDropdown = dropdownRef.current?.contains(target);
const isInsideEditor = editorRef.current?.contains(target);
if (!isInsideEditor) {
setSelectedBlock(null);
setSelectedElement(null);
setShowSwapUI(false);
return;
}
if (isInsideEditor && !isInsideBlockEditor && !isInsideElementEditor && !isInsideDropdown) {
const clickedElement = event.target as HTMLElement;
const isBlock = clickedElement.closest("[data-block-id]");
const isElement = clickedElement.closest("[data-element-id]");
const isButton = clickedElement.closest("button");
const isResizeHandle = clickedElement.closest(".resize-handle");
if (!isBlock && !isElement && !isButton && !isResizeHandle) {
setSelectedBlock(null);
setSelectedElement(null);
setShowSwapUI(false);
setShowElementDropdown(null);
}
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [selectedBlock, selectedElement]);
// Drag and drop handler
useEffect(() => {
const handleMouseMove = (e: MouseEvent): void => {
// Element dragging - direct DOM manipulation
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;
if (blockElement && elementToDrag) {
const blockRect = blockElement.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`;
}
}
// Block dragging - direct DOM manipulation
if (draggingBlock && currentBlock?.positionType && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) {
const editorElement = editorRef.current;
const blockToDrag = document.querySelector(`[data-block-id="${draggingBlock}"]`) as HTMLElement;
if (editorElement && blockToDrag) {
const editorRect = editorElement.getBoundingClientRect();
const newX = e.clientX - editorRect.left - blockDragOffset.x;
const newY = e.clientY - editorRect.top - blockDragOffset.y;
// Direct DOM manipulation
blockToDrag.style.left = `${Math.max(0, Math.min(editorRect.width - (currentBlock.size?.width || 400), newX))}px`;
blockToDrag.style.top = `${Math.max(0, Math.min(editorRect.height - (currentBlock.size?.height || 300), newY))}px`;
}
}
// Resizing - direct DOM manipulation
if ((resizingElement || resizingBlock) && resizeStart) {
if (resizingElement && selectedBlock) {
const elementToResize = document.querySelector(`[data-element-id="${resizingElement}"]`) as HTMLElement;
if (elementToResize) {
const deltaX = e.clientX - resizeStart.x;
const deltaY = e.clientY - resizeStart.y;
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`;
}
} else if (resizingBlock) {
const blockToResize = document.querySelector(`[data-block-id="${resizingBlock}"]`) as HTMLElement;
if (blockToResize) {
const deltaX = e.clientX - resizeStart.x;
const deltaY = e.clientY - resizeStart.y;
const currentBlock = blocks.find((b) => b.blockUuid === resizingBlock);
const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 300, height: 200 };
const newWidth = Math.max(minSize.width, resizeStart.width + deltaX);
const newHeight = Math.max(minSize.height, resizeStart.height + deltaY);
// Direct DOM manipulation
blockToResize.style.width = `${newWidth}px`;
blockToResize.style.height = `${newHeight}px`;
}
}
}
};
const handleMouseUp = (): void => {
// Update state only on mouse up for elements
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;
if (blockElement && elementToDrag) {
const computedStyle = window.getComputedStyle(elementToDrag);
const x = parseFloat(computedStyle.left);
const y = parseFloat(computedStyle.top);
updateElementPosition(selectedBlock, draggingElement, { x, y });
}
}
// Update state only on mouse up for blocks
if (draggingBlock && currentBlock?.positionType && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) {
const blockToDrag = document.querySelector(`[data-block-id="${draggingBlock}"]`) as HTMLElement;
if (blockToDrag) {
const computedStyle = window.getComputedStyle(blockToDrag);
const x = parseFloat(computedStyle.left);
const y = parseFloat(computedStyle.top);
updateBlockPosition(draggingBlock, { x, y });
}
}
// Update state only on mouse up for resizing
if (resizingElement && selectedBlock) {
const elementToResize = document.querySelector(`[data-element-id="${resizingElement}"]`) as HTMLElement;
if (elementToResize) {
const computedStyle = window.getComputedStyle(elementToResize);
const width = parseFloat(computedStyle.width);
const height = parseFloat(computedStyle.height);
updateElementSize(selectedBlock, resizingElement, { width, height });
}
}
if (resizingBlock) {
const blockToResize = document.querySelector(`[data-block-id="${resizingBlock}"]`) as HTMLElement;
if (blockToResize) {
const computedStyle = window.getComputedStyle(blockToResize);
const width = parseFloat(computedStyle.width);
const height = parseFloat(computedStyle.height);
updateBlockSize(resizingBlock, { width, height });
}
}
// Reset all dragging states
setDraggingElement(null);
setResizingElement(null);
setDraggingBlock(null);
setResizingBlock(null);
setResizeStart(null);
};
if (draggingElement || draggingBlock || resizingElement || resizingBlock) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [draggingElement, resizingElement, draggingBlock, resizingBlock, elementDragOffset, blockDragOffset, selectedBlock, currentElement, resizeStart, currentBlock, blocks]);
// Update dropdown position when showElementDropdown changes
useEffect(() => {
if (showElementDropdown && blockRef.current) {
const rect = blockRef.current.getBoundingClientRect();
setDropDownPosition({
top: rect.bottom + window.scrollY,
left: rect.left + window.scrollX,
});
}
}, [showElementDropdown]);
return (
<div ref={editorRef} className="dashboard-editor">
{activeModule === "visualization" && (
<ControlPanel editMode={editMode} setEditMode={setEditMode} addBlock={() => addBlock()} showDataModelPanel={showDataModelPanel} setShowDataModelPanel={setShowDataModelPanel} />
)}
<BlockGrid
blocks={blocks}
editMode={editMode}
selectedBlock={selectedBlock}
selectedElement={selectedElement}
showSwapUI={showSwapUI}
swapSource={swapSource}
calculateMinBlockSize={calculateMinBlockSize}
handleBlockClick={(blockId, event) => handleBlockClick(blockId, event, editMode, setSelectedBlock, setSelectedElement, setShowSwapUI)}
handleElementClick={(blockId, elementId, event) => handleElementClick(blockId, elementId, event, editMode, setSelectedElement, setSelectedBlock, setShowSwapUI, 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={(elementId, event) => handleSwapTarget(elementId, event, swapSource, selectedBlock, swapElements)}
handleBlockDragStart={(blockId, event) => handleBlockDragStart(blockId, event, setDraggingBlock, setBlockDragOffset)}
setShowElementDropdown={setShowElementDropdown}
showElementDropdown={showElementDropdown}
blockRef={blockRef}
/>
<ElementDropdown
showElementDropdown={showElementDropdown}
dropDownPosition={dropDownPosition}
addElement={(blockId, type, graphType) => addElement(blockId, type as UIType, graphType as GraphTypes)}
dropdownRef={dropdownRef}
/>
{showDataModelPanel && editMode && <DataModelPanel dataModel={dataModel} dataModelManager={dataModelManager} />}
{selectedBlock && editMode && !selectedElement && currentBlock && (
<BlockEditor
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)}
/>
)}
{selectedElement && editMode && selectedBlock && currentElement && (
<ElementEditor
elementEditorRef={elementEditorRef}
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)}
setSwapSource={setSwapSource}
setShowSwapUI={setShowSwapUI}
dataModelManager={dataModelManager}
/>
)}
{showSwapUI && <SwapModal setShowSwapUI={setShowSwapUI} setSwapSource={setSwapSource} />}
</div>
);
};
export default DashboardEditor;