diff --git a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx b/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx index a719023..243ef4b 100644 --- a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx +++ b/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx @@ -35,19 +35,32 @@ interface ProductionCapacityProps { // onPointerDown:any } -const ProductionCapacity: React.FC = ({ id, type, position, rotation, onContextMenu }) => { +const ProductionCapacity: React.FC = ({ + id, + type, + position, + rotation, + onContextMenu, +}) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); - const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); + const { + measurements: chartMeasurements, + duration: chartDuration, + name: widgetName, + } = useChartStore(); const [measurements, setmeasurements] = useState({}); - const [duration, setDuration] = useState("1h") - const [name, setName] = useState("Widget") - const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ + const [duration, setDuration] = useState("1h"); + const [name, setName] = useState("Widget"); + const [chartData, setChartData] = useState<{ + labels: string[]; + datasets: any[]; + }>({ labels: [], datasets: [], }); const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; const email = localStorage.getItem("email") || ""; - const organization = email?.split("@")[1]?.split(".")[0] + const organization = email?.split("@")[1]?.split(".")[0]; // Chart data for a week const defaultChartData = { labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], // Days of the week @@ -101,7 +114,8 @@ const ProductionCapacity: React.FC = ({ id, type, posit }; useEffect(() => { - if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; + if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) + return; const socket = io(`http://${iotApiUrl}`); @@ -111,7 +125,6 @@ const ProductionCapacity: React.FC = ({ id, type, posit interval: 1000, }; - const startStream = () => { socket.emit("lineInput", inputData); }; @@ -148,22 +161,20 @@ const ProductionCapacity: React.FC = ({ id, type, posit }, [measurements, duration, iotApiUrl]); const fetchSavedInputes = async () => { - if (id !== "") { try { - const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`); + const response = await axios.get( + `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}` + ); if (response.status === 200) { - setmeasurements(response.data.Data.measurements) - setDuration(response.data.Data.duration) - setName(response.data.widgetName) + setmeasurements(response.data.Data.measurements); + setDuration(response.data.Data.duration); + setName(response.data.widgetName); } else { - } - } catch (error) { - - } + } catch (error) { } } - } + }; useEffect(() => { fetchSavedInputes(); @@ -173,13 +184,9 @@ const ProductionCapacity: React.FC = ({ id, type, posit if (selectedChartId?.id === id) { fetchSavedInputes(); } - } - , [chartMeasurements, chartDuration, widgetName]) + }, [chartMeasurements, chartDuration, widgetName]); - useEffect(() => { - - - }, [rotation]) + useEffect(() => { }, [rotation]); const rotationDegrees = { x: (rotation[0] * 180) / Math.PI, y: (rotation[1] * 180) / Math.PI, @@ -187,30 +194,43 @@ const ProductionCapacity: React.FC = ({ id, type, posit }; const transformStyle = { - transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, + transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg) translate(-50%, -50%)`, }; return ( - -
{ + e.preventDefault(); + e.stopPropagation(); + }} + onDrop={(e) => { + e.preventDefault(); + // e.stopPropagation(); + }} + wrapperClass="pointer-none" + className="pointer-none" + > +
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} style={{ - width: '300px', // Original width - height: '300px', // Original height + width: "300px", // Original width + height: "300px", // Original height transform: transformStyle.transform, - transformStyle: 'preserve-3d' + transformStyle: "preserve-3d", + position: "absolute", }} >
@@ -233,10 +253,18 @@ const ProductionCapacity: React.FC = ({ id, type, posit
{" "}
{/* Bar Chart */} - 0 ? chartData : defaultChartData} options={chartOptions} /> + 0 + ? chartData + : defaultChartData + } + options={chartOptions} + />
+ ); }; diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx index 6872497..c54f75c 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx @@ -21,11 +21,13 @@ const Widgets3D = () => { className="widget-item" draggable onDragStart={(e) => { + let name = widget.name let crt = e.target if (crt instanceof HTMLElement) { const widget = crt.cloneNode(true) as HTMLElement; e.dataTransfer.setDragImage(widget, 0, 0) e.dataTransfer.effectAllowed = "move" + e.dataTransfer.setData("text/plain", "ui-" + name) } }} onPointerDown={() => { @@ -40,7 +42,7 @@ const Widgets3D = () => { className="widget-image" src={widget.img} alt={widget.name} - // draggable={false} + draggable={false} />
))} diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index 433052f..139ce9c 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -101,7 +101,7 @@ const SideBarRight: React.FC = () => { )} {toggleUI && subModule === "zoneProperties" && - activeModule === "builder" && ( + (activeModule === "builder" || activeModule === "simulation") && (
@@ -135,7 +135,7 @@ const SideBarRight: React.FC = () => { selectedActionSphere.path.type === "StaticMachine" && (
- +
)} @@ -144,7 +144,7 @@ const SideBarRight: React.FC = () => { selectedActionSphere.path.type === "ArmBot" && (
- +
)} diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/components/ui/componets/Dropped3dWidget.tsx index f03dea8..5dbdb5b 100644 --- a/app/src/components/ui/componets/Dropped3dWidget.tsx +++ b/app/src/components/ui/componets/Dropped3dWidget.tsx @@ -1,6 +1,10 @@ import { useThree } from "@react-three/fiber"; import React, { useEffect, useRef } from "react"; -import { useAsset3dWidget, useSocketStore, useWidgetSubOption } from "../../../store/store"; +import { + useAsset3dWidget, + useSocketStore, + useWidgetSubOption, +} from "../../../store/store"; import useModuleStore from "../../../store/useModuleStore"; import { ThreeState } from "../../../types/world/worldTypes"; import * as THREE from "three"; @@ -13,11 +17,19 @@ 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 { useWidgetStore } from "../../../store/useWidgetStore"; -import EditWidgetOption from "../menu/EditWidgetOption"; +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"; +import { + update3dWidget, + update3dWidgetRotation, +} from "../../../services/realTimeVisulization/zoneData/update3dWidget"; type WidgetData = { id: string; type: string; @@ -26,7 +38,6 @@ type WidgetData = { tempPosition?: [number, number, number]; }; - export default function Dropped3dWidgets() { const { widgetSelect } = useAsset3dWidget(); const { activeModule } = useModuleStore(); @@ -36,19 +47,18 @@ export default function Dropped3dWidgets() { const { top, setTop } = useTopData(); const { left, setLeft } = useLeftData(); const { rightSelect, setRightSelect } = useRightSelected(); - const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore() - const { zoneWidgetData, setZoneWidgetData, addWidget, updateWidgetPosition, updateWidgetRotation } = useZoneWidgetStore(); + 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; @@ -57,7 +67,10 @@ export default function Dropped3dWidgets() { const organization = email?.split("@")[1]?.split(".")[0]; async function get3dWidgetData() { - const result = await get3dWidgetZoneData(selectedZone.zoneId, organization); + const result = await get3dWidgetZoneData( + selectedZone.zoneId, + organization + ); setWidgets3D(result); @@ -68,66 +81,50 @@ export default function Dropped3dWidgets() { 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 = gl.domElement; + const canvasElement = document.getElementById("real-time-vis-canvas"); + + if (!canvasElement) return; + + const hasEntered = { current: false }; const handleDragEnter = (event: DragEvent) => { event.preventDefault(); event.stopPropagation(); - console.log("Drag enter"); - }; + if (hasEntered.current || !widgetSelect.startsWith("ui")) return; + hasEntered.current = true; - const handleDragOver = (event: DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - - }; - - const handleDragLeave = (event: DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - console.log("Drag leave"); - // Remove visual feedback - canvasElement.style.cursor = ""; - }; - - const onDrop = async (event: DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - canvasElement.style.cursor = ""; // Reset cursor - - const email = localStorage.getItem("email") || ""; - const organization = email?.split("@")[1]?.split(".")[0]; - if (!widgetSelect.startsWith("ui")) return; const group1 = scene.getObjectByName("itemsGroup"); if (!group1) return; - // Update raycaster with current mouse position 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") - ); + 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; @@ -138,36 +135,100 @@ export default function Dropped3dWidgets() { rotation: [0, 0, 0], }; - const add3dWidget = { - organization: organization, - widget: newWidget, - zoneId: selectedZone.zoneId - }; - - if (visualizationSocket) { - visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget); - } - - addWidget(selectedZone.zoneId, newWidget); + createdWidgetRef.current = newWidget; + tempWidget(selectedZone.zoneId, newWidget); // temp add in UI } }; - // Add all event listeners - // canvasElement.addEventListener("dragenter", handleDragEnter); - // canvasElement.addEventListener("dragover", handleDragOver); - // canvasElement.addEventListener("dragleave", handleDragLeave); + 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 () => { - // // Clean up all event listeners - // canvasElement.removeEventListener("dragenter", handleDragEnter); - // canvasElement.removeEventListener("dragover", handleDragOver); - // canvasElement.removeEventListener("dragleave", handleDragLeave); + canvasElement.removeEventListener("dragenter", handleDragEnter); + canvasElement.removeEventListener("dragover", handleDragOver); canvasElement.removeEventListener("drop", onDrop); - canvasElement.style.cursor = ""; // Ensure cursor is reset }; - }, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption, gl.domElement, scene, raycaster, camera]); - const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || []; + }, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption, camera,]); + + useEffect(() => { if (!rightClickSelected) return; @@ -176,7 +237,9 @@ export default function Dropped3dWidgets() { if (rightSelect === "Duplicate") { async function duplicateWidget() { - const widgetToDuplicate = activeZoneWidgets.find((w: WidgetData) => w.id === rightClickSelected); + const widgetToDuplicate = activeZoneWidgets.find( + (w: WidgetData) => w.id === rightClickSelected + ); if (!widgetToDuplicate) return; const newWidget: WidgetData = { id: generateUniqueId(), @@ -191,19 +254,19 @@ export default function Dropped3dWidgets() { const adding3dWidget = { organization: organization, widget: newWidget, - zoneId: selectedZone.zoneId + 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() + duplicateWidget(); } if (rightSelect === "Delete") { @@ -215,7 +278,6 @@ export default function Dropped3dWidgets() { zoneId: selectedZone.zoneId, }; - if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:delete", deleteWidget); } @@ -223,10 +285,11 @@ export default function Dropped3dWidgets() { // const response = await delete3dWidgetApi(selectedZone.zoneId, organization, rightClickSelected); setZoneWidgetData( selectedZone.zoneId, - activeZoneWidgets.filter((w: WidgetData) => w.id !== rightClickSelected) + activeZoneWidgets.filter( + (w: WidgetData) => w.id !== rightClickSelected + ) ); } catch (error) { - } finally { setRightClickSelected(null); setRightSelect(null); @@ -238,7 +301,6 @@ export default function Dropped3dWidgets() { }, [rightSelect, rightClickSelected]); useEffect(() => { - const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; const handleMouseDown = (event: MouseEvent) => { @@ -247,13 +309,18 @@ export default function Dropped3dWidgets() { 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) + 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); + const selectedWidget = zoneWidgetData[selectedZone].find( + (widget: WidgetData) => widget.id === rightClickSelected + ); if (selectedWidget) { rotationStartRef.current = selectedWidget.rotation || [0, 0, 0]; } @@ -263,11 +330,15 @@ export default function Dropped3dWidgets() { const handleMouseMove = (event: MouseEvent) => { if (!rightClickSelected || !rightSelect) return; const selectedZone = Object.keys(zoneWidgetData).find((zoneId: string) => - zoneWidgetData[zoneId].some((widget: WidgetData) => widget.id === rightClickSelected) + zoneWidgetData[zoneId].some( + (widget: WidgetData) => widget.id === rightClickSelected + ) ); if (!selectedZone) return; - const selectedWidget = zoneWidgetData[selectedZone].find((widget: WidgetData) => widget.id === rightClickSelected); + const selectedWidget = zoneWidgetData[selectedZone].find( + (widget: WidgetData) => widget.id === rightClickSelected + ); if (!selectedWidget) return; const rect = gl.domElement.getBoundingClientRect(); @@ -276,22 +347,29 @@ export default function Dropped3dWidgets() { raycaster.setFromCamera(mouse, camera); - if (rightSelect === "Horizontal Move" && raycaster.ray.intersectPlane(plane.current, planeIntersect.current)) { + 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 + planeIntersect.current.z, ]; updateWidgetPosition(selectedZone, rightClickSelected, newPosition); - } if (rightSelect === "Vertical Move") { - if (raycaster.ray.intersectPlane(verticalPlane.current, planeIntersect.current)) { + if ( + raycaster.ray.intersectPlane( + verticalPlane.current, + planeIntersect.current + ) + ) { updateWidgetPosition(selectedZone, rightClickSelected, [ selectedWidget.position[0], planeIntersect.current.y, - selectedWidget.position[2] + selectedWidget.position[2], ]); } } @@ -302,7 +380,7 @@ export default function Dropped3dWidgets() { const newRotation: [number, number, number] = [ rotationStartRef.current[0] + deltaX * rotationSpeed, rotationStartRef.current[1], - rotationStartRef.current[2] + rotationStartRef.current[2], ]; updateWidgetRotation(selectedZone, rightClickSelected, newRotation); } @@ -313,7 +391,7 @@ export default function Dropped3dWidgets() { const newRotation: [number, number, number] = [ rotationStartRef.current[0], rotationStartRef.current[1] + deltaY * rotationSpeed, - rotationStartRef.current[2] + rotationStartRef.current[2], ]; updateWidgetRotation(selectedZone, rightClickSelected, newRotation); } @@ -324,31 +402,42 @@ export default function Dropped3dWidgets() { const newRotation: [number, number, number] = [ currentRotation[0], currentRotation[1], - currentRotation[2] + deltaX * rotationSpeed + 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) + 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); + 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]; + 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 = { @@ -356,21 +445,22 @@ export default function Dropped3dWidgets() { zoneId: selectedZone, id: rightClickSelected, position: lastPosition, - } + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-3D-widget:modifyPositionRotation", updatingPosition); + visualizationSocket.emit( + "v2:viz-3D-widget:modifyPositionRotation", + updatingPosition + ); } - - } - else if (rightSelect.includes("Rotate")) { + } 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 = { @@ -378,9 +468,12 @@ export default function Dropped3dWidgets() { zoneId: selectedZone, id: rightClickSelected, rotation: lastRotation, - } + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-3D-widget:modifyPositionRotation", updatingRotation); + visualizationSocket.emit( + "v2:viz-3D-widget:modifyPositionRotation", + updatingRotation + ); } } @@ -403,33 +496,73 @@ export default function Dropped3dWidgets() { 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); - }; + {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; + 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; + } } - })} + )} ); -} \ No newline at end of file +} diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 8044ed2..293f42e 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -28,6 +28,7 @@ import { useRightClickSelected, useRightSelected, } from "../../../store/useZone3DWidgetStore"; +import Dropped3dWidgets from "./Dropped3dWidget"; type Side = "top" | "bottom" | "left" | "right"; @@ -127,6 +128,8 @@ const RealTimeVisulization: React.FC = () => { // useEffect(() => {}, [floatingWidgets]); const handleDrop = async (event: React.DragEvent) => { + event.preventDefault(); + try { event.preventDefault(); const email = localStorage.getItem("email") || ""; @@ -172,7 +175,6 @@ const RealTimeVisulization: React.FC = () => { if (visualizationSocket) { visualizationSocket.emit("v2:viz-float:add", addFloatingWidget); } - // let response = await addingFloatingWidgets( // selectedZone.zoneId, // organization, @@ -198,7 +200,9 @@ const RealTimeVisulization: React.FC = () => { }, })); } catch (error) { } + }; + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const editWidgetOptions = document.querySelector( @@ -219,31 +223,10 @@ const RealTimeVisulization: React.FC = () => { }; }, [setRightClickSelected]); - // Add this useEffect hook to your component - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - const editWidgetOptions = document.querySelector( - ".editWidgetOptions-wrapper" - ); - if ( - editWidgetOptions && - !editWidgetOptions.contains(event.target as Node) - ) { - setRightClickSelected(null); - setRightSelect(null); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [setRightClickSelected]); return ( <> - -
{ }} onDrop={(event) => handleDrop(event)} onDragOver={(event) => event.preventDefault()} + >
diff --git a/app/src/components/ui/componets/zoneAssets.tsx b/app/src/components/ui/componets/zoneAssets.tsx new file mode 100644 index 0000000..7953e36 --- /dev/null +++ b/app/src/components/ui/componets/zoneAssets.tsx @@ -0,0 +1,42 @@ +import React, { useEffect, useRef } from 'react' +import { useselectedFloorItem, useZoneAssetId } from '../../../store/store'; +import * as THREE from "three"; +import { useThree } from '@react-three/fiber'; +import * as Types from "../../../types/world/worldTypes"; +export default function ZoneAssets() { + const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); + const { setselectedFloorItem } = useselectedFloorItem(); + const { raycaster, controls, scene }: any = useThree(); + useEffect(() => { + // console.log('zoneAssetId: ', zoneAssetId); + if (!zoneAssetId) return + console.log('zoneAssetId: ', zoneAssetId); + let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id); + if (!AssetMesh) return; + + const bbox = new THREE.Box3().setFromObject(AssetMesh); + const size = bbox.getSize(new THREE.Vector3()); + const center = bbox.getCenter(new THREE.Vector3()); + + const front = new THREE.Vector3(0, 0, 1); + AssetMesh.localToWorld(front); + front.sub(AssetMesh.position).normalize(); + + const distance = Math.max(size.x, size.y, size.z) * 2; + const newPosition = center.clone().addScaledVector(front, distance); + + controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true); + controls.setTarget(center.x, center.y, center.z, true); + controls.fitToBox(AssetMesh, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, }); + + setselectedFloorItem(AssetMesh); + + }, [zoneAssetId, scene, controls]) + + + + return ( + <> + + ) +} \ No newline at end of file diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index 3260eb6..faa5f4e 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import List from "./List"; import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons"; import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect"; -import { useZones } from "../../../store/store"; +import { useFloorItems, useZones } from "../../../store/store"; import { useSelectedZoneStore } from "../../../store/useZoneStore"; interface DropDownListProps { @@ -38,92 +38,66 @@ const DropDownList: React.FC = ({ const handleToggle = () => { setIsOpen((prev) => !prev); // Toggle the state }; - interface Asset { id: string; name: string; + position: [number, number, number]; // x, y, z } - const [zoneDataList, setZoneDataList] = useState< - { id: string; name: string; assets: Asset[] }[] - >([]); - const [zonePoints3D, setZonePoints3D] = useState<[]>([]); + interface Zone { + zoneId: string; + zoneName: string; + points: [number, number, number][]; // polygon vertices + } + interface ZoneData { + id: string; + name: string; + assets: { id: string; name: string; position?: [] ;rotation?:{}}[]; + } + const [zoneDataList, setZoneDataList] = useState([]); + const { floorItems, setFloorItems } = useFloorItems(); - const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const isPointInsidePolygon = (point: [number, number], polygon: [number, number][]) => { + let inside = false; + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i][0], zi = polygon[i][1]; + const xj = polygon[j][0], zj = polygon[j][1]; - useEffect(() => { - // const value = (zones || []).map( - // (val: { zoneId: string; zoneName: string }) => ({ - // id: val.zoneId, - // name: val.zoneName, - // }) - // ); - // console.log('zones: ', zones); - const value = (zones || []).map((val: { zoneId: string; zoneName: string }) => ({ - id: val.zoneId, - name: val.zoneName - })); - setZoneDataList(prev => (JSON.stringify(prev) !== JSON.stringify(value) ? value : prev)); - const allPoints = zones.flatMap((zone: any) => zone.points); - setZonePoints3D(allPoints); - // setZoneDataList([ - // { - // id: "zone1", - // name: "Zone 1", - // assets: [ - // { - // id: "asset1", - // name: "Asset 1", - // }, - // { - // id: "asset2", - // name: "Asset 2", - // }, - // { - // id: "asset3", - // name: "Asset 3", - // }, - // ], - // }, - // { - // id: "zone2", - // name: "Zone 2", - // assets: [ - // { - // id: "asset4", - // name: "Asset 4", - // }, - // { - // id: "asset5", - // name: "Asset 5", - // }, - // { - // id: "asset6", - // name: "Asset 6", - // }, - // ], - // }, - // { - // id: "zone3", - // name: "Zone 3", - // assets: [ - // { - // id: "asset7", - // name: "Asset 7", - // }, - // { - // id: "asset8", - // name: "Asset 8", - // }, - // ], - // }, - // ]); + const intersect = ((zi > point[1]) !== (zj > point[1])) && + (point[0] < (xj - xi) * (point[1] - zi) / (zj - zi + 0.000001) + xi); - }, [zones]); + if (intersect) inside = !inside; + } + return inside; + }; useEffect(() => { - // console.log('zonePoints3D: ', zonePoints3D); - }, [zonePoints3D]) + const updatedZoneList: ZoneData[] = zones.map((zone: Zone) => { + const polygon2D = zone.points.map((p: [number, number, number]) => [p[0], p[2]]) as [number, number][]; + + const assetsInZone = floorItems + .filter((item: any) => { + const [x, , z] = item.position; + return isPointInsidePolygon([x, z], polygon2D); + }) + .map((item: any) => ({ + id: item.modeluuid, + name: item.modelname, + position: item.position, + rotation:item.rotation + })); + + return { + id: zone.zoneId, + name: zone.zoneName, + assets: assetsInZone, + }; + }); + setZoneDataList(updatedZoneList); + }, [zones, floorItems]); + + + return (
diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index 000bc20..a176e14 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -12,10 +12,14 @@ import { LockIcon, RmoveIcon, } from "../../icons/ExportCommonIcons"; +import { useThree } from "@react-three/fiber"; +import { useZoneAssetId } from "../../../store/store"; interface Asset { id: string; name: string; + position?: [number, number, number]; // Proper 3D vector + rotation?: { x: number; y: number; z: number }; // Proper rotation format } interface ZoneItem { @@ -33,11 +37,13 @@ interface ListProps { const List: React.FC = ({ items = [], remove }) => { const { activeModule, setActiveModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); const { setSubModule } = useSubModuleStore(); const [expandedZones, setExpandedZones] = useState>( {} ); + useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ zoneName: "", @@ -88,7 +94,9 @@ const List: React.FC = ({ items = [], remove }) => { console.error("Error selecting zone:", error); } } - + function handleAssetClick(asset: Asset) { + setZoneAssetId(asset) + } return ( <> {items.length > 0 ? ( @@ -139,7 +147,7 @@ const List: React.FC = ({ items = [], remove }) => { className="list-container asset-item" >
-
+
handleAssetClick(asset)}>
diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 59273d0..3c1bf78 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -19,6 +19,7 @@ import Simulation from "../simulation/simulation"; // import Simulation from "./simulationtemp/simulation"; import ZoneCentreTarget from "../../components/ui/componets/zoneCameraTarget"; import Dropped3dWidgets from "../../components/ui/componets/Dropped3dWidget"; +import ZoneAssets from "../../components/ui/componets/zoneAssets"; export default function Scene() { const map = useMemo( @@ -48,6 +49,7 @@ export default function Scene() { + {savedTheme !== "dark" ? : <>} diff --git a/app/src/store/store.ts b/app/src/store/store.ts index 89cd1a7..2041bd7 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -446,3 +446,23 @@ export const useTileDistance = create((set: any) => ({ planeValue: { ...state.planeValue, ...value }, })), })); + +// Define the Asset type +type Asset = { + id: string; + name: string; + position?: [number, number, number]; // Optional: 3D position + rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation +}; + +// Zustand store type +type ZoneAssetState = { + zoneAssetId: Asset | null; + setZoneAssetId: (asset: Asset | null) => void; +}; + +// Zustand store +export const useZoneAssetId = create((set) => ({ + zoneAssetId: null, + setZoneAssetId: (asset) => set({ zoneAssetId: asset }), +})); \ No newline at end of file diff --git a/app/src/store/useZone3DWidgetStore.ts b/app/src/store/useZone3DWidgetStore.ts index 7dcaaeb..187c9ec 100644 --- a/app/src/store/useZone3DWidgetStore.ts +++ b/app/src/store/useZone3DWidgetStore.ts @@ -12,7 +12,9 @@ type ZoneWidgetStore = { zoneWidgetData: Record; setZoneWidgetData: (zoneId: string, widgets: WidgetData[]) => void; addWidget: (zoneId: string, widget: WidgetData) => void; + tempWidget: (zoneId: string, widget: WidgetData) => void; updateWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => void; + tempWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => void; updateWidgetRotation: (zoneId: string, widgetId: string, newRotation: [number, number, number]) => void; }; @@ -31,6 +33,13 @@ export const useZoneWidgetStore = create((set) => ({ [zoneId]: [...(state.zoneWidgetData[zoneId] || []), { ...widget, rotation: widget.rotation || [0, 0, 0] }], }, })), + tempWidget: (zoneId: string, widget: WidgetData) => + set((state: ZoneWidgetStore) => ({ + zoneWidgetData: { + ...state.zoneWidgetData, + [zoneId]: [...(state.zoneWidgetData[zoneId] || []), { ...widget, rotation: widget.rotation || [0, 0, 0] }], + }, + })), updateWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => set((state: ZoneWidgetStore) => { @@ -44,6 +53,18 @@ export const useZoneWidgetStore = create((set) => ({ }, }; }), + tempWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => + set((state: ZoneWidgetStore) => { + const widgets = state.zoneWidgetData[zoneId] || []; + return { + zoneWidgetData: { + ...state.zoneWidgetData, + [zoneId]: widgets.map((widget: WidgetData) => + widget.id === widgetId ? { ...widget, position: newPosition } : widget + ), + }, + }; + }), updateWidgetRotation: (zoneId: string, widgetId: string, newRotation: [number, number, number]) => set((state: ZoneWidgetStore) => { @@ -59,13 +80,6 @@ export const useZoneWidgetStore = create((set) => ({ }), })); -// export type WidgetData = { -// id: string; -// type: string; -// position: [number, number, number]; -// rotation?: [number, number, number]; -// tempPosition?: [number, number, number]; -// }; interface RightClickStore { rightClickSelected: string | null; diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 9de35db..8fdb90f 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -21,3 +21,7 @@ box-shadow: var(--box-shadow-light); } } + +.pointer-none{ + pointer-events: none; +}