import { useThree } from "@react-three/fiber"; import React, { useEffect, useRef } from "react"; import { useAsset3dWidget, useSocketStore, useWidgetSubOption, } from "../../../store/store"; import useModuleStore from "../../../store/useModuleStore"; import { ThreeState } from "../../../types/world/worldTypes"; import * as THREE from "three"; import Throughput from "../../layout/3D-cards/cards/Throughput"; import ProductionCapacity from "../../layout/3D-cards/cards/ProductionCapacity"; import ReturnOfInvestment from "../../layout/3D-cards/cards/ReturnOfInvestment"; import StateWorking from "../../layout/3D-cards/cards/StateWorking"; import { useSelectedZoneStore } from "../../../store/useZoneStore"; import { generateUniqueId } from "../../../functions/generateUniqueId"; import { adding3dWidgets } from "../../../services/realTimeVisulization/zoneData/add3dWidget"; import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData"; import { use3DWidget } from "../../../store/useDroppedObjectsStore"; import { useEditWidgetOptionsStore, useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore, } from "../../../store/useZone3DWidgetStore"; import { delete3dWidgetApi } from "../../../services/realTimeVisulization/zoneData/delete3dWidget"; import { update3dWidget, update3dWidgetRotation, } from "../../../services/realTimeVisulization/zoneData/update3dWidget"; type WidgetData = { id: string; type: string; position: [number, number, number]; rotation?: [number, number, number]; tempPosition?: [number, number, number]; }; export default function Dropped3dWidgets() { const { widgetSelect } = useAsset3dWidget(); const { activeModule } = useModuleStore(); const { raycaster, gl, scene, mouse, camera }: ThreeState = useThree(); const { widgetSubOption } = useWidgetSubOption(); const { selectedZone } = useSelectedZoneStore(); const { top, setTop } = useTopData(); const { left, setLeft } = useLeftData(); const { rightSelect, setRightSelect } = useRightSelected(); const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore(); const { zoneWidgetData, setZoneWidgetData, addWidget, updateWidgetPosition, updateWidgetRotation, tempWidget, tempWidgetPosition } = useZoneWidgetStore(); const { setWidgets3D } = use3DWidget(); const { visualizationSocket } = useSocketStore(); const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); // Floor plane for horizontal move const verticalPlane = useRef(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0)); // Vertical plane for vertical move const planeIntersect = useRef(new THREE.Vector3()); const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]); const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || []; useEffect(() => { if (activeModule !== "visualization") return; if (!selectedZone.zoneId) return; const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; async function get3dWidgetData() { const result = await get3dWidgetZoneData( selectedZone.zoneId, organization ); setWidgets3D(result); const formattedWidgets = result.map((widget: WidgetData) => ({ id: widget.id, type: widget.type, position: widget.position, rotation: widget.rotation || [0, 0, 0], })); setZoneWidgetData(selectedZone.zoneId, formattedWidgets); } get3dWidgetData(); }, [selectedZone.zoneId, activeModule]); const createdWidgetRef = useRef(null); useEffect(() => { if (activeModule !== "visualization") return; if (widgetSubOption === "Floating" || widgetSubOption === "2D") return; if (selectedZone.zoneName === "") return; const canvasElement = document.getElementById("real-time-vis-canvas"); if (!canvasElement) return; const hasEntered = { current: false }; const handleDragEnter = (event: DragEvent) => { event.preventDefault(); event.stopPropagation(); if (hasEntered.current || !widgetSelect.startsWith("ui")) return; hasEntered.current = true; const group1 = scene.getObjectByName("itemsGroup"); if (!group1) return; const rect = canvasElement.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster .intersectObjects(scene.children, true) .filter( (intersect) => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.userData.isPathObject && !(intersect.object.type === "GridHelper") ); if (intersects.length > 0) { const { x, y, z } = intersects[0].point; const newWidget: WidgetData = { id: generateUniqueId(), type: widgetSelect, position: [x, y, z], rotation: [0, 0, 0], }; createdWidgetRef.current = newWidget; tempWidget(selectedZone.zoneId, newWidget); // temp add in UI } }; const handleDragOver = (event: DragEvent) => { event.preventDefault(); event.stopPropagation(); event.dataTransfer!.dropEffect = "move"; // ✅ Add this line const widget = createdWidgetRef.current; if (!widget) return; const rect = canvasElement.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster .intersectObjects(scene.children, true) .filter( (intersect) => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.userData.isPathObject && !(intersect.object.type === "GridHelper") ); // Update widget's position in memory if (intersects.length > 0) { const { x, y, z } = intersects[0].point; tempWidgetPosition(selectedZone.zoneId, widget.id, [x, y, z]); widget.position = [x, y, z]; } }; const onDrop = (event: any) => { console.log("onDrop called. hasEntered: ", hasEntered.current); event.preventDefault(); event.stopPropagation(); hasEntered.current = false; const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; const newWidget = createdWidgetRef.current; if (!newWidget || !widgetSelect.startsWith("ui")) return; // ✅ Manual removal of the temp widget (same ID) const prevWidgets = useZoneWidgetStore.getState().zoneWidgetData[selectedZone.zoneId] || []; const cleanedWidgets = prevWidgets.filter(w => w.id !== newWidget.id); useZoneWidgetStore.setState((state) => ({ zoneWidgetData: { ...state.zoneWidgetData, [selectedZone.zoneId]: cleanedWidgets, }, })); // ✅ Now re-add it as final addWidget(selectedZone.zoneId, newWidget); const add3dWidget = { organization, widget: newWidget, zoneId: selectedZone.zoneId, }; if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget); } setTimeout(() => { let pointerDivs = document.getElementsByClassName("pointer-none"); Array.from(pointerDivs).forEach((el) => { el.classList.remove("pointer-none"); }); }, 1000); createdWidgetRef.current = null; }; canvasElement.addEventListener("dragenter", handleDragEnter); canvasElement.addEventListener("dragover", handleDragOver); canvasElement.addEventListener("drop", onDrop); return () => { canvasElement.removeEventListener("dragenter", handleDragEnter); canvasElement.removeEventListener("dragover", handleDragOver); canvasElement.removeEventListener("drop", onDrop); }; }, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption, camera,]); useEffect(() => { if (!rightClickSelected) return; const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; if (rightSelect === "Duplicate") { async function duplicateWidget() { const widgetToDuplicate = activeZoneWidgets.find( (w: WidgetData) => w.id === rightClickSelected ); if (!widgetToDuplicate) return; const newWidget: WidgetData = { id: generateUniqueId(), type: widgetToDuplicate.type, position: [ widgetToDuplicate.position[0] + 0.5, widgetToDuplicate.position[1], widgetToDuplicate.position[2] + 0.5, ], rotation: widgetToDuplicate.rotation || [0, 0, 0], }; const adding3dWidget = { organization: organization, widget: newWidget, zoneId: selectedZone.zoneId, }; if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:add", adding3dWidget); } // let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget) // addWidget(selectedZone.zoneId, newWidget); setRightSelect(null); setRightClickSelected(null); } duplicateWidget(); } if (rightSelect === "Delete") { const deleteWidgetApi = async () => { try { const deleteWidget = { organization, id: rightClickSelected, zoneId: selectedZone.zoneId, }; if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:delete", deleteWidget); } // Call the API to delete the widget // const response = await delete3dWidgetApi(selectedZone.zoneId, organization, rightClickSelected); setZoneWidgetData( selectedZone.zoneId, activeZoneWidgets.filter( (w: WidgetData) => w.id !== rightClickSelected ) ); } catch (error) { } finally { setRightClickSelected(null); setRightSelect(null); } }; deleteWidgetApi(); } }, [rightSelect, rightClickSelected]); useEffect(() => { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; const handleMouseDown = (event: MouseEvent) => { if (!rightClickSelected || !rightSelect) return; if (rightSelect === "RotateX" || rightSelect === "RotateY") { mouseStartRef.current = { x: event.clientX, y: event.clientY }; const selectedZone = Object.keys(zoneWidgetData).find( (zoneId: string) => zoneWidgetData[zoneId].some( (widget: WidgetData) => widget.id === rightClickSelected ) ); if (!selectedZone) return; const selectedWidget = zoneWidgetData[selectedZone].find( (widget: WidgetData) => widget.id === rightClickSelected ); if (selectedWidget) { rotationStartRef.current = selectedWidget.rotation || [0, 0, 0]; } } }; const handleMouseMove = (event: MouseEvent) => { if (!rightClickSelected || !rightSelect) return; const selectedZone = Object.keys(zoneWidgetData).find((zoneId: string) => zoneWidgetData[zoneId].some( (widget: WidgetData) => widget.id === rightClickSelected ) ); if (!selectedZone) return; const selectedWidget = zoneWidgetData[selectedZone].find( (widget: WidgetData) => widget.id === rightClickSelected ); if (!selectedWidget) return; const rect = gl.domElement.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); if ( rightSelect === "Horizontal Move" && raycaster.ray.intersectPlane(plane.current, planeIntersect.current) ) { const newPosition: [number, number, number] = [ planeIntersect.current.x, selectedWidget.position[1], planeIntersect.current.z, ]; updateWidgetPosition(selectedZone, rightClickSelected, newPosition); } if (rightSelect === "Vertical Move") { if ( raycaster.ray.intersectPlane( verticalPlane.current, planeIntersect.current ) ) { updateWidgetPosition(selectedZone, rightClickSelected, [ selectedWidget.position[0], planeIntersect.current.y, selectedWidget.position[2], ]); } } if (rightSelect === "RotateX") { const deltaX = event.clientX - mouseStartRef.current.x; const rotationSpeed = 0.03; const newRotation: [number, number, number] = [ rotationStartRef.current[0] + deltaX * rotationSpeed, rotationStartRef.current[1], rotationStartRef.current[2], ]; updateWidgetRotation(selectedZone, rightClickSelected, newRotation); } if (rightSelect === "RotateY") { const deltaY = event.clientY - mouseStartRef.current.y; const rotationSpeed = 0.03; const newRotation: [number, number, number] = [ rotationStartRef.current[0], rotationStartRef.current[1] + deltaY * rotationSpeed, rotationStartRef.current[2], ]; updateWidgetRotation(selectedZone, rightClickSelected, newRotation); } if (rightSelect === "RotateZ") { const deltaX = event.movementX; const rotationSpeed = 0.03; const currentRotation = selectedWidget.rotation || [0, 0, 0]; const newRotation: [number, number, number] = [ currentRotation[0], currentRotation[1], currentRotation[2] + deltaX * rotationSpeed, ]; updateWidgetRotation(selectedZone, rightClickSelected, newRotation); } }; const handleMouseUp = () => { if (!rightClickSelected || !rightSelect) return; const selectedZone = Object.keys(zoneWidgetData).find((zoneId) => zoneWidgetData[zoneId].some( (widget) => widget.id === rightClickSelected ) ); if (!selectedZone) return; const selectedWidget = zoneWidgetData[selectedZone].find( (widget) => widget.id === rightClickSelected ); if (!selectedWidget) return; // Format values to 2 decimal places const formatValues = (vals: number[]) => vals.map((val) => parseFloat(val.toFixed(2))); if ( rightSelect === "Horizontal Move" || rightSelect === "Vertical Move" ) { let lastPosition = formatValues(selectedWidget.position) as [ number, number, number ]; // (async () => { // let response = await update3dWidget(selectedZone, organization, rightClickSelected, lastPosition); // // if (response) { // // } // })(); let updatingPosition = { organization: organization, zoneId: selectedZone, id: rightClickSelected, position: lastPosition, }; if (visualizationSocket) { visualizationSocket.emit( "v2:viz-3D-widget:modifyPositionRotation", updatingPosition ); } } else if (rightSelect.includes("Rotate")) { const rotation = selectedWidget.rotation || [0, 0, 0]; let lastRotation = formatValues(rotation) as [number, number, number]; // (async () => { // let response = await update3dWidgetRotation(selectedZone, organization, rightClickSelected, lastRotation); // // if (response) { // // } // })(); let updatingRotation = { organization: organization, zoneId: selectedZone, id: rightClickSelected, rotation: lastRotation, }; if (visualizationSocket) { visualizationSocket.emit( "v2:viz-3D-widget:modifyPositionRotation", updatingRotation ); } } // Reset selection setTimeout(() => { setRightClickSelected(null); setRightSelect(null); }, 50); }; window.addEventListener("mousedown", handleMouseDown); window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); return () => { window.removeEventListener("mousedown", handleMouseDown); window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }, [rightClickSelected, rightSelect, zoneWidgetData, gl]); return ( <> {activeZoneWidgets.map( ({ id, type, position, rotation = [0, 0, 0] }: WidgetData) => { const handleRightClick = (event: React.MouseEvent, id: string) => { event.preventDefault(); const canvasElement = document.getElementById( "real-time-vis-canvas" ); if (!canvasElement) throw new Error("Canvas element not found"); const canvasRect = canvasElement.getBoundingClientRect(); const relativeX = event.clientX - canvasRect.left; const relativeY = event.clientY - canvasRect.top; setEditWidgetOptions(true); setRightClickSelected(id); setTop(relativeY); setLeft(relativeX); }; switch (type) { case "ui-Widget 1": return ( handleRightClick(e, id)} /> ); case "ui-Widget 2": return ( handleRightClick(e, id)} /> ); case "ui-Widget 3": return ( handleRightClick(e, id)} /> ); case "ui-Widget 4": return ( handleRightClick(e, id)} /> ); default: return null; } } )} ); }