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 9adf77f..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; - 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); } } - } - - - + }; 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 { 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 fd64db9..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, - useSimulationStates, - 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 { simulationStates, setSimulationStates } = useSimulationStates(); - 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 simulationStates - .filter( - (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" - ) - .flatMap((path) => path.points) - .find((point) => point.uuid === selectedActionSphere.points.uuid); - }, [selectedActionSphere, simulationStates]); + 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 = 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, - }; - - 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); - - setSimulationStates(updatedPaths); - }; - - const handleDeleteAction = (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.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); - - setSimulationStates(updatedPaths); - }; - - const handleActionSelect = (uuid: string, actionType: 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.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); - - setSimulationStates(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 = 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 - ); + 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; - setSimulationStates(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 = 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 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 + ); - 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; + setSimulationStates(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 - ); + // 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); - - setSimulationStates(updatedPaths); - }; - - const handleSpeedChange = (speed: number | string) => { - if (!selectedPath) return; - - const updatedPaths = simulationStates.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); - - setSimulationStates(updatedPaths); - setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); - }; - - const handleAddTrigger = () => { - if (!selectedActionSphere) return; - - 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, - }; - - 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; - setSimulationStates(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 = 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 - ), + 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; - setSimulationStates(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 = 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 - ); + 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; - setSimulationStates(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 = 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 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 + ); - setSimulationStates(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 = 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 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); - - 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 }) + 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 4db77fe..67d132f 100644 --- a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx @@ -5,6 +5,7 @@ import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionS 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(); @@ -126,6 +127,33 @@ const VehicleMechanics: React.FC = () => { 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'); setEyeDropMode(true); @@ -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 d870195..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,21 +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; @@ -59,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) => ({ @@ -70,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; @@ -111,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; @@ -140,7 +237,9 @@ export default function Dropped3dWidgets() { if (rightSelect === "Duplicate") { async function duplicateWidget() { - const widgetToDuplicate = activeZoneWidgets.find((w: WidgetData) => w.id === rightClickSelected); + const widgetToDuplicate = activeZoneWidgets.find( + (w: WidgetData) => w.id === rightClickSelected + ); if (!widgetToDuplicate) return; const newWidget: WidgetData = { id: generateUniqueId(), @@ -155,19 +254,19 @@ export default function Dropped3dWidgets() { const adding3dWidget = { organization: organization, widget: newWidget, - zoneId: selectedZone.zoneId + zoneId: selectedZone.zoneId, }; if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:add", adding3dWidget); } // let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget) - // console.log('response: ', response); + // addWidget(selectedZone.zoneId, newWidget); setRightSelect(null); setRightClickSelected(null); } - duplicateWidget() + duplicateWidget(); } if (rightSelect === "Delete") { @@ -179,7 +278,6 @@ export default function Dropped3dWidgets() { zoneId: selectedZone.zoneId, }; - console.log('deleteWidget: ', deleteWidget); if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:delete", deleteWidget); } @@ -187,10 +285,11 @@ export default function Dropped3dWidgets() { // const response = await delete3dWidgetApi(selectedZone.zoneId, organization, rightClickSelected); setZoneWidgetData( selectedZone.zoneId, - activeZoneWidgets.filter((w: WidgetData) => w.id !== rightClickSelected) + activeZoneWidgets.filter( + (w: WidgetData) => w.id !== rightClickSelected + ) ); } catch (error) { - console.error("Error deleting widget:", error); } finally { setRightClickSelected(null); setRightSelect(null); @@ -202,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) => { @@ -211,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]; } @@ -227,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(); @@ -240,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], ]); } } @@ -266,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); } @@ -277,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); } @@ -288,36 +402,42 @@ export default function Dropped3dWidgets() { const newRotation: [number, number, number] = [ currentRotation[0], currentRotation[1], - currentRotation[2] + deltaX * rotationSpeed + currentRotation[2] + deltaX * rotationSpeed, ]; updateWidgetRotation(selectedZone, rightClickSelected, newRotation); } - }; const handleMouseUp = () => { if (!rightClickSelected || !rightSelect) return; - const selectedZone = Object.keys(zoneWidgetData).find(zoneId => - zoneWidgetData[zoneId].some(widget => widget.id === rightClickSelected) + const selectedZone = Object.keys(zoneWidgetData).find((zoneId) => + zoneWidgetData[zoneId].some( + (widget) => widget.id === rightClickSelected + ) ); if (!selectedZone) return; - const selectedWidget = zoneWidgetData[selectedZone].find(widget => widget.id === rightClickSelected); + const selectedWidget = zoneWidgetData[selectedZone].find( + (widget) => widget.id === rightClickSelected + ); if (!selectedWidget) return; - - - // Format values to 2 decimal places - const formatValues = (vals: number[]) => vals.map(val => parseFloat(val.toFixed(2))); - - if (rightSelect === "Horizontal Move" || rightSelect === "Vertical Move") { - console.log(`${rightSelect} Completed - Full Position:`, formatValues(selectedWidget.position)); - let lastPosition = formatValues(selectedWidget.position) as [number, number, number]; + const formatValues = (vals: number[]) => + vals.map((val) => parseFloat(val.toFixed(2))); + if ( + rightSelect === "Horizontal Move" || + rightSelect === "Vertical Move" + ) { + let lastPosition = formatValues(selectedWidget.position) as [ + number, + number, + number + ]; // (async () => { // let response = await update3dWidget(selectedZone, organization, rightClickSelected, lastPosition); - // console.log('response: ', response); + // // if (response) { - // console.log("Widget position updated in API:", response); + // // } // })(); let updatingPosition = { @@ -325,21 +445,22 @@ export default function Dropped3dWidgets() { zoneId: selectedZone, id: rightClickSelected, position: lastPosition, - } + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-3D-widget:modifyPositionRotation", updatingPosition); + visualizationSocket.emit( + "v2:viz-3D-widget:modifyPositionRotation", + updatingPosition + ); } - - } - else if (rightSelect.includes("Rotate")) { + } else if (rightSelect.includes("Rotate")) { const rotation = selectedWidget.rotation || [0, 0, 0]; - console.log(`${rightSelect} Completed - Full Rotation:`, formatValues(rotation)); + let lastRotation = formatValues(rotation) as [number, number, number]; // (async () => { // let response = await update3dWidgetRotation(selectedZone, organization, rightClickSelected, lastRotation); - // console.log('response: ', response); + // // if (response) { - // console.log("Widget position updated in API:", response); + // // } // })(); let updatingRotation = { @@ -347,9 +468,12 @@ export default function Dropped3dWidgets() { zoneId: selectedZone, id: rightClickSelected, rotation: lastRotation, - } + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-3D-widget:modifyPositionRotation", updatingRotation); + visualizationSocket.emit( + "v2:viz-3D-widget:modifyPositionRotation", + updatingRotation + ); } } @@ -372,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 000bc20..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 { @@ -33,10 +39,13 @@ interface ListProps { const List: React.FC = ({ items = [], remove }) => { const { activeModule, setActiveModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); + const { setSubModule } = useSubModuleStore(); const [expandedZones, setExpandedZones] = useState>( {} ); + const { floorItems, setFloorItems } = useFloorItems(); useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ @@ -61,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; } @@ -83,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) => (
  • @@ -102,7 +146,7 @@ const List: React.FC = ({ items = [], remove }) => { className="value" onClick={() => handleSelectZone(item.id)} > - +
@@ -139,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 78434d9..5b8a2e4 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -18,18 +18,21 @@ type PathPoints = { interface ProcessContainerProps { processes: any[]; agvRef: any; + MaterialRef: any; } -const Agv: React.FC = ({ processes, agvRef }) => { +const Agv: React.FC = ({ + processes, + agvRef, + MaterialRef, +}) => { const [pathPoints, setPathPoints] = useState([]); const { simulationStates } = useSimulationStates(); const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); const { PlayAgv, setPlayAgv } = usePlayAgv(); - useEffect(() => { - console.log("agvRef: ", agvRef); - }, [agvRef]); + useEffect(() => { if (simulationStates.length > 0) { @@ -90,6 +93,7 @@ const Agv: React.FC = ({ processes, agvRef }) => { hitCount={pair.hitCount} processes={processes} agvRef={agvRef} + MaterialRef={MaterialRef} /> {pair.points.slice(1).map((point, idx) => ( 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 7d17ee6..d68da57 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -1,10 +1,11 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState, useRef, useMemo } from "react"; 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 { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { usePlayAgv } from "../../../store/store"; +import crate from "../../../assets/gltf-glb/crate_box.glb"; interface PathNavigatorProps { navMesh: any; @@ -15,6 +16,7 @@ interface PathNavigatorProps { hitCount: number; processes: any[]; agvRef: any; + MaterialRef: any; } interface AGVData { processId: string; @@ -23,7 +25,7 @@ interface AGVData { totalHits: number; } type Phase = "initial" | "toDrop" | "toPickup"; - +type MaterialType = "Box" | "Crate"; export default function PathNavigator({ navMesh, pathPoints, @@ -33,11 +35,13 @@ export default function PathNavigator({ hitCount, processes, agvRef, + MaterialRef, }: PathNavigatorProps) { const [currentPhase, setCurrentPhase] = useState("initial"); - // console.log('agvRef: ', agvRef); + // const [path, setPath] = useState<[number, number, number][]>([]); + const PickUpDrop = useRef([]); // const [currentPhase, setCurrentPhase] = useState<"initial" | "loop">( // "initial" @@ -45,6 +49,7 @@ export default function PathNavigator({ const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>( [] ); + const [pickupDropPath, setPickupDropPath] = useState< [number, number, number][] >([]); @@ -57,6 +62,7 @@ export default function PathNavigator({ const [initialRotation, setInitialRotation] = useState( null ); + const [boxVisible, setBoxVisible] = useState(false); const distancesRef = useRef([]); const totalDistanceRef = useRef(0); @@ -123,6 +129,9 @@ export default function PathNavigator({ if (!navMesh || pathPoints.length < 2) return; const [pickup, drop] = pathPoints.slice(-2); + + PickUpDrop.current = pathPoints.slice(-2); + const object = scene.getObjectByProperty("uuid", id); if (!object) return; @@ -171,13 +180,101 @@ export default function PathNavigator({ // Add these refs outside the useFrame if not already present: const startPointReached = useRef(false); + const baseMaterials = useMemo( + () => ({ + Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), + Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), + // Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), + Default: new THREE.MeshStandardMaterial(), + }), + [] + ); + + // Example usage: + const targetModelUUID = id; // Replace with your target ID + const matchedProcess = findProcessByTargetModelUUID( + processes, + targetModelUUID + ); + + function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) { + for (const process of processes) { + for (const path of process.paths) { + for (const point of path.points) { + if ( + point.connections?.targets?.some( + (target: any) => target.modelUUID === targetModelUUID + ) + ) { + // + return process.id; // Return the process if a match is found + } + } + } + } + return null; // Return null if no match is found + } + useFrame((_, delta) => {}); + const boxRef = useRef(null); + + useEffect(() => { + if (!scene || !boxRef || !processes) return; + + // 1. Find the existing object by UUID + const existingObject = scene.getObjectByProperty("uuid", id); + if (!existingObject) return; + + // 2. Find the process that targets this object's modelUUID + if (boxVisible) { + const matchedProcess = findProcessByTargetModelUUID(processes, id); + + // 3. Determine the material (from materialref) if a process is matched + let materialType = "Default"; + + if (matchedProcess) { + const materialEntry = MaterialRef.current.find((item: any) => + item.objects.some((obj: any) => obj.processId === matchedProcess) + ); + console.log("materialEntry: ", materialEntry); + if (materialEntry) { + materialType = materialEntry.material; // "Box" or "Crate" + } + } + + // 4. Create the box mesh with the assigned material + const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); + const boxMaterial = baseMaterials[materialType as MaterialType]; + const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial); + boxMesh.position.y = 1; + boxMesh.name = `box-${id}`; + + // 5. Add to scene and cleanup + existingObject.add(boxMesh); + boxRef.current = boxMesh; + } + + if (!startPointReached.current && boxVisible) { + setBoxVisible(false); + } + return () => { + if (boxRef.current?.parent) { + boxRef.current.parent.remove(boxRef.current); + } + boxRef.current = null; + }; + }, [ + processes, + MaterialRef, + boxVisible, + findProcessByTargetModelUUID, + startPointReached, + ]); useFrame((_, delta) => { const currentAgv = (agvRef.current || []).find( (agv: AGVData) => agv.vehicleId === id ); - console.log("currentAgv: ", currentAgv?.isplaying); if (!scene || !id || !isPlaying) return; @@ -203,6 +300,7 @@ export default function PathNavigator({ // Step 1: Snap to start point on first play if (isPlaying && !hasStarted.current && toPickupPath.length > 0) { + setBoxVisible(false); const startPoint = new THREE.Vector3(...toPickupPath[0]); object.position.copy(startPoint); @@ -216,23 +314,23 @@ export default function PathNavigator({ startPointReached.current = true; progressRef.current = 0; + setToPickupPath(toPickupPath.slice(-1)); + return; } - // Step 2: Wait at start point for AGV readiness (only if expected hit count is not met) - if ( - isPlaying && - startPointReached.current && - path.length === 0 && - currentAgv?.isplaying - ) { + // Step 2: Wait at start point for AGV readiness + if (isPlaying && startPointReached.current && path.length === 0) { if (!isAgvReady()) { return; // Prevent transitioning to the next phase if AGV is not ready } + setBoxVisible(true); + setPath([...toPickupPath]); // Start path transition once the AGV is ready setCurrentPhase("toDrop"); progressRef.current = 0; + console.log("startPointReached: ", startPointReached.current); startPointReached.current = false; return; @@ -283,12 +381,44 @@ export default function PathNavigator({ if (currentPhase === "toDrop") { nextPath = dropPickupPath; nextPhase = "toPickup"; + setBoxVisible(false); } else if (currentPhase === "toPickup") { - nextPath = pickupDropPath; - nextPhase = "toDrop"; + // When returning to start point (toPickup phase completed) + // Set position to toPickupPath[1] instead of [0] + if (toPickupPath.length > 1) { + object.position.copy(new THREE.Vector3(...toPickupPath[1])); + // Also set rotation towards the next point if available + if (toPickupPath.length > 2) { + const nextPoint = new THREE.Vector3(...toPickupPath[2]); + const direction = nextPoint + .clone() + .sub(object.position) + .normalize(); + object.rotation.y = Math.atan2(direction.x, direction.z); + } + } + + // Reset the path and mark start point as reached + setPath([]); + startPointReached.current = true; + progressRef.current = 0; + distancesRef.current = []; + + // Stop the AGV by setting isplaying to false + if (currentAgv) { + currentAgv.isplaying = false; + } + + // Clear timeout and return to prevent further movement + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + return; } else { nextPath = pickupDropPath; nextPhase = "toDrop"; + setBoxVisible(true); } setPath([...nextPath]); @@ -297,28 +427,12 @@ export default function PathNavigator({ isWaiting.current = false; distancesRef.current = []; - // Decrease the expected count if AGV is ready and has completed its path - if (currentAgv) { - currentAgv.expectedCount = Math.max(0, currentAgv.expectedCount - 1); // Decrease but ensure it's not negative - console.log( - "Decreased expected count to: ", - currentAgv.expectedCount - ); - } - + // Reset hit count for the next cycle if (agvRef.current) { agvRef.current = agvRef.current.map((agv: AGVData) => - agv.vehicleId === id ? { ...agv, hitCount: null } : agv + agv.vehicleId === id ? { ...agv, hitCount: 0 } : agv ); } - - // 🔁 Reset and wait again after reaching start - if (currentPhase === "toPickup" && currentAgv) { - currentAgv.isplaying = false; - setPath([]); - startPointReached.current = true; - progressRef.current = 0; - } }, bufferTime * 1000); return; 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 0284245..1b1c959 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -86,8 +86,8 @@ 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, setSimulationStates, socket); @@ -220,7 +220,7 @@ async function handleModelLoad( eventData.position = newFloorItem.position; eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), eventData as Types.ConveyorEventsSchema ]); @@ -237,6 +237,7 @@ async function handleModelLoad( 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', type: '', start: {}, hitCount: 1, end: {}, buffer: 0 }, connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] }, speed: 2, @@ -283,13 +284,141 @@ async function handleModelLoad( return updatedItems; }); - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + 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 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/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..307d51f 100644 --- a/app/src/modules/builder/geomentries/lines/distanceText.tsx +++ b/app/src/modules/builder/geomentries/lines/distanceText/distanceText.tsx @@ -1,143 +1,143 @@ -import { useEffect, useState } from "react"; -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"; -import { Html } from "@react-three/drei"; -import * as Types from "../../../../types/world/worldTypes"; - -const DistanceText = () => { - const [lines, setLines] = useState< - { - distance: string; - position: THREE.Vector3; - userData: Types.Line; - layer: string; - }[] - >([]); - const { activeLayer } = useActiveLayer(); - const { toggleView } = useToggleView(); - const { newLines, setNewLines } = useNewLines(); - const { deletedLines, setDeletedLines } = useDeletedLines(); - - useEffect(() => { - const email = localStorage.getItem("email"); - if (!email) return; - const organization = email.split("@")[1].split(".")[0]; - - getLines(organization).then((data) => { - data = objectLinesToArray(data); - - const lines = data - .filter((line: Types.Line) => line[0][2] === activeLayer) - .map((line: Types.Line) => { - const point1 = new THREE.Vector3( - line[0][0].x, - line[0][0].y, - line[0][0].z - ); - const point2 = new THREE.Vector3( - line[1][0].x, - line[1][0].y, - line[1][0].z - ); - const distance = point1.distanceTo(point2); - const midpoint = new THREE.Vector3() - .addVectors(point1, point2) - .divideScalar(2); - return { - distance: distance.toFixed(1), - position: midpoint, - userData: line, - layer: activeLayer, - }; - }); - setLines(lines); - }); - }, [activeLayer]); - - useEffect(() => { - if (newLines.length > 0) { - if (newLines[0][0][2] !== activeLayer) return; - const newLinesData = newLines.map((line: Types.Line) => { - const point1 = new THREE.Vector3( - line[0][0].x, - line[0][0].y, - line[0][0].z - ); - const point2 = new THREE.Vector3( - line[1][0].x, - line[1][0].y, - line[1][0].z - ); - const distance = point1.distanceTo(point2); - const midpoint = new THREE.Vector3() - .addVectors(point1, point2) - .divideScalar(2); - - return { - distance: distance.toFixed(1), - position: midpoint, - userData: line, - layer: activeLayer, - }; - }); - setLines((prevLines) => [...prevLines, ...newLinesData]); - setNewLines([]); - } - }, [newLines, activeLayer]); - - useEffect(() => { - if ((deletedLines as Types.Lines).length > 0) { - setLines((prevLines) => - prevLines.filter( - (line) => - !deletedLines.some( - (deletedLine: any) => - deletedLine[0][1] === line.userData[0][1] && - deletedLine[1][1] === line.userData[1][1] - ) - ) - ); - setDeletedLines([]); - } - }, [deletedLines]); - - return ( - <> - {toggleView && ( - - {lines.map((text) => ( - -
- {text.distance} m -
- - ))} -
- )} - - ); -}; - -export default DistanceText; +import { useEffect, useState } from "react"; +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"; +import { Html } from "@react-three/drei"; +import * as Types from "../../../../../types/world/worldTypes"; + +const DistanceText = () => { + const [lines, setLines] = useState< + { + distance: string; + position: THREE.Vector3; + userData: Types.Line; + layer: string; + }[] + >([]); + const { activeLayer } = useActiveLayer(); + const { toggleView } = useToggleView(); + const { newLines, setNewLines } = useNewLines(); + const { deletedLines, setDeletedLines } = useDeletedLines(); + + useEffect(() => { + const email = localStorage.getItem("email"); + if (!email) return; + const organization = email.split("@")[1].split(".")[0]; + + getLines(organization).then((data) => { + data = objectLinesToArray(data); + + const lines = data + .filter((line: Types.Line) => line[0][2] === activeLayer) + .map((line: Types.Line) => { + const point1 = new THREE.Vector3( + line[0][0].x, + line[0][0].y, + line[0][0].z + ); + const point2 = new THREE.Vector3( + line[1][0].x, + line[1][0].y, + line[1][0].z + ); + const distance = point1.distanceTo(point2); + const midpoint = new THREE.Vector3() + .addVectors(point1, point2) + .divideScalar(2); + return { + distance: distance.toFixed(1), + position: midpoint, + userData: line, + layer: activeLayer, + }; + }); + setLines(lines); + }); + }, [activeLayer]); + + useEffect(() => { + if (newLines.length > 0) { + if (newLines[0][0][2] !== activeLayer) return; + const newLinesData = newLines.map((line: Types.Line) => { + const point1 = new THREE.Vector3( + line[0][0].x, + line[0][0].y, + line[0][0].z + ); + const point2 = new THREE.Vector3( + line[1][0].x, + line[1][0].y, + line[1][0].z + ); + const distance = point1.distanceTo(point2); + const midpoint = new THREE.Vector3() + .addVectors(point1, point2) + .divideScalar(2); + + return { + distance: distance.toFixed(1), + position: midpoint, + userData: line, + layer: activeLayer, + }; + }); + setLines((prevLines) => [...prevLines, ...newLinesData]); + setNewLines([]); + } + }, [newLines, activeLayer]); + + useEffect(() => { + if ((deletedLines as Types.Lines).length > 0) { + setLines((prevLines) => + prevLines.filter( + (line) => + !deletedLines.some( + (deletedLine: any) => + deletedLine[0][1] === line.userData[0][1] && + deletedLine[1][1] === line.userData[1][1] + ) + ) + ); + setDeletedLines([]); + } + }, [deletedLines]); + + return ( + <> + {toggleView && ( + + {lines.map((text) => ( + +
+ {text.distance} m +
+ + ))} +
+ )} + + ); +}; + +export default DistanceText; 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..7342b1e --- /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 96f4fad..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, - useSimulationStates, - 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 { simulationStates, setSimulationStates } = useSimulationStates(); - 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, setSimulationStates); - 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, setSimulationStates); - 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, - setSimulationStates, - 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, - setSimulationStates, - 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 a1383f2..95a9833 100644 --- a/app/src/modules/collaboration/collabCams.tsx +++ b/app/src/modules/collaboration/collabCams.tsx @@ -185,10 +185,9 @@ const CamModelsGroup = () => { position={[-0.015, 0, 0.7]} > diff --git a/app/src/modules/collaboration/collabUserIcon.tsx b/app/src/modules/collaboration/collabUserIcon.tsx index 4cea436..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,24 +18,7 @@ const CollabUserIcon: React.FC = ({ {userImage ? ( {userName} ) : ( - - //
- // {userName[0]} - //
+ )}
diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx index ceef567..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); diff --git a/app/src/modules/collaboration/users/Avatar.tsx b/app/src/modules/collaboration/users/Avatar.tsx index b04ff7d..d3e5dca 100644 --- a/app/src/modules/collaboration/users/Avatar.tsx +++ b/app/src/modules/collaboration/users/Avatar.tsx @@ -5,7 +5,6 @@ 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 } @@ -13,7 +12,6 @@ interface AvatarProps { const CustomAvatar: React.FC = ({ name, size = 100, - index = 0, textColor = "#ffffff", color, // Optional color prop for future use }) => { @@ -28,7 +26,7 @@ const CustomAvatar: React.FC = ({ const initials = getInitials(name); // Convert name to initials if needed // Draw background - ctx.fillStyle = color || getAvatarColor(index); // Use color prop or generate color based on index + ctx.fillStyle = color || "#323232"; // Use color prop or generate color based on index ctx.fillRect(0, 0, size, size); // Draw initials @@ -42,7 +40,7 @@ const CustomAvatar: React.FC = ({ const dataURL = canvas.toDataURL("image/png"); setImageSrc(dataURL); } - }, [name, size, textColor, index]); + }, [name, size, textColor]); if (!imageSrc) { return null; // Return null while the image is being generated @@ -55,18 +53,6 @@ const CustomAvatar: React.FC = ({ alt="User Avatar" style={{ width: "100%", height: "100%" }} /> - //
- // {name[0]} - //
); }; 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 3cafc32..c93b660 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, - setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => 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}`; @@ -83,16 +83,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, setSimulationStates); - 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}:`); @@ -105,17 +103,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, setSimulationStates); - 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 || item.modelfileID === '67e3db95c2e8f37134526fb2') { + if (item.eventData) { processEventData(item, setSimulationStates); } @@ -158,7 +154,7 @@ function processLoadedModel( item: Types.EventData, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, - setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => void + setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => void ) { const model = gltf; model.uuid = item.modeluuid; @@ -193,7 +189,7 @@ function processLoadedModel( }, ]); - if (item.eventData || item.modelfileID === '67e3db95c2e8f37134526fb2') { + if (item.eventData) { processEventData(item, setSimulationStates); } @@ -211,7 +207,7 @@ function processEventData(item: Types.EventData, setSimulationStates: any) { data.position = item.position; data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), data as Types.ConveyorEventsSchema ]); @@ -222,35 +218,39 @@ function processEventData(item: Types.EventData, setSimulationStates: any) { 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)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), data as Types.VehicleEventsSchema ]); - } else if (item.modelfileID === '67e3db95c2e8f37134526fb2') { + } else if (item.eventData?.type === 'StaticMachine') { - const pointUUID = THREE.MathUtils.generateUUID(); - const pointPosition = new THREE.Vector3(0, 1.75, 0); + 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]; - const staticMachine: Types.StaticMachineEventsSchema = { - modeluuid: item.modeluuid, - modelName: item.modelname, - type: "StaticMachine", - points: { - uuid: pointUUID, - position: [pointPosition.x, pointPosition.y, pointPosition.z], - actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', buffer: 'Inherit', material: 'Inherit', isUsed: false }, - triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, - connections: { source: { modelUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, - }, - position: item.position - }; - - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), - staticMachine as Types.StaticMachineEventsSchema + 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/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index 7b494cd..aed1325 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -151,7 +151,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.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,7 +181,10 @@ 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: { modelUUID: obj.uuid, pointUUID }, targets: [] @@ -234,7 +237,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + 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, @@ -312,14 +316,206 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas 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)[]) => [ + 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 { diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index 852b541..9d40a72 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -132,7 +132,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.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,7 +163,10 @@ 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: { 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 }; @@ -216,7 +219,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + 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, @@ -294,14 +298,206 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb 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)[]) => [ + 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 { diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx index 2693531..2a11c53 100644 --- a/app/src/modules/scene/controls/selection/moveControls.tsx +++ b/app/src/modules/scene/controls/selection/moveControls.tsx @@ -180,12 +180,12 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.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"; - if (eventData && eventData.type !== 'StaticMachine') { + if (eventData) { if (eventData.type === 'Conveyor' && eventData) { const backendEventData = { @@ -229,7 +229,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -279,8 +279,115 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje 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)[]) => { + 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 020705d..90143ca 100644 --- a/app/src/modules/scene/controls/selection/rotateControls.tsx +++ b/app/src/modules/scene/controls/selection/rotateControls.tsx @@ -184,13 +184,12 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); - console.log('eventData: ', eventData); + 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"; - if (eventData && eventData.type !== 'StaticMachine') { + if (eventData) { if (eventData.type === 'Conveyor' && eventData) { const backendEventData = { @@ -233,9 +232,8 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - console.log('newEventData: ', newEventData); - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -286,8 +284,115 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo 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)[]) => { + 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/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index 8a464d5..11c2dfb 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -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 = () => { } }); - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + 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 678d1a4..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 ////////// diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index 8a295be..c561cbb 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -1,9 +1,9 @@ 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, useSimulationStates, useSocketStore } 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'; @@ -11,28 +11,23 @@ import { setEventApi } from '../../../services/factoryBuilder/assest/floorAsset/ 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 { simulationStates, setSimulationStates } = useSimulationStates(); const { isPlaying } = usePlayButtonStore(); const { socket } = useSocketStore(); + const groupRefs = useRef<{ [key: string]: any }>({}); - const [firstSelected, setFirstSelected] = useState<{ - modelUUID: 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 = ( - fromModelUUID: string, - fromPointUUID: string, - toModelUUID: string, - toPointUUID: string - ) => { + const updatePathConnections = (fromModelUUID: string, fromPointUUID: string, toModelUUID: string, toPointUUID: string) => { const updatedPaths = simulationStates.map(path => { if (path.type === 'Conveyor') { + // Handle outgoing connections from Conveyor if (path.modeluuid === fromModelUUID) { return { ...path, @@ -61,6 +56,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }) }; } + // Handle incoming connections to Conveyor else if (path.modeluuid === toModelUUID) { return { ...path, @@ -167,82 +163,170 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } return path; } - // else if (path.type === 'StaticMachine') { - // if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { - // const newTarget = { - // modelUUID: toModelUUID, - // pointUUID: toPointUUID - // }; - // const existingTargets = path.points.connections.targets || []; + else if (path.type === 'StaticMachine') { + // Handle outgoing connections from StaticMachine + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { + const newTarget = { + modelUUID: toModelUUID, + pointUUID: toPointUUID + }; - // // Check if 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; - // } + // 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; + } - // // Check if already has a connection - // if (existingTargets.length >= 1) { - // console.log("StaticMachine can have only one connection"); - // return path; - // } + const existingTargets = path.points.connections.targets || []; - // 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 existingTargets = path.points.connections.targets || []; + // Allow only one connection + if (existingTargets.length >= 1) { + console.log("StaticMachine can only have one connection"); + return path; + } - // // Check if source is an ArmBot - // const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); - // if (fromPath?.type !== 'ArmBot') { - // console.log("StaticMachine can only connect to ArmBot"); - // 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] + } + } + }; + } + } - // // Check if already has a connection - // if (existingTargets.length >= 1) { - // console.log("StaticMachine can have only one connection"); - // return path; - // } + // 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; + } - // 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; }); @@ -255,7 +339,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec updateBackend(updatedPathDetails); }; - const updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + 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] : ""; @@ -293,6 +377,38 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec 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); + } }) @@ -397,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"); @@ -437,6 +552,69 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec 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."); @@ -444,20 +622,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } // All checks passed - make the connection - handleAddConnection( - firstSelected.modelUUID, - firstSelected.sphereUUID, - modelUUID, - sphereUUID - ); + handleAddConnection(firstSelected.modelUUID, firstSelected.sphereUUID, modelUUID, sphereUUID); } else { // First selection - just store it - setFirstSelected({ - modelUUID, - sphereUUID, - position: worldPosition, - isCorner: isStartOrEnd - }); + setFirstSelected({ modelUUID, sphereUUID, position: worldPosition, isCorner: isStartOrEnd }); setIsConnecting(true); } } @@ -470,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); @@ -487,7 +655,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("contextmenu", onContextMenu); }; - }, [camera, scene, raycaster, firstSelected, simulationStates]); + }, [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) { @@ -511,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; @@ -528,15 +703,21 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec 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 = simulationStates.some(path => { @@ -574,12 +755,50 @@ 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.modelUUID !== modelUUID && (firstSelected.isCorner || isConnectable) && @@ -596,6 +815,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } else { isInvalidConnection = true; } + } if (snappedSphere) { @@ -632,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 ( - + {simulationStates.flatMap(path => { if (path.type === 'Conveyor') { return path.points.flatMap(point => point.connections.targets.map((target, index) => { const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); - if (targetPath?.type === 'Vehicle') return null; + 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); @@ -652,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); @@ -689,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 []; })} @@ -730,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 62bb79e..3e13d27 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -206,6 +206,7 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec return ( (groupRefs.current[path.modeluuid] = el!)} position={path.position} @@ -271,10 +272,11 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec })} ); - } else if (path.type === "Vehicle" || path.type === "StaticMachine") { + } else if (path.type === "Vehicle") { return ( (groupRefs.current[path.modeluuid] = el!)} position={path.position} @@ -323,6 +325,112 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec ); + } else if (path.type === "StaticMachine") { + 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 === "ArmBot") { + 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; })} diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index a790d19..7b906d2 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -9,17 +9,20 @@ import { useProcessAnimation } from "./useProcessAnimations"; import ProcessObject from "./processObject"; import { ProcessData } from "./types"; import { useSimulationStates } from "../../../store/store"; +import { retrieveGLTF } from "../../../utils/indexDB/idbUtils"; interface ProcessContainerProps { processes: ProcessData[]; setProcesses: React.Dispatch>; agvRef: any; + MaterialRef: any; } const ProcessAnimator: React.FC = ({ processes, setProcesses, agvRef, + MaterialRef, }) => { const gltf = useLoader(GLTFLoader, crate) as GLTF; const groupRef = useRef(null); @@ -44,11 +47,57 @@ const ProcessAnimator: React.FC = ({ () => ({ Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), - Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), + // Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), + Default: new THREE.MeshStandardMaterial(), }), [] ); + useEffect(() => { + // Update material references for all spawned objects + Object.entries(animationStates).forEach(([processId, processState]) => { + Object.keys(processState.spawnedObjects).forEach((objectId) => { + const entry = { + processId, + objectId, + }; + + const materialType = + processState.spawnedObjects[objectId]?.currentMaterialType; + + if (!materialType) { + return; + } + + const matRefArray = MaterialRef.current; + + // Find existing material group + const existing = matRefArray.find( + (entryGroup: { material: string; objects: any[] }) => + entryGroup.material === materialType + ); + + if (existing) { + // Check if this processId + objectId already exists + const alreadyExists = existing.objects.some( + (o: any) => + o.processId === entry.processId && o.objectId === entry.objectId + ); + + if (!alreadyExists) { + existing.objects.push(entry); + } + } else { + // Create new group for this material type + matRefArray.push({ + material: materialType, + objects: [entry], + }); + } + }); + }); + }, [animationStates, MaterialRef, agvRef]); + // In processAnimator.tsx - only the relevant spawn logic part that needs fixes useFrame(() => { @@ -252,14 +301,39 @@ const ProcessAnimator: React.FC = ({ const nextPointIdx = stateRef.currentIndex + 1; const isLastPoint = nextPointIdx >= path.length; + // if (isLastPoint) { + // if (currentPointData?.actions) { + // const shouldStop = !hasNonInheritActions( + // currentPointData.actions + // ); + // if (shouldStop) { + // return; + // } + // } + // } + if (isLastPoint) { if (currentPointData?.actions) { - const shouldStop = !hasNonInheritActions( + const hasNonInherit = hasNonInheritActions( currentPointData.actions ); - if (shouldStop) { + if (!hasNonInherit) { + // Remove the object if all actions are inherit + updatedObjects[objectId] = { + ...obj, + visible: false, + state: { ...stateRef, isAnimating: false }, + }; return; } + } else { + // No actions at last point - remove the object + updatedObjects[objectId] = { + ...obj, + visible: false, + state: { ...stateRef, isAnimating: false }, + }; + return; } } @@ -329,6 +403,7 @@ const ProcessAnimator: React.FC = ({ .filter(([_, obj]) => obj.visible) .map(([objectId, obj]) => { const process = processedProcesses.find((p) => p.id === processId); + const renderAs = process?.renderAs || "custom"; return ( diff --git a/app/src/modules/simulation/process/processContainer.tsx b/app/src/modules/simulation/process/processContainer.tsx index a0378db..1cbc75b 100644 --- a/app/src/modules/simulation/process/processContainer.tsx +++ b/app/src/modules/simulation/process/processContainer.tsx @@ -6,18 +6,24 @@ interface ProcessContainerProps { processes: any[]; setProcesses: React.Dispatch>; agvRef: any; + MaterialRef: any; } const ProcessContainer: React.FC = ({ processes, setProcesses, - agvRef + agvRef, + MaterialRef, }) => { - console.log("processes: ", processes); return ( <> - + ); }; diff --git a/app/src/modules/simulation/process/useProcessAnimations.tsx b/app/src/modules/simulation/process/useProcessAnimations.tsx index 9280a78..7525c29 100644 --- a/app/src/modules/simulation/process/useProcessAnimations.tsx +++ b/app/src/modules/simulation/process/useProcessAnimations.tsx @@ -25,7 +25,6 @@ interface EnhancedProcessAnimationState extends ProcessAnimationState { pointId: string; objectId: string; triggerId: string; - hasSpawnedZeroIntervalObject?: boolean; }>; } @@ -139,7 +138,6 @@ export const useProcessAnimation = ( processDelayDuration: 0, triggerCounts, triggerLogs: [], - hasSpawnedZeroIntervalObject: false, // Initialize the new property }; }); @@ -453,7 +451,7 @@ export const useProcessAnimation = ( // Increment expectedHitCount if not playing if (!vehicleEntry.isplaying) { - vehicleEntry.expectedHitCount = processTotalHits + 1; + vehicleEntry.expectedHitCount = processTotalHits; } // Set vehicle's hitCount to the processTotalHits diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index bd3eb58..5bcf7d1 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -18,6 +18,7 @@ function Simulation() { const { simulationStates, setSimulationStates } = useSimulationStates(); const [processes, setProcesses] = useState([]); const agvRef = useRef([]); + const MaterialRef = useRef([]); return ( <> @@ -26,8 +27,17 @@ function Simulation() { <> - - + + )} 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 9ee6b5b..f1abaaa 100644 --- a/app/src/modules/visualization/realTimeVizSocket.dev.tsx +++ b/app/src/modules/visualization/realTimeVizSocket.dev.tsx @@ -15,22 +15,19 @@ type WidgetData = { 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) @@ -38,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) => { @@ -65,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) => ({ @@ -78,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") { @@ -105,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) => { @@ -133,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); @@ -142,9 +157,9 @@ export default function SocketRealTimeViz() { //add 3D Widget response visualizationSocket.on("viz-widget3D:response:updates", (add3DWidget: any) => { - console.log('add3DWidget: ', add3DWidget); + if (add3DWidget.success) { - console.log('add3DWidget: ', add3DWidget); + if (add3DWidget.message === "Widget created successfully") { addWidget(add3DWidget.data.zoneId, add3DWidget.data.widget); } @@ -152,7 +167,7 @@ export default function SocketRealTimeViz() { }); //delete 3D Widget response visualizationSocket.on("viz-widget3D:response:delete", (delete3DWidget: any) => { - console.log('delete3DWidget: ', delete3DWidget); + // "3DWidget delete unsuccessfull" if (delete3DWidget.success && delete3DWidget.message === "3DWidget delete successfull") { const activeZoneWidgets = zoneWidgetData[delete3DWidget.data.zoneId] || []; @@ -164,16 +179,16 @@ export default function SocketRealTimeViz() { }); //update3D widget response visualizationSocket.on("viz-widget3D:response:modifyPositionRotation", (update3DWidget: any) => { - console.log('update3DWidget: ', update3DWidget); - - if (update3DWidget.success && update3DWidget.message==="widget update successfully") { + + + 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") { @@ -183,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") { @@ -206,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/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/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 3c7e4a9..3210afc 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -68,7 +68,10 @@ 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 +109,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 +167,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 +251,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 }), @@ -349,6 +357,7 @@ interface SimulationPathsStore { | Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema + | Types.ArmBotEventsSchema )[]; setSimulationStates: ( paths: @@ -356,17 +365,20 @@ interface SimulationPathsStore { | 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; } @@ -467,3 +479,22 @@ export const usePlayAgv = create((set, get) => ({ setPlayAgv: (updateFn: (prev: any[]) => any[]) => set({ PlayAgv: updateFn(get().PlayAgv) }), })); +// 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 }), +})); 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 6909081..19cd2b4 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; @@ -173,12 +190,15 @@ overflow: auto; z-index: $z-index-tools; overflow: auto; + &::-webkit-scrollbar { display: none; } + .panel-content { position: relative; height: 100%; + width: 100%; padding: 10px; display: flex; flex-direction: column; @@ -191,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; @@ -284,16 +304,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; } } } @@ -310,29 +331,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; } } @@ -621,9 +661,6 @@ } } } - - - } } @@ -714,19 +751,18 @@ } .activeChart { - outline: 1px solid var(--accent-color); + outline: 2px solid var(--accent-color); z-index: 2 !important; } .connectionSuccess { - outline-color: #43C06D; + outline-color: #43c06d; } .connectionFails { outline-color: #ffe3e0; } - .editWidgetOptions { position: absolute; // top: 50%; @@ -739,7 +775,8 @@ border-radius: 6px; overflow: hidden; -min-width: 150px; + min-width: 150px; + .option { padding: 8px 10px; color: var(--text-color); 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 811b7de..3197198 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -455,6 +455,7 @@ export type EventData = { points: { uuid: string; position: [number, number, number]; + rotation: [number, number, number]; actions: { uuid: string; name: string; @@ -470,5 +471,48 @@ export type EventData = { }; 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..3dab047 --- /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;