diff --git a/app/package-lock.json b/app/package-lock.json index 8be748e..a820896 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -29,7 +29,6 @@ "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", - "html2canvas": "^1.4.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", @@ -2020,7 +2019,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2032,7 +2031,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -4135,6 +4134,26 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -4246,25 +4265,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "devOptional": true }, "node_modules/@turf/along": { "version": "7.2.0", @@ -8028,15 +8047,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9007,7 +9017,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -9092,15 +9102,6 @@ "postcss": "^8.4" } }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "license": "MIT", - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -9884,7 +9885,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -12488,19 +12489,6 @@ } } }, - "node_modules/html2canvas": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", - "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "license": "MIT", - "dependencies": { - "css-line-break": "^2.1.0", - "text-segmentation": "^1.0.3" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -15247,7 +15235,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -20446,15 +20434,6 @@ "node": "*" } }, - "node_modules/text-segmentation": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "license": "MIT", - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -20715,7 +20694,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20758,7 +20737,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "dependencies": { "acorn": "^8.11.0" }, @@ -20770,7 +20749,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -21241,15 +21220,6 @@ "node": ">= 0.4.0" } }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "license": "MIT", - "dependencies": { - "base64-arraybuffer": "^1.0.2" - } - }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -21266,7 +21236,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -22325,7 +22295,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } diff --git a/app/package.json b/app/package.json index ce5c7d3..66e3b39 100644 --- a/app/package.json +++ b/app/package.json @@ -24,7 +24,6 @@ "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", - "html2canvas": "^1.4.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", diff --git a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx b/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx index a719023..a6343ed 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,21 @@ 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 +185,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 +195,44 @@ 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" + + > +
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 +255,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/3D-cards/cards/ReturnOfInvestment.tsx b/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx index 8e6c707..3d5d291 100644 --- a/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx +++ b/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx @@ -46,20 +46,32 @@ interface ReturnOfInvestmentProps { rotation: [number, number, number]; onContextMenu?: (event: React.MouseEvent) => void; } -const ReturnOfInvestment: React.FC = ({ id, type, position, rotation, onContextMenu }) => { - +const ReturnOfInvestment: 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]; // Improved sample data for the smooth curve graph (single day) const graphData: ChartData<"line"> = { labels: [ @@ -129,7 +141,8 @@ const ReturnOfInvestment: 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}`); @@ -139,7 +152,6 @@ const ReturnOfInvestment: React.FC = ({ id, type, posit interval: 1000, }; - const startStream = () => { socket.emit("lineInput", inputData); }; @@ -157,8 +169,10 @@ const ReturnOfInvestment: React.FC = ({ id, type, posit return { label: datasetKey, data: responseData[datasetKey]?.values ?? [], - borderColor: index === 0 ? "rgba(75, 192, 192, 1)" : "rgba(255, 99, 132, 1)", // Light blue color - backgroundColor: index === 0 ? "rgba(75, 192, 192, 0.2)" : "rgba(255, 99, 132, 0.2)", + borderColor: + index === 0 ? "rgba(75, 192, 192, 1)" : "rgba(255, 99, 132, 1)", // Light blue color + backgroundColor: + index === 0 ? "rgba(75, 192, 192, 0.2)" : "rgba(255, 99, 132, 0.2)", fill: true, tension: 0.4, // Smooth curve effect pointRadius: 0, // Hide dots @@ -177,14 +191,15 @@ const ReturnOfInvestment: 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 { console.log("Unexpected response:", response); } @@ -192,7 +207,7 @@ const ReturnOfInvestment: React.FC = ({ id, type, posit console.error("There was an error!", error); } } - } + }; useEffect(() => { fetchSavedInputes(); @@ -202,8 +217,7 @@ const ReturnOfInvestment: React.FC = ({ id, type, posit if (selectedChartId?.id === id) { fetchSavedInputes(); } - } - , [chartMeasurements, chartDuration, widgetName]) + }, [chartMeasurements, chartDuration, widgetName]); const rotationDegrees = { x: (rotation[0] * 180) / Math.PI, y: (rotation[1] * 180) / Math.PI, @@ -215,26 +229,32 @@ const ReturnOfInvestment: React.FC = ({ id, type, posit }; return ( - -
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} >
Return of Investment
{/* Smooth curve graph with two datasets */} - 0 ? chartData : graphData} options={graphOptions} /> + 0 ? chartData : graphData} + options={graphOptions} + />
diff --git a/app/src/components/layout/3D-cards/cards/StateWorking.tsx b/app/src/components/layout/3D-cards/cards/StateWorking.tsx index 287bc15..829d7bf 100644 --- a/app/src/components/layout/3D-cards/cards/StateWorking.tsx +++ b/app/src/components/layout/3D-cards/cards/StateWorking.tsx @@ -13,16 +13,26 @@ interface StateWorkingProps { rotation: [number, number, number]; onContextMenu?: (event: React.MouseEvent) => void; } -const StateWorking: React.FC = ({ id, type, position, rotation, onContextMenu }) => { +const StateWorking: 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 [duration, setDuration] = useState("1h"); + const [name, setName] = useState("Widget"); const [datas, setDatas] = useState({}); 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]; // const datas = [ // { key: "Oil Tank:", value: "24/341" }, // { key: "Oil Refin:", value: 36.023 }, @@ -33,7 +43,8 @@ const StateWorking: React.FC = ({ id, type, position, rotatio // ]; useEffect(() => { - if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; + if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) + return; const socket = io(`http://${iotApiUrl}`); const inputData = { measurements, @@ -46,7 +57,6 @@ const StateWorking: React.FC = ({ id, type, position, rotatio socket.on("connect", startStream); socket.on("lastOutput", (response) => { const responseData = response; - console.log("responceeeeeeeeeee", response); setDatas(responseData); }); @@ -59,14 +69,15 @@ const StateWorking: React.FC = ({ id, type, position, rotatio }, [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 { console.log("Unexpected response:", response); } @@ -74,10 +85,7 @@ const StateWorking: React.FC = ({ id, type, position, rotatio console.error("There was an error!", error); } } - } - - console.log("dataaaaa", datas); - + }; useEffect(() => { fetchSavedInputes(); @@ -87,8 +95,7 @@ const StateWorking: React.FC = ({ id, type, position, rotatio if (selectedChartId?.id === id) { fetchSavedInputes(); } - } - , [chartMeasurements, chartDuration, widgetName]) + }, [chartMeasurements, chartDuration, widgetName]); const rotationDegrees = { x: (rotation[0] * 180) / Math.PI, @@ -100,19 +107,22 @@ const StateWorking: React.FC = ({ id, type, position, rotatio transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, }; return ( - -
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} > @@ -120,12 +130,10 @@ const StateWorking: React.FC = ({ id, type, position, rotatio
State - {datas?.input1 ? datas.input1 : 'input1'} . + {datas?.input1 ? datas.input1 : "input1"} .
-
- {/* */} -
+
{/* */}
{/* Data */}
@@ -136,28 +144,52 @@ const StateWorking: React.FC = ({ id, type, position, rotatio
))} */}
-
{measurements?.input2?.fields ? measurements.input2.fields : 'input2'}
-
{datas?.input2 ? datas.input2 : 'data'}
+
+ {measurements?.input2?.fields + ? measurements.input2.fields + : "input2"} +
+
{datas?.input2 ? datas.input2 : "data"}
-
{measurements?.input3?.fields ? measurements.input3.fields : 'input3'}
-
{datas?.input3 ? datas.input3 : 'data'}
+
+ {measurements?.input3?.fields + ? measurements.input3.fields + : "input3"} +
+
{datas?.input3 ? datas.input3 : "data"}
-
{measurements?.input4?.fields ? measurements.input4.fields : 'input4'}
-
{datas?.input4 ? datas.input4 : 'data'}
+
+ {measurements?.input4?.fields + ? measurements.input4.fields + : "input4"} +
+
{datas?.input4 ? datas.input4 : "data"}
-
{measurements?.input5?.fields ? measurements.input5.fields : 'input5'}
-
{datas?.input5 ? datas.input5 : 'data'}
+
+ {measurements?.input5?.fields + ? measurements.input5.fields + : "input5"} +
+
{datas?.input5 ? datas.input5 : "data"}
-
{measurements?.input6?.fields ? measurements.input6.fields : 'input6'}
-
{datas?.input6 ? datas.input6 : 'data'}
+
+ {measurements?.input6?.fields + ? measurements.input6.fields + : "input6"} +
+
{datas?.input6 ? datas.input6 : "data"}
-
{measurements?.input7?.fields ? measurements.input7.fields : 'input7'}
-
{datas?.input7 ? datas.input7 : 'data'}
+
+ {measurements?.input7?.fields + ? measurements.input7.fields + : "input7"} +
+
{datas?.input7 ? datas.input7 : "data"}
@@ -166,5 +198,3 @@ const StateWorking: React.FC = ({ id, type, position, rotatio }; export default StateWorking; - - diff --git a/app/src/components/layout/3D-cards/cards/Throughput.tsx b/app/src/components/layout/3D-cards/cards/Throughput.tsx index de3109b..a99d171 100644 --- a/app/src/components/layout/3D-cards/cards/Throughput.tsx +++ b/app/src/components/layout/3D-cards/cards/Throughput.tsx @@ -49,20 +49,32 @@ interface ThroughputProps { onContextMenu?: (event: React.MouseEvent) => void; } -const Throughput: React.FC = ({ id, type, position, rotation, onContextMenu }) => { - +const Throughput: 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]; // Sample data for the line graph const graphData: ChartData<"line"> = { @@ -112,7 +124,8 @@ const Throughput: React.FC = ({ id, type, position, rotation, o }; useEffect(() => { - if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; + if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) + return; const socket = io(`http://${iotApiUrl}`); @@ -122,7 +135,6 @@ const Throughput: React.FC = ({ id, type, position, rotation, o interval: 1000, }; - const startStream = () => { socket.emit("lineInput", inputData); }; @@ -157,14 +169,15 @@ const Throughput: React.FC = ({ id, type, position, rotation, o }, [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 { console.log("Unexpected response:", response); } @@ -172,7 +185,7 @@ const Throughput: React.FC = ({ id, type, position, rotation, o console.error("There was an error!", error); } } - } + }; useEffect(() => { fetchSavedInputes(); @@ -182,8 +195,7 @@ const Throughput: React.FC = ({ id, type, position, rotation, o if (selectedChartId?.id === id) { fetchSavedInputes(); } - } - , [chartMeasurements, chartDuration, widgetName]) + }, [chartMeasurements, chartDuration, widgetName]); const rotationDegrees = { x: (rotation[0] * 180) / Math.PI, y: (rotation[1] * 180) / Math.PI, @@ -195,19 +207,22 @@ const Throughput: React.FC = ({ id, type, position, rotation, o }; return ( - -
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} > @@ -234,7 +249,10 @@ const Throughput: React.FC = ({ id, type, position, rotation, o
{/* Line graph using react-chartjs-2 */} - 0 ? chartData : graphData} options={graphOptions} /> + 0 ? chartData : graphData} + options={graphOptions} + />
You made an extra $1256.13 this month diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index c924b0c..67bf969 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -129,6 +129,7 @@ const Assets: React.FC = () => { } else { try { const res = await getCategoryAsset(asset); + console.log('res: ', res); setCategoryAssets(res); setFiltereredAssets(res); } catch (error) {} @@ -147,7 +148,7 @@ const Assets: React.FC = () => {
{categoryAssets && categoryAssets?.map((asset: any, index: number) => ( -
+
{asset.filename} {
{ setSelectedCategory(null); setCategoryAssets([]); @@ -182,7 +184,7 @@ const Assets: React.FC = () => {
{categoryAssets && categoryAssets?.map((asset: any, index: number) => ( -
+
{asset.filename} { onPointerDown={() => setSelectedItem({ name: asset.filename, - id: asset.modelfileID, + id: asset.AssetID, }) } /> @@ -222,6 +224,7 @@ const Assets: React.FC = () => {
fetchCategoryAssets(category)} > { const dropdownItems = [ { id: "1", name: "Ground Floor" }, - { id: "2", name: "Floor 1" }, + // { id: "2", name: "Floor 1" }, ]; // Example dropdown items return ( diff --git a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx index b99b297..436af7f 100644 --- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx @@ -3,13 +3,11 @@ import { useDroppedObjectsStore } from "../../../../store/useDroppedObjectsStore import useTemplateStore from "../../../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../../../store/useZoneStore"; import { getTemplateData } from "../../../../services/realTimeVisulization/zoneData/getTemplate"; -import { deleteTemplateApi } from "../../../../services/realTimeVisulization/zoneData/deleteTemplate"; -import { loadTempleteApi } from "../../../../services/realTimeVisulization/zoneData/loadTemplate"; import { useSocketStore } from "../../../../store/store"; +import RenameInput from "../../../ui/inputs/RenameInput"; const Templates = () => { - const { templates, removeTemplate } = useTemplateStore(); - const { setTemplates } = useTemplateStore(); + const { templates, removeTemplate, setTemplates } = useTemplateStore(); const { setSelectedZone, selectedZone } = useSelectedZoneStore(); const { visualizationSocket } = useSocketStore(); @@ -35,15 +33,14 @@ const Templates = () => { let deleteTemplate = { organization: organization, templateID: id, - } + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-template:deleteTemplate", deleteTemplate) + visualizationSocket.emit( + "v2:viz-template:deleteTemplate", + deleteTemplate + ); } removeTemplate(id); - // let response = await deleteTemplateApi(id, organization); - - // if (response.message === "Template deleted successfully") { - // } } catch (error) { console.error("Error deleting template:", error); } @@ -60,114 +57,59 @@ const Templates = () => { organization: organization, zoneId: selectedZone.zoneId, templateID: template.id, - } - console.log('template: ', template); - console.log('loadingTemplate: ', loadingTemplate); + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-template:addToZone", loadingTemplate) + visualizationSocket.emit("v2:viz-template:addToZone", loadingTemplate); } - // let response = await loadTempleteApi(template.id, selectedZone.zoneId, organization); - // if (response.message === "Template placed in Zone") { - setSelectedZone({ - panelOrder: template.panelOrder, - activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides` - widgets: template.widgets, + setSelectedZone({ + panelOrder: template.panelOrder, + activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides` + widgets: template.widgets, + }); + + useDroppedObjectsStore + .getState() + .setZone(selectedZone.zoneName, selectedZone.zoneId); + + if (Array.isArray(template.floatingWidget)) { + template.floatingWidget.forEach((val: any) => { + useDroppedObjectsStore + .getState() + .addObject(selectedZone.zoneName, val); }); - - useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId); - - if (Array.isArray(template.floatingWidget)) { - template.floatingWidget.forEach((val: any) => { - useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, val); - }); - } - // } + } } catch (error) { console.error("Error loading template:", error); } }; - return ( -
- {templates.map((template) => ( -
+
+ {templates.map((template, index) => ( +
{template?.snapshot && ( -
- {" "} - {/* 16:9 aspect ratio */} +
{`${template.name} handleLoadTemplate(template)} />
)} -
+
handleLoadTemplate(template)} - style={{ - cursor: "pointer", - fontWeight: "500", - // ':hover': { - // textDecoration: 'underline' - // } - }} + className="template-name" > - {template.name} + {/* {`Template ${index + 1}`} */} +
))} {templates.length === 0 && ( -
+
No saved templates yet. Create one in the visualization view!
)} @@ -192,4 +127,3 @@ const Templates = () => { }; export default Templates; - 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/sidebarLeft/visualization/widgets/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx index 8c3df32..09c481a 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx @@ -46,7 +46,7 @@ const WidgetsFloating = () => { ))} */} {/* Floating 1 */} {
{user.userName[0]}
diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index 4821772..f991478 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -16,19 +16,21 @@ import Analysis from "./analysis/Analysis"; import Simulations from "./simulation/Simulations"; import { useSelectedActionSphere, - useselectedFloorItem, + useSelectedFloorItem, } from "../../../store/store"; import GlobalProperties from "./properties/GlobalProperties"; import AsstePropertiies from "./properties/AssetProperties"; import ZoneProperties from "./properties/ZoneProperties"; import VehicleMechanics from "./mechanics/VehicleMechanics"; +import StaticMachineMechanics from "./mechanics/StaticMachineMechanics"; +import ArmBotMechanics from "./mechanics/ArmBotMechanics"; const SideBarRight: React.FC = () => { const { activeModule } = useModuleStore(); const { toggleUI } = useToggleStore(); const { selectedActionSphere } = useSelectedActionSphere(); const { subModule, setSubModule } = useSubModuleStore(); - const { selectedFloorItem } = useselectedFloorItem(); + const { selectedFloorItem } = useSelectedFloorItem(); // Reset activeList whenever activeModule changes useEffect(() => { if (activeModule !== "simulation") setSubModule("properties"); @@ -42,9 +44,8 @@ const SideBarRight: React.FC = () => {
{/* {activeModule === "builder" && ( */}
setSubModule("properties")} > @@ -53,25 +54,22 @@ const SideBarRight: React.FC = () => { {activeModule === "simulation" && ( <>
setSubModule("mechanics")} >
setSubModule("simulations")} >
setSubModule("analysis")} > @@ -103,7 +101,7 @@ const SideBarRight: React.FC = () => { )} {toggleUI && subModule === "zoneProperties" && - activeModule === "builder" && ( + (activeModule === "builder" || activeModule === "simulation") && (
@@ -132,10 +130,28 @@ const SideBarRight: React.FC = () => {
)} + {subModule === "mechanics" && + selectedActionSphere && + selectedActionSphere.path.type === "StaticMachine" && ( +
+
+ +
+
+ )} + {subModule === "mechanics" && + selectedActionSphere && + selectedActionSphere.path.type === "ArmBot" && ( +
+
+ +
+
+ )} {subModule === "mechanics" && !selectedActionSphere && (
- + {/* default */}
)} diff --git a/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx new file mode 100644 index 0000000..cfa7822 --- /dev/null +++ b/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx @@ -0,0 +1,401 @@ +import React, { useRef, useMemo, useCallback, useState } from "react"; +import { InfoIcon, AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +import { useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store"; +import * as Types from '../../../../types/world/worldTypes'; +import LabledDropdown from "../../../ui/inputs/LabledDropdown"; +import { handleResize } from "../../../../functions/handleResizePannel"; + +interface ConnectedModel { + modelUUID: string; + modelName: string; + points: { + uuid: string; + position: [number, number, number]; + index?: number; + }[]; + triggers?: { + uuid: string; + name: string; + type: string; + isUsed: boolean; + }[]; +} + +const ArmBotMechanics: React.FC = () => { + const { selectedActionSphere } = useSelectedActionSphere(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { socket } = useSocketStore(); + const [selectedProcessIndex, setSelectedProcessIndex] = useState(null); + const actionsContainerRef = useRef(null); + + // Get connected models and their triggers + const connectedModels = useMemo(() => { + if (!selectedActionSphere?.points?.uuid) return []; + + const armBotPaths = simulationStates.filter( + (path): path is Types.ArmBotEventsSchema => path.type === "ArmBot" + ); + + const currentPoint = armBotPaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; + + if (!currentPoint?.connections?.targets) return []; + + return currentPoint.connections.targets.reduce((acc, target) => { + const connectedModel = simulationStates.find( + (model) => model.modeluuid === target.modelUUID + ); + + if (!connectedModel) return acc; + + let triggers: { uuid: string; name: string; type: string; isUsed: boolean }[] = []; + let points: { uuid: string; position: [number, number, number] }[] = []; + + if (connectedModel.type === "Conveyor") { + const conveyor = connectedModel as Types.ConveyorEventsSchema; + + const connectedPointUUIDs = currentPoint?.connections?.targets + .filter(t => t.modelUUID === connectedModel.modeluuid) + .map(t => t.pointUUID) || []; + + points = conveyor.points + .map((point, idx) => ({ + uuid: point.uuid, + position: point.position, + index: idx + })) + .filter(point => connectedPointUUIDs.includes(point.uuid)); + + + triggers = conveyor.points.flatMap(p => p.triggers?.filter(t => t.isUsed) || []); + } + else if (connectedModel.type === "StaticMachine") { + const staticMachine = connectedModel as Types.StaticMachineEventsSchema; + + points = [{ + uuid: staticMachine.points.uuid, + position: staticMachine.points.position + }]; + + triggers = staticMachine.points.triggers ? + [{ + uuid: staticMachine.points.triggers.uuid, + name: staticMachine.points.triggers.name, + type: staticMachine.points.triggers.type, + isUsed: true // StaticMachine triggers are always considered used + }] : []; + } + + if (!acc.some(m => m.modelUUID === connectedModel.modeluuid)) { + acc.push({ + modelUUID: connectedModel.modeluuid, + modelName: connectedModel.modelName, + points, + triggers + }); + } + + return acc; + }, []); + }, [selectedActionSphere, simulationStates]); + + // Get triggers from connected models + const connectedTriggers = useMemo(() => { + return connectedModels.flatMap(model => + (model.triggers || []).map(trigger => ({ + ...trigger, + displayName: `${model.modelName} - ${trigger.name}`, + modelUUID: model.modelUUID + })) + ); + }, [connectedModels]); + + // Get all points from connected models + const connectedPoints = useMemo(() => { + return connectedModels.flatMap(model => + model.points.map(point => ({ + ...point, + displayName: `${model.modelName} - Point${typeof point.index === 'number' ? ` ${point.index}` : ''}`, + modelUUID: model.modelUUID + })) + ); + }, [connectedModels]); + + + const { selectedPoint } = useMemo(() => { + if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null }; + + const armBotPaths = simulationStates.filter( + (path): path is Types.ArmBotEventsSchema => path.type === "ArmBot" + ); + + const points = armBotPaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; + + return { + selectedPoint: points || null + }; + }, [selectedActionSphere, simulationStates]); + + const updateBackend = async (updatedPath: Types.ArmBotEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "ArmBot", points: updatedPath.points } + } + console.log('data: ', data); + + socket.emit('v2:model-asset:updateEventData', data); + } + + const handleActionUpdate = useCallback((updatedAction: Partial) => { + if (!selectedActionSphere?.points?.uuid || !selectedPoint) return; + + const updatedPaths = simulationStates.map((path) => { + if (path.type === "ArmBot" && path.points.uuid === selectedActionSphere.points.uuid) { + return { + ...path, + points: { + ...path.points, + actions: { + ...path.points.actions, + ...updatedAction + } + } + }; + } + return path; + }); + + const updatedPath = updatedPaths.find( + (path): path is Types.ArmBotEventsSchema => + path.type === "ArmBot" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, selectedPoint, simulationStates, setSimulationStates]); + + const handleSpeedChange = useCallback((speed: number) => { + handleActionUpdate({ speed }); + }, [handleActionUpdate]); + + const handleProcessChange = useCallback((processes: Types.ArmBotEventsSchema['points']['actions']['processes']) => { + handleActionUpdate({ processes }); + }, [handleActionUpdate]); + + const handleAddProcess = useCallback(() => { + if (!selectedPoint) return; + + const newProcess: any = { + triggerId: "", + startPoint: "", + endPoint: "" + }; + + const updatedProcesses = selectedPoint.actions.processes ? [...selectedPoint.actions.processes, newProcess] : [newProcess]; + + handleProcessChange(updatedProcesses); + setSelectedProcessIndex(updatedProcesses.length - 1); + }, [selectedPoint, handleProcessChange]); + + const handleDeleteProcess = useCallback((index: number) => { + if (!selectedPoint?.actions.processes) return; + + const updatedProcesses = [...selectedPoint.actions.processes]; + updatedProcesses.splice(index, 1); + + handleProcessChange(updatedProcesses); + + // Reset selection if deleting the currently selected process + if (selectedProcessIndex === index) { + setSelectedProcessIndex(null); + } else if (selectedProcessIndex !== null && selectedProcessIndex > index) { + // Adjust selection index if needed + setSelectedProcessIndex(selectedProcessIndex - 1); + } + }, [selectedPoint, selectedProcessIndex, handleProcessChange]); + + const handleTriggerSelect = useCallback((displayName: string, index: number) => { + const selected = connectedTriggers.find(t => t.displayName === displayName); + if (!selected || !selectedPoint?.actions.processes) return; + + const oldProcess = selectedPoint.actions.processes[index]; + + const updatedProcesses = [...selectedPoint.actions.processes]; + + // Only reset start/end if new trigger invalidates them (your logic can expand this) + updatedProcesses[index] = { + ...oldProcess, + triggerId: selected.uuid, + startPoint: oldProcess.startPoint || "", // preserve if exists + endPoint: oldProcess.endPoint || "" // preserve if exists + }; + + handleProcessChange(updatedProcesses); + }, [connectedTriggers, selectedPoint, handleProcessChange]); + + const handleStartPointSelect = useCallback((displayName: string, index: number) => { + if (!selectedPoint?.actions.processes) return; + + const point = connectedPoints.find(p => p.displayName === displayName); + if (!point) return; + + const updatedProcesses = [...selectedPoint.actions.processes]; + updatedProcesses[index] = { + ...updatedProcesses[index], + startPoint: point.uuid + }; + + handleProcessChange(updatedProcesses); + }, [selectedPoint, connectedPoints, handleProcessChange]); + + const handleEndPointSelect = useCallback((displayName: string, index: number) => { + if (!selectedPoint?.actions.processes) return; + + const point = connectedPoints.find(p => p.displayName === displayName); + if (!point) return; + + const updatedProcesses = [...selectedPoint.actions.processes]; + updatedProcesses[index] = { + ...updatedProcesses[index], + endPoint: point.uuid + }; + + handleProcessChange(updatedProcesses); + }, [selectedPoint, connectedPoints, handleProcessChange]); + + const getProcessByIndex = useCallback((index: number) => { + if (!selectedPoint?.actions.processes || index >= selectedPoint.actions.processes.length) return null; + return selectedPoint.actions.processes[index]; + }, [selectedPoint]); + + const getFilteredTriggerOptions = (currentIndex: number) => { + const usedTriggerUUIDs = selectedPoint?.actions.processes?.filter((_, i) => i !== currentIndex).map(p => p.triggerId).filter(Boolean) ?? []; + + return connectedTriggers.filter(trigger => !usedTriggerUUIDs.includes(trigger.uuid)).map(trigger => trigger.displayName); + }; + + return ( +
+
+ {selectedActionSphere?.path?.modelName || "ArmBot point not found"} +
+ +
+
+
ArmBot Properties
+ + {selectedPoint && ( + <> + handleSpeedChange(parseInt(value))} + /> + +
+
+
Processes
+
+ Add +
+
+
+
+ {selectedPoint.actions.processes?.map((process, index) => ( +
+
setSelectedProcessIndex(index)} + > + Process {index + 1} +
+
handleDeleteProcess(index)} + > + +
+
+ ))} +
+
handleResize(e, actionsContainerRef)} + > + +
+
+
+ + {selectedProcessIndex !== null && ( +
+ + t.uuid === getProcessByIndex(selectedProcessIndex)?.triggerId + )?.displayName || 'Select a trigger' + } + onSelect={(value) => handleTriggerSelect(value, selectedProcessIndex)} + options={getFilteredTriggerOptions(selectedProcessIndex)} + /> + + + p.uuid === getProcessByIndex(selectedProcessIndex)?.startPoint + )?.displayName || 'Select start point' + } + onSelect={(value) => handleStartPointSelect(value, selectedProcessIndex)} + options={connectedPoints.map(point => point.displayName)} + /> + + + p.uuid === getProcessByIndex(selectedProcessIndex)?.endPoint + )?.displayName || 'Select end point' + } + onSelect={(value) => handleEndPointSelect(value, selectedProcessIndex)} + options={connectedPoints.map(point => point.displayName)} + /> +
+ )} + + )} +
+ +
+ + Configure ArmBot properties and trigger-based processes. +
+
+
+ ); +}; + +export default React.memo(ArmBotMechanics); \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index 8ad0886..71ee9ce 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -1,20 +1,20 @@ import React, { useRef, useState, useMemo, useEffect } from "react"; import { - AddIcon, - InfoIcon, - RemoveIcon, - ResizeHeightIcon, + AddIcon, + InfoIcon, + RemoveIcon, + ResizeHeightIcon, } from "../../../icons/ExportCommonIcons"; import RenameInput from "../../../ui/inputs/RenameInput"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import LabledDropdown from "../../../ui/inputs/LabledDropdown"; import { handleResize } from "../../../../functions/handleResizePannel"; import { - useFloorItems, - useSelectedActionSphere, - useSelectedPath, - useSimulationPaths, - useSocketStore, + useFloorItems, + useSelectedActionSphere, + useSelectedPath, + useSimulationStates, + useSocketStore, } from "../../../../store/store"; import * as THREE from "three"; import * as Types from "../../../../types/world/worldTypes"; @@ -23,859 +23,857 @@ import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floo import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; const ConveyorMechanics: React.FC = () => { - const { selectedActionSphere } = useSelectedActionSphere(); - const { selectedPath, setSelectedPath } = useSelectedPath(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); - const { floorItems, setFloorItems } = useFloorItems(); - const { socket } = useSocketStore(); + const { selectedActionSphere } = useSelectedActionSphere(); + const { selectedPath, setSelectedPath } = useSelectedPath(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { floorItems, setFloorItems } = useFloorItems(); + const { socket } = useSocketStore(); - const actionsContainerRef = useRef(null); - const triggersContainerRef = useRef(null); + const actionsContainerRef = useRef(null); + const triggersContainerRef = useRef(null); - const selectedPoint = useMemo(() => { - if (!selectedActionSphere) return null; - return simulationPaths - .filter( - (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" - ) - .flatMap((path) => path.points) - .find((point) => point.uuid === selectedActionSphere.points.uuid); - }, [selectedActionSphere, simulationPaths]); + const selectedPoint = useMemo(() => { + if (!selectedActionSphere) return null; + return simulationStates + .filter( + (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" + ) + .flatMap((path) => path.points) + .find((point) => point.uuid === selectedActionSphere.points.uuid); + }, [selectedActionSphere, simulationStates]); - const updateBackend = async (updatedPath: Types.ConveyorEventsSchema | undefined) => { - if (!updatedPath) return; - const email = localStorage.getItem("email"); - const organization = email ? email.split("@")[1].split(".")[0] : ""; + const updateBackend = async (updatedPath: Types.ConveyorEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; - // await setEventApi( - // organization, - // updatedPath.modeluuid, - // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } - // ); + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + } + + socket.emit('v2:model-asset:updateEventData', data); - const data = { - organization: organization, - modeluuid: updatedPath.modeluuid, - eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } } - socket.emit('v2:model-asset:updateEventData', data); + const handleAddAction = () => { + if (!selectedActionSphere) return; - } + const updatedPaths = simulationStates.map((path) => { + if (path.type === "Conveyor") { + return { + ...path, + points: path.points.map((point) => { + if (point.uuid === selectedActionSphere.points.uuid) { + const actionIndex = point.actions.length; + const newAction = { + uuid: THREE.MathUtils.generateUUID(), + name: `Action ${actionIndex + 1}`, + type: "Inherit", + material: "Inherit", + delay: "Inherit", + spawnInterval: "Inherit", + isUsed: false, + }; - const handleAddAction = () => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Conveyor") { - return { - ...path, - points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.points.uuid) { - const actionIndex = point.actions.length; - const newAction = { - uuid: THREE.MathUtils.generateUUID(), - name: `Action ${actionIndex + 1}`, - type: "Inherit", - material: "Inherit", - delay: "Inherit", - spawnInterval: "Inherit", - isUsed: false, - }; - - return { ...point, actions: [...point.actions, newAction] }; + return { ...point, actions: [...point.actions, newAction] }; + } + return point; + }), + }; } - return point; - }), - }; - } - return path; - }); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - }; - - const handleDeleteAction = (uuid: string) => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.filter( - (action) => action.uuid !== uuid - ), - } - : point - ), - } - : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - }; - - const handleActionSelect = (uuid: string, actionType: string) => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { - ...action, - type: actionType, - material: - actionType === "Spawn" || actionType === "Swap" - ? "Inherit" - : action.material, - delay: - actionType === "Delay" ? "Inherit" : action.delay, - spawnInterval: - actionType === "Spawn" - ? "Inherit" - : action.spawnInterval, - } - : action - ), - } - : point - ), - } - : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - - // Update the selected item to reflect changes - if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { - const updatedAction = updatedPaths - .filter( - (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" - ) - .flatMap((path) => path.points) - .find((p) => p.uuid === selectedActionSphere.points.uuid) - ?.actions.find((a) => a.uuid === uuid); - - if (updatedAction) { - setSelectedItem({ - type: "action", - item: updatedAction, + return path; }); - } - } - }; - // Modified handleMaterialSelect to ensure it only applies to relevant action types - const handleMaterialSelect = (uuid: string, material: string) => { - if (!selectedActionSphere) return; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid && - (action.type === "Spawn" || action.type === "Swap") - ? { ...action, material } - : action - ), - } - : point - ), - } - : path - ); + setSimulationStates(updatedPaths); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const handleDeleteAction = (uuid: string) => { + if (!selectedActionSphere) return; - setSimulationPaths(updatedPaths); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.filter( + (action) => action.uuid !== uuid + ), + } + : point + ), + } + : path + ); - // Update selected item if it's the current action - if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - material, - }, - }); - } - }; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const handleDelayChange = (uuid: string, delay: number | string) => { - if (!selectedActionSphere) return; + setSimulationStates(updatedPaths); + }; - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid ? { ...action, delay } : action - ), - } - : point - ), - } - : path - ); + const handleActionSelect = (uuid: string, actionType: string) => { + if (!selectedActionSphere) return; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { + ...action, + type: actionType, + material: + actionType === "Spawn" || actionType === "Swap" + ? "Inherit" + : action.material, + delay: + actionType === "Delay" ? "Inherit" : action.delay, + spawnInterval: + actionType === "Spawn" + ? "Inherit" + : action.spawnInterval, + } + : action + ), + } + : point + ), + } + : path + ); - setSimulationPaths(updatedPaths); - }; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const handleSpawnIntervalChange = ( - uuid: string, - spawnInterval: number | string - ) => { - if (!selectedActionSphere) return; + setSimulationStates(updatedPaths); - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { ...action, spawnInterval } - : action - ), - } - : point - ), - } - : path - ); + // Update the selected item to reflect changes + if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { + const updatedAction = updatedPaths + .filter( + (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" + ) + .flatMap((path) => path.points) + .find((p) => p.uuid === selectedActionSphere.points.uuid) + ?.actions.find((a) => a.uuid === uuid); - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - }; - - const handleSpeedChange = (speed: number | string) => { - if (!selectedPath) return; - - const updatedPaths = simulationPaths.map((path) => - path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); - }; - - const handleAddTrigger = () => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.points.uuid) { - const triggerIndex = point.triggers.length; - const newTrigger = { - uuid: THREE.MathUtils.generateUUID(), - name: `Trigger ${triggerIndex + 1}`, - type: "", - bufferTime: 0, - isUsed: false, - }; - - return { ...point, triggers: [...point.triggers, newTrigger] }; + if (updatedAction) { + setSelectedItem({ + type: "action", + item: updatedAction, + }); } - return point; - }), } - : path - ); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + // Modified handleMaterialSelect to ensure it only applies to relevant action types + const handleMaterialSelect = (uuid: string, material: string) => { + if (!selectedActionSphere) return; - setSimulationPaths(updatedPaths); - }; + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid && + (action.type === "Spawn" || action.type === "Swap") + ? { ...action, material } + : action + ), + } + : point + ), + } + : path + ); - const handleDeleteTrigger = (uuid: string) => { - if (!selectedActionSphere) return; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.filter( - (trigger) => trigger.uuid !== uuid - ), - } - : point - ), + setSimulationStates(updatedPaths); + + // Update selected item if it's the current action + if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + material, + }, + }); } - : path - ); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const handleDelayChange = (uuid: string, delay: number | string) => { + if (!selectedActionSphere) return; - setSimulationPaths(updatedPaths); - }; + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid ? { ...action, delay } : action + ), + } + : point + ), + } + : path + ); - const handleTriggerSelect = (uuid: string, triggerType: string) => { - if (!selectedActionSphere) return; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid - ? { ...trigger, type: triggerType } - : trigger - ), - } - : point - ), - } - : path - ); + setSimulationStates(updatedPaths); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const handleSpawnIntervalChange = ( + uuid: string, + spawnInterval: number | string + ) => { + if (!selectedActionSphere) return; - setSimulationPaths(updatedPaths); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { ...action, spawnInterval } + : action + ), + } + : point + ), + } + : path + ); - // Ensure the selectedItem is updated immediately - const updatedTrigger = updatedPaths - .flatMap((path) => (path.type === "Conveyor" ? path.points : [])) - .flatMap((point) => point.triggers) - .find((trigger) => trigger.uuid === uuid); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - if (updatedTrigger) { - setSelectedItem({ type: "trigger", item: updatedTrigger }); - } - }; + setSimulationStates(updatedPaths); + }; - // Update the toggle handlers to immediately update the selected item - const handleActionToggle = (uuid: string) => { - if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => ({ - ...action, - isUsed: action.uuid === uuid ? !action.isUsed : false, - })), - } - : point - ), - } - : path - ); + const handleSpeedChange = (speed: number | string) => { + if (!selectedPath) return; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const updatedPaths = simulationStates.map((path) => + path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path + ); - setSimulationPaths(updatedPaths); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.modeluuid === selectedPath.path.modeluuid + ); + updateBackend(updatedPath); - // Immediately update the selected item if it's the one being toggled - if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - isUsed: !selectedItem.item.isUsed, - }, - }); - } - }; + setSimulationStates(updatedPaths); + setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); + }; - // Do the same for trigger toggle - const handleTriggerToggle = (uuid: string) => { - if (!selectedActionSphere) return; + const handleAddTrigger = () => { + if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => ({ - ...trigger, - isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, - })), - } - : point - ), - } - : path - ); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => { + if (point.uuid === selectedActionSphere.points.uuid) { + const triggerIndex = point.triggers.length; + const newTrigger = { + uuid: THREE.MathUtils.generateUUID(), + name: `Trigger ${triggerIndex + 1}`, + type: "", + bufferTime: 0, + isUsed: false, + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - - // Immediately update the selected item if it's the one being toggled - if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - isUsed: !selectedItem.item.isUsed, - }, - }); - } - }; - - const handleTriggerBufferTimeChange = (uuid: string, bufferTime: number) => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationPaths.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid - ? { ...trigger, bufferTime } - : trigger - ), - } - : point - ), - } - : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationPaths(updatedPaths); - - // Immediately update selectedItem if it's the currently selected trigger - if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - bufferTime, - }, - }); - } - }; - - const [selectedItem, setSelectedItem] = useState<{ - type: "action" | "trigger"; - item: any; - } | null>(null); - - useEffect(() => { - setSelectedItem(null); - }, [selectedActionSphere]); - - return ( -
- {!selectedPath && ( -
- {selectedActionSphere?.path?.modelName || "point name not found"} -
- )} - - {selectedPath && ( -
- {selectedPath.path.modelName || "path name not found"} -
- )} - -
- {!selectedPath && ( - <> -
-
-
Actions
-
- Add -
-
-
-
- {selectedPoint?.actions.map((action) => ( -
-
- setSelectedItem({ type: "action", item: action }) + return { ...point, triggers: [...point.triggers, newTrigger] }; } - > - - -
-
handleDeleteAction(action.uuid)} - > - -
-
- ))} -
-
handleResize(e, actionsContainerRef)} - > - -
-
-
-
-
-
Triggers
-
- Add -
-
-
-
- {selectedPoint?.triggers.map((trigger) => ( -
-
- setSelectedItem({ type: "trigger", item: trigger }) - } - > - - -
-
handleDeleteTrigger(trigger.uuid)} - > - -
-
- ))} -
-
handleResize(e, triggersContainerRef)} - > - -
-
-
- - )} + return point; + }), + } + : path + ); -
- {selectedItem && ( - <> -
{selectedItem.item.name}
+ const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - {selectedItem.type === "action" && ( - <> - handleActionToggle(selectedItem.item.uuid)} - /> - - handleActionSelect(selectedItem.item.uuid, option) - } - /> + setSimulationStates(updatedPaths); + }; - {/* Only show material dropdown for Spawn/Swap actions */} - {(selectedItem.item.type === "Spawn" || - selectedItem.item.type === "Swap") && ( - - handleMaterialSelect(selectedItem.item.uuid, option) - } - /> + const handleDeleteTrigger = (uuid: string) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.filter( + (trigger) => trigger.uuid !== uuid + ), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }; + + const handleTriggerSelect = (uuid: string, triggerType: string) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, type: triggerType } + : trigger + ), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Ensure the selectedItem is updated immediately + const updatedTrigger = updatedPaths + .flatMap((path) => (path.type === "Conveyor" ? path.points : [])) + .flatMap((point) => point.triggers) + .find((trigger) => trigger.uuid === uuid); + + if (updatedTrigger) { + setSelectedItem({ type: "trigger", item: updatedTrigger }); + } + }; + + // Update the toggle handlers to immediately update the selected item + const handleActionToggle = (uuid: string) => { + if (!selectedActionSphere) return; + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => ({ + ...action, + isUsed: action.uuid === uuid ? !action.isUsed : false, + })), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Immediately update the selected item if it's the one being toggled + if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + isUsed: !selectedItem.item.isUsed, + }, + }); + } + }; + + // Do the same for trigger toggle + const handleTriggerToggle = (uuid: string) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => ({ + ...trigger, + isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, + })), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Immediately update the selected item if it's the one being toggled + if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + isUsed: !selectedItem.item.isUsed, + }, + }); + } + }; + + const handleTriggerBufferTimeChange = (uuid: string, bufferTime: number) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, bufferTime } + : trigger + ), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Immediately update selectedItem if it's the currently selected trigger + if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + bufferTime, + }, + }); + } + }; + + const [selectedItem, setSelectedItem] = useState<{ + type: "action" | "trigger"; + item: any; + } | null>(null); + + useEffect(() => { + setSelectedItem(null); + }, [selectedActionSphere]); + + return ( +
+ {!selectedPath && ( +
+ {selectedActionSphere?.path?.modelName || "point name not found"} +
+ )} + + {selectedPath && ( +
+ {selectedPath.path.modelName || "path name not found"} +
+ )} + +
+ {!selectedPath && ( + <> +
+
+
Actions
+
+ Add +
+
+
+
+ {selectedPoint?.actions.map((action) => ( +
+
+ setSelectedItem({ type: "action", item: action }) + } + > + + +
+
handleDeleteAction(action.uuid)} + > + +
+
+ ))} +
+
handleResize(e, actionsContainerRef)} + > + +
+
+
+
+
+
Triggers
+
+ Add +
+
+
+
+ {selectedPoint?.triggers.map((trigger) => ( +
+
+ setSelectedItem({ type: "trigger", item: trigger }) + } + > + + +
+
handleDeleteTrigger(trigger.uuid)} + > + +
+
+ ))} +
+
handleResize(e, triggersContainerRef)} + > + +
+
+
+ + )} + +
+ {selectedItem && ( + <> +
{selectedItem.item.name}
+ + {selectedItem.type === "action" && ( + <> + handleActionToggle(selectedItem.item.uuid)} + /> + + handleActionSelect(selectedItem.item.uuid, option) + } + /> + + {/* Only show material dropdown for Spawn/Swap actions */} + {(selectedItem.item.type === "Spawn" || + selectedItem.item.type === "Swap") && ( + + handleMaterialSelect(selectedItem.item.uuid, option) + } + /> + )} + + {/* Only show delay input for Delay actions */} + {selectedItem.item.type === "Delay" && ( + { + const numValue = parseInt(value); + handleDelayChange( + selectedItem.item.uuid, + !value ? "Inherit" : numValue + ); + }} + /> + )} + + {/* Only show spawn interval for Spawn actions */} + {selectedItem.item.type === "Spawn" && ( + { + handleSpawnIntervalChange( + selectedItem.item.uuid, + value === "" ? "Inherit" : parseInt(value) + ); + }} + /> + )} + + )} + + {selectedItem.type === "trigger" && ( + <> + handleTriggerToggle(selectedItem.item.uuid)} + /> + + + handleTriggerSelect(selectedItem.item.uuid, option) + } + /> + + {selectedItem.item.type === "Buffer" && ( + { + handleTriggerBufferTimeChange( + selectedItem.item.uuid, + parseInt(value) + ); + }} + /> + )} + + )} + )} - {/* Only show delay input for Delay actions */} - {selectedItem.item.type === "Delay" && ( - { - const numValue = parseInt(value); - handleDelayChange( - selectedItem.item.uuid, - !value ? "Inherit" : numValue - ); - }} - /> - )} - - {/* Only show spawn interval for Spawn actions */} - {selectedItem.item.type === "Spawn" && ( - { - handleSpawnIntervalChange( - selectedItem.item.uuid, - value === "" ? "Inherit" : parseInt(value) - ); - }} - /> - )} - - )} - - {selectedItem.type === "trigger" && ( - <> - handleTriggerToggle(selectedItem.item.uuid)} - /> - - - handleTriggerSelect(selectedItem.item.uuid, option) - } - /> - - {selectedItem.item.type === "Buffer" && ( - { - handleTriggerBufferTimeChange( - selectedItem.item.uuid, - parseInt(value) - ); - }} - /> - )} - - )} - - )} - - {selectedPath && !selectedItem && ( -
- - handleSpeedChange(value === "" ? "Inherit" : parseInt(value)) - } - /> + {selectedPath && !selectedItem && ( +
+ + handleSpeedChange(value === "" ? "Inherit" : parseInt(value)) + } + /> +
+ )} +
+ {!selectedPath && ( +
+ + Configure the point's action and trigger properties. +
+ )} + {selectedPath && ( +
+ + Configure the path properties. +
+ )}
- )}
- {!selectedPath && ( -
- - Configure the point's action and trigger properties. -
- )} - {selectedPath && ( -
- - Configure the path properties. -
- )} -
-
- ); + ); }; export default ConveyorMechanics; diff --git a/app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx new file mode 100644 index 0000000..564f221 --- /dev/null +++ b/app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx @@ -0,0 +1,188 @@ +import React, { useRef, useMemo, useCallback } from "react"; +import { InfoIcon } from "../../../icons/ExportCommonIcons"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +import { useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store"; +import * as Types from '../../../../types/world/worldTypes'; +import LabledDropdown from "../../../ui/inputs/LabledDropdown"; +import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; + +const StaticMachineMechanics: React.FC = () => { + const { selectedActionSphere } = useSelectedActionSphere(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { socket } = useSocketStore(); + + const propertiesContainerRef = useRef(null); + + const { selectedPoint, connectedPointUuids } = useMemo(() => { + if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; + + const staticMachinePaths = simulationStates.filter( + (path): path is Types.StaticMachineEventsSchema => path.type === "StaticMachine" + ); + + const points = staticMachinePaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; + + if (!points) return { selectedPoint: null, connectedPointUuids: [] }; + + const connectedUuids: string[] = []; + if (points.connections?.targets) { + points.connections.targets.forEach(target => { + connectedUuids.push(target.pointUUID); + }); + } + + return { + selectedPoint: points, + connectedPointUuids: connectedUuids + }; + }, [selectedActionSphere, simulationStates]); + + const updateBackend = async (updatedPath: Types.StaticMachineEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Vehicle", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "StaticMachine", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + } + + const handleActionUpdate = useCallback((updatedAction: Partial) => { + if (!selectedActionSphere?.points?.uuid) return; + + const updatedPaths = simulationStates.map((path) => { + if (path.type === "StaticMachine" && path.points.uuid === selectedActionSphere.points.uuid) { + return { + ...path, + points: { + ...path.points, + actions: { + ...path.points.actions, + ...updatedAction + } + } + }; + } + return path; + }); + + const updatedPath = updatedPaths.find( + (path): path is Types.StaticMachineEventsSchema => + path.type === "StaticMachine" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); + + const handleBufferChange = useCallback((buffer: number) => { + handleActionUpdate({ buffer }); + }, [handleActionUpdate]); + + const handleMaterialChange = useCallback((material: string) => { + handleActionUpdate({ material }); + }, [handleActionUpdate]); + + const handleTriggerChange = useCallback((updatedTrigger: Partial) => { + if (!selectedActionSphere?.points?.uuid) return; + + const updatedPaths = simulationStates.map((path) => { + if (path.type === "StaticMachine" && path.points.uuid === selectedActionSphere.points.uuid) { + return { + ...path, + points: { + ...path.points, + triggers: { + ...path.points.triggers, + ...updatedTrigger + } + } + }; + } + return path; + }); + + const updatedPath = updatedPaths.find( + (path): path is Types.StaticMachineEventsSchema => + path.type === "StaticMachine" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); + + const handleTriggerTypeChange = useCallback((type: string) => { + handleTriggerChange({ type }); + }, [handleTriggerChange]); + + return ( +
+
+ {selectedActionSphere?.path?.modelName || "Machine point not found"} +
+ + +
+
+
Machine Properties
+ + {selectedPoint && ( + <> + handleBufferChange(parseInt(value))} + /> + + handleMaterialChange(value)} + options={["Inherit", "Crate", "Box"]} + /> + + handleTriggerTypeChange(value)} + options={["OnComplete", "OnStart"]} + /> + + {/* { + // Implement reset functionality if needed + }} + /> */} + + )} +
+ +
+ + Configure machine interaction properties and triggers. +
+
+
+ ); +}; + +export default React.memo(StaticMachineMechanics); \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx index e9bd6a2..67d132f 100644 --- a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx @@ -1,14 +1,15 @@ import React, { useRef, useMemo } from "react"; import { InfoIcon } from "../../../icons/ExportCommonIcons"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; -import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationPaths, useSocketStore } from "../../../../store/store"; +import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store"; import * as Types from '../../../../types/world/worldTypes'; import PositionInput from "../customInput/PositionInputs"; import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; +import LabeledButton from "../../../ui/inputs/LabledButton"; const VehicleMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); const { editingPoint, setEditingPoint } = useEditingPoint(); const { previewPosition, setPreviewPosition } = usePreviewPosition(); @@ -19,7 +20,7 @@ const VehicleMechanics: React.FC = () => { const { selectedPoint, connectedPointUuids } = useMemo(() => { if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; - const vehiclePaths = simulationPaths.filter( + const vehiclePaths = simulationStates.filter( (path): path is Types.VehicleEventsSchema => path.type === "Vehicle" ); @@ -40,7 +41,7 @@ const VehicleMechanics: React.FC = () => { selectedPoint: points, connectedPointUuids: connectedUuids }; - }, [selectedActionSphere, simulationPaths]); + }, [selectedActionSphere, simulationStates]); const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { if (!updatedPath) return; @@ -66,7 +67,7 @@ const VehicleMechanics: React.FC = () => { const handleActionUpdate = React.useCallback((updatedAction: Partial) => { if (!selectedActionSphere?.points?.uuid) return; - const updatedPaths = simulationPaths.map((path) => { + const updatedPaths = simulationStates.map((path) => { if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, @@ -89,8 +90,8 @@ const VehicleMechanics: React.FC = () => { ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); - }, [selectedActionSphere?.points?.uuid, simulationPaths, setSimulationPaths]); + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); const handleHitCountChange = React.useCallback((hitCount: number) => { handleActionUpdate({ hitCount }); @@ -103,7 +104,7 @@ const VehicleMechanics: React.FC = () => { const handleSpeedChange = React.useCallback((speed: number) => { if (!selectedActionSphere?.points?.uuid) return; - const updatedPaths = simulationPaths.map((path) => { + const updatedPaths = simulationStates.map((path) => { if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, @@ -123,8 +124,35 @@ const VehicleMechanics: React.FC = () => { ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); - }, [selectedActionSphere?.points?.uuid, simulationPaths, setSimulationPaths]); + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); + + + const ResetVehicleState = React.useCallback(() => { + if (!selectedActionSphere?.points?.uuid) return; + + const updatedPaths = simulationStates.map((state) => { + if (state.type === "Vehicle" && state.points.uuid === selectedActionSphere.points.uuid) { + return { + ...state, + points: { + ...state.points, + actions: { ...state.points.actions, start: {}, end: {} } + } + }; + } + return state; + }); + + const updatedPath = updatedPaths.find( + (path): path is Types.VehicleEventsSchema => + path.type === "Vehicle" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); const handleStartEyeDropClick = () => { setEditingPoint('start'); @@ -193,6 +221,14 @@ const VehicleMechanics: React.FC = () => { handleEyeDropClick={handleEndEyeDropClick} /> + { + ResetVehicleState(); + }} + /> + { const [userData, setUserData] = useState([]); // State to track user data const [nextId, setNextId] = useState(1); // Unique ID for new entries - const { selectedFloorItem } = useselectedFloorItem(); + const { selectedFloorItem } = useSelectedFloorItem(); // Function to handle adding new user data const handleAddUserData = () => { const newUserData: UserData = { diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 9e8b37e..6492252 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import RenameInput from "../../../ui/inputs/RenameInput"; import Vector3Input from "../customInput/Vector3Input"; import { useSelectedZoneStore } from "../../../../store/useZoneStore"; -import { useEditPosition, usezonePosition, usezoneTarget } from "../../../../store/store"; +import { useEditPosition, usezonePosition, useZones, usezoneTarget } from "../../../../store/store"; import { zoneCameraUpdate } from "../../../../services/realTimeVisulization/zoneData/zoneCameraUpdation"; const ZoneProperties: React.FC = () => { @@ -10,6 +10,7 @@ const ZoneProperties: React.FC = () => { const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { zonePosition, setZonePosition } = usezonePosition(); const { zoneTarget, setZoneTarget } = usezoneTarget(); + const { zones, setZones } = useZones(); useEffect(() => { setZonePosition(selectedZone.zoneViewPortPosition) @@ -28,11 +29,14 @@ const ZoneProperties: React.FC = () => { }; let response = await zoneCameraUpdate(zonesdata, organization); - console.log('response: ', response); + if (response.message === "updated successfully") { + setEdit(false); + } else { + console.log(response); + } - setEdit(false); } catch (error) { - console.error("Error in handleSetView:", error); + } } @@ -40,17 +44,32 @@ const ZoneProperties: React.FC = () => { setEdit(!Edit); // This will toggle the `Edit` state correctly } - function handleZoneNameChange(newName: string) { - setSelectedZone((prev) => ({ ...prev, zoneName: newName })); + async function handleZoneNameChange(newName: string) { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + const zonesdata = { + zoneId: selectedZone.zoneId, + zoneName: newName + }; + // Call your API to update the zone + let response = await zoneCameraUpdate(zonesdata, organization); + console.log('response: ', response); + if (response.message === "updated successfully") { + setZones((prevZones: any[]) => + prevZones.map((zone) => + zone.zoneId === selectedZone.zoneId + ? { ...zone, zoneName: newName } + : zone + ) + ); + }else{ + console.log(response?.message); + } } - function handleVectorChange(key: "zoneViewPortTarget" | "zoneViewPortPosition", newValue: [number, number, number]) { setSelectedZone((prev) => ({ ...prev, [key]: newValue })); } - useEffect(() => { - - }, [selectedZone]); return (
diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index e78b4dd..4b54c88 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -42,16 +42,19 @@ const Design = () => { const [elementColor, setElementColor] = useState("#6f42c1"); const [showColorPicker, setShowColorPicker] = useState(false); const [chartElements, setChartElements] = useState([]); - const [selectedElementToStyle, setSelectedElementToStyle] = useState(null); + const [selectedElementToStyle, setSelectedElementToStyle] = useState< + string | null + >(null); const [nameInput, setNameInput] = useState(""); const chartRef = useRef(null); - const { selectedChartId, setSelectedChartId, widgets, setWidgets } = useWidgetStore(); + const { selectedChartId, setSelectedChartId, widgets, setWidgets } = + useWidgetStore(); // Initialize name input and extract elements when selectedChartId changes useEffect(() => { setNameInput(selectedChartId?.header || selectedChartId?.title || ""); - + if (!chartRef.current) return; const timer = setTimeout(() => { @@ -65,13 +68,16 @@ const Design = () => { }) .map((el, index) => { const tagName = el.tagName.toLowerCase(); - const className = typeof el.className === "string" ? el.className : ""; + const className = + typeof el.className === "string" ? el.className : ""; const textContent = el.textContent?.trim() || ""; let selector = tagName; if (className && typeof className === "string") { - const classList = className.split(/\s+/).filter((c) => c.length > 0); + const classList = className + .split(/\s+/) + .filter((c) => c.length > 0); if (classList.length > 0) { selector += "." + classList.join("."); } @@ -126,7 +132,13 @@ const Design = () => { useEffect(() => { applyStyles(); - }, [selectedFont, selectedSize, selectedWeight, elementColor, selectedElementToStyle]); + }, [ + selectedFont, + selectedSize, + selectedWeight, + elementColor, + selectedElementToStyle, + ]); const handleUpdateWidget = (updatedProperties: Partial) => { if (!selectedChartId) return; @@ -138,7 +150,9 @@ const Design = () => { setSelectedChartId(updatedChartId); const updatedWidgets = widgets.map((widget) => - widget.id === selectedChartId.id ? { ...widget, ...updatedProperties } : widget + widget.id === selectedChartId.id + ? { ...widget, ...updatedProperties } + : widget ); setWidgets(updatedWidgets); }; @@ -146,7 +160,7 @@ const Design = () => { const handleNameChange = (e: React.ChangeEvent) => { const newName = e.target.value; setNameInput(newName); - + if (selectedChartId?.title) { handleUpdateWidget({ title: newName }); } else if (selectedChartId?.header) { @@ -155,12 +169,12 @@ const Design = () => { }; const defaultChartData = { - labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + labels: ["January", "February", "March", "April", "May", "June", "July"], datasets: [ { data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: elementColor, - borderColor: "#ffffff", + backgroundColor: "#6f42c1", + borderColor: "#b392f0", borderWidth: 1, }, ], @@ -311,4 +325,4 @@ const Design = () => { ); }; -export default Design; \ No newline at end of file +export default Design; diff --git a/app/src/components/ui/ModuleToggle.tsx b/app/src/components/ui/ModuleToggle.tsx index b7421af..053e251 100644 --- a/app/src/components/ui/ModuleToggle.tsx +++ b/app/src/components/ui/ModuleToggle.tsx @@ -7,13 +7,11 @@ import { VisualizationIcon, } from "../icons/ExportModuleIcons"; import useToggleStore from "../../store/useUIToggleStore"; -import { useSelectedZoneStore } from "../../store/useZoneStore"; const ModuleToggle: React.FC = () => { const { activeModule, setActiveModule } = useModuleStore(); const { setToggleUI } = useToggleStore(); - return (
{ const { templates } = useTemplateStore(); - const [activeSubTool, setActiveSubTool] = useState("cursor"); + const { activeSubTool, setActiveSubTool } = useActiveSubTool(); const { toggleThreeD, setToggleThreeD } = useThreeDStore(); const { setToggleUI } = useToggleStore(); @@ -52,11 +54,14 @@ const Tools: React.FC = () => { const { addTemplate } = useTemplateStore(); const { selectedZone } = useSelectedZoneStore(); const { floatingWidget } = useFloatingWidget(); + const { widgets3D } = use3DWidget(); + const zones = useDroppedObjectsStore((state) => state.zones); + // wall options const { toggleView, setToggleView } = useToggleView(); - const { setDeleteModels } = useDeleteModels(); + const { setDeleteTool } = useDeleteTool(); const { setAddAction } = useAddAction(); const { setSelectedWallItem } = useSelectedWallItem(); @@ -84,7 +89,7 @@ const Tools: React.FC = () => { const toggleSwitch = () => { if (toggleThreeD) { setSelectedWallItem(null); - setDeleteModels(false); + setDeleteTool(false); setAddAction(null); setToggleView(true); // localStorage.setItem("navBarUi", JSON.stringify(!toggleThreeD)); @@ -131,7 +136,7 @@ const Tools: React.FC = () => { useEffect(() => { setToolMode(null); - setDeleteModels(false); + setDeleteTool(false); setAddAction(null); setTransformMode(null); setMovePoint(false); @@ -197,7 +202,7 @@ const Tools: React.FC = () => { if (toggleView) { setDeletePointOrLine(true); } else { - setDeleteModels(true); + setDeleteTool(true); } break; @@ -409,10 +414,9 @@ const Tools: React.FC = () => { widgets3D, selectedZone, templates, - visualizationSocket - }) - } - } + visualizationSocket, + }); + }} >
diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index d5bfb29..eb70dea 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -1,17 +1,22 @@ -import React, { useEffect } from "react"; +import React from "react"; import { CleanPannel, EyeIcon, LockIcon, } from "../../icons/RealTimeVisulationIcons"; -import { panelData } from "../../../services/realTimeVisulization/zoneData/panel"; import { AddIcon } from "../../icons/ExportCommonIcons"; -import { deletePanelApi } from "../../../services/realTimeVisulization/zoneData/deletePanel"; import { useSocketStore } from "../../../store/store"; +import { clearPanel } from "../../../services/realTimeVisulization/zoneData/clearPanel"; +import { lockPanel } from "../../../services/realTimeVisulization/zoneData/lockPanel"; // Define the type for `Side` type Side = "top" | "bottom" | "left" | "right"; +// Define the type for HiddenPanels, where keys are zone IDs and values are arrays of hidden sides +interface HiddenPanels { + [zoneId: string]: Side[]; +} + // Define the type for the props passed to the Buttons component interface ButtonsProps { selectedZone: { @@ -35,7 +40,6 @@ interface ButtonsProps { zoneName: string; activeSides: Side[]; panelOrder: Side[]; - lockedPanels: Side[]; zoneId: string; zoneViewPortTarget: number[]; @@ -49,8 +53,8 @@ interface ButtonsProps { }[]; }> >; - hiddenPanels: Side[]; // Add this prop for hidden panels - setHiddenPanels: React.Dispatch>; // Add this prop for updating hidden panels + hiddenPanels: HiddenPanels; // Updated prop type + setHiddenPanels: React.Dispatch>; // Updated prop type } const AddButtons: React.FC = ({ @@ -59,13 +63,38 @@ const AddButtons: React.FC = ({ setHiddenPanels, hiddenPanels, }) => { - const { visualizationSocket } = useSocketStore(); - // Local state to track hidden panels + // Function to toggle visibility of a panel + const toggleVisibility = (side: Side) => { + const isHidden = hiddenPanels[selectedZone.zoneId]?.includes(side) ?? false; + + if (isHidden) { + // If the panel is already hidden, remove it from the hiddenPanels array for this zone + setHiddenPanels((prevHiddenPanels) => ({ + ...prevHiddenPanels, + [selectedZone.zoneId]: prevHiddenPanels[selectedZone.zoneId].filter( + (panel) => panel !== side + ), + })); + } else { + // If the panel is visible, add it to the hiddenPanels array for this zone + setHiddenPanels((prevHiddenPanels) => ({ + ...prevHiddenPanels, + [selectedZone.zoneId]: [ + ...(prevHiddenPanels[selectedZone.zoneId] || []), + side, + ], + })); + } + }; // Function to toggle lock/unlock a panel - const toggleLockPanel = (side: Side) => { + const toggleLockPanel = async (side: Side) => { + // console.log('side: ', side); + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value + //add api const newLockedPanels = selectedZone.lockedPanels.includes(side) ? selectedZone.lockedPanels.filter((panel) => panel !== side) : [...selectedZone.lockedPanels, side]; @@ -75,35 +104,65 @@ const AddButtons: React.FC = ({ lockedPanels: newLockedPanels, }; - // Update the selectedZone state - setSelectedZone(updatedZone); - }; - - // Function to toggle visibility of a panel - const toggleVisibility = (side: Side) => { - const isHidden = hiddenPanels.includes(side); - if (isHidden) { - // If the panel is already hidden, remove it from the hiddenPanels array - setHiddenPanels(hiddenPanels.filter((panel) => panel !== side)); - } else { - // If the panel is visible, add it to the hiddenPanels array - setHiddenPanels([...hiddenPanels, side]); + let lockedPanel = { + organization: organization, + lockedPanel: newLockedPanels, + zoneId: selectedZone.zoneId, + }; + if (visualizationSocket) { + visualizationSocket.emit("v2:viz-panel:locked", lockedPanel); } + + setSelectedZone(updatedZone); + // let response = await lockPanel(selectedZone.zoneId, organization, newLockedPanels) + // console.log('response: ', response); + // if (response.message === 'locked panel updated successfully') { + // // Update the selectedZone state + // setSelectedZone(updatedZone); + // } + }; // Function to clean all widgets from a panel - const cleanPanel = (side: Side) => { + const cleanPanel = async (side: Side) => { + //add api + // console.log('side: ', side); + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value + + let clearPanel = { + organization: organization, + panelName: side, + zoneId: selectedZone.zoneId, + }; + if (visualizationSocket) { + visualizationSocket.emit("v2:viz-panel:clear", clearPanel); + } const cleanedWidgets = selectedZone.widgets.filter( (widget) => widget.panel !== side ); - const updatedZone = { ...selectedZone, widgets: cleanedWidgets, }; - // Update the selectedZone state + // console.log('updatedZone: ', updatedZone); setSelectedZone(updatedZone); + + // let response = await clearPanel(selectedZone.zoneId, organization, side) + // console.log('response: ', response); + // if (response.message === 'PanelWidgets cleared successfully') { + + // const cleanedWidgets = selectedZone.widgets.filter( + // (widget) => widget.panel !== side + // ); + // const updatedZone = { + // ...selectedZone, + // widgets: cleanedWidgets, + // }; + // // Update the selectedZone state + // setSelectedZone(updatedZone); + // } }; // Function to handle "+" button click @@ -129,23 +188,23 @@ const AddButtons: React.FC = ({ let deletePanel = { organization: organization, panelName: side, - zoneId: selectedZone.zoneId - } + zoneId: selectedZone.zoneId, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-panel:delete", deletePanel) + visualizationSocket.emit("v2:viz-panel:delete", deletePanel); } setSelectedZone(updatedZone); // API call to delete the panel // try { // const response = await deletePanelApi(selectedZone.zoneId, side, organization); - // + // // if (response.message === "Panel deleted successfully") { // } else { - // + // // } // } catch (error) { - // + // // } } else { // Panel does not exist: Create panel @@ -167,27 +226,23 @@ const AddButtons: React.FC = ({ let addPanel = { organization: organization, zoneId: selectedZone.zoneId, - panelOrder: newActiveSides - } + panelOrder: newActiveSides, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-panel:add", addPanel) + visualizationSocket.emit("v2:viz-panel:add", addPanel); } setSelectedZone(updatedZone); // API call to create panels // const response = await panelData(organization, selectedZone.zoneId, newActiveSides); - // + // // if (response.message === "Panels created successfully") { // } else { - // + // // } - } catch (error) { - - } + } catch (error) { } } }; - - return ( <>
@@ -214,15 +269,24 @@ const AddButtons: React.FC = ({
{/* Hide Panel */}
toggleVisibility(side)} >
diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index 0890e5c..e0a5c07 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -74,6 +74,7 @@ const DisplayZone: React.FC = ({ const [showLeftArrow, setShowLeftArrow] = useState(false); const [showRightArrow, setShowRightArrow] = useState(false); const { floatingWidget, setFloatingWidget } = useFloatingWidget() + const{setSelectedChartId}=useWidgetStore() // Function to calculate overflow state @@ -156,6 +157,7 @@ const DisplayZone: React.FC = ({ const organization = email?.split("@")[1]?.split(".")[0]; let response = await getSelect2dZoneData(zoneId, organization); + let res = await getFloatingZoneData(zoneId, organization); setFloatingWidget(res); @@ -178,7 +180,7 @@ const DisplayZone: React.FC = ({ zoneViewPortPosition: response.viewPortposition || {}, }); } catch (error) { - console.error(error); + } } diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index fa7cff8..fb20169 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -18,6 +18,7 @@ import { deleteWidgetApi } from "../../../services/realTimeVisulization/zoneData import { useClickOutside } from "./functions/handleWidgetsOuterClick"; import { useSocketStore } from "../../../store/store"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import OuterClick from "../../../utils/outerClick"; type Side = "top" | "bottom" | "left" | "right"; @@ -87,7 +88,15 @@ export const DraggableWidget = ({ const chartWidget = useRef(null); - const isPanelHidden = hiddenPanels.includes(widget.panel); + OuterClick({ + contextClassName: [ + "chart-container", + "floating", + "sidebar-right-wrapper", + "card", + ], + setMenuVisible: () => setSelectedChartId(null), + }); const deleteSelectedChart = async () => { try { @@ -96,16 +105,16 @@ export const DraggableWidget = ({ let deleteWidget = { zoneId: selectedZone.zoneId, widgetID: widget.id, - organization: organization - } - console.log('deleteWidget: ', deleteWidget); + organization: organization, + }; + if (visualizationSocket) { - visualizationSocket.emit("v2:viz-widget:delete", deleteWidget) + visualizationSocket.emit("v2:viz-widget:delete", deleteWidget); } const updatedWidgets = selectedZone.widgets.filter( (w: Widget) => w.id !== widget.id ); - console.log('updatedWidgets: ', updatedWidgets); + setSelectedZone((prevZone: any) => ({ ...prevZone, widgets: updatedWidgets, @@ -168,10 +177,10 @@ export const DraggableWidget = ({ let duplicateWidget = { organization: organization, zoneId: selectedZone.zoneId, - widget: duplicatedWidget - } + widget: duplicatedWidget, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-widget:add", duplicateWidget) + visualizationSocket.emit("v2:viz-widget:add", duplicateWidget); } setSelectedZone((prevZone: any) => ({ ...prevZone, @@ -245,21 +254,62 @@ export const DraggableWidget = ({ // }); const { isPlaying } = usePlayButtonStore(); - + const [canvasDimensions, setCanvasDimensions] = useState({ + width: 0, + height: 0, + }); + // Track canvas dimensions + + // Current: Two identical useEffect hooks for canvas dimensions + // Remove the duplicate and keep only one + useEffect(() => { + const canvas = document.getElementById("real-time-vis-canvas"); + if (!canvas) return; + + const updateCanvasDimensions = () => { + const rect = canvas.getBoundingClientRect(); + setCanvasDimensions({ + width: rect.width, + height: rect.height, + }); + }; + + updateCanvasDimensions(); + const resizeObserver = new ResizeObserver(updateCanvasDimensions); + resizeObserver.observe(canvas); + + return () => resizeObserver.unobserve(canvas); + }, []); + return ( <> +
setSelectedChartId(widget)} @@ -273,8 +323,9 @@ export const DraggableWidget = ({ {openKebabId === widget.id && (
@@ -348,3 +399,5 @@ export const DraggableWidget = ({ ); }; + +// by using canvasDimensions.height canvasDimensions.width dynamically div value insted of static 6 and 4 calculate according to canvasDimensions.width canvasDimensions.height diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/components/ui/componets/Dropped3dWidget.tsx index 567aa7a..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,20 +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 plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); - // const verticalPlane = useRef(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); - // 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; @@ -58,8 +67,11 @@ export default function Dropped3dWidgets() { const organization = email?.split("@")[1]?.split(".")[0]; async function get3dWidgetData() { - const result = await get3dWidgetZoneData(selectedZone.zoneId, organization); - console.log('result: ', result); + const result = await get3dWidgetZoneData( + selectedZone.zoneId, + organization + ); + setWidgets3D(result); const formattedWidgets = result.map((widget: WidgetData) => ({ @@ -69,37 +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"); - const onDrop = async (event: DragEvent) => { + 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 email = localStorage.getItem("email") || ""; - const organization = email?.split("@")[1]?.split(".")[0]; - if (!widgetSelect.startsWith("ui")) return; const group1 = scene.getObjectByName("itemsGroup"); if (!group1) return; - 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 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; @@ -110,27 +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 } }; + 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]); + }, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption, camera,]); + - const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || []; useEffect(() => { if (!rightClickSelected) return; @@ -139,9 +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(), @@ -154,18 +252,21 @@ export default function Dropped3dWidgets() { rotation: widgetToDuplicate.rotation || [0, 0, 0], }; const adding3dWidget = { - organization, + organization: organization, widget: newWidget, - zoneId: selectedZone.zoneId + zoneId: selectedZone.zoneId, }; - let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget) - console.log('response: ', response); + 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") { @@ -173,25 +274,22 @@ export default function Dropped3dWidgets() { try { const deleteWidget = { organization, - widgetId: rightClickSelected, + 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); - - - // if (response?.success) { - // Remove from state only if API call succeeds + // 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 + ) ); - // } else { - // console.error("Failed to delete widget:", response?.message); - // } } catch (error) { - console.error("Error deleting widget:", error); } finally { setRightClickSelected(null); setRightSelect(null); @@ -203,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) => { @@ -212,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]; } @@ -228,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(); @@ -241,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], ]); } } @@ -267,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); } @@ -278,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); } @@ -289,53 +402,79 @@ 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") { - console.log(`${rightSelect} Completed - Full Position:`, formatValues(selectedWidget.position)); - let lastPosition = formatValues(selectedWidget.position) as [number, number, number]; - - (async () => { - let response = await update3dWidget(selectedZone, organization, rightClickSelected, lastPosition); - console.log('response: ', response); - if (response) { - console.log("Widget position updated in API:", response); - } - })(); - } - else if (rightSelect.includes("Rotate")) { + 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]; - console.log(`${rightSelect} Completed - Full Rotation:`, formatValues(rotation)); - let lastPosition = formatValues(rotation) as [number, number, number]; - console.log('lastPosition: ', lastPosition); - (async () => { - let response = await update3dWidgetRotation(selectedZone, organization, rightClickSelected, lastPosition); - console.log('response: ', response); - if (response) { - console.log("Widget position updated in API:", response); - } - })(); + 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 @@ -357,69 +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/DroppedFloatingWidgets.tsx b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx index 465e8e4..6e7513e 100644 --- a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx +++ b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx @@ -65,6 +65,7 @@ const DroppedObjects: React.FC = () => { ); const [offset, setOffset] = useState<[number, number] | null>(null); const { selectedChartId, setSelectedChartId } = useWidgetStore(); + const [activeEdges, setActiveEdges] = useState<{ vertical: "top" | "bottom"; horizontal: "left" | "right"; @@ -84,7 +85,6 @@ const DroppedObjects: React.FC = () => { // }); const kebabRef = useRef(null); - // Clean up animation frame on unmount useEffect(() => { return () => { @@ -95,7 +95,10 @@ const DroppedObjects: React.FC = () => { }, []); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - if (kebabRef.current && !kebabRef.current.contains(event.target as Node)) { + if ( + kebabRef.current && + !kebabRef.current.contains(event.target as Node) + ) { setOpenKebabId(null); } }; @@ -113,7 +116,6 @@ const DroppedObjects: React.FC = () => { if (zoneEntries.length === 0) return null; const [zoneName, zone] = zoneEntries[0]; - function handleDuplicate(zoneName: string, index: number) { setOpenKebabId(null); duplicateObject(zoneName, index); // Call the duplicateObject method from the store @@ -124,15 +126,14 @@ const DroppedObjects: React.FC = () => { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; - let deleteFloatingWidget = { floatWidgetID: id, organization: organization, - zoneId: zone.zoneId - } + zoneId: zone.zoneId, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-float:delete", deleteFloatingWidget) + visualizationSocket.emit("v2:viz-float:delete", deleteFloatingWidget); } deleteObject(zoneName, id); @@ -142,13 +143,14 @@ const DroppedObjects: React.FC = () => { // if (res.message === "FloatingWidget deleted successfully") { // deleteObject(zoneName, id, index); // Call the deleteObject method from the store // } - } catch (error) { - - } + } catch (error) { } } const handlePointerDown = (event: React.PointerEvent, index: number) => { - if ((event.target as HTMLElement).closest(".kebab-options") || (event.target as HTMLElement).closest(".kebab")) { + if ( + (event.target as HTMLElement).closest(".kebab-options") || + (event.target as HTMLElement).closest(".kebab") + ) { return; // Prevent dragging when clicking on the kebab menu or its options } const obj = zone.objects[index]; @@ -449,7 +451,6 @@ const DroppedObjects: React.FC = () => { // position: boundedPosition, // }); - let updateFloatingWidget = { organization: organization, widget: { @@ -457,15 +458,14 @@ const DroppedObjects: React.FC = () => { position: boundedPosition, }, index: draggingIndex.index, - zoneId: zone.zoneId - } + zoneId: zone.zoneId, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-float:add", updateFloatingWidget) + visualizationSocket.emit("v2:viz-float:add", updateFloatingWidget); } // if (response.message === "Widget updated successfully") { - console.log('boundedPosition: ', boundedPosition); updateObjectPosition(zoneName, draggingIndex.index, boundedPosition); // } @@ -479,7 +479,6 @@ const DroppedObjects: React.FC = () => { // animationRef.current = null; // } } catch (error) { - } finally { // Clean up regardless of success or failure setDraggingIndex(null); @@ -500,101 +499,124 @@ const DroppedObjects: React.FC = () => { setOpenKebabId((prevId) => (prevId === id ? null : id)); }; + const containerHeight = getComputedStyle( + document.documentElement + ).getPropertyValue("--realTimeViz-container-height"); + const containerWidth = getComputedStyle( + document.documentElement + ).getPropertyValue("--realTimeViz-container-width"); + + const heightMultiplier = parseFloat(containerHeight) * 0.14; + + const widthMultiplier = parseFloat(containerWidth) * 0.13; + return (
- {zone.objects.map((obj, index) => ( -
{ - setSelectedChartId(obj); - handlePointerDown(event, index); - }} - > - {obj.className === "floating total-card" ? ( - <> - - - ) : obj.className === "warehouseThroughput floating" ? ( - <> - - - ) : obj.className === "fleetEfficiency floating" ? ( - <> - - - ) : null} + {zone.objects.map((obj, index) => { + const topPosition = + typeof obj.position.top === "number" + ? `calc(${obj.position.top}px + ${isPlaying && selectedZone.activeSides.includes("top") + ? `${heightMultiplier - 55}px` + : "0px" + })` + : "auto"; + + const leftPosition = + typeof obj.position.left === "number" + ? `calc(${obj.position.left}px + ${isPlaying && selectedZone.activeSides.includes("left") + ? `${widthMultiplier - 100}px` + : "0px" + })` + : "auto"; + + const rightPosition = + typeof obj.position.right === "number" + ? `calc(${obj.position.right}px + ${isPlaying && selectedZone.activeSides.includes("right") + ? `${widthMultiplier - 100}px` + : "0px" + })` + : "auto"; + + const bottomPosition = + typeof obj.position.bottom === "number" + ? `calc(${obj.position.bottom}px + ${isPlaying && selectedZone.activeSides.includes("bottom") + ? `${heightMultiplier - 55}px` + : "0px" + })` + : "auto"; + + return (
{ - event.stopPropagation(); - handleKebabClick(obj.id, event) + key={`${zoneName}-${index}`} + className={`${obj.className} ${selectedChartId?.id === obj.id && "activeChart" + }`} + ref={chartWidget} + style={{ + position: "absolute", + top: topPosition, + left: leftPosition, + right: rightPosition, + bottom: bottomPosition, + }} + onPointerDown={(event) => { + setSelectedChartId(obj); + handlePointerDown(event, index); }} > - -
- {openKebabId === obj.id && ( -
-
{ - event.stopPropagation(); - handleDuplicate(zoneName, index); // Call the duplicate handler - }}> -
- -
-
Duplicate
-
-
{ - event.stopPropagation(); - handleDelete(zoneName, obj.id); // Call the delete handler - }}> -
- -
-
Delete
-
-
- )} + {obj.className === "floating total-card" ? ( + + ) : obj.className === "warehouseThroughput floating" ? ( + + ) : obj.className === "fleetEfficiency floating" ? ( + + ) : null} -
- ))} +
{ + event.stopPropagation(); + handleKebabClick(obj.id, event); + }} + > + +
+ + {openKebabId === obj.id && ( +
+
{ + event.stopPropagation(); + handleDuplicate(zoneName, index); // Call the duplicate handler + }} + > +
+ +
+
Duplicate
+
+
{ + event.stopPropagation(); + handleDelete(zoneName, obj.id); // Call the delete handler + }} + > +
+ +
+
Delete
+
+
+ )} +
+ ); + })} {/* Render DistanceLines component during drag */} {isPlaying === false && @@ -630,5 +652,3 @@ const DroppedObjects: React.FC = () => { }; export default DroppedObjects; - - diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx index 8ec26a0..30136e5 100644 --- a/app/src/components/ui/componets/Panel.tsx +++ b/app/src/components/ui/componets/Panel.tsx @@ -21,7 +21,6 @@ interface PanelProps { zoneName: string; activeSides: Side[]; panelOrder: Side[]; - lockedPanels: Side[]; zoneId: string; zoneViewPortTarget: number[]; @@ -33,7 +32,6 @@ interface PanelProps { zoneName: string; activeSides: Side[]; panelOrder: Side[]; - lockedPanels: Side[]; zoneId: string; zoneViewPortTarget: number[]; @@ -41,8 +39,8 @@ interface PanelProps { widgets: Widget[]; }> >; - hiddenPanels: string[]; - setZonesData: React.Dispatch>; // Add this line + hiddenPanels: any; + setZonesData: React.Dispatch>; } const generateUniqueId = () => @@ -60,10 +58,40 @@ const Panel: React.FC = ({ [side in Side]?: { width: number; height: number }; }>({}); const [openKebabId, setOpenKebabId] = useState(null); - const { isPlaying } = usePlayButtonStore(); const { visualizationSocket } = useSocketStore(); + const [canvasDimensions, setCanvasDimensions] = useState({ + width: 0, + height: 0, + }); + // Track canvas dimensions + useEffect(() => { + const canvas = document.getElementById("real-time-vis-canvas"); + if (!canvas) return; + + const updateCanvasDimensions = () => { + const rect = canvas.getBoundingClientRect(); + setCanvasDimensions({ + width: rect.width, + height: rect.height, + }); + }; + + updateCanvasDimensions(); + const resizeObserver = new ResizeObserver(updateCanvasDimensions); + resizeObserver.observe(canvas); + + return () => resizeObserver.unobserve(canvas); + }, []); + + // Calculate panel size + const panelSize = Math.max( + Math.min(canvasDimensions.width * 0.25, canvasDimensions.height * 0.25), + 170 // Min 170px + ); + + // Define getPanelStyle const getPanelStyle = useMemo( () => (side: Side) => { const currentIndex = selectedZone.panelOrder.indexOf(side); @@ -72,16 +100,17 @@ const Panel: React.FC = ({ const rightActive = previousPanels.includes("right"); const topActive = previousPanels.includes("top"); const bottomActive = previousPanels.includes("bottom"); - const panelSize = isPlaying ? 300 : 210; switch (side) { case "top": case "bottom": return { + minWidth: "170px", width: `calc(100% - ${ (leftActive ? panelSize : 0) + (rightActive ? panelSize : 0) }px)`, - height: `${panelSize - 2}px`, + minHeight: "170px", + height: `${panelSize}px`, left: leftActive ? `${panelSize}px` : "0", right: rightActive ? `${panelSize}px` : "0", [side]: "0", @@ -89,7 +118,9 @@ const Panel: React.FC = ({ case "left": case "right": return { - width: `${panelSize - 2}px`, + minWidth: "170px", + width: `${panelSize}px`, + minHeight: "170px", height: `calc(100% - ${ (topActive ? panelSize : 0) + (bottomActive ? panelSize : 0) }px)`, @@ -101,83 +132,79 @@ const Panel: React.FC = ({ return {}; } }, - [selectedZone.panelOrder, isPlaying] + [selectedZone.panelOrder, panelSize] ); + // Handle drop event const handleDrop = (e: React.DragEvent, panel: Side) => { e.preventDefault(); const { draggedAsset } = useWidgetStore.getState(); - if (!draggedAsset) return; - if (isPanelLocked(panel)) return; + if ( + !draggedAsset || + isPanelLocked(panel) || + hiddenPanels[selectedZone.zoneId]?.includes(panel) + ) + return; const currentWidgetsCount = getCurrentWidgetCount(panel); const maxCapacity = calculatePanelCapacity(panel); - if (currentWidgetsCount >= maxCapacity) return; - addWidgetToPanel(draggedAsset, panel); + if (currentWidgetsCount < maxCapacity) { + addWidgetToPanel(draggedAsset, panel); + } }; + // Check if panel is locked const isPanelLocked = (panel: Side) => selectedZone.lockedPanels.includes(panel); + // Get current widget count in a panel const getCurrentWidgetCount = (panel: Side) => selectedZone.widgets.filter((w) => w.panel === panel).length; + // Calculate panel capacity const calculatePanelCapacity = (panel: Side) => { - const CHART_WIDTH = 170; - const CHART_HEIGHT = 170; - const FALLBACK_HORIZONTAL_CAPACITY = 5; - const FALLBACK_VERTICAL_CAPACITY = 3; + const CHART_WIDTH = panelSize; + const CHART_HEIGHT = panelSize; const dimensions = panelDimensions[panel]; if (!dimensions) { - return panel === "top" || panel === "bottom" - ? FALLBACK_HORIZONTAL_CAPACITY - : FALLBACK_VERTICAL_CAPACITY; + return panel === "top" || panel === "bottom" ? 5 : 3; // Fallback capacities } return panel === "top" || panel === "bottom" - ? Math.floor(dimensions.width / CHART_WIDTH) - : Math.floor(dimensions.height / CHART_HEIGHT); + ? Math.max(1, Math.floor(dimensions.width / CHART_WIDTH)) + : Math.max(1, Math.floor(dimensions.height / CHART_HEIGHT)); }; - // while dublicate check this and add + // Add widget to panel const addWidgetToPanel = async (asset: any, panel: Side) => { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; + const newWidget = { ...asset, id: generateUniqueId(), panel, }; + let addWidget = { organization: organization, zoneId: selectedZone.zoneId, - widget: newWidget - } + widget: newWidget, + }; + if (visualizationSocket) { - visualizationSocket.emit("v2:viz-widget:add", addWidget) + visualizationSocket.emit("v2:viz-widget:add", addWidget); } + setSelectedZone((prev) => ({ ...prev, widgets: [...prev.widgets, newWidget], })); - - try { - // let response = await addingWidgets(selectedZone.zoneId, organization, newWidget); - - // if (response.message === "Widget created successfully") { - // setSelectedZone((prev) => ({ - // ...prev, - // widgets: [...prev.widgets, newWidget], - // })); - // } - } catch (error) { - console.error("Error adding widget:", error); - } - }; + // Observe panel dimensions useEffect(() => { const observers: ResizeObserver[] = []; const currentPanelRefs = panelRefs.current; @@ -194,6 +221,7 @@ const Panel: React.FC = ({ })); } }); + observer.observe(element); observers.push(observer); } @@ -204,22 +232,15 @@ const Panel: React.FC = ({ }; }, [selectedZone.activeSides]); + // Handle widget reordering const handleReorder = (fromIndex: number, toIndex: number, panel: Side) => { - if (!selectedZone) return; // Ensure selectedZone is not null - setSelectedZone((prev) => { - if (!prev) return prev; // Ensure prev is not null - - // Filter widgets for the specified panel const widgetsInPanel = prev.widgets.filter((w) => w.panel === panel); - - // Reorder widgets within the same panel const reorderedWidgets = arrayMove(widgetsInPanel, fromIndex, toIndex); - // Merge the reordered widgets back into the full list while preserving the order const updatedWidgets = prev.widgets - .filter((widget) => widget.panel !== panel) // Keep widgets from other panels - .concat(reorderedWidgets); // Add the reordered widgets for the specified panel + .filter((widget) => widget.panel !== panel) + .concat(reorderedWidgets); return { ...prev, @@ -228,13 +249,41 @@ const Panel: React.FC = ({ }); }; + // Calculate capacities and dimensions + const topWidth = getPanelStyle("top").width; + const bottomWidth = getPanelStyle("bottom").height; + const leftHeight = getPanelStyle("left").height; + const rightHeight = getPanelStyle("right").height; + + const topCapacity = calculatePanelCapacity("top"); + const bottomCapacity = calculatePanelCapacity("bottom"); + const leftCapacity = calculatePanelCapacity("left"); + const rightCapacity = calculatePanelCapacity("right"); + return ( <> + + {selectedZone.activeSides.map((side) => (
handleDrop(e, side)} @@ -250,9 +299,11 @@ const Panel: React.FC = ({
diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 5a47964..aa67191 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -7,7 +7,7 @@ import DisplayZone from "./DisplayZone"; import Scene from "../../../modules/scene/scene"; import useModuleStore from "../../../store/useModuleStore"; -import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore"; +import { useDroppedObjectsStore, useFloatingWidget } from "../../../store/useDroppedObjectsStore"; import { useAsset3dWidget, useSocketStore, @@ -23,8 +23,12 @@ import RenderOverlay from "../../templates/Overlay"; import ConfirmationPopup from "../../layout/confirmationPopup/ConfirmationPopup"; import DroppedObjects from "./DroppedFloatingWidgets"; import EditWidgetOption from "../menu/EditWidgetOption"; -import { useEditWidgetOptionsStore, useRightClickSelected, useRightSelected } from "../../../store/useZone3DWidgetStore"; - +import { + useEditWidgetOptionsStore, + useRightClickSelected, + useRightSelected, +} from "../../../store/useZone3DWidgetStore"; +import Dropped3dWidgets from "./Dropped3dWidget"; type Side = "top" | "bottom" | "left" | "right"; @@ -49,8 +53,13 @@ type Widget = { data: any; }; +// Define the type for HiddenPanels, where keys are zone IDs and values are arrays of hidden sides +interface HiddenPanels { + [zoneId: string]: Side[]; +} + const RealTimeVisulization: React.FC = () => { - const [hiddenPanels, setHiddenPanels] = React.useState([]); + const [hiddenPanels, setHiddenPanels] = React.useState({}); const containerRef = useRef(null); const { isPlaying } = usePlayButtonStore(); const { activeModule } = useModuleStore(); @@ -58,27 +67,24 @@ const RealTimeVisulization: React.FC = () => { const [zonesData, setZonesData] = useState({}); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - - const { rightSelect, setRightSelect } = useRightSelected() - const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore() - const { rightClickSelected, setRightClickSelected } = useRightClickSelected() + const { rightSelect, setRightSelect } = useRightSelected(); + const { editWidgetOptions, setEditWidgetOptions } = + useEditWidgetOptionsStore(); + const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false); - const [floatingWidgets, setFloatingWidgets] = useState< - Record - >({}); + // const [floatingWidgets, setFloatingWidgets] = useState>({}); + const { floatingWidget, setFloatingWidget } = useFloatingWidget() const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); const { visualizationSocket } = useSocketStore(); - useEffect(() => { async function GetZoneData() { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); - // console.log('response: ', response); if (!Array.isArray(response)) { return; @@ -99,7 +105,7 @@ const RealTimeVisulization: React.FC = () => { {} ); setZonesData(formattedData); - } catch (error) { } + } catch (error) {} } GetZoneData(); @@ -127,6 +133,7 @@ const RealTimeVisulization: React.FC = () => { // useEffect(() => {}, [floatingWidgets]); const handleDrop = async (event: React.DragEvent) => { + event.preventDefault(); try { event.preventDefault(); const email = localStorage.getItem("email") || ""; @@ -172,6 +179,24 @@ const RealTimeVisulization: React.FC = () => { if (visualizationSocket) { visualizationSocket.emit("v2:viz-float:add", addFloatingWidget); } + useDroppedObjectsStore + .getState() + .addObject(selectedZone.zoneName, newObject); + + //I need to console here objects based on selectedZone.zoneId + // Console the objects after adding + const droppedObjectsStore = useDroppedObjectsStore.getState(); + const currentZone = droppedObjectsStore.zones[selectedZone.zoneName]; + + if (currentZone && currentZone.zoneId === selectedZone.zoneId) { + console.log( + `Objects for Zone ID: ${selectedZone.zoneId}`, + currentZone.objects + ); + setFloatingWidget(currentZone.objects) + } else { + console.warn("Zone not found or mismatched zoneId"); + } // let response = await addingFloatingWidgets( // selectedZone.zoneId, @@ -180,46 +205,14 @@ const RealTimeVisulization: React.FC = () => { // ); // Add the dropped object to the zone if the API call is successful // if (response.message === "FloatWidget created successfully") { - useDroppedObjectsStore - .getState() - .addObject(selectedZone.zoneName, newObject); // } + // Update floating widgets state - setFloatingWidgets((prevWidgets) => ({ - ...prevWidgets, - [selectedZone.zoneName]: { - ...prevWidgets[selectedZone.zoneName], - zoneName: selectedZone.zoneName, - zoneId: selectedZone.zoneId, - objects: [ - ...(prevWidgets[selectedZone.zoneName]?.objects || []), - newObject, - ], - }, - })); + } catch (error) { } + }; - 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]); - - // Add this useEffect hook to your component useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const editWidgetOptions = document.querySelector( @@ -241,84 +234,88 @@ const RealTimeVisulization: React.FC = () => { }, [setRightClickSelected]); return ( -
- {openConfirmationPopup && ( - - console.log("confirm")} - onCancel={() => setOpenConfirmationPopup(false)} - /> - - )} + <>
handleDrop(event)} - onDragOver={(event) => event.preventDefault()} > - -
- {activeModule === "visualization" && selectedZone.zoneName !== "" && ( - - )} - {activeModule === "visualization" && } - - {activeModule === "visualization" && - editWidgetOptions && - rightClickSelected && ( - - )} - - {activeModule === "visualization" && ( - <> - - - {!isPlaying && selectedZone?.zoneName !== "" && ( - +
+ {openConfirmationPopup && ( + + console.log("confirm")} + onCancel={() => setOpenConfirmationPopup(false)} + /> + )} +
handleDrop(event)} + onDragOver={(event) => event.preventDefault()} + > + +
+ {activeModule === "visualization" && selectedZone.zoneName !== "" && ( + + )} + {activeModule === "visualization" && } - - - )} -
+ {activeModule === "visualization" && + editWidgetOptions && + rightClickSelected && ( + + )} + + {activeModule === "visualization" && ( + <> + + + {!isPlaying && selectedZone?.zoneName !== "" && ( + + )} + + + + )} +
+
+ ); }; diff --git a/app/src/components/ui/componets/zoneAssets.tsx b/app/src/components/ui/componets/zoneAssets.tsx new file mode 100644 index 0000000..e2ec994 --- /dev/null +++ b/app/src/components/ui/componets/zoneAssets.tsx @@ -0,0 +1,83 @@ +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) { + 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); + } else { + console.log('zoneAssetId: ', zoneAssetId) + if (Array.isArray(zoneAssetId.position) && zoneAssetId.position.length >= 3) { + let selectedAssetPosition = [ + zoneAssetId.position[0], + 10, + zoneAssetId.position[2] + ]; + console.log('selectedAssetPosition: ', selectedAssetPosition); + let selectedAssetTarget = [ + zoneAssetId.position[0], + zoneAssetId.position[1], + zoneAssetId.position[2] + ]; + console.log('selectedAssetTarget: ', selectedAssetTarget); + const setCam = async () => { + await controls?.setLookAt(...selectedAssetPosition, ...selectedAssetTarget, true); + setTimeout(() => { + let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id); + if (AssetMesh) { + 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); + } + }, 500) + + }; + setCam(); + } + } + }, [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 3d2a828..b2f05b9 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -2,8 +2,9 @@ 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"; +import { getZone2dData } from "../../../services/realTimeVisulization/zoneData/getZoneData"; interface DropDownListProps { value?: string; // Value to display in the DropDownList @@ -38,89 +39,63 @@ 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[] }[] - >([]); + 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 } = 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]; + + const intersect = ((zi > point[1]) !== (zj > point[1])) && + (point[0] < (xj - xi) * (point[1] - zi) / (zj - zi + 0.000001) + xi); + + if (intersect) inside = !inside; + } + return inside; + }; useEffect(() => { - // console.log(zones); - // setZoneDataList([ - // { id: "2e996073-546c-470c-8323-55bd3700c6aa", name: "zone1" }, - // { id: "3f473bf0-197c-471c-a71f-943fc9ca2b47", name: "zone2" }, - // { id: "905e8fb6-9e18-469b-9474-e0478fb9601b", name: "zone3" }, - // { id: "9d9efcbe-8e96-47eb-bfad-128a9e4c532e", name: "zone4" }, - // { id: "884f3d29-eb5a-49a5-abe9-d11971c08e85", name: "zone5" }, - // { id: "70fa55cd-b5c9-4f80-a8c4-6319af3bfb4e", name: "zone6" }, - // ]) + const updatedZoneList: ZoneData[] = zones?.map((zone: Zone) => { + const polygon2D = zone.points.map((p: [number, number, number]) => [p[0], p[2]]) as [number, number][]; - const value = (zones || []).map( - (val: { zoneId: string; zoneName: string }) => ({ - id: val.zoneId, - name: val.zoneName, - }) - ); - 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", - }, - ], - }, - ]); - - }, [zones]); + 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 7629730..06bc129 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -12,10 +12,16 @@ import { LockIcon, RmoveIcon, } from "../../icons/ExportCommonIcons"; +import { useThree } from "@react-three/fiber"; +import { useFloorItems, useZoneAssetId } from "../../../store/store"; +import { zoneCameraUpdate } from "../../../services/realTimeVisulization/zoneData/zoneCameraUpdation"; +import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; 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 { @@ -31,13 +37,15 @@ interface ListProps { } const List: React.FC = ({ items = [], remove }) => { - console.log("items: ", items); const { activeModule, setActiveModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); + const { setSubModule } = useSubModuleStore(); const [expandedZones, setExpandedZones] = useState>( {} ); + const { floorItems, setFloorItems } = useFloorItems(); useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ @@ -62,7 +70,7 @@ const List: React.FC = ({ items = [], remove }) => { async function handleSelectZone(id: string) { try { if (selectedZone?.zoneId === id) { - console.log("Zone is already selected:", selectedZone.zoneName); + return; } @@ -84,17 +92,52 @@ const List: React.FC = ({ items = [], remove }) => { zoneViewPortPosition: response?.viewPortposition || [], }); - console.log("Zone selected:", response?.zoneName); + } catch (error) { - console.error("Error selecting zone:", error); + } } + function handleAssetClick(asset: Asset) { + setZoneAssetId(asset) + } + async function handleZoneNameChange(newName: string) { + //zone apiiiiii + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + let zonesdata = { + zoneId: selectedZone.zoneId, + zoneName: newName + }; + let response = await zoneCameraUpdate(zonesdata, organization); + if (response.message === "updated successfully") { + setSelectedZone((prev) => ({ ...prev, zoneName: newName })); + } + } + async function handleZoneAssetName(newName: string) { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + if (zoneAssetId?.id) { + let response = await setFloorItemApi(organization, zoneAssetId.id, newName) + console.log('response: ', response); + setFloorItems((prevFloorItems: any[]) => + prevFloorItems.map((floorItems) => + floorItems.modeluuid === zoneAssetId.id + ? { ...floorItems, modelname: response.modelname } + : floorItems + ) + ); + } + + console.log('newName: ', newName); + + } return ( <> - {items.length > 0 ? ( + {items?.length > 0 ? (
    - {items.map((item) => ( + {items?.map((item) => (
  • @@ -103,7 +146,7 @@ const List: React.FC = ({ items = [], remove }) => { className="value" onClick={() => handleSelectZone(item.id)} > - +
@@ -140,8 +183,8 @@ const List: React.FC = ({ items = [], remove }) => { className="list-container asset-item" >
-
- +
handleAssetClick(asset)} > +
diff --git a/app/src/components/ui/realTimeVis/floating/SimpleCard.tsx b/app/src/components/ui/realTimeVis/floating/SimpleCard.tsx index 0c36977..7fafb79 100644 --- a/app/src/components/ui/realTimeVis/floating/SimpleCard.tsx +++ b/app/src/components/ui/realTimeVis/floating/SimpleCard.tsx @@ -5,7 +5,7 @@ interface SimpleCardProps { icon: React.ComponentType>; // React component for SVG icon value: string; per: string; // Percentage change - position?: [number, number] + position?: [number, number]; } const SimpleCard: React.FC = ({ @@ -15,7 +15,6 @@ const SimpleCard: React.FC = ({ per, position = [0, 0], }) => { - const handleDragStart = (event: React.DragEvent) => { const rect = event.currentTarget.getBoundingClientRect(); // Get position const cardData = JSON.stringify({ @@ -23,7 +22,7 @@ const SimpleCard: React.FC = ({ value, per, icon: Icon, - + className: event.currentTarget.className, position: [rect.top, rect.left], // ✅ Store position }); diff --git a/app/src/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx index 24003ea..48f4306 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -1,163 +1,68 @@ -import PolygonGenerator from "./polygonGenerator"; -import { useThree } from "@react-three/fiber"; -import { useEffect, useMemo, useRef, useState } from "react"; -import * as THREE from "three"; -import * as Types from "../../../types/world/worldTypes"; +import { useEffect, useState } from "react"; +import { Line } from "@react-three/drei"; +import { useNavMesh, useSimulationStates } from "../../../store/store"; import PathNavigator from "./pathNavigator"; -import NavMeshDetails from "./navMeshDetails"; -import { - useSelectedActionSphere, - useSimulationPaths, -} from "../../../store/store"; -import * as CONSTANTS from "../../../types/world/worldConstants"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -const Agv = ({ - lines, -}: { - lines: Types.RefLines; -}) => { - const [pathPoints, setPathPoints] = useState< - { - modelUuid: string; - modelSpeed: number; - bufferTime: number; - points: { x: number; y: number; z: number }[]; - hitCount: number; - }[] - >([]); - const { simulationPaths } = useSimulationPaths(); - const { selectedActionSphere } = useSelectedActionSphere(); - useEffect(() => { - if (!Array.isArray(simulationPaths)) { - } else { - let agvModels = simulationPaths.filter( - (val: any) => val.modelName === "agv" - ); +type PathPoints = { + modelUuid: string; + modelSpeed: number; + bufferTime: number; + points: { x: number; y: number; z: number }[]; + hitCount: number; +}; - - let findMesh = agvModels.filter( - (val: any) => - val.modeluuid === selectedActionSphere?.path?.modeluuid && - val.type === "Vehicle" - ); +const Agv = () => { + const [pathPoints, setPathPoints] = useState([]); + const { simulationStates } = useSimulationStates(); + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); - const result = - findMesh.length > 0 && - findMesh[0].type === "Vehicle" && - typeof findMesh[0].points?.actions.start === "object" && - typeof findMesh[0].points?.actions.end === "object" && - "x" in findMesh[0].points.actions.start && - "y" in findMesh[0].points.actions.start && - "x" in findMesh[0].points.actions.end && - "y" in findMesh[0].points.actions.end - ? [ - { - modelUuid: findMesh[0].modeluuid, // Ensure it's a number - modelSpeed: findMesh[0].points.speed, - bufferTime: findMesh[0].points.actions.buffer, - hitCount: findMesh[0].points.actions.hitCount, - points: [ - { - x: findMesh[0].position[0], - y: findMesh[0].position[1], - z: findMesh[0].position[2], - }, - { - x: findMesh[0].points.actions.start.x, - y: 0, - z: findMesh[0].points.actions.start.y, - }, - { - x: findMesh[0].points.actions.end.x, - y: 0, - z: findMesh[0].points.actions.end.y, - }, - ], - }, - ] - : []; - if (result.length > 0) { - // setPathPoints((prev) => [...prev, ...result]); - setPathPoints((prev) => { - const existingIndex = prev.findIndex( - (item) => item.modelUuid === result[0].modelUuid - ); + useEffect(() => { + if (simulationStates.length > 0) { - if (existingIndex !== -1) { - const updatedPathPoints = [...prev]; - updatedPathPoints[existingIndex] = result[0]; - return updatedPathPoints; - } else { - return [...prev, ...result]; - } - }); - } - } - // setPathPoints((prev) => { - // const existingUUIDs = new Set(prev.map((item) => item.uuid)); - // const newItems = result.filter( - // (item) => !existingUUIDs.has(item.uuid) - // ); - // return [...prev, ...newItems]; - // }); - }, [simulationPaths, selectedActionSphere]); + const agvModels = simulationStates.filter((val) => val.modelName === "agv" && val.type === "Vehicle"); - let groupRef = useRef() as Types.RefGroup; - const [navMesh, setNavMesh] = useState(); + const newPathPoints = agvModels.filter((model: any) => model.points && model.points.actions && typeof model.points.actions.start === "object" && typeof model.points.actions.end === "object" && "x" in model.points.actions.start && "y" in model.points.actions.start && "x" in model.points.actions.end && "y" in model.points.actions.end) + .map((model: any) => ({ + modelUuid: model.modeluuid, + modelSpeed: model.points.speed, + bufferTime: model.points.actions.buffer, + hitCount: model.points.actions.hitCount, + points: [ + { x: model.position[0], y: model.position[1], z: model.position[2] }, + { x: model.points.actions.start.x, y: 0, z: model.points.actions.start.y }, + { x: model.points.actions.end.x, y: 0, z: model.points.actions.end.y }, + ], + })); - return ( - <> - - - {pathPoints.map((pair, i) => ( - <> - - {/* {pair.points.length > 2 && ( - <> - - - - - - - - - - )} */} - - ))} - - - - - - - - ); + setPathPoints(newPathPoints); + } + }, [simulationStates]); + + return ( + <> + {pathPoints.map((pair, i) => ( + + + + {pair.points.slice(1).map((point, idx) => ( + + + + + ))} + + ))} + + ); }; export default Agv; diff --git a/app/src/modules/builder/agv/navMeshCreator.tsx b/app/src/modules/builder/agv/navMeshCreator.tsx new file mode 100644 index 0000000..cdbca45 --- /dev/null +++ b/app/src/modules/builder/agv/navMeshCreator.tsx @@ -0,0 +1,31 @@ +import { useRef } from "react"; +import { useNavMesh } from "../../../store/store"; +import PolygonGenerator from "./polygonGenerator"; +import NavMeshDetails from "./navMeshDetails"; +import * as CONSTANTS from "../../../types/world/worldConstants"; +import * as Types from "../../../types/world/worldTypes"; + +type NavMeshCreatorProps = { + lines: Types.RefLines +}; + +function NavMeshCreator({ lines }: NavMeshCreatorProps) { + let groupRef = useRef() as Types.RefGroup; + const { setNavMesh } = useNavMesh(); + + return ( + <> + + + + + + + + + + + ) +} + +export default NavMeshCreator \ No newline at end of file diff --git a/app/src/modules/builder/agv/navMeshDetails.tsx b/app/src/modules/builder/agv/navMeshDetails.tsx index ae95942..697d89b 100644 --- a/app/src/modules/builder/agv/navMeshDetails.tsx +++ b/app/src/modules/builder/agv/navMeshDetails.tsx @@ -32,7 +32,7 @@ export default function NavMeshDetails({ const [positions, indices] = getPositionsAndIndices(meshes); - const cellSize = 0.35; + const cellSize = 0.2; const cellHeight = 0.7; const walkableRadius = 0.5; const { success, navMesh } = generateSoloNavMesh(positions, indices, { diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx index 65a299b..6369a87 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -3,179 +3,214 @@ import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { NavMeshQuery } from "@recast-navigation/core"; import { Line } from "@react-three/drei"; -import { useActiveTool } from "../../../store/store"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; interface PathNavigatorProps { - navMesh: any; - selectedPoints: any; - id: string; - speed: number; - bufferTime: number; - hitCount: number; + navMesh: any; + pathPoints: any; + id: string; + speed: number; + bufferTime: number; + hitCount: number; } export default function PathNavigator({ - navMesh, - selectedPoints, - id, - speed, - bufferTime, - hitCount, + navMesh, + pathPoints, + id, + speed, + bufferTime, + hitCount }: PathNavigatorProps) { - const [path, setPath] = useState<[number, number, number][]>([]); - const progressRef = useRef(0); - const distancesRef = useRef([]); - const totalDistanceRef = useRef(0); - const currentSegmentIndex = useRef(0); - const [stop, setStop] = useState(true); - const { scene } = useThree(); - const { isPlaying, setIsPlaying } = usePlayButtonStore(); - const [startPoint, setStartPoint] = useState(new THREE.Vector3()); - const isWaiting = useRef(false); // Flag to track waiting state - const delayTime = bufferTime; + const [path, setPath] = useState<[number, number, number][]>([]); + const [currentPhase, setCurrentPhase] = useState<'initial' | 'loop'>('initial'); + const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]); + const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]); + const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]); + const [initialPosition, setInitialPosition] = useState(null); + const [initialRotation, setInitialRotation] = useState(null); + const [targetPosition] = useState(new THREE.Vector3()); + const [smoothPosition] = useState(new THREE.Vector3()); + const [targetQuaternion] = useState(new THREE.Quaternion()); + const distancesRef = useRef([]); + const totalDistanceRef = useRef(0); + const progressRef = useRef(0); + const isWaiting = useRef(false); + const timeoutRef = useRef(null); + const pathTransitionProgress = useRef(0); - const movingForward = useRef(true); // Tracks whether the object is moving forward - // Compute distances and total distance when the path changes - useEffect(() => { - if (!scene || !id || path.length < 2) return; + const { scene } = useThree(); + const { isPlaying } = usePlayButtonStore(); - let totalDistance = 0; - const distances: number[] = []; - for (let i = 0; i < path.length - 1; i++) { - const start = new THREE.Vector3(...path[i]); - const end = new THREE.Vector3(...path[i + 1]); - const segmentDistance = start.distanceTo(end); - distances.push(segmentDistance); - totalDistance += segmentDistance; - } - distancesRef.current = distances; - totalDistanceRef.current = totalDistance; - progressRef.current = 0; - }, [path]); + useEffect(() => { + const object = scene.getObjectByProperty("uuid", id); + if (object) { + setInitialPosition(object.position.clone()); + setInitialRotation(object.rotation.clone()); + smoothPosition.copy(object.position.clone()); + targetPosition.copy(object.position.clone()); + targetQuaternion.setFromEuler(object.rotation.clone()); + } + }, [scene, id]); - // Compute the path using NavMeshQuery - useEffect(() => { - if (!navMesh || selectedPoints.length === 0) return; + const computePath = (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + return segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || []; + } catch { + return []; + } + }; - const allPoints = selectedPoints.flat(); - const computedPath: [number, number, number][] = []; + const resetState = () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } - for (let i = 0; i < allPoints.length - 1; i++) { - const start = allPoints[i]; - setStartPoint( - new THREE.Vector3(allPoints[0].x, allPoints[0].y, allPoints[0].z) - ); + setPath([]); + setCurrentPhase('initial'); + distancesRef.current = []; + totalDistanceRef.current = 0; + progressRef.current = 0; + isWaiting.current = false; + pathTransitionProgress.current = 0; - const end = allPoints[i + 1]; + const object = scene.getObjectByProperty("uuid", id); + if (object && initialPosition && initialRotation) { + object.position.copy(initialPosition); + object.rotation.copy(initialRotation); + smoothPosition.copy(initialPosition); + targetPosition.copy(initialPosition); + targetQuaternion.setFromEuler(initialRotation); + } + }; - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); - if (!segmentPath || segmentPath.length === 0) { - continue; - } + useEffect(() => { + if (!isPlaying) { + resetState(); + } - computedPath.push( - ...segmentPath.map(({ x, y, z }): [number, number, number] => [ - x, - y + 0.1, - z, - ]) - ); - } catch (error) {} - } + if (!navMesh || pathPoints.length < 2) return; - if (computedPath.length > 0) { - setPath(computedPath); - currentSegmentIndex.current = 0; - } - }, [selectedPoints, navMesh]); - useFrame((_, delta) => { - if (!scene || !id || path.length < 2) return; + const [pickup, drop] = pathPoints.slice(-2); + const object = scene.getObjectByProperty("uuid", id); + if (!object) return; - // Find the object in the scene by its UUID - const findObject = scene.getObjectByProperty("uuid", id); - if (!findObject) return; + const currentPosition = { x: object.position.x, y: object.position.y, z: object.position.z }; - if (isPlaying) { - const fast = speed; - progressRef.current += delta * fast; + const toPickupPath = computePath(currentPosition, pickup); + const pickupToDropPath = computePath(pickup, drop); + const dropToPickupPath = computePath(drop, pickup); - let coveredDistance = progressRef.current; - let accumulatedDistance = 0; - let index = 0; + if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) { + setPickupDropPath(pickupToDropPath); + setDropPickupPath(dropToPickupPath); + setToPickupPath(toPickupPath); + setPath(toPickupPath); + setCurrentPhase('initial'); + } + }, [navMesh, pathPoints, hitCount, isPlaying]); - // Determine the current segment of the path - while ( - index < distancesRef.current.length && - coveredDistance > accumulatedDistance + distancesRef.current[index] - ) { - accumulatedDistance += distancesRef.current[index]; - index++; - } + useEffect(() => { + if (path.length < 2) return; - if (index >= distancesRef.current.length) { - progressRef.current = totalDistanceRef.current; + let total = 0; + const segmentDistances = path.slice(0, -1).map((point, i) => { + const dist = new THREE.Vector3(...point).distanceTo(new THREE.Vector3(...path[i + 1])); + total += dist; + return dist; + }); - if (!isWaiting.current) { - isWaiting.current = true; // Set waiting flag + distancesRef.current = segmentDistances; + totalDistanceRef.current = total; + progressRef.current = 0; + isWaiting.current = false; + }, [path]); - if (movingForward.current) { - // Moving forward: reached the end, wait for `delay` - // console.log( - // "Reached end position. Waiting for delay:", - // delayTime, - // "seconds" - // ); - setTimeout(() => { - // After delay, reverse direction - movingForward.current = false; - progressRef.current = 0; // Reset progress - path.reverse(); // Reverse the path - distancesRef.current.reverse(); - isWaiting.current = false; // Reset waiting flag - }, delayTime * 1000); // Wait for `delay` seconds - } - } - return; - } + useFrame((_, delta) => { + if (!isPlaying || path.length < 2 || !scene || !id) return; - // Interpolate position within the current segment - const start = new THREE.Vector3(...path[index]); - const end = new THREE.Vector3(...path[index + 1]); - const segmentDistance = distancesRef.current[index]; + const object = scene.getObjectByProperty("uuid", id); + if (!object) return; - const t = Math.min( - (coveredDistance - accumulatedDistance) / segmentDistance, - 1 - ); // Clamp t to avoid overshooting - const position = start.clone().lerp(end, t); - findObject.position.copy(position); + const speedFactor = speed; + progressRef.current += delta * speedFactor; - // Rotate the object to face the direction of movement - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - const targetYRotation = Math.atan2(direction.x, direction.z); - findObject.rotation.y += (targetYRotation - findObject.rotation.y) * 0.1; - } else { - findObject.position.copy(startPoint); - } - }); + let covered = progressRef.current; + let accumulated = 0; + let index = 0; - return ( - <> - {path.length > 0 && ( - <> - - {selectedPoints.map((val: any, i: any) => ( - - - - - ))} - - )} - - ); -} + while ( + index < distancesRef.current.length && + covered > accumulated + distancesRef.current[index] + ) { + accumulated += distancesRef.current[index]; + index++; + } + + if (index >= distancesRef.current.length) { + progressRef.current = totalDistanceRef.current; + + if (!isWaiting.current) { + isWaiting.current = true; + + timeoutRef.current = setTimeout(() => { + if (currentPhase === 'initial') { + setPath(pickupDropPath); + setCurrentPhase('loop'); + } else { + setPath(prevPath => + prevPath === pickupDropPath ? dropPickupPath : pickupDropPath + ); + } + + progressRef.current = 0; + isWaiting.current = false; + }, bufferTime * 1000); + } + return; + } + + const start = new THREE.Vector3(...path[index]); + const end = new THREE.Vector3(...path[index + 1]); + const dist = distancesRef.current[index]; + const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1); + + targetPosition.copy(start).lerp(end, t); + + smoothPosition.lerp(targetPosition, 0.1); + object.position.copy(smoothPosition); + + const direction = new THREE.Vector3().subVectors(end, start).normalize(); + const targetRotationY = Math.atan2(direction.x, direction.z); + targetQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), targetRotationY); + object.quaternion.slerp(targetQuaternion, 0.1); + }); + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return ( + + {toPickupPath.length > 0 && ( + + )} + + {pickupDropPath.length > 0 && ( + + )} + + {dropPickupPath.length > 0 && ( + + )} + + ); +} \ No newline at end of file diff --git a/app/src/modules/builder/csg/csg.tsx b/app/src/modules/builder/csg/csg.tsx index f84fad0..7e49598 100644 --- a/app/src/modules/builder/csg/csg.tsx +++ b/app/src/modules/builder/csg/csg.tsx @@ -1,6 +1,6 @@ import * as THREE from "three"; import { Geometry, Base, Subtraction } from "@react-three/csg"; -import { useDeleteModels } from "../../../store/store"; +import { useDeleteTool } from "../../../store/store"; import { useRef } from "react"; export interface CsgProps { @@ -11,19 +11,19 @@ export interface CsgProps { } export const Csg: React.FC = (props) => { - const { deleteModels } = useDeleteModels(); + const { deleteTool } = useDeleteTool(); const modelRef = useRef(); const originalMaterials = useRef>(new Map()); const handleHover = (hovered: boolean, object: THREE.Mesh | null) => { - if (modelRef.current && deleteModels) { + if (modelRef.current && deleteTool) { modelRef.current.traverse((child) => { if (child instanceof THREE.Mesh) { if (!originalMaterials.current.has(child)) { originalMaterials.current.set(child, child.material); } child.material = child.material.clone(); - child.material.color.set(hovered && deleteModels ? 0xff0000 : (originalMaterials.current.get(child) as any).color); + child.material.color.set(hovered && deleteTool ? 0xff0000 : (originalMaterials.current.get(child) as any).color); } }); } diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index 91c1f12..d109eb8 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -24,7 +24,7 @@ async function addAssetModel( socket: Socket, selectedItem: any, setSelectedItem: any, - setSimulationPaths: any, + setSimulationStates: any, plane: Types.RefMesh, ): Promise { @@ -65,7 +65,7 @@ async function addAssetModel( const cachedModel = THREE.Cache.get(selectedItem.id); if (cachedModel) { // console.log(`[Cache] Fetching ${selectedItem.name}`); - handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); + handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket); return; } else { const cachedModelBlob = await retrieveGLTF(selectedItem.id); @@ -78,7 +78,7 @@ async function addAssetModel( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(selectedItem.id, gltf); - handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); + handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket); }, () => { TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup); @@ -86,11 +86,11 @@ async function addAssetModel( } else { // console.log(`Added ${selectedItem.name} from Backend`); - loader.load(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`, async (gltf) => { - const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob()); + loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => { + const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob()); await storeGLTF(selectedItem.id, modelBlob); THREE.Cache.add(selectedItem.id, gltf); - await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); + await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket); }, () => { TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup); @@ -113,7 +113,7 @@ async function handleModelLoad( tempLoader: Types.RefMesh, isTempLoader: Types.RefBoolean, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: any, + setSimulationStates: any, socket: Socket ) { const model = gltf.scene.clone(); @@ -171,7 +171,7 @@ async function handleModelLoad( }], triggers: [], connections: { - source: { pathUUID: model.uuid, pointUUID: pointUUIDs[index] }, + source: { modelUUID: model.uuid, pointUUID: pointUUIDs[index] }, targets: [] } })), @@ -219,7 +219,7 @@ async function handleModelLoad( eventData.position = newFloorItem.position; eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), eventData as Types.ConveyorEventsSchema ]); @@ -235,7 +235,8 @@ async function handleModelLoad( points: { uuid: pointUUID, position: res.points.position as [number, number, number], - actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, + rotation: res.points.rotation as [number, number, number], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: '', start: {}, hitCount: 1, end: {}, buffer: 0 }, connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] }, speed: 2, } @@ -281,13 +282,178 @@ async function handleModelLoad( return updatedItems; }); - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), eventData as Types.VehicleEventsSchema ]); socket.emit("v2:model-asset:add", data); + } else if (res.type === "StaticMachine") { + + const pointUUID = THREE.MathUtils.generateUUID(); + + const backendEventData: Extract = { + type: "StaticMachine", + points: { + uuid: pointUUID, + position: res.points.position as [number, number, number], + rotation: res.points.rotation as [number, number, number], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', buffer: 0, material: 'Inherit' }, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, + connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] }, + } + } + + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id + }; + + const eventData: any = backendEventData; + eventData.modeluuid = newFloorItem.modeluuid; + eventData.modelName = newFloorItem.modelname; + eventData.position = newFloorItem.position; + eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z]; + + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + eventData as Types.StaticMachineEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else if (res.type === "ArmBot") { + + const pointUUID = THREE.MathUtils.generateUUID(); + + const backendEventData: Extract = { + type: "ArmBot", + points: { + uuid: pointUUID, + position: res.points.position as [number, number, number], + rotation: res.points.rotation as [number, number, number], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', speed: 1, processes: [] }, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, + connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] }, + } + } + + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id + }; + + const eventData: any = backendEventData; + eventData.modeluuid = newFloorItem.modeluuid; + eventData.modelName = newFloorItem.modelname; + eventData.position = newFloorItem.position; + eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z]; + + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + eventData as Types.ArmBotEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else { + + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true, + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id + }; + + + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); + + socket.emit("v2:model-asset:add", data); } gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" }); diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts index 240e7a1..5ff75a3 100644 --- a/app/src/modules/builder/geomentries/assets/assetManager.ts +++ b/app/src/modules/builder/geomentries/assets/assetManager.ts @@ -53,7 +53,7 @@ export default async function assetManager( if (!activePromises.get(taskId)) return; // Stop processing if task is canceled await new Promise(async (resolve) => { - const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`; + const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`; // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); diff --git a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts index a71aaea..028863c 100644 --- a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts +++ b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts @@ -10,7 +10,7 @@ async function DeleteFloorItems( itemsGroup: Types.RefGroup, hoveredDeletableFloorItem: Types.RefMesh, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: any, + setSimulationStates: any, socket: Socket ): Promise { @@ -76,7 +76,7 @@ async function DeleteFloorItems( } setFloorItems(updatedItems); - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== removedItem.modeluuid); return updatedEvents; }); diff --git a/app/src/modules/builder/geomentries/floors/addFloorToScene.ts b/app/src/modules/builder/geomentries/floors/addFloorToScene.ts index e2f0baa..c951ba0 100644 --- a/app/src/modules/builder/geomentries/floors/addFloorToScene.ts +++ b/app/src/modules/builder/geomentries/floors/addFloorToScene.ts @@ -53,7 +53,7 @@ export default function addFloorToScene( const mesh = new THREE.Mesh(geometry, material); mesh.receiveShadow = true; - mesh.position.y = layer; + mesh.position.y = (layer) * CONSTANTS.wallConfig.height; mesh.rotateX(Math.PI / 2); mesh.name = `Floor_Layer_${layer}`; diff --git a/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts b/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts index 8f33b57..9400be2 100644 --- a/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts +++ b/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts @@ -171,7 +171,7 @@ function loadOnlyFloors( mesh.castShadow = true; mesh.receiveShadow = true; - mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height + 0.03; + mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height; mesh.rotateX(Math.PI / 2); mesh.name = `Only_Floor_Line_${floor[0][0][2]}`; diff --git a/app/src/modules/builder/geomentries/lines/distanceText.tsx b/app/src/modules/builder/geomentries/lines/distanceText/distanceText.tsx similarity index 89% rename from app/src/modules/builder/geomentries/lines/distanceText.tsx rename to app/src/modules/builder/geomentries/lines/distanceText/distanceText.tsx index 98e20a6..39162ad 100644 --- a/app/src/modules/builder/geomentries/lines/distanceText.tsx +++ b/app/src/modules/builder/geomentries/lines/distanceText/distanceText.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; -import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi"; +import { getLines } from "../../../../../services/factoryBuilder/lines/getLinesApi"; import * as THREE from "three"; import { useActiveLayer, useDeletedLines, useNewLines, useToggleView, -} from "../../../../store/store"; -import objectLinesToArray from "./lineConvertions/objectLinesToArray"; +} from "../../../../../store/store"; +import objectLinesToArray from "../lineConvertions/objectLinesToArray"; import { Html } from "@react-three/drei"; -import * as Types from "../../../../types/world/worldTypes"; +import * as Types from "../../../../../types/world/worldTypes"; const DistanceText = () => { const [lines, setLines] = useState< @@ -122,7 +122,7 @@ const DistanceText = () => { wrapperClass="distance-text-wrapper" className="distance-text" // other - zIndexRange={[100, 0]} + zIndexRange={[1, 0]} prepend sprite > diff --git a/app/src/modules/builder/geomentries/lines/distanceText/referenceDistanceText.tsx b/app/src/modules/builder/geomentries/lines/distanceText/referenceDistanceText.tsx new file mode 100644 index 0000000..039d30f --- /dev/null +++ b/app/src/modules/builder/geomentries/lines/distanceText/referenceDistanceText.tsx @@ -0,0 +1,71 @@ +import * as THREE from "three"; +import { Html } from "@react-three/drei"; +import { useState, useEffect } from "react"; +import { useActiveLayer } from "../../../../../store/store"; + +const ReferenceDistanceText = ({ line }: { line: any }) => { + interface TextState { + distance: string; + position: THREE.Vector3; + userData: any; + layer: any; + } + + const [text, setTexts] = useState(null); + const { activeLayer } = useActiveLayer(); + + useEffect(() => { + if (line) { + if (line.parent === null) { + setTexts(null); + return; + } + const distance = line.userData.linePoints.cursorPosition.distanceTo( + line.userData.linePoints.startPoint + ); + const midpoint = new THREE.Vector3() + .addVectors( + line.userData.linePoints.cursorPosition, + line.userData.linePoints.startPoint + ) + .divideScalar(2); + const newTexts = { + distance: distance.toFixed(1), + position: midpoint, + userData: line, + layer: activeLayer, + }; + setTexts(newTexts); + } + }); + + return ( + + + {text !== null && ( + +
+ {text.distance} m +
+ + )} +
+
+ ); +}; + +export default ReferenceDistanceText; diff --git a/app/src/modules/builder/geomentries/lines/referenceDistanceText.tsx b/app/src/modules/builder/geomentries/lines/referenceDistanceText.tsx deleted file mode 100644 index cc1ca30..0000000 --- a/app/src/modules/builder/geomentries/lines/referenceDistanceText.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as THREE from 'three'; -import { Html } from '@react-three/drei'; -import { useState, useEffect } from 'react'; -import { useActiveLayer } from '../../../../store/store'; - -const ReferenceDistanceText = ({ line }: { line: any }) => { - interface TextState { - distance: string; - position: THREE.Vector3; - userData: any; - layer: any; - } - - const [text, setTexts] = useState(null); - const { activeLayer } = useActiveLayer(); - - useEffect(() => { - if (line) { - if (line.parent === null) { - setTexts(null); - return; - } - const distance = line.userData.linePoints.cursorPosition.distanceTo(line.userData.linePoints.startPoint); - const midpoint = new THREE.Vector3().addVectors(line.userData.linePoints.cursorPosition, line.userData.linePoints.startPoint).divideScalar(2); - const newTexts = { - distance: distance.toFixed(1), - position: midpoint, - userData: line, - layer: activeLayer - }; - setTexts(newTexts); - } - }); - - return ( - - - {text !== null && - < Html transform sprite key={text.distance} userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }}> -
{text.distance} m
- - } -
-
- ); -}; - -export default ReferenceDistanceText; \ No newline at end of file diff --git a/app/src/modules/builder/groups/floorGroup.tsx b/app/src/modules/builder/groups/floorGroup.tsx index da3536b..e589380 100644 --- a/app/src/modules/builder/groups/floorGroup.tsx +++ b/app/src/modules/builder/groups/floorGroup.tsx @@ -1,5 +1,5 @@ import { useFrame, useThree } from "@react-three/fiber"; -import { useAddAction, useDeleteModels, useRoofVisibility, useToggleView, useWallVisibility, useUpdateScene } from "../../../store/store"; +import { useAddAction, useDeleteTool, useRoofVisibility, useToggleView, useWallVisibility, useUpdateScene } from "../../../store/store"; import hideRoof from "../geomentries/roofs/hideRoof"; import hideWalls from "../geomentries/walls/hideWalls"; import addAndUpdateReferencePillar from "../geomentries/pillars/addAndUpdateReferencePillar"; @@ -16,7 +16,7 @@ const FloorGroup = ({ floorGroup, lines, referencePole, hoveredDeletablePillar } const { toggleView, setToggleView } = useToggleView(); const { scene, camera, pointer, raycaster, gl } = useThree(); const { addAction, setAddAction } = useAddAction(); - const { deleteModels, setDeleteModels } = useDeleteModels(); + const { deleteTool, setDeleteTool } = useDeleteTool(); const { updateScene, setUpdateScene } = useUpdateScene(); useEffect(() => { @@ -56,7 +56,7 @@ const FloorGroup = ({ floorGroup, lines, referencePole, hoveredDeletablePillar } if (addAction === "pillar") { addPillar(referencePole, floorGroup); } - if (deleteModels) { + if (deleteTool) { DeletePillar(hoveredDeletablePillar, floorGroup); } } @@ -78,7 +78,7 @@ const FloorGroup = ({ floorGroup, lines, referencePole, hoveredDeletablePillar } canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mousemove", onMouseMove); }; - }, [deleteModels, addAction]) + }, [deleteTool, addAction]) useFrame(() => { hideRoof(roofVisibility, floorGroup, camera); @@ -87,7 +87,7 @@ const FloorGroup = ({ floorGroup, lines, referencePole, hoveredDeletablePillar } if (addAction === "pillar") { addAndUpdateReferencePillar(raycaster, floorGroup, referencePole); } - if (deleteModels) { + if (deleteTool) { DeletableHoveredPillar(state, floorGroup, hoveredDeletablePillar); } }) diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index cef7d74..bc0791e 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -1,26 +1,11 @@ import { useFrame, useThree } from "@react-three/fiber"; -import { - useActiveTool, - useAsset3dWidget, - useCamMode, - useDeletableFloorItem, - useDeleteModels, - useFloorItems, - useLoadingProgress, - useRenderDistance, - useselectedFloorItem, - useSelectedItem, - useSimulationPaths, - useSocketStore, - useToggleView, - useTransformMode, -} from "../../../store/store"; +import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteTool, useFloorItems, useLoadingProgress, useRenderDistance, useSelectedFloorItem, useSelectedItem, useSimulationStates, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store"; import assetVisibility from "../geomentries/assets/assetVisibility"; import { useEffect } from "react"; import * as THREE from "three"; import * as Types from "../../../types/world/worldTypes"; import assetManager, { - cancelOngoingTasks, + cancelOngoingTasks, } from "../geomentries/assets/assetManager"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; @@ -31,413 +16,315 @@ import addAssetModel from "../geomentries/assets/addAssetModel"; import { getFloorAssets } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi"; import useModuleStore from "../../../store/useModuleStore"; // import { retrieveGLTF } from "../../../utils/indexDB/idbUtils"; -const assetManagerWorker = new Worker( - new URL( - "../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", - import.meta.url - ) -); -const gltfLoaderWorker = new Worker( - new URL( - "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", - import.meta.url - ) -); +const assetManagerWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", import.meta.url)); +const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url)); -const FloorItemsGroup = ({ - itemsGroup, - hoveredDeletableFloorItem, - AttachedObject, - floorGroup, - tempLoader, - isTempLoader, - plane, -}: any) => { - const state: Types.ThreeState = useThree(); - const { raycaster, controls }: any = state; - const { renderDistance } = useRenderDistance(); - const { toggleView } = useToggleView(); - const { floorItems, setFloorItems } = useFloorItems(); - const { camMode } = useCamMode(); - const { deleteModels } = useDeleteModels(); - const { setDeletableFloorItem } = useDeletableFloorItem(); - const { transformMode } = useTransformMode(); - const { setselectedFloorItem } = useselectedFloorItem(); - const { activeTool } = useActiveTool(); - const { selectedItem, setSelectedItem } = useSelectedItem(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); - const { setLoadingProgress } = useLoadingProgress(); - const { activeModule } = useModuleStore(); - const { socket } = useSocketStore(); - const loader = new GLTFLoader(); - const dracoLoader = new DRACOLoader(); +const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane, }: any) => { + const state: Types.ThreeState = useThree(); + const { raycaster, controls }: any = state; + const { renderDistance } = useRenderDistance(); + const { toggleView } = useToggleView(); + const { floorItems, setFloorItems } = useFloorItems(); + const { camMode } = useCamMode(); + const { deleteTool } = useDeleteTool(); + const { setDeletableFloorItem } = useDeletableFloorItem(); + const { transformMode } = useTransformMode(); + const { setSelectedFloorItem } = useSelectedFloorItem(); + const { activeTool } = useActiveTool(); + const { selectedItem, setSelectedItem } = useSelectedItem(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { setLoadingProgress } = useLoadingProgress(); + const { activeModule } = useModuleStore(); + const { socket } = useSocketStore(); + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); - dracoLoader.setDecoderPath( - "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/" - ); - loader.setDRACOLoader(dracoLoader); + dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"); + loader.setDRACOLoader(dracoLoader); - useEffect(() => { - const email = localStorage.getItem("email"); - const organization = email!.split("@")[1].split(".")[0]; + useEffect(() => { + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; - let totalAssets = 0; - let loadedAssets = 0; + let totalAssets = 0; + let loadedAssets = 0; - const updateLoadingProgress = (progress: number) => { - if (progress < 100) { - setLoadingProgress(progress); - } else if (progress === 100) { - setTimeout(() => { - setLoadingProgress(100); - setTimeout(() => { - setLoadingProgress(0); - }, 1500); - }, 1000); - } - }; + const updateLoadingProgress = (progress: number) => { + if (progress < 100) { + setLoadingProgress(progress); + } else if (progress === 100) { + setTimeout(() => { + setLoadingProgress(100); + setTimeout(() => { + setLoadingProgress(0); + }, 1500); + }, 1000); + } + }; - getFloorAssets(organization).then((data) => { - if (data.length > 0) { - const uniqueItems = (data as Types.FloorItems).filter( - (item, index, self) => - index === self.findIndex((t) => t.modelfileID === item.modelfileID) - ); - totalAssets = uniqueItems.length; - if (totalAssets === 0) { - updateLoadingProgress(100); - return; - } - gltfLoaderWorker.postMessage({ floorItems: data }); - } else { - gltfLoaderWorker.postMessage({ floorItems: [] }); - loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); - updateLoadingProgress(100); - } - }); - - gltfLoaderWorker.onmessage = async (event) => { - if (event.data.message === "gltfLoaded" && event.data.modelBlob) { - const blobUrl = URL.createObjectURL(event.data.modelBlob); - - loader.load(blobUrl, (gltf) => { - URL.revokeObjectURL(blobUrl); - THREE.Cache.remove(blobUrl); - THREE.Cache.add(event.data.modelID, gltf); - - loadedAssets++; - const progress = Math.round((loadedAssets / totalAssets) * 100); - updateLoadingProgress(progress); - - if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); - updateLoadingProgress(100); - } + getFloorAssets(organization).then((data) => { + if (data.length > 0) { + const uniqueItems = (data as Types.FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID)); + totalAssets = uniqueItems.length; + if (totalAssets === 0) { + updateLoadingProgress(100); + return; + } + gltfLoaderWorker.postMessage({ floorItems: data }); + } else { + gltfLoaderWorker.postMessage({ floorItems: [] }); + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); + updateLoadingProgress(100); + } }); - } - }; - }, []); - useEffect(() => { - assetManagerWorker.onmessage = async (event) => { - cancelOngoingTasks(); // Cancel the ongoing process - await assetManager(event.data, itemsGroup, loader); - }; - }, [assetManagerWorker]); + gltfLoaderWorker.onmessage = async (event) => { + if (event.data.message === "gltfLoaded" && event.data.modelBlob) { + const blobUrl = URL.createObjectURL(event.data.modelBlob); - useEffect(() => { - if (toggleView) return; + loader.load(blobUrl, (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(event.data.modelID, gltf); - const uuids: string[] = []; - itemsGroup.current?.children.forEach((child: any) => { - uuids.push(child.uuid); - }); - const cameraPosition = state.camera.position; + loadedAssets++; + const progress = Math.round((loadedAssets / totalAssets) * 100); + updateLoadingProgress(progress); - assetManagerWorker.postMessage({ - floorItems, - cameraPosition, - uuids, - renderDistance, - }); - }, [camMode, renderDistance]); + if (loadedAssets === totalAssets) { + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); + updateLoadingProgress(100); + } + }); + } + }; + }, []); - useEffect(() => { - const controls: any = state.controls; - const camera: any = state.camera; + useEffect(() => { + assetManagerWorker.onmessage = async (event) => { + cancelOngoingTasks(); // Cancel the ongoing process + await assetManager(event.data, itemsGroup, loader); + }; + }, [assetManagerWorker]); - if (controls) { - let intervalId: NodeJS.Timeout | null = null; - - const handleChange = () => { + useEffect(() => { if (toggleView) return; const uuids: string[] = []; - itemsGroup.current?.children.forEach((child: any) => { - uuids.push(child.uuid); - }); - const cameraPosition = camera.position; + itemsGroup.current?.children.forEach((child: any) => { uuids.push(child.uuid); }); + const cameraPosition = state.camera.position; - assetManagerWorker.postMessage({ - floorItems, - cameraPosition, - uuids, - renderDistance, - }); - }; + assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance, }); + }, [camMode, renderDistance]); - const startInterval = () => { - if (!intervalId) { - intervalId = setInterval(handleChange, 50); + useEffect(() => { + const controls: any = state.controls; + const camera: any = state.camera; + + if (controls) { + let intervalId: NodeJS.Timeout | null = null; + + const handleChange = () => { + if (toggleView) return; + + const uuids: string[] = []; + itemsGroup.current?.children.forEach((child: any) => { uuids.push(child.uuid); }); + const cameraPosition = camera.position; + + assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance, }); + }; + + const startInterval = () => { + if (!intervalId) { + intervalId = setInterval(handleChange, 50); + } + }; + + const stopInterval = () => { + handleChange(); + if (intervalId) { + clearInterval(intervalId); + intervalId = null; + } + }; + + controls.addEventListener("rest", handleChange); + controls.addEventListener("rest", stopInterval); + controls.addEventListener("control", startInterval); + controls.addEventListener("controlend", stopInterval); + + return () => { + controls.removeEventListener("rest", handleChange); + controls.removeEventListener("rest", stopInterval); + controls.removeEventListener("control", startInterval); + controls.removeEventListener("controlend", stopInterval); + if (intervalId) { + clearInterval(intervalId); + } + }; } - }; + }, [state.controls, floorItems, toggleView, renderDistance]); - const stopInterval = () => { - handleChange(); - if (intervalId) { - clearInterval(intervalId); - intervalId = null; - } - }; + useEffect(() => { + const canvasElement = state.gl.domElement; + let drag = false; + let isLeftMouseDown = false; - controls.addEventListener("rest", handleChange); - controls.addEventListener("rest", stopInterval); - controls.addEventListener("control", startInterval); - controls.addEventListener("controlend", stopInterval); - - return () => { - controls.removeEventListener("rest", handleChange); - controls.removeEventListener("rest", stopInterval); - controls.removeEventListener("control", startInterval); - controls.removeEventListener("controlend", stopInterval); - if (intervalId) { - clearInterval(intervalId); - } - }; - } - }, [state.controls, floorItems, toggleView, renderDistance]); - - useEffect(() => { - const canvasElement = state.gl.domElement; - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) { - drag = true; - } - }; - - const onMouseUp = async (evt: any) => { - if (controls) { - (controls as any).enabled = true; - } - if (evt.button === 0) { - isLeftMouseDown = false; - if (drag) return; - - if (deleteModels) { - DeleteFloorItems( - itemsGroup, - hoveredDeletableFloorItem, - setFloorItems, - setSimulationPaths, - socket - ); - } - const Mode = transformMode; - - if (Mode !== null || activeTool === "cursor") { - if (!itemsGroup.current) return; - let intersects = raycaster.intersectObjects( - itemsGroup.current.children, - true - ); - if ( - intersects.length > 0 && - intersects[0]?.object?.parent?.parent?.position && - intersects[0]?.object?.parent?.parent?.scale && - intersects[0]?.object?.parent?.parent?.rotation - ) { - // let currentObject = intersects[0].object; - // while (currentObject) { - // if (currentObject.name === "Scene") { - // break; - // } - // currentObject = currentObject.parent as THREE.Object3D; - // } - // if (currentObject) { - // AttachedObject.current = currentObject as any; - // setselectedFloorItem(AttachedObject.current!); - // } - } else { - const target = controls.getTarget(new THREE.Vector3()); - await controls.setTarget(target.x, 0, target.z, true); - setselectedFloorItem(null); - } - } - } - }; - - const onDblClick = async (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = false; - if (drag) return; - - const Mode = transformMode; - - if (Mode !== null || activeTool === "cursor") { - if (!itemsGroup.current) return; - let intersects = raycaster.intersectObjects( - itemsGroup.current.children, - true - ); - if ( - intersects.length > 0 && - intersects[0]?.object?.parent?.parent?.position && - intersects[0]?.object?.parent?.parent?.scale && - intersects[0]?.object?.parent?.parent?.rotation - ) { - let currentObject = intersects[0].object; - - while (currentObject) { - if (currentObject.name === "Scene") { - break; - } - currentObject = currentObject.parent as THREE.Object3D; + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown = true; + drag = false; } - if (currentObject) { - AttachedObject.current = currentObject as any; - // controls.fitToSphere(AttachedObject.current!, true); + }; - const bbox = new THREE.Box3().setFromObject( - AttachedObject.current - ); - const size = bbox.getSize(new THREE.Vector3()); - const center = bbox.getCenter(new THREE.Vector3()); - - const front = new THREE.Vector3(0, 0, 1); - AttachedObject.current.localToWorld(front); - front.sub(AttachedObject.current.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(AttachedObject.current!, true, { - cover: true, - paddingTop: 5, - paddingLeft: 5, - paddingBottom: 5, - paddingRight: 5, - }); - - setselectedFloorItem(AttachedObject.current!); + const onMouseMove = () => { + if (isLeftMouseDown) { + drag = true; + } + }; + + const onMouseUp = async (evt: any) => { + if (controls) { + (controls as any).enabled = true; + } + if (evt.button === 0) { + isLeftMouseDown = false; + if (drag) return; + + if (deleteTool) { + DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, setSimulationStates, socket); + + // Remove EventData if there are any in the asset. + } + const Mode = transformMode; + + if (Mode !== null || activeTool === "cursor") { + if (!itemsGroup.current) return; + let intersects = raycaster.intersectObjects( + itemsGroup.current.children, + true + ); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + // let currentObject = intersects[0].object; + // while (currentObject) { + // if (currentObject.name === "Scene") { + // break; + // } + // currentObject = currentObject.parent as THREE.Object3D; + // } + // if (currentObject) { + // AttachedObject.current = currentObject as any; + // setSelectedFloorItem(AttachedObject.current!); + // } + } else { + const target = controls.getTarget(new THREE.Vector3()); + await controls.setTarget(target.x, 0, target.z, true); + setSelectedFloorItem(null); + } + } + } + }; + + const onDblClick = async (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown = false; + if (drag) return; + + const Mode = transformMode; + + if (Mode !== null || activeTool === "cursor") { + if (!itemsGroup.current) return; + let intersects = raycaster.intersectObjects(itemsGroup.current.children, true); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + let currentObject = intersects[0].object; + + while (currentObject) { + if (currentObject.name === "Scene") { + break; + } + currentObject = currentObject.parent as THREE.Object3D; + } + if (currentObject) { + AttachedObject.current = currentObject as any; + // controls.fitToSphere(AttachedObject.current!, true); + + const bbox = new THREE.Box3().setFromObject(AttachedObject.current); + const size = bbox.getSize(new THREE.Vector3()); + const center = bbox.getCenter(new THREE.Vector3()); + + const front = new THREE.Vector3(0, 0, 1); + AttachedObject.current.localToWorld(front); + front.sub(AttachedObject.current.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(AttachedObject.current!, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, }); + + setSelectedFloorItem(AttachedObject.current!); + } + } else { + const target = controls.getTarget(new THREE.Vector3()); + await controls.setTarget(target.x, 0, target.z, true); + setSelectedFloorItem(null); + } + } + } + }; + + const onDrop = (event: any) => { + if (!event.dataTransfer?.files[0]) return; + + if (selectedItem.id !== "" && event.dataTransfer?.files[0]) { + addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, setSimulationStates, plane); + } + }; + + const onDragOver = (event: any) => { + event.preventDefault(); + }; + + if (activeModule === "builder") { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("dblclick", onDblClick); + canvasElement.addEventListener("drop", onDrop); + canvasElement.addEventListener("dragover", onDragOver); + } else { + if (controls) { + const target = controls.getTarget(new THREE.Vector3()); + controls.setTarget(target.x, 0, target.z, true); + setSelectedFloorItem(null); } - } else { - const target = controls.getTarget(new THREE.Vector3()); - await controls.setTarget(target.x, 0, target.z, true); - setselectedFloorItem(null); - } } - } - }; - const onDrop = (event: any) => { - if (!event.dataTransfer?.files[0]) return; + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("dblclick", onDblClick); + canvasElement.removeEventListener("drop", onDrop); + canvasElement.removeEventListener("dragover", onDragOver); + }; + }, [deleteTool, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool, activeModule,]); - if (selectedItem.id !== "" && event.dataTransfer?.files[0]) { - addAssetModel( - raycaster, - state.camera, - state.pointer, - floorGroup, - setFloorItems, - itemsGroup, - isTempLoader, - tempLoader, - socket, - selectedItem, - setSelectedItem, - setSimulationPaths, - plane - ); - } - }; + useFrame(() => { + if (controls) + assetVisibility(itemsGroup, state.camera.position, renderDistance); + if (deleteTool && activeModule === "builder") { + DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem); + } else if (!deleteTool) { + if (hoveredDeletableFloorItem.current) { + hoveredDeletableFloorItem.current = undefined; + setDeletableFloorItem(null); + } + } + }); - const onDragOver = (event: any) => { - event.preventDefault(); - }; - - if (activeModule === "builder") { - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener("dblclick", onDblClick); - canvasElement.addEventListener("drop", onDrop); - canvasElement.addEventListener("dragover", onDragOver); - } else { - if (controls) { - const target = controls.getTarget(new THREE.Vector3()); - controls.setTarget(target.x, 0, target.z, true); - setselectedFloorItem(null); - } - } - - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("dblclick", onDblClick); - canvasElement.removeEventListener("drop", onDrop); - canvasElement.removeEventListener("dragover", onDragOver); - }; - }, [ - deleteModels, - transformMode, - controls, - selectedItem, - state.camera, - state.pointer, - activeTool, - activeModule, - ]); - - - useFrame(() => { - if (controls) - assetVisibility(itemsGroup, state.camera.position, renderDistance); - if (deleteModels) { - DeletableHoveredFloorItems( - state, - itemsGroup, - hoveredDeletableFloorItem, - setDeletableFloorItem - ); - } else if (!deleteModels) { - if (hoveredDeletableFloorItem.current) { - hoveredDeletableFloorItem.current = undefined; - setDeletableFloorItem(null); - } - } - }); - - return ; + return ; }; export default FloorItemsGroup; diff --git a/app/src/modules/builder/groups/wallItemsGroup.tsx b/app/src/modules/builder/groups/wallItemsGroup.tsx index c79adde..cdc326e 100644 --- a/app/src/modules/builder/groups/wallItemsGroup.tsx +++ b/app/src/modules/builder/groups/wallItemsGroup.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { useDeleteModels, useDeletePointOrLine, useObjectPosition, useObjectRotation, useObjectScale, useSelectedWallItem, useSocketStore, useWallItems } from "../../../store/store"; +import { useDeleteTool, useDeletePointOrLine, useObjectPosition, useObjectRotation, useObjectScale, useSelectedWallItem, useSocketStore, useWallItems } from "../../../store/store"; import { Csg } from "../csg/csg"; import * as Types from "../../../types/world/worldTypes"; import * as CONSTANTS from "../../../types/world/worldConstants"; @@ -9,20 +9,21 @@ import handleMeshMissed from "../eventFunctions/handleMeshMissed"; import DeleteWallItems from "../geomentries/walls/deleteWallItems"; import loadInitialWallItems from "../../scene/IntialLoad/loadInitialWallItems"; import AddWallItems from "../geomentries/walls/addWallItems"; +import useModuleStore from "../../../store/useModuleStore"; const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletableWallItem, selectedItemsIndex, setSelectedItemsIndex, CSGGroup }: any) => { - const { deleteModels, setDeleteModels } = useDeleteModels(); + const state = useThree(); + const { socket } = useSocketStore(); + const { pointer, camera, raycaster } = state; + const { deleteTool, setDeleteTool } = useDeleteTool(); const { wallItems, setWallItems } = useWallItems(); const { objectPosition, setObjectPosition } = useObjectPosition(); const { objectScale, setObjectScale } = useObjectScale(); const { objectRotation, setObjectRotation } = useObjectRotation(); const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); - const { socket } = useSocketStore(); - const state = useThree(); - const { pointer, camera, raycaster } = state; - + const { activeModule } = useModuleStore(); useEffect(() => { // Load Wall Items from the backend @@ -209,7 +210,7 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable const onMouseUp = (evt: any) => { if (evt.button === 0) { isLeftMouseDown = false; - if (!drag && deleteModels) { + if (!drag && deleteTool && activeModule === "builder") { DeleteWallItems(hoveredDeletableWallItem, setWallItems, wallItems, socket); } } @@ -224,7 +225,7 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable const onDrop = (event: any) => { if (!event.dataTransfer?.files[0]) return - + pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); @@ -256,15 +257,15 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable canvasElement.removeEventListener("drop", onDrop); canvasElement.removeEventListener("dragover", onDragOver); }; - }, [deleteModels, wallItems]) + }, [deleteTool, wallItems]) useEffect(() => { - if (deleteModels) { + if (deleteTool && activeModule === "builder") { handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex); setSelectedWallItem(null); setSelectedItemsIndex(null); } - }, [deleteModels]) + }, [deleteTool]) return ( <> diff --git a/app/src/modules/builder/groups/wallsAndWallItems.tsx b/app/src/modules/builder/groups/wallsAndWallItems.tsx index 7366f28..6b6c97d 100644 --- a/app/src/modules/builder/groups/wallsAndWallItems.tsx +++ b/app/src/modules/builder/groups/wallsAndWallItems.tsx @@ -1,5 +1,5 @@ import { Geometry } from "@react-three/csg"; -import { useDeleteModels, useSelectedWallItem, useToggleView, useTransformMode, useWallItems, useWalls } from "../../../store/store"; +import { useDeleteTool, useSelectedWallItem, useToggleView, useTransformMode, useWallItems, useWalls } from "../../../store/store"; import handleMeshDown from "../eventFunctions/handleMeshDown"; import handleMeshMissed from "../eventFunctions/handleMeshMissed"; import WallsMesh from "./wallsMesh"; @@ -11,13 +11,13 @@ const WallsAndWallItems = ({ CSGGroup, AssetConfigurations, setSelectedItemsInde const { walls, setWalls } = useWalls(); const { wallItems, setWallItems } = useWallItems(); const { toggleView, setToggleView } = useToggleView(); - const { deleteModels, setDeleteModels } = useDeleteModels(); + const { deleteTool, setDeleteTool } = useDeleteTool(); const { transformMode, setTransformMode } = useTransformMode(); const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); useEffect(() => { if (transformMode === null) { - if (!deleteModels) { + if (!deleteTool) { handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex); setSelectedWallItem(null); setSelectedItemsIndex(null); @@ -33,12 +33,12 @@ const WallsAndWallItems = ({ CSGGroup, AssetConfigurations, setSelectedItemsInde receiveShadow visible={!toggleView} onClick={(event) => { - if (!deleteModels && transformMode !== null) { + if (!deleteTool && transformMode !== null) { handleMeshDown(event, currentWallItem, setSelectedWallItem, setSelectedItemsIndex, wallItems, toggleView); } }} onPointerMissed={() => { - if (!deleteModels) { + if (!deleteTool) { handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex); setSelectedWallItem(null); setSelectedItemsIndex(null); diff --git a/app/src/modules/builder/groups/zoneGroup.tsx b/app/src/modules/builder/groups/zoneGroup.tsx index c5e54d3..d218534 100644 --- a/app/src/modules/builder/groups/zoneGroup.tsx +++ b/app/src/modules/builder/groups/zoneGroup.tsx @@ -2,34 +2,52 @@ import React, { useState, useEffect, useMemo, useRef } from "react"; import { Line, Sphere } from "@react-three/drei"; import { useThree, useFrame } from "@react-three/fiber"; import * as THREE from "three"; -import { useActiveLayer, useDeleteModels, useDeletePointOrLine, useMovePoint, useSocketStore, useToggleView, useToolMode, useRemovedLayer, useZones, useZonePoints } from "../../../store/store"; +import { + useActiveLayer, + useDeleteTool, + useDeletePointOrLine, + useMovePoint, + useSocketStore, + useToggleView, + useToolMode, + useRemovedLayer, + useZones, + useZonePoints, +} from "../../../store/store"; // import { setZonesApi } from "../../../services/factoryBuilder/zones/setZonesApi"; // import { deleteZonesApi } from "../../../services/factoryBuilder/zones/deleteZoneApi"; import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi"; -import * as CONSTANTS from '../../../types/world/worldConstants'; +import * as CONSTANTS from "../../../types/world/worldConstants"; const ZoneGroup: React.FC = () => { - const { camera, pointer, gl, raycaster, scene, controls } = useThree(); - const [startPoint, setStartPoint] = useState(null); - const [endPoint, setEndPoint] = useState(null); - const { zones, setZones } = useZones(); - const { zonePoints, setZonePoints } = useZonePoints(); - const [isDragging, setIsDragging] = useState(false); - const [draggedSphere, setDraggedSphere] = useState(null); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const { toggleView } = useToggleView(); - const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); - const { removedLayer, setRemovedLayer } = useRemovedLayer(); - const { toolMode, setToolMode } = useToolMode(); - const { movePoint, setMovePoint } = useMovePoint(); - const { deleteModels, setDeleteModels } = useDeleteModels(); - const { activeLayer, setActiveLayer } = useActiveLayer(); - const { socket } = useSocketStore(); + const { camera, pointer, gl, raycaster, scene, controls } = useThree(); + const [startPoint, setStartPoint] = useState(null); + const [endPoint, setEndPoint] = useState(null); + const { zones, setZones } = useZones(); + const { zonePoints, setZonePoints } = useZonePoints(); + const [isDragging, setIsDragging] = useState(false); + const [draggedSphere, setDraggedSphere] = useState( + null + ); + const plane = useMemo( + () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), + [] + ); + const { toggleView } = useToggleView(); + const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); + const { removedLayer, setRemovedLayer } = useRemovedLayer(); + const { toolMode, setToolMode } = useToolMode(); + const { movePoint, setMovePoint } = useMovePoint(); + const { deleteTool, setDeleteTool } = useDeleteTool(); + const { activeLayer, setActiveLayer } = useActiveLayer(); + const { socket } = useSocketStore(); - const groupsRef = useRef(); + const groupsRef = useRef(); - const zoneMaterial = useMemo(() => new THREE.ShaderMaterial({ + const zoneMaterial = useMemo( + () => + new THREE.ShaderMaterial({ side: THREE.DoubleSide, vertexShader: ` varying vec2 vUv; @@ -47,465 +65,555 @@ const ZoneGroup: React.FC = () => { } `, uniforms: { - uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) }, + uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) }, }, transparent: true, + depthWrite: false, }), []); - useEffect(() => { - const fetchZones = async () => { - const email = localStorage.getItem('email'); - if (!email) return; + useEffect(() => { + const fetchZones = async () => { + const email = localStorage.getItem("email"); + if (!email) return; - const organization = email.split("@")[1].split(".")[0]; - const data = await getZonesApi(organization); + const organization = email.split("@")[1].split(".")[0]; + const data = await getZonesApi(organization); - if (data.data && data.data.length > 0) { - const fetchedZones = data.data.map((zone: any) => ({ - zoneId: zone.zoneId, - zoneName: zone.zoneName, - points: zone.points, - viewPortCenter: zone.viewPortCenter, - viewPortposition: zone.viewPortposition, - layer: zone.layer - })); + if (data.data && data.data.length > 0) { + const fetchedZones = data.data.map((zone: any) => ({ + zoneId: zone.zoneId, + zoneName: zone.zoneName, + points: zone.points, + viewPortCenter: zone.viewPortCenter, + viewPortposition: zone.viewPortposition, + layer: zone.layer, + })); - setZones(fetchedZones); + setZones(fetchedZones); - const fetchedPoints = data.data.flatMap((zone: any) => - zone.points.slice(0, 4).map((point: [number, number, number]) => new THREE.Vector3(...point)) - ); + const fetchedPoints = data.data.flatMap((zone: any) => + zone.points + .slice(0, 4) + .map( + (point: [number, number, number]) => new THREE.Vector3(...point) + ) + ); - setZonePoints(fetchedPoints); - } - }; + setZonePoints(fetchedPoints); + } + }; - fetchZones(); - }, []); + fetchZones(); + }, []); - useEffect(() => { + useEffect(() => { + localStorage.setItem("zones", JSON.stringify(zones)); + }, [zones]); - localStorage.setItem('zones', JSON.stringify(zones)); + useEffect(() => { + if (removedLayer) { + const updatedZones = zones.filter( + (zone: any) => zone.layer !== removedLayer + ); + setZones(updatedZones); - }, [zones]) + const updatedzonePoints = zonePoints.filter((_: any, index: any) => { + const zoneIndex = Math.floor(index / 4); + return zones[zoneIndex]?.layer !== removedLayer; + }); + setZonePoints(updatedzonePoints); - useEffect(() => { - if (removedLayer) { - const updatedZones = zones.filter((zone: any) => zone.layer !== removedLayer); - setZones(updatedZones); + zones + .filter((zone: any) => zone.layer === removedLayer) + .forEach((zone: any) => { + deleteZoneFromBackend(zone.zoneId); + }); - const updatedzonePoints = zonePoints.filter((_: any, index: any) => { - const zoneIndex = Math.floor(index / 4); - return zones[zoneIndex]?.layer !== removedLayer; - }); - setZonePoints(updatedzonePoints); + setRemovedLayer(null); + } + }, [removedLayer]); - zones.filter((zone: any) => zone.layer === removedLayer).forEach((zone: any) => { - deleteZoneFromBackend(zone.zoneId); - }); + useEffect(() => { + if (toolMode !== "Zone") { + setStartPoint(null); + setEndPoint(null); + } else { + setDeletePointOrLine(false); + setMovePoint(false); + setDeleteTool(false); + } + if (!toggleView) { + setStartPoint(null); + setEndPoint(null); + } + }, [toolMode, toggleView]); - setRemovedLayer(null); + const addZoneToBackend = async (zone: { + zoneId: string; + zoneName: string; + points: [number, number, number][]; + layer: string; + }) => { + const email = localStorage.getItem("email"); + const userId = localStorage.getItem("userId"); + const organization = email!.split("@")[1].split(".")[0]; + + const calculateCenter = (points: number[][]) => { + if (!points || points.length === 0) return null; + + let sumX = 0, + sumY = 0, + sumZ = 0; + const numPoints = points.length; + + points.forEach(([x, y, z]) => { + sumX += x; + sumY += y; + sumZ += z; + }); + + return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [ + number, + number, + number + ]; + }; + + const target: [number, number, number] | null = calculateCenter( + zone.points + ); + if (!target) return; + const position = [target[0], 10, target[2]]; + + const input = { + userId: userId, + organization: organization, + zoneData: { + zoneName: zone.zoneName, + zoneId: zone.zoneId, + points: zone.points, + viewPortCenter: target, + viewPortposition: position, + layer: zone.layer, + }, + }; + + socket.emit("v2:zone:set", input); + }; + + const updateZoneToBackend = async (zone: { + zoneId: string; + zoneName: string; + points: [number, number, number][]; + layer: string; + }) => { + const email = localStorage.getItem("email"); + const userId = localStorage.getItem("userId"); + const organization = email!.split("@")[1].split(".")[0]; + + const calculateCenter = (points: number[][]) => { + if (!points || points.length === 0) return null; + + let sumX = 0, + sumY = 0, + sumZ = 0; + const numPoints = points.length; + + points.forEach(([x, y, z]) => { + sumX += x; + sumY += y; + sumZ += z; + }); + + return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [ + number, + number, + number + ]; + }; + + const target: [number, number, number] | null = calculateCenter( + zone.points + ); + if (!target) return; + const position = [target[0], 10, target[2]]; + + const input = { + userId: userId, + organization: organization, + zoneData: { + zoneName: zone.zoneName, + zoneId: zone.zoneId, + points: zone.points, + viewPortCenter: target, + viewPortposition: position, + layer: zone.layer, + }, + }; + + socket.emit("v2:zone:set", input); + }; + + const deleteZoneFromBackend = async (zoneId: string) => { + const email = localStorage.getItem("email"); + const userId = localStorage.getItem("userId"); + const organization = email!.split("@")[1].split(".")[0]; + + const input = { + userId: userId, + organization: organization, + zoneId: zoneId, + }; + + socket.emit("v2:zone:delete", input); + }; + + const handleDeleteZone = (zoneId: string) => { + const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId); + setZones(updatedZones); + + const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId); + if (zoneIndex !== -1) { + const zonePointsToRemove = zonePoints.slice( + zoneIndex * 4, + zoneIndex * 4 + 4 + ); + zonePointsToRemove.forEach((point: any) => + groupsRef.current.remove(point) + ); + const updatedzonePoints = zonePoints.filter( + (_: any, index: any) => + index < zoneIndex * 4 || index >= zoneIndex * 4 + 4 + ); + setZonePoints(updatedzonePoints); + } + + deleteZoneFromBackend(zoneId); + }; + + useEffect(() => { + if (!camera || !toggleView) return; + const canvasElement = gl.domElement; + + let drag = false; + let isLeftMouseDown = false; + + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown = true; + drag = false; + + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + groupsRef.current.children, + true + ); + + if (intersects.length > 0 && movePoint) { + const clickedObject = intersects[0].object; + const sphereIndex = zonePoints.findIndex((point: any) => + point.equals(clickedObject.position) + ); + if (sphereIndex !== -1) { + (controls as any).enabled = false; + setDraggedSphere(zonePoints[sphereIndex]); + setIsDragging(true); + } } - }, [removedLayer]); + } + }; - useEffect(() => { - if (toolMode !== "Zone") { - setStartPoint(null); + const onMouseUp = (evt: any) => { + if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) { + isLeftMouseDown = false; + + if (!startPoint && !movePoint) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (point) { + setStartPoint(point); setEndPoint(null); - } else { - setDeletePointOrLine(false); - setMovePoint(false); - setDeleteModels(false); + } + } else if (startPoint && !movePoint) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!point) return; + + const points = [ + [startPoint.x, 0.15, startPoint.z], + [point.x, 0.15, startPoint.z], + [point.x, 0.15, point.z], + [startPoint.x, 0.15, point.z], + [startPoint.x, 0.15, startPoint.z], + ] as [number, number, number][]; + + const zoneName = `Zone ${zones.length + 1}`; + const zoneId = THREE.MathUtils.generateUUID(); + const newZone = { + zoneId, + zoneName, + points: points, + layer: activeLayer, + }; + + const newZones = [...zones, newZone]; + + setZones(newZones); + + const newzonePoints = [ + new THREE.Vector3(startPoint.x, 0.15, startPoint.z), + new THREE.Vector3(point.x, 0.15, startPoint.z), + new THREE.Vector3(point.x, 0.15, point.z), + new THREE.Vector3(startPoint.x, 0.15, point.z), + ]; + + const updatedZonePoints = [...zonePoints, ...newzonePoints]; + setZonePoints(updatedZonePoints); + + addZoneToBackend(newZone); + + setStartPoint(null); + setEndPoint(null); } - if (!toggleView) { - setStartPoint(null); - setEndPoint(null); + } else if ( + evt.button === 0 && + !drag && + !isDragging && + deletePointOrLine + ) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + groupsRef.current.children, + true + ); + + if (intersects.length > 0) { + const clickedObject = intersects[0].object; + + const sphereIndex = zonePoints.findIndex((point: any) => + point.equals(clickedObject.position) + ); + if (sphereIndex !== -1) { + const zoneIndex = Math.floor(sphereIndex / 4); + const zoneId = zones[zoneIndex].zoneId; + handleDeleteZone(zoneId); + return; + } } - }, [toolMode, toggleView]); + } + if (evt.button === 0) { + if (isDragging && draggedSphere) { + setIsDragging(false); + setDraggedSphere(null); - const addZoneToBackend = async (zone: { zoneId: string; zoneName: string; points: [number, number, number][]; layer: string }) => { + const sphereIndex = zonePoints.findIndex( + (point: any) => point === draggedSphere + ); + if (sphereIndex !== -1) { + const zoneIndex = Math.floor(sphereIndex / 4); - const email = localStorage.getItem('email'); - const userId = localStorage.getItem('userId'); - const organization = (email!.split("@")[1]).split(".")[0]; - - const calculateCenter = (points: number[][]) => { - if (!points || points.length === 0) return null; - - let sumX = 0, sumY = 0, sumZ = 0; - const numPoints = points.length; - - points.forEach(([x, y, z]) => { - sumX += x; - sumY += y; - sumZ += z; - }); - - return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [number, number, number]; - }; - - const target: [number, number, number] | null = calculateCenter(zone.points); - if (!target) return; - const position = [target[0], 10, target[2]]; - - const input = { - userId: userId, - organization: organization, - zoneData: { - zoneName: zone.zoneName, - zoneId: zone.zoneId, - points: zone.points, - viewPortCenter: target, - viewPortposition: position, - layer: zone.layer + if (zoneIndex !== -1 && zones[zoneIndex]) { + updateZoneToBackend(zones[zoneIndex]); } + } } - - socket.emit('v2:zone:set', input); + } }; - const updateZoneToBackend = async (zone: { zoneId: string; zoneName: string; points: [number, number, number][]; layer: string }) => { + const onMouseMove = () => { + if (isLeftMouseDown) { + drag = true; + } + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + groupsRef.current.children, + true + ); - const email = localStorage.getItem('email'); - const userId = localStorage.getItem('userId'); - const organization = (email!.split("@")[1]).split(".")[0]; - - const calculateCenter = (points: number[][]) => { - if (!points || points.length === 0) return null; - - let sumX = 0, sumY = 0, sumZ = 0; - const numPoints = points.length; - - points.forEach(([x, y, z]) => { - sumX += x; - sumY += y; - sumZ += z; - }); - - return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [number, number, number]; - }; - - const target: [number, number, number] | null = calculateCenter(zone.points); - if (!target) return; - const position = [target[0], 10, target[2]]; - - const input = { - userId: userId, - organization: organization, - zoneData: { - zoneName: zone.zoneName, - zoneId: zone.zoneId, - points: zone.points, - viewPortCenter: target, - viewPortposition: position, - layer: zone.layer - } - } - - socket.emit('v2:zone:set', input); - }; - - - const deleteZoneFromBackend = async (zoneId: string) => { - - const email = localStorage.getItem('email'); - const userId = localStorage.getItem('userId'); - const organization = (email!.split("@")[1]).split(".")[0]; - - const input = { - userId: userId, - organization: organization, - zoneId: zoneId - } - - socket.emit('v2:zone:delete', input); - }; - - const handleDeleteZone = (zoneId: string) => { - const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId); - setZones(updatedZones); - - const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId); - if (zoneIndex !== -1) { - const zonePointsToRemove = zonePoints.slice(zoneIndex * 4, zoneIndex * 4 + 4); - zonePointsToRemove.forEach((point: any) => groupsRef.current.remove(point)); - const updatedzonePoints = zonePoints.filter((_: any, index: any) => index < zoneIndex * 4 || index >= zoneIndex * 4 + 4); - setZonePoints(updatedzonePoints); - } - - deleteZoneFromBackend(zoneId); - }; - - useEffect(() => { - if (!camera || !toggleView) return; - const canvasElement = gl.domElement; - - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; - - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(groupsRef.current.children, true); - - if (intersects.length > 0 && movePoint) { - const clickedObject = intersects[0].object; - const sphereIndex = zonePoints.findIndex((point: any) => point.equals(clickedObject.position)); - if (sphereIndex !== -1) { - (controls as any).enabled = false; - setDraggedSphere(zonePoints[sphereIndex]); - setIsDragging(true); - } - } - } - }; - - const onMouseUp = (evt: any) => { - if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) { - isLeftMouseDown = false; - - if (!startPoint && !movePoint) { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - setStartPoint(point); - setEndPoint(null); - } - } else if (startPoint && !movePoint) { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (!point) return; - - const points = [ - [startPoint.x, 0.15, startPoint.z], - [point.x, 0.15, startPoint.z], - [point.x, 0.15, point.z], - [startPoint.x, 0.15, point.z], - [startPoint.x, 0.15, startPoint.z], - ] as [number, number, number][]; - - const zoneName = `Zone ${zones.length + 1}`; - const zoneId = THREE.MathUtils.generateUUID(); - const newZone = { - zoneId, - zoneName, - points: points, - layer: activeLayer - }; - - const newZones = [...zones, newZone]; - - setZones(newZones); - - const newzonePoints = [ - new THREE.Vector3(startPoint.x, 0.15, startPoint.z), - new THREE.Vector3(point.x, 0.15, startPoint.z), - new THREE.Vector3(point.x, 0.15, point.z), - new THREE.Vector3(startPoint.x, 0.15, point.z), - ]; - - const updatedZonePoints = [...zonePoints, ...newzonePoints]; - setZonePoints(updatedZonePoints); - - addZoneToBackend(newZone); - - setStartPoint(null); - setEndPoint(null); - } - } else if (evt.button === 0 && !drag && !isDragging && deletePointOrLine) { - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(groupsRef.current.children, true); - - if (intersects.length > 0) { - const clickedObject = intersects[0].object; - - const sphereIndex = zonePoints.findIndex((point: any) => point.equals(clickedObject.position)); - if (sphereIndex !== -1) { - const zoneIndex = Math.floor(sphereIndex / 4); - const zoneId = zones[zoneIndex].zoneId; - handleDeleteZone(zoneId); - return; - } - } - } - - if (evt.button === 0) { - if (isDragging && draggedSphere) { - setIsDragging(false); - setDraggedSphere(null); - - const sphereIndex = zonePoints.findIndex((point: any) => point === draggedSphere); - if (sphereIndex !== -1) { - const zoneIndex = Math.floor(sphereIndex / 4); - - if (zoneIndex !== -1 && zones[zoneIndex]) { - updateZoneToBackend(zones[zoneIndex]); - } - } - } - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) { - drag = true; - } - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(groupsRef.current.children, true); - - if (intersects.length > 0 && intersects[0].object.name.includes('point')) { - gl.domElement.style.cursor = movePoint ? "pointer" : "default"; - } else { - gl.domElement.style.cursor = "default"; - } - if (isDragging && draggedSphere) { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - draggedSphere.set(point.x, 0.15, point.z); - - const sphereIndex = zonePoints.findIndex((point: any) => point === draggedSphere); - if (sphereIndex !== -1) { - const zoneIndex = Math.floor(sphereIndex / 4); - const cornerIndex = sphereIndex % 4; - - const updatedZones = zones.map((zone: any, index: number) => { - if (index === zoneIndex) { - const updatedPoints = [...zone.points]; - updatedPoints[cornerIndex] = [point.x, 0.15, point.z]; - updatedPoints[4] = updatedPoints[0]; - return { ...zone, points: updatedPoints }; - } - return zone; - }); - - setZones(updatedZones); - } - } - } - }; - - const onContext = (event: any) => { - event.preventDefault(); - setStartPoint(null); - setEndPoint(null); - }; - - if (toolMode === 'Zone' || deletePointOrLine || movePoint) { - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener("contextmenu", onContext); - } - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("contextmenu", onContext); - }; - }, [gl, camera, startPoint, toggleView, scene, toolMode, zones, isDragging, deletePointOrLine, zonePoints, draggedSphere, movePoint, activeLayer]); - - useFrame(() => { - if (!startPoint) return; + if ( + intersects.length > 0 && + intersects[0].object.name.includes("point") + ) { + gl.domElement.style.cursor = movePoint ? "pointer" : "default"; + } else { + gl.domElement.style.cursor = "default"; + } + if (isDragging && draggedSphere) { raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const point = raycaster.ray.intersectPlane(plane, intersectionPoint); if (point) { - setEndPoint(point); + draggedSphere.set(point.x, 0.15, point.z); + + const sphereIndex = zonePoints.findIndex( + (point: any) => point === draggedSphere + ); + if (sphereIndex !== -1) { + const zoneIndex = Math.floor(sphereIndex / 4); + const cornerIndex = sphereIndex % 4; + + const updatedZones = zones.map((zone: any, index: number) => { + if (index === zoneIndex) { + const updatedPoints = [...zone.points]; + updatedPoints[cornerIndex] = [point.x, 0.15, point.z]; + updatedPoints[4] = updatedPoints[0]; + return { ...zone, points: updatedPoints }; + } + return zone; + }); + + setZones(updatedZones); + } } - }); - return ( - - - {zones - .map((zone: any) => ( - - {zone.points.slice(0, -1).map((point: [number, number, number], index: number) => { - const nextPoint = zone.points[index + 1]; + } + }; - const point1 = new THREE.Vector3(point[0], point[1], point[2]); - const point2 = new THREE.Vector3(nextPoint[0], nextPoint[1], nextPoint[2]); + const onContext = (event: any) => { + event.preventDefault(); + setStartPoint(null); + setEndPoint(null); + }; - const planeWidth = point1.distanceTo(point2); - const planeHeight = CONSTANTS.zoneConfig.height; + if (toolMode === "Zone" || deletePointOrLine || movePoint) { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("contextmenu", onContext); + } + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("contextmenu", onContext); + }; + }, [ + gl, + camera, + startPoint, + toggleView, + scene, + toolMode, + zones, + isDragging, + deletePointOrLine, + zonePoints, + draggedSphere, + movePoint, + activeLayer, + ]); - const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.zoneConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.zoneConfig.height), (point1.z + point2.z) / 2); + useFrame(() => { + if (!startPoint) return; + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (point) { + setEndPoint(point); + } + }); + return ( + + + {zones.map((zone: any) => ( + + {zone.points + .slice(0, -1) + .map((point: [number, number, number], index: number) => { + const nextPoint = zone.points[index + 1]; - const angle = Math.atan2(point2.z - point1.z, point2.x - point1.x); + const point1 = new THREE.Vector3(point[0], point[1], point[2]); + const point2 = new THREE.Vector3( + nextPoint[0], + nextPoint[1], + nextPoint[2] + ); - return ( - - - - - ); - })} - - ))} - - - {zones - .filter((zone: any) => zone.layer === activeLayer) - .map((zone: any) => ( - { - e.stopPropagation(); - if (deletePointOrLine) { - handleDeleteZone(zone.zoneId); - } - }} - /> - ))} - - - {zones.filter((zone: any) => zone.layer === activeLayer).flatMap((zone: any) => ( - zone.points.slice(0, 4).map((point: any, pointIndex: number) => ( - - - - )) - ))} - - - {startPoint && endPoint && ( - + + - )} - - - ); + + ); + })} + + ))} + + + {zones + .filter((zone: any) => zone.layer === activeLayer) + .map((zone: any) => ( + { + e.stopPropagation(); + if (deletePointOrLine) { + handleDeleteZone(zone.zoneId); + } + }} + /> + ))} + + + {zones + .filter((zone: any) => zone.layer === activeLayer) + .flatMap((zone: any) => + zone.points.slice(0, 4).map((point: any, pointIndex: number) => ( + + + + )) + )} + + + {startPoint && endPoint && ( + + )} + + + ); }; -export default ZoneGroup; \ No newline at end of file +export default ZoneGroup; diff --git a/app/src/modules/builder/groups/zoneGroup1.tsx b/app/src/modules/builder/groups/zoneGroup1.tsx index b0c6638..80be9a3 100644 --- a/app/src/modules/builder/groups/zoneGroup1.tsx +++ b/app/src/modules/builder/groups/zoneGroup1.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; import * as CONSTANTS from "../../../types/world/worldConstants"; -import { useActiveLayer, useSocketStore, useDeleteModels, useDeletePointOrLine, useMovePoint, useToggleView, useUpdateScene, useNewLines, useToolMode } from "../../../store/store"; +import { useActiveLayer, useSocketStore, useDeleteTool, useDeletePointOrLine, useMovePoint, useToggleView, useUpdateScene, useNewLines, useToolMode } from "../../../store/store"; import { useThree } from "@react-three/fiber"; import arrayLineToObject from "../geomentries/lines/lineConvertions/arrayLineToObject"; import addPointToScene from "../geomentries/points/addPointToScene"; @@ -14,7 +14,7 @@ import loadZones from "../geomentries/zones/loadZones"; const ZoneGroup = ({ zoneGroup, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => { const { toggleView, setToggleView } = useToggleView(); - const { deleteModels, setDeleteModels } = useDeleteModels(); + const { deleteTool, setDeleteTool } = useDeleteTool(); const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); const { toolMode, setToolMode } = useToolMode(); const { movePoint, setMovePoint } = useMovePoint(); @@ -35,7 +35,7 @@ const ZoneGroup = ({ zoneGroup, plane, floorPlanGroupLine, floorPlanGroupPoint, if (toolMode === "Zone") { setDeletePointOrLine(false); setMovePoint(false); - setDeleteModels(false); + setDeleteTool(false); } else { removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint); removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line); diff --git a/app/src/modules/collaboration/collabCams.tsx b/app/src/modules/collaboration/collabCams.tsx index 20d983e..95a9833 100644 --- a/app/src/modules/collaboration/collabCams.tsx +++ b/app/src/modules/collaboration/collabCams.tsx @@ -12,203 +12,188 @@ import CollabUserIcon from "./collabUserIcon"; import { getAvatarColor } from "./users/functions/getAvatarColor"; const CamModelsGroup = () => { - let navigate = useNavigate(); - const groupRef = useRef(null); - const email = localStorage.getItem("email"); - const { activeUsers, setActiveUsers } = useActiveUsers(); - const { socket } = useSocketStore(); - const loader = new GLTFLoader(); - const dracoLoader = new DRACOLoader(); - const [cams, setCams] = useState([]); - const [models, setModels] = useState>({}); + const navigate = useNavigate(); + const groupRef = useRef(null); + const email = localStorage.getItem("email"); + const { activeUsers, setActiveUsers } = useActiveUsers(); + const { socket } = useSocketStore(); - dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); - loader.setDRACOLoader(dracoLoader); + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); + dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); + loader.setDRACOLoader(dracoLoader); - useEffect(() => { - if (!email) { - navigate("/"); - } - if (!socket) return; - const organization = email!.split("@")[1].split(".")[0]; + const [cams, setCams] = useState([]); + const [models, setModels] = useState>({}); - socket.on("userConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (data.data.userData.email === email) return; - if (socket.id === data.socketId || organization !== data.organization) - return; + const dedupeCams = (cams: any[]) => { + const seen = new Set(); + return cams.filter((cam) => { + if (seen.has(cam.uuid)) return false; + seen.add(cam.uuid); + return true; + }); + }; - const model = groupRef.current.getObjectByProperty( - "uuid", - data.data.userData._id - ); - if (model) { - groupRef.current.remove(model); - } - loader.load(camModel, (gltf) => { - const newModel = gltf.scene.clone(); - newModel.uuid = data.data.userData._id; - newModel.position.set( - data.data.position.x, - data.data.position.y, - data.data.position.z - ); - newModel.rotation.set( - data.data.rotation.x, - data.data.rotation.y, - data.data.rotation.z - ); - newModel.userData = data.data.userData; - setCams((prev) => [...prev, newModel]); - setActiveUsers([...activeUsers, data.data.userData]); - }); - }); + const dedupeUsers = (users: any[]) => { + const seen = new Set(); + return users.filter((user) => { + if (seen.has(user._id)) return false; + seen.add(user._id); + return true; + }); + }; - // socket.on("users:online", (data: any) => { - // console.log('users online: ', data); - // }) + useEffect(() => { + if (!email) navigate("/"); - socket.on("userDisConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (socket.id === data.socketId || organization !== data.organization) - return; + if (!socket) return; + const organization = email!.split("@")[1].split(".")[0]; - setCams((prev) => - prev.filter((cam) => cam.uuid !== data.data.userData._id) - ); - setActiveUsers( - activeUsers.filter((user: any) => user._id !== data.data.userData._id) - ); - }); + socket.on("userConnectResponse", (data: any) => { + if (!groupRef.current) return; + if (data.data.userData.email === email) return; + if (socket.id === data.socketId || organization !== data.organization) return; - socket.on("cameraUpdateResponse", (data: any) => { - if ( - !groupRef.current || - socket.id === data.socketId || - organization !== data.organization - ) - return; + const model = groupRef.current.getObjectByProperty("uuid", data.data.userData._id); + if (model) { + groupRef.current.remove(model); + } - setModels((prev) => ({ - ...prev, - [data.data.userId]: { - targetPosition: new THREE.Vector3( - data.data.position.x, - data.data.position.y, - data.data.position.z - ), - targetRotation: new THREE.Euler( - data.data.rotation.x, - data.data.rotation.y, - data.data.rotation.z - ), - }, - })); - }); + loader.load(camModel, (gltf) => { + const newModel = gltf.scene.clone(); + newModel.uuid = data.data.userData._id; + newModel.position.set( + data.data.position.x, + data.data.position.y, + data.data.position.z + ); + newModel.rotation.set( + data.data.rotation.x, + data.data.rotation.y, + data.data.rotation.z + ); + newModel.userData = data.data.userData; - return () => { - socket.off("userConnectRespones"); - socket.off("userDisConnectRespones"); - socket.off("cameraUpdateResponse"); - }; - }, [socket]); + setCams((prev) => dedupeCams([...prev, newModel])); + setActiveUsers((prev: any) => + dedupeUsers([...prev, data.data.userData]) + ); + }); + }); + socket.on("userDisConnectResponse", (data: any) => { + if (!groupRef.current) return; + if (socket.id === data.socketId || organization !== data.organization) return; - // useEffect(() => { - // console.log(activeUsers); - // }, [activeUsers]) + setCams((prev) => + prev.filter((cam) => cam.uuid !== data.data.userData._id) + ); + setActiveUsers((prev: any) => + prev.filter((user: any) => user._id !== data.data.userData._id) + ); + }); - // useEffect(() => { - // console.log(models); - // }, [models]) + socket.on("cameraUpdateResponse", (data: any) => { + if ( + !groupRef.current || + socket.id === data.socketId || + organization !== data.organization + ) + return; - useFrame(() => { - if (!groupRef.current) return; - Object.keys(models).forEach((uuid) => { - const model = groupRef.current!.getObjectByProperty("uuid", uuid); - if (!model) return; + setModels((prev) => ({ + ...prev, + [data.data.userId]: { + targetPosition: new THREE.Vector3( + data.data.position.x, + data.data.position.y, + data.data.position.z + ), + targetRotation: new THREE.Euler( + data.data.rotation.x, + data.data.rotation.y, + data.data.rotation.z + ), + }, + })); + }); - const { targetPosition, targetRotation } = models[uuid]; - model.position.lerp(targetPosition, 0.1); - model.rotation.x = THREE.MathUtils.lerp( - model.rotation.x, - targetRotation.x, - 0.1 - ); - model.rotation.y = THREE.MathUtils.lerp( - model.rotation.y, - targetRotation.y, - 0.1 - ); - model.rotation.z = THREE.MathUtils.lerp( - model.rotation.z, - targetRotation.z, - 0.1 - ); - }); - }); + return () => { + socket.off("userConnectResponse"); + socket.off("userDisConnectResponse"); + socket.off("cameraUpdateResponse"); + }; + }, [socket]); - useEffect(() => { - if (!groupRef.current) return; - const organization = email!.split("@")[1].split(".")[0]; - getActiveUsersData(organization).then((data) => { - const filteredData = data.cameraDatas.filter( - (camera: any) => camera.userData.email !== email - ); - let a: any = []; - if (filteredData.length > 0) { - loader.load(camModel, (gltf) => { - const newCams = filteredData.map((cam: any) => { - const newModel = gltf.scene.clone(); - newModel.uuid = cam.userData._id; - newModel.position.set( - cam.position.x, - cam.position.y, - cam.position.z - ); - newModel.rotation.set( - cam.rotation.x, - cam.rotation.y, - cam.rotation.z - ); - newModel.userData = cam.userData; - a.push(cam.userData); - return newModel; - }); - setActiveUsers(a); - setCams((prev) => [...prev, ...newCams]); - }); - } - }); - }, []); + useFrame(() => { + if (!groupRef.current) return; + Object.keys(models).forEach((uuid) => { + const model = groupRef.current!.getObjectByProperty("uuid", uuid); + if (!model) return; - return ( - - {cams.map((cam, index) => ( - - - - - - ))} - - ); + const { targetPosition, targetRotation } = models[uuid]; + model.position.lerp(targetPosition, 0.1); + model.rotation.x = THREE.MathUtils.lerp(model.rotation.x, targetRotation.x, 0.1); + model.rotation.y = THREE.MathUtils.lerp(model.rotation.y, targetRotation.y, 0.1); + model.rotation.z = THREE.MathUtils.lerp(model.rotation.z, targetRotation.z, 0.1); + }); + }); + + useEffect(() => { + if (!groupRef.current) return; + const organization = email!.split("@")[1].split(".")[0]; + + getActiveUsersData(organization).then((data) => { + const filteredData = data.cameraDatas.filter( + (camera: any) => camera.userData.email !== email + ); + + if (filteredData.length > 0) { + loader.load(camModel, (gltf) => { + const newCams = filteredData.map((cam: any) => { + const newModel = gltf.scene.clone(); + newModel.uuid = cam.userData._id; + newModel.position.set(cam.position.x, cam.position.y, cam.position.z); + newModel.rotation.set(cam.rotation.x, cam.rotation.y, cam.rotation.z); + newModel.userData = cam.userData; + return newModel; + }); + + const users = filteredData.map((cam: any) => cam.userData); + setActiveUsers((prev: any) => dedupeUsers([...prev, ...users])); + setCams((prev) => dedupeCams([...prev, ...newCams])); + }); + } + }); + }, []); + + return ( + + {cams.map((cam, index) => ( + + + + + + ))} + + ); }; export default CamModelsGroup; diff --git a/app/src/modules/collaboration/collabUserIcon.tsx b/app/src/modules/collaboration/collabUserIcon.tsx index acc3fa0..a8738ce 100644 --- a/app/src/modules/collaboration/collabUserIcon.tsx +++ b/app/src/modules/collaboration/collabUserIcon.tsx @@ -4,14 +4,12 @@ import CustomAvatar from "./users/Avatar"; interface CollabUserIconProps { userName: string; userImage?: string; - index?: number; color: string; } const CollabUserIcon: React.FC = ({ userImage, userName, - index = 0, color, }) => { return ( @@ -20,7 +18,7 @@ const CollabUserIcon: React.FC = ({ {userImage ? ( {userName} ) : ( - + )}
diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx index 8771303..6e578a1 100644 --- a/app/src/modules/collaboration/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socketResponses.dev.tsx @@ -105,7 +105,7 @@ export default function SocketResponses({ // console.log(`Getting ${data.data.modelname} from cache`); const model = cachedModel.scene.clone(); model.uuid = data.data.modeluuid; - model.userData = { name: data.data.modelname, modelId: data.data.modelFileID, modeluuid: data.data.modeluuid }; + model.userData = { name: data.data.modelname, modelId: data.data.modelfileID, modeluuid: data.data.modeluuid }; model.position.set(...data.data.position as [number, number, number]); model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); @@ -159,7 +159,7 @@ export default function SocketResponses({ url = URL.createObjectURL(indexedDBModel); } else { // console.log(`Getting ${data.data.modelname} from Backend`); - url = `${url_Backend_dwinzo}/api/v1/AssetFile/${data.data.modelfileID}`; + url = `${url_Backend_dwinzo}/api/v2/AssetFile/${data.data.modelfileID}`; const modelBlob = await fetch(url).then((res) => res.blob()); await storeGLTF(data.data.modelfileID, modelBlob); } @@ -179,7 +179,7 @@ export default function SocketResponses({ THREE.Cache.remove(url); const model = gltf.scene; model.uuid = data.data.modeluuid; - model.userData = { name: data.data.modelname, modelId: data.data.modelFileID, modeluuid: data.data.modeluuid }; + model.userData = { name: data.data.modelname, modelId: data.data.modelfileID, modeluuid: data.data.modeluuid }; model.position.set(...data.data.position as [number, number, number]); model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); @@ -233,7 +233,7 @@ export default function SocketResponses({ } } else if (data.message === "Model updated successfully") { - itemsGroup.current.children.forEach((item: THREE.Group) => { + itemsGroup.current?.children.forEach((item: THREE.Group) => { if (item.uuid === data.data.modeluuid) { item.position.set(...data.data.position as [number, number, number]); item.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); diff --git a/app/src/modules/collaboration/users/Avatar.tsx b/app/src/modules/collaboration/users/Avatar.tsx index f08a545..d3e5dca 100644 --- a/app/src/modules/collaboration/users/Avatar.tsx +++ b/app/src/modules/collaboration/users/Avatar.tsx @@ -5,15 +5,15 @@ import { getAvatarColor } from "./functions/getAvatarColor"; interface AvatarProps { name: string; // Name can be a full name or initials size?: number; - index?: number; textColor?: string; + color?: string; // Optional color prop for future use } const CustomAvatar: React.FC = ({ name, size = 100, - index = 0, textColor = "#ffffff", + color, // Optional color prop for future use }) => { const [imageSrc, setImageSrc] = useState(null); @@ -26,7 +26,7 @@ const CustomAvatar: React.FC = ({ const initials = getInitials(name); // Convert name to initials if needed // Draw background - ctx.fillStyle = getAvatarColor(index); + ctx.fillStyle = color || "#323232"; // Use color prop or generate color based on index ctx.fillRect(0, 0, size, size); // Draw initials diff --git a/app/src/modules/collaboration/users/functions/getAvatarColor.ts b/app/src/modules/collaboration/users/functions/getAvatarColor.ts index 3deacca..f2bd816 100644 --- a/app/src/modules/collaboration/users/functions/getAvatarColor.ts +++ b/app/src/modules/collaboration/users/functions/getAvatarColor.ts @@ -1,26 +1,67 @@ const avatarColors: string[] = [ - "#FF5733", // Red Orange + "#FF5733", // Vivid Orange "#48ac2a", // Leaf Green - "#0050eb", // Royal Blue + "#0050eb", // Bright Blue "#FF33A1", // Hot Pink - "#FF8C33", // Deep Orange - "#8C33FF", // Violet - "#FF3333", // Bright Red + "#FF8C33", // Sunset Orange + "#8C33FF", // Violet Purple + "#FF3333", // Fiery Red "#43c06d", // Emerald Green - "#A133FF", // Amethyst Purple - "#C70039", // Crimson - "#900C3F", // Maroon - "#581845", // Plum - "#3498DB", // Sky Blue - "#2ECC71", // Green Mint - "#E74C3C", // Tomato Red - "#00adff", // Azure - "#DBAD05", // Amber Yellow - "#FF5733", // Red Orange - "#FF33A1", // Hot Pink - "#900C3F", // Maroon + "#A133FF", // Royal Purple + "#C70039", // Crimson Red + "#900C3F", // Deep Burgundy + "#581845", // Plum Purple + "#3859AD", // Steel Blue + "#08873E", // Forest Green + "#E74C3C", // Cherry Red + "#00adff", // Sky Blue + "#DBAD05", // Golden Yellow + "#A13E31", // Brick Red + "#94C40E", // Lime Green + "#060C47", // Midnight Blue + "#2FAFAF", // Teal ]; -export function getAvatarColor(index: number): string { +export function getAvatarColor(index: number, name?: string): string { + // Check if the color is already stored in localStorage + const localStorageKey = "userAvatarColors"; + // Helper function to check if local storage is available + function isLocalStorageAvailable(): boolean { + try { + const testKey = "__test__"; + localStorage.setItem(testKey, "test"); + localStorage.removeItem(testKey); + return true; + } catch (e) { + return false; + } + } + // Check if local storage is available + if (isLocalStorageAvailable() && name) { + let userColors = JSON.parse(localStorage.getItem(localStorageKey) || "{}"); + + // Check if the user already has an assigned color + if (userColors[name]) { + return userColors[name]; + } + + // Find a new color not already assigned + const usedColors = Object.values(userColors); + const availableColors = avatarColors.filter(color => !usedColors.includes(color)); + + // Assign a new color + const assignedColor = availableColors.length > 0 + ? availableColors[0] + : avatarColors[index % avatarColors.length]; + + userColors[name] = assignedColor; + + // Save back to local storage + localStorage.setItem(localStorageKey, JSON.stringify(userColors)); + + return assignedColor; + } + + // Fallback: Assign a color using the index if no name or local storage is unavailable return avatarColors[index % avatarColors.length]; } diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts index 5dd41de..ea5ea74 100644 --- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts @@ -12,7 +12,7 @@ import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAss async function loadInitialFloorItems( itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void + setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => void ): Promise { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -71,7 +71,7 @@ async function loadInitialFloorItems( const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { // console.log(`[Cache] Fetching ${item.modelname}`); - processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); + processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); return; @@ -82,16 +82,14 @@ async function loadInitialFloorItems( if (indexedDBModel) { // console.log(`[IndexedDB] Fetching ${item.modelname}`); const blobUrl = URL.createObjectURL(indexedDBModel); - loader.load( - blobUrl, - (gltf) => { - URL.revokeObjectURL(blobUrl); - THREE.Cache.remove(blobUrl); - THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); - modelsLoaded++; - checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); - }, + loader.load(blobUrl, (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(item.modelfileID!, gltf); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates); + modelsLoaded++; + checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); + }, undefined, (error) => { toast.error(`[IndexedDB] Error loading ${item.modelname}:`); @@ -104,17 +102,15 @@ async function loadInitialFloorItems( // Fetch from Backend // console.log(`[Backend] Fetching ${item.modelname}`); - const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`; - loader.load( - modelUrl, - async (gltf) => { - const modelBlob = await fetch(modelUrl).then((res) => res.blob()); - await storeGLTF(item.modelfileID!, modelBlob); - THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); - modelsLoaded++; - checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); - }, + const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`; + loader.load(modelUrl, async (gltf) => { + const modelBlob = await fetch(modelUrl).then((res) => res.blob()); + await storeGLTF(item.modelfileID!, modelBlob); + THREE.Cache.add(item.modelfileID!, gltf); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates); + modelsLoaded++; + checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); + }, undefined, (error) => { toast.error(`[Backend] Error loading ${item.modelname}:`); @@ -138,7 +134,7 @@ async function loadInitialFloorItems( ]); if (item.eventData) { - processEventData(item, setSimulationPaths); + processEventData(item, setSimulationStates); } modelsLoaded++; @@ -157,7 +153,7 @@ function processLoadedModel( item: Types.EventData, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void + setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => void ) { const model = gltf; model.uuid = item.modeluuid; @@ -193,14 +189,14 @@ function processLoadedModel( ]); if (item.eventData) { - processEventData(item, setSimulationPaths); + processEventData(item, setSimulationStates); } gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } -function processEventData(item: Types.EventData, setSimulationPaths: any) { +function processEventData(item: Types.EventData, setSimulationStates: any) { if (item.eventData?.type === 'Conveyor') { @@ -210,23 +206,50 @@ function processEventData(item: Types.EventData, setSimulationPaths: any) { data.position = item.position; data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), data as Types.ConveyorEventsSchema ]); - } else { + } else if (item.eventData?.type === 'Vehicle') { const data: any = item.eventData; data.modeluuid = item.modeluuid; data.modelName = item.modelname; data.position = item.position; + data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), data as Types.VehicleEventsSchema ]); + } else if (item.eventData?.type === 'StaticMachine') { + + const data: any = item.eventData; + data.modeluuid = item.modeluuid; + data.modelName = item.modelname; + data.position = item.position; + data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + data as Types.StaticMachineEventsSchema + ]); + + } else if (item.eventData?.type === 'ArmBot') { + + const data: any = item.eventData; + data.modeluuid = item.modeluuid; + data.modelName = item.modelname; + data.position = item.position; + data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + data as Types.ArmBotEventsSchema + ]); + } } diff --git a/app/src/modules/scene/controls/selection/boundingBoxHelper.tsx b/app/src/modules/scene/controls/selection/boundingBoxHelper.tsx index ebc3761..dd9d251 100644 --- a/app/src/modules/scene/controls/selection/boundingBoxHelper.tsx +++ b/app/src/modules/scene/controls/selection/boundingBoxHelper.tsx @@ -44,11 +44,13 @@ const BoundingBox = ({ boundingBoxRef }: any) => { }; }, [selectedAssets]); + const savedTheme: string | null = localStorage.getItem("theme") || "light"; + return ( <> {points.length > 0 && ( <> - + diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index abcc1b0..5af10c0 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; @@ -10,7 +10,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore() @@ -151,7 +151,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -181,9 +181,12 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas uuid: THREE.MathUtils.generateUUID() })) : [defaultAction], - triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers, + triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers.map(trigger => ({ + ...trigger, + uuid: THREE.MathUtils.generateUUID() + })), connections: { - source: { pathUUID: obj.uuid, pointUUID }, + source: { modelUUID: obj.uuid, pointUUID }, targets: [] } }; @@ -224,17 +227,17 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.ConveyorEventsSchema ]); @@ -260,6 +263,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas return { uuid: pointUUID, position: vehiclePoint?.position, + rotation: vehiclePoint?.rotation, actions: hasActions ? { ...vehiclePoint.actions, @@ -277,7 +281,6 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas const backendEventData = { type: 'Vehicle', points: createVehiclePoint(), - speed: (eventData as Types.VehicleEventsSchema)?.points.speed }; // API @@ -305,22 +308,214 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.VehicleEventsSchema ]); socket.emit("v2:model-asset:add", data); + } else if (eventData.type === 'StaticMachine' && eventData) { + const createStaticMachinePoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.StaticMachineEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + buffer: 0, + material: 'Inherit', + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + rotation: vehiclePoint?.rotation, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + } + }; + }; + + const backendEventData = { + type: 'StaticMachine', + points: createStaticMachinePoint() + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.StaticMachineEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'ArmBot' && eventData) { + const createArmBotPoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.ArmBotEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + buffer: 0, + material: 'Inherit', + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + rotation: vehiclePoint?.rotation, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + triggers: { + uuid: THREE.MathUtils.generateUUID(), + name: vehiclePoint.triggers.name, + type: vehiclePoint.triggers.type, + }, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + } + }; + }; + + const backendEventData = { + type: 'ArmBot', + points: createArmBotPoint() + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.ArmBotEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else { + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + } } else { @@ -355,6 +550,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas } + obj.userData.modeluuid = newFloorItem.modeluuid; itemsGroupRef.current.add(obj); } }); diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index c28c401..2964181 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; @@ -11,7 +11,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); @@ -132,7 +132,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -163,9 +163,12 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb uuid: THREE.MathUtils.generateUUID() })) : [defaultAction], - triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers, + triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers.map(trigger => ({ + ...trigger, + uuid: THREE.MathUtils.generateUUID() + })), connections: { - source: { pathUUID: obj.uuid, pointUUID }, + source: { modelUUID: newFloorItem.modeluuid, pointUUID }, targets: [] } }; @@ -174,9 +177,9 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb const backendEventData = { type: 'Conveyor', points: [ - createConveyorPoint(0), // point1 - createConveyorPoint(1), // middlePoint - createConveyorPoint(2) // point2 + createConveyorPoint(0), + createConveyorPoint(1), + createConveyorPoint(2) ], speed: (eventData as Types.ConveyorEventsSchema)?.speed }; @@ -206,17 +209,17 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.ConveyorEventsSchema ]); @@ -242,6 +245,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb return { uuid: pointUUID, position: vehiclePoint?.position, + rotation: vehiclePoint?.rotation, actions: hasActions ? { ...vehiclePoint.actions, @@ -286,22 +290,214 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.VehicleEventsSchema ]); socket.emit("v2:model-asset:add", data); + } else if (eventData.type === 'StaticMachine' && eventData) { + const createStaticMachinePoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.StaticMachineEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + buffer: 0, + material: 'Inherit', + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + rotation: vehiclePoint?.rotation, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + } + }; + }; + + const backendEventData = { + type: 'StaticMachine', + points: createStaticMachinePoint() + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.StaticMachineEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'ArmBot' && eventData) { + const createArmBotPoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.ArmBotEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + buffer: 0, + material: 'Inherit', + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + rotation: vehiclePoint?.rotation, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + triggers: { + uuid: THREE.MathUtils.generateUUID(), + name: vehiclePoint.triggers.name, + type: vehiclePoint.triggers.type, + }, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + } + }; + }; + + const backendEventData = { + type: 'ArmBot', + points: createArmBotPoint() + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.ArmBotEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else { + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + } } else { @@ -336,6 +532,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb } + obj.userData.modeluuid = newFloorItem.modeluuid; itemsGroupRef.current.add(obj); } }); diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx index a4340b5..2a11c53 100644 --- a/app/src/modules/scene/controls/selection/moveControls.tsx +++ b/app/src/modules/scene/controls/selection/moveControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -12,7 +12,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); @@ -180,7 +180,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -205,7 +205,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje // obj.userData.modelId, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); //SOCKET @@ -219,17 +219,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -257,7 +257,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points } // ); //SOCKET @@ -271,16 +271,123 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'StaticMachine' && eventData) { + + const backendEventData = { + type: 'StaticMachine', + points: eventData.points, + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'ArmBot' && eventData) { + + const backendEventData = { + type: 'ArmBot', + points: eventData.points, + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } diff --git a/app/src/modules/scene/controls/selection/rotateControls.tsx b/app/src/modules/scene/controls/selection/rotateControls.tsx index 708a00a..90143ca 100644 --- a/app/src/modules/scene/controls/selection/rotateControls.tsx +++ b/app/src/modules/scene/controls/selection/rotateControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -13,7 +13,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); @@ -184,7 +184,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -209,7 +209,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); //SOCKET @@ -223,17 +223,17 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -262,7 +262,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points } // ); //SOCKET @@ -276,16 +276,123 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'StaticMachine' && eventData) { + + const backendEventData = { + type: 'StaticMachine', + points: eventData.points, + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'ArmBot' && eventData) { + + const backendEventData = { + type: 'ArmBot', + points: eventData.points, + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -299,7 +406,6 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo } } else { - //REST // await setFloorItemApi( diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index fd7e19e..11c2dfb 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; import { SelectionHelper } from "./selectionHelper"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; import BoundingBox from "./boundingBoxHelper"; import { toast } from "react-toastify"; // import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi'; @@ -20,7 +20,7 @@ const SelectionControls: React.FC = () => { const itemsGroupRef = useRef(undefined); const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); - const { setSimulationPaths } = useSimulationPaths(); + const { setSimulationStates } = useSimulationStates(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const [movedObjects, setMovedObjects] = useState([]); const [rotatedObjects, setRotatedObjects] = useState([]); @@ -122,10 +122,15 @@ const SelectionControls: React.FC = () => { if (!toggleView && activeModule === "builder") { helper.enabled = true; - canvasElement.addEventListener("pointerdown", onPointerDown); - canvasElement.addEventListener("pointermove", onPointerMove); + if (duplicatedObjects.length === 0 && pastedObjects.length === 0) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + } else { + helper.enabled = false; + helper.dispose(); + } canvasElement.addEventListener("contextmenu", onContextMenu); - canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); } else { helper.enabled = false; @@ -240,7 +245,7 @@ const SelectionControls: React.FC = () => { } }); - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== selectedMesh.uuid); return updatedEvents; }); diff --git a/app/src/modules/scene/controls/transformControls.tsx b/app/src/modules/scene/controls/transformControls.tsx index 0f0aaf0..2e586cc 100644 --- a/app/src/modules/scene/controls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls.tsx @@ -1,6 +1,6 @@ import { TransformControls } from "@react-three/drei"; import * as THREE from "three"; -import { useselectedFloorItem, useObjectPosition, useObjectScale, useObjectRotation, useTransformMode, useFloorItems, useSocketStore, useActiveTool } from "../../../store/store"; +import { useSelectedFloorItem, useObjectPosition, useObjectScale, useObjectRotation, useTransformMode, useFloorItems, useSocketStore, useActiveTool } from "../../../store/store"; import { useThree } from "@react-three/fiber"; import * as Types from '../../../types/world/worldTypes'; @@ -8,7 +8,7 @@ import { useEffect } from "react"; export default function TransformControl() { const state = useThree(); - const { selectedFloorItem, setselectedFloorItem } = useselectedFloorItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { objectPosition, setObjectPosition } = useObjectPosition(); const { objectScale, setObjectScale } = useObjectScale(); const { objectRotation, setObjectRotation } = useObjectRotation(); @@ -96,7 +96,7 @@ export default function TransformControl() { const target = (state.controls as any).getTarget(new THREE.Vector3()); (state.controls as any).setTarget(target.x, 0, target.z, true); } - setselectedFloorItem(null); + setSelectedFloorItem(null); { setObjectPosition({ x: undefined, y: undefined, z: undefined }); setObjectScale({ x: undefined, y: undefined, z: undefined }); diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 30d8a8f..acae6e3 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -6,7 +6,7 @@ import { useSelectedActionSphere, useSelectedPath, useSelectedWallItem, - useselectedFloorItem, + useSelectedFloorItem, } from "../../../store/store"; import * as Types from "../../../types/world/worldTypes"; import * as CONSTANTS from "../../../types/world/worldConstants"; @@ -15,7 +15,7 @@ import { useEffect } from "react"; export default function PostProcessing() { const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); - const { selectedFloorItem, setselectedFloorItem } = useselectedFloorItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { selectedActionSphere } = useSelectedActionSphere(); const { selectedPath } = useSelectedPath(); 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/modules/scene/tools/measurementTool.tsx b/app/src/modules/scene/tools/measurementTool.tsx index f8054af..9f9fa03 100644 --- a/app/src/modules/scene/tools/measurementTool.tsx +++ b/app/src/modules/scene/tools/measurementTool.tsx @@ -1,190 +1,244 @@ -import * as THREE from 'three'; -import { useEffect, useRef, useState } from 'react'; -import { useThree, useFrame } from '@react-three/fiber'; -import { useToolMode } from '../../../store/store'; -import { Html } from '@react-three/drei'; +import * as THREE from "three"; +import { useEffect, useRef, useState } from "react"; +import { useThree, useFrame } from "@react-three/fiber"; +import { useToolMode } from "../../../store/store"; +import { Html } from "@react-three/drei"; const MeasurementTool = () => { - const { gl, raycaster, pointer, camera, scene } = useThree(); - const { toolMode } = useToolMode(); + const { gl, raycaster, pointer, camera, scene } = useThree(); + const { toolMode } = useToolMode(); - const [points, setPoints] = useState([]); - const [tubeGeometry, setTubeGeometry] = useState(null); - const groupRef = useRef(null); - const [startConePosition, setStartConePosition] = useState(null); - const [endConePosition, setEndConePosition] = useState(null); - const [startConeQuaternion, setStartConeQuaternion] = useState(new THREE.Quaternion()); - const [endConeQuaternion, setEndConeQuaternion] = useState(new THREE.Quaternion()); - const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 }); + const [points, setPoints] = useState([]); + const [tubeGeometry, setTubeGeometry] = useState( + null + ); + const groupRef = useRef(null); + const [startConePosition, setStartConePosition] = + useState(null); + const [endConePosition, setEndConePosition] = useState( + null + ); + const [startConeQuaternion, setStartConeQuaternion] = useState( + new THREE.Quaternion() + ); + const [endConeQuaternion, setEndConeQuaternion] = useState( + new THREE.Quaternion() + ); + const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 }); + const MIN_RADIUS = 0.001, + MAX_RADIUS = 0.1; + const MIN_CONE_RADIUS = 0.01, + MAX_CONE_RADIUS = 0.4; + const MIN_CONE_HEIGHT = 0.035, + MAX_CONE_HEIGHT = 2.0; - const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1; - const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4; - const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0; + useEffect(() => { + const canvasElement = gl.domElement; + let drag = false; + let isLeftMouseDown = false; - useEffect(() => { - const canvasElement = gl.domElement; - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = () => { - isLeftMouseDown = true; - drag = false; - }; - - const onMouseUp = (evt: any) => { - isLeftMouseDown = false; - if (evt.button === 0 && !drag) { - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(scene.children, true).filter(intersect => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("agv-collider") && !(intersect.object.type === "GridHelper")); - - if (intersects.length > 0) { - const intersectionPoint = intersects[0].point.clone(); - if (points.length < 2) { - setPoints([...points, intersectionPoint]); - } else { - setPoints([intersectionPoint]); - } - } - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) drag = true; - }; - - const onContextMenu = (evt: any) => { - evt.preventDefault(); - if (!drag) { - evt.preventDefault(); - setPoints([]); - setTubeGeometry(null); - } - }; - - if (toolMode === "MeasurementScale") { - canvasElement.addEventListener("pointerdown", onMouseDown); - canvasElement.addEventListener("pointermove", onMouseMove); - canvasElement.addEventListener("pointerup", onMouseUp); - canvasElement.addEventListener("contextmenu", onContextMenu); - } else { - resetMeasurement(); - setPoints([]); - } - - return () => { - canvasElement.removeEventListener("pointerdown", onMouseDown); - canvasElement.removeEventListener("pointermove", onMouseMove); - canvasElement.removeEventListener("pointerup", onMouseUp); - canvasElement.removeEventListener("contextmenu", onContextMenu); - }; - }, [toolMode, camera, raycaster, pointer, scene, points]); - - useFrame(() => { - if (points.length === 1) { - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(scene.children, true).filter(intersect => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("agv-collider") && !(intersect.object.type === "GridHelper")); - - if (intersects.length > 0) { - updateMeasurement(points[0], intersects[0].point); - } - } else if (points.length === 2) { - updateMeasurement(points[0], points[1]); - } else { - resetMeasurement(); - } - }); - - const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { - const distance = start.distanceTo(end); - - const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS); - const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS); - const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT); - - setConeSize({ radius: coneRadius, height: coneHeight }); - - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - - const offset = direction.clone().multiplyScalar(coneHeight * 0.5); - - let tubeStart = start.clone().add(offset); - let tubeEnd = end.clone().sub(offset); - - tubeStart.y = Math.max(tubeStart.y, 0); - tubeEnd.y = Math.max(tubeEnd.y, 0); - - const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]); - setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false)); - - setStartConePosition(tubeStart); - setEndConePosition(tubeEnd); - setStartConeQuaternion(getArrowOrientation(start, end)); - setEndConeQuaternion(getArrowOrientation(end, start)); + const onMouseDown = () => { + isLeftMouseDown = true; + drag = false; }; - const resetMeasurement = () => { + const onMouseUp = (evt: any) => { + isLeftMouseDown = false; + if (evt.button === 0 && !drag) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster + .intersectObjects(scene.children, true) + .filter( + (intersect) => + !intersect.object.name.includes("Roof") && + !intersect.object.name.includes("MeasurementReference") && + !intersect.object.name.includes("agv-collider") && + !(intersect.object.type === "GridHelper") + ); + + if (intersects.length > 0) { + const intersectionPoint = intersects[0].point.clone(); + if (points.length < 2) { + setPoints([...points, intersectionPoint]); + } else { + setPoints([intersectionPoint]); + } + } + } + }; + + const onMouseMove = () => { + if (isLeftMouseDown) drag = true; + }; + + const onContextMenu = (evt: any) => { + evt.preventDefault(); + if (!drag) { + evt.preventDefault(); + setPoints([]); setTubeGeometry(null); - setStartConePosition(null); - setEndConePosition(null); + } }; - const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => { - const direction = new THREE.Vector3().subVectors(end, start).normalize().negate(); - const quaternion = new THREE.Quaternion(); - quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction); - return quaternion; + if (toolMode === "MeasurementScale") { + canvasElement.addEventListener("pointerdown", onMouseDown); + canvasElement.addEventListener("pointermove", onMouseMove); + canvasElement.addEventListener("pointerup", onMouseUp); + canvasElement.addEventListener("contextmenu", onContextMenu); + } else { + resetMeasurement(); + setPoints([]); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onMouseDown); + canvasElement.removeEventListener("pointermove", onMouseMove); + canvasElement.removeEventListener("pointerup", onMouseUp); + canvasElement.removeEventListener("contextmenu", onContextMenu); }; + }, [toolMode, camera, raycaster, pointer, scene, points]); - useEffect(() => { - if (points.length === 2) { - console.log(points[0].distanceTo(points[1])); - } - }, [points]) + useFrame(() => { + if (points.length === 1) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster + .intersectObjects(scene.children, true) + .filter( + (intersect) => + !intersect.object.name.includes("Roof") && + !intersect.object.name.includes("MeasurementReference") && + !intersect.object.name.includes("agv-collider") && + !(intersect.object.type === "GridHelper") + ); - return ( - - {startConePosition && ( - - - - - )} - {endConePosition && ( - - - - - )} - {tubeGeometry && ( - - - - )} + if (intersects.length > 0) { + updateMeasurement(points[0], intersects[0].point); + } + } else if (points.length === 2) { + updateMeasurement(points[0], points[1]); + } else { + resetMeasurement(); + } + }); - {startConePosition && endConePosition && ( - -
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
- - )} -
+ const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { + const distance = start.distanceTo(end); + + const radius = THREE.MathUtils.clamp( + distance * 0.02, + MIN_RADIUS, + MAX_RADIUS + ); + const coneRadius = THREE.MathUtils.clamp( + distance * 0.05, + MIN_CONE_RADIUS, + MAX_CONE_RADIUS + ); + const coneHeight = THREE.MathUtils.clamp( + distance * 0.2, + MIN_CONE_HEIGHT, + MAX_CONE_HEIGHT ); + setConeSize({ radius: coneRadius, height: coneHeight }); + + const direction = new THREE.Vector3().subVectors(end, start).normalize(); + + const offset = direction.clone().multiplyScalar(coneHeight * 0.5); + + let tubeStart = start.clone().add(offset); + let tubeEnd = end.clone().sub(offset); + + tubeStart.y = Math.max(tubeStart.y, 0); + tubeEnd.y = Math.max(tubeEnd.y, 0); + + const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]); + setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false)); + + setStartConePosition(tubeStart); + setEndConePosition(tubeEnd); + setStartConeQuaternion(getArrowOrientation(start, end)); + setEndConeQuaternion(getArrowOrientation(end, start)); + }; + + const resetMeasurement = () => { + setTubeGeometry(null); + setStartConePosition(null); + setEndConePosition(null); + }; + + const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => { + const direction = new THREE.Vector3() + .subVectors(end, start) + .normalize() + .negate(); + const quaternion = new THREE.Quaternion(); + quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction); + return quaternion; + }; + + useEffect(() => { + if (points.length === 2) { + console.log(points[0].distanceTo(points[1])); + } + }, [points]); + + return ( + + {startConePosition && ( + + + + + )} + {endConePosition && ( + + + + + )} + {tubeGeometry && ( + + + + )} + + {startConePosition && endConePosition && ( + +
+ {startConePosition.distanceTo(endConePosition).toFixed(2)} m +
+ + )} +
+ ); }; export default MeasurementTool; diff --git a/app/src/modules/scene/world/world.tsx b/app/src/modules/scene/world/world.tsx index 1b4f71f..df7f9c0 100644 --- a/app/src/modules/scene/world/world.tsx +++ b/app/src/modules/scene/world/world.tsx @@ -6,8 +6,8 @@ import { useThree, useFrame } from "@react-three/fiber"; ////////// Component Imports ////////// -import DistanceText from "../../builder/geomentries/lines/distanceText"; -import ReferenceDistanceText from "../../builder/geomentries/lines/referenceDistanceText"; +import DistanceText from "../../builder/geomentries/lines/distanceText/distanceText"; +import ReferenceDistanceText from "../../builder/geomentries/lines/distanceText/referenceDistanceText"; ////////// Assests Imports ////////// @@ -53,8 +53,8 @@ import { findEnvironment } from "../../../services/factoryBuilder/environment/fi import Layer2DVisibility from "../../builder/geomentries/layers/layer2DVisibility"; import DrieHtmlTemp from "../mqttTemp/drieHtmlTemp"; import ZoneGroup from "../../builder/groups/zoneGroup"; -import Agv from "../../builder/agv/agv"; import useModuleStore from "../../../store/useModuleStore"; +import NavMeshCreator from "../../builder/agv/navMeshCreator"; export default function World() { const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -368,7 +368,7 @@ export default function World() { {/* */} - {activeModule === "simulation" && } + ); diff --git a/app/src/modules/simulation/behaviour/behaviour.tsx b/app/src/modules/simulation/behaviour/behaviour.tsx index 27c791f..0ddc3aa 100644 --- a/app/src/modules/simulation/behaviour/behaviour.tsx +++ b/app/src/modules/simulation/behaviour/behaviour.tsx @@ -1,14 +1,14 @@ -import { useFloorItems, useSimulationPaths } from '../../../store/store'; +import { useFloorItems, useSimulationStates } from '../../../store/store'; import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; import { useEffect } from 'react'; function Behaviour() { - const { setSimulationPaths } = useSimulationPaths(); + const { setSimulationStates } = useSimulationStates(); const { floorItems } = useFloorItems(); useEffect(() => { - const newPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] = []; + const newPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[] = []; // floorItems.forEach((item: Types.FloorItemType) => { // if (item.modelfileID === "672a090f80d91ac979f4d0bd") { @@ -31,7 +31,7 @@ function Behaviour() { // rotation: [0, 0, 0], // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], // triggers: [], - // connections: { source: { pathUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] }, // }, // { // uuid: middlePointUUID, @@ -39,7 +39,7 @@ function Behaviour() { // rotation: [0, 0, 0], // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], // triggers: [], - // connections: { source: { pathUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] }, // }, // { // uuid: point2UUID, @@ -47,7 +47,7 @@ function Behaviour() { // rotation: [0, 0, 0], // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], // triggers: [], - // connections: { source: { pathUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] }, // }, // ], // position: [...item.position], @@ -68,7 +68,7 @@ function Behaviour() { // uuid: pointUUID, // position: [pointPosition.x, pointPosition.y, pointPosition.z], // actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, - // connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, // speed: 2, // }, // position: [...item.position], @@ -78,7 +78,7 @@ function Behaviour() { // } // }); - // setSimulationPaths(newPaths); + // setSimulationStates(newPaths); // console.log('floorItems: ', floorItems); }, [floorItems]); diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index 3866a10..c561cbb 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -1,49 +1,46 @@ import { useFrame, useThree } from '@react-three/fiber'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; import { QuadraticBezierLine } from '@react-three/drei'; -import { useIsConnecting, useSimulationPaths } from '../../../store/store'; +import { useDeleteTool, useIsConnecting, useRenderDistance, useSimulationStates, useSocketStore } from '../../../store/store'; import useModuleStore from '../../../store/useModuleStore'; import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; +import { setEventApi } from '../../../services/factoryBuilder/assest/floorAsset/setEventsApt'; function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject }) { const { activeModule } = useModuleStore(); const { gl, raycaster, scene, pointer, camera } = useThree(); + const { deleteTool } = useDeleteTool(); + const { renderDistance } = useRenderDistance(); const { setIsConnecting } = useIsConnecting(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { isPlaying } = usePlayButtonStore(); + const { socket } = useSocketStore(); + const groupRefs = useRef<{ [key: string]: any }>({}); - const [firstSelected, setFirstSelected] = useState<{ - pathUUID: string; - sphereUUID: string; - position: THREE.Vector3; - isCorner: boolean; - } | null>(null); + const [firstSelected, setFirstSelected] = useState<{ modelUUID: string; sphereUUID: string; position: THREE.Vector3; isCorner: boolean; } | null>(null); const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3, end: THREE.Vector3, mid: THREE.Vector3 } | null>(null); const [helperlineColor, setHelperLineColor] = useState('red'); + const [hoveredLineKey, setHoveredLineKey] = useState(null); - const updatePathConnections = ( - fromPathUUID: string, - fromPointUUID: string, - toPathUUID: string, - toPointUUID: string - ) => { - const updatedPaths = simulationPaths.map(path => { + const updatePathConnections = (fromModelUUID: string, fromPointUUID: string, toModelUUID: string, toPointUUID: string) => { + const updatedPaths = simulationStates.map(path => { if (path.type === 'Conveyor') { - if (path.modeluuid === fromPathUUID) { + // Handle outgoing connections from Conveyor + if (path.modeluuid === fromModelUUID) { return { ...path, points: path.points.map(point => { if (point.uuid === fromPointUUID) { const newTarget = { - pathUUID: toPathUUID, + modelUUID: toModelUUID, pointUUID: toPointUUID }; const existingTargets = point.connections.targets || []; if (!existingTargets.some(target => - target.pathUUID === newTarget.pathUUID && + target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID )) { return { @@ -59,19 +56,20 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }) }; } - else if (path.modeluuid === toPathUUID) { + // Handle incoming connections to Conveyor + else if (path.modeluuid === toModelUUID) { return { ...path, points: path.points.map(point => { if (point.uuid === toPointUUID) { const reverseTarget = { - pathUUID: fromPathUUID, + modelUUID: fromModelUUID, pointUUID: fromPointUUID }; const existingTargets = point.connections.targets || []; if (!existingTargets.some(target => - target.pathUUID === reverseTarget.pathUUID && + target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID )) { return { @@ -88,18 +86,17 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }; } } - // In the updatePathConnections function, modify the Vehicle handling section: else if (path.type === 'Vehicle') { // Handle outgoing connections from Vehicle - if (path.modeluuid === fromPathUUID && path.points.uuid === fromPointUUID) { + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { const newTarget = { - pathUUID: toPathUUID, + modelUUID: toModelUUID, pointUUID: toPointUUID }; const existingTargets = path.points.connections.targets || []; // Check if target is a Conveyor - const toPath = simulationPaths.find(p => p.modeluuid === toPathUUID); + const toPath = simulationStates.find(p => p.modeluuid === toModelUUID); if (toPath?.type !== 'Conveyor') { console.log("Vehicle can only connect to Conveyors"); return path; @@ -112,7 +109,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } if (!existingTargets.some(target => - target.pathUUID === newTarget.pathUUID && + target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID )) { return { @@ -128,15 +125,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } } // Handle incoming connections to Vehicle - else if (path.modeluuid === toPathUUID && path.points.uuid === toPointUUID) { + else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { const reverseTarget = { - pathUUID: fromPathUUID, + modelUUID: fromModelUUID, pointUUID: fromPointUUID }; const existingTargets = path.points.connections.targets || []; // Check if source is a Conveyor - const fromPath = simulationPaths.find(p => p.modeluuid === fromPathUUID); + const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); if (fromPath?.type !== 'Conveyor') { console.log("Vehicle can only connect to Conveyors"); return path; @@ -149,7 +146,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } if (!existingTargets.some(target => - target.pathUUID === reverseTarget.pathUUID && + target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID )) { return { @@ -166,14 +163,259 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } return path; } + else if (path.type === 'StaticMachine') { + // Handle outgoing connections from StaticMachine + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { + const newTarget = { + modelUUID: toModelUUID, + pointUUID: toPointUUID + }; + + // Ensure target is an ArmBot + const toPath = simulationStates.find(p => p.modeluuid === toModelUUID); + if (toPath?.type !== 'ArmBot') { + console.log("StaticMachine can only connect to ArmBot"); + return path; + } + + const existingTargets = path.points.connections.targets || []; + + // Allow only one connection + if (existingTargets.length >= 1) { + console.log("StaticMachine can only have one connection"); + return path; + } + + if (!existingTargets.some(target => + target.modelUUID === newTarget.modelUUID && + target.pointUUID === newTarget.pointUUID + )) { + return { + ...path, + points: { + ...path.points, + connections: { + ...path.points.connections, + targets: [...existingTargets, newTarget] + } + } + }; + } + } + + // Handle incoming connections to StaticMachine + else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { + const reverseTarget = { + modelUUID: fromModelUUID, + pointUUID: fromPointUUID + }; + + const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); + if (fromPath?.type !== 'ArmBot') { + console.log("StaticMachine can only be connected from ArmBot"); + return path; + } + + const existingTargets = path.points.connections.targets || []; + + if (existingTargets.length >= 1) { + console.log("StaticMachine can only have one connection"); + return path; + } + + if (!existingTargets.some(target => + target.modelUUID === reverseTarget.modelUUID && + target.pointUUID === reverseTarget.pointUUID + )) { + return { + ...path, + points: { + ...path.points, + connections: { + ...path.points.connections, + targets: [...existingTargets, reverseTarget] + } + } + }; + } + } + return path; + } + else if (path.type === 'ArmBot') { + // Handle outgoing connections from ArmBot + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { + const newTarget = { + modelUUID: toModelUUID, + pointUUID: toPointUUID + }; + + const toPath = simulationStates.find(p => p.modeluuid === toModelUUID); + if (!toPath) return path; + + const existingTargets = path.points.connections.targets || []; + + // Check if connecting to a StaticMachine and already connected to one + const alreadyConnectedToStatic = existingTargets.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + + if (toPath.type === 'StaticMachine') { + if (alreadyConnectedToStatic) { + console.log("ArmBot can only connect to one StaticMachine"); + return path; + } + } + + if (!existingTargets.some(target => + target.modelUUID === newTarget.modelUUID && + target.pointUUID === newTarget.pointUUID + )) { + return { + ...path, + points: { + ...path.points, + connections: { + ...path.points.connections, + targets: [...existingTargets, newTarget] + } + } + }; + } + } + + // Handle incoming connections to ArmBot + else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { + const reverseTarget = { + modelUUID: fromModelUUID, + pointUUID: fromPointUUID + }; + + const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); + if (!fromPath) return path; + + const existingTargets = path.points.connections.targets || []; + + const alreadyConnectedFromStatic = existingTargets.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + + if (fromPath.type === 'StaticMachine') { + if (alreadyConnectedFromStatic) { + console.log("ArmBot can only be connected from one StaticMachine"); + return path; + } + } + + if (!existingTargets.some(target => + target.modelUUID === reverseTarget.modelUUID && + target.pointUUID === reverseTarget.pointUUID + )) { + return { + ...path, + points: { + ...path.points, + connections: { + ...path.points.connections, + targets: [...existingTargets, reverseTarget] + } + } + }; + } + } + return path; + } + return path; }); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); + + const updatedPathDetails = updatedPaths.filter(path => + path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID + ); + + updateBackend(updatedPathDetails); }; - const handleAddConnection = (fromPathUUID: string, fromUUID: string, toPathUUID: string, toUUID: string) => { - updatePathConnections(fromPathUUID, fromUUID, toPathUUID, toUUID); + const updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + if (updatedPaths.length === 0) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + updatedPaths.forEach(async (updatedPath) => { + if (updatedPath.type === 'Conveyor') { + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } else if (updatedPath.type === 'Vehicle') { + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Vehicle", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Vehicle", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } else if (updatedPath.type === 'StaticMachine') { + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "StaticMachine", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "StaticMachine", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } else if (updatedPath.type === 'ArmBot') { + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "ArmBot", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "ArmBot", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } + }) + + } + + const handleAddConnection = (fromModelUUID: string, fromUUID: string, toModelUUID: string, toUUID: string) => { + updatePathConnections(fromModelUUID, fromUUID, toModelUUID, toUUID); setFirstSelected(null); setCurrentLine(null); setIsConnecting(false); @@ -210,7 +452,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const intersected = intersects[0].object; if (intersected.name.includes("events-sphere")) { - const pathUUID = intersected.userData.path.modeluuid; + const modelUUID = intersected.userData.path.modeluuid; const sphereUUID = intersected.uuid; const worldPosition = new THREE.Vector3(); intersected.getWorldPosition(worldPosition); @@ -226,9 +468,9 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec isStartOrEnd = sphereUUID === intersected.userData.path.points.uuid; } - if (pathUUID) { - const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected?.pathUUID); - const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID); + if (modelUUID) { + const firstPath = simulationStates.find(p => p.modeluuid === firstSelected?.modelUUID); + const secondPath = simulationStates.find(p => p.modeluuid === modelUUID); // Prevent vehicle-to-vehicle connections if (firstPath && secondPath && firstPath.type === 'Vehicle' && secondPath.type === 'Vehicle') { @@ -240,23 +482,23 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec if (firstPath && secondPath && firstPath.type === 'Conveyor' && secondPath.type === 'Conveyor' && - !firstSelected?.isCorner) { - console.log("Conveyor middle points can only connect to non-conveyor paths"); + (!firstSelected?.isCorner || !isStartOrEnd)) { + console.log("Conveyor connections must be between start/end points"); return; } // Check if this specific connection already exists const isDuplicateConnection = firstSelected - ? simulationPaths.some(path => { - if (path.modeluuid === firstSelected.pathUUID) { + ? simulationStates.some(path => { + if (path.modeluuid === firstSelected.modelUUID) { if (path.type === 'Conveyor') { const point = path.points.find(p => p.uuid === firstSelected.sphereUUID); return point?.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } else if (path.type === 'Vehicle') { return path.points.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } } @@ -271,7 +513,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For Vehicles, check if they're already connected to anything if (intersected.userData.path.type === 'Vehicle') { - console.log('intersected: ', intersected); const vehicleConnections = intersected.userData.path.points.connections.targets.length; if (vehicleConnections >= 1) { console.log("Vehicle can only have one connection"); @@ -281,7 +522,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For non-Vehicle paths, check if already connected if (intersected.userData.path.type !== 'Vehicle') { - const isAlreadyConnected = simulationPaths.some(path => { + const isAlreadyConnected = simulationStates.some(path => { if (path.type === 'Conveyor') { return path.points.some(point => point.uuid === sphereUUID && @@ -306,11 +547,74 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } // Prevent same-path connections - if (firstSelected.pathUUID === pathUUID) { + if (firstSelected.modelUUID === modelUUID) { console.log("Cannot connect spheres on the same path."); return; } + // Check if StaticMachine is involved in the connection + if ((firstPath?.type === 'StaticMachine' && secondPath?.type !== 'ArmBot') || + (secondPath?.type === 'StaticMachine' && firstPath?.type !== 'ArmBot')) { + console.log("StaticMachine can only connect to ArmBot"); + return; + } + + // Check if StaticMachine already has a connection + if (firstPath?.type === 'StaticMachine') { + const staticConnections = firstPath.points.connections.targets.length; + if (staticConnections >= 1) { + console.log("StaticMachine can only have one connection"); + return; + } + } + if (secondPath?.type === 'StaticMachine') { + const staticConnections = secondPath.points.connections.targets.length; + if (staticConnections >= 1) { + console.log("StaticMachine can only have one connection"); + return; + } + } + + // Check if ArmBot is involved + if ((firstPath?.type === 'ArmBot' && secondPath?.type === 'StaticMachine') || + (secondPath?.type === 'ArmBot' && firstPath?.type === 'StaticMachine')) { + + const armBotPath = firstPath?.type === 'ArmBot' ? firstPath : secondPath; + const staticPath = firstPath?.type === 'StaticMachine' ? firstPath : secondPath; + + const armBotConnections = armBotPath.points.connections.targets || []; + const alreadyConnectedToStatic = armBotConnections.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + + if (alreadyConnectedToStatic) { + console.log("ArmBot can only connect to one StaticMachine"); + return; + } + + const staticConnections = staticPath.points.connections.targets.length; + if (staticConnections >= 1) { + console.log("StaticMachine can only have one connection"); + return; + } + } + + // Prevent ArmBot ↔ ArmBot + if (firstPath?.type === 'ArmBot' && secondPath?.type === 'ArmBot') { + console.log("Cannot connect two ArmBots together"); + return; + } + + // If one is ArmBot, ensure the other is StaticMachine or Conveyor + if (firstPath?.type === 'ArmBot' || secondPath?.type === 'ArmBot') { + const otherType = firstPath?.type === 'ArmBot' ? secondPath?.type : firstPath?.type; + if (otherType !== 'StaticMachine' && otherType !== 'Conveyor') { + console.log("ArmBot can only connect to Conveyors or one StaticMachine"); + return; + } + } + // At least one must be start/end point if (!firstSelected.isCorner && !isStartOrEnd) { console.log("At least one of the selected spheres must be a start or end point."); @@ -318,20 +622,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } // All checks passed - make the connection - handleAddConnection( - firstSelected.pathUUID, - firstSelected.sphereUUID, - pathUUID, - sphereUUID - ); + handleAddConnection(firstSelected.modelUUID, firstSelected.sphereUUID, modelUUID, sphereUUID); } else { // First selection - just store it - setFirstSelected({ - pathUUID, - sphereUUID, - position: worldPosition, - isCorner: isStartOrEnd - }); + setFirstSelected({ modelUUID, sphereUUID, position: worldPosition, isCorner: isStartOrEnd }); setIsConnecting(true); } } @@ -344,7 +638,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } }; - if (activeModule === 'simulation') { + if (activeModule === 'simulation' && !deleteTool) { canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mousemove", onMouseMove); @@ -361,7 +655,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("contextmenu", onContextMenu); }; - }, [camera, scene, raycaster, firstSelected, simulationPaths]); + }, [camera, scene, raycaster, firstSelected, simulationStates, deleteTool]); + + useFrame(() => { + Object.values(groupRefs.current).forEach((group) => { + if (group) { + const distance = new THREE.Vector3(...group.position.toArray()).distanceTo(camera.position); + group.visible = ((distance <= renderDistance) && !isPlaying); + } + }); + }); useFrame(() => { if (firstSelected) { @@ -375,7 +678,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec ); let point: THREE.Vector3 | null = null; - let snappedSphere: { sphereUUID: string, position: THREE.Vector3, pathUUID: string, isCorner: boolean } | null = null; + let snappedSphere: { sphereUUID: string, position: THREE.Vector3, modelUUID: string, isCorner: boolean } | null = null; let isInvalidConnection = false; if (intersects.length > 0) { @@ -385,9 +688,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } } - const sphereIntersects = raycaster.intersectObjects(pathsGroupRef.current.children, true).filter((obj) => - obj.object.name.includes("events-sphere") - ); + const sphereIntersects = raycaster.intersectObjects(pathsGroupRef.current.children, true).filter((obj) => obj.object.name.includes("events-sphere")); if (sphereIntersects.length > 0) { const sphere = sphereIntersects[0].object; @@ -395,34 +696,40 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const spherePosition = new THREE.Vector3(); sphere.getWorldPosition(spherePosition); const pathData = sphere.userData.path; - const pathUUID = pathData.modeluuid; + const modelUUID = pathData.modeluuid; - const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected.pathUUID); - const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID); + const firstPath = simulationStates.find(p => p.modeluuid === firstSelected.modelUUID); + const secondPath = simulationStates.find(p => p.modeluuid === modelUUID); const isVehicleToVehicle = firstPath?.type === 'Vehicle' && secondPath?.type === 'Vehicle'; // Inside the useFrame hook, where we check for snapped spheres: - const isConnectable = (pathData.type === 'Vehicle' || + const isConnectable = ( + pathData.type === 'Vehicle' || + pathData.type === 'ArmBot' || (pathData.points.length > 0 && ( sphereUUID === pathData.points[0].uuid || - sphereUUID === pathData.points[pathData.points.length - 1].uuid - ))) && + sphereUUID === pathData.points[pathData.points.length - 1].uuid || + (pathData.type === 'Conveyor' && firstPath?.type === 'ArmBot') // Allow ArmBot to connect to middle points + )) + ) && !isVehicleToVehicle && - !(firstPath?.type === 'Conveyor' && + !( + firstPath?.type === 'Conveyor' && pathData.type === 'Conveyor' && - !firstSelected.isCorner); + !firstSelected.isCorner + ); // Check for duplicate connection (regardless of path type) - const isDuplicateConnection = simulationPaths.some(path => { - if (path.modeluuid === firstSelected.pathUUID) { + const isDuplicateConnection = simulationStates.some(path => { + if (path.modeluuid === firstSelected.modelUUID) { if (path.type === 'Conveyor') { const point = path.points.find(p => p.uuid === firstSelected.sphereUUID); return point?.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } else if (path.type === 'Vehicle') { return path.points.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } } @@ -431,7 +738,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For non-Vehicle paths, check if already connected const isNonVehicleAlreadyConnected = pathData.type !== 'Vehicle' && - simulationPaths.some(path => { + simulationStates.some(path => { if (path.type === 'Conveyor') { return path.points.some(point => point.uuid === sphereUUID && @@ -448,25 +755,67 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec (firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') || (secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor'); + // Check if StaticMachine is connecting to non-ArmBot + const isStaticMachineToNonArmBot = + (firstPath?.type === 'StaticMachine' && secondPath?.type !== 'ArmBot') || + (secondPath?.type === 'StaticMachine' && firstPath?.type !== 'ArmBot'); + + // Check if StaticMachine already has a connection + const isStaticMachineAtMaxConnections = + (firstPath?.type === 'StaticMachine' && firstPath.points.connections.targets.length >= 1) || + (secondPath?.type === 'StaticMachine' && secondPath.points.connections.targets.length >= 1); + + // Check if ArmBot is connecting to StaticMachine + const isArmBotToStaticMachine = + (firstPath?.type === 'ArmBot' && secondPath?.type === 'StaticMachine') || + (secondPath?.type === 'ArmBot' && firstPath?.type === 'StaticMachine'); + + // Prevent multiple StaticMachine connections to ArmBot + let isArmBotAlreadyConnectedToStatic = false; + if (isArmBotToStaticMachine) { + const armBotPath = firstPath?.type === 'ArmBot' ? firstPath : secondPath; + isArmBotAlreadyConnectedToStatic = armBotPath.points.connections.targets.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + } + + // Prevent ArmBot to ArmBot + const isArmBotToArmBot = firstPath?.type === 'ArmBot' && secondPath?.type === 'ArmBot'; + + // If ArmBot is involved, other must be Conveyor or StaticMachine + const isArmBotToInvalidType = (firstPath?.type === 'ArmBot' || secondPath?.type === 'ArmBot') && + !(firstPath?.type === 'Conveyor' || firstPath?.type === 'StaticMachine' || + secondPath?.type === 'Conveyor' || secondPath?.type === 'StaticMachine'); + if ( !isDuplicateConnection && !isVehicleToVehicle && !isNonVehicleAlreadyConnected && !isVehicleAtMaxConnections && !isVehicleConnectingToNonConveyor && + !isStaticMachineToNonArmBot && + !isStaticMachineAtMaxConnections && + !isArmBotToArmBot && + !isArmBotToInvalidType && + !isArmBotAlreadyConnectedToStatic && firstSelected.sphereUUID !== sphereUUID && - firstSelected.pathUUID !== pathUUID && - (firstSelected.isCorner || isConnectable) + firstSelected.modelUUID !== modelUUID && + (firstSelected.isCorner || isConnectable) && + !(firstPath?.type === 'Conveyor' && + pathData.type === 'Conveyor' && + !(firstSelected.isCorner && isConnectable)) ) { snappedSphere = { sphereUUID, position: spherePosition, - pathUUID, + modelUUID, isCorner: isConnectable }; } else { isInvalidConnection = true; } + } if (snappedSphere) { @@ -503,14 +852,144 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } }); + const removeConnections = (connection1: { model: string; point: string }, connection2: { model: string; point: string }) => { + const updatedStates = simulationStates.map(state => { + // Handle Conveyor (which has multiple points) + if (state.type === 'Conveyor') { + const updatedConveyor: Types.ConveyorEventsSchema = { + ...state, + points: state.points.map(point => { + // Check if this point is either connection1 or connection2 + if ((state.modeluuid === connection1.model && point.uuid === connection1.point) || + (state.modeluuid === connection2.model && point.uuid === connection2.point)) { + + return { + ...point, + connections: { + ...point.connections, + targets: point.connections.targets.filter(target => { + // Remove the target that matches the other connection + return !( + (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || + (target.modelUUID === connection2.model && target.pointUUID === connection2.point) + ); + }) + } + }; + } + return point; + }) + }; + return updatedConveyor; + } + // Handle Vehicle + else if (state.type === 'Vehicle') { + if ((state.modeluuid === connection1.model && state.points.uuid === connection1.point) || + (state.modeluuid === connection2.model && state.points.uuid === connection2.point)) { + + const updatedVehicle: Types.VehicleEventsSchema = { + ...state, + points: { + ...state.points, + connections: { + ...state.points.connections, + targets: state.points.connections.targets.filter(target => { + return !( + (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || + (target.modelUUID === connection2.model && target.pointUUID === connection2.point) + ); + }) + }, + // Ensure all required Vehicle point properties are included + speed: state.points.speed, + actions: state.points.actions + } + }; + return updatedVehicle; + } + } + // Handle StaticMachine + else if (state.type === 'StaticMachine') { + if ((state.modeluuid === connection1.model && state.points.uuid === connection1.point) || + (state.modeluuid === connection2.model && state.points.uuid === connection2.point)) { + + const updatedStaticMachine: Types.StaticMachineEventsSchema = { + ...state, + points: { + ...state.points, + connections: { + ...state.points.connections, + targets: state.points.connections.targets.filter(target => { + return !( + (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || + (target.modelUUID === connection2.model && target.pointUUID === connection2.point) + ); + }) + }, + // Ensure all required StaticMachine point properties are included + actions: state.points.actions, + triggers: state.points.triggers + } + }; + return updatedStaticMachine; + } + } + // Handle ArmBot + else if (state.type === 'ArmBot') { + if ((state.modeluuid === connection1.model && state.points.uuid === connection1.point) || + (state.modeluuid === connection2.model && state.points.uuid === connection2.point)) { + + const updatedArmBot: Types.ArmBotEventsSchema = { + ...state, + points: { + ...state.points, + connections: { + ...state.points.connections, + targets: state.points.connections.targets.filter(target => { + return !( + (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || + (target.modelUUID === connection2.model && target.pointUUID === connection2.point) + ); + }) + }, + actions: { + ...state.points.actions, + processes: state.points.actions.processes?.filter(process => { + return !( + process.startPoint === connection1.point || + process.endPoint === connection1.point || + process.startPoint === connection2.point || + process.endPoint === connection2.point + ); + }) || [] + }, + triggers: state.points.triggers + } + }; + return updatedArmBot; + } + } + return state; + }); + + const updatedPaths = updatedStates.filter(state => + state.modeluuid === connection1.model || state.modeluuid === connection2.model + ); + + updateBackend(updatedPaths); + + setSimulationStates(updatedStates); + }; + + return ( - - {simulationPaths.flatMap(path => { + + {simulationStates.flatMap(path => { if (path.type === 'Conveyor') { return path.points.flatMap(point => point.connections.targets.map((target, index) => { - const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID); - if (targetPath?.type === 'Vehicle') return null; + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + if (targetPath?.type !== 'Conveyor' && targetPath?.type !== 'ArmBot') return null; const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', point.uuid); const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); @@ -523,31 +1002,48 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); - - const midPoint = new THREE.Vector3( - (fromWorldPosition.x + toWorldPosition.x) / 2, - Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, - (fromWorldPosition.z + toWorldPosition.z) / 2 - ); + const midPoint = new THREE.Vector3((fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, (fromWorldPosition.z + toWorldPosition.z) / 2); return ( (groupRefs.current[`${point.uuid}-${target.pointUUID}-${index}`] = el!)} start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} - color="white" + color={ + deleteTool && hoveredLineKey === `${point.uuid}-${target.pointUUID}-${index}` + ? 'red' + : targetPath?.type === 'ArmBot' + ? '#42a5f5' + : 'white' + } lineWidth={4} - dashed + dashed={(deleteTool && hoveredLineKey === `${point.uuid}-${target.pointUUID}-${index}`) ? false : true} dashSize={0.75} dashScale={20} + onPointerOver={() => setHoveredLineKey(`${point.uuid}-${target.pointUUID}-${index}`)} + onPointerOut={() => setHoveredLineKey(null)} + onClick={() => { + if (deleteTool) { + + const connection1 = { model: path.modeluuid, point: point.uuid } + const connection2 = { model: target.modelUUID, point: target.pointUUID } + + removeConnections(connection1, connection2) + + } + }} + userData={target} /> ); } return null; }) ); - } else if (path.type === 'Vehicle') { + } + + if (path.type === 'Vehicle') { return path.points.connections.targets.map((target, index) => { const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.points.uuid); const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); @@ -560,30 +1056,97 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); - - const midPoint = new THREE.Vector3( - (fromWorldPosition.x + toWorldPosition.x) / 2, - Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, - (fromWorldPosition.z + toWorldPosition.z) / 2 - ); + const midPoint = new THREE.Vector3((fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, (fromWorldPosition.z + toWorldPosition.z) / 2); return ( (groupRefs.current[`${path.points.uuid}-${target.pointUUID}-${index}`] = el!)} start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} - color="orange" + color={ + deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}` + ? 'red' + : 'orange' + } lineWidth={4} - dashed + dashed={(deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}`) ? false : true} dashSize={0.75} dashScale={20} + onPointerOver={() => setHoveredLineKey(`${path.points.uuid}-${target.pointUUID}-${index}`)} + onPointerOut={() => setHoveredLineKey(null)} + onClick={() => { + if (deleteTool) { + + const connection1 = { model: path.modeluuid, point: path.points.uuid } + const connection2 = { model: target.modelUUID, point: target.pointUUID } + + removeConnections(connection1, connection2) + } + }} + userData={target} /> ); } return null; }); } + + if (path.type === 'StaticMachine') { + return path.points.connections.targets.map((target, index) => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + if (targetPath?.type !== 'ArmBot') return null; + + const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.points.uuid); + const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); + + if (fromSphere && toSphere) { + const fromWorldPosition = new THREE.Vector3(); + const toWorldPosition = new THREE.Vector3(); + fromSphere.getWorldPosition(fromWorldPosition); + toSphere.getWorldPosition(toWorldPosition); + + const distance = fromWorldPosition.distanceTo(toWorldPosition); + const heightFactor = Math.max(0.5, distance * 0.2); + const midPoint = new THREE.Vector3((fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, (fromWorldPosition.z + toWorldPosition.z) / 2); + + return ( + (groupRefs.current[`${path.points.uuid}-${target.pointUUID}-${index}`] = el!)} + start={fromWorldPosition.toArray()} + end={toWorldPosition.toArray()} + mid={midPoint.toArray()} + color={ + deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}` + ? 'red' + : '#42a5f5' + } + lineWidth={4} + dashed={(deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}`) ? false : true} + dashSize={0.75} + dashScale={20} + onPointerOver={() => setHoveredLineKey(`${path.points.uuid}-${target.pointUUID}-${index}`)} + onPointerOut={() => setHoveredLineKey(null)} + onClick={() => { + if (deleteTool) { + + const connection1 = { model: path.modeluuid, point: path.points.uuid } + const connection2 = { model: target.modelUUID, point: target.pointUUID } + + removeConnections(connection1, connection2) + + } + }} + userData={target} + /> + ); + } + return null; + }); + } + return []; })} @@ -601,6 +1164,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec )} ); + } export default PathConnector; \ No newline at end of file diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx index 619a011..839cf39 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -3,375 +3,450 @@ import * as Types from "../../../types/world/worldTypes"; import { useRef, useState, useEffect, useMemo } from "react"; import { Sphere, TransformControls } from "@react-three/drei"; import { - useEditingPoint, - useEyeDropMode, - useIsConnecting, - usePreviewPosition, - useRenderDistance, - useSelectedActionSphere, - useSelectedPath, - useSimulationPaths, + useEditingPoint, + useEyeDropMode, + useIsConnecting, + usePreviewPosition, + useRenderDistance, + useSelectedActionSphere, + useSelectedPath, + useSimulationStates, } from "../../../store/store"; import { useFrame, useThree } from "@react-three/fiber"; import { useSubModuleStore } from "../../../store/useModuleStore"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; -function PathCreation({ - pathsGroupRef, -}: { - pathsGroupRef: React.MutableRefObject; -}) { - const { isPlaying } = usePlayButtonStore(); - const { renderDistance } = useRenderDistance(); - const { setSubModule } = useSubModuleStore(); - const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere(); - const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); - const { editingPoint, setEditingPoint } = useEditingPoint(); - const { previewPosition, setPreviewPosition } = usePreviewPosition(); - const { raycaster, camera, pointer, gl } = useThree(); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const { setSelectedPath } = useSelectedPath(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); - const { isConnecting } = useIsConnecting(); +function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject; }) { + const { isPlaying } = usePlayButtonStore(); + const { renderDistance } = useRenderDistance(); + const { setSubModule } = useSubModuleStore(); + const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere(); + const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); + const { editingPoint, setEditingPoint } = useEditingPoint(); + const { previewPosition, setPreviewPosition } = usePreviewPosition(); + const { raycaster, camera, pointer, gl } = useThree(); + const { setSelectedPath } = useSelectedPath(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { isConnecting } = useIsConnecting(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const groupRefs = useRef<{ [key: string]: THREE.Group }>({}); - const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); - const isMovingRef = useRef(false); - const transformRef = useRef(null); - const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); + const groupRefs = useRef<{ [key: string]: THREE.Group }>({}); + const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); + const isMovingRef = useRef(false); + const transformRef = useRef(null); + const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); - useEffect(() => { - setTransformMode(null); - const handleKeyDown = (e: KeyboardEvent) => { - if (!selectedActionSphere) return; - if (e.key === "g") { - setTransformMode((prev) => (prev === "translate" ? null : "translate")); - } - if (e.key === "r") { - setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); - } - }; + useEffect(() => { + setTransformMode(null); + const handleKeyDown = (e: KeyboardEvent) => { + if (!selectedActionSphere) return; + if (e.key === "g") { + setTransformMode((prev) => (prev === "translate" ? null : "translate")); + } + if (e.key === "r") { + setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); + } + }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [selectedActionSphere]); + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedActionSphere]); - useFrame(() => { - Object.values(groupRefs.current).forEach((group) => { - if (group) { - const distance = new THREE.Vector3( - ...group.position.toArray() - ).distanceTo(camera.position); - group.visible = ((distance <= renderDistance) && !isPlaying); - } - }); - }); + useFrame(() => { + Object.values(groupRefs.current).forEach((group) => { + if (group) { + const distance = new THREE.Vector3( + ...group.position.toArray() + ).distanceTo(camera.position); + group.visible = ((distance <= renderDistance) && !isPlaying); + } + }); + }); - const updateSimulationPaths = () => { - if (!selectedActionSphere) return; + const updateSimulationPaths = () => { + if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Conveyor") { - return { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - position: [ - selectedActionSphere.points.position.x, - selectedActionSphere.points.position.y, - selectedActionSphere.points.position.z, - ], - rotation: [ - selectedActionSphere.points.rotation.x, - selectedActionSphere.points.rotation.y, - selectedActionSphere.points.rotation.z, - ], - } - : point - ), - }; - } - return path; - }) as Types.ConveyorEventsSchema[]; + const updatedPaths = simulationStates.map((path) => { + if (path.type === "Conveyor") { + return { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + position: [ + selectedActionSphere.points.position.x, + selectedActionSphere.points.position.y, + selectedActionSphere.points.position.z, + ], + rotation: [ + selectedActionSphere.points.rotation.x, + selectedActionSphere.points.rotation.y, + selectedActionSphere.points.rotation.z, + ], + } + : point + ), + }; + } else { + return path; + } + }) as Types.ConveyorEventsSchema[]; - setSimulationPaths(updatedPaths); - }; + const updatedPath = updatedPaths.find( + (path) => path.type === "Conveyor" && path.points.some((point) => point.uuid === selectedActionSphere.points.uuid) + ); - useFrame(() => { - if (eyeDropMode) { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + // console.log("Updated Path:", updatedPath); - if (point) { - setPreviewPosition({ x: point.x, y: point.z }); - } - } else { - setPreviewPosition(null); - } - }); + setSimulationStates(updatedPaths); + }; - useEffect(() => { - if (!camera) return; - const canvasElement = gl.domElement; - canvasElement.tabIndex = 0; + useFrame(() => { + if (eyeDropMode) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - const onPointerDown = () => { - isMovingRef.current = false; - }; + if (point) { + setPreviewPosition({ x: point.x, y: point.z }); + } + } else { + setPreviewPosition(null); + } + }); - const onPointerMove = () => { - isMovingRef.current = true; - }; + useEffect(() => { + if (!camera) return; + const canvasElement = gl.domElement; + canvasElement.tabIndex = 0; - const onPointerUp = (event: PointerEvent) => { - if ( - !isMovingRef.current && - eyeDropMode && - event.button === 0 && - previewPosition - ) { - event.preventDefault(); - if (editingPoint) { - handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y); - setEditingPoint(null); - setEyeDropMode(false); - } - } - }; + const onPointerDown = () => { + isMovingRef.current = false; + }; - if (eyeDropMode) { - canvasElement.addEventListener("pointerdown", onPointerDown); - canvasElement.addEventListener("pointermove", onPointerMove); - canvasElement.addEventListener("pointerup", onPointerUp); - } + const onPointerMove = () => { + isMovingRef.current = true; + }; - return () => { - canvasElement.removeEventListener("pointerdown", onPointerDown); - canvasElement.removeEventListener("pointermove", onPointerMove); - canvasElement.removeEventListener("pointerup", onPointerUp); - }; - }, [eyeDropMode, editingPoint, previewPosition]); + const onPointerUp = (event: PointerEvent) => { + if ( + !isMovingRef.current && + eyeDropMode && + event.button === 0 && + previewPosition + ) { + event.preventDefault(); + if (editingPoint) { + handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y); + setEditingPoint(null); + setEyeDropMode(false); + } + } + }; - const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { - if (!updatedPath) return; - const email = localStorage.getItem("email"); - const organization = email ? email.split("@")[1].split(".")[0] : ""; - await setEventApi( - organization, - updatedPath.modeluuid, - { type: "Vehicle", points: updatedPath.points } - ); - } + if (eyeDropMode) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + } - const handlePointUpdate = ( - pointType: "start" | "end", - x: number, - z: number - ) => { - if (!selectedActionSphere?.points?.uuid) return; - const updatedPaths = simulationPaths.map((path) => { + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + }; + }, [eyeDropMode, editingPoint, previewPosition]); - if ( - path.type === "Vehicle" && - path.points.uuid === selectedActionSphere.points.uuid - ) { - return { - ...path, - points: { - ...path.points, - actions: { - ...path.points.actions, - [pointType]: { - ...path.points.actions[pointType], - x: x, - y: z, - }, - }, - }, - }; - } - return path; - }); + const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + await setEventApi( + organization, + updatedPath.modeluuid, + { type: "Vehicle", points: updatedPath.points } + ); + } - const updatedPath = updatedPaths.find( - (path): path is Types.VehicleEventsSchema => - path.type === "Vehicle" && - path.points.uuid === selectedActionSphere.points.uuid - ); - updateBackend(updatedPath); + const handlePointUpdate = (pointType: "start" | "end", x: number, z: number) => { + if (!selectedActionSphere?.points?.uuid) return; + const updatedPaths = simulationStates.map((path) => { - setSimulationPaths(updatedPaths); - }; + if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { + return { + ...path, + points: { + ...path.points, + actions: { + ...path.points.actions, + [pointType]: { ...path.points.actions[pointType], x: x, y: z, }, + }, + }, + }; + } + return path; + }); - return ( - - {simulationPaths.map((path) => { - if (path.type === "Conveyor") { - const points = path.points.map( - (point) => new THREE.Vector3(...point.position) - ); + const updatedPath = updatedPaths.find((path): path is Types.VehicleEventsSchema => path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid); + updateBackend(updatedPath); - return ( - (groupRefs.current[path.modeluuid] = el!)} - position={path.position} - rotation={path.rotation} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedPath({ - path, - group: groupRefs.current[path.modeluuid], - }); - setSelectedActionSphere(null); - setTransformMode(null); - setSubModule("mechanics"); - }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSelectedPath(null); - setSubModule("properties"); - }} - > - {path.points.map((point, index) => ( - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedActionSphere({ - path, - points: sphereRefs.current[point.uuid], - }); - setSubModule("mechanics"); - setSelectedPath(null); - }} - userData={{ points, path }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSubModule("properties"); - setSelectedActionSphere(null); - }} - > - - - ))} + setSimulationStates(updatedPaths); + }; - {points.slice(0, -1).map((point, index) => { - const nextPoint = points[index + 1]; - const segmentCurve = new THREE.CatmullRomCurve3([ - point, - nextPoint, - ]); - const tubeGeometry = new THREE.TubeGeometry( - segmentCurve, - 20, - 0.1, - 16, - false - ); + return ( + + {simulationStates.map((path) => { + if (path.type === "Conveyor") { + const points = path.points.map( + (point) => new THREE.Vector3(...point.position) + ); - return ( - - - - ); - })} - - ); - } else if (path.type === "Vehicle") { - return ( - (groupRefs.current[path.modeluuid] = el!)} - position={path.position} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedPath({ - path, - group: groupRefs.current[path.modeluuid], - }); - setSelectedActionSphere(null); - setTransformMode(null); - setSubModule("mechanics"); - }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSelectedPath(null); - setSubModule("properties"); - }} - > - (sphereRefs.current[path.points.uuid] = el!)} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedActionSphere({ - path, - points: sphereRefs.current[path.points.uuid], - }); - setSubModule("mechanics"); - setSelectedPath(null); - }} - userData={{ points: path.points, path }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSubModule("properties"); - setSelectedActionSphere(null); - }} - > - - - - ); - } - return null; - })} + return ( + (groupRefs.current[path.modeluuid] = el!)} + position={path.position} + rotation={path.rotation} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedPath({ + path, + group: groupRefs.current[path.modeluuid], + }); + setSelectedActionSphere(null); + setTransformMode(null); + setSubModule("mechanics"); + }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSelectedPath(null); + setSubModule("properties"); + }} + > + {path.points.map((point, index) => ( + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedActionSphere({ + path, + points: sphereRefs.current[point.uuid], + }); + setSubModule("mechanics"); + setSelectedPath(null); + }} + userData={{ points, path }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSubModule("properties"); + setSelectedActionSphere(null); + }} + > + + + ))} - {selectedActionSphere && transformMode && ( - - )} - - ); + {points.slice(0, -1).map((point, index) => { + const nextPoint = points[index + 1]; + const segmentCurve = new THREE.CatmullRomCurve3([point, nextPoint,]); + const tubeGeometry = new THREE.TubeGeometry(segmentCurve, 20, 0.1, 16, false); + + return ( + + + + ); + })} + + ); + } else if (path.type === "Vehicle") { + return ( + (groupRefs.current[path.modeluuid] = el!)} + position={path.position} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedPath({ + path, + group: groupRefs.current[path.modeluuid], + }); + setSelectedActionSphere(null); + setTransformMode(null); + setSubModule("mechanics"); + }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSelectedPath(null); + setSubModule("properties"); + }} + > + (sphereRefs.current[path.points.uuid] = el!)} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedActionSphere({ + path, + points: sphereRefs.current[path.points.uuid], + }); + setSubModule("mechanics"); + setSelectedPath(null); + }} + userData={{ points: path.points, path }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSubModule("properties"); + setSelectedActionSphere(null); + }} + > + + + + ); + } else if (path.type === "StaticMachine") { + return ( + (groupRefs.current[path.modeluuid] = el!)} + position={path.position} + rotation={path.rotation} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedPath({ + path, + group: groupRefs.current[path.modeluuid], + }); + setSelectedActionSphere(null); + setTransformMode(null); + setSubModule("mechanics"); + }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSelectedPath(null); + setSubModule("properties"); + }} + > + (sphereRefs.current[path.points.uuid] = el!)} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedActionSphere({ + path, + points: sphereRefs.current[path.points.uuid], + }); + setSubModule("mechanics"); + setSelectedPath(null); + }} + userData={{ points: path.points, path }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSubModule("properties"); + setSelectedActionSphere(null); + }} + > + + + + ); + } else if (path.type === "ArmBot") { + return ( + (groupRefs.current[path.modeluuid] = el!)} + position={path.position} + rotation={path.rotation} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedPath({ + path, + group: groupRefs.current[path.modeluuid], + }); + setSelectedActionSphere(null); + setTransformMode(null); + setSubModule("mechanics"); + }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSelectedPath(null); + setSubModule("properties"); + }} + > + (sphereRefs.current[path.points.uuid] = el!)} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedActionSphere({ + path, + points: sphereRefs.current[path.points.uuid], + }); + setSubModule("mechanics"); + setSelectedPath(null); + }} + userData={{ points: path.points, path }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSubModule("properties"); + setSelectedActionSphere(null); + }} + > + + + + ); + } + return null; + })} + + {selectedActionSphere && transformMode && ( + + )} + + ); } export default PathCreation; diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index d82b800..c8cf07f 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -29,8 +29,8 @@ interface ProcessPoint { rotation: number[]; actions: PointAction[]; connections: { - source: { pathUUID: string; pointUUID: string }; - targets: { pathUUID: string; pointUUID: string }[]; + source: { modelUUID: string; pointUUID: string }; + targets: { modelUUID: string; pointUUID: string }[]; }; } diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx index f089285..4403a71 100644 --- a/app/src/modules/simulation/process/processCreator.tsx +++ b/app/src/modules/simulation/process/processCreator.tsx @@ -5,7 +5,7 @@ // useCallback, // useRef, // } from "react"; -// import { useSimulationPaths } from "../../../store/store"; +// import { useSimulationStates } from "../../../store/store"; // import * as THREE from "three"; // import { useThree } from "@react-three/fiber"; // import { @@ -29,7 +29,7 @@ // position: [number, number, number]; // actions: PointAction[]; // connections: { -// targets: Array<{ pathUUID: string }>; +// targets: Array<{ modelUUID: string }>; // }; // } @@ -72,7 +72,7 @@ // actions: point.actions.map(normalizeAction), // Preserve exact actions // connections: { // targets: point.connections.targets.map((target) => ({ -// pathUUID: target.pathUUID, +// modelUUID: target.modelUUID, // })), // }, // })), @@ -94,7 +94,7 @@ // : [normalizeAction(path.point.actions)], // connections: { // targets: path.point.connections.targets.map((target) => ({ -// pathUUID: target.pathUUID, +// modelUUID: target.modelUUID, // })), // }, // }, @@ -137,18 +137,18 @@ // // Check if current last connects to next last (requires reversal) // const connectsToLast = currentLastPoint.connections.targets.some( // (target) => -// target.pathUUID === nextPath.modeluuid && +// target.modelUUID === nextPath.modeluuid && // nextLastPoint.connections.targets.some( -// (t) => t.pathUUID === currentPath.modeluuid +// (t) => t.modelUUID === currentPath.modeluuid // ) // ); // // Check if current last connects to next first (no reversal needed) // const connectsToFirst = currentLastPoint.connections.targets.some( // (target) => -// target.pathUUID === nextPath.modeluuid && +// target.modelUUID === nextPath.modeluuid && // nextFirstPoint.connections.targets.some( -// (t) => t.pathUUID === currentPath.modeluuid +// (t) => t.modelUUID === currentPath.modeluuid // ) // ); @@ -249,10 +249,10 @@ // // Process outgoing connections // for (const point of currentPath.points) { // for (const target of point.connections.targets) { -// if (!visited.has(target.pathUUID)) { -// const targetPath = pathMap.get(target.pathUUID); +// if (!visited.has(target.modelUUID)) { +// const targetPath = pathMap.get(target.modelUUID); // if (targetPath) { -// visited.add(target.pathUUID); +// visited.add(target.modelUUID); // queue.push(targetPath); // } // } @@ -264,7 +264,7 @@ // if (!visited.has(uuid)) { // const hasConnectionToCurrent = path.points.some((point) => // point.connections.targets.some( -// (t) => t.pathUUID === currentPath.modeluuid +// (t) => t.modelUUID === currentPath.modeluuid // ) // ); // if (hasConnectionToCurrent) { @@ -312,19 +312,19 @@ // const ProcessCreator: React.FC = React.memo( // ({ onProcessesCreated }) => { -// const { simulationPaths } = useSimulationPaths(); +// const { simulationStates } = useSimulationStates(); // const { createProcessesFromPaths } = useProcessCreation(); // const prevPathsRef = useRef([]); // const prevProcessesRef = useRef([]); // const convertedPaths = useMemo((): SimulationPath[] => { -// if (!simulationPaths) return []; -// return simulationPaths.map((path) => +// if (!simulationStates) return []; +// return simulationStates.map((path) => // convertToSimulationPath( // path as ConveyorEventsSchema | VehicleEventsSchema // ) // ); -// }, [simulationPaths]); +// }, [simulationStates]); // const pathsDependency = useMemo(() => { // if (!convertedPaths) return null; @@ -335,7 +335,7 @@ // ), // connections: path.points // .flatMap((p: PathPoint) => -// p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) +// p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) // ) // .join(","), // })); @@ -404,7 +404,7 @@ import React, { useCallback, useRef, } from "react"; -import { useSimulationPaths } from "../../../store/store"; +import { useSimulationStates } from "../../../store/store"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { @@ -428,11 +428,12 @@ export interface PathPoint { position: [number, number, number]; actions: PointAction[]; connections: { - targets: Array<{ pathUUID: string }>; + targets: Array<{ modelUUID: string }>; }; } export interface SimulationPath { + type: string; modeluuid: string; points: PathPoint[]; pathPosition: [number, number, number]; @@ -464,6 +465,7 @@ function convertToSimulationPath( if (path.type === "Conveyor") { return { + type: path.type, modeluuid, points: path.points.map((point) => ({ uuid: point.uuid, @@ -471,7 +473,7 @@ function convertToSimulationPath( actions: point.actions.map(normalizeAction), // Preserve exact actions connections: { targets: point.connections.targets.map((target) => ({ - pathUUID: target.pathUUID, + modelUUID: target.modelUUID, })), }, })), @@ -483,6 +485,7 @@ function convertToSimulationPath( }; } else { return { + type: path.type, modeluuid, points: [ { @@ -493,7 +496,7 @@ function convertToSimulationPath( : [normalizeAction(path.points.actions)], connections: { targets: path.points.connections.targets.map((target) => ({ - pathUUID: target.pathUUID, + modelUUID: target.modelUUID, })), }, }, @@ -536,18 +539,18 @@ function shouldReverseNextPath( // Check if current last connects to next last (requires reversal) const connectsToLast = currentLastPoint.connections.targets.some( (target) => - target.pathUUID === nextPath.modeluuid && + target.modelUUID === nextPath.modeluuid && nextLastPoint.connections.targets.some( - (t) => t.pathUUID === currentPath.modeluuid + (t) => t.modelUUID === currentPath.modeluuid ) ); // Check if current last connects to next first (no reversal needed) const connectsToFirst = currentLastPoint.connections.targets.some( (target) => - target.pathUUID === nextPath.modeluuid && + target.modelUUID === nextPath.modeluuid && nextFirstPoint.connections.targets.some( - (t) => t.pathUUID === currentPath.modeluuid + (t) => t.modelUUID === currentPath.modeluuid ) ); @@ -614,6 +617,7 @@ export function useProcessCreation() { const [processes, setProcesses] = useState([]); const hasSpawnAction = useCallback((path: SimulationPath): boolean => { + if (path.type !== "Conveyor") return false; return path.points.some((point) => point.actions.some((action) => action.type.toLowerCase() === "spawn") ); @@ -674,10 +678,10 @@ export function useProcessCreation() { // Process outgoing connections for (const point of currentPath.points) { for (const target of point.connections.targets) { - if (!visited.has(target.pathUUID)) { - const targetPath = pathMap.get(target.pathUUID); + if (!visited.has(target.modelUUID)) { + const targetPath = pathMap.get(target.modelUUID); if (targetPath) { - visited.add(target.pathUUID); + visited.add(target.modelUUID); queue.push(targetPath); } } @@ -689,7 +693,7 @@ export function useProcessCreation() { if (!visited.has(uuid)) { const hasConnectionToCurrent = path.points.some((point) => point.connections.targets.some( - (t) => t.pathUUID === currentPath.modeluuid + (t) => t.modelUUID === currentPath.modeluuid ) ); if (hasConnectionToCurrent) { @@ -737,19 +741,19 @@ export function useProcessCreation() { const ProcessCreator: React.FC = React.memo( ({ onProcessesCreated }) => { - const { simulationPaths } = useSimulationPaths(); + const { simulationStates } = useSimulationStates(); const { createProcessesFromPaths } = useProcessCreation(); const prevPathsRef = useRef([]); const prevProcessesRef = useRef([]); const convertedPaths = useMemo((): SimulationPath[] => { - if (!simulationPaths) return []; - return simulationPaths.map((path) => + if (!simulationStates) return []; + return simulationStates.map((path) => convertToSimulationPath( path as ConveyorEventsSchema | VehicleEventsSchema ) ); - }, [simulationPaths]); + }, [simulationStates]); // Enhanced dependency tracking that includes action types const pathsDependency = useMemo(() => { @@ -764,7 +768,7 @@ const ProcessCreator: React.FC = React.memo( .join(","), connections: path.points .flatMap((p: PathPoint) => - p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) + p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) ) .join(","), })); diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index fd8b520..98fd3dd 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useMemo } from "react"; import { useSelectedActionSphere, useSelectedPath, - useSimulationPaths, + useSimulationStates, } from "../../store/store"; import * as THREE from "three"; import Behaviour from "./behaviour/behaviour"; @@ -15,12 +15,12 @@ import Agv from "../builder/agv/agv"; function Simulation() { const { activeModule } = useModuleStore(); const pathsGroupRef = useRef() as React.MutableRefObject; - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const [processes, setProcesses] = useState([]); useEffect(() => { - // console.log('simulationPaths: ', simulationPaths); - }, [simulationPaths]); + // console.log('simulationStates: ', simulationStates); + }, [simulationStates]); // useEffect(() => { // if (selectedActionSphere) { @@ -42,7 +42,7 @@ function Simulation() { - {/* */} + )} diff --git a/app/src/modules/simulation/simulationUI.tsx b/app/src/modules/simulation/simulationUI.tsx index bff2b20..055fb36 100644 --- a/app/src/modules/simulation/simulationUI.tsx +++ b/app/src/modules/simulation/simulationUI.tsx @@ -1,5 +1,5 @@ // import { useMemo, useState } from 'react'; -// import { useSelectedActionSphere, useToggleView, useSimulationPaths, useSelectedPath, useStartSimulation, useDrawMaterialPath } from '../../store/store'; +// import { useSelectedActionSphere, useToggleView, useSimulationStates, useSelectedPath, useStartSimulation, useDrawMaterialPath } from '../../store/store'; // import * as THREE from 'three'; // import useModuleStore from '../../store/useModuleStore'; @@ -9,14 +9,14 @@ // const { startSimulation, setStartSimulation } = useStartSimulation(); // const { selectedActionSphere } = useSelectedActionSphere(); // const { selectedPath, setSelectedPath } = useSelectedPath(); -// const { simulationPaths, setSimulationPaths } = useSimulationPaths(); +// const { simulationStates, setSimulationStates } = useSimulationStates(); // const { drawMaterialPath, setDrawMaterialPath } = useDrawMaterialPath(); // const [activeButton, setActiveButton] = useState(null); // const handleAddAction = () => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => { // if (point.uuid === selectedActionSphere.points.uuid) { @@ -37,13 +37,13 @@ // }), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleDeleteAction = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -52,13 +52,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleActionSelect = (uuid: string, actionType: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -72,13 +72,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleMaterialSelect = (uuid: string, material: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -92,13 +92,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleDelayChange = (uuid: string, delay: number | string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -112,13 +112,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleSpawnIntervalChange = (uuid: string, spawnInterval: number | string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -132,24 +132,24 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleSpeedChange = (speed: number) => { // if (!selectedPath) return; -// const updatedPaths = simulationPaths.map((path) => +// const updatedPaths = simulationStates.map((path) => // path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path // ); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); // }; // const handleAddTrigger = () => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => { // if (point.uuid === selectedActionSphere.points.uuid) { @@ -167,13 +167,13 @@ // }), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleDeleteTrigger = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -182,13 +182,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleTriggerSelect = (uuid: string, triggerType: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -202,7 +202,7 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleResetPath = () => { @@ -214,7 +214,7 @@ // const handleActionToggle = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -229,13 +229,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleTriggerToggle = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => // point.uuid === selectedActionSphere.points.uuid @@ -250,13 +250,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const selectedPoint = useMemo(() => { // if (!selectedActionSphere) return null; -// return simulationPaths.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.points.uuid); -// }, [selectedActionSphere, simulationPaths]); +// return simulationStates.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.points.uuid); +// }, [selectedActionSphere, simulationStates]); // const createPath = () => { // setActiveButton(activeButton !== 'addMaterialPath' ? 'addMaterialPath' : null); diff --git a/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx b/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx index c09b21c..c9dd36b 100644 --- a/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx +++ b/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx @@ -11,13 +11,13 @@ type PathPoint = { }; type PathCreatorProps = { - simulationPaths: PathPoint[][]; - setSimulationPaths: React.Dispatch>; + simulationStates: PathPoint[][]; + setSimulationStates: React.Dispatch>; connections: { start: PathPoint; end: PathPoint }[]; setConnections: React.Dispatch> }; -const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConnections }: PathCreatorProps) => { +const PathCreator = ({ simulationStates, setSimulationStates, connections, setConnections }: PathCreatorProps) => { const { camera, scene, raycaster, pointer, gl } = useThree(); const { drawMaterialPath } = useDrawMaterialPath(); @@ -71,7 +71,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn e.preventDefault(); if (drag || e.button === 0) return; if (currentPath.length > 1) { - setSimulationPaths((prevPaths) => [...prevPaths, currentPath]); + setSimulationStates((prevPaths) => [...prevPaths, currentPath]); } setCurrentPath([]); setTemporaryPoint(null); @@ -125,7 +125,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn canvasElement.addEventListener("contextmenu", onContextMenu); } else { if (currentPath.length > 1) { - setSimulationPaths((prevPaths) => [...prevPaths, currentPath]); + setSimulationStates((prevPaths) => [...prevPaths, currentPath]); } setCurrentPath([]); setTemporaryPoint(null); @@ -179,25 +179,25 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn if (selectedPoint) { const updatedPosition = e.target.object.position.clone(); const updatedRotation = e.target.object.quaternion.clone(); - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.map((p) => p.uuid === selectedPoint.uuid ? { ...p, position: updatedPosition, rotation: updatedRotation } : p ) ); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); } }; const meshContext = (uuid: string) => { - const pathIndex = simulationPaths.findIndex(path => path.some(point => point.uuid === uuid)); + const pathIndex = simulationStates.findIndex(path => path.some(point => point.uuid === uuid)); if (pathIndex === -1) return; - const clickedPoint = simulationPaths[pathIndex].find(point => point.uuid === uuid); + const clickedPoint = simulationStates[pathIndex].find(point => point.uuid === uuid); if (!clickedPoint) return; - const isStart = simulationPaths[pathIndex][0].uuid === uuid; - const isEnd = simulationPaths[pathIndex][simulationPaths[pathIndex].length - 1].uuid === uuid; + const isStart = simulationStates[pathIndex][0].uuid === uuid; + const isEnd = simulationStates[pathIndex][simulationStates[pathIndex].length - 1].uuid === uuid; if (pathIndex === 0 && isStart) { console.log("The first-ever point is not connectable."); @@ -285,8 +285,8 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn return ( <> - {/* Render finalized simulationPaths */} - {simulationPaths.map((path, pathIndex) => ( + {/* Render finalized simulationStates */} + {simulationStates.map((path, pathIndex) => ( + {simulationStates.map((path) => path.map((point) => ( ([]); + const [simulationStates, setSimulationStates] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }[][]>([]); const [connections, setConnections] = useState<{ start: PathPoint; end: PathPoint }[]>([]); return ( <> - - {simulationPaths.map((path, index) => ( + + {simulationStates.map((path, index) => ( ))} diff --git a/app/src/modules/visualization/captureVisualization.ts b/app/src/modules/visualization/captureVisualization.ts index 01d67a0..7cf7bc5 100644 --- a/app/src/modules/visualization/captureVisualization.ts +++ b/app/src/modules/visualization/captureVisualization.ts @@ -1,55 +1,22 @@ +// import html2canvas from "html2canvas"; + export const captureVisualization = async (): Promise => { - const container = document.getElementById("real-time-vis-canvas"); - if (!container) return null; + const container = document.getElementById("real-time-vis-canvas"); + if (!container) return null; - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - if (!ctx) return null; + try { + // Use html2canvas to capture the container + // const canvas = await html2canvas(container, { + // scale: 1, // Adjust scale for higher/lower resolution + // }); - const rect = container.getBoundingClientRect(); - canvas.width = rect.width; - canvas.height = rect.height; + // // Convert the canvas to a data URL (PNG format) + // const dataUrl = canvas.toDataURL("image/png"); + // return dataUrl; - // Draw background - ctx.fillStyle = getComputedStyle(container).backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Capture all canvas elements - const canvases = container.querySelectorAll("canvas"); - canvases.forEach((childCanvas) => { - const childRect = childCanvas.getBoundingClientRect(); - const x = childRect.left - rect.left; - const y = childRect.top - rect.top; - ctx.drawImage(childCanvas, x, y, childRect.width, childRect.height); - }); - - // Capture SVG elements - const svgs = container.querySelectorAll("svg"); - for (const svg of Array.from(svgs)) { - const svgString = new XMLSerializer().serializeToString(svg); - const svgBlob = new Blob([svgString], { type: "image/svg+xml" }); - const url = URL.createObjectURL(svgBlob); - - try { - const img = await new Promise((resolve, reject) => { - const image = new Image(); - image.onload = () => resolve(image); - image.onerror = reject; - image.src = url; - }); - - const svgRect = svg.getBoundingClientRect(); - ctx.drawImage( - img, - svgRect.left - rect.left, - svgRect.top - rect.top, - svgRect.width, - svgRect.height - ); - } finally { - URL.revokeObjectURL(url); - } - } - - return canvas.toDataURL("image/png"); + return null; + } catch (error) { + console.error("Error capturing visualization:", error); + return null; + } }; diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts index e5f90ab..6fc5a3e 100644 --- a/app/src/modules/visualization/handleSaveTemplate.ts +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -28,10 +28,10 @@ export const handleSaveTemplate = async ({ templates = [], visualizationSocket, }: HandleSaveTemplateProps): Promise => { + console.log('floatingWidget: ', floatingWidget); try { // Check if the selected zone has any widgets if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) { - console.warn("No widgets found in the selected zone."); return; } @@ -49,7 +49,8 @@ export const handleSaveTemplate = async ({ } // Capture visualization snapshot - const snapshot = await captureVisualization(); + // const snapshot = await captureVisualization(); + const snapshot = null; // if (!snapshot) { // return; @@ -72,7 +73,6 @@ export const handleSaveTemplate = async ({ : ""; if (!organization) { - console.error("Organization could not be determined from email."); return; } let saveTemplate = { @@ -87,13 +87,13 @@ export const handleSaveTemplate = async ({ try { addTemplate(newTemplate); // const response = await saveTemplateApi(organization, newTemplate); - // console.log("Save API Response:", response); + // // Add template only if API call succeeds } catch (apiError) { - // console.error("Error saving template to API:", apiError); + // } } catch (error) { - // console.error("Error in handleSaveTemplate:", error); + // } }; diff --git a/app/src/modules/visualization/realTimeVizSocket.dev.tsx b/app/src/modules/visualization/realTimeVizSocket.dev.tsx index ad5e226..f1abaaa 100644 --- a/app/src/modules/visualization/realTimeVizSocket.dev.tsx +++ b/app/src/modules/visualization/realTimeVizSocket.dev.tsx @@ -5,23 +5,29 @@ import { useDroppedObjectsStore } from "../../store/useDroppedObjectsStore"; import { useZoneWidgetStore } from "../../store/useZone3DWidgetStore"; import useTemplateStore from "../../store/useTemplateStore"; +type WidgetData = { + id: string; + type: string; + position: [number, number, number]; + rotation?: [number, number, number]; + tempPosition?: [number, number, number]; +}; + export default function SocketRealTimeViz() { const { visualizationSocket } = useSocketStore(); - const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const { setSelectedZone } = useSelectedZoneStore(); const deleteObject = useDroppedObjectsStore((state) => state.deleteObject); - const duplicateObject = useDroppedObjectsStore((state) => state.duplicateObject); const updateObjectPosition = useDroppedObjectsStore((state) => state.updateObjectPosition); const { addWidget } = useZoneWidgetStore() - const { templates, removeTemplate } = useTemplateStore(); + const { removeTemplate } = useTemplateStore(); const { setTemplates } = useTemplateStore(); + const { zoneWidgetData, setZoneWidgetData, updateWidgetPosition, updateWidgetRotation } = useZoneWidgetStore(); useEffect(() => { - const email = localStorage.getItem("email") || ""; - const organization = email?.split("@")[1]?.split(".")[0]; if (visualizationSocket) { //add panel response visualizationSocket.on("viz-panel:response:updates", (addPanel: any) => { - console.log('addPanel: ', addPanel); + if (addPanel.success) { let addPanelData = addPanel.data.data setSelectedZone(addPanelData) @@ -29,15 +35,33 @@ export default function SocketRealTimeViz() { }) //delete panel response visualizationSocket.on("viz-panel:response:delete", (deletePanel: any) => { - console.log('deletePanel: ', deletePanel); + if (deletePanel.success) { let deletePanelData = deletePanel.data.data setSelectedZone(deletePanelData) } }) + //clear Panel response + visualizationSocket.on("viz-panel:response:clear", (clearPanel: any) => { + + if (clearPanel.success && clearPanel.message === "PanelWidgets cleared successfully") { + + let clearPanelData = clearPanel.data.data + setSelectedZone(clearPanelData) + } + }) + //lock Panel response + visualizationSocket.on("viz-panel:response:locked", (lockPanel: any) => { + + if (lockPanel.success && lockPanel.message === "locked panel updated successfully") { + + let lockPanelData = lockPanel.data.data + setSelectedZone(lockPanelData) + } + }) // add 2dWidget response visualizationSocket.on("viz-widget:response:updates", (add2dWidget: any) => { - console.log('add2dWidget: ', add2dWidget); + if (add2dWidget.success && add2dWidget.data) { setSelectedZone((prev) => { @@ -56,7 +80,7 @@ export default function SocketRealTimeViz() { }); //delete 2D Widget response visualizationSocket.on("viz-widget:response:delete", (deleteWidget: any) => { - console.log('deleteWidget: ', deleteWidget); + if (deleteWidget?.success && deleteWidget.data) { setSelectedZone((prevZone: any) => ({ @@ -69,7 +93,7 @@ export default function SocketRealTimeViz() { }); //add Floating Widget response visualizationSocket.on("viz-float:response:updates", (addFloatingWidget: any) => { - console.log('addFloatingWidget: ', addFloatingWidget); + if (addFloatingWidget.success) { if (addFloatingWidget.success && addFloatingWidget.message === "FloatWidget created successfully") { @@ -96,7 +120,7 @@ export default function SocketRealTimeViz() { }); //duplicate Floating Widget response visualizationSocket.on("viz-float:response:addDuplicate", (duplicateFloatingWidget: any) => { - console.log('duplicateFloatingWidget: ', duplicateFloatingWidget); + if (duplicateFloatingWidget.success && duplicateFloatingWidget.message === "duplicate FloatWidget created successfully") { useDroppedObjectsStore.setState((state) => { @@ -124,7 +148,7 @@ export default function SocketRealTimeViz() { }); //delete Floating Widget response visualizationSocket.on("viz-float:response:delete", (deleteFloatingWidget: any) => { - console.log('deleteFloatingWidget: ', deleteFloatingWidget); + if (deleteFloatingWidget.success) { deleteObject(deleteFloatingWidget.data.zoneName, deleteFloatingWidget.data.floatWidgetID); @@ -132,17 +156,39 @@ export default function SocketRealTimeViz() { }); //add 3D Widget response visualizationSocket.on("viz-widget3D:response:updates", (add3DWidget: any) => { - console.log('add3DWidget: ', add3DWidget); + if (add3DWidget.success) { + if (add3DWidget.message === "Widget created successfully") { addWidget(add3DWidget.data.zoneId, add3DWidget.data.widget); } } }); + //delete 3D Widget response + visualizationSocket.on("viz-widget3D:response:delete", (delete3DWidget: any) => { + + // "3DWidget delete unsuccessfull" + if (delete3DWidget.success && delete3DWidget.message === "3DWidget delete successfull") { + const activeZoneWidgets = zoneWidgetData[delete3DWidget.data.zoneId] || []; + setZoneWidgetData( + delete3DWidget.data.zoneId, + activeZoneWidgets.filter((w: WidgetData) => w.id !== delete3DWidget.data.id) + ); + } + }); + //update3D widget response + visualizationSocket.on("viz-widget3D:response:modifyPositionRotation", (update3DWidget: any) => { + + + if (update3DWidget.success && update3DWidget.message === "widget update successfully") { + updateWidgetPosition(update3DWidget.data.zoneId, update3DWidget.data.widget.id, update3DWidget.data.widget.position); + updateWidgetRotation(update3DWidget.data.zoneId, update3DWidget.data.widget.id, update3DWidget.data.widget.rotation); + } + }); // add Template response visualizationSocket.on("viz-template:response:add", (addingTemplate: any) => { - console.log('addingTemplate: ', addingTemplate); + if (addingTemplate.success) { if (addingTemplate.message === "Template saved successfully") { @@ -152,7 +198,7 @@ export default function SocketRealTimeViz() { }); //load Template response visualizationSocket.on("viz-template:response:addTemplateZone", (loadTemplate: any) => { - console.log('loadTemplate: ', loadTemplate); + if (loadTemplate.success) { if (loadTemplate.message === "Template placed in Zone") { @@ -175,14 +221,12 @@ export default function SocketRealTimeViz() { }); //delete Template response visualizationSocket.on("viz-template:response:delete", (deleteTemplate: any) => { - console.log('deleteTemplate: ', deleteTemplate); + if (deleteTemplate.success) { if (deleteTemplate.message === 'Template deleted successfully') { removeTemplate(deleteTemplate.data); } - - } }); } diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 54fe61f..ac05db8 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -22,17 +22,17 @@ import LoadingPage from "../components/templates/LoadingPage"; import SimulationPlayer from "../components/ui/simulation/simulationPlayer"; import RenderOverlay from "../components/templates/Overlay"; import MenuBar from "../components/ui/menu/menu"; +import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys"; const Project: React.FC = () => { let navigate = useNavigate(); const { activeModule } = useModuleStore(); - const { loadingProgress, setLoadingProgress } = useLoadingProgress(); + const { loadingProgress } = useLoadingProgress(); const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); const { setFloorItems } = useFloorItems(); const { setWallItems } = useWallItems(); const { setZones } = useZones(); - const [openMenu, setOpenMenu] = useState(true); useEffect(() => { setFloorItems([]); @@ -56,6 +56,7 @@ const Project: React.FC = () => { return (
+ {loadingProgress && } {!isPlaying && ( <> diff --git a/app/src/services/factoryBuilder/assest/assets/getAssetModel.ts b/app/src/services/factoryBuilder/assest/assets/getAssetModel.ts index 4ccb6e9..ae7aaa4 100644 --- a/app/src/services/factoryBuilder/assest/assets/getAssetModel.ts +++ b/app/src/services/factoryBuilder/assest/assets/getAssetModel.ts @@ -2,7 +2,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL} export const getAssetModel = async (modelId: string) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${modelId}`, { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${modelId}`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts index 2685ca4..fc94323 100644 --- a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts +++ b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts @@ -1,8 +1,7 @@ let BackEnd_url = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; export const getCategoryAsset = async (categoryName: any) => { try { - const response = await fetch( - `${BackEnd_url}/api/v2/getCategoryAssets/${categoryName}`, + const response = await fetch(`${BackEnd_url}/api/v2/getCategoryAssets/${categoryName}`, { method: "GET", headers: { diff --git a/app/src/services/factoryBuilder/assest/floorAsset/getFloorItemsApi.ts b/app/src/services/factoryBuilder/assest/floorAsset/getFloorItemsApi.ts index 7a0290a..7c78c41 100644 --- a/app/src/services/factoryBuilder/assest/floorAsset/getFloorItemsApi.ts +++ b/app/src/services/factoryBuilder/assest/floorAsset/getFloorItemsApi.ts @@ -1,5 +1,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + export const getFloorAssets = async (organization: string) => { try { const response = await fetch(`${url_Backend_dwinzo}/api/v2/floorAssets/${organization}`, { @@ -14,6 +15,7 @@ export const getFloorAssets = async (organization: string) => { } const result = await response.json(); + return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts index e25e05e..3e156cb 100644 --- a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts +++ b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts @@ -1,14 +1,13 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; - export const setFloorItemApi = async ( organization: string, - modeluuid: string, - modelname: string, - modelfileID: string, - position: Object, - rotation: Object, - isLocked: boolean, - isVisible: boolean, + modeluuid?: string, + modelname?: string, + modelfileID?: string, + position?: Object, + rotation?: Object, + isLocked?: boolean, + isVisible?: boolean, eventData?: any ) => { try { diff --git a/app/src/services/factoryBuilder/environment/findEnvironment.ts b/app/src/services/factoryBuilder/environment/findEnvironment.ts index de5b6a6..08cfe68 100644 --- a/app/src/services/factoryBuilder/environment/findEnvironment.ts +++ b/app/src/services/factoryBuilder/environment/findEnvironment.ts @@ -26,7 +26,7 @@ export const findEnvironment = async (organization: string, userId: string) => { false, false, false, - 0, + 40, true ); return userpos; diff --git a/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js b/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js index 2fbdc13..9e7397d 100644 --- a/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js +++ b/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js @@ -26,7 +26,7 @@ onmessage = async (event) => { const message = "gltfLoaded"; postMessage({ message, modelID, modelBlob }); } else { - const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${modelID}`; + const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${modelID}`; const modelBlob = await fetch(modelUrl).then((res) => res.blob()); await storeGLTF(modelID, modelBlob); const message = "gltfLoaded"; diff --git a/app/src/services/factoryBuilder/zones/deleteZoneApi.ts b/app/src/services/factoryBuilder/zones/deleteZoneApi.ts index fbe4a83..2537fb6 100644 --- a/app/src/services/factoryBuilder/zones/deleteZoneApi.ts +++ b/app/src/services/factoryBuilder/zones/deleteZoneApi.ts @@ -15,7 +15,7 @@ export const deleteZonesApi = async (userId: string, organization: string, zoneI } const result = await response.json(); - console.log('result: ', result); + return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/services/factoryBuilder/zones/getZonesApi.ts b/app/src/services/factoryBuilder/zones/getZonesApi.ts index be7741c..413458c 100644 --- a/app/src/services/factoryBuilder/zones/getZonesApi.ts +++ b/app/src/services/factoryBuilder/zones/getZonesApi.ts @@ -1,4 +1,5 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const getZonesApi = async (organization: string) => { try { diff --git a/app/src/services/realTimeVisulization/zoneData/clearPanel.ts b/app/src/services/realTimeVisulization/zoneData/clearPanel.ts new file mode 100644 index 0000000..6e1f1d1 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/clearPanel.ts @@ -0,0 +1,30 @@ +// let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const clearPanel = async ( + zoneId: string, + organization: string, + panelName: string +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/clearpanel`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, panelName }), + }); + + if (!response.ok) { + throw new Error("Failed to clearPanel in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/getZones.ts b/app/src/services/realTimeVisulization/zoneData/getZones.ts index 8dbf79a..b189050 100644 --- a/app/src/services/realTimeVisulization/zoneData/getZones.ts +++ b/app/src/services/realTimeVisulization/zoneData/getZones.ts @@ -2,8 +2,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR // let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const getZoneData = async (zoneId: string, organization: string) => { - console.log("organization: ", organization); - console.log("zoneId: ", zoneId); + try { const response = await fetch( `${url_Backend_dwinzo}/api/v2/A_zone/${zoneId}/${organization}`, diff --git a/app/src/services/realTimeVisulization/zoneData/lockPanel.ts b/app/src/services/realTimeVisulization/zoneData/lockPanel.ts new file mode 100644 index 0000000..4b5898f --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/lockPanel.ts @@ -0,0 +1,30 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const lockPanel = async ( + zoneId: string, + organization: string, + lockedPanel: any +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/zones/lockedPanels`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, lockedPanel }), + }); + + if (!response.ok) { + throw new Error("Failed to Lock Panel in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts b/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts index 553fc68..cb37a7f 100644 --- a/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts +++ b/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts @@ -9,20 +9,16 @@ export const update3dWidget = async ( console.log("organization: ", organization); console.log("zoneId: ", zoneId); try { - const response = await fetch( - `${url_Backend_dwinzo}/api/v2/modifyPR/widget3D`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - organization, - zoneId, - id, - position, - }), - } + const response = await fetch(`${url_Backend_dwinzo}/api/v2/modifyPR/widget3D`, { + method: "PATCH", + headers: { "Content-Type": "application/json", }, + body: JSON.stringify({ + organization, + zoneId, + id, + position, + }), + } ); if (!response.ok) { diff --git a/app/src/store/store.ts b/app/src/store/store.ts index 365eb97..8810dbe 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -14,7 +14,7 @@ export const useSocketStore = create((set: any, get: any) => ({ const socket = io( `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder`, { - reconnection: false, + reconnection: true, auth: { email, organization }, } ); @@ -22,7 +22,7 @@ export const useSocketStore = create((set: any, get: any) => ({ const visualizationSocket = io( `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization`, { - reconnection: false, + reconnection: true, auth: { email, organization }, } ); @@ -68,7 +68,11 @@ export const useWalls = create((set: any) => ({ export const useZones = create((set: any) => ({ zones: [], - setZones: (x: any) => set(() => ({ zones: x })), + setZones: (callback: any) => + set((state: any) => ({ + zones: + typeof callback === 'function' ? callback(state.zones) : callback + })) })); interface ZonePointsState { @@ -106,9 +110,9 @@ export const useMenuVisible = create((set: any) => ({ setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), })); -export const useDeleteModels = create((set: any) => ({ - deleteModels: false, - setDeleteModels: (x: any) => set(() => ({ deleteModels: x })), +export const useDeleteTool = create((set: any) => ({ + deleteTool: false, + setDeleteTool: (x: any) => set(() => ({ deleteTool: x })), })); export const useToolMode = create((set: any) => ({ @@ -164,9 +168,9 @@ export const useSelectedWallItem = create((set: any) => ({ setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), })); -export const useselectedFloorItem = create((set: any) => ({ +export const useSelectedFloorItem = create((set: any) => ({ selectedFloorItem: null, - setselectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), + setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), })); export const useDeletableFloorItem = create((set: any) => ({ @@ -248,6 +252,11 @@ export const useActiveTool = create((set: any) => ({ setActiveTool: (x: any) => set({ activeTool: x }), })); +export const useActiveSubTool = create((set: any) => ({ + activeSubTool: "cursor", + setActiveSubTool: (x: any) => set({ activeSubTool: x }), +})); + export const use2DUndoRedo = create((set: any) => ({ is2DUndoRedo: null, set2DUndoRedo: (x: any) => set({ is2DUndoRedo: x }), @@ -302,7 +311,13 @@ export const useDrieTemp = create((set: any) => ({ export const useActiveUsers = create((set: any) => ({ activeUsers: [], - setActiveUsers: (x: any) => set({ activeUsers: x }), + setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => + set((state: { activeUsers: any[] }) => ({ + activeUsers: + typeof callback === "function" + ? callback(state.activeUsers) + : callback, + })), })); export const useDrieUIValue = create((set: any) => ({ @@ -341,24 +356,29 @@ export const useSelectedPath = create((set: any) => ({ })); interface SimulationPathsStore { - simulationPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]; - setSimulationPaths: ( + simulationStates: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]; + setSimulationStates: ( paths: - | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] - | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] - ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) + | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[] + | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[] + ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) ) => void; } -export const useSimulationPaths = create((set) => ({ - simulationPaths: [], - setSimulationPaths: (paths) => +export const useSimulationStates = create((set) => ({ + simulationStates: [], + setSimulationStates: (paths) => set((state) => ({ - simulationPaths: - typeof paths === "function" ? paths(state.simulationPaths) : paths, + simulationStates: + typeof paths === "function" ? paths(state.simulationStates) : paths, })), })) +export const useNavMesh = create((set: any) => ({ + navMesh: null, + setNavMesh: (x: any) => set({ navMesh: x }), +})); + export const useIsConnecting = create((set: any) => ({ isConnecting: false, setIsConnecting: (x: any) => set({ isConnecting: x }), @@ -435,3 +455,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/components/tools.scss b/app/src/styles/components/tools.scss index cb65697..5a10518 100644 --- a/app/src/styles/components/tools.scss +++ b/app/src/styles/components/tools.scss @@ -4,7 +4,7 @@ .tools-container { @include flex-center; position: fixed; - bottom: 50px; + bottom: 32px; left: 50%; transform: translate(-50%, 0); padding: 8px; diff --git a/app/src/styles/components/visualization/floating/common.scss b/app/src/styles/components/visualization/floating/common.scss index 7c74d54..94ea379 100644 --- a/app/src/styles/components/visualization/floating/common.scss +++ b/app/src/styles/components/visualization/floating/common.scss @@ -81,9 +81,12 @@ .returnOfInvestment { gap: 10px; + min-width: 150px; .charts { + width: 100%; height: 200px; + min-width: 150px; } .returns-wrapper { @@ -126,6 +129,12 @@ gap: 6px; border-radius: 5.2px; + width: 100%; + height: 150px; + display: flex; + justify-content: center; + align-items: center; + .header { font-size: $small; text-align: start; diff --git a/app/src/styles/layout/popup.scss b/app/src/styles/layout/popup.scss index 9d22e64..a354c10 100644 --- a/app/src/styles/layout/popup.scss +++ b/app/src/styles/layout/popup.scss @@ -146,5 +146,6 @@ font-size: var(--font-size-regulaar); font-weight: var(--font-size-regulaar); text-transform: capitalize; + white-space: nowrap; } } diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index d4cf595..3b14aac 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -70,6 +70,67 @@ position: relative; overflow: auto; + .template-list { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + min-height: 50vh; + max-height: 60vh; + } + + .template-item { + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 1rem; + transition: box-shadow 0.3s ease; + } + + .template-image-container { + position: relative; + padding-bottom: 56.25%; // 16:9 aspect ratio + } + + .template-image { + position: absolute; + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 4px; + cursor: pointer; + transition: transform 0.3s ease; + } + + .template-details { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.5rem; + } + + .template-name { + cursor: pointer; + font-weight: 500; + } + + .delete-button { + padding: 0.25rem 0.5rem; + background: #ff4444; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: opacity 0.3s ease; + } + + .no-templates { + text-align: center; + color: #666; + padding: 2rem; + grid-column: 1 / -1; + } + + .widget-left-sideBar { min-height: 50vh; max-height: 60vh; @@ -472,6 +533,15 @@ font-size: var(--font-weight-regular); color: #4a4a4a; + .reviewChart { + width: 100%; + + .floating { + width: 100%; + + } + } + .selectedWidget { padding: 6px 12px; border-top: 1px solid var(--border-color); diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index c9ea2d4..cb43853 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -7,7 +7,7 @@ border-radius: 20px; box-shadow: $box-shadow-medium; width: calc(100% - (320px + 270px + 90px)); - height: calc(100% - (200px + 80px)); + height: calc(100% - (250px)); position: absolute; top: 50%; left: calc(270px + 45px); @@ -16,10 +16,25 @@ transition: all 0.2s; z-index: #{$z-index-default}; - .floating { + .realTime-viz-wrapper { width: 100%; - max-width: 250px; - min-height: 83px; + height: 100%; + position: relative; + z-index: -1; + } + + .floating { + + + width: calc(var(--realTimeViz-container-width) * 0.2); + height: calc(var(--realTimeViz-container-width) * 0.05); + + min-width: 250px; + max-width: 300px; + + min-height: 83px !important; + // max-height: 100px !important; + background: var(--background-color); border: 1.23px solid var(--border-color); box-shadow: 0px 4.91px 4.91px 0px #0000001c; @@ -53,15 +68,16 @@ display: flex; background-color: var(--background-color); position: absolute; - bottom: 10px; + bottom: 0px; left: 50%; - transform: translate(-50%, 0); gap: 6px; - border-radius: 8px; max-width: 80%; overflow: auto; max-width: calc(100% - 500px); + min-width: 150px; + z-index: 3; + transform: translate(-50%, -10%); &::-webkit-scrollbar { display: none; @@ -108,7 +124,8 @@ } .zone-wrapper.bottom { - bottom: 210px; + bottom: var(--bottomWidth); + // bottom: 200px; } .content-container { @@ -129,7 +146,7 @@ display: flex; background-color: rgba(224, 223, 255, 0.5); position: absolute; - bottom: 10px; + // bottom: 10px; left: 50%; transform: translate(-50%, 0); gap: 6px; @@ -181,6 +198,7 @@ .panel-content { position: relative; height: 100%; + width: 100%; padding: 10px; display: flex; flex-direction: column; @@ -193,15 +211,15 @@ .chart-container { width: 100%; - height: 25% !important; - min-height: 150px; + max-height: 100%; - // border: 1px dashed var(--background-color-gray); + border: 1px dashed var(--background-color-gray); border-radius: 8px; box-shadow: var(--box-shadow-medium); padding: 6px 0; background-color: var(--background-color); position: relative; + padding: 0 10px; .kebab { width: 30px; @@ -283,16 +301,17 @@ &.bottom-panel { left: 0; right: 0; + min-height: 150px; .panel-content { display: flex; flex-direction: row; height: 100%; + width: 100%; + min-height: 150px; .chart-container { - height: 100% !important; - width: 20%; - min-width: 150px; + min-width: 160px; } } } @@ -309,29 +328,48 @@ left: 0; top: 0; bottom: 0; - - .chart-container { - width: 100%; - height: 180px; - } } &.right-panel { right: 0; top: 0; bottom: 0; + } + &.left-panel, + &.right-panel { + min-width: 150px; + + .panel-content { + flex-direction: column; + width: 100%; + + gap: 6px; + + .chart-container { + width: 100%; + min-height: 150px; + max-height: 100%; + border-radius: 8px; + box-shadow: var(--box-shadow-medium); + padding: 6px 0; + background-color: var(--background-color); + position: relative; + } + } } } .panel.hidePanel { - opacity: 0; + pointer-events: none; + opacity: 0.1; } } .playingFlase { .zone-wrapper.bottom { - bottom: 300px; + bottom: var(--bottomWidth); + // bottom: 210px; } } @@ -620,9 +658,6 @@ } } } - - - } } @@ -713,7 +748,7 @@ } .activeChart { - outline: 1px solid var(--accent-color); + outline: 2px solid var(--accent-color); z-index: 2 !important; } @@ -725,14 +760,13 @@ } .connectionSuccess { - outline-color: #43C06D; + outline-color: #43c06d; } .connectionFails { outline-color: #ffe3e0; } - .editWidgetOptions { position: absolute; // top: 50%; diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 9de35db..fc3b824 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -6,9 +6,9 @@ } .distance-text { pointer-events: none !important; - .distance { + div { position: absolute; - transform: translate(-50%, -50%) scale(.8); + transform: translate(-50%, -50%) scale(0.8); pointer-events: none !important; white-space: nowrap; // style @@ -21,3 +21,7 @@ box-shadow: var(--box-shadow-light); } } + +.pointer-none { + pointer-events: none; +} diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index 3ec0682..fefca2a 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -271,10 +271,10 @@ export type RefCSM = React.MutableRefObject; export type RefCSMHelper = React.MutableRefObject; interface PathConnection { - fromPathUUID: string; + fromModelUUID: string; fromUUID: string; toConnections: { - toPathUUID: string; + toModelUUID: string; toUUID: string; }[]; } @@ -296,7 +296,7 @@ interface ConveyorEventsSchema { rotation: [number, number, number]; actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; }[]; position: [number, number, number]; rotation: [number, number, number]; @@ -310,11 +310,45 @@ interface VehicleEventsSchema { points: { uuid: string; position: [number, number, number]; + rotation: [number, number, number]; actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; - connections: { source: { modelUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; speed: number; }; position: [number, number, number]; + rotation: [number, number, number]; +} + +interface StaticMachineEventsSchema { + modeluuid: string; + modelName: string; + type: 'StaticMachine'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; buffer: number; material: string; }; + triggers: { uuid: string; name: string; type: string }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }; + position: [number, number, number]; + rotation: [number, number, number]; +} + +interface ArmBotEventsSchema { + modeluuid: string; + modelName: string; + type: 'ArmBot'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[] }; + triggers: { uuid: string; name: string; type: string }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }; + position: [number, number, number]; + rotation: [number, number, number]; } export type EventData = { @@ -333,7 +367,7 @@ export type EventData = { rotation: [number, number, number]; actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; }[]; speed: number | string; } | { @@ -341,9 +375,30 @@ export type EventData = { points: { uuid: string; position: [number, number, number]; + rotation: [number, number, number]; actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; - connections: { source: { modelUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; speed: number; }; + } | { + type: 'StaticMachine'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; buffer: number; material: string; }; + triggers: { uuid: string; name: string; type: string }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }; + } | { + type: 'ArmBot'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[] }; + triggers: { uuid: string; name: string; type: string }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }; }; } diff --git a/app/src/utils/outerClick.ts b/app/src/utils/outerClick.ts index de8f7ef..280b3f0 100644 --- a/app/src/utils/outerClick.ts +++ b/app/src/utils/outerClick.ts @@ -1,7 +1,7 @@ import React from "react"; interface OuterClickProps { - contextClassName: string; + contextClassName: string[]; // Make sure this is an array of strings setMenuVisible: React.Dispatch>; } @@ -11,8 +11,12 @@ export default function OuterClick({ }: OuterClickProps) { const handleClick = (event: MouseEvent) => { const targets = event.target as HTMLElement; - // Check if the click is outside the selectable-dropdown-wrapper - if (!targets.closest(`.${contextClassName}`)) { + // Check if the click is outside of any of the provided class names + const isOutside = contextClassName.every( + (className) => !targets.closest(`.${className}`) + ); + + if (isOutside) { setMenuVisible(false); // Close the menu by updating the state } }; @@ -23,7 +27,7 @@ export default function OuterClick({ return () => { document.removeEventListener("click", handleClick); }; - }, []); + }, [contextClassName]); // Add contextClassName to dependency array to handle any changes return null; // This component doesn't render anything } diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts new file mode 100644 index 0000000..57fdf66 --- /dev/null +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -0,0 +1,198 @@ +// Importing React and useEffect from React library +import React, { useEffect } from "react"; +// Importing the necessary hooks and types from React and TypeScript +import useModuleStore, { useThreeDStore } from "../../store/useModuleStore"; +import useToggleStore from "../../store/useUIToggleStore"; +import { useActiveSubTool, useActiveTool, useAddAction, useDeleteTool, useSelectedWallItem, useToggleView, useToolMode } from "../../store/store"; +import { usePlayButtonStore } from "../../store/usePlayButtonStore"; + +const KeyPressListener: React.FC = () => { + // Function to detect if Shift, Ctrl, Alt, or combinations are pressed + const detectModifierKeys = (event: KeyboardEvent): string => { + const modifiers = [ + event.ctrlKey ? "Ctrl" : "", + event.altKey ? "Alt" : "", + event.shiftKey ? "Shift" : "", + event.metaKey ? "Meta" : "" // Add support for Command/Win key + ].filter(Boolean); + + // Ignore modifier keys when they're pressed alone + const isModifierKey = [ + "Control", "Shift", "Alt", "Meta", + "Ctrl", "AltGraph", "OS" // Additional modifier key aliases + ].includes(event.key); + + const mainKey = isModifierKey ? "" : event.key.toUpperCase(); + + // Handle special cases for keys with different representations + const normalizedKey = mainKey === " " ? "Space" : mainKey; + + // Build the combination string + if (modifiers.length > 0 && normalizedKey) { + return `${modifiers.join("+")}+${normalizedKey}`; + } else if (modifiers.length > 0) { + return modifiers.join("+"); + } else { + return normalizedKey; + } + }; + + // Importing the necessary hooks from the store + const { activeModule, setActiveModule } = useModuleStore(); + const { setActiveSubTool } = useActiveSubTool(); + const { toggleUI, setToggleUI } = useToggleStore(); + const { setToggleThreeD } = useThreeDStore(); + const { setToolMode } = useToolMode(); + const { setIsPlaying } = usePlayButtonStore(); + + // wall options + const { toggleView, setToggleView } = useToggleView(); + const { setDeleteTool } = useDeleteTool(); + const { setAddAction } = useAddAction(); + const { setSelectedWallItem } = useSelectedWallItem(); + const { setActiveTool } = useActiveTool(); + + useEffect(() => { + // Function to handle keydown events + const handleKeyPress = (event: KeyboardEvent) => { + + const activeElement = document.activeElement; + + const isTyping = + activeElement instanceof HTMLInputElement || + activeElement instanceof HTMLTextAreaElement || + (activeElement && activeElement.getAttribute('contenteditable') === 'true'); + + if (isTyping) { + return; // Don't trigger shortcuts while typing + } + + const keyCombination = detectModifierKeys(event); + + // Allow default behavior for F5 and F12 + if (["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") { + return; + } + + // Prevent default action for the key press + event.preventDefault(); + + // Detect the key combination pressed + if (keyCombination) { + // Check for specific key combinations to switch modules + if (keyCombination === "1" && !toggleView) { + setActiveTool("cursor"); + setActiveSubTool("cursor"); + setActiveModule("builder"); + } + if (keyCombination === "2" && !toggleView) { + setActiveTool("cursor"); + setActiveSubTool("cursor"); + setActiveModule("simulation"); + } + if (keyCombination === "3" && !toggleView) { + setActiveTool("cursor"); + setActiveSubTool("cursor"); + setActiveModule("visualization"); + } + if (keyCombination === "4" && !toggleView) { + setActiveTool("cursor"); + setActiveSubTool("cursor"); + setToggleUI(false); + setActiveModule("market"); + } + + // sidebar toggle + if (keyCombination === "Ctrl+." && activeModule !== "market") { + setToggleUI(!toggleUI); + } + + // tools toggle + if (keyCombination === "V") { + setActiveTool("cursor"); + setActiveSubTool("cursor"); + } + if (keyCombination === "X") { + setActiveTool("delete"); + setActiveSubTool("delete"); + } + if (keyCombination === "H") { + setActiveTool("free-hand"); + setActiveSubTool("free-hand"); + } + + // player toggle + if (keyCombination === "Ctrl+P" && !toggleView) { + setIsPlaying(true); + } + + // builder key combination + if (activeModule === "builder") { + if (keyCombination === "TAB") { + if (toggleView) { + setToggleView(false); + setToggleThreeD(true); + setActiveTool("cursor"); + } else { + setSelectedWallItem(null); + setDeleteTool(false); + setAddAction(null); + setToggleView(true); + setToggleThreeD(false); + setActiveTool("cursor"); + } + } + // builder tools + if (toggleView) { + if (keyCombination === "Q" || keyCombination === "6") { + setActiveTool("draw-wall"); + setToolMode("Wall"); + } + if (keyCombination === "R" || keyCombination === "7") { + setActiveTool("draw-aisle"); + setToolMode("Aisle"); + } + if (keyCombination === "E" || keyCombination === "8") { + setActiveTool("draw-zone"); + setToolMode("Zone"); + } + if (keyCombination === "T" || keyCombination === "9") { + setActiveTool("draw-floor"); + setToolMode("Floor"); + } + } + if (keyCombination === "M") { + setActiveTool("measure"); + setToolMode("MeasurementScale"); + } + } + + // Undo redo + if (keyCombination === "Ctrl+Z") { + // Handle undo action here + } + if (keyCombination === "Ctrl+Y" || keyCombination === "Ctrl+Shift+Z") { + // Handle redo action here + } + + // cleanup function to remove event listener + if (keyCombination === "ESCAPE") { + setActiveTool("cursor"); + setIsPlaying(false); + } + } + }; + + // Add event listener for keydown + window.addEventListener("keydown", handleKeyPress); + + // Cleanup function to remove event listener + return () => { + window.removeEventListener("keydown", handleKeyPress); + }; + }, [activeModule, toggleUI, toggleView]); // Empty dependency array ensures this runs only once + + return null; // No UI component to render, so return null +}; + +export default KeyPressListener;