import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { SelectionHelper } from "../selectionHelper"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; import useModuleStore from "../../../../../store/useModuleStore"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore"; import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; import MoveControls2D from "./moveControls2D"; // import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; // import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi"; // import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi"; // import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; // import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi"; // import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; const SelectionControls2D: React.FC = () => { const { camera, controls, gl, scene, pointer } = useThree(); const { toggleView } = useToggleView(); const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints(); const [movedObjects, setMovedObjects] = useState([]); const [rotatedObjects, setRotatedObjects] = useState([]); const [copiedObjects, setCopiedObjects] = useState([]); const [pastedObjects, setPastedObjects] = useState([]); const [duplicatedObjects, setDuplicatedObjects] = useState([]); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { hoveredLine, hoveredPoint } = useBuilderStore(); const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); const { push2D } = undoRedo2DStore(); const { removePoint: removeAislePoint } = aisleStore(); const { removePoint: removeWallPoint } = wallStore(); const { removePoint: removeFloorPoint, getFloorsByPointId, getFloorById } = floorStore(); const { removePoint: removeZonePoint, getZonesByPointId, getZoneById } = zoneStore(); const isDragging = useRef(false); const isLeftMouseDown = useRef(false); const isSelecting = useRef(false); const isRightClick = useRef(false); const rightClickMoved = useRef(false); const isCtrlSelecting = useRef(false); const isShiftSelecting = useRef(false); const { userId, organization } = getUserData(); useEffect(() => { if (!camera || !scene || !toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; const helper = new SelectionHelper(gl); const onPointerDown = (event: PointerEvent) => { if (event.button === 2) { isRightClick.current = true; rightClickMoved.current = false; } else if (event.button === 0) { isSelecting.current = false; isCtrlSelecting.current = event.ctrlKey; isShiftSelecting.current = event.shiftKey; isLeftMouseDown.current = true; isDragging.current = false; if (event.ctrlKey && duplicatedObjects.length === 0) { if (controls) (controls as any).enabled = false; selectionBox.startPoint.set(pointer.x, pointer.y, 0); } } }; const onPointerMove = (event: PointerEvent) => { if (isRightClick.current) { rightClickMoved.current = true; } if (isLeftMouseDown.current) { isDragging.current = true; } isSelecting.current = true; if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting.current) { selectionBox.endPoint.set(pointer.x, pointer.y, 0); } }; const onPointerUp = (event: PointerEvent) => { if (event.button === 2 && !event.ctrlKey && !event.shiftKey) { isRightClick.current = false; if (!rightClickMoved.current) { clearSelection(); } return; } if (isSelecting.current && isCtrlSelecting.current) { isCtrlSelecting.current = false; isSelecting.current = false; if (event.ctrlKey && duplicatedObjects.length === 0) { selectAssets(); } } else if (!isSelecting.current && selectedPoints.length > 0 && ((!event.ctrlKey && !event.shiftKey && pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) { clearSelection(); helper.enabled = true; isCtrlSelecting.current = false; } else if (controls) { (controls as any).enabled = true; } if (!isDragging.current && isLeftMouseDown.current && isShiftSelecting.current && event.shiftKey) { isShiftSelecting.current = false; isLeftMouseDown.current = false; isDragging.current = false; } else if (controls) { (controls as any).enabled = true; } }; const onKeyDown = (event: KeyboardEvent) => { if (movedObjects.length > 0 || rotatedObjects.length > 0) return; if (event.key.toLowerCase() === "escape") { event.preventDefault(); clearSelection(); } if (event.key.toLowerCase() === "delete") { event.preventDefault(); deleteSelection(); } }; const onContextMenu = (event: MouseEvent) => { event.preventDefault(); if (!rightClickMoved.current) { clearSelection(); } rightClickMoved.current = false; }; if (toggleView && toolMode === 'move') { helper.enabled = true; canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("contextmenu", onContextMenu); canvasElement.addEventListener("keydown", onKeyDown); } else { helper.enabled = false; helper.dispose(); } return () => { canvasElement.removeEventListener("pointerdown", onPointerDown); canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("contextmenu", onContextMenu); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); helper.enabled = false; helper.dispose(); }; }, [camera, controls, scene, toggleView, selectedPoints, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, toolMode, hoveredLine, hoveredPoint]); useEffect(() => { if (toolMode !== 'move' || !toggleView) { clearSelection(); } }, [activeModule, toolMode, toggleView]); const selectAssets = useCallback(() => { selectionBox.endPoint.set(pointer.x, pointer.y, 0); if (controls) (controls as any).enabled = true; let selectedObjects = selectionBox.select(); let Objects = new Set(); selectedObjects.forEach((object) => { let currentObject: THREE.Object3D | null = object; while (currentObject) { if (currentObject.userData.pointUuid) { Objects.add(currentObject); break; } currentObject = currentObject.parent || null; } }); if (Objects.size === 0) { clearSelection(); return; } const updatedSelections = new Set(selectedPoints); Objects.forEach((obj) => { const existing = Array.from(updatedSelections).find((o) => o.userData?.pointUuid === obj.userData?.pointUuid); if (existing) { updatedSelections.delete(existing); } else { updatedSelections.add(obj); } }); const selected = Array.from(updatedSelections); setSelectedPoints(selected); }, [selectionBox, pointer, controls, selectedPoints, setSelectedPoints]); const clearSelection = () => { setPastedObjects([]); setDuplicatedObjects([]); clearSelectedPoints(); }; const deleteSelection = () => { if (selectedPoints.length > 0 && duplicatedObjects.length === 0) { const deletedPoints: UndoRedo2DDataTypeSchema[] = []; const updatedPoints: UndoRedo2DDataTypeSchema[] = []; const processedAisles: UndoRedo2DDataTypeSchema[] = []; const processedWalls: UndoRedo2DDataTypeSchema[] = []; const processedFloors: UndoRedo2DDataTypeSchema[] = []; const processedZones: UndoRedo2DDataTypeSchema[] = []; selectedPoints.forEach((selectedPoint) => { if (selectedPoint.userData.pointUuid) { const point: Point = selectedPoint.userData as Point; if (point.pointType === 'Aisle') { const removedAisles = removeAislePoint(point.pointUuid); if (removedAisles.length > 0) { removedAisles.forEach(aisle => { if (projectId) { // API // deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ''); // 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(), })); processedAisles.push(...removedAislesData); } } if (point.pointType === 'Wall') { const removedWalls = removeWallPoint(point.pointUuid); if (removedWalls.length > 0) { removedWalls.forEach(wall => { if (projectId) { // API // deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid); // 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(), })); processedWalls.push(...removedWallsData); } } if (point.pointType === 'Floor') { const Floors = getFloorsByPointId(point.pointUuid); const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); if (removedFloors.length > 0) { removedFloors.forEach(floor => { if (projectId) { // API // deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid); // 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(), })); processedFloors.push(...removedFloorsData); } if (updatedFloors.length > 0) { updatedFloors.forEach(floor => { if (projectId) { // API // upsertFloorApi(projectId, selectedVersion?.versionId || '', floor); // 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(), })); processedFloors.push(...updatedFloorsData); } } if (point.pointType === 'Zone') { const Zones = getZonesByPointId(point.pointUuid); const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); if (removedZones.length > 0) { removedZones.forEach(zone => { if (projectId) { // API // deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid); // 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(), })); processedZones.push(...removedZonesData); } if (updatedZones.length > 0) { updatedZones.forEach(zone => { if (projectId) { // API // upsertZoneApi(projectId, selectedVersion?.versionId || '', zone); // 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(), })); processedZones.push(...updatedZonesData); } } } }) setTimeout(() => { if (processedWalls.length > 0) { const wallMap = new Map(); for (const wall of processedWalls) { if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue; const uuid = wall.lineData.wallUuid; if (!wallMap.has(uuid)) wallMap.set(uuid, []); wallMap.get(uuid)!.push(wall); } wallMap.forEach((actions) => { const hasDelete = actions.some(action => !('newData' in action)); if (hasDelete) { deletedPoints.push({ type: 'Wall', lineData: actions[0].lineData as Wall, timeStamp: new Date().toISOString() }); } }); } if (processedAisles.length > 0) { const aisleMap = new Map(); for (const aisle of processedAisles) { if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue; const uuid = aisle.lineData.aisleUuid; if (!aisleMap.has(uuid)) aisleMap.set(uuid, []); aisleMap.get(uuid)!.push(aisle); } aisleMap.forEach((actions) => { const hasDelete = actions.some(action => !('newData' in action)); if (hasDelete) { deletedPoints.push({ type: 'Aisle', lineData: actions[0].lineData as Aisle, timeStamp: new Date().toISOString() }); } }); } if (processedFloors.length > 0) { const floorMap = new Map(); for (const floor of processedFloors) { if (floor.type !== 'Floor' || !floor.lineData.floorUuid) return; const uuid = floor.lineData.floorUuid; if (!floorMap.has(uuid)) { floorMap.set(uuid, []); } floorMap.get(uuid)!.push(floor); } floorMap.forEach((actions, uuid) => { const hasDelete = actions.some(action => !('newData' in action)); const hasUpdate = actions.some(action => 'newData' in action); if (hasDelete) { deletedPoints.push({ type: 'Floor', lineData: actions[0].lineData as Floor, timeStamp: new Date().toISOString() }); } else if (!hasDelete && hasUpdate) { const floorData = getFloorById(uuid); if (floorData) { updatedPoints.push({ type: 'Floor', lineData: actions[0].lineData as Floor, newData: floorData as Floor, timeStamp: new Date().toISOString() }); } } }); } if (processedZones.length > 0) { const zoneMap = new Map(); for (const zone of processedZones) { if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) return; const uuid = zone.lineData.zoneUuid; if (!zoneMap.has(uuid)) { zoneMap.set(uuid, []); } zoneMap.get(uuid)!.push(zone); } zoneMap.forEach((actions, uuid) => { const hasDelete = actions.some(action => !('newData' in action)); const hasUpdate = actions.some(action => 'newData' in action); if (hasDelete) { deletedPoints.push({ type: 'Zone', lineData: actions[0].lineData as Zone, timeStamp: new Date().toISOString() }); } else if (!hasDelete && hasUpdate) { const zoneData = getZoneById(uuid); if (zoneData) { updatedPoints.push({ type: 'Zone', lineData: actions[0].lineData as Zone, newData: zoneData as Zone, timeStamp: new Date().toISOString() }); } } }); } if (deletedPoints.length > 0 && updatedPoints.length > 0) { push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Delete', points: deletedPoints }, { actionType: 'Lines-Update', points: updatedPoints } ] }); } else if (deletedPoints.length > 0 && updatedPoints.length === 0) { push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Delete', points: deletedPoints } ] }); } else if (updatedPoints.length > 0 && deletedPoints.length === 0) { push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Update', points: updatedPoints } ] }); } }, 0); } echo.success("Selected points removed!"); clearSelection(); }; return ( <> ); }; export default SelectionControls2D;