Merge remote-tracking branch 'origin/main-dev' into main-demo
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||
import { RemoveIcon } from "../../../icons/ExportCommonIcons";
|
||||
@@ -6,6 +7,12 @@ import PositionInput from "../customInput/PositionInputs";
|
||||
import RotationInput from "../customInput/RotationInput";
|
||||
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||
import { useContextActionStore } from "../../../../store/builder/store";
|
||||
import { useSocketStore } from "../../../../store/socket/useSocketStore";
|
||||
import useAssetResponseHandler from "../../../../modules/collaboration/responseHandler/useAssetResponseHandler";
|
||||
|
||||
import { getUserData } from "../../../../functions/getUserData";
|
||||
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
|
||||
interface UserData {
|
||||
id: number;
|
||||
@@ -15,10 +22,17 @@ interface UserData {
|
||||
|
||||
const AssetProperties: React.FC = () => {
|
||||
const [userData, setUserData] = useState<UserData[]>([]);
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets, setCurrentAnimation, selectedAssets, getAssetById } = assetStore();
|
||||
const { assetStore, undoRedo3DStore, versionStore } = useSceneContext();
|
||||
const { assets, setCurrentAnimation, selectedAssets, getAssetById, peekToggleVisibility, peekToggleLock } = assetStore();
|
||||
const { loopAnimation } = useBuilderStore();
|
||||
const [hoveredIndex, setHoveredIndex] = useState<any>(null);
|
||||
const { setContextAction } = useContextActionStore();
|
||||
const { projectId } = useParams();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { builderSocket } = useSocketStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { updateAssetInScene } = useAssetResponseHandler();
|
||||
|
||||
const asset = getAssetById(selectedAssets[0]?.uuid);
|
||||
|
||||
@@ -36,6 +50,228 @@ const AssetProperties: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssetVisibilityUpdate = async (asset: Asset | undefined) => {
|
||||
if (!asset) return;
|
||||
|
||||
if (!builderSocket?.connected) {
|
||||
setAssetsApi({
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
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 ${asset.isVisible ? "hiding" : "unhiding"} 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(`${asset.isVisible ? "Hid" : "Unhid"} asset: ${model.modelName}`);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
||||
});
|
||||
} else {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
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 handleAssetLockUpdate = async (asset: Asset | null) => {
|
||||
if (!asset) return;
|
||||
|
||||
if (!builderSocket?.connected) {
|
||||
setAssetsApi({
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
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 ${asset.isVisible ? "locking" : "unlocking"} 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(`${asset.isVisible ? "Locked" : "Unlocked"} asset: ${model.modelName}`);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error ${asset.isVisible ? "locking" : "unlocking"} asset: ${asset.modelName}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error ${asset.isVisible ? "locking" : "unlocking"} asset: ${asset.modelName}`);
|
||||
});
|
||||
} else {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
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 handleOptionClick = useCallback(
|
||||
(option: string) => {
|
||||
if (!asset) return;
|
||||
if (option === "visibility") {
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
const assetUuid = asset.modelUuid;
|
||||
|
||||
const updatedAsset = peekToggleVisibility(assetUuid);
|
||||
if (!updatedAsset) return;
|
||||
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
...asset,
|
||||
isVisible: asset.isVisible,
|
||||
},
|
||||
newData: {
|
||||
...asset,
|
||||
isVisible: updatedAsset.isVisible,
|
||||
},
|
||||
timeStap: new Date().toISOString(),
|
||||
});
|
||||
|
||||
handleAssetVisibilityUpdate(updatedAsset);
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
} else if (option === "lock") {
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
const assetUuid = asset.modelUuid;
|
||||
|
||||
const updatedAsset = peekToggleLock(assetUuid);
|
||||
if (!updatedAsset) return;
|
||||
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: { ...asset, isLocked: asset.isLocked },
|
||||
newData: { ...asset, isLocked: updatedAsset.isLocked },
|
||||
timeStap: new Date().toISOString(),
|
||||
});
|
||||
|
||||
handleAssetLockUpdate(updatedAsset);
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedVersion, builderSocket, projectId, userId, organization, asset]
|
||||
);
|
||||
|
||||
if (selectedAssets.length !== 1 || !asset) return null;
|
||||
|
||||
return (
|
||||
@@ -45,12 +281,39 @@ const AssetProperties: React.FC = () => {
|
||||
<section>
|
||||
<PositionInput disabled={true} onChange={() => {}} value1={parseFloat(asset.position[0]?.toFixed(5))} value2={parseFloat(asset.position[2]?.toFixed(5))} />
|
||||
<RotationInput disabled={true} onChange={() => {}} value={parseFloat(asset.rotation[1]?.toFixed(5))} />
|
||||
<div className="value-field-container">
|
||||
<div className="label">Flip 90°</div>
|
||||
<button
|
||||
id="pick-and-place-action-clear-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
setContextAction("flipAsset");
|
||||
}}
|
||||
>
|
||||
Flip
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="header">Render settings</div>
|
||||
<section>
|
||||
<InputToggle inputKey="visible" label="Visible" />
|
||||
<InputToggle inputKey="frustumCull" label="Frustum cull" />
|
||||
<InputToggle
|
||||
inputKey="visible"
|
||||
label="Visible"
|
||||
onClick={() => {
|
||||
handleOptionClick("visibility");
|
||||
}}
|
||||
value={asset.isVisible}
|
||||
/>
|
||||
<InputToggle
|
||||
inputKey="lock"
|
||||
label="Lock"
|
||||
onClick={() => {
|
||||
handleOptionClick("lock");
|
||||
}}
|
||||
value={asset.isLocked}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as THREE from "three";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { ThreeEvent, useThree } from "@react-three/fiber";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
@@ -13,12 +12,13 @@ import { useLeftData, useTopData } from "../../../../../../store/visualization/u
|
||||
import { useSelectedAction, useSelectedAsset, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { useBuilderStore } from "../../../../../../store/builder/useBuilderStore";
|
||||
import useAssetResponseHandler from "../../../../../collaboration/responseHandler/useAssetResponseHandler";
|
||||
import useZoomMesh from "../../../../hooks/useZoomMesh";
|
||||
|
||||
import { updateEventToBackend } from "../../../../../../components/layout/sidebarRight/properties/eventProperties/functions/handleUpdateEventToBackend";
|
||||
import { deleteFloorAssetApi } from "../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorAssetApi";
|
||||
|
||||
export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundingBox: THREE.Box3 | null; groupRef: React.RefObject<THREE.Group>; asset: Asset }) {
|
||||
const { controls, gl, camera } = useThree();
|
||||
const { gl } = useThree();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { toolMode } = useToolMode();
|
||||
const { activeModule } = useModuleStore();
|
||||
@@ -37,6 +37,7 @@ export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundi
|
||||
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { deletableFloorAsset, setDeletableFloorAsset } = useBuilderStore();
|
||||
const { removeAssetFromScene } = useAssetResponseHandler();
|
||||
const { zoomMeshes } = useZoomMesh();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization } = getUserData();
|
||||
@@ -96,45 +97,7 @@ export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundi
|
||||
groupRef.current &&
|
||||
(activeModule === "builder" || (activeModule === "simulation" && resourceManagementId))
|
||||
) {
|
||||
const frontView = false;
|
||||
if (frontView) {
|
||||
const size = boundingBox.getSize(new THREE.Vector3());
|
||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||
|
||||
const front = new THREE.Vector3(0, 0, 1);
|
||||
groupRef.current.localToWorld(front);
|
||||
front.sub(groupRef.current.position).normalize();
|
||||
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
const newPosition = center.clone().addScaledVector(front, distance);
|
||||
|
||||
(controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
||||
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
||||
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
||||
cover: true,
|
||||
paddingTop: 5,
|
||||
paddingLeft: 5,
|
||||
paddingBottom: 5,
|
||||
paddingRight: 5,
|
||||
});
|
||||
} else {
|
||||
const collisionPos = new THREE.Vector3();
|
||||
groupRef.current.getWorldPosition(collisionPos);
|
||||
const size = boundingBox.getSize(new THREE.Vector3());
|
||||
|
||||
const currentPos = new THREE.Vector3().copy(camera.position);
|
||||
|
||||
const target = new THREE.Vector3();
|
||||
if (!controls) return;
|
||||
(controls as CameraControls).getTarget(target);
|
||||
const direction = new THREE.Vector3().subVectors(target, currentPos).normalize();
|
||||
|
||||
const offsetDistance = 5;
|
||||
const newCameraPos = new THREE.Vector3().copy(collisionPos).sub(direction.multiplyScalar(offsetDistance));
|
||||
|
||||
camera.position.copy(newCameraPos);
|
||||
(controls as CameraControls).setLookAt(newCameraPos.x, size.y > newCameraPos.y ? size.y : newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true);
|
||||
}
|
||||
zoomMeshes([asset.modelUuid]);
|
||||
|
||||
if (groupRef.current) {
|
||||
toggleSelectedAsset(groupRef.current);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
import { Group, Vector3 } from "three";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { useLimitDistance, useRenderDistance } from "../../../../store/builder/store";
|
||||
import { GLTFLoader } from "three/examples/jsm/Addons";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
import { useContextActionStore, useLimitDistance, useRenderDistance } from "../../../../store/builder/store";
|
||||
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
|
||||
import { useSceneContext } from "../../../scene/sceneContext";
|
||||
import useZoomMesh from "../../hooks/useZoomMesh";
|
||||
import useCallBackOnKey from "../../../../utils/hooks/useCallBackOnKey";
|
||||
|
||||
import Model from "./model/model";
|
||||
import { GLTFLoader } from "three/examples/jsm/Addons";
|
||||
|
||||
const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
|
||||
|
||||
@@ -15,17 +17,36 @@ function Models({ loader }: { readonly loader: GLTFLoader }) {
|
||||
const { controls, camera } = useThree();
|
||||
const assetGroupRef = useRef<Group>(null);
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets, selectedAssets } = assetStore();
|
||||
const { assets, selectedAssets, getSelectedAssetUuids } = assetStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { contextAction, setContextAction } = useContextActionStore();
|
||||
const { limitDistance } = useLimitDistance();
|
||||
const { renderDistance } = useRenderDistance();
|
||||
const [renderMap, setRenderMap] = useState<Record<string, boolean>>({});
|
||||
const { zoomMeshes } = useZoomMesh();
|
||||
const cameraPos = useRef(new Vector3());
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(assets);
|
||||
}, [assets]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contextAction === "focusAsset") {
|
||||
zoomMeshes(getSelectedAssetUuids());
|
||||
setContextAction(null);
|
||||
}
|
||||
}, [contextAction]);
|
||||
|
||||
useCallBackOnKey(
|
||||
() => {
|
||||
if (selectedAssets.length > 0) {
|
||||
zoomMeshes(getSelectedAssetUuids());
|
||||
}
|
||||
},
|
||||
".",
|
||||
{ dependencies: [selectedAssets.length], noRepeat: true }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
distanceWorker.onmessage = (e) => {
|
||||
const { shouldRender, modelUuid } = e.data;
|
||||
@@ -57,7 +78,7 @@ function Models({ loader }: { readonly loader: GLTFLoader }) {
|
||||
ref={assetGroupRef}
|
||||
onPointerMissed={(e) => {
|
||||
e.stopPropagation();
|
||||
if (selectedAssets.length === 1) {
|
||||
if (selectedAssets.length > 0) {
|
||||
const target = (controls as CameraControls).getTarget(new Vector3());
|
||||
(controls as CameraControls).setTarget(target.x, 0, target.z, true);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as THREE from "three";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Geometry } from "@react-three/csg";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
|
||||
////////// Zustand State Imports //////////
|
||||
@@ -30,7 +31,7 @@ import Decal from "./Decal/decal";
|
||||
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
|
||||
|
||||
export default function Builder() {
|
||||
const { gl, scene } = useThree();
|
||||
const { gl, scene, camera, controls } = useThree();
|
||||
const plane = useRef<THREE.Mesh>(null);
|
||||
const csgRef = useRef<any>(null);
|
||||
|
||||
@@ -42,12 +43,14 @@ export default function Builder() {
|
||||
const { setRenderDistance } = useRenderDistance();
|
||||
const { setLimitDistance } = useLimitDistance();
|
||||
const { projectId } = useParams();
|
||||
const { scene: storeScene } = useSceneContext();
|
||||
const { scene: storeScene, camera: storeCamera, controls: storeControls } = useSceneContext();
|
||||
const { setHoveredPoint, setHoveredLine } = useBuilderStore();
|
||||
|
||||
useEffect(() => {
|
||||
storeScene.current = scene;
|
||||
}, [scene]);
|
||||
storeCamera.current = camera;
|
||||
storeControls.current = controls as CameraControls;
|
||||
}, [scene, camera, controls]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!toggleView) {
|
||||
|
||||
79
app/src/modules/builder/hooks/useZoomMesh.tsx
Normal file
79
app/src/modules/builder/hooks/useZoomMesh.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Box3, Object3D, Vector3, PerspectiveCamera } from "three";
|
||||
import { useCallback } from "react";
|
||||
import { useSceneContext } from "../../scene/sceneContext";
|
||||
|
||||
export function useZoomMesh() {
|
||||
const { scene, camera, controls } = useSceneContext();
|
||||
|
||||
const zoomMeshes = useCallback(
|
||||
(meshUuids: string[]) => {
|
||||
if (!scene.current || !camera.current || !controls.current) return;
|
||||
if (meshUuids.length === 0) return;
|
||||
|
||||
const models: Object3D[] = meshUuids.map((uuid) => scene.current!.getObjectByProperty("uuid", uuid)).filter((m): m is Object3D => m !== undefined);
|
||||
|
||||
if (models.length === 0) return;
|
||||
|
||||
const box = new Box3();
|
||||
models.forEach((model) => {
|
||||
box.expandByObject(model);
|
||||
});
|
||||
|
||||
const cam = camera.current;
|
||||
if (!(cam instanceof PerspectiveCamera)) {
|
||||
console.warn("Only PerspectiveCamera is supported for directional zoom.");
|
||||
return;
|
||||
}
|
||||
|
||||
const camPos = new Vector3();
|
||||
cam.getWorldPosition(camPos);
|
||||
const camDir = new Vector3(0, 0, -1).transformDirection(cam.matrixWorld);
|
||||
const boxCenter = box.getCenter(new Vector3());
|
||||
const fov = (cam.fov * Math.PI) / 180;
|
||||
const aspect = cam.aspect;
|
||||
const halfFovY = fov / 2;
|
||||
const halfFovX = Math.atan(Math.tan(halfFovY) * aspect);
|
||||
const camRight = new Vector3(1, 0, 0).transformDirection(cam.matrixWorld);
|
||||
const camUp = new Vector3(0, 1, 0).transformDirection(cam.matrixWorld);
|
||||
|
||||
const corners = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const corner = new Vector3();
|
||||
corner.x = i & 1 ? box.max.x : box.min.x;
|
||||
corner.y = i & 2 ? box.max.y : box.min.y;
|
||||
corner.z = i & 4 ? box.max.z : box.min.z;
|
||||
corners.push(corner);
|
||||
}
|
||||
|
||||
let maxRight = 0;
|
||||
let maxUp = 0;
|
||||
for (const corner of corners) {
|
||||
const toCorner = corner.clone().sub(boxCenter);
|
||||
const projRight = Math.abs(toCorner.dot(camRight));
|
||||
const projUp = Math.abs(toCorner.dot(camUp));
|
||||
maxRight = Math.max(maxRight, projRight);
|
||||
maxUp = Math.max(maxUp, projUp);
|
||||
}
|
||||
|
||||
const distX = maxRight / Math.sin(halfFovX);
|
||||
const distY = maxUp / Math.sin(halfFovY);
|
||||
const requiredDist = Math.max(distX, distY);
|
||||
const toBox = boxCenter.clone().sub(camPos);
|
||||
const currentDistAlongView = toBox.dot(camDir);
|
||||
|
||||
if (currentDistAlongView <= 0) {
|
||||
const newPos = boxCenter.clone().add(camDir.clone().multiplyScalar(requiredDist));
|
||||
controls.current.setLookAt(newPos.x, newPos.y, newPos.z, boxCenter.x, boxCenter.y, boxCenter.z, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetCamPos = boxCenter.clone().sub(camDir.clone().multiplyScalar(requiredDist));
|
||||
controls.current.setLookAt(targetCamPos.x, targetCamPos.y, targetCamPos.z, boxCenter.x, boxCenter.y, boxCenter.z, true);
|
||||
},
|
||||
[scene, camera, controls]
|
||||
);
|
||||
|
||||
return { zoomMeshes };
|
||||
}
|
||||
|
||||
export default useZoomMesh;
|
||||
@@ -55,6 +55,7 @@ const TreeNode = ({
|
||||
const { isGroup, getGroupsContainingAsset, getGroupsContainingGroup } = assetGroupStore();
|
||||
const isGroupNode = isGroup(item);
|
||||
|
||||
const itemId = isGroupNode ? item.groupUuid : item.modelUuid;
|
||||
const itemName = isGroupNode ? item.groupName : item.modelName;
|
||||
const isVisible = item.isVisible;
|
||||
const isLocked = item.isLocked;
|
||||
@@ -120,7 +121,7 @@ const TreeNode = ({
|
||||
const shouldShowHighlight = isDropTarget();
|
||||
|
||||
return (
|
||||
<div className={`tree-node ${shouldShowHighlight ? "drop-target-highlight" : ""}`}>
|
||||
<div key={itemId} className={`tree-node ${shouldShowHighlight ? "drop-target-highlight" : ""}`}>
|
||||
<div
|
||||
className={clsx("tree-node-content", {
|
||||
locked: isLocked,
|
||||
@@ -216,7 +217,7 @@ const TreeNode = ({
|
||||
|
||||
// Main Component
|
||||
export const Outline = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const lastSelectedRef = useRef<{ item: AssetGroupChild; index: number } | null>(null);
|
||||
const dragStateRef = useRef<DragState>({
|
||||
draggedItem: null,
|
||||
@@ -226,24 +227,8 @@ export const Outline = () => {
|
||||
});
|
||||
const [_, forceUpdate] = useState({});
|
||||
const { scene, assetGroupStore, assetStore, versionStore, undoRedo3DStore } = useSceneContext();
|
||||
const {
|
||||
addSelectedAsset,
|
||||
clearSelectedAssets,
|
||||
getAssetById,
|
||||
peekToggleVisibility,
|
||||
toggleSelectedAsset,
|
||||
selectedAssets,
|
||||
} = assetStore();
|
||||
const {
|
||||
groupHierarchy,
|
||||
isGroup,
|
||||
getGroupsContainingAsset,
|
||||
getFlatGroupChildren,
|
||||
setGroupExpanded,
|
||||
addChildToGroup,
|
||||
removeChildFromGroup,
|
||||
getGroupsContainingGroup,
|
||||
} = assetGroupStore();
|
||||
const { addSelectedAsset, clearSelectedAssets, getAssetById, peekToggleVisibility, peekToggleLock, toggleSelectedAsset, selectedAssets } = assetStore();
|
||||
const { groupHierarchy, isGroup, getGroupsContainingAsset, getFlatGroupChildren, setGroupExpanded, addChildToGroup, removeChildFromGroup, getGroupsContainingGroup } = assetGroupStore();
|
||||
const { projectId } = useParams();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { builderSocket } = useSocketStore();
|
||||
@@ -350,6 +335,77 @@ export const Outline = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssetLockUpdate = async (asset: Asset | null) => {
|
||||
if (!asset) return;
|
||||
|
||||
if (!builderSocket?.connected) {
|
||||
setAssetsApi({
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
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 ${asset.isVisible ? "locking" : "unlocking"} 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(`${asset.isVisible ? "Locked" : "Unlocked"} asset: ${model.modelName}`);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error ${asset.isVisible ? "locking" : "unlocking"} asset: ${asset.modelName}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error ${asset.isVisible ? "locking" : "unlocking"} asset: ${asset.modelName}`);
|
||||
});
|
||||
} else {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
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 handleToggleExpand = useCallback(
|
||||
(groupUuid: string, newExpanded: boolean) => {
|
||||
setGroupExpanded(groupUuid, newExpanded);
|
||||
@@ -646,14 +702,8 @@ export const Outline = () => {
|
||||
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
...asset,
|
||||
isVisible: asset.isVisible,
|
||||
},
|
||||
newData: {
|
||||
...asset,
|
||||
isVisible: updatedAsset.isVisible,
|
||||
},
|
||||
assetData: { ...asset, isVisible: asset.isVisible },
|
||||
newData: { ...asset, isVisible: updatedAsset.isVisible },
|
||||
timeStap: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -661,17 +711,9 @@ export const Outline = () => {
|
||||
|
||||
if (assetsToUpdate.length > 0) {
|
||||
if (assetsToUpdate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: assetsToUpdate[0],
|
||||
});
|
||||
undoActions.push({ module: "builder", actionType: "Asset-Update", asset: assetsToUpdate[0] });
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Update",
|
||||
assets: assetsToUpdate,
|
||||
});
|
||||
undoActions.push({ module: "builder", actionType: "Assets-Update", assets: assetsToUpdate });
|
||||
}
|
||||
|
||||
push3D({
|
||||
@@ -683,6 +725,36 @@ export const Outline = () => {
|
||||
} else if (option === "lock") {
|
||||
if (isGroup(item)) {
|
||||
} else {
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
const assetUuid = item.modelUuid;
|
||||
const asset = getAssetById(assetUuid);
|
||||
if (!asset) return;
|
||||
|
||||
const updatedAsset = peekToggleLock(assetUuid);
|
||||
if (!updatedAsset) return;
|
||||
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: { ...asset, isLocked: asset.isLocked },
|
||||
newData: { ...asset, isLocked: updatedAsset.isLocked },
|
||||
timeStap: new Date().toISOString(),
|
||||
});
|
||||
|
||||
handleAssetLockUpdate(updatedAsset);
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (option === "kebab") {
|
||||
if (isGroup(item)) {
|
||||
@@ -699,7 +771,7 @@ export const Outline = () => {
|
||||
<div className="outline-card">
|
||||
<div className="outline-header">
|
||||
<div className="header-title">
|
||||
<p>Scene Hierarchy</p>
|
||||
<p>Assets</p>
|
||||
</div>
|
||||
<div className="outline-toolbar">
|
||||
<button className="toolbar-button" title="Add Group">
|
||||
|
||||
@@ -42,7 +42,7 @@ const CutCopyPasteControls3D = () => {
|
||||
rotatedObjects,
|
||||
setRotatedObjects,
|
||||
} = assetStore();
|
||||
const { updateAssetInScene, removeAssetFromScene } = useAssetResponseHandler();
|
||||
const { addAssetToScene, removeAssetFromScene } = useAssetResponseHandler();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
@@ -369,13 +369,13 @@ const CutCopyPasteControls3D = () => {
|
||||
isVisible: asset.isVisible,
|
||||
};
|
||||
|
||||
if (asset.eventData) {
|
||||
let updatedEventData = JSON.parse(JSON.stringify(asset.eventData)) as EventsSchema;
|
||||
if (pastedAsset.userData.eventData) {
|
||||
let updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData)) as EventsSchema;
|
||||
updatedEventData.modelUuid = newFloorItem.modelUuid;
|
||||
|
||||
const eventData: any = {
|
||||
type: asset.eventData.type,
|
||||
subType: asset.eventData.subType || "",
|
||||
type: pastedAsset.userData.eventData.type,
|
||||
subType: pastedAsset.userData.eventData.subType || "",
|
||||
};
|
||||
|
||||
let points: THREE.Vector3[] = [];
|
||||
@@ -439,7 +439,7 @@ const CutCopyPasteControls3D = () => {
|
||||
opacity: newFloorItem.opacity,
|
||||
isLocked: newFloorItem.isLocked,
|
||||
isVisible: newFloorItem.isVisible,
|
||||
eventData: eventData,
|
||||
eventData: newFloorItem.eventData,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
projectId: projectId || "",
|
||||
})
|
||||
@@ -463,7 +463,7 @@ const CutCopyPasteControls3D = () => {
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
updateAssetInScene(model, () => {
|
||||
addAssetToScene(model, () => {
|
||||
echo.info(`Pasted asset: ${model.modelName}`);
|
||||
});
|
||||
} else {
|
||||
@@ -537,7 +537,7 @@ const CutCopyPasteControls3D = () => {
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
updateAssetInScene(model, () => {
|
||||
addAssetToScene(model, () => {
|
||||
echo.info(`Pasted asset: ${model.modelUuid}`);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -25,7 +25,7 @@ const DuplicationControls3D = () => {
|
||||
const { projectId } = useParams();
|
||||
const { assets, addAsset, removeAsset, getAssetById, selectedAssets, duplicatedObjects, setDuplicatedObjects, setPastedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects } =
|
||||
assetStore();
|
||||
const { updateAssetInScene, removeAssetFromScene } = useAssetResponseHandler();
|
||||
const { addAssetToScene, removeAssetFromScene } = useAssetResponseHandler();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
@@ -296,13 +296,13 @@ const DuplicationControls3D = () => {
|
||||
isVisible: asset.isVisible,
|
||||
};
|
||||
|
||||
if (asset.eventData) {
|
||||
let updatedEventData = JSON.parse(JSON.stringify(asset.eventData)) as EventsSchema;
|
||||
if (duplicatedAsset.userData.eventData) {
|
||||
let updatedEventData = JSON.parse(JSON.stringify(duplicatedAsset.userData.eventData)) as EventsSchema;
|
||||
updatedEventData.modelUuid = newFloorItem.modelUuid;
|
||||
|
||||
const eventData: any = {
|
||||
type: asset.eventData.type,
|
||||
subType: asset.eventData.subType || "",
|
||||
type: duplicatedAsset.userData.eventData.type,
|
||||
subType: duplicatedAsset.userData.eventData.subType || "",
|
||||
};
|
||||
|
||||
let points: THREE.Vector3[] = [];
|
||||
@@ -364,7 +364,7 @@ const DuplicationControls3D = () => {
|
||||
opacity: newFloorItem.opacity,
|
||||
isLocked: newFloorItem.isLocked,
|
||||
isVisible: newFloorItem.isVisible,
|
||||
eventData: eventData,
|
||||
eventData: newFloorItem.eventData,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
projectId: projectId || "",
|
||||
})
|
||||
@@ -388,7 +388,7 @@ const DuplicationControls3D = () => {
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
updateAssetInScene(model, () => {
|
||||
addAssetToScene(model, () => {
|
||||
echo.info(`Duplicated asset: ${model.modelName}`);
|
||||
});
|
||||
} else {
|
||||
@@ -460,7 +460,7 @@ const DuplicationControls3D = () => {
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
updateAssetInScene(model, () => {
|
||||
addAssetToScene(model, () => {
|
||||
echo.info(`Duplicated asset: ${model.modelUuid}`);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ import useCallBackOnKey from "../../../../utils/hooks/useCallBackOnKey";
|
||||
|
||||
import generateUniqueAssetGroupName from "../../../builder/asset/functions/generateUniqueAssetGroupName";
|
||||
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
import { getAssetGroupsApi } from "../../../../services/factoryBuilder/group/assetGroup/getAssetGroupsApi";
|
||||
|
||||
function GroupControls() {
|
||||
const { projectId } = useParams();
|
||||
@@ -24,8 +25,17 @@ function GroupControls() {
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
useEffect(() => {
|
||||
console.log("assetGroups: ", assetGroups);
|
||||
console.log("hierarchy: ", buildHierarchy(assets, assetGroups));
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
getAssetGroupsApi(projectId, selectedVersion.versionId).then((data) => {
|
||||
console.log("data: ", data);
|
||||
});
|
||||
}, [projectId, selectedVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
const hierarchy = buildHierarchy(assets, assetGroups);
|
||||
// console.log("assetGroups: ", assetGroups);
|
||||
// console.log("hierarchy: ", hierarchy);
|
||||
}, [assetGroups, assets]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -81,6 +81,7 @@ function ScaleControls3D() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('builderSocket: ', builderSocket);
|
||||
if (!builderSocket?.connected) {
|
||||
setAssetsApi({
|
||||
modelUuid: updatedAsset.modelUuid,
|
||||
|
||||
@@ -15,6 +15,7 @@ import useAssetResponseHandler from "../../../../collaboration/responseHandler/u
|
||||
|
||||
import { deleteFloorAssetApi } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorAssetApi";
|
||||
import { updateEventToBackend } from "../../../../../components/layout/sidebarRight/properties/eventProperties/functions/handleUpdateEventToBackend";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
|
||||
const SelectionControls3D: React.FC = () => {
|
||||
const { camera, controls, gl, scene, pointer } = useThree();
|
||||
@@ -152,14 +153,21 @@ const SelectionControls3D: React.FC = () => {
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
|
||||
if (movedObjects.length > 0 || rotatedObjects.length > 0 || event.repeat) return;
|
||||
if (event.key.toLowerCase() === "escape") {
|
||||
event.preventDefault();
|
||||
clearSelection();
|
||||
return;
|
||||
}
|
||||
if (event.key.toLowerCase() === "delete") {
|
||||
event.preventDefault();
|
||||
deleteSelection();
|
||||
return;
|
||||
}
|
||||
const modifier = detectModifierKeys(event);
|
||||
|
||||
if (modifier === "Ctrl+A") {
|
||||
selectedAll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -224,6 +232,7 @@ const SelectionControls3D: React.FC = () => {
|
||||
selectedAisle,
|
||||
selectedFloor,
|
||||
selectedWallAsset,
|
||||
assets,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -267,6 +276,11 @@ const SelectionControls3D: React.FC = () => {
|
||||
setSelectedAssets(selected);
|
||||
}, [selectionBox, pointer, controls, selectedAssets, setSelectedAssets]);
|
||||
|
||||
const selectedAll = useCallback(() => {
|
||||
const assetMeshes: THREE.Object3D[] = assets.map((asset) => scene.getObjectByProperty("uuid", asset.modelUuid)).filter((obj): obj is THREE.Object3D => obj !== undefined);
|
||||
setSelectedAssets(assetMeshes);
|
||||
}, [assets]);
|
||||
|
||||
const clearSelection = () => {
|
||||
setPastedObjects([]);
|
||||
setDuplicatedObjects([]);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Scene } from "three";
|
||||
import { Scene, PerspectiveCamera, OrthographicCamera } from "three";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { createContext, MutableRefObject, useContext, useMemo, useRef } from "react";
|
||||
|
||||
import { createVersionStore, VersionStoreType } from "../../store/builder/useVersionStore";
|
||||
@@ -33,6 +34,8 @@ import { createCollabusersStore, CollabUsersStoreType } from "../../store/collab
|
||||
|
||||
type SceneContextValue = {
|
||||
scene: MutableRefObject<Scene | null>;
|
||||
controls: MutableRefObject<CameraControls | null>;
|
||||
camera: MutableRefObject<PerspectiveCamera | OrthographicCamera | null>;
|
||||
|
||||
versionStore: VersionStoreType;
|
||||
|
||||
@@ -86,6 +89,8 @@ export function SceneProvider({
|
||||
readonly layoutType: "default" | "useCase" | "tutorial" | null;
|
||||
}) {
|
||||
const scene = useRef(null);
|
||||
const controls = useRef(null);
|
||||
const camera = useRef(null);
|
||||
|
||||
const versionStore = useMemo(() => createVersionStore(), []);
|
||||
|
||||
@@ -123,6 +128,8 @@ export function SceneProvider({
|
||||
const clearStores = useMemo(
|
||||
() => () => {
|
||||
scene.current = null;
|
||||
controls.current = null;
|
||||
camera.current = null;
|
||||
versionStore.getState().clearVersions();
|
||||
assetStore.getState().clearAssets();
|
||||
wallAssetStore.getState().clearWallAssets();
|
||||
@@ -177,6 +184,8 @@ export function SceneProvider({
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
scene,
|
||||
controls,
|
||||
camera,
|
||||
versionStore,
|
||||
assetStore,
|
||||
wallAssetStore,
|
||||
@@ -207,6 +216,8 @@ export function SceneProvider({
|
||||
}),
|
||||
[
|
||||
scene,
|
||||
controls,
|
||||
camera,
|
||||
versionStore,
|
||||
assetStore,
|
||||
wallAssetStore,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
versionId: string;
|
||||
groupUuid: string;
|
||||
groupName: string;
|
||||
isVisible: string;
|
||||
isExpanded: string;
|
||||
isLocked: string;
|
||||
childrens: {
|
||||
type: "Asset" | "Group";
|
||||
childrenUuid: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const createAssetGroupApi = async (props: Props) => {
|
||||
try {
|
||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/assetgroup/${props.projectId}/${props.versionId}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${localStorage.getItem("token") || ""}`,
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
body: JSON.stringify({
|
||||
groupUuid: props.groupUuid,
|
||||
groupName: props.groupName,
|
||||
isVisible: props.isVisible,
|
||||
isExpanded: props.isExpanded,
|
||||
isLocked: props.isLocked,
|
||||
childrens: props.childrens,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to create asset group");
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to create asset group", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
versionId: string;
|
||||
groupsData: {
|
||||
groupUuid: string;
|
||||
childUuid: string;
|
||||
type: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const createAssetGroupChildrenApi = async ({ projectId, versionId, groupsData }: Props) => {
|
||||
try {
|
||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/assetgroup/child/${projectId}/${versionId}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${localStorage.getItem("token") || ""}`,
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
data: JSON.parse(JSON.stringify({ groupsData: groupsData })),
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to update asset group");
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to update asset group", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
export const getAssetGroupApi = async (projectId: string, versionId: string) => {
|
||||
try {
|
||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/assetgroup/${projectId}/${versionId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
echo.error("Failed to get asset group");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch {
|
||||
echo.error("Failed to get asset group");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
export const getAssetGroupsApi = async (projectId: string, versionId: string) => {
|
||||
try {
|
||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/assetGroups/${projectId}/${versionId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
echo.error("Failed to get asset groups");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch {
|
||||
echo.error("Failed to get asset groups");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
versionId: string;
|
||||
groupsData: {
|
||||
groupUuid: string;
|
||||
groupName: string;
|
||||
isVisible: string;
|
||||
isExpanded: string;
|
||||
isLocked: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const updateAssetGroupApi = async (props: Props) => {
|
||||
try {
|
||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/assetgroup/${props.projectId}/${props.versionId}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${localStorage.getItem("token") || ""}`,
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
data: JSON.parse(JSON.stringify({ groupsData: props.groupsData })),
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to update asset group");
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to update asset group", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -42,6 +42,7 @@ interface AssetsStore {
|
||||
setCollision: (modelUuid: string, isCollidable: boolean) => void;
|
||||
setVisibility: (modelUuid: string, isVisible: boolean) => void;
|
||||
peekToggleVisibility: (modelUuid: string) => Asset | undefined;
|
||||
peekToggleLock: (modelUuid: string) => Asset | undefined;
|
||||
setOpacity: (modelUuid: string, opacity: number) => void;
|
||||
|
||||
// Animation controls
|
||||
@@ -63,6 +64,7 @@ interface AssetsStore {
|
||||
getInvisibleAssets: () => Asset[];
|
||||
getVisibleAssets: () => Asset[];
|
||||
hasAsset: (modelUuid: string) => boolean;
|
||||
getSelectedAssetUuids: () => string[];
|
||||
}
|
||||
|
||||
export const createAssetStore = () => {
|
||||
@@ -276,7 +278,16 @@ export const createAssetStore = () => {
|
||||
const asset = get().assets.find((a) => a.modelUuid === modelUuid);
|
||||
if (!asset) return undefined;
|
||||
|
||||
const updatedAsset = { ...asset, isVisible: !asset.isVisible };
|
||||
const updatedAsset: Asset = { ...asset, isVisible: !asset.isVisible };
|
||||
|
||||
return updatedAsset;
|
||||
},
|
||||
|
||||
peekToggleLock: (modelUuid: string): Asset | undefined => {
|
||||
const asset = get().assets.find((a) => a.modelUuid === modelUuid);
|
||||
if (!asset) return undefined;
|
||||
|
||||
const updatedAsset: Asset = { ...asset, isLocked: !asset.isLocked };
|
||||
|
||||
return updatedAsset;
|
||||
},
|
||||
@@ -410,6 +421,10 @@ export const createAssetStore = () => {
|
||||
hasAsset: (modelUuid) => {
|
||||
return get().assets.some((a) => a.modelUuid === modelUuid);
|
||||
},
|
||||
|
||||
getSelectedAssetUuids: () => {
|
||||
return get().selectedAssets.map((o) => o.uuid);
|
||||
},
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user