diff --git a/app/src/components/SimulationDashboard/DashboardEditor.tsx b/app/src/components/SimulationDashboard/DashboardEditor.tsx index f7d780c..2971e5c 100644 --- a/app/src/components/SimulationDashboard/DashboardEditor.tsx +++ b/app/src/components/SimulationDashboard/DashboardEditor.tsx @@ -3,6 +3,7 @@ import React, { useState, useRef, useEffect } from "react"; import { dataModelManager } from "./data/dataModel"; import ControlPanel from "./ControlPanel"; import SwapModal from "./SwapModal"; +import { Block } from "../../types/exportedTypes"; import DataModelPanel from "./components/models/DataModelPanel"; import { useSceneContext } from "../../modules/scene/sceneContext"; @@ -77,23 +78,35 @@ 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]); + 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); - // } - // }); + getDashBoardBlocksApi(projectId, selectedVersion.versionId).then((data) => { + if (data.data?.blocks) { + console.log("data.data.blocks: ", data.data.blocks); + setBlocks(data.data.blocks); + } + }); }, [projectId, selectedVersion]); + const updateBackend = (blocks: Block[]) => { + if (!projectId || !selectedVersion) return; + + upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => { + if (data.data?.blocks) { + console.log("data.data.blocks: ", data.data.blocks); + setBlocks(data.data.blocks); + } + }); + }; + useEffect(() => { const unsubscribe = subscribe(() => { if (!projectId || !selectedVersion) return; - - // upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => { - // console.log("data: ", data); - // }); + updateBackend(blocks); }); return () => { @@ -103,10 +116,11 @@ const DashboardEditor: React.FC = () => { useCallBackOnKey( () => { - console.log(blocks); + if (!projectId || !selectedVersion) return; + updateBackend(blocks); }, "Ctrl+S", - { dependencies: [blocks], noRepeat: true, allowOnInput: true } + { dependencies: [blocks, projectId, selectedVersion], noRepeat: true, allowOnInput: true } ); // Subscribe to data model changes @@ -118,27 +132,27 @@ const DashboardEditor: React.FC = () => { const keys = dataModelManager.getAvailableKeys(); const subscriptions: Array<[string, () => void]> = []; - keys.forEach((key) => { + for (const key of keys) { 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) => { + for (const key of newKeys) { const callback = () => handleDataChange(); dataModelManager.subscribe(key, callback); subscriptions.push([key, callback]); - }); + } }, 1000); return () => { - subscriptions.forEach(([key, callback]) => { + for (const [key, callback] of subscriptions) { dataModelManager.unsubscribe(key, callback); - }); + } clearInterval(interval); }; }, []); @@ -183,144 +197,144 @@ const DashboardEditor: React.FC = () => { }, [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; + 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; - // 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`; + 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; + // 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; - // 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`; + 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) { + // 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: 100, height: 50 }; + + 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 deltaX = e.clientX - resizeStart.x; - const deltaY = e.clientY - resizeStart.y; + const computedStyle = window.getComputedStyle(elementToResize); + const width = parseFloat(computedStyle.width); + const height = parseFloat(computedStyle.height); - 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`; + updateElementSize(selectedBlock, resizingElement, { width, height }); } - } else if (resizingBlock) { + } + + 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 computedStyle = window.getComputedStyle(blockToResize); + const width = parseFloat(computedStyle.width); + const height = parseFloat(computedStyle.height); - 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`; + updateBlockSize(resizingBlock, { width, height }); } } - } - }; - 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 }); - } + // 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); } - // 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]); + 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(() => { diff --git a/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts b/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts index a366c20..fb63e7f 100644 --- a/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts +++ b/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts @@ -1,8 +1,8 @@ import type { Block } from "../../../../types/exportedTypes"; export const calculateMinBlockSize = (block: Block): Size => { - let minWidth = 300; - let minHeight = 200; + let minWidth = 100; + let minHeight = 50; block.elements.forEach((element) => { if (element.positionType === "absolute") { diff --git a/app/src/components/SimulationDashboard/functions/eventHandlers/index.ts b/app/src/components/SimulationDashboard/functions/eventHandlers/index.ts index 92f0f2b..427feb4 100644 --- a/app/src/components/SimulationDashboard/functions/eventHandlers/index.ts +++ b/app/src/components/SimulationDashboard/functions/eventHandlers/index.ts @@ -30,7 +30,6 @@ export const handleBlockDragStart = ( setDraggingBlock: (blockId: string | null) => void, setBlockDragOffset: (offset: Position) => void // Change to specific offset ): void => { - console.log("Block drag start:", blockId); setDraggingBlock(blockId); const element = event.currentTarget as HTMLElement; const rect = element.getBoundingClientRect(); diff --git a/app/src/components/ui/list/OutlineList/AssetOutline.tsx b/app/src/components/ui/list/OutlineList/AssetOutline.tsx index 8be3a61..2f67787 100644 --- a/app/src/components/ui/list/OutlineList/AssetOutline.tsx +++ b/app/src/components/ui/list/OutlineList/AssetOutline.tsx @@ -32,6 +32,7 @@ const TreeNode = ({ onDrop, onToggleExpand, onOptionClick, + onRename, }: { item: AssetGroupChild; level?: number; @@ -43,6 +44,7 @@ const TreeNode = ({ onClick: (e: React.MouseEvent, selectedItem: AssetGroupChild) => void; onToggleExpand: (groupUuid: string, newExpanded: boolean) => void; onOptionClick: (option: string, item: AssetGroupChild) => void; + onRename: (item: AssetGroupChild, newName: string) => void; }) => { const { assetGroupStore, assetStore } = useSceneContext(); const { hasSelectedAsset, selectedAssets } = assetStore(); @@ -136,7 +138,13 @@ const TreeNode = ({
{isGroupNode ? : }
- {}} canEdit={true} /> + { + onRename(item, newName); + }} + canEdit={true} + />
@@ -195,6 +204,7 @@ export const AssetOutline = () => { const { groupHierarchy, isGroup, + getItemId, getGroupsContainingAsset, getFlatGroupChildren, setGroupExpanded, @@ -207,6 +217,8 @@ export const AssetOutline = () => { toggleSelectedGroup, clearSelectedGroups, hasSelectedGroup, + toggleGroupVisibility, + toggleGroupLock, } = assetGroupStore(); const { projectId } = useParams(); const { push3D } = undoRedo3DStore(); @@ -232,6 +244,77 @@ export const AssetOutline = () => { return flattened; }, [groupHierarchy, isGroup]); + const handleAssetRenameUpdate = async (asset: Asset | null, newName: string) => { + if (!asset) return; + + if (!builderSocket?.connected) { + setAssetsApi({ + modelUuid: asset.modelUuid, + modelName: newName, + assetId: asset.assetId, + position: asset.position, + rotation: asset.rotation, + scale: asset.scale, + isCollidable: asset.isCollidable, + opacity: asset.opacity, + isLocked: asset.isLocked, + isVisible: asset.isVisible, + versionId: selectedVersion?.versionId || "", + projectId: projectId || "", + }) + .then((data) => { + if (!data.message || !data.data) { + echo.error(`Error renaming asset: ${asset.modelName}`); + return; + } + if (data.message === "Model updated successfully" && data.data) { + const model: Asset = { + modelUuid: data.data.modelUuid, + modelName: data.data.modelName, + assetId: data.data.assetId, + position: data.data.position, + rotation: data.data.rotation, + scale: data.data.scale, + isLocked: data.data.isLocked, + isVisible: data.data.isVisible, + isCollidable: data.data.isCollidable, + opacity: data.data.opacity, + ...(data.data.eventData ? { eventData: data.data.eventData } : {}), + }; + + updateAssetInScene(model, () => { + echo.info(`Renamed asset to: ${model.modelName}`); + }); + } else { + echo.error(`Error renaming asset: ${asset.modelName}`); + } + }) + .catch(() => { + echo.error(`Error renaming asset: ${asset.modelName}`); + }); + } else { + const data = { + organization, + modelUuid: asset.modelUuid, + modelName: newName, + assetId: asset.assetId, + position: asset.position, + rotation: asset.rotation, + scale: asset.scale, + isCollidable: asset.isCollidable, + opacity: asset.opacity, + isLocked: asset.isLocked, + isVisible: asset.isVisible, + socketId: builderSocket?.id, + versionId: selectedVersion?.versionId || "", + projectId, + userId, + }; + + builderSocket.emit("v1:model-asset:add", data); + } + }; + const handleAssetVisibilityUpdate = async (asset: Asset | null) => { if (!asset) return; @@ -556,11 +639,6 @@ export const AssetOutline = () => { if (!scene.current) return; - // Helper to get item ID - const getItemId = (i: AssetGroupChild): string => { - return isGroup(i) ? i.groupUuid : i.modelUuid; - }; - const itemId = getItemId(item); const flattened = getFlattenedHierarchy(); const clickedIndex = flattened.findIndex((flatItem) => getItemId(flatItem) === itemId); @@ -674,14 +752,57 @@ export const AssetOutline = () => { ] ); + const handleRename = useCallback( + (item: AssetGroupChild, newName: string) => { + if (isGroup(item)) { + console.log("Rename group:", item.groupUuid, "to:", newName); + } else { + const asset = getAssetById(item.modelUuid); + if (!asset) return; + const oldName = asset.modelName; + + const undoActions: UndoRedo3DAction[] = []; + const assetsToUpdate: AssetData[] = []; + + assetsToUpdate.push({ + type: "Asset", + assetData: { ...asset, modelName: oldName }, + newData: { ...asset, modelName: newName }, + timeStap: new Date().toISOString(), + }); + + if (assetsToUpdate.length > 0) { + if (assetsToUpdate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Update", + asset: assetsToUpdate[0], + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Update", + assets: assetsToUpdate, + }); + } + + push3D({ + type: "Scene", + actions: undoActions, + }); + } + + handleAssetRenameUpdate(asset, newName); + } + }, + [selectedVersion, builderSocket, projectId, userId, organization] + ); + const handleOptionClick = useCallback( (option: string, item: AssetGroupChild) => { - const getItemId = (i: AssetGroupChild): string => { - return isGroup(i) ? i.groupUuid : i.modelUuid; - }; - if (option === "visibility") { if (isGroup(item)) { + toggleGroupVisibility(item.groupUuid); } else { const undoActions: UndoRedo3DAction[] = []; const assetsToUpdate: AssetData[] = []; @@ -724,6 +845,7 @@ export const AssetOutline = () => { } } else if (option === "lock") { if (isGroup(item)) { + toggleGroupLock(item.groupUuid); } else { const undoActions: UndoRedo3DAction[] = []; const assetsToUpdate: AssetData[] = []; @@ -790,7 +912,7 @@ export const AssetOutline = () => { const handleAddGroup = useCallback(() => {}, [assetGroupStore, clearSelectedGroups, addSelectedGroup]); - const handleExpandAll = useCallback(() => {}, [assetGroupStore, setGroupExpanded]); + const handleCollapseAll = useCallback(() => {}, [assetGroupStore, setGroupExpanded]); // Selection statistics const selectionStats = useMemo(() => { @@ -824,7 +946,7 @@ export const AssetOutline = () => { -