diff --git a/app/src/modules/builder/line/eventHandler/useLineEventHandler.ts b/app/src/modules/builder/line/eventHandler/useLineEventHandler.ts new file mode 100644 index 0000000..bc7a657 --- /dev/null +++ b/app/src/modules/builder/line/eventHandler/useLineEventHandler.ts @@ -0,0 +1,585 @@ +import * as THREE from "three"; +import { ThreeEvent, useThree } from "@react-three/fiber"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useSocketStore, useToolMode } from "../../../../store/builder/store"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; +import { useSceneContext } from "../../../scene/sceneContext"; +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../functions/getUserData"; +import { handleCanvasCursors } from "../../../../utils/mouseUtils/handleCanvasCursors"; +import { useSelectedPoints } from "../../../../store/simulation/useSimulationStore"; +import { calculateAssetTransformationOnWall } from "../../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall"; + +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 { deleteZoneApi } from "../../../../services/factoryBuilder/zone/deleteZoneApi"; +import { upsertZoneApi } from "../../../../services/factoryBuilder/zone/upsertZoneApi"; +import { upsertWallAssetApi } from "../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi"; +import { deleteWallAssetApi } from "../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi"; + +interface LineProps { + points: [Point, Point]; +} + +export function useLineEventHandler({ points }: Readonly) { + const [isHovered, setIsHovered] = useState(false); + const { raycaster, camera, pointer } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); + const [isDeletable, setIsDeletable] = useState(false); + const { socket } = useSocketStore(); + const { toolMode } = useToolMode(); + const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore, versionStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); + const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore(); + const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore(); + const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore(); + const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore(); + const { selectedVersion } = versionStore(); + const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); + const [dragOffset, setDragOffset] = useState(null); + const { selectedPoints } = useSelectedPoints(); + const { projectId } = useParams(); + const { userId, organization } = getUserData(); + + const [initialPositions, setInitialPositions] = 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 start = new THREE.Vector3(...points[0].position); + const end = new THREE.Vector3(...points[1].position); + const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); + + const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint); + + const newStart = new THREE.Vector3().addVectors(start, delta); + const newEnd = new THREE.Vector3().addVectors(end, delta); + + if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { + setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); + setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); + } + if (points[0].pointType === "Floor" && points[1].pointType === "Floor") { + setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); + setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); + } + if (points[0].pointType === "Zone" && points[1].pointType === "Zone") { + setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); + setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); + } + } + } + }, [camera, toolMode, isHovered, dragOffset, points]); + + const handleDragStart = useCallback(() => { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit && !hoveredPoint) { + const start = new THREE.Vector3(...points[0].position); + const end = new THREE.Vector3(...points[1].position); + const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); + + const offset = new THREE.Vector3().subVectors(midPoint, hit); + setDragOffset(offset); + + if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { + const wall = getWallByPoints(points); + if (wall) { + const walls = getConnectedWallsByWallId(wall.wallUuid, false); + setInitialPositions({ walls }); + } + } else if (points[0].pointType === "Floor") { + const floors = getFloorsByPointId(points[0].pointUuid); + setInitialPositions({ floors }); + } else if (points[0].pointType === "Zone") { + const zones = getZonesByPointId(points[0].pointUuid); + setInitialPositions({ zones }); + } + } + }, [camera, points, hoveredPoint]); + + const handleDragEnd = useCallback(() => { + if (toolMode !== "move" || !dragOffset) return; + handleCanvasCursors("default"); + setDragOffset(null); + + if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { + const wall = getWallByPoints(points); + if (!wall) return; + const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false); + + if (updatedWalls.length > 0 && projectId) { + updatedWalls.forEach((updatedWall) => { + const initialWall = initialPositions.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 (initialPositions.walls && initialPositions.walls.length > 0) { + const updatedPoints = initialPositions.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 (points[0].pointType === "Floor" && points[1].pointType === "Floor") { + const updatedFloors1 = getFloorsByPointId(points[0].pointUuid); + const updatedFloors2 = getFloorsByPointId(points[1].pointUuid); + const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid)); + + if (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 (initialPositions.floors && initialPositions.floors.length > 0) { + const updatedPoints = initialPositions.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 (points[0].pointType === "Zone" && points[1].pointType === "Zone") { + const updatedZones1 = getZonesByPointId(points[0].pointUuid); + const updatedZones2 = getZonesByPointId(points[1].pointUuid); + const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid)); + + if (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 (initialPositions.zones && initialPositions.zones.length > 0) { + const updatedPoints = initialPositions.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, + }, + ], + }); + } + } + } + }, [socket, points, toolMode, dragOffset, projectId, selectedVersion, userId, organization]); + + const handleLineClick = useCallback(() => { + if (toolMode === "2D-Delete") { + if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { + const removedWall = removeWallByPoints(points); + if (removedWall && projectId) { + const assetsOnWall = getWallAssetsByWall(removedWall.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 (!socket?.connected) { + // API + + deleteWallApi(projectId, selectedVersion?.versionId || "", removedWall.wallUuid); + } else { + // SOCKET + + const data = { + wallUuid: removedWall.wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + }; + + socket.emit("v1:model-Wall:delete", data); + } + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Delete", + point: { + type: "Wall", + lineData: removedWall, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + } + + setHoveredLine(null); + } + if (points[0].pointType === "Floor" && points[1].pointType === "Floor") { + const Floors = getFloorsByPoints(points); + const { removedFloors, updatedFloors } = removeFloorByPoints(points); + 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, + }, + ], + }); + } + + setHoveredLine(null); + } + if (points[0].pointType === "Zone" && points[1].pointType === "Zone") { + const Zones = getZonesByPoints(points); + const { removedZones, updatedZones } = removeZoneByPoints(points); + 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, + }, + ], + }); + } + + setHoveredLine(null); + } + handleCanvasCursors("default"); + } + }, [socket, points, toolMode, projectId, selectedVersion, userId, organization]); + + const handlePointerOver = useCallback( + (e: ThreeEvent) => { + if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { + setHoveredLine(points); + setIsHovered(true); + if (toolMode === "move" && !hoveredPoint) { + handleCanvasCursors("grab"); + } + } + }, + [selectedPoints, toolMode, hoveredPoint] + ); + + const handlePointerOut = useCallback(() => { + if (hoveredLine && isHovered) { + setHoveredLine(null); + if (!hoveredPoint && toolMode === "move") { + handleCanvasCursors("default"); + } + } + setIsHovered(false); + }, [hoveredLine, isHovered, hoveredPoint, toolMode]); + + useEffect(() => { + if (toolMode === "2D-Delete") { + if (isHovered && !hoveredPoint) { + setIsDeletable(true); + } else { + setIsDeletable(false); + } + } else { + setIsDeletable(false); + } + }, [isHovered, toolMode, hoveredPoint]); + + useEffect(() => { + if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) { + setIsHovered(false); + } + }, [hoveredLine]); + + return { + isHovered, + isDeletable, + handleDrag, + handleDragStart, + handleDragEnd, + handleLineClick, + handlePointerOver, + handlePointerOut, + }; +} diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index 1d11db1..23f9525 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -1,57 +1,15 @@ import * as THREE from "three"; -import { useThree } from "@react-three/fiber"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import { DragControls, Tube } from "@react-three/drei"; -import { useSocketStore, useToolMode } from "../../../store/builder/store"; -import { useBuilderStore } from "../../../store/builder/useBuilderStore"; -import { useSceneContext } from "../../scene/sceneContext"; import * as Constants from "../../../types/world/worldConstants"; -import { useParams } from "react-router-dom"; -import { getUserData } from "../../../functions/getUserData"; -import { handleCanvasCursors } from "../../../utils/mouseUtils/handleCanvasCursors"; -import { useSelectedPoints } from "../../../store/simulation/useSimulationStore"; -import { calculateAssetTransformationOnWall } from "../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall"; -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 { deleteZoneApi } from "../../../services/factoryBuilder/zone/deleteZoneApi"; -import { upsertZoneApi } from "../../../services/factoryBuilder/zone/upsertZoneApi"; -import { upsertWallAssetApi } from "../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi"; -import { deleteWallAssetApi } from "../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi"; +import { useLineEventHandler } from "./eventHandler/useLineEventHandler"; interface LineProps { points: [Point, Point]; } function Line({ points }: Readonly) { - const [isHovered, setIsHovered] = useState(false); - const { raycaster, camera, pointer } = useThree(); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const [isDeletable, setIsDeletable] = useState(false); - const { socket } = useSocketStore(); - const { toolMode } = useToolMode(); - const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore, versionStore } = useSceneContext(); - const { push2D } = undoRedo2DStore(); - const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore(); - const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore(); - const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore(); - const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore(); - const { selectedVersion } = versionStore(); - const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); - const [dragOffset, setDragOffset] = useState(null); - const { selectedPoints } = useSelectedPoints(); - const { projectId } = useParams(); - const { userId, organization } = getUserData(); - - const [initialPositions, setInitialPositions] = useState<{ - aisles?: Aisle[]; - walls?: Wall[]; - floors?: Floor[]; - zones?: Zone[]; - }>({}); - const path = useMemo(() => { const [start, end] = points.map((p) => new THREE.Vector3(...p.position)); return new THREE.LineCurve3(start, end); @@ -88,534 +46,19 @@ function Line({ points }: Readonly) { } } - useEffect(() => { - if (toolMode === "2D-Delete") { - if (isHovered && !hoveredPoint) { - setIsDeletable(true); - } else { - setIsDeletable(false); - } - } else { - setIsDeletable(false); - } - }, [isHovered, colors.defaultLineColor, colors.defaultDeleteColor, toolMode, hoveredPoint]); - - useEffect(() => { - if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) { - setIsHovered(false); - } - }, [hoveredLine]); - - const handlePointClick = (points: [Point, Point]) => { - if (toolMode === "2D-Delete") { - if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { - const removedWall = removeWallByPoints(points); - if (removedWall && projectId) { - const assetsOnWall = getWallAssetsByWall(removedWall.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 (!socket?.connected) { - // API - - deleteWallApi(projectId, selectedVersion?.versionId || "", removedWall.wallUuid); - } else { - // SOCKET - - const data = { - wallUuid: removedWall.wallUuid, - projectId: projectId, - versionId: selectedVersion?.versionId || "", - userId: userId, - organization: organization, - }; - - socket.emit("v1:model-Wall:delete", data); - } - - push2D({ - type: "Draw", - actions: [ - { - actionType: "Line-Delete", - point: { - type: "Wall", - lineData: removedWall, - timeStamp: new Date().toISOString(), - }, - }, - ], - }); - } - - setHoveredLine(null); - } - if (points[0].pointType === "Floor" && points[1].pointType === "Floor") { - const Floors = getFloorsByPoints(points); - const { removedFloors, updatedFloors } = removeFloorByPoints(points); - 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, - }, - ], - }); - } - - setHoveredLine(null); - } - if (points[0].pointType === "Zone" && points[1].pointType === "Zone") { - const Zones = getZonesByPoints(points); - const { removedZones, updatedZones } = removeZoneByPoints(points); - 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, - }, - ], - }); - } - - setHoveredLine(null); - } - handleCanvasCursors("default"); - } - }; - - const handleDrag = (points: [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 start = new THREE.Vector3(...points[0].position); - const end = new THREE.Vector3(...points[1].position); - const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); - - const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint); - - const newStart = new THREE.Vector3().addVectors(start, delta); - const newEnd = new THREE.Vector3().addVectors(end, delta); - - if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { - setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); - setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); - } - if (points[0].pointType === "Floor" && points[1].pointType === "Floor") { - setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); - setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); - } - if (points[0].pointType === "Zone" && points[1].pointType === "Zone") { - setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); - setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); - } - } - } - }; - - const handleDragStart = (points: [Point, Point]) => { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - - if (hit && !hoveredPoint) { - const start = new THREE.Vector3(...points[0].position); - const end = new THREE.Vector3(...points[1].position); - const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); - - const offset = new THREE.Vector3().subVectors(midPoint, hit); - setDragOffset(offset); - - if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { - const wall = getWallByPoints(points); - if (wall) { - const walls = getConnectedWallsByWallId(wall.wallUuid, false); - setInitialPositions({ walls }); - } - } else if (points[0].pointType === "Floor") { - const floors = getFloorsByPointId(points[0].pointUuid); - setInitialPositions({ floors }); - } else if (points[0].pointType === "Zone") { - const zones = getZonesByPointId(points[0].pointUuid); - setInitialPositions({ zones }); - } - } - }; - - const handleDragEnd = (points: [Point, Point]) => { - if (toolMode !== "move" || !dragOffset) return; - handleCanvasCursors("default"); - setDragOffset(null); - - if (points[0].pointType === "Wall" && points[1].pointType === "Wall") { - const wall = getWallByPoints(points); - if (!wall) return; - const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false); - - if (updatedWalls.length > 0 && projectId) { - updatedWalls.forEach((updatedWall) => { - const initialWall = initialPositions.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 (initialPositions.walls && initialPositions.walls.length > 0) { - const updatedPoints = initialPositions.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 (points[0].pointType === "Floor" && points[1].pointType === "Floor") { - const updatedFloors1 = getFloorsByPointId(points[0].pointUuid); - const updatedFloors2 = getFloorsByPointId(points[1].pointUuid); - const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid)); - - if (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 (initialPositions.floors && initialPositions.floors.length > 0) { - const updatedPoints = initialPositions.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 (points[0].pointType === "Zone" && points[1].pointType === "Zone") { - const updatedZones1 = getZonesByPointId(points[0].pointUuid); - const updatedZones2 = getZonesByPointId(points[1].pointUuid); - const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid)); - - if (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 (initialPositions.zones && initialPositions.zones.length > 0) { - const updatedPoints = initialPositions.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, - }, - ], - }); - } - } - } - }; + const { isDeletable, handleDrag, handleDragStart, handleDragEnd, handleLineClick, handlePointerOver, handlePointerOut } = useLineEventHandler({ points }); return ( - handleDragStart(points)} onDrag={() => handleDrag(points)} onDragEnd={() => handleDragEnd(points)}> + { - handlePointClick(points); - }} - onPointerOver={(e) => { - if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { - setHoveredLine(points); - setIsHovered(true); - if (toolMode === "move" && !hoveredPoint) { - handleCanvasCursors("grab"); - } - } - }} - onPointerOut={() => { - if (hoveredLine && isHovered) { - setHoveredLine(null); - if (!hoveredPoint && toolMode === "move") { - handleCanvasCursors("default"); - } - } - setIsHovered(false); - }} + onClick={handleLineClick} + onPointerOver={handlePointerOver} + onPointerOut={handlePointerOut} >