diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 13fb414..cb54f87 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -1,4 +1,4 @@ -import * as THREE from "three" + import * as THREE from "three" import { useEffect } from 'react' import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi'; import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store'; diff --git a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx index d9f8df0..8c72dcf 100644 --- a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx +++ b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx @@ -1,18 +1,25 @@ import * as THREE from 'three'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useThree } from '@react-three/fiber'; +import { Base, Geometry, Subtraction } from '@react-three/csg'; import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; -import { Base, Geometry, Subtraction } from '@react-three/csg'; import useModuleStore from '../../../../../store/useModuleStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useBuilderStore } from '../../../../../store/builder/useBuilderStore'; -import { useActiveTool, useToggleView } from '../../../../../store/builder/store'; +import { useActiveTool, useSocketStore, useToggleView } from '../../../../../store/builder/store'; +import { useParams } from 'react-router-dom'; +import { useVersionContext } from '../../../version/versionContext'; +import { getUserData } from '../../../../../functions/getUserData'; import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment'; -import { useThree } from '@react-three/fiber'; + +import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi'; +import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi'; function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; + const { socket } = useSocketStore(); const { raycaster, pointer, camera, scene, controls, gl } = useThree(); const { wallStore, wallAssetStore } = useSceneContext(); const { walls, getWallById } = wallStore(); @@ -26,6 +33,10 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const groupRef = useRef(null); const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]); const draggingRef = useRef(false); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); useEffect(() => { const loader = new GLTFLoader(); @@ -131,11 +142,31 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const wallRotation = intersect.object.rotation.clone(); - updateWallAsset(wallAsset.modelUuid, { + const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, { wallUuid: intersect.object.userData.wallUuid, position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z], rotation: [wallRotation.x, wallRotation.y, wallRotation.z], }); + + if (projectId && updatedWallAsset) { + + // API + + // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset); + + // SOCKET + + const data = { + wallAssetData: updatedWallAsset, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:wall-asset:add', data); + + } } }; @@ -149,13 +180,34 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { canvasElement.removeEventListener('pointerup', onPointerUp); }; - }, [gl, camera, toggleView, activeModule, selectedWallAsset]) + }, [gl, camera, toggleView, activeModule, selectedWallAsset, socket]) const handlePointerClick = useCallback((wallAsset: WallAsset) => { if (activeTool === 'delete' && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) { - removeWallAsset(wallAsset.modelUuid); + const removedWallAsset = removeWallAsset(wallAsset.modelUuid); + + if (projectId && removedWallAsset) { + + // API + + // deleteWallAssetApi(projectId, selectedVersion?.versionId || '', removedWallAsset.modelUuid, removedWallAsset.wallUuid); + + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + modelUuid: removedWallAsset.modelUuid, + wallUuid: removedWallAsset.wallUuid + } + + socket.emit('v1:wall-asset:delete', data); + + } } - }, [activeTool, activeModule, deletableWallAsset]) + }, [activeTool, activeModule, deletableWallAsset, socket]) const handlePointerOver = useCallback((wallAsset: WallAsset, currentObject: THREE.Object3D) => { if (activeTool === "delete" && activeModule === 'builder') { diff --git a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx index aacca0e..c27646f 100644 --- a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx +++ b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx @@ -1,11 +1,16 @@ import { useThree } from '@react-three/fiber'; import { useEffect } from 'react' -import { useSelectedItem, useSocketStore, useToggleView } from '../../../store/builder/store'; import useModuleStore from '../../../store/useModuleStore'; +import { useSelectedItem, useSocketStore, useToggleView } from '../../../store/builder/store'; import { MathUtils, Vector3 } from 'three'; import { useSceneContext } from '../../scene/sceneContext'; +import { useParams } from 'react-router-dom'; +import { useVersionContext } from '../version/versionContext'; +import { getUserData } from '../../../functions/getUserData'; import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegment'; +import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi'; + function WallAssetCreator() { const { socket } = useSocketStore(); const { pointer, camera, raycaster, scene, gl } = useThree(); @@ -14,6 +19,10 @@ function WallAssetCreator() { const { wallAssetStore } = useSceneContext(); const { addWallAsset } = wallAssetStore(); const { selectedItem, setSelectedItem } = useSelectedItem(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); useEffect(() => { const canvasElement = gl.domElement; @@ -52,6 +61,25 @@ function WallAssetCreator() { }; addWallAsset(newWallAsset); + if (projectId) { + + // API + + // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', newWallAsset); + + // SOCKET + + const data = { + wallAssetData: newWallAsset, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:wall-asset:add', data); + + } } } }; diff --git a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx index f9d1290..113c1b3 100644 --- a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx +++ b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx @@ -7,6 +7,7 @@ import { useParams } from 'react-router-dom'; import useModuleStore from '../../../store/useModuleStore'; import WallAssetCreator from './wallAssetCreator' import WallAssetInstances from './Instances/wallAssetInstances' +import { getWallAssetsApi } from '../../../services/factoryBuilder/asset/wallAsset/getWallAssetsApi'; function WallAssetGroup() { const { togglView } = useToggleView(); @@ -26,6 +27,21 @@ function WallAssetGroup() { setDeletableWallAsset(null); }, [togglView, activeModule, activeTool]) + useEffect(() => { + if (projectId && selectedVersion) { + getWallAssetsApi(projectId, selectedVersion?.versionId || '').then((wallAssets) => { + console.log('wallAssets: ', wallAssets); + if (wallAssets && wallAssets.length > 0) { + setWallAssets(wallAssets); + } else { + setWallAssets([]); + } + }).catch((err) => { + console.log(err); + }) + } + }, [projectId, selectedVersion?.versionId]) + return ( <> diff --git a/app/src/modules/collaboration/socket/socketResponses.dev.tsx b/app/src/modules/collaboration/socket/socketResponses.dev.tsx index 3f4bfdb..db9e96a 100644 --- a/app/src/modules/collaboration/socket/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socket/socketResponses.dev.tsx @@ -1,6 +1,17 @@ -import React from 'react'; +import { useEffect } from 'react'; +import { useSocketStore } from '../../../store/builder/store'; export default function SocketResponses() { + const { socket } = useSocketStore(); + + useEffect(() => { + socket.on("v1:wall-asset:response:delete", (data: any) => { + }); + + return () => { + socket.off("v1:wall-asset:response:delete"); + } + }, [socket]) return ( <> diff --git a/app/src/services/factoryBuilder/asset/wallAsset/deleteWallAssetApi.ts b/app/src/services/factoryBuilder/asset/wallAsset/deleteWallAssetApi.ts new file mode 100644 index 0000000..3751aae --- /dev/null +++ b/app/src/services/factoryBuilder/asset/wallAsset/deleteWallAssetApi.ts @@ -0,0 +1,40 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteWallAssetApi = async ( + projectId: string, + versionId: string, + modelUuid: string, + wallUuid: string, +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/delete/wallasset`, { + method: "PATCH", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify({ projectId, versionId, modelUuid, wallUuid }), + }); + + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to delete wall asset:", response.statusText); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to delete wall asset"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/factoryBuilder/asset/wallAsset/getWallAssetsApi.ts b/app/src/services/factoryBuilder/asset/wallAsset/getWallAssetsApi.ts new file mode 100644 index 0000000..d6b12ed --- /dev/null +++ b/app/src/services/factoryBuilder/asset/wallAsset/getWallAssetsApi.ts @@ -0,0 +1,37 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getWallAssetsApi = async ( + projectId: string, + versionId: string, +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/wallassets/${projectId}/${versionId}`, { + method: "GET", + headers: { + Authorization: "Bearer ", + "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) { + console.error("Failed to get wall assets:", response.statusText); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to get wall assets"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/factoryBuilder/asset/wallAsset/upsertWallAssetApi.ts b/app/src/services/factoryBuilder/asset/wallAsset/upsertWallAssetApi.ts new file mode 100644 index 0000000..9f94183 --- /dev/null +++ b/app/src/services/factoryBuilder/asset/wallAsset/upsertWallAssetApi.ts @@ -0,0 +1,39 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const upsertWallAssetApi = async ( + projectId: string, + versionId: string, + wallAssetData: WallAsset +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/wall-asset`, { + method: "POST", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify({ projectId, versionId, wallAssetData }), + }); + + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to upsert wall asset:", response.statusText); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to upsert wall asset"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/store/builder/useWallAssetStore.ts b/app/src/store/builder/useWallAssetStore.ts index 3d4e89d..d6081d7 100644 --- a/app/src/store/builder/useWallAssetStore.ts +++ b/app/src/store/builder/useWallAssetStore.ts @@ -5,9 +5,9 @@ interface WallAssetStore { wallAssets: WallAsset[]; setWallAssets: (assets: WallAsset[]) => void; addWallAsset: (asset: WallAsset) => void; - updateWallAsset: (uuid: string, updated: Partial) => void; + updateWallAsset: (uuid: string, updated: Partial) => WallAsset | undefined; setWallAssetPosition: (uuid: string, position: [number, number, number]) => void; - removeWallAsset: (uuid: string) => void; + removeWallAsset: (uuid: string) => WallAsset | undefined; clearWallAssets: () => void; setVisibility: (uuid: string, isVisible: boolean) => void; @@ -31,12 +31,17 @@ export const createWallAssetStore = () => { state.wallAssets.push(asset); }), - updateWallAsset: (uuid, updated) => set(state => { - const asset = state.wallAssets.find(a => a.modelUuid === uuid); - if (asset) { - Object.assign(asset, updated); - } - }), + updateWallAsset: (uuid, updated) => { + let updatedWallAsset: WallAsset | undefined; + set(state => { + const asset = state.wallAssets.find(a => a.modelUuid === uuid); + if (asset) { + Object.assign(asset, updated); + updatedWallAsset = JSON.parse(JSON.stringify(asset)); + } + }); + return updatedWallAsset; + }, setWallAssetPosition: (uuid, position) => set(state => { const asset = state.wallAssets.find(a => a.modelUuid === uuid); @@ -45,9 +50,17 @@ export const createWallAssetStore = () => { } }), - removeWallAsset: (uuid) => set(state => { - state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid); - }), + removeWallAsset: (uuid) => { + let removedAsset: WallAsset | undefined; + set(state => { + const asset = state.wallAssets.find(a => a.modelUuid === uuid); + if (asset) { + removedAsset = JSON.parse(JSON.stringify(asset)); + state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid); + } + }); + return removedAsset; + }, clearWallAssets: () => { set(state => {