Refactor DashboardEditor to improve block and element handling, including backend updates and drag-and-drop functionality. Adjust minimum block size constraints and enhance asset visibility checks in the asset group store. Update socket initialization to streamline project connections. Modify styles for element dropdown transitions.

This commit is contained in:
2025-10-17 15:19:12 +05:30
parent ba615cefed
commit 68899162b2
11 changed files with 452 additions and 232 deletions

View File

@@ -3,6 +3,7 @@ import React, { useState, useRef, useEffect } from "react";
import { dataModelManager } from "./data/dataModel"; import { dataModelManager } from "./data/dataModel";
import ControlPanel from "./ControlPanel"; import ControlPanel from "./ControlPanel";
import SwapModal from "./SwapModal"; import SwapModal from "./SwapModal";
import { Block } from "../../types/exportedTypes";
import DataModelPanel from "./components/models/DataModelPanel"; import DataModelPanel from "./components/models/DataModelPanel";
import { useSceneContext } from "../../modules/scene/sceneContext"; import { useSceneContext } from "../../modules/scene/sceneContext";
@@ -77,23 +78,35 @@ const DashboardEditor: React.FC = () => {
const currentBlock = blocks.find((b) => b.blockUuid === selectedBlock); const currentBlock = blocks.find((b) => b.blockUuid === selectedBlock);
const currentElement = currentBlock?.elements.find((el) => el.elementUuid === selectedElement); const currentElement = currentBlock?.elements.find((el) => el.elementUuid === selectedElement);
useEffect(() => {
console.log("blocks: ", blocks);
}, [blocks]);
useEffect(() => { useEffect(() => {
if (!projectId || !selectedVersion) return; if (!projectId || !selectedVersion) return;
// getDashBoardBlocksApi(projectId, selectedVersion.versionId).then((data) => { getDashBoardBlocksApi(projectId, selectedVersion.versionId).then((data) => {
// if (data.data && data.data.blocks) { if (data.data?.blocks) {
// console.log("data: ", data); console.log("data.data.blocks: ", data.data.blocks);
// setBlocks(data.data.blocks); setBlocks(data.data.blocks);
// } }
// }); });
}, [projectId, selectedVersion]); }, [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(() => { useEffect(() => {
const unsubscribe = subscribe(() => { const unsubscribe = subscribe(() => {
if (!projectId || !selectedVersion) return; if (!projectId || !selectedVersion) return;
updateBackend(blocks);
// upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => {
// console.log("data: ", data);
// });
}); });
return () => { return () => {
@@ -103,10 +116,11 @@ const DashboardEditor: React.FC = () => {
useCallBackOnKey( useCallBackOnKey(
() => { () => {
console.log(blocks); if (!projectId || !selectedVersion) return;
updateBackend(blocks);
}, },
"Ctrl+S", "Ctrl+S",
{ dependencies: [blocks], noRepeat: true, allowOnInput: true } { dependencies: [blocks, projectId, selectedVersion], noRepeat: true, allowOnInput: true }
); );
// Subscribe to data model changes // Subscribe to data model changes
@@ -118,27 +132,27 @@ const DashboardEditor: React.FC = () => {
const keys = dataModelManager.getAvailableKeys(); const keys = dataModelManager.getAvailableKeys();
const subscriptions: Array<[string, () => void]> = []; const subscriptions: Array<[string, () => void]> = [];
keys.forEach((key) => { for (const key of keys) {
const callback = () => handleDataChange(); const callback = () => handleDataChange();
dataModelManager.subscribe(key, callback); dataModelManager.subscribe(key, callback);
subscriptions.push([key, callback]); subscriptions.push([key, callback]);
}); }
const interval = setInterval(() => { const interval = setInterval(() => {
const currentKeys = dataModelManager.getAvailableKeys(); const currentKeys = dataModelManager.getAvailableKeys();
const newKeys = currentKeys.filter((key) => !keys.includes(key)); const newKeys = currentKeys.filter((key) => !keys.includes(key));
newKeys.forEach((key) => { for (const key of newKeys) {
const callback = () => handleDataChange(); const callback = () => handleDataChange();
dataModelManager.subscribe(key, callback); dataModelManager.subscribe(key, callback);
subscriptions.push([key, callback]); subscriptions.push([key, callback]);
}); }
}, 1000); }, 1000);
return () => { return () => {
subscriptions.forEach(([key, callback]) => { for (const [key, callback] of subscriptions) {
dataModelManager.unsubscribe(key, callback); dataModelManager.unsubscribe(key, callback);
}); }
clearInterval(interval); clearInterval(interval);
}; };
}, []); }, []);
@@ -239,7 +253,7 @@ useEffect(() => {
const deltaY = e.clientY - resizeStart.y; const deltaY = e.clientY - resizeStart.y;
const currentBlock = blocks.find((b) => b.blockUuid === resizingBlock); const currentBlock = blocks.find((b) => b.blockUuid === resizingBlock);
const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 300, height: 200 }; const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 100, height: 50 };
const newWidth = Math.max(minSize.width, resizeStart.width + deltaX); const newWidth = Math.max(minSize.width, resizeStart.width + deltaX);
const newHeight = Math.max(minSize.height, resizeStart.height + deltaY); const newHeight = Math.max(minSize.height, resizeStart.height + deltaY);

View File

@@ -1,8 +1,8 @@
import type { Block } from "../../../../types/exportedTypes"; import type { Block } from "../../../../types/exportedTypes";
export const calculateMinBlockSize = (block: Block): Size => { export const calculateMinBlockSize = (block: Block): Size => {
let minWidth = 300; let minWidth = 100;
let minHeight = 200; let minHeight = 50;
block.elements.forEach((element) => { block.elements.forEach((element) => {
if (element.positionType === "absolute") { if (element.positionType === "absolute") {

View File

@@ -30,7 +30,6 @@ export const handleBlockDragStart = (
setDraggingBlock: (blockId: string | null) => void, setDraggingBlock: (blockId: string | null) => void,
setBlockDragOffset: (offset: Position) => void // Change to specific offset setBlockDragOffset: (offset: Position) => void // Change to specific offset
): void => { ): void => {
console.log("Block drag start:", blockId);
setDraggingBlock(blockId); setDraggingBlock(blockId);
const element = event.currentTarget as HTMLElement; const element = event.currentTarget as HTMLElement;
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();

View File

@@ -32,6 +32,7 @@ const TreeNode = ({
onDrop, onDrop,
onToggleExpand, onToggleExpand,
onOptionClick, onOptionClick,
onRename,
}: { }: {
item: AssetGroupChild; item: AssetGroupChild;
level?: number; level?: number;
@@ -43,6 +44,7 @@ const TreeNode = ({
onClick: (e: React.MouseEvent, selectedItem: AssetGroupChild) => void; onClick: (e: React.MouseEvent, selectedItem: AssetGroupChild) => void;
onToggleExpand: (groupUuid: string, newExpanded: boolean) => void; onToggleExpand: (groupUuid: string, newExpanded: boolean) => void;
onOptionClick: (option: string, item: AssetGroupChild) => void; onOptionClick: (option: string, item: AssetGroupChild) => void;
onRename: (item: AssetGroupChild, newName: string) => void;
}) => { }) => {
const { assetGroupStore, assetStore } = useSceneContext(); const { assetGroupStore, assetStore } = useSceneContext();
const { hasSelectedAsset, selectedAssets } = assetStore(); const { hasSelectedAsset, selectedAssets } = assetStore();
@@ -136,7 +138,13 @@ const TreeNode = ({
<div className="node-icon">{isGroupNode ? <FolderIcon isOpen={isExpanded} /> : <CubeIcon />}</div> <div className="node-icon">{isGroupNode ? <FolderIcon isOpen={isExpanded} /> : <CubeIcon />}</div>
<RenameInput value={itemName} onRename={() => {}} canEdit={true} /> <RenameInput
value={itemName}
onRename={(newName) => {
onRename(item, newName);
}}
canEdit={true}
/>
<div className="node-controls"> <div className="node-controls">
<button className="control-button" title={isVisible ? "Visible" : "Hidden"} onClick={(e) => handleOptionClick(e, "visibility")}> <button className="control-button" title={isVisible ? "Visible" : "Hidden"} onClick={(e) => handleOptionClick(e, "visibility")}>
@@ -171,6 +179,7 @@ const TreeNode = ({
onDrop={onDrop} onDrop={onDrop}
onToggleExpand={onToggleExpand} onToggleExpand={onToggleExpand}
onOptionClick={onOptionClick} onOptionClick={onOptionClick}
onRename={onRename}
/> />
))} ))}
</div> </div>
@@ -195,6 +204,7 @@ export const AssetOutline = () => {
const { const {
groupHierarchy, groupHierarchy,
isGroup, isGroup,
getItemId,
getGroupsContainingAsset, getGroupsContainingAsset,
getFlatGroupChildren, getFlatGroupChildren,
setGroupExpanded, setGroupExpanded,
@@ -207,6 +217,8 @@ export const AssetOutline = () => {
toggleSelectedGroup, toggleSelectedGroup,
clearSelectedGroups, clearSelectedGroups,
hasSelectedGroup, hasSelectedGroup,
toggleGroupVisibility,
toggleGroupLock,
} = assetGroupStore(); } = assetGroupStore();
const { projectId } = useParams(); const { projectId } = useParams();
const { push3D } = undoRedo3DStore(); const { push3D } = undoRedo3DStore();
@@ -232,6 +244,77 @@ export const AssetOutline = () => {
return flattened; return flattened;
}, [groupHierarchy, isGroup]); }, [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) => { const handleAssetVisibilityUpdate = async (asset: Asset | null) => {
if (!asset) return; if (!asset) return;
@@ -556,11 +639,6 @@ export const AssetOutline = () => {
if (!scene.current) return; 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 itemId = getItemId(item);
const flattened = getFlattenedHierarchy(); const flattened = getFlattenedHierarchy();
const clickedIndex = flattened.findIndex((flatItem) => getItemId(flatItem) === itemId); 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( const handleOptionClick = useCallback(
(option: string, item: AssetGroupChild) => { (option: string, item: AssetGroupChild) => {
const getItemId = (i: AssetGroupChild): string => {
return isGroup(i) ? i.groupUuid : i.modelUuid;
};
if (option === "visibility") { if (option === "visibility") {
if (isGroup(item)) { if (isGroup(item)) {
toggleGroupVisibility(item.groupUuid);
} else { } else {
const undoActions: UndoRedo3DAction[] = []; const undoActions: UndoRedo3DAction[] = [];
const assetsToUpdate: AssetData[] = []; const assetsToUpdate: AssetData[] = [];
@@ -724,6 +845,7 @@ export const AssetOutline = () => {
} }
} else if (option === "lock") { } else if (option === "lock") {
if (isGroup(item)) { if (isGroup(item)) {
toggleGroupLock(item.groupUuid);
} else { } else {
const undoActions: UndoRedo3DAction[] = []; const undoActions: UndoRedo3DAction[] = [];
const assetsToUpdate: AssetData[] = []; const assetsToUpdate: AssetData[] = [];
@@ -790,7 +912,7 @@ export const AssetOutline = () => {
const handleAddGroup = useCallback(() => {}, [assetGroupStore, clearSelectedGroups, addSelectedGroup]); const handleAddGroup = useCallback(() => {}, [assetGroupStore, clearSelectedGroups, addSelectedGroup]);
const handleExpandAll = useCallback(() => {}, [assetGroupStore, setGroupExpanded]); const handleCollapseAll = useCallback(() => {}, [assetGroupStore, setGroupExpanded]);
// Selection statistics // Selection statistics
const selectionStats = useMemo(() => { const selectionStats = useMemo(() => {
@@ -824,7 +946,7 @@ export const AssetOutline = () => {
<button className="toolbar-button" title="Add Group" onClick={handleAddGroup}> <button className="toolbar-button" title="Add Group" onClick={handleAddGroup}>
<AddIcon /> <AddIcon />
</button> </button>
<button className="toolbar-button" title="Expand All" onClick={handleExpandAll}> <button className="toolbar-button" title="Collapse All" onClick={handleCollapseAll}>
<CollapseAllIcon /> <CollapseAllIcon />
</button> </button>
<button className="close-button" onClick={() => setIsOpen(!isOpen)}> <button className="close-button" onClick={() => setIsOpen(!isOpen)}>
@@ -847,6 +969,7 @@ export const AssetOutline = () => {
onClick={handleClick} onClick={handleClick}
onToggleExpand={handleToggleExpand} onToggleExpand={handleToggleExpand}
onOptionClick={handleOptionClick} onOptionClick={handleOptionClick}
onRename={handleRename}
/> />
))} ))}
</div> </div>

View File

@@ -27,8 +27,9 @@ export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundi
const { selectedEventData, setSelectedEventData } = useSelectedEventData(); const { selectedEventData, setSelectedEventData } = useSelectedEventData();
const { setSelectedAction, selectedAction } = useSelectedAction(); const { setSelectedAction, selectedAction } = useSelectedAction();
const { builderSocket, simulationSocket } = useSocketStore(); const { builderSocket, simulationSocket } = useSocketStore();
const { eventStore, productStore, undoRedo3DStore, versionStore, assetStore } = useSceneContext(); const { eventStore, productStore, undoRedo3DStore, versionStore, assetStore, assetGroupStore } = useSceneContext();
const { toggleSelectedAsset, selectedAssets, clearSelectedAssets } = assetStore(); const { toggleSelectedAsset, selectedAssets, clearSelectedAssets } = assetStore();
const { isAssetVisible } = assetGroupStore();
const { push3D } = undoRedo3DStore(); const { push3D } = undoRedo3DStore();
const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { resourceManagementId, setResourceManagementId } = useResourceManagementId(); const { resourceManagementId, setResourceManagementId } = useResourceManagementId();
@@ -95,7 +96,9 @@ export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundi
(toolMode === "cursor" || toolMode === "Move-Asset" || toolMode === "Rotate-Asset") && (toolMode === "cursor" || toolMode === "Move-Asset" || toolMode === "Rotate-Asset") &&
boundingBox && boundingBox &&
groupRef.current && groupRef.current &&
(activeModule === "builder" || (activeModule === "simulation" && resourceManagementId)) (activeModule === "builder" || (activeModule === "simulation" && resourceManagementId)) &&
asset.isVisible &&
isAssetVisible(asset.modelUuid)
) { ) {
zoomMeshes([asset.modelUuid]); zoomMeshes([asset.modelUuid]);

View File

@@ -13,17 +13,14 @@ import { getAssetFieldApi } from "../../../../../services/builder/asset/floorAss
import { ModelAnimator } from "./animator/modelAnimator"; import { ModelAnimator } from "./animator/modelAnimator";
import { useModelEventHandlers } from "./eventHandlers/useModelEventHandlers"; import { useModelEventHandlers } from "./eventHandlers/useModelEventHandlers";
function Model({ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) {
asset,
isRendered,
loader,
}: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const savedTheme: string = localStorage.getItem("theme") || "light"; const savedTheme: string = localStorage.getItem("theme") || "light";
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { toggleView } = useToggleView(); const { toggleView } = useToggleView();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { assetStore, layout } = useSceneContext(); const { assetStore, assetGroupStore } = useSceneContext();
const { isAssetVisible } = assetGroupStore();
const { resetAnimation, hasSelectedAsset, updateSelectedAsset, selectedAssets } = assetStore(); const { resetAnimation, hasSelectedAsset, updateSelectedAsset, selectedAssets } = assetStore();
const { setDeletableFloorAsset } = useBuilderStore(); const { setDeletableFloorAsset } = useBuilderStore();
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null); const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
@@ -56,12 +53,7 @@ function Model({
}, [activeModule, toolMode, selectedAssets]); }, [activeModule, toolMode, selectedAssets]);
useEffect(() => { useEffect(() => {
if ( if (groupRef.current && selectedAssets.length === 1 && selectedAssets[0].userData.modelUuid === asset.modelUuid && hasSelectedAsset(asset.modelUuid)) {
groupRef.current &&
selectedAssets.length === 1 &&
selectedAssets[0].userData.modelUuid === asset.modelUuid &&
hasSelectedAsset(asset.modelUuid)
) {
updateSelectedAsset(groupRef.current); updateSelectedAsset(groupRef.current);
} }
}, [isRendered, selectedAssets, asset]); }, [isRendered, selectedAssets, asset]);
@@ -139,10 +131,7 @@ function Model({
logModelStatus(assetId, "backend-loaded"); logModelStatus(assetId, "backend-loaded");
}) })
.catch((error) => { .catch((error) => {
console.error( console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
`[Backend] Error storing/loading ${asset.modelName}:`,
error
);
}); });
}, },
undefined, undefined,
@@ -156,8 +145,7 @@ function Model({
}); });
}, []); }, []);
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef, asset });
useModelEventHandlers({ boundingBox, groupRef, asset });
return ( return (
<group <group
@@ -205,30 +193,14 @@ function Model({
<> <>
{isRendered ? ( {isRendered ? (
<> <>
<primitive visible={asset.isVisible} object={gltfScene} /> <primitive visible={asset.isVisible && isAssetVisible(asset.modelUuid)} object={gltfScene} />
<ModelAnimator asset={asset} gltfScene={gltfScene} /> <ModelAnimator asset={asset} gltfScene={gltfScene} />
</> </>
) : ( ) : (
<> <>{!hasSelectedAsset(asset.modelUuid) && <AssetBoundingBox name="Asset Fallback" boundingBox={boundingBox} color="gray" lineWidth={2.5} />}</>
{!hasSelectedAsset(asset.modelUuid) && (
<AssetBoundingBox
name="Asset Fallback"
boundingBox={boundingBox}
color="gray"
lineWidth={2.5}
/>
)}
</>
)}
{hasSelectedAsset(asset.modelUuid) && (
<AssetBoundingBox
name="Asset BBox"
boundingBox={boundingBox}
color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"}
lineWidth={2.7}
/>
)} )}
{hasSelectedAsset(asset.modelUuid) && <AssetBoundingBox name="Asset BBox" boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />}
</> </>
)} )}
</group> </group>

View File

@@ -24,9 +24,10 @@ const SelectionControls3D: React.FC = () => {
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { builderSocket, simulationSocket } = useSocketStore(); const { builderSocket, simulationSocket } = useSocketStore();
const { contextAction, setContextAction } = useContextActionStore(); const { contextAction, setContextAction } = useContextActionStore();
const { assetStore, productStore, undoRedo3DStore, versionStore } = useSceneContext(); const { assetStore, productStore, undoRedo3DStore, versionStore, assetGroupStore } = useSceneContext();
const { selectedDecal, selectedWall, selectedAisle, selectedFloor, selectedWallAsset } = useBuilderStore(); const { selectedDecal, selectedWall, selectedAisle, selectedFloor, selectedWallAsset } = useBuilderStore();
const { push3D, undoStack, redoStack } = undoRedo3DStore(); const { push3D, undoStack, redoStack } = undoRedo3DStore();
const { isAssetVisible } = assetGroupStore();
const { selectedProduct, updateEvent, deleteEvent } = productStore(); const { selectedProduct, updateEvent, deleteEvent } = productStore();
const { const {
assets, assets,
@@ -253,7 +254,13 @@ const SelectionControls3D: React.FC = () => {
let currentObject: THREE.Object3D | null = object; let currentObject: THREE.Object3D | null = object;
while (currentObject) { while (currentObject) {
// if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType) { // if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType) {
if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType && currentObject.userData.isVisible && currentObject.visible) { if (
currentObject.userData.modelUuid &&
!currentObject.userData.wallAssetType &&
currentObject.userData.isVisible &&
currentObject.visible &&
isAssetVisible(currentObject.userData.modelUuid)
) {
Objects.add(currentObject); Objects.add(currentObject);
break; break;
} }

View File

@@ -77,13 +77,10 @@ const Project: React.FC = () => {
}, [projectId]); }, [projectId]);
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("token"); if (projectId) {
const refreshToken = localStorage.getItem("refreshToken");
if (token && refreshToken && projectId) {
echo.warn("Validating token"); echo.warn("Validating token");
initializeBuilderSocket(token, refreshToken, projectId); initializeBuilderSocket(projectId);
initializeSimulationSocket(token, refreshToken, projectId); initializeSimulationSocket(projectId);
echo.success("Builder socket initialized"); echo.success("Builder socket initialized");
} else { } else {
navigate("/"); navigate("/");
@@ -100,11 +97,8 @@ const Project: React.FC = () => {
}, [projectId, builderSocket]); }, [projectId, builderSocket]);
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("token"); if (projectId) {
const refreshToken = localStorage.getItem("refreshToken"); initializeVisualizationSocket(projectId);
if (token && refreshToken && projectId) {
initializeVisualizationSocket(token, refreshToken, projectId);
echo.success("Visualization socket initialized"); echo.success("Visualization socket initialized");
} else { } else {
navigate("/"); navigate("/");
@@ -121,11 +115,8 @@ const Project: React.FC = () => {
}, [projectId, visualizationSocket]); }, [projectId, visualizationSocket]);
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("token"); if (projectId) {
const refreshToken = localStorage.getItem("refreshToken"); initializeThreadSocket(projectId);
if (token && refreshToken && projectId) {
initializeThreadSocket(token, refreshToken, projectId);
echo.success("Thread socket initialized"); echo.success("Thread socket initialized");
} else { } else {
navigate("/"); navigate("/");
@@ -142,11 +133,8 @@ const Project: React.FC = () => {
}, [projectId, threadSocket]); }, [projectId, threadSocket]);
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("token"); if (projectId) {
const refreshToken = localStorage.getItem("refreshToken"); initializeSimulationSocket(projectId);
if (token && refreshToken && projectId) {
initializeSimulationSocket(token, refreshToken, projectId);
echo.success("Simulation socket initialized"); echo.success("Simulation socket initialized");
} else { } else {
navigate("/"); navigate("/");

View File

@@ -27,8 +27,9 @@ interface AssetGroupStore {
// Group properties // Group properties
setGroupName: (groupUuid: string, newName: string) => void; setGroupName: (groupUuid: string, newName: string) => void;
setGroupVisibility: (groupUuid: string, isVisible: boolean) => void;
setGroupLock: (groupUuid: string, isLocked: boolean) => void; setGroupLock: (groupUuid: string, isLocked: boolean) => void;
setGroupVisibility: (groupUuid: string, isVisible: boolean) => void;
toggleGroupLock: (groupUuid: string) => void;
toggleGroupVisibility: (groupUuid: string) => void; toggleGroupVisibility: (groupUuid: string) => void;
setGroupExpanded: (groupUuid: string, isExpanded: boolean) => void; setGroupExpanded: (groupUuid: string, isExpanded: boolean) => void;
@@ -46,7 +47,12 @@ interface AssetGroupStore {
getParentGroup: (item: AssetGroupChild) => string | null; getParentGroup: (item: AssetGroupChild) => string | null;
hasGroup: (groupUuid: string) => boolean; hasGroup: (groupUuid: string) => boolean;
isGroup: (item: AssetGroupChild) => item is AssetGroupHierarchyNode; isGroup: (item: AssetGroupChild) => item is AssetGroupHierarchyNode;
getItemId: (i: AssetGroupChild) => string;
isEmptyGroup: (groupUuid: string) => boolean; isEmptyGroup: (groupUuid: string) => boolean;
isAssetGroupVisible: (groupUuid: string) => boolean;
isAssetGroupLocked: (groupUuid: string) => boolean;
isAssetVisible: (assetUuid: string) => boolean;
isAssetLocked: (assetUuid: string) => boolean;
} }
export const createAssetGroupStore = () => { export const createAssetGroupStore = () => {
@@ -228,6 +234,15 @@ export const createAssetGroupStore = () => {
}); });
}, },
setGroupLock: (groupUuid, isLocked) => {
set((state) => {
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
if (group) {
group.isLocked = isLocked;
}
});
},
setGroupVisibility: (groupUuid, isVisible) => { setGroupVisibility: (groupUuid, isVisible) => {
set((state) => { set((state) => {
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid); const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
@@ -237,11 +252,11 @@ export const createAssetGroupStore = () => {
}); });
}, },
setGroupLock: (groupUuid, isLocked) => { toggleGroupLock: (groupUuid) => {
set((state) => { set((state) => {
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid); const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
if (group) { if (group) {
group.isLocked = isLocked; group.isLocked = !group.isLocked;
} }
}); });
}, },
@@ -455,10 +470,94 @@ export const createAssetGroupStore = () => {
return "children" in item; return "children" in item;
}, },
getItemId: (i: AssetGroupChild): string => {
return get().isGroup(i) ? i.groupUuid : i.modelUuid;
},
isEmptyGroup: (groupUuid) => { isEmptyGroup: (groupUuid) => {
const group = get().assetGroups.find((g) => g.groupUuid === groupUuid); const group = get().assetGroups.find((g) => g.groupUuid === groupUuid);
return !group || group.children.length === 0; return !group || group.children.length === 0;
}, },
isAssetGroupVisible: (groupUuid: string) => {
const state = get();
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
if (!group) return true; // Default to visible if group not found
// If this group is not visible, return false regardless of parents
if (!group.isVisible) return false;
// Check parent groups recursively
const parentGroups = state.getGroupsContainingGroup(groupUuid);
for (const parentGroup of parentGroups) {
if (!state.isAssetGroupVisible(parentGroup.groupUuid)) {
return false;
}
}
return true;
},
isAssetGroupLocked: (groupUuid: string) => {
const state = get();
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
if (!group) return false; // Default to unlocked if group not found
// If this group is locked, return true regardless of parents
if (group.isLocked) return true;
// Check parent groups recursively
const parentGroups = state.getGroupsContainingGroup(groupUuid);
for (const parentGroup of parentGroups) {
if (state.isAssetGroupLocked(parentGroup.groupUuid)) {
return true;
}
}
return false;
},
isAssetVisible: (assetUuid: string) => {
const state = get();
// Check if asset is in any groups
const parentGroups = state.getGroupsContainingAsset(assetUuid);
// If asset is not in any groups, it's always visible
if (parentGroups.length === 0) return true;
// Asset is visible only if ALL parent groups are visible
for (const parentGroup of parentGroups) {
if (!state.isAssetGroupVisible(parentGroup.groupUuid)) {
return false;
}
}
return true;
},
isAssetLocked: (assetUuid: string) => {
const state = get();
// Check if asset is in any groups
const parentGroups = state.getGroupsContainingAsset(assetUuid);
// If asset is not in any groups, check its own locked state
if (parentGroups.length === 0) {
// You might need access to the assets store to get the asset's own locked state
// For now, returning false as default
return false;
}
// Asset is locked if ANY parent group is locked
for (const parentGroup of parentGroups) {
if (state.isAssetGroupLocked(parentGroup.groupUuid)) {
return true;
}
}
return false;
},
})) }))
); );
}; };

View File

@@ -8,11 +8,11 @@ interface SocketStore {
threadSocket: Socket | null; threadSocket: Socket | null;
simulationSocket: Socket | null; simulationSocket: Socket | null;
initializeBuilderSocket: (token: string, refreshToken: string, projectId: string) => void; initializeBuilderSocket: (projectId: string) => void;
initializeVisualizationSocket: (token: string, refreshToken: string, projectId: string) => void; initializeVisualizationSocket: (projectId: string) => void;
initializeThreadSocket: (token: string, refreshToken: string, projectId: string) => void; initializeThreadSocket: (projectId: string) => void;
initializeProjectSocket: (token: string, refreshToken: string) => void; initializeProjectSocket: (token: string, refreshToken: string) => void;
initializeSimulationSocket: (token: string, refreshToken: string, projectId: string) => void; initializeSimulationSocket: (projectId: string) => void;
disconnectBuilderSocket: (projectId: string) => void; disconnectBuilderSocket: (projectId: string) => void;
disconnectVisualizationSocket: (projectId: string) => void; disconnectVisualizationSocket: (projectId: string) => void;
@@ -56,36 +56,51 @@ export const useSocketStore = create<SocketStore>((set, get) => ({
threadSocket: null, threadSocket: null,
simulationSocket: null, simulationSocket: null,
initializeBuilderSocket: (token, refreshToken, projectId) => { initializeBuilderSocket: (projectId) => {
if (get().builderSocket) return; const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken");
if (get().builderSocket || !token || !refreshToken) return;
const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, socketOptions({ token, refreshToken, projectId })); const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, socketOptions({ token, refreshToken, projectId }));
attachLogs("Builder", builderSocket); attachLogs("Builder", builderSocket);
set({ builderSocket }); set({ builderSocket });
}, },
initializeVisualizationSocket: (token, refreshToken, projectId) => { initializeVisualizationSocket: (projectId) => {
if (get().visualizationSocket) return; const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken");
if (get().visualizationSocket || !token || !refreshToken) return;
const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, socketOptions({ token, refreshToken, projectId })); const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, socketOptions({ token, refreshToken, projectId }));
attachLogs("Visualization", visualizationSocket); attachLogs("Visualization", visualizationSocket);
set({ visualizationSocket }); set({ visualizationSocket });
}, },
initializeThreadSocket: (token, refreshToken, projectId) => { initializeThreadSocket: (projectId) => {
if (get().threadSocket) return; const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken");
if (get().threadSocket || !token || !refreshToken) return;
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId })); const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId }));
attachLogs("Thread", threadSocket); attachLogs("Thread", threadSocket);
set({ threadSocket }); set({ threadSocket });
}, },
initializeProjectSocket: (token, refreshToken) => { initializeProjectSocket: () => {
if (get().projectSocket) return; const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken");
if (get().projectSocket || !token || !refreshToken) return;
const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, socketOptions({ token, refreshToken })); const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, socketOptions({ token, refreshToken }));
attachLogs("Project", projectSocket); attachLogs("Project", projectSocket);
set({ projectSocket }); set({ projectSocket });
}, },
initializeSimulationSocket: (token, refreshToken, projectId) => { initializeSimulationSocket: (projectId) => {
if (get().simulationSocket) return; const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken");
if (get().simulationSocket || !token || !refreshToken) return;
const simulationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Simulation_v1`, socketOptions({ token, refreshToken, projectId })); const simulationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Simulation_v1`, socketOptions({ token, refreshToken, projectId }));
attachLogs("Simulation", simulationSocket); attachLogs("Simulation", simulationSocket);
set({ simulationSocket }); set({ simulationSocket });

View File

@@ -151,7 +151,7 @@
margin: 5px; margin: 5px;
border-radius: 4px; border-radius: 4px;
padding: 8px; padding: 8px;
transition: all 0.2s ease; // transition: all 0.2s ease;
user-select: none; user-select: none;
border: 1px solid transparent; border: 1px solid transparent;