diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index 3977f8f..1d11db1 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -610,7 +610,7 @@ function Line({ points }: Readonly) { onPointerOut={() => { if (hoveredLine && isHovered) { setHoveredLine(null); - if (!hoveredPoint) { + if (!hoveredPoint && toolMode === "move") { handleCanvasCursors("default"); } } diff --git a/app/src/modules/builder/point/eventHandler/usePointEventHandler.ts b/app/src/modules/builder/point/eventHandler/usePointEventHandler.ts new file mode 100644 index 0000000..747747a --- /dev/null +++ b/app/src/modules/builder/point/eventHandler/usePointEventHandler.ts @@ -0,0 +1,672 @@ +import * as THREE from "three"; +import { useState, useEffect, useMemo, useCallback } from "react"; +import { useSocketStore, useToolMode } from "../../../../store/builder/store"; +import { ThreeEvent, useThree } from "@react-three/fiber"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; +import { useSelectedPoints } from "../../../../store/simulation/useSimulationStore"; +import { usePointSnapping } from "../helpers/usePointSnapping"; +import { useParams } from "react-router-dom"; +import { useSceneContext } from "../../../scene/sceneContext"; + +import { upsertAisleApi } from "../../../../services/factoryBuilder/aisle/upsertAisleApi"; +import { deleteAisleApi } from "../../../../services/factoryBuilder/aisle/deleteAisleApi"; +import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi"; +import { deleteWallApi } from "../../../../services/factoryBuilder/wall/deleteWallApi"; +import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi"; +import { deleteFloorApi } from "../../../../services/factoryBuilder/floor/deleteFloorApi"; +import { upsertZoneApi } from "../../../../services/factoryBuilder/zone/upsertZoneApi"; +import { deleteZoneApi } from "../../../../services/factoryBuilder/zone/deleteZoneApi"; +import { upsertWallAssetApi } from "../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi"; +import { deleteWallAssetApi } from "../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi"; + +import { getUserData } from "../../../../functions/getUserData"; +import { handleCanvasCursors } from "../../../../utils/mouseUtils/handleCanvasCursors"; +import { calculateAssetTransformationOnWall } from "../../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall"; + +export function usePointEventHandler({ point }: { point: Point }) { + const { raycaster, camera, pointer } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); + const [isHovered, setIsHovered] = useState(false); + const [isDeleteHovered, setIsDeleteHovered] = useState(false); + const [isSelected, setIsSelected] = useState(false); + const [dragOffset, setDragOffset] = useState(null); + const { socket } = useSocketStore(); + const { toolMode } = useToolMode(); + const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore, versionStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); + const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore(); + const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore(); + const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore(); + const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore(); + const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore(); + const { selectedVersion } = versionStore(); + const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ + uuid: point.pointUuid, + pointType: point.pointType, + position: point.position, + }); + const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore(); + const { selectedPoints } = useSelectedPoints(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + + const [initialStates, setInitialStates] = useState<{ + aisles?: Aisle[]; + walls?: Wall[]; + floors?: Floor[]; + zones?: Zone[]; + }>({}); + + const handleDrag = useCallback(() => { + if (toolMode === "move" && isHovered && dragOffset) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit) { + handleCanvasCursors("grabbing"); + const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset); + const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z]; + + if (point.pointType === "Aisle") { + const aisleSnapped = snapAisleAngle(newPosition); + const finalSnapped = snapAislePoint(aisleSnapped.position); + setAislePosition(point.pointUuid, finalSnapped.position); + } else if (point.pointType === "Wall") { + const wallSnapped = snapWallAngle(newPosition); + const finalSnapped = snapWallPoint(wallSnapped.position); + setWallPosition(point.pointUuid, finalSnapped.position); + } else if (point.pointType === "Floor") { + const floorSnapped = snapFloorAngle(newPosition); + const finalSnapped = snapFloorPoint(floorSnapped.position); + setFloorPosition(point.pointUuid, finalSnapped.position); + } else if (point.pointType === "Zone") { + const zoneSnapped = snapZoneAngle(newPosition); + const finalSnapped = snapZonePoint(zoneSnapped.position); + setZonePosition(point.pointUuid, finalSnapped.position); + } + } + } + }, [toolMode, isHovered, dragOffset, camera, raycaster, plane, point]); + + const handleDragStart = useCallback(() => { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit) { + const currentPosition = new THREE.Vector3(...point.position); + const offset = new THREE.Vector3().subVectors(currentPosition, hit); + setDragOffset(offset); + + if (point.pointType === "Aisle") { + const aisles = getAislesByPointId(point.pointUuid); + setInitialStates({ aisles }); + } else if (point.pointType === "Wall") { + const walls = getWallsByPointId(point.pointUuid); + setInitialStates({ walls }); + } else if (point.pointType === "Floor") { + const floors = getFloorsByPointId(point.pointUuid); + setInitialStates({ floors }); + } else if (point.pointType === "Zone") { + const zones = getZonesByPointId(point.pointUuid); + setInitialStates({ zones }); + } + } + }, [raycaster, camera, plane, point]); + + const handleDragEnd = useCallback(() => { + handleCanvasCursors("default"); + setDragOffset(null); + if (toolMode !== "move") return; + + if (point.pointType === "Aisle") { + const updatedAisles = getAislesByPointId(point.pointUuid); + if (updatedAisles.length > 0 && projectId) { + updatedAisles.forEach((updatedAisle) => { + if (!socket?.connected) { + // API + + upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ""); + } else { + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: updatedAisle.aisleUuid, + points: updatedAisle.points, + type: updatedAisle.type, + }); + } + }); + + if (initialStates.aisles && initialStates.aisles.length > 0) { + const updatedPoints = initialStates.aisles.map((aisle) => ({ + type: "Aisle" as const, + lineData: aisle, + newData: updatedAisles.find((a) => a.aisleUuid === aisle.aisleUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Update", + points: updatedPoints, + }, + ], + }); + } + } + } else if (point.pointType === "Wall") { + const updatedWalls = getWallsByPointId(point.pointUuid); + if (updatedWalls && updatedWalls.length > 0 && projectId) { + updatedWalls.forEach((updatedWall) => { + const initialWall = initialStates.walls?.find((w) => w.wallUuid === updatedWall.wallUuid); + + if (initialWall) { + const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid); + + assetsOnWall.forEach((asset) => { + const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall); + + const updatedWallAsset = updateWallAsset(asset.modelUuid, { + position: [position[0], asset.position[1], position[2]], + rotation: rotation, + }); + + if (projectId && updatedWallAsset) { + if (!socket?.connected) { + // API + + upsertWallAssetApi(projectId, selectedVersion?.versionId || "", updatedWallAsset); + } else { + // SOCKET + + const data = { + wallAssetData: updatedWallAsset, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:wall-asset:add", data); + } + } + }); + } + + if (!socket?.connected) { + // API + + upsertWallApi(projectId, selectedVersion?.versionId || "", updatedWall); + } else { + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:model-Wall:add", data); + } + }); + } + + if (initialStates.walls && initialStates.walls.length > 0) { + const updatedPoints = initialStates.walls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + newData: updatedWalls.find((w) => w.wallUuid === wall.wallUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Update", + points: updatedPoints, + }, + ], + }); + } + } else if (point.pointType === "Floor") { + const updatedFloors = getFloorsByPointId(point.pointUuid); + if (updatedFloors && updatedFloors.length > 0 && projectId) { + updatedFloors.forEach((updatedFloor) => { + if (!socket?.connected) { + // API + + upsertFloorApi(projectId, selectedVersion?.versionId || "", updatedFloor); + } else { + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:model-Floor:add", data); + } + }); + } + + if (initialStates.floors && initialStates.floors.length > 0) { + const updatedPoints = initialStates.floors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + newData: updatedFloors.find((f) => f.floorUuid === floor.floorUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Update", + points: updatedPoints, + }, + ], + }); + } + } else if (point.pointType === "Zone") { + const updatedZones = getZonesByPointId(point.pointUuid); + if (updatedZones && updatedZones.length > 0 && projectId) { + updatedZones.forEach((updatedZone) => { + if (!socket?.connected) { + // API + + upsertZoneApi(projectId, selectedVersion?.versionId || "", updatedZone); + } else { + // SOCKET + + const data = { + zoneData: updatedZone, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:zone:add", data); + } + }); + } + + if (initialStates.zones && initialStates.zones.length > 0) { + const updatedPoints = initialStates.zones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + newData: updatedZones.find((z) => z.zoneUuid === zone.zoneUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Update", + points: updatedPoints, + }, + ], + }); + } + } + + setInitialStates({}); + }, [toolMode, projectId, socket, selectedVersion, userId, organization, initialStates, point]); + + const handlePointClick = useCallback( + (e: ThreeEvent) => { + e.stopPropagation(); + if (toolMode === "2D-Delete") { + if (point.pointType === "Aisle") { + const removedAisles = removeAislePoint(point.pointUuid); + setHoveredPoint(null); + if (removedAisles.length > 0) { + removedAisles.forEach((aisle) => { + if (projectId) { + if (!socket?.connected) { + // API + + deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ""); + } else { + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + }; + + socket.emit("v1:model-aisle:delete", data); + } + } + }); + + const removedAislesData = removedAisles.map((aisle) => ({ + type: "Aisle" as const, + lineData: aisle, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Delete", + points: removedAislesData, + }, + ], + }); + } + } + if (point.pointType === "Wall") { + const removedWalls = removeWallPoint(point.pointUuid); + if (removedWalls.length > 0) { + setHoveredPoint(null); + removedWalls.forEach((wall) => { + const assetsOnWall = getWallAssetsByWall(wall.wallUuid); + + assetsOnWall.forEach((asset) => { + if (projectId && asset) { + removeWallAsset(asset.modelUuid); + if (!socket?.connected) { + // API + + deleteWallAssetApi(projectId, selectedVersion?.versionId || "", asset.modelUuid, asset.wallUuid); + } else { + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + modelUuid: asset.modelUuid, + wallUuid: asset.wallUuid, + }; + + socket.emit("v1:wall-asset:delete", data); + } + } + }); + + if (projectId) { + if (!socket?.connected) { + // API + + deleteWallApi(projectId, selectedVersion?.versionId || "", wall.wallUuid); + } else { + // SOCKET + + const data = { + wallUuid: wall.wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:model-Wall:delete", data); + } + } + }); + + const removedWallsData = removedWalls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Delete", + points: removedWallsData, + }, + ], + }); + } + } + if (point.pointType === "Floor") { + const Floors = getFloorsByPointId(point.pointUuid); + const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); + setHoveredPoint(null); + if (removedFloors.length > 0) { + removedFloors.forEach((floor) => { + if (projectId) { + if (!socket?.connected) { + // API + + deleteFloorApi(projectId, selectedVersion?.versionId || "", floor.floorUuid); + } else { + // SOCKET + + const data = { + floorUuid: floor.floorUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:model-Floor:delete", data); + } + } + }); + + const removedFloorsData = removedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Delete", + points: removedFloorsData, + }, + ], + }); + } + if (updatedFloors.length > 0) { + updatedFloors.forEach((floor) => { + if (projectId) { + if (!socket?.connected) { + // API + + upsertFloorApi(projectId, selectedVersion?.versionId || "", floor); + } else { + // SOCKET + + const data = { + floorData: floor, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:model-Floor:add", data); + } + } + }); + + const updatedFloorsData = updatedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: Floors.find((f) => f.floorUuid === floor.floorUuid) || floor, + newData: floor, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Update", + points: updatedFloorsData, + }, + ], + }); + } + } + if (point.pointType === "Zone") { + const Zones = getZonesByPointId(point.pointUuid); + const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); + setHoveredPoint(null); + if (removedZones.length > 0) { + removedZones.forEach((zone) => { + if (projectId) { + if (!socket?.connected) { + // API + + deleteZoneApi(projectId, selectedVersion?.versionId || "", zone.zoneUuid); + } else { + // SOCKET + + const data = { + zoneUuid: zone.zoneUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:zone:delete", data); + } + } + }); + + const removedZonesData = removedZones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Delete", + points: removedZonesData, + }, + ], + }); + } + if (updatedZones.length > 0) { + updatedZones.forEach((zone) => { + if (projectId) { + if (!socket?.connected) { + // API + + upsertZoneApi(projectId, selectedVersion?.versionId || "", zone); + } else { + // SOCKET + + const data = { + zoneData: zone, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:zone:add", data); + } + } + }); + + const updatedZonesData = updatedZones.map((zone) => ({ + type: "Zone" as const, + lineData: Zones.find((z) => z.zoneUuid === zone.zoneUuid) || zone, + newData: zone, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Lines-Update", + points: updatedZonesData, + }, + ], + }); + } + } + handleCanvasCursors("default"); + } + }, + [toolMode, projectId, socket, selectedVersion, userId, organization, point] + ); + + const handlePointerOver = useCallback( + (e: ThreeEvent) => { + if (!hoveredPoint && selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { + setHoveredPoint(point); + if (toolMode === "move") { + setIsHovered(true); + handleCanvasCursors("grab"); + } else if (toolMode === "2D-Delete") { + setIsDeleteHovered(true); + } + } + }, + [hoveredPoint, selectedPoints, toolMode, point, setHoveredPoint] + ); + + const handlePointerOut = useCallback(() => { + if (hoveredPoint) { + setHoveredPoint(null); + if (!hoveredLine && toolMode === "move") { + handleCanvasCursors("default"); + } + } + setIsHovered(false); + setIsDeleteHovered(false); + }, [hoveredPoint, hoveredLine, setHoveredPoint]); + + useEffect(() => { + if (hoveredPoint && hoveredPoint.pointUuid !== point.pointUuid) { + setIsHovered(false); + } + }, [hoveredPoint]); + + useEffect(() => { + if (selectedPoints.length > 0 && selectedPoints.some((selectedPoint) => selectedPoint.userData.pointUuid && selectedPoint.userData.pointUuid === point.pointUuid)) { + setIsSelected(true); + } else { + setIsSelected(false); + } + }, [selectedPoints]); + + return { + isHovered, + isDeleteHovered, + isSelected, + handleDrag, + handleDragStart, + handleDragEnd, + handlePointClick, + handlePointerOver, + handlePointerOut, + }; +} diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index 3b32d5b..a1f8420 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -1,72 +1,14 @@ -import * as THREE from "three"; -import * as Constants from "../../../types/world/worldConstants"; -import { useState, useEffect, useMemo } from "react"; -import { useSocketStore, useToolMode } from "../../../store/builder/store"; import { DragControls } from "@react-three/drei"; -import { useThree } from "@react-three/fiber"; -import { useBuilderStore } from "../../../store/builder/useBuilderStore"; -import { useSelectedPoints } from "../../../store/simulation/useSimulationStore"; -import { usePointSnapping } from "./helpers/usePointSnapping"; -import { useParams } from "react-router-dom"; -import { useSceneContext } from "../../scene/sceneContext"; +import * as Constants from "../../../types/world/worldConstants"; import BoxMaterial from "../wrappers/materials/boxMaterial"; -import { upsertAisleApi } from "../../../services/factoryBuilder/aisle/upsertAisleApi"; -import { deleteAisleApi } from "../../../services/factoryBuilder/aisle/deleteAisleApi"; -import { upsertWallApi } from "../../../services/factoryBuilder/wall/upsertWallApi"; -import { deleteWallApi } from "../../../services/factoryBuilder/wall/deleteWallApi"; -import { upsertFloorApi } from "../../../services/factoryBuilder/floor/upsertFloorApi"; -import { deleteFloorApi } from "../../../services/factoryBuilder/floor/deleteFloorApi"; -import { upsertZoneApi } from "../../../services/factoryBuilder/zone/upsertZoneApi"; -import { deleteZoneApi } from "../../../services/factoryBuilder/zone/deleteZoneApi"; -import { upsertWallAssetApi } from "../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi"; -import { deleteWallAssetApi } from "../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi"; - -import { getUserData } from "../../../functions/getUserData"; -import { handleCanvasCursors } from "../../../utils/mouseUtils/handleCanvasCursors"; -import { calculateAssetTransformationOnWall } from "../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall"; +import { usePointEventHandler } from "./eventHandler/usePointEventHandler"; function Point({ point }: { readonly point: Point }) { - const { raycaster, camera, pointer } = useThree(); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const [isHovered, setIsHovered] = useState(false); - const [isDeleteHovered, setIsDeleteHovered] = useState(false); - const [isSelected, setIsSelected] = useState(false); - const [dragOffset, setDragOffset] = useState(null); - const { socket } = useSocketStore(); - const { toolMode } = useToolMode(); - const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore, versionStore } = useSceneContext(); - const { push2D } = undoRedo2DStore(); - const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore(); - const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore(); - const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore(); - const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore(); - const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore(); - const { selectedVersion } = versionStore(); - const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ - uuid: point.pointUuid, - pointType: point.pointType, - position: point.position, - }); - const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore(); - const { selectedPoints } = useSelectedPoints(); - const { userId, organization } = getUserData(); - const { projectId } = useParams(); const boxScale: [number, number, number] = Constants.pointConfig.boxScale; const colors = getColor(point); - const [initialStates, setInitialStates] = useState<{ - aisles?: Aisle[]; - walls?: Wall[]; - floors?: Floor[]; - zones?: Zone[]; - }>({}); - - useEffect(() => { - handleCanvasCursors("default"); - }, [toolMode]); - function getColor(point: Point) { if (point.pointType === "Aisle") { return { @@ -101,612 +43,20 @@ function Point({ point }: { readonly point: Point }) { } } - const handleDrag = (point: Point) => { - if (toolMode === "move" && isHovered && dragOffset) { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - - if (hit) { - handleCanvasCursors("grabbing"); - const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset); - const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z]; - - if (point.pointType === "Aisle") { - const aisleSnapped = snapAisleAngle(newPosition); - const finalSnapped = snapAislePoint(aisleSnapped.position); - setAislePosition(point.pointUuid, finalSnapped.position); - } else if (point.pointType === "Wall") { - const wallSnapped = snapWallAngle(newPosition); - const finalSnapped = snapWallPoint(wallSnapped.position); - setWallPosition(point.pointUuid, finalSnapped.position); - } else if (point.pointType === "Floor") { - const floorSnapped = snapFloorAngle(newPosition); - const finalSnapped = snapFloorPoint(floorSnapped.position); - setFloorPosition(point.pointUuid, finalSnapped.position); - } else if (point.pointType === "Zone") { - const zoneSnapped = snapZoneAngle(newPosition); - const finalSnapped = snapZonePoint(zoneSnapped.position); - setZonePosition(point.pointUuid, finalSnapped.position); - } - } - } - }; - - const handleDragStart = (point: Point) => { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - - if (hit) { - const currentPosition = new THREE.Vector3(...point.position); - const offset = new THREE.Vector3().subVectors(currentPosition, hit); - setDragOffset(offset); - - if (point.pointType === "Aisle") { - const aisles = getAislesByPointId(point.pointUuid); - setInitialStates({ aisles }); - } else if (point.pointType === "Wall") { - const walls = getWallsByPointId(point.pointUuid); - setInitialStates({ walls }); - } else if (point.pointType === "Floor") { - const floors = getFloorsByPointId(point.pointUuid); - setInitialStates({ floors }); - } else if (point.pointType === "Zone") { - const zones = getZonesByPointId(point.pointUuid); - setInitialStates({ zones }); - } - } - }; - - const handleDragEnd = (point: Point) => { - handleCanvasCursors("default"); - setDragOffset(null); - if (toolMode !== "move") return; - - if (point.pointType === "Aisle") { - const updatedAisles = getAislesByPointId(point.pointUuid); - if (updatedAisles.length > 0 && projectId) { - updatedAisles.forEach((updatedAisle) => { - if (!socket?.connected) { - // API - - upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ""); - } else { - // SOCKET - - socket.emit("v1:model-aisle:add", { - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - aisleUuid: updatedAisle.aisleUuid, - points: updatedAisle.points, - type: updatedAisle.type, - }); - } - }); - - if (initialStates.aisles && initialStates.aisles.length > 0) { - const updatedPoints = initialStates.aisles.map((aisle) => ({ - type: "Aisle" as const, - lineData: aisle, - newData: updatedAisles.find((a) => a.aisleUuid === aisle.aisleUuid), - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Update", - points: updatedPoints, - }, - ], - }); - } - } - } else if (point.pointType === "Wall") { - const updatedWalls = getWallsByPointId(point.pointUuid); - if (updatedWalls && updatedWalls.length > 0 && projectId) { - updatedWalls.forEach((updatedWall) => { - const initialWall = initialStates.walls?.find((w) => w.wallUuid === updatedWall.wallUuid); - - if (initialWall) { - const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid); - - assetsOnWall.forEach((asset) => { - const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall); - - const updatedWallAsset = updateWallAsset(asset.modelUuid, { - position: [position[0], asset.position[1], position[2]], - rotation: rotation, - }); - - if (projectId && updatedWallAsset) { - if (!socket?.connected) { - // API - - upsertWallAssetApi(projectId, selectedVersion?.versionId || "", updatedWallAsset); - } else { - // SOCKET - - const data = { - wallAssetData: updatedWallAsset, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:wall-asset:add", data); - } - } - }); - } - - if (!socket?.connected) { - // API - - upsertWallApi(projectId, selectedVersion?.versionId || "", updatedWall); - } else { - // SOCKET - - const data = { - wallData: updatedWall, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:model-Wall:add", data); - } - }); - } - - if (initialStates.walls && initialStates.walls.length > 0) { - const updatedPoints = initialStates.walls.map((wall) => ({ - type: "Wall" as const, - lineData: wall, - newData: updatedWalls.find((w) => w.wallUuid === wall.wallUuid), - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Update", - points: updatedPoints, - }, - ], - }); - } - } else if (point.pointType === "Floor") { - const updatedFloors = getFloorsByPointId(point.pointUuid); - if (updatedFloors && updatedFloors.length > 0 && projectId) { - updatedFloors.forEach((updatedFloor) => { - if (!socket?.connected) { - // API - - upsertFloorApi(projectId, selectedVersion?.versionId || "", updatedFloor); - } else { - // SOCKET - - const data = { - floorData: updatedFloor, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:model-Floor:add", data); - } - }); - } - - if (initialStates.floors && initialStates.floors.length > 0) { - const updatedPoints = initialStates.floors.map((floor) => ({ - type: "Floor" as const, - lineData: floor, - newData: updatedFloors.find((f) => f.floorUuid === floor.floorUuid), - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Update", - points: updatedPoints, - }, - ], - }); - } - } else if (point.pointType === "Zone") { - const updatedZones = getZonesByPointId(point.pointUuid); - if (updatedZones && updatedZones.length > 0 && projectId) { - updatedZones.forEach((updatedZone) => { - if (!socket?.connected) { - // API - - upsertZoneApi(projectId, selectedVersion?.versionId || "", updatedZone); - } else { - // SOCKET - - const data = { - zoneData: updatedZone, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:zone:add", data); - } - }); - } - - if (initialStates.zones && initialStates.zones.length > 0) { - const updatedPoints = initialStates.zones.map((zone) => ({ - type: "Zone" as const, - lineData: zone, - newData: updatedZones.find((z) => z.zoneUuid === zone.zoneUuid), - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Update", - points: updatedPoints, - }, - ], - }); - } - } - - setInitialStates({}); - }; - - const handlePointClick = (point: Point) => { - if (toolMode === "2D-Delete") { - if (point.pointType === "Aisle") { - const removedAisles = removeAislePoint(point.pointUuid); - setHoveredPoint(null); - if (removedAisles.length > 0) { - removedAisles.forEach((aisle) => { - if (projectId) { - if (!socket?.connected) { - // API - - deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ""); - } else { - // SOCKET - - const data = { - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - }; - - socket.emit("v1:model-aisle:delete", data); - } - } - }); - - const removedAislesData = removedAisles.map((aisle) => ({ - type: "Aisle" as const, - lineData: aisle, - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Delete", - points: removedAislesData, - }, - ], - }); - } - } - if (point.pointType === "Wall") { - const removedWalls = removeWallPoint(point.pointUuid); - if (removedWalls.length > 0) { - setHoveredPoint(null); - removedWalls.forEach((wall) => { - const assetsOnWall = getWallAssetsByWall(wall.wallUuid); - - assetsOnWall.forEach((asset) => { - if (projectId && asset) { - removeWallAsset(asset.modelUuid); - if (!socket?.connected) { - // API - - deleteWallAssetApi(projectId, selectedVersion?.versionId || "", asset.modelUuid, asset.wallUuid); - } else { - // SOCKET - - const data = { - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - modelUuid: asset.modelUuid, - wallUuid: asset.wallUuid, - }; - - socket.emit("v1:wall-asset:delete", data); - } - } - }); - - if (projectId) { - if (!socket?.connected) { - // API - - deleteWallApi(projectId, selectedVersion?.versionId || "", wall.wallUuid); - } else { - // SOCKET - - const data = { - wallUuid: wall.wallUuid, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:model-Wall:delete", data); - } - } - }); - - const removedWallsData = removedWalls.map((wall) => ({ - type: "Wall" as const, - lineData: wall, - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Delete", - points: removedWallsData, - }, - ], - }); - } - } - if (point.pointType === "Floor") { - const Floors = getFloorsByPointId(point.pointUuid); - const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); - setHoveredPoint(null); - if (removedFloors.length > 0) { - removedFloors.forEach((floor) => { - if (projectId) { - if (!socket?.connected) { - // API - - deleteFloorApi(projectId, selectedVersion?.versionId || "", floor.floorUuid); - } else { - // SOCKET - - const data = { - floorUuid: floor.floorUuid, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:model-Floor:delete", data); - } - } - }); - - const removedFloorsData = removedFloors.map((floor) => ({ - type: "Floor" as const, - lineData: floor, - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Delete", - points: removedFloorsData, - }, - ], - }); - } - if (updatedFloors.length > 0) { - updatedFloors.forEach((floor) => { - if (projectId) { - if (!socket?.connected) { - // API - - upsertFloorApi(projectId, selectedVersion?.versionId || "", floor); - } else { - // SOCKET - - const data = { - floorData: floor, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:model-Floor:add", data); - } - } - }); - - const updatedFloorsData = updatedFloors.map((floor) => ({ - type: "Floor" as const, - lineData: Floors.find((f) => f.floorUuid === floor.floorUuid) || floor, - newData: floor, - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Update", - points: updatedFloorsData, - }, - ], - }); - } - } - if (point.pointType === "Zone") { - const Zones = getZonesByPointId(point.pointUuid); - const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); - setHoveredPoint(null); - if (removedZones.length > 0) { - removedZones.forEach((zone) => { - if (projectId) { - if (!socket?.connected) { - // API - - deleteZoneApi(projectId, selectedVersion?.versionId || "", zone.zoneUuid); - } else { - // SOCKET - - const data = { - zoneUuid: zone.zoneUuid, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:zone:delete", data); - } - } - }); - - const removedZonesData = removedZones.map((zone) => ({ - type: "Zone" as const, - lineData: zone, - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Delete", - points: removedZonesData, - }, - ], - }); - } - if (updatedZones.length > 0) { - updatedZones.forEach((zone) => { - if (projectId) { - if (!socket?.connected) { - // API - - upsertZoneApi(projectId, selectedVersion?.versionId || "", zone); - } else { - // SOCKET - - const data = { - zoneData: zone, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:zone:add", data); - } - } - }); - - const updatedZonesData = updatedZones.map((zone) => ({ - type: "Zone" as const, - lineData: Zones.find((z) => z.zoneUuid === zone.zoneUuid) || zone, - newData: zone, - timeStamp: new Date().toISOString(), - })); - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Lines-Update", - points: updatedZonesData, - }, - ], - }); - } - } - handleCanvasCursors("default"); - } - }; - - useEffect(() => { - if (hoveredPoint && hoveredPoint.pointUuid !== point.pointUuid) { - setIsHovered(false); - } - }, [hoveredPoint]); - - useEffect(() => { - if (selectedPoints.length > 0 && selectedPoints.some((selectedPoint) => selectedPoint.userData.pointUuid && selectedPoint.userData.pointUuid === point.pointUuid)) { - setIsSelected(true); - } else { - setIsSelected(false); - } - }, [selectedPoints]); + const { isHovered, isSelected, isDeleteHovered, handleDrag, handleDragStart, handleDragEnd, handlePointClick, handlePointerOver, handlePointerOut } = usePointEventHandler({ point }); return ( <> {!isSelected ? ( - handleDragStart(point)} onDrag={() => handleDrag(point)} onDragEnd={() => handleDragEnd(point)}> + { - e.stopPropagation(); - handlePointClick(point); - }} - onPointerOver={(e) => { - if (!hoveredPoint && selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { - setHoveredPoint(point); - if (toolMode === "move") { - setIsHovered(true); - handleCanvasCursors("grab"); - } else if (toolMode === "2D-Delete") { - setIsDeleteHovered(true); - } - } - }} - onPointerOut={() => { - if (hoveredPoint) { - handleCanvasCursors("default"); - setHoveredPoint(null); - if (!hoveredLine) { - handleCanvasCursors("default"); - } - } - setIsHovered(false); - setIsDeleteHovered(false); - }} + onClick={handlePointClick} + onPointerOver={handlePointerOver} + onPointerOut={handlePointerOut} userData={point} >