v2 #74
|
@ -3,23 +3,20 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import Project from "./pages/Project";
|
import Project from "./pages/Project";
|
||||||
import UserAuth from "./pages/UserAuth";
|
import UserAuth from "./pages/UserAuth";
|
||||||
import ToastProvider from "./components/templates/ToastProvider";
|
import "./styles/main.scss";
|
||||||
import "./styles/main.scss"
|
import { LoggerProvider } from "./components/ui/log/LoggerContext";
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
<LoggerProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route path="/" element={<UserAuth />} />
|
||||||
path="/"
|
|
||||||
element={<UserAuth />}
|
|
||||||
/>
|
|
||||||
<Route path="/dashboard" element={<Dashboard />} />
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
<Route path="/project" element={<Project />} />
|
<Route path="/project" element={<Project />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</ToastProvider>
|
</LoggerProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import React from "react";
|
||||||
|
import { HelpIcon } from "../icons/DashboardIcon";
|
||||||
|
import {
|
||||||
|
LogInfoIcon,
|
||||||
|
ErrorIcon,
|
||||||
|
WarningIcon,
|
||||||
|
} from "../icons/ExportCommonIcons"; // Adjust path as needed
|
||||||
|
import { useLogger } from "../ui/log/LoggerContext";
|
||||||
|
|
||||||
|
const getLogIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case "info":
|
||||||
|
return <LogInfoIcon />;
|
||||||
|
case "error":
|
||||||
|
return <ErrorIcon />;
|
||||||
|
case "warning":
|
||||||
|
return <WarningIcon />;
|
||||||
|
case "log":
|
||||||
|
default:
|
||||||
|
return <LogInfoIcon />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Footer: React.FC = () => {
|
||||||
|
const { logs, setIsLogListVisible } = useLogger();
|
||||||
|
const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="footer-wrapper">
|
||||||
|
<div className="selection-wrapper">
|
||||||
|
<div className="selector-wrapper">
|
||||||
|
<div className="icon"></div>
|
||||||
|
<div className="selector">Selection</div>
|
||||||
|
</div>
|
||||||
|
<div className="selector-wrapper">
|
||||||
|
<div className="icon"></div>
|
||||||
|
<div className="selector">Rotate/Zoom</div>
|
||||||
|
</div>
|
||||||
|
<div className="selector-wrapper">
|
||||||
|
<div className="icon"></div>
|
||||||
|
<div className="selector">Pan/Context Menu</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="logs-wrapper">
|
||||||
|
<div className="logs-detail" onClick={() => setIsLogListVisible(true)}>
|
||||||
|
{lastLog ? (
|
||||||
|
<>
|
||||||
|
<span className="log-icon">{getLogIcon(lastLog.type)}</span>
|
||||||
|
<span className="log-message">{lastLog.message}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"No logs yet."
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="version">
|
||||||
|
V 0.01
|
||||||
|
<div className="icon">
|
||||||
|
<HelpIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
|
@ -458,7 +458,7 @@ export function InfoIcon() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M5.46289 7.75441C5.46289 7.68342 5.47691 7.6131 5.50422 7.54758C5.53158 7.48201 5.57156 7.42254 5.62201 7.37257C5.67246 7.3226 5.73231 7.2831 5.79807 7.25636C5.86388 7.22963 5.93425 7.21619 6.00529 7.21681C6.11003 7.21873 6.21188 7.25142 6.29814 7.31089C6.38435 7.37036 6.45121 7.45398 6.49019 7.55118C6.52921 7.64843 6.53862 7.75499 6.5174 7.85757C6.49614 7.96014 6.44511 8.05417 6.37071 8.12795C6.29631 8.20168 6.20185 8.25184 6.09908 8.27219C5.99631 8.29254 5.8898 8.28212 5.79294 8.24224C5.69603 8.2024 5.61308 8.13486 5.55438 8.04808C5.49567 7.96134 5.46385 7.8592 5.46289 7.75441ZM5.63564 6.44401L5.56844 3.93842C5.56206 3.87819 5.56844 3.81729 5.58716 3.75968C5.60583 3.70207 5.63641 3.64902 5.67692 3.604C5.71743 3.55897 5.76697 3.52297 5.82227 3.49832C5.87761 3.47368 5.93751 3.46094 5.99804 3.46094C6.05862 3.46094 6.11852 3.47368 6.17387 3.49832C6.22916 3.52297 6.2787 3.55897 6.31921 3.604C6.35972 3.64902 6.3903 3.70207 6.40897 3.75968C6.42769 3.81729 6.43407 3.87819 6.42769 3.93842L6.36529 6.44401C6.36529 6.54073 6.32689 6.63356 6.25844 6.70196C6.19004 6.77036 6.09721 6.80881 6.00049 6.80881C5.90372 6.80881 5.81094 6.77036 5.74254 6.70196C5.67414 6.63356 5.63564 6.54073 5.63564 6.44401Z"
|
d="M5.46289 7.75441C5.46289 7.68342 5.47691 7.6131 5.50422 7.54758C5.53158 7.48201 5.57156 7.42254 5.62201 7.37257C5.67246 7.3226 5.73231 7.2831 5.79807 7.25636C5.86388 7.22963 5.93425 7.21619 6.00529 7.21681C6.11003 7.21873 6.21188 7.25142 6.29814 7.31089C6.38435 7.37036 6.45121 7.45398 6.49019 7.55118C6.52921 7.64843 6.53862 7.75499 6.5174 7.85757C6.49614 7.96014 6.44511 8.05417 6.37071 8.12795C6.29631 8.20168 6.20185 8.25184 6.09908 8.27219C5.99631 8.29254 5.8898 8.28212 5.79294 8.24224C5.69603 8.2024 5.61308 8.13486 5.55438 8.04808C5.49567 7.96134 5.46385 7.8592 5.46289 7.75441ZM5.63564 6.44401L5.56844 3.93842C5.56206 3.87819 5.56844 3.81729 5.58716 3.75968C5.60583 3.70207 5.63641 3.64902 5.67692 3.604C5.71743 3.55897 5.76697 3.52297 5.82227 3.49832C5.87761 3.47368 5.93751 3.46094 5.99804 3.46094C6.05862 3.46094 6.11852 3.47368 6.17387 3.49832C6.22916 3.52297 6.2787 3.55897 6.31921 3.604C6.35972 3.64902 6.3903 3.70207 6.40897 3.75968C6.42769 3.81729 6.43407 3.87819 6.42769 3.93842L6.36529 6.44401C6.36529 6.54073 6.32689 6.63356 6.25844 6.70196C6.19004 6.77036 6.09721 6.80881 6.00049 6.80881C5.90372 6.80881 5.81094 6.77036 5.74254 6.70196C5.67414 6.63356 5.63564 6.54073 5.63564 6.44401Z"
|
||||||
fill="var(--icon-default-color)"
|
fill="var(--text-color)"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M6.00006 10.3175C8.45219 10.3175 10.4401 8.32963 10.4401 5.8775C10.4401 3.42536 8.45219 1.4375 6.00006 1.4375C3.54792 1.4375 1.56006 3.42536 1.56006 5.8775C1.56006 8.32963 3.54792 10.3175 6.00006 10.3175Z"
|
d="M6.00006 10.3175C8.45219 10.3175 10.4401 8.32963 10.4401 5.8775C10.4401 3.42536 8.45219 1.4375 6.00006 1.4375C3.54792 1.4375 1.56006 3.42536 1.56006 5.8775C1.56006 8.32963 3.54792 10.3175 6.00006 10.3175Z"
|
||||||
|
@ -471,7 +471,7 @@ export function InfoIcon() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AI_Icon() {
|
export function AIIcon() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="20"
|
width="20"
|
||||||
|
@ -814,3 +814,136 @@ export const SpeedIcon = () => {
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LogListIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="25"
|
||||||
|
viewBox="0 0 24 25"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.5 18.25H5.5M18.5 21.7272V19.3568H17.3829C16.8245 19.3568 16.375 18.9072 16.375 18.3489V18.08C16.375 17.5216 16.8246 17.0721 17.3829 17.0721H18.5V13.7318H17.2579C16.6995 13.7318 16.25 13.2822 16.25 12.7239V12.4551C16.25 11.8967 16.6996 11.4472 17.2579 11.4472H18.5V7.98185H17.2579C16.6995 7.98185 16.25 7.5323 16.25 6.97395V6.70515C16.25 6.14675 16.6996 5.69725 17.2579 5.69725H18.5V3.27065M14.5 12.75H5.5M12 7.24995H5.5M20.25 3.25H3.75C3.1977 3.25 2.75 3.6977 2.75 4.25V20.75C2.75 21.3023 3.1977 21.75 3.75 21.75H20.25C20.8023 21.75 21.25 21.3023 21.25 20.75V4.25C21.25 3.6977 20.8023 3.25 20.25 3.25Z"
|
||||||
|
stroke="#2B3344"
|
||||||
|
strokeWidth="1.2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogTickIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_3962_4223)">
|
||||||
|
<path
|
||||||
|
d="M5.00065 0.835938C2.70482 0.835938 0.833984 2.70677 0.833984 5.0026C0.833984 7.29844 2.70482 9.16927 5.00065 9.16927C7.29648 9.16927 9.16732 7.29844 9.16732 5.0026C9.16732 2.70677 7.29648 0.835938 5.00065 0.835938ZM6.99232 4.04427L4.62982 6.40677C4.57148 6.4651 4.49232 6.49844 4.40898 6.49844C4.32565 6.49844 4.24648 6.4651 4.18815 6.40677L3.00898 5.2276C2.88815 5.10677 2.88815 4.90677 3.00898 4.78594C3.12982 4.6651 3.32982 4.6651 3.45065 4.78594L4.40898 5.74427L6.55065 3.6026C6.67148 3.48177 6.87148 3.48177 6.99232 3.6026C7.11315 3.72344 7.11315 3.91927 6.99232 4.04427Z"
|
||||||
|
fill="#49B841"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_3962_4223">
|
||||||
|
<rect width="10" height="10" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogInfoIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0.75 2.64438V7.41438C0.75 7.92094 0.951232 8.40675 1.30943 8.76495C1.66762 9.12314 2.15344 9.32438 2.66 9.32438H3.615V10.7544L6.5 9.32438H9.365C9.87156 9.32438 10.3574 9.12314 10.7156 8.76495C11.0738 8.40675 11.275 7.92094 11.275 7.41438V2.64438C11.275 2.13781 11.0738 1.652 10.7156 1.2938C10.3574 0.935607 9.87156 0.734375 9.365 0.734375H2.66C2.15344 0.734375 1.66762 0.935607 1.30943 1.2938C0.951232 1.652 0.75 2.13781 0.75 2.64438Z"
|
||||||
|
fill="#8E8E93"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.04492 6.94531H6.95492"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="0.955"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.04492 4.07812H5.99992V6.94313"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="0.955"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.52539 2.65625H6.47539"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="0.955"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WarningIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0.75 2.64438V7.41438C0.75 7.92094 0.951232 8.40675 1.30943 8.76495C1.66762 9.12314 2.15344 9.32438 2.66 9.32438H3.615V10.7544L6.5 9.32438H9.365C9.87156 9.32438 10.3574 9.12314 10.7156 8.76495C11.0738 8.40675 11.275 7.92094 11.275 7.41438V2.64438C11.275 2.13781 11.0738 1.652 10.7156 1.2938C10.3574 0.935607 9.87156 0.734375 9.365 0.734375H2.66C2.15344 0.734375 1.66762 0.935607 1.30943 1.2938C0.951232 1.652 0.75 2.13781 0.75 2.64438Z"
|
||||||
|
fill="#8E8E93"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.04492 6.94531H6.95492"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="0.955"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.04492 4.07812H5.99992V6.94313"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="0.955"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.52539 2.65625H6.47539"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="0.955"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ErrorIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M11 6C11 8.7614 8.7614 11 6 11C3.23857 11 1 8.7614 1 6C1 3.23857 3.23857 1 6 1C8.7614 1 11 3.23857 11 6ZM4.48482 4.48483C4.63126 4.33838 4.8687 4.33838 5.01515 4.48483L6 5.46965L6.9848 4.48484C7.13125 4.33839 7.3687 4.33839 7.51515 4.48484C7.6616 4.63128 7.6616 4.86872 7.51515 5.01515L6.5303 6L7.51515 6.9848C7.6616 7.13125 7.6616 7.3687 7.51515 7.51515C7.3687 7.6616 7.13125 7.6616 6.9848 7.51515L6 6.53035L5.01515 7.51515C4.86871 7.6616 4.63127 7.6616 4.48483 7.51515C4.33838 7.3687 4.33838 7.13125 4.48483 6.98485L5.46965 6L4.48482 5.01515C4.33837 4.86871 4.33837 4.63127 4.48482 4.48483Z"
|
||||||
|
fill="#ED5342"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -125,21 +125,21 @@ export function ResetIcon() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M2.54182 4.09637C3.33333 2.73422 4.80832 1.81836 6.49721 1.81836C9.02194 1.81836 11.0686 3.86506 11.0686 6.38979C11.0686 8.91452 9.02194 10.9612 6.49721 10.9612C3.97248 10.9612 1.92578 8.91452 1.92578 6.38979"
|
d="M2.54182 4.09637C3.33333 2.73422 4.80832 1.81836 6.49721 1.81836C9.02194 1.81836 11.0686 3.86506 11.0686 6.38979C11.0686 8.91452 9.02194 10.9612 6.49721 10.9612C3.97248 10.9612 1.92578 8.91452 1.92578 6.38979"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M7.64118 6.38895C7.64118 7.02013 7.12951 7.53181 6.49833 7.53181C5.86714 7.53181 5.35547 7.02013 5.35547 6.38895C5.35547 5.75777 5.86714 5.24609 6.49833 5.24609C7.12951 5.24609 7.64118 5.75777 7.64118 6.38895Z"
|
d="M7.64118 6.38895C7.64118 7.02013 7.12951 7.53181 6.49833 7.53181C5.86714 7.53181 5.35547 7.02013 5.35547 6.38895C5.35547 5.75777 5.86714 5.24609 6.49833 5.24609C7.12951 5.24609 7.64118 5.75777 7.64118 6.38895Z"
|
||||||
fill="var(--icon-default-color-active)"
|
fill="var(--text-color)"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeWidth="0.571429"
|
strokeWidth="0.571429"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M4.78125 4.10407H2.49554V1.81836"
|
d="M4.78125 4.10407H2.49554V1.81836"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
|
@ -158,7 +158,7 @@ export function PlayStopIcon() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M8 2.88867V9.88867M11 2.88867V9.88867M2 3.99171V8.78562C2 9.28847 2 9.53987 2.09943 9.65627C2.1857 9.75732 2.31512 9.81092 2.44756 9.80047C2.60019 9.78847 2.77796 9.61072 3.13352 9.25517L5.5305 6.85817C5.69485 6.69382 5.777 6.61167 5.8078 6.51692C5.83485 6.43357 5.83485 6.34377 5.8078 6.26042C5.777 6.16567 5.69485 6.08352 5.5305 5.91917L3.13352 3.52219C2.77796 3.16664 2.60019 2.98886 2.44756 2.97685C2.31512 2.96643 2.1857 3.02004 2.09943 3.12105C2 3.23747 2 3.48888 2 3.99171Z"
|
d="M8 2.88867V9.88867M11 2.88867V9.88867M2 3.99171V8.78562C2 9.28847 2 9.53987 2.09943 9.65627C2.1857 9.75732 2.31512 9.81092 2.44756 9.80047C2.60019 9.78847 2.77796 9.61072 3.13352 9.25517L5.5305 6.85817C5.69485 6.69382 5.777 6.61167 5.8078 6.51692C5.83485 6.43357 5.83485 6.34377 5.8078 6.26042C5.777 6.16567 5.69485 6.08352 5.5305 5.91917L3.13352 3.52219C2.77796 3.16664 2.60019 2.98886 2.44756 2.97685C2.31512 2.96643 2.1857 3.02004 2.09943 3.12105C2 3.23747 2 3.48888 2 3.99171Z"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
|
@ -177,7 +177,7 @@ export function ExitIcon() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M2.08203 6.34311L5.03647 3.38867M2.08203 6.34311L5.03647 9.29755M2.08203 6.34311H5.9228C7.22276 6.34334 9.34995 7.05241 10.7681 9.88867"
|
d="M2.08203 6.34311L5.03647 3.38867M2.08203 6.34311L5.03647 9.29755M2.08203 6.34311H5.9228C7.22276 6.34334 9.34995 7.05241 10.7681 9.88867"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,7 +19,7 @@ const Header: React.FC = () => {
|
||||||
<FileMenu />
|
<FileMenu />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<button
|
||||||
className={`toggle-sidebar-ui-button ${!toggleUI ? "active" : ""}`}
|
className={`toggle-sidebar-ui-button ${!toggleUI ? "active" : ""}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (activeModule !== "market") {
|
if (activeModule !== "market") {
|
||||||
|
@ -29,7 +29,7 @@ const Header: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ToggleSidebarIcon />
|
<ToggleSidebarIcon />
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { AI_Icon } from "../../../icons/ExportCommonIcons";
|
import { AIIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
|
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
|
||||||
import { AnalysisPresetsType } from "../../../../types/analysis";
|
import { AnalysisPresetsType } from "../../../../types/analysis";
|
||||||
import RenderAnalysisInputs from "./RenderAnalysisInputs";
|
import RenderAnalysisInputs from "./RenderAnalysisInputs";
|
||||||
|
@ -13,53 +13,24 @@ const Analysis: React.FC = () => {
|
||||||
|
|
||||||
const AnalysisPresets: AnalysisPresetsType = {
|
const AnalysisPresets: AnalysisPresetsType = {
|
||||||
"Throughput time": [
|
"Throughput time": [
|
||||||
{ type: "default", inputs: { label: "Height", activeOption: "mm" } },
|
{ type: "default", inputs: { label: "Cycle time", activeOption: "s" } },
|
||||||
{ type: "default", inputs: { label: "Width", activeOption: "mm" } },
|
{ type: "default", inputs: { label: "machines / lines", activeOption: "item" } },
|
||||||
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } },
|
{ type: "default", inputs: { label: "Machine uptime", activeOption: "%" } },
|
||||||
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
|
|
||||||
{
|
|
||||||
type: "default",
|
|
||||||
inputs: { label: "Raw Material", activeOption: "mm" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
inputs: { label: "Material flow", activeOption: "m/min" },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"Production capacity": [
|
"Production capacity": [
|
||||||
{ type: "default", inputs: { label: "Height", activeOption: "mm" } },
|
{ type: "default", inputs: { label: "Shift length", activeOption: "hr" } },
|
||||||
{ type: "default", inputs: { label: "Width", activeOption: "mm" } },
|
{ type: "default", inputs: { label: "Shifts / day", activeOption: "unit" } },
|
||||||
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } },
|
{ type: "default", inputs: { label: "Working days / year", activeOption: "days" } },
|
||||||
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
|
{ type: "default", inputs: { label: "Yield rate", activeOption: "%" } },
|
||||||
{
|
|
||||||
type: "default",
|
|
||||||
inputs: { label: "Raw Material", activeOption: "mm" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
inputs: { label: "Material flow", activeOption: "m/min" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
inputs: { label: "Shift 1", activeOption: "hr(s)" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
inputs: { label: "Shift 2", activeOption: "hr(s)" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
inputs: { label: "Over time", activeOption: "hr(s)" },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
ROI: [
|
ROI: [
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Equipment Cost", activeOption: "INR" },
|
inputs: { label: "Selling price", activeOption: "INR" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Employee Salary", activeOption: "INR" },
|
inputs: { label: "Material cost", activeOption: "INR" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
|
@ -67,7 +38,7 @@ const Analysis: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Cost per unit", activeOption: "INR" },
|
inputs: { label: "Maintenance cost", activeOption: "INR" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
|
@ -75,20 +46,19 @@ const Analysis: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Upkeep Cost", activeOption: "INR" },
|
inputs: { label: "Fixed costs", activeOption: "INR" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Working Hours", activeOption: "Hrs" },
|
inputs: { label: "Salvage value", activeOption: "Hrs" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Power Usage", activeOption: "KWH" },
|
inputs: { label: "Production period", activeOption: "yrs" },
|
||||||
},
|
},
|
||||||
{ type: "default", inputs: { label: "KWH", activeOption: "Mos" } },
|
|
||||||
{
|
{
|
||||||
type: "default",
|
type: "default",
|
||||||
inputs: { label: "Man Power", activeOption: "Person" },
|
inputs: { label: "Tax rate", activeOption: "%" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -98,7 +68,7 @@ const Analysis: React.FC = () => {
|
||||||
<div className="analysis-main-container">
|
<div className="analysis-main-container">
|
||||||
<div className="header">Object</div>
|
<div className="header">Object</div>
|
||||||
<div className="generate-report-button">
|
<div className="generate-report-button">
|
||||||
<AI_Icon /> Generate Report
|
<AIIcon /> Generate Report
|
||||||
</div>
|
</div>
|
||||||
<div className="analysis-content-container section">
|
<div className="analysis-content-container section">
|
||||||
<div className="dropdown-header-container">
|
<div className="dropdown-header-container">
|
||||||
|
@ -121,7 +91,7 @@ const Analysis: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
<div className="buttons-container">
|
<div className="buttons-container">
|
||||||
<input type="button" value={"Clear"} className="cancel" />
|
<input type="button" value={"Clear"} className="cancel" />
|
||||||
<input type="button" value={"Calculate"} className="submit" />
|
<input type="button" value={"Update"} className="submit" />
|
||||||
</div>
|
</div>
|
||||||
<div className="create-custom-analysis-container">
|
<div className="create-custom-analysis-container">
|
||||||
<div className="custom-analysis-header">Create Custom Analysis</div>
|
<div className="custom-analysis-header">Create Custom Analysis</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import InputRange from "../../../ui/inputs/InputRange";
|
import InputRange from "../../../ui/inputs/InputRange";
|
||||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||||
import { AI_Icon } from "../../../icons/ExportCommonIcons";
|
import { AIIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import LabeledButton from "../../../ui/inputs/LabledButton";
|
import LabeledButton from "../../../ui/inputs/LabledButton";
|
||||||
import {
|
import {
|
||||||
useAzimuth,
|
useAzimuth,
|
||||||
|
@ -225,7 +225,7 @@ const GlobalProperties: React.FC = () => {
|
||||||
<section>
|
<section>
|
||||||
<div className="header">Environment</div>
|
<div className="header">Environment</div>
|
||||||
<div className="optimize-button" onClick={optimizeScene}>
|
<div className="optimize-button" onClick={optimizeScene}>
|
||||||
<AI_Icon />
|
<AIIcon />
|
||||||
Optimize
|
Optimize
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
useSelectedEventData,
|
useSelectedEventData,
|
||||||
useSelectedEventSphere,
|
useSelectedEventSphere,
|
||||||
useSelectedProduct,
|
useSelectedProduct,
|
||||||
} from "../../../../../store/simulation/useSimulationStore";
|
} from "../../../../../store/simulation/useSimulationStore";
|
||||||
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
||||||
import ConveyorMechanics from "./mechanics/conveyorMechanics";
|
import ConveyorMechanics from "./mechanics/conveyorMechanics";
|
||||||
|
@ -15,114 +15,118 @@ import { handleAddEventToProduct } from "../../../../../modules/simulation/event
|
||||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||||
|
|
||||||
const EventProperties: React.FC = () => {
|
const EventProperties: React.FC = () => {
|
||||||
const { selectedEventData } = useSelectedEventData();
|
const { selectedEventData } = useSelectedEventData();
|
||||||
const { getEventByModelUuid } = useProductStore();
|
const { getEventByModelUuid } = useProductStore();
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(null);
|
const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(
|
||||||
const [assetType, setAssetType] = useState<string | null>(null);
|
null
|
||||||
const { products, addEvent } = useProductStore();
|
);
|
||||||
const { selectedEventSphere } = useSelectedEventSphere();
|
const [assetType, setAssetType] = useState<string | null>(null);
|
||||||
|
const { products, addEvent } = useProductStore();
|
||||||
|
const { selectedEventSphere } = useSelectedEventSphere();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const event = getCurrentEventData();
|
const event = getCurrentEventData();
|
||||||
setCurrentEventData(event);
|
setCurrentEventData(event);
|
||||||
|
|
||||||
const type = determineAssetType(event);
|
const type = determineAssetType(event);
|
||||||
setAssetType(type);
|
setAssetType(type);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedEventData, selectedProduct]);
|
}, [selectedEventData, selectedProduct]);
|
||||||
|
|
||||||
const getCurrentEventData = () => {
|
|
||||||
if (!selectedEventData?.data || !selectedProduct) return null;
|
|
||||||
return (
|
|
||||||
getEventByModelUuid(
|
|
||||||
selectedProduct.productId,
|
|
||||||
selectedEventData.data.modelUuid
|
|
||||||
) ?? null
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const determineAssetType = (event: EventsSchema | null) => {
|
|
||||||
if (!event) return null;
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case "transfer":
|
|
||||||
return "conveyor";
|
|
||||||
case "vehicle":
|
|
||||||
return "vehicle";
|
|
||||||
case "roboticArm":
|
|
||||||
return "roboticArm";
|
|
||||||
case "machine":
|
|
||||||
return "machine";
|
|
||||||
case "storageUnit":
|
|
||||||
return "storageUnit";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const getCurrentEventData = () => {
|
||||||
|
if (!selectedEventData?.data || !selectedProduct) return null;
|
||||||
return (
|
return (
|
||||||
<div className="event-proprties-wrapper">
|
getEventByModelUuid(
|
||||||
{currentEventData && (
|
selectedProduct.productId,
|
||||||
<>
|
selectedEventData.data.modelUuid
|
||||||
<div className="header">
|
) ?? null
|
||||||
<div className="header-value">
|
|
||||||
{selectedEventData?.data.modelName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{assetType === "conveyor" && <ConveyorMechanics />}
|
|
||||||
{assetType === "vehicle" && <VehicleMechanics />}
|
|
||||||
{assetType === "roboticArm" && <RoboticArmMechanics />}
|
|
||||||
{assetType === "machine" && <MachineMechanics />}
|
|
||||||
{assetType === "storageUnit" && <StorageMechanics />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!currentEventData && selectedEventSphere && (
|
|
||||||
<div className="no-event-selected">
|
|
||||||
<p>
|
|
||||||
<strong>Oops!</strong> It looks like this object doesn't have an
|
|
||||||
event assigned yet. To continue, please link it to one of the
|
|
||||||
products below.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="products-list">
|
|
||||||
<p>
|
|
||||||
<strong>Here are some products you can add it to:</strong>
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{products.map((product) => (
|
|
||||||
<li key={product.productId}>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (selectedEventData) {
|
|
||||||
handleAddEventToProduct({
|
|
||||||
event: useEventsStore.getState().getEventByModelUuid(selectedEventData?.data.modelUuid),
|
|
||||||
addEvent,
|
|
||||||
selectedProduct,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AddIcon />
|
|
||||||
{product.productName}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{!selectedEventSphere && (
|
|
||||||
<div className="no-event-selected">
|
|
||||||
<p>
|
|
||||||
<strong>Oops!</strong> It looks like you haven't selected an event
|
|
||||||
point yet. Please select an event to view its properties.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div >
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const determineAssetType = (event: EventsSchema | null) => {
|
||||||
|
if (!event) return null;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case "transfer":
|
||||||
|
return "conveyor";
|
||||||
|
case "vehicle":
|
||||||
|
return "vehicle";
|
||||||
|
case "roboticArm":
|
||||||
|
return "roboticArm";
|
||||||
|
case "machine":
|
||||||
|
return "machine";
|
||||||
|
case "storageUnit":
|
||||||
|
return "storageUnit";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="event-proprties-wrapper">
|
||||||
|
{currentEventData && (
|
||||||
|
<>
|
||||||
|
<div className="header">
|
||||||
|
<div className="header-value">
|
||||||
|
{selectedEventData?.data.modelName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{assetType === "conveyor" && <ConveyorMechanics />}
|
||||||
|
{assetType === "vehicle" && <VehicleMechanics />}
|
||||||
|
{assetType === "roboticArm" && <RoboticArmMechanics />}
|
||||||
|
{assetType === "machine" && <MachineMechanics />}
|
||||||
|
{assetType === "storageUnit" && <StorageMechanics />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!currentEventData && selectedEventSphere && (
|
||||||
|
<div className="no-event-selected">
|
||||||
|
<p>
|
||||||
|
<strong>Oops!</strong> It looks like this object doesn't have an
|
||||||
|
event assigned yet. To continue, please link it to one of the
|
||||||
|
products below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="products-list">
|
||||||
|
<p>
|
||||||
|
<strong>Here are some products you can add it to:</strong>
|
||||||
|
</p>
|
||||||
|
<div className="product-item">
|
||||||
|
{products.map((product) => (
|
||||||
|
<button
|
||||||
|
key={product.productId}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedEventData) {
|
||||||
|
handleAddEventToProduct({
|
||||||
|
event: useEventsStore
|
||||||
|
.getState()
|
||||||
|
.getEventByModelUuid(
|
||||||
|
selectedEventData?.data.modelUuid
|
||||||
|
),
|
||||||
|
addEvent,
|
||||||
|
selectedProduct,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
{product.productName}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!selectedEventSphere && (
|
||||||
|
<div className="no-event-selected">
|
||||||
|
<p>
|
||||||
|
<strong>Oops!</strong> It looks like you haven't selected an event
|
||||||
|
point yet. Please select an event to view its properties.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EventProperties;
|
export default EventProperties;
|
||||||
|
|
|
@ -35,7 +35,7 @@ const ActionsList: React.FC<ActionsListProps> = ({
|
||||||
|
|
||||||
const handleRenameAction = (newName: string) => {
|
const handleRenameAction = (newName: string) => {
|
||||||
if (!selectedAction.actionId) return;
|
if (!selectedAction.actionId) return;
|
||||||
const event = renameAction(selectedAction.actionId, newName);
|
const event = renameAction(selectedProduct.productId, selectedAction.actionId, newName);
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
upsertProductOrEventApi({
|
upsertProductOrEventApi({
|
||||||
|
|
|
@ -72,7 +72,7 @@ function ConveyorMechanics() {
|
||||||
const validOption = option as | "default" | "spawn" | "swap" | "delay" | "despawn";
|
const validOption = option as | "default" | "spawn" | "swap" | "delay" | "despawn";
|
||||||
setActiveOption(validOption);
|
setActiveOption(validOption);
|
||||||
|
|
||||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
actionType: validOption,
|
actionType: validOption,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ function ConveyorMechanics() {
|
||||||
|
|
||||||
const handleRenameAction = (newName: string) => {
|
const handleRenameAction = (newName: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const event = updateAction(selectedPointData.action.actionUuid, { actionName: newName });
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName });
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
updateBackend(
|
updateBackend(
|
||||||
|
@ -102,7 +102,7 @@ function ConveyorMechanics() {
|
||||||
|
|
||||||
const handleSpawnCountChange = (value: string) => {
|
const handleSpawnCountChange = (value: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
spawnCount: value === "inherit" ? "inherit" : parseFloat(value),
|
spawnCount: value === "inherit" ? "inherit" : parseFloat(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ function ConveyorMechanics() {
|
||||||
|
|
||||||
const handleSpawnIntervalChange = (value: string) => {
|
const handleSpawnIntervalChange = (value: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
spawnInterval: value === "inherit" ? "inherit" : parseFloat(value),
|
spawnInterval: value === "inherit" ? "inherit" : parseFloat(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ function ConveyorMechanics() {
|
||||||
|
|
||||||
const handleMaterialSelect = (material: string) => {
|
const handleMaterialSelect = (material: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const event = updateAction(selectedPointData.action.actionUuid, { material });
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { material });
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
updateBackend(
|
updateBackend(
|
||||||
|
@ -148,7 +148,7 @@ function ConveyorMechanics() {
|
||||||
|
|
||||||
const handleDelayChange = (value: string) => {
|
const handleDelayChange = (value: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
delay: value === "inherit" ? "inherit" : parseFloat(value),
|
delay: value === "inherit" ? "inherit" : parseFloat(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ function ConveyorMechanics() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tirgger">
|
<div className="tirgger">
|
||||||
<Trigger />
|
<Trigger selectedPointData={selectedPointData as any} type= {'Conveyor'}/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useSelectedEventData, useSelectedProduct } from "../../../../../../stor
|
||||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||||
import ProcessAction from "../actions/ProcessAction";
|
import ProcessAction from "../actions/ProcessAction";
|
||||||
import ActionsList from "../components/ActionsList";
|
import ActionsList from "../components/ActionsList";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
|
||||||
function MachineMechanics() {
|
function MachineMechanics() {
|
||||||
const [activeOption, setActiveOption] = useState<"default" | "process">("default");
|
const [activeOption, setActiveOption] = useState<"default" | "process">("default");
|
||||||
|
@ -14,6 +15,9 @@ function MachineMechanics() {
|
||||||
const { getPointByUuid, updateAction } = useProductStore();
|
const { getPointByUuid, updateAction } = useProductStore();
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEventData) {
|
if (selectedEventData) {
|
||||||
const point = getPointByUuid(
|
const point = getPointByUuid(
|
||||||
|
@ -28,31 +32,54 @@ function MachineMechanics() {
|
||||||
}
|
}
|
||||||
}, [selectedProduct, selectedEventData, getPointByUuid]);
|
}, [selectedProduct, selectedEventData, getPointByUuid]);
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleActionTypeChange = (option: string) => {
|
const handleActionTypeChange = (option: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const validOption = option as "process";
|
const validOption = option as "process";
|
||||||
setActiveOption(validOption);
|
setActiveOption(validOption);
|
||||||
|
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
actionType: validOption,
|
actionType: validOption,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRenameAction = (newName: string) => {
|
const handleRenameAction = (newName: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, { actionName: newName });
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProcessTimeChange = (value: string) => {
|
const handleProcessTimeChange = (value: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
processTime: parseFloat(value),
|
processTime: parseFloat(value),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMaterialSelect = (material: string) => {
|
const handleMaterialSelect = (material: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
swapMaterial: material,
|
swapMaterial: material,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -110,7 +137,7 @@ function MachineMechanics() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tirgger">
|
<div className="tirgger">
|
||||||
<Trigger />
|
<Trigger selectedPointData={selectedPointData as any} type= {'Machine'} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -59,7 +59,7 @@ function RoboticArmMechanics() {
|
||||||
|
|
||||||
const handleRenameAction = (newName: string) => {
|
const handleRenameAction = (newName: string) => {
|
||||||
if (!selectedAction.actionId) return;
|
if (!selectedAction.actionId) return;
|
||||||
const event = updateAction(selectedAction.actionId, { actionName: newName });
|
const event = updateAction(selectedProduct.productId, selectedAction.actionId, { actionName: newName });
|
||||||
|
|
||||||
if (selectedPointData) {
|
if (selectedPointData) {
|
||||||
const updatedActions = selectedPointData.actions.map((action) =>
|
const updatedActions = selectedPointData.actions.map((action) =>
|
||||||
|
@ -101,7 +101,7 @@ function RoboticArmMechanics() {
|
||||||
if (!selectedAction.actionId || !selectedPointData) return;
|
if (!selectedAction.actionId || !selectedPointData) return;
|
||||||
const [x, y, z] = value.split(",").map(Number);
|
const [x, y, z] = value.split(",").map(Number);
|
||||||
|
|
||||||
const event = updateAction(selectedAction.actionId, {
|
const event = updateAction(selectedProduct.productId, selectedAction.actionId, {
|
||||||
process: {
|
process: {
|
||||||
startPoint: [x, y, z] as [number, number, number],
|
startPoint: [x, y, z] as [number, number, number],
|
||||||
endPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.endPoint || null,
|
endPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.endPoint || null,
|
||||||
|
@ -122,7 +122,7 @@ function RoboticArmMechanics() {
|
||||||
if (!selectedAction.actionId || !selectedPointData) return;
|
if (!selectedAction.actionId || !selectedPointData) return;
|
||||||
const [x, y, z] = value.split(",").map(Number);
|
const [x, y, z] = value.split(",").map(Number);
|
||||||
|
|
||||||
const event = updateAction(selectedAction.actionId, {
|
const event = updateAction(selectedProduct.productId, selectedAction.actionId, {
|
||||||
process: {
|
process: {
|
||||||
startPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.startPoint || null,
|
startPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.startPoint || null,
|
||||||
endPoint: [x, y, z] as [number, number, number],
|
endPoint: [x, y, z] as [number, number, number],
|
||||||
|
@ -181,7 +181,7 @@ function RoboticArmMechanics() {
|
||||||
const handleDeleteAction = (actionUuid: string) => {
|
const handleDeleteAction = (actionUuid: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
|
|
||||||
const event = removeAction(actionUuid);
|
const event = removeAction(selectedProduct.productId, actionUuid);
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
updateBackend(
|
updateBackend(
|
||||||
|
@ -280,7 +280,7 @@ function RoboticArmMechanics() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="tirgger">
|
<div className="tirgger">
|
||||||
<Trigger />
|
<Trigger selectedPointData={selectedPointData as any} type= {'RoboticArm'} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useSelectedEventData, useSelectedProduct } from "../../../../../../stor
|
||||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||||
import StorageAction from "../actions/StorageAction";
|
import StorageAction from "../actions/StorageAction";
|
||||||
import ActionsList from "../components/ActionsList";
|
import ActionsList from "../components/ActionsList";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
|
||||||
function StorageMechanics() {
|
function StorageMechanics() {
|
||||||
const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default");
|
const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default");
|
||||||
|
@ -14,6 +15,9 @@ function StorageMechanics() {
|
||||||
const { getPointByUuid, updateAction } = useProductStore();
|
const { getPointByUuid, updateAction } = useProductStore();
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEventData) {
|
if (selectedEventData) {
|
||||||
const point = getPointByUuid(
|
const point = getPointByUuid(
|
||||||
|
@ -28,26 +32,67 @@ function StorageMechanics() {
|
||||||
}
|
}
|
||||||
}, [selectedProduct, selectedEventData, getPointByUuid]);
|
}, [selectedProduct, selectedEventData, getPointByUuid]);
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleActionTypeChange = (option: string) => {
|
const handleActionTypeChange = (option: string) => {
|
||||||
if (!selectedEventData || !selectedPointData) return;
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
const validOption = option as "store" | "spawn";
|
const validOption = option as "store" | "spawn";
|
||||||
setActiveOption(validOption);
|
setActiveOption(validOption);
|
||||||
|
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
actionType: validOption,
|
actionType: validOption,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRenameAction = (newName: string) => {
|
const handleRenameAction = (newName: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, { actionName: newName });
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName });
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCapacityChange = (value: string) => {
|
const handleCapacityChange = (value: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
storageCapacity: parseInt(value),
|
storageCapacity: parseInt(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get current values from store
|
// Get current values from store
|
||||||
|
@ -101,7 +146,7 @@ function StorageMechanics() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tirgger">
|
<div className="tirgger">
|
||||||
<Trigger />
|
<Trigger selectedPointData={selectedPointData as any} type= {'StorageUnit'} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||||
import TravelAction from "../actions/TravelAction";
|
import TravelAction from "../actions/TravelAction";
|
||||||
import ActionsList from "../components/ActionsList";
|
import ActionsList from "../components/ActionsList";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
|
||||||
function VehicleMechanics() {
|
function VehicleMechanics() {
|
||||||
const [activeOption, setActiveOption] = useState<"default" | "travel">("default");
|
const [activeOption, setActiveOption] = useState<"default" | "travel">("default");
|
||||||
|
@ -18,8 +19,11 @@ function VehicleMechanics() {
|
||||||
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore();
|
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore();
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEventData) {
|
if (selectedEventData && selectedEventData.data.type === 'vehicle') {
|
||||||
const point = getPointByUuid(
|
const point = getPointByUuid(
|
||||||
selectedProduct.productId,
|
selectedProduct.productId,
|
||||||
selectedEventData.data.modelUuid,
|
selectedEventData.data.modelUuid,
|
||||||
|
@ -33,11 +37,34 @@ function VehicleMechanics() {
|
||||||
}
|
}
|
||||||
}, [selectedProduct, selectedEventData, getPointByUuid]);
|
}, [selectedProduct, selectedEventData, getPointByUuid]);
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleSpeedChange = (value: string) => {
|
const handleSpeedChange = (value: string) => {
|
||||||
if (!selectedEventData) return;
|
if (!selectedEventData) return;
|
||||||
updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, {
|
const event = updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, {
|
||||||
speed: parseFloat(value),
|
speed: parseFloat(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleActionTypeChange = (option: string) => {
|
const handleActionTypeChange = (option: string) => {
|
||||||
|
@ -45,28 +72,64 @@ function VehicleMechanics() {
|
||||||
const validOption = option as "travel";
|
const validOption = option as "travel";
|
||||||
setActiveOption(validOption);
|
setActiveOption(validOption);
|
||||||
|
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
actionType: validOption,
|
actionType: validOption,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRenameAction = (newName: string) => {
|
const handleRenameAction = (newName: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, { actionName: newName });
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName });
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoadCapacityChange = (value: string) => {
|
const handleLoadCapacityChange = (value: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
loadCapacity: parseFloat(value),
|
loadCapacity: parseFloat(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnloadDurationChange = (value: string) => {
|
const handleUnloadDurationChange = (value: string) => {
|
||||||
if (!selectedPointData) return;
|
if (!selectedPointData) return;
|
||||||
updateAction(selectedPointData.action.actionUuid, {
|
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||||
unLoadDuration: parseFloat(value),
|
unLoadDuration: parseFloat(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePickPointChange = (value: string) => {
|
const handlePickPointChange = (value: string) => {
|
||||||
|
@ -172,7 +235,7 @@ function VehicleMechanics() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tirgger">
|
<div className="tirgger">
|
||||||
<Trigger />
|
<Trigger selectedPointData={selectedPointData as any} type= {'Vehicle'} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,137 +1,325 @@
|
||||||
import React, { useRef, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
import {
|
import {
|
||||||
AddIcon,
|
AddIcon,
|
||||||
RemoveIcon,
|
RemoveIcon,
|
||||||
ResizeHeightIcon,
|
ResizeHeightIcon,
|
||||||
} from "../../../../../icons/ExportCommonIcons";
|
} from "../../../../../icons/ExportCommonIcons";
|
||||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||||
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
||||||
|
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||||
|
import { useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
|
||||||
const Trigger: React.FC = () => {
|
type TriggerProps = {
|
||||||
// State to hold the list of triggers
|
selectedPointData?: PointsScheme | undefined;
|
||||||
const [triggers, setTriggers] = useState<string[]>(["Trigger 1"]);
|
type?: 'Conveyor' | 'Vehicle' | 'RoboticArm' | 'Machine' | 'StorageUnit';
|
||||||
const [selectedTrigger, setSelectedTrigger] = useState<string>("Trigger 1");
|
};
|
||||||
const [activeOption, setActiveOption] = useState("onComplete");
|
|
||||||
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// States for dropdowns
|
const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||||
const [triggeredModel, setTriggeredModel] = useState<string[]>([]);
|
const [currentAction, setCurrentAction] = useState<string | undefined>();
|
||||||
const [triggeredPoint, setTriggeredPoint] = useState<string[]>([]);
|
const { selectedProduct } = useSelectedProduct();
|
||||||
const [triggeredAction, setTriggeredAction] = useState<string[]>([]);
|
const { getActionByUuid, addTrigger, removeTrigger, updateTrigger, renameTrigger, getProductById } = useProductStore();
|
||||||
|
const [triggers, setTriggers] = useState<TriggerSchema[]>([]);
|
||||||
|
const [selectedTrigger, setSelectedTrigger] = useState<TriggerSchema | undefined>();
|
||||||
|
const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete");
|
||||||
|
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Function to handle adding a new trigger
|
const email = localStorage.getItem('email')
|
||||||
const addTrigger = (): void => {
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
const newTrigger = `Trigger ${triggers.length + 1}`;
|
|
||||||
setTriggers([...triggers, newTrigger]); // Add new trigger to the state
|
|
||||||
|
|
||||||
// Initialize the states for the new trigger
|
useEffect(() => {
|
||||||
setTriggeredModel([...triggeredModel, ""]);
|
if (!selectedPointData || !selectedProduct) return;
|
||||||
setTriggeredPoint([...triggeredPoint, ""]);
|
|
||||||
setTriggeredAction([...triggeredAction, ""]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to handle removing a trigger
|
let actionUuid: string | undefined;
|
||||||
const removeTrigger = (index: number): void => {
|
|
||||||
setTriggers(triggers.filter((_, i) => i !== index)); // Remove trigger by index
|
|
||||||
setTriggeredModel(triggeredModel.filter((_, i) => i !== index));
|
|
||||||
setTriggeredPoint(triggeredPoint.filter((_, i) => i !== index));
|
|
||||||
setTriggeredAction(triggeredAction.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') {
|
||||||
<div className="trigger-wrapper">
|
actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid;
|
||||||
<div className="header">
|
} else if (type === 'RoboticArm') {
|
||||||
<div className="title">Trigger</div>
|
actionUuid = (selectedPointData as RoboticArmPointSchema).actions[0]?.actionUuid;
|
||||||
<button
|
}
|
||||||
className="add-button"
|
|
||||||
onClick={addTrigger}
|
setCurrentAction(actionUuid);
|
||||||
style={{ cursor: "pointer" }}
|
}, [selectedPointData, selectedProduct, type]);
|
||||||
>
|
|
||||||
<AddIcon /> Add
|
|
||||||
</button>
|
const updateBackend = (
|
||||||
</div>
|
productName: string,
|
||||||
<div className="trigger-list">
|
productId: string,
|
||||||
<div
|
organization: string,
|
||||||
className="lists-main-container"
|
eventData: EventsSchema
|
||||||
ref={triggersContainerRef}
|
) => {
|
||||||
style={{ height: "120px" }}
|
upsertProductOrEventApi({
|
||||||
>
|
productName: productName,
|
||||||
<div className="list-container">
|
productId: productId,
|
||||||
{triggers.map((trigger: any, index: number) => (
|
organization: organization,
|
||||||
<div
|
eventDatas: eventData
|
||||||
key={index}
|
})
|
||||||
className={`list-item ${
|
}
|
||||||
selectedTrigger === trigger ? "active" : ""
|
|
||||||
}`}
|
useEffect(() => {
|
||||||
onClick={() => setSelectedTrigger(trigger)}
|
if (!currentAction || !selectedProduct) return;
|
||||||
>
|
const action = getActionByUuid(selectedProduct.productId, currentAction);
|
||||||
<button className="value" onClick={() => {}}>
|
const actionTriggers = action?.triggers || [];
|
||||||
<RenameInput value={trigger} onRename={() => {}} />
|
setTriggers(actionTriggers);
|
||||||
|
setSelectedTrigger(actionTriggers[0]);
|
||||||
|
}, [currentAction, selectedProduct]);
|
||||||
|
|
||||||
|
const handleAddTrigger = () => {
|
||||||
|
if (!selectedProduct || !currentAction) return;
|
||||||
|
|
||||||
|
const newTrigger: TriggerSchema = {
|
||||||
|
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||||
|
triggerName: `New Trigger ${triggers.length + 1}`,
|
||||||
|
triggerType: activeOption,
|
||||||
|
delay: 0,
|
||||||
|
triggeredAsset: null
|
||||||
|
};
|
||||||
|
|
||||||
|
addTrigger(selectedProduct.productId, currentAction, newTrigger);
|
||||||
|
setSelectedTrigger(newTrigger);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveTrigger = (triggerUuid: string) => {
|
||||||
|
if (!selectedProduct) return;
|
||||||
|
removeTrigger(selectedProduct.productId, triggerUuid);
|
||||||
|
if (selectedTrigger?.triggerUuid === triggerUuid) {
|
||||||
|
const remainingTriggers = triggers.filter(t => t.triggerUuid !== triggerUuid);
|
||||||
|
setSelectedTrigger(remainingTriggers[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTriggerRename = (triggerUuid: string, newName: string) => {
|
||||||
|
if (!selectedProduct) return;
|
||||||
|
renameTrigger(selectedProduct.productId, triggerUuid, newName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTriggerTypeChange = (option: string) => {
|
||||||
|
if (!selectedTrigger || !selectedProduct) return;
|
||||||
|
|
||||||
|
const validTypes: Array<TriggerSchema['triggerType']> = ["onComplete", "onStart", "onStop", "delay", "onError"];
|
||||||
|
if (!validTypes.includes(option as TriggerSchema['triggerType'])) return;
|
||||||
|
|
||||||
|
setActiveOption(option as TriggerSchema['triggerType']);
|
||||||
|
updateTrigger(selectedProduct.productId, selectedTrigger.triggerUuid, {
|
||||||
|
triggerType: option as TriggerSchema['triggerType']
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggeredModel = selectedTrigger?.triggeredAsset?.triggeredModel || { modelName: "Select Model", modelUuid: "" };
|
||||||
|
const triggeredPoint = selectedTrigger?.triggeredAsset?.triggeredPoint || { pointName: "Select Point", pointUuid: "" };
|
||||||
|
const triggeredAction = selectedTrigger?.triggeredAsset?.triggeredAction || { actionName: "Select Action", actionUuid: "" };
|
||||||
|
console.log('selectedTrigger: ', selectedTrigger);
|
||||||
|
console.log('triggeredAction: ', triggeredAction);
|
||||||
|
|
||||||
|
const modelOptions = getProductById(selectedProduct.productId)?.eventDatas || [];
|
||||||
|
|
||||||
|
const pointOptions: PointsScheme[] = useMemo(() => {
|
||||||
|
if (!triggeredModel.modelUuid) return [];
|
||||||
|
|
||||||
|
const model = modelOptions.find(m => m.modelUuid === triggeredModel.modelUuid);
|
||||||
|
if (!model) return [];
|
||||||
|
|
||||||
|
if ('points' in model) {
|
||||||
|
return (model as ConveyorEventSchema).points;
|
||||||
|
} else if ('point' in model) {
|
||||||
|
return [(model as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [triggeredModel.modelUuid, modelOptions]);
|
||||||
|
|
||||||
|
const actionOptions: any = useMemo(() => {
|
||||||
|
if (!triggeredPoint.pointUuid) return [];
|
||||||
|
const point = pointOptions.find((p) => p.uuid === triggeredPoint.pointUuid);
|
||||||
|
if (!point) return [];
|
||||||
|
|
||||||
|
if ('action' in point) {
|
||||||
|
const typedPoint = point as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema;
|
||||||
|
return typedPoint.action ? [typedPoint.action] : [];
|
||||||
|
} else if ('actions' in point) {
|
||||||
|
const typedPoint = point as RoboticArmPointSchema;
|
||||||
|
return typedPoint.actions;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [triggeredPoint.pointUuid, pointOptions]);
|
||||||
|
|
||||||
|
const handleModelSelect = (option: string, triggerUuid: string) => {
|
||||||
|
if (!selectedProduct) return;
|
||||||
|
|
||||||
|
const selectedModel = modelOptions.find(m => m.modelName === option);
|
||||||
|
if (!selectedModel) return;
|
||||||
|
|
||||||
|
const event = updateTrigger(selectedProduct.productId, triggerUuid, {
|
||||||
|
triggeredAsset: {
|
||||||
|
triggeredModel: {
|
||||||
|
modelName: selectedModel.modelName,
|
||||||
|
modelUuid: selectedModel.modelUuid
|
||||||
|
},
|
||||||
|
triggeredPoint: null,
|
||||||
|
triggeredAction: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointSelect = (option: string, triggerUuid: string) => {
|
||||||
|
if (!selectedProduct || !selectedTrigger) return;
|
||||||
|
|
||||||
|
const pointUuid = pointOptions.find(p => `Point ${p.uuid.slice(0, 5)}` === option)?.uuid;
|
||||||
|
|
||||||
|
if (!pointUuid) return;
|
||||||
|
|
||||||
|
if (selectedTrigger.triggeredAsset?.triggeredModel) {
|
||||||
|
const event = updateTrigger(selectedProduct.productId, triggerUuid, {
|
||||||
|
triggeredAsset: {
|
||||||
|
...selectedTrigger.triggeredAsset,
|
||||||
|
triggeredPoint: {
|
||||||
|
pointName: option,
|
||||||
|
pointUuid: pointUuid
|
||||||
|
},
|
||||||
|
triggeredAction: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleActionSelect = (option: string, triggerUuid: string) => {
|
||||||
|
if (!selectedProduct || !selectedTrigger) return;
|
||||||
|
|
||||||
|
const selectedAction = actionOptions.find((a: any) => a.actionName === option);
|
||||||
|
|
||||||
|
if (!selectedAction) return;
|
||||||
|
|
||||||
|
if (selectedTrigger.triggeredAsset?.triggeredPoint) {
|
||||||
|
const event = updateTrigger(selectedProduct.productId, triggerUuid, {
|
||||||
|
triggeredAsset: {
|
||||||
|
...selectedTrigger.triggeredAsset,
|
||||||
|
triggeredAction: {
|
||||||
|
actionName: option,
|
||||||
|
actionUuid: selectedAction.actionUuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="trigger-wrapper">
|
||||||
|
<div className="header">
|
||||||
|
<div className="title">Trigger</div>
|
||||||
|
<button
|
||||||
|
className="add-button"
|
||||||
|
onClick={handleAddTrigger}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
disabled={!currentAction}
|
||||||
|
>
|
||||||
|
<AddIcon /> Add
|
||||||
</button>
|
</button>
|
||||||
{triggers.length > 1 && (
|
</div>
|
||||||
<button
|
<div className="trigger-list">
|
||||||
className="remove-button"
|
<div
|
||||||
onClick={() => removeTrigger(index)}
|
className="lists-main-container"
|
||||||
>
|
ref={triggersContainerRef}
|
||||||
<RemoveIcon />
|
style={{ height: "120px" }}
|
||||||
</button>
|
>
|
||||||
|
<div className="list-container">
|
||||||
|
{triggers.map((trigger) => (
|
||||||
|
<div
|
||||||
|
key={trigger.triggerUuid}
|
||||||
|
className={`list-item ${selectedTrigger?.triggerUuid === trigger.triggerUuid ? "active" : ""}`}
|
||||||
|
onClick={() => setSelectedTrigger(trigger)}
|
||||||
|
>
|
||||||
|
<button className="value">
|
||||||
|
<RenameInput
|
||||||
|
value={trigger.triggerName}
|
||||||
|
onRename={(newName) => handleTriggerRename(trigger.triggerUuid, newName)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{triggers.length > 1 && (
|
||||||
|
<button
|
||||||
|
className="remove-button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleRemoveTrigger(trigger.triggerUuid);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RemoveIcon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="resize-icon"
|
||||||
|
id="action-resize"
|
||||||
|
onMouseDown={(e: any) => handleResize(e, triggersContainerRef)}
|
||||||
|
>
|
||||||
|
<ResizeHeightIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedTrigger && (
|
||||||
|
<div className="trigger-item">
|
||||||
|
<div className="trigger-name">{selectedTrigger.triggerName}</div>
|
||||||
|
<LabledDropdown
|
||||||
|
label="Trigger Type"
|
||||||
|
defaultOption={selectedTrigger.triggerType}
|
||||||
|
options={["onComplete", "onStart", "onStop", "delay", "onError"]}
|
||||||
|
onSelect={handleTriggerTypeChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="trigger-options">
|
||||||
|
<LabledDropdown
|
||||||
|
label="Triggered Object"
|
||||||
|
defaultOption={triggeredModel.modelName}
|
||||||
|
options={[...modelOptions.map((option) => (option.modelName))]}
|
||||||
|
onSelect={(option) => { handleModelSelect(option, selectedTrigger.triggerUuid) }}
|
||||||
|
/>
|
||||||
|
<LabledDropdown
|
||||||
|
label="Triggered Point"
|
||||||
|
defaultOption={triggeredPoint.pointName}
|
||||||
|
options={[...pointOptions.map((option) => (`Point ${option.uuid.slice(0, 5)}`))]}
|
||||||
|
onSelect={(option) => { handlePointSelect(option, selectedTrigger.triggerUuid) }}
|
||||||
|
/>
|
||||||
|
<LabledDropdown
|
||||||
|
label="Triggered Action"
|
||||||
|
defaultOption={triggeredAction.actionName}
|
||||||
|
options={[...actionOptions.map((option: any) => (option.actionName))]}
|
||||||
|
onSelect={(option) => { handleActionSelect(option, selectedTrigger.triggerUuid) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="resize-icon"
|
|
||||||
id="action-resize"
|
|
||||||
onMouseDown={(e: any) => handleResize(e, triggersContainerRef)}
|
|
||||||
>
|
|
||||||
<ResizeHeightIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="trigger-item">
|
);
|
||||||
<div className="trigger-name">{selectedTrigger}</div>
|
|
||||||
<LabledDropdown
|
|
||||||
label="Trigger on"
|
|
||||||
defaultOption={activeOption}
|
|
||||||
options={["onComplete", "onStart", "onStop", "delay"]}
|
|
||||||
onSelect={(option) => setActiveOption(option)}
|
|
||||||
/>
|
|
||||||
<div className="trigger-options">
|
|
||||||
<LabledDropdown
|
|
||||||
label="Triggered Object"
|
|
||||||
defaultOption={triggeredModel[0] || "Select Model"}
|
|
||||||
options={["Model 1", "Model 2", "Model 3"]}
|
|
||||||
onSelect={(option) => {
|
|
||||||
const newModel = [...triggeredModel];
|
|
||||||
newModel[0] = option;
|
|
||||||
setTriggeredModel(newModel);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<LabledDropdown
|
|
||||||
label="Triggered Point"
|
|
||||||
defaultOption={triggeredPoint[0] || "Select Point"}
|
|
||||||
options={["Point 1", "Point 2", "Point 3"]}
|
|
||||||
onSelect={(option) => {
|
|
||||||
const newPoint = [...triggeredPoint];
|
|
||||||
newPoint[0] = option;
|
|
||||||
setTriggeredPoint(newPoint);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<LabledDropdown
|
|
||||||
label="Triggered Action"
|
|
||||||
defaultOption={triggeredAction[0] || "Select Action"}
|
|
||||||
options={["Action 1", "Action 2", "Action 3"]}
|
|
||||||
onSelect={(option) => {
|
|
||||||
const newAction = [...triggeredAction];
|
|
||||||
newAction[0] = option;
|
|
||||||
setTriggeredAction(newAction);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Trigger;
|
export default Trigger;
|
||||||
|
|
|
@ -36,7 +36,6 @@ import {
|
||||||
import useToggleStore from "../../store/useUIToggleStore";
|
import useToggleStore from "../../store/useUIToggleStore";
|
||||||
import {
|
import {
|
||||||
use3DWidget,
|
use3DWidget,
|
||||||
useDroppedObjectsStore,
|
|
||||||
useFloatingWidget,
|
useFloatingWidget,
|
||||||
} from "../../store/visualization/useDroppedObjectsStore";
|
} from "../../store/visualization/useDroppedObjectsStore";
|
||||||
|
|
||||||
|
@ -57,20 +56,18 @@ const Tools: React.FC = () => {
|
||||||
|
|
||||||
const { widgets3D } = use3DWidget();
|
const { widgets3D } = use3DWidget();
|
||||||
|
|
||||||
const zones = useDroppedObjectsStore((state) => state.zones);
|
|
||||||
|
|
||||||
// wall options
|
// wall options
|
||||||
const { toggleView, setToggleView } = useToggleView();
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
const { setDeleteTool } = useDeleteTool();
|
const { setDeleteTool } = useDeleteTool();
|
||||||
const { setAddAction } = useAddAction();
|
const { setAddAction } = useAddAction();
|
||||||
const { setSelectedWallItem } = useSelectedWallItem();
|
const { setSelectedWallItem } = useSelectedWallItem();
|
||||||
|
|
||||||
const { transformMode, setTransformMode } = useTransformMode();
|
const { setTransformMode } = useTransformMode();
|
||||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
const { setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
const { movePoint, setMovePoint } = useMovePoint();
|
const { setMovePoint } = useMovePoint();
|
||||||
const { toolMode, setToolMode } = useToolMode();
|
const { setToolMode } = useToolMode();
|
||||||
const { activeTool, setActiveTool } = useActiveTool();
|
const { activeTool, setActiveTool } = useActiveTool();
|
||||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
const { setRefTextUpdate } = useRefTextUpdate();
|
||||||
|
|
||||||
// Reset activeTool whenever activeModule changes
|
// Reset activeTool whenever activeModule changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -209,268 +206,266 @@ const Tools: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isPlaying ? (
|
{!isPlaying ? (
|
||||||
<>
|
<div className="tools-container">
|
||||||
<div className="tools-container">
|
<div className="drop-down-icons">
|
||||||
<div className="drop-down-icons">
|
<div className="activeDropicon">
|
||||||
<div className="activeDropicon">
|
{activeSubTool == "cursor" && (
|
||||||
{activeSubTool == "cursor" && (
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "cursor" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("cursor");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CursorIcon isActive={activeTool === "cursor"} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeSubTool == "free-hand" && (
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "free-hand" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("free-hand");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeSubTool == "delete" && (
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "delete" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("delete");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DeleteIcon isActive={activeTool === "delete"} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeModule !== "visualization" && (
|
|
||||||
<div
|
|
||||||
className="drop-down-option-button"
|
|
||||||
ref={dropdownRef}
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(!openDrop);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowIcon />
|
|
||||||
{openDrop && (
|
|
||||||
<div className="drop-down-container">
|
|
||||||
<div
|
|
||||||
className="option-list"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(false);
|
|
||||||
setActiveTool("cursor");
|
|
||||||
setActiveSubTool("cursor");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="active-option">
|
|
||||||
{activeSubTool === "cursor" && <TickIcon />}
|
|
||||||
</div>
|
|
||||||
<CursorIcon isActive={false} />
|
|
||||||
<div className="option">Cursor</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="option-list"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(false);
|
|
||||||
setActiveTool("free-hand");
|
|
||||||
setActiveSubTool("free-hand");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="active-option">
|
|
||||||
{activeSubTool === "free-hand" && <TickIcon />}
|
|
||||||
</div>
|
|
||||||
<FreeMoveIcon isActive={false} />
|
|
||||||
<div className="option">Free Hand</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="option-list"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(false);
|
|
||||||
setActiveTool("delete");
|
|
||||||
setActiveSubTool("delete");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="active-option">
|
|
||||||
{activeSubTool === "delete" && <TickIcon />}
|
|
||||||
</div>
|
|
||||||
<DeleteIcon isActive={false} />
|
|
||||||
<div className="option">Delete</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!toggleThreeD && activeModule === "builder" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-wall" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-wall");
|
|
||||||
}}
|
|
||||||
title="Wall"
|
|
||||||
>
|
|
||||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-zone" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-zone");
|
|
||||||
}}
|
|
||||||
title="Zone"
|
|
||||||
>
|
|
||||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-aisle" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-aisle");
|
|
||||||
}}
|
|
||||||
title="Aisle"
|
|
||||||
>
|
|
||||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-floor" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-floor");
|
|
||||||
}}
|
|
||||||
title="Floor"
|
|
||||||
>
|
|
||||||
<FloorIcon isActive={activeTool === "draw-floor"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeModule === "builder" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "measure" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("measure");
|
|
||||||
}}
|
|
||||||
title="Measure"
|
|
||||||
>
|
|
||||||
<MeasureToolIcon isActive={activeTool === "measure"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeModule === "simulation" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "pen" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("pen");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PenIcon isActive={activeTool === "pen"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeModule === "visualization" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button`}
|
|
||||||
onClick={() => {
|
|
||||||
handleSaveTemplate({
|
|
||||||
addTemplate,
|
|
||||||
floatingWidget,
|
|
||||||
widgets3D,
|
|
||||||
selectedZone,
|
|
||||||
templates,
|
|
||||||
visualizationSocket,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SaveTemplateIcon isActive={false} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="general-options">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "comment" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("comment");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CommentIcon isActive={activeTool === "comment"} />
|
|
||||||
</div>
|
|
||||||
{toggleThreeD && (
|
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${
|
||||||
activeTool === "play" ? "active" : ""
|
activeTool === "cursor" ? "active" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsPlaying(!isPlaying);
|
setActiveTool("cursor");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlayIcon isActive={activeTool === "play"} />
|
<CursorIcon isActive={activeTool === "cursor"} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeSubTool == "free-hand" && (
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "free-hand" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("free-hand");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeSubTool == "delete" && (
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "delete" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("delete");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon isActive={activeTool === "delete"} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeModule !== "visualization" && (
|
||||||
|
<div
|
||||||
|
className="drop-down-option-button"
|
||||||
|
ref={dropdownRef}
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(!openDrop);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowIcon />
|
||||||
|
{openDrop && (
|
||||||
|
<div className="drop-down-container">
|
||||||
|
<div
|
||||||
|
className="option-list"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(false);
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="active-option">
|
||||||
|
{activeSubTool === "cursor" && <TickIcon />}
|
||||||
|
</div>
|
||||||
|
<CursorIcon isActive={false} />
|
||||||
|
<div className="option">Cursor</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="option-list"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(false);
|
||||||
|
setActiveTool("free-hand");
|
||||||
|
setActiveSubTool("free-hand");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="active-option">
|
||||||
|
{activeSubTool === "free-hand" && <TickIcon />}
|
||||||
|
</div>
|
||||||
|
<FreeMoveIcon isActive={false} />
|
||||||
|
<div className="option">Free Hand</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="option-list"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(false);
|
||||||
|
setActiveTool("delete");
|
||||||
|
setActiveSubTool("delete");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="active-option">
|
||||||
|
{activeSubTool === "delete" && <TickIcon />}
|
||||||
|
</div>
|
||||||
|
<DeleteIcon isActive={false} />
|
||||||
|
<div className="option">Delete</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{activeModule === "builder" && (
|
</div>
|
||||||
<>
|
{!toggleThreeD && activeModule === "builder" && (
|
||||||
<div className="split"></div>
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
<div
|
<div
|
||||||
className={`toggle-threed-button${
|
className={`tool-button ${
|
||||||
toggleThreeD ? " toggled" : ""
|
activeTool === "draw-wall" ? "active" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={toggleSwitch}
|
onClick={() => {
|
||||||
|
setActiveTool("draw-wall");
|
||||||
|
}}
|
||||||
|
title="Wall"
|
||||||
>
|
>
|
||||||
<div
|
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||||
className={`toggle-option${!toggleThreeD ? " active" : ""}`}
|
|
||||||
>
|
|
||||||
2d
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`toggle-option${toggleThreeD ? " active" : ""}`}
|
|
||||||
>
|
|
||||||
3d
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "draw-zone" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("draw-zone");
|
||||||
|
}}
|
||||||
|
title="Zone"
|
||||||
|
>
|
||||||
|
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "draw-aisle" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("draw-aisle");
|
||||||
|
}}
|
||||||
|
title="Aisle"
|
||||||
|
>
|
||||||
|
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "draw-floor" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("draw-floor");
|
||||||
|
}}
|
||||||
|
title="Floor"
|
||||||
|
>
|
||||||
|
<FloorIcon isActive={activeTool === "draw-floor"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeModule === "builder" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "measure" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("measure");
|
||||||
|
}}
|
||||||
|
title="Measure"
|
||||||
|
>
|
||||||
|
<MeasureToolIcon isActive={activeTool === "measure"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeModule === "simulation" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "pen" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("pen");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PenIcon isActive={activeTool === "pen"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeModule === "visualization" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
|
<div
|
||||||
|
className={`tool-button`}
|
||||||
|
onClick={() => {
|
||||||
|
handleSaveTemplate({
|
||||||
|
addTemplate,
|
||||||
|
floatingWidget,
|
||||||
|
widgets3D,
|
||||||
|
selectedZone,
|
||||||
|
templates,
|
||||||
|
visualizationSocket,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SaveTemplateIcon isActive={false} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="general-options">
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "comment" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("comment");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CommentIcon isActive={activeTool === "comment"} />
|
||||||
|
</div>
|
||||||
|
{toggleThreeD && (
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "play" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setIsPlaying(!isPlaying);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayIcon isActive={activeTool === "play"} />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
{activeModule === "builder" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div
|
||||||
|
className={`toggle-threed-button${
|
||||||
|
toggleThreeD ? " toggled" : ""
|
||||||
|
}`}
|
||||||
|
onClick={toggleSwitch}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`toggle-option${!toggleThreeD ? " active" : ""}`}
|
||||||
|
>
|
||||||
|
2d
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`toggle-option${toggleThreeD ? " active" : ""}`}
|
||||||
|
>
|
||||||
|
3d
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{activeModule !== "simulation" && (
|
{activeModule !== "simulation" && (
|
||||||
<div className="exitPlay" onClick={() => setIsPlaying(false)}>
|
<button className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||||
X
|
X
|
||||||
</div>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { ROISummaryIcon } from "../../icons/analysis";
|
import { ROISummaryIcon } from "../../icons/analysis";
|
||||||
import SemiCircleProgress from "./SemiCircleProgress";
|
import SemiCircleProgress from "./SemiCircleProgress";
|
||||||
|
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||||
|
|
||||||
const ROISummary = ({
|
const ROISummary = ({
|
||||||
roiSummaryData = {
|
roiSummaryData = {
|
||||||
|
@ -121,7 +122,7 @@ const ROISummary = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className={`expand-icon ${isTableOpen ? "open" : ""}`}>
|
<span className={`expand-icon ${isTableOpen ? "open" : ""}`}>
|
||||||
{isTableOpen ? "⌵" : "⌵"}
|
<ArrowIcon />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// LogList.tsx
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
LogListIcon,
|
||||||
|
LogInfoIcon,
|
||||||
|
WarningIcon,
|
||||||
|
ErrorIcon,
|
||||||
|
CloseIcon,
|
||||||
|
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
|
||||||
|
import { useLogger } from "./LoggerContext";
|
||||||
|
|
||||||
|
const LogList: React.FC = () => {
|
||||||
|
const { logs, clear, setIsLogListVisible } = useLogger();
|
||||||
|
const [selectedTab, setSelectedTab] = useState<
|
||||||
|
"all" | "info" | "warning" | "error" | "log"
|
||||||
|
>("all");
|
||||||
|
|
||||||
|
const getLogIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case "info":
|
||||||
|
return <LogInfoIcon />;
|
||||||
|
case "error":
|
||||||
|
return <ErrorIcon />;
|
||||||
|
case "warning":
|
||||||
|
return <WarningIcon />;
|
||||||
|
case "log":
|
||||||
|
default:
|
||||||
|
return <LogInfoIcon />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
|
||||||
|
|
||||||
|
const filteredLogs =
|
||||||
|
selectedTab === "all"
|
||||||
|
? [...logs].reverse()
|
||||||
|
: [...logs].filter((log) => log.type === selectedTab).reverse();
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line
|
||||||
|
<div
|
||||||
|
className="log-list-container"
|
||||||
|
onClick={() => setIsLogListVisible(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="log-list-wrapper"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="log-header">
|
||||||
|
<div className="log-header-wrapper">
|
||||||
|
<div className="icon">
|
||||||
|
<LogListIcon />
|
||||||
|
</div>
|
||||||
|
<div className="head">Log List</div>
|
||||||
|
</div>
|
||||||
|
<button className="close" onClick={() => setIsLogListVisible(false)}>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<div className="log-nav-wrapper">
|
||||||
|
{["all", "info", "warning", "error"].map((type) => (
|
||||||
|
<div
|
||||||
|
key={type}
|
||||||
|
className={`log-nav ${selectedTab === type ? "active" : ""}`}
|
||||||
|
onClick={() => setSelectedTab(type as any)}
|
||||||
|
>
|
||||||
|
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Log Entries */}
|
||||||
|
<div className="log-entry-wrapper">
|
||||||
|
{filteredLogs.map((log) => (
|
||||||
|
<div key={log.id} className={`log-entry ${log.type}`}>
|
||||||
|
<div className="log-icon">{getLogIcon(log.type)}</div>
|
||||||
|
<div className="log-entry-message-container">
|
||||||
|
<div className="log-entry-message">{log.message}</div>
|
||||||
|
<div className="message-time">
|
||||||
|
{formatTimestamp(log.timestamp)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogList;
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React, { createContext, useContext, useState, useCallback } from "react";
|
||||||
|
|
||||||
|
export type LogType = "log" | "info" | "warning" | "error";
|
||||||
|
|
||||||
|
export interface LogEntry {
|
||||||
|
id: string;
|
||||||
|
type: LogType;
|
||||||
|
message: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoggerContextValue {
|
||||||
|
logs: LogEntry[];
|
||||||
|
setLogs: React.Dispatch<React.SetStateAction<LogEntry[]>>;
|
||||||
|
isLogListVisible: boolean;
|
||||||
|
setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
log: (message: string) => void;
|
||||||
|
info: (message: string) => void;
|
||||||
|
warn: (message: string) => void;
|
||||||
|
error: (message: string) => void;
|
||||||
|
clear: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoggerContext = createContext<LoggerContextValue | undefined>(undefined);
|
||||||
|
|
||||||
|
export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||||
|
const [isLogListVisible, setIsLogListVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const generateId = useCallback(
|
||||||
|
() => Math.random().toString(36).substring(2, 9),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addLog = useCallback(
|
||||||
|
(type: LogType, message: string) => {
|
||||||
|
const newLog: LogEntry = {
|
||||||
|
id: generateId(),
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
setLogs((prevLogs) => [...prevLogs, newLog]);
|
||||||
|
},
|
||||||
|
[generateId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const loggerMethods: LoggerContextValue = {
|
||||||
|
logs,
|
||||||
|
setLogs,
|
||||||
|
isLogListVisible,
|
||||||
|
setIsLogListVisible,
|
||||||
|
log: (message: string) => addLog("log", message),
|
||||||
|
info: (message: string) => addLog("info", message),
|
||||||
|
warn: (message: string) => addLog("warning", message),
|
||||||
|
error: (message: string) => addLog("error", message),
|
||||||
|
clear: () => {
|
||||||
|
setLogs([]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoggerContext.Provider value={loggerMethods}>
|
||||||
|
{children}
|
||||||
|
</LoggerContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLogger = () => {
|
||||||
|
const context = useContext(LoggerContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useLogger must be used within a LoggerProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
|
@ -0,0 +1,71 @@
|
||||||
|
type LogType = 'log' | 'info' | 'warning' | 'error';
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
type: LogType;
|
||||||
|
message: string;
|
||||||
|
timestamp: Date;
|
||||||
|
context?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
private static instance: Logger;
|
||||||
|
private logs: LogEntry[] = [];
|
||||||
|
private subscribers: Array<(log: LogEntry) => void> = [];
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): Logger {
|
||||||
|
if (!Logger.instance) {
|
||||||
|
Logger.instance = new Logger();
|
||||||
|
}
|
||||||
|
return Logger.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifySubscribers(log: LogEntry) {
|
||||||
|
this.subscribers.forEach(callback => callback(log));
|
||||||
|
}
|
||||||
|
|
||||||
|
private addLog(type: LogType, message: string, context?: string) {
|
||||||
|
const log: LogEntry = { type, message, timestamp: new Date(), context };
|
||||||
|
this.logs.push(log);
|
||||||
|
this.notifySubscribers(log);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
const logMessage = context ? `[${context}] ${message}` : message;
|
||||||
|
console[type === 'warning' ? 'warn' : type](logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(message: string, context?: string) {
|
||||||
|
this.addLog('log', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(message: string, context?: string) {
|
||||||
|
this.addLog('info', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warning(message: string, context?: string) {
|
||||||
|
this.addLog('warning', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(message: string, context?: string) {
|
||||||
|
this.addLog('error', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLogs(): LogEntry[] {
|
||||||
|
return [...this.logs];
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.logs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe(callback: (log: LogEntry) => void) {
|
||||||
|
this.subscribers.push(callback);
|
||||||
|
return () => {
|
||||||
|
this.subscribers = this.subscribers.filter(sub => sub !== callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = Logger.getInstance();
|
|
@ -12,25 +12,31 @@ import {
|
||||||
EndIcon,
|
EndIcon,
|
||||||
ExpandIcon,
|
ExpandIcon,
|
||||||
HourlySimulationIcon,
|
HourlySimulationIcon,
|
||||||
|
InfoIcon,
|
||||||
MonthlyROI,
|
MonthlyROI,
|
||||||
SpeedIcon,
|
SpeedIcon,
|
||||||
StartIcon,
|
StartIcon,
|
||||||
} from "../../icons/ExportCommonIcons";
|
} from "../../icons/ExportCommonIcons";
|
||||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||||
|
import { useSubModuleStore } from "../../../store/useModuleStore";
|
||||||
|
import ProductionCapacity from "../analysis/ProductionCapacity";
|
||||||
|
import ThroughputSummary from "../analysis/ThroughputSummary";
|
||||||
|
import ROISummary from "../analysis/ROISummary";
|
||||||
|
|
||||||
const SimulationPlayer: React.FC = () => {
|
const SimulationPlayer: React.FC = () => {
|
||||||
const MAX_SPEED = 8; // Maximum speed
|
const MAX_SPEED = 8; // Maximum speed
|
||||||
|
|
||||||
|
const isDragging = useRef(false);
|
||||||
|
const sliderRef = useRef<HTMLDivElement>(null);
|
||||||
const [expand, setExpand] = useState(true);
|
const [expand, setExpand] = useState(true);
|
||||||
|
const [playSimulation, setPlaySimulation] = useState(false);
|
||||||
|
|
||||||
const { speed, setSpeed } = useAnimationPlaySpeed();
|
const { speed, setSpeed } = useAnimationPlaySpeed();
|
||||||
const [playSimulation, setPlaySimulation] = useState(false);
|
|
||||||
const { setIsPlaying } = usePlayButtonStore();
|
const { setIsPlaying } = usePlayButtonStore();
|
||||||
const sliderRef = useRef<HTMLDivElement>(null);
|
|
||||||
const isDragging = useRef(false);
|
|
||||||
const { setActiveTool } = useActiveTool();
|
const { setActiveTool } = useActiveTool();
|
||||||
const { isPaused, setIsPaused } = usePauseButtonStore();
|
const { isPaused, setIsPaused } = usePauseButtonStore();
|
||||||
const { isReset, setReset } = useResetButtonStore();
|
const { isReset, setReset } = useResetButtonStore();
|
||||||
|
const { subModule } = useSubModuleStore();
|
||||||
|
|
||||||
// Button functions
|
// Button functions
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
|
@ -114,7 +120,7 @@ const SimulationPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store colors for each process item
|
// Store colors for each process item
|
||||||
const [processColors, setProcessColors] = useState<string[]>([]);
|
const [_, setProcessColors] = useState<string[]>([]);
|
||||||
|
|
||||||
// Generate colors on mount or when process changes
|
// Generate colors on mount or when process changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -159,210 +165,236 @@ const SimulationPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="simulation-player-wrapper">
|
<>
|
||||||
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
<div className="simulation-player-wrapper">
|
||||||
<div className="controls-container">
|
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
||||||
<div className="production-details">
|
<div className="controls-container">
|
||||||
{/* hourlySimulation */}
|
{subModule === "analysis" && (
|
||||||
<div className="hourly-wrapper production-wrapper">
|
<div className="production-details">
|
||||||
<div className="header">
|
{/* hourlySimulation */}
|
||||||
<div className="icon">
|
<div className="hourly-wrapper production-wrapper">
|
||||||
<HourlySimulationIcon />
|
<div className="header">
|
||||||
|
<div className="icon">
|
||||||
|
<HourlySimulationIcon />
|
||||||
|
</div>
|
||||||
|
<div className="label">Hourly Simulation</div>
|
||||||
|
</div>
|
||||||
|
<div className="progress-wrapper">
|
||||||
|
<div
|
||||||
|
className="progress"
|
||||||
|
style={{ width: hourlySimulation }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="label">Hourly Simulation</div>
|
{/* dailyProduction */}
|
||||||
</div>
|
<div className="dailyProduction-wrapper production-wrapper">
|
||||||
<div className="progress-wrapper">
|
<div className="header">
|
||||||
<div
|
<div className="icon">
|
||||||
className="progress"
|
<DailyProductionIcon />
|
||||||
style={{ width: hourlySimulation }}
|
</div>
|
||||||
></div>
|
<div className="label">Daily Production</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="progress-wrapper">
|
||||||
{/* dailyProduction */}
|
<div
|
||||||
<div className="dailyProduction-wrapper production-wrapper">
|
className="progress"
|
||||||
<div className="header">
|
style={{ width: dailyProduction }}
|
||||||
<div className="icon">
|
></div>
|
||||||
<DailyProductionIcon />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="label">Daily Production</div>
|
{/* monthlyROI */}
|
||||||
</div>
|
<div className="monthlyROI-wrapper production-wrapper">
|
||||||
<div className="progress-wrapper">
|
<div className="header">
|
||||||
<div
|
<div className="icon">
|
||||||
className="progress"
|
<MonthlyROI />
|
||||||
style={{ width: dailyProduction }}
|
</div>
|
||||||
></div>
|
<div className="label">Monthly ROI</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="progress-wrapper">
|
||||||
{/* monthlyROI */}
|
<div
|
||||||
<div className="monthlyROI-wrapper production-wrapper">
|
className="progress"
|
||||||
<div className="header">
|
style={{ width: monthlyROI }}
|
||||||
<div className="icon">
|
></div>
|
||||||
<MonthlyROI />
|
</div>{" "}
|
||||||
</div>
|
</div>
|
||||||
<div className="label">Monthly ROI</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="progress-wrapper">
|
)}
|
||||||
<div className="progress" style={{ width: monthlyROI }}></div>
|
{subModule === "simulations" && (
|
||||||
</div>{" "}
|
<div className="header">
|
||||||
</div>
|
<InfoIcon />
|
||||||
</div>
|
{playSimulation
|
||||||
<div className="controls-wrapper">
|
? "Paused - system idle."
|
||||||
<button
|
: "Running simulation..."}
|
||||||
className="simulation-button-container"
|
|
||||||
onClick={() => {
|
|
||||||
handleReset();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ResetIcon />
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="simulation-button-container"
|
|
||||||
onClick={() => {
|
|
||||||
handlePlayStop();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlayStopIcon />
|
|
||||||
{playSimulation ? "Play" : "Stop"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="simulation-button-container"
|
|
||||||
onClick={() => {
|
|
||||||
handleExit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ExitIcon />
|
|
||||||
Exit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="expand-icon-container"
|
|
||||||
onClick={() => setExpand(!expand)}
|
|
||||||
>
|
|
||||||
<ExpandIcon isActive={!expand} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="progresser-wrapper">
|
|
||||||
<div className="time-displayer">
|
|
||||||
<div className="start-time-wrappper">
|
|
||||||
<div className="icon">
|
|
||||||
<StartIcon />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="time-wrapper">
|
)}
|
||||||
<div className="date">23 April ,25</div>
|
<div className="controls-wrapper">
|
||||||
<div className="time">04:41 PM</div>
|
<button
|
||||||
</div>
|
className="simulation-button-container"
|
||||||
</div>
|
onClick={() => {
|
||||||
<div className="time-progresser">
|
handleReset();
|
||||||
<div className="timeline">
|
}}
|
||||||
{intervals.map((label, index) => {
|
>
|
||||||
const segmentProgress = (index / totalSegments) * 100;
|
<ResetIcon />
|
||||||
const isFilled = progress >= segmentProgress;
|
Reset
|
||||||
return (
|
</button>
|
||||||
<React.Fragment key={index}>
|
<button
|
||||||
<div className="label-dot-wrapper">
|
className="simulation-button-container"
|
||||||
<div className="label">{label} mins</div>
|
onClick={() => {
|
||||||
<div
|
handlePlayStop();
|
||||||
className={`dot ${isFilled ? "filled" : ""}`}
|
}}
|
||||||
></div>
|
>
|
||||||
</div>
|
<PlayStopIcon />
|
||||||
{index < intervals.length - 1 && (
|
{playSimulation ? "Play" : "Stop"}
|
||||||
<div
|
</button>
|
||||||
className={`line ${
|
<button
|
||||||
progress >= ((index + 1) / totalSegments) * 100
|
className="simulation-button-container"
|
||||||
? "filled"
|
onClick={() => {
|
||||||
: ""
|
handleExit();
|
||||||
}`}
|
}}
|
||||||
></div>
|
>
|
||||||
)}
|
<ExitIcon />
|
||||||
</React.Fragment>
|
Exit
|
||||||
);
|
</button>
|
||||||
})}
|
{subModule === "analysis" && (
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="end-time-wrappper">
|
|
||||||
<div className="time-wrapper">
|
|
||||||
<div className="time">00:10:20</div>
|
|
||||||
</div>
|
|
||||||
<div className="icon">
|
|
||||||
<EndIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="speed-control-container">
|
|
||||||
<div className="min-value">
|
|
||||||
<div className="icon">
|
|
||||||
<SpeedIcon />
|
|
||||||
</div>
|
|
||||||
Speed
|
|
||||||
</div>
|
|
||||||
<div className="slider-container" ref={sliderRef}>
|
|
||||||
<div className="speed-label mix-value">0.5X</div>
|
|
||||||
<div className="marker marker-10"></div>
|
|
||||||
<div className="marker marker-20"></div>
|
|
||||||
<div className="marker marker-30"></div>
|
|
||||||
<div className="marker marker-40"></div>
|
|
||||||
<div className="marker marker-50"></div>
|
|
||||||
<div className="marker marker-60"></div>
|
|
||||||
<div className="marker marker-70"></div>
|
|
||||||
<div className="marker marker-80"></div>
|
|
||||||
<div className="marker marker-90"></div>
|
|
||||||
<div className="custom-slider">
|
|
||||||
<button
|
<button
|
||||||
className={`slider-handle ${isDragging ? "dragging" : ""}`}
|
className="expand-icon-container"
|
||||||
style={{ left: `${calculateHandlePosition()}%` }}
|
onClick={() => setExpand(!expand)}
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
>
|
>
|
||||||
{speed.toFixed(1)}x
|
<ExpandIcon isActive={!expand} />
|
||||||
</button>
|
</button>
|
||||||
<input
|
)}
|
||||||
type="range"
|
|
||||||
min="0.5"
|
|
||||||
max={MAX_SPEED}
|
|
||||||
step="0.1"
|
|
||||||
value={speed}
|
|
||||||
onChange={handleSpeedChange}
|
|
||||||
className="slider-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="speed-label max-value">4x</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="progresser-wrapper">
|
||||||
<div className="processDisplayer">
|
<div className="time-displayer">
|
||||||
<div className="start-displayer timmer">00:00</div>
|
<div className="start-time-wrappper">
|
||||||
<div className="end-displayer timmer">24:00</div>
|
<div className="icon">
|
||||||
<div
|
<StartIcon />
|
||||||
className="process-wrapper"
|
|
||||||
style={{ padding: expand ? "0px" : "5px 35px" }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="process-container"
|
|
||||||
ref={processWrapperRef}
|
|
||||||
onMouseDown={handleProcessMouseDown}
|
|
||||||
>
|
|
||||||
{process.map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="process"
|
|
||||||
style={{
|
|
||||||
width: `${item.completed}%`,
|
|
||||||
backgroundColor: getAvatarColor(index),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="process-player"
|
|
||||||
ref={processPlayerRef}
|
|
||||||
style={{ left: playerPosition, position: "absolute" }}
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="time-wrapper">
|
||||||
|
<div className="date">23 April ,25</div>
|
||||||
|
<div className="time">04:41 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="time-progresser">
|
||||||
|
<div className="timeline">
|
||||||
|
{intervals.map((label, index) => {
|
||||||
|
const segmentProgress = (index / totalSegments) * 100;
|
||||||
|
const isFilled = progress >= segmentProgress;
|
||||||
|
return (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<div className="label-dot-wrapper">
|
||||||
|
<div className="label">{label} mins</div>
|
||||||
|
<div
|
||||||
|
className={`dot ${isFilled ? "filled" : ""}`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
{index < intervals.length - 1 && (
|
||||||
|
<div
|
||||||
|
className={`line ${
|
||||||
|
progress >= ((index + 1) / totalSegments) * 100
|
||||||
|
? "filled"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="end-time-wrappper">
|
||||||
|
<div className="time-wrapper">
|
||||||
|
<div className="time">00:10:20</div>
|
||||||
|
</div>
|
||||||
|
<div className="icon">
|
||||||
|
<EndIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="speed-control-container">
|
||||||
|
<div className="min-value">
|
||||||
|
<div className="icon">
|
||||||
|
<SpeedIcon />
|
||||||
|
</div>
|
||||||
|
Speed
|
||||||
|
</div>
|
||||||
|
<div className="slider-container" ref={sliderRef}>
|
||||||
|
<div className="speed-label mix-value">0.5X</div>
|
||||||
|
<div className="marker marker-10"></div>
|
||||||
|
<div className="marker marker-20"></div>
|
||||||
|
<div className="marker marker-30"></div>
|
||||||
|
<div className="marker marker-40"></div>
|
||||||
|
<div className="marker marker-50"></div>
|
||||||
|
<div className="marker marker-60"></div>
|
||||||
|
<div className="marker marker-70"></div>
|
||||||
|
<div className="marker marker-80"></div>
|
||||||
|
<div className="marker marker-90"></div>
|
||||||
|
<div className="custom-slider">
|
||||||
|
<button
|
||||||
|
className={`slider-handle ${isDragging ? "dragging" : ""}`}
|
||||||
|
style={{ left: `${calculateHandlePosition()}%` }}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
{speed.toFixed(1)}x
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.5"
|
||||||
|
max={MAX_SPEED}
|
||||||
|
step="0.1"
|
||||||
|
value={speed}
|
||||||
|
onChange={handleSpeedChange}
|
||||||
|
className="slider-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="speed-label max-value">4x</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{subModule === "analysis" && (
|
||||||
|
<div className="processDisplayer">
|
||||||
|
<div className="start-displayer timmer">00:00</div>
|
||||||
|
<div className="end-displayer timmer">24:00</div>
|
||||||
|
<div
|
||||||
|
className="process-wrapper"
|
||||||
|
style={{ padding: expand ? "0px" : "5px 35px" }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="process-container"
|
||||||
|
ref={processWrapperRef}
|
||||||
|
onMouseDown={handleProcessMouseDown}
|
||||||
|
>
|
||||||
|
{process.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="process"
|
||||||
|
style={{
|
||||||
|
width: `${item.completed}%`,
|
||||||
|
backgroundColor: getAvatarColor(index),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="process-player"
|
||||||
|
ref={processPlayerRef}
|
||||||
|
style={{ left: playerPosition, position: "absolute" }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="analysis">
|
||||||
|
<div className="analysis-wrapper">
|
||||||
|
<ProductionCapacity />
|
||||||
|
<ThroughputSummary />
|
||||||
|
</div>
|
||||||
|
<ROISummary />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import * as Types from "../../../types/world/worldTypes";
|
||||||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||||
import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator';
|
|
||||||
|
|
||||||
async function loadInitialFloorItems(
|
async function loadInitialFloorItems(
|
||||||
itemsGroup: Types.RefGroup,
|
itemsGroup: Types.RefGroup,
|
||||||
|
@ -72,7 +71,7 @@ async function loadInitialFloorItems(
|
||||||
// Check Three.js Cache
|
// Check Three.js Cache
|
||||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||||
if (cachedModel) {
|
if (cachedModel) {
|
||||||
// console.log(`[Cache] Fetching ${item.modelName}`);
|
//
|
||||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
|
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
|
||||||
modelsLoaded++;
|
modelsLoaded++;
|
||||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
|
@ -82,7 +81,7 @@ async function loadInitialFloorItems(
|
||||||
// Check IndexedDB
|
// Check IndexedDB
|
||||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||||
if (indexedDBModel) {
|
if (indexedDBModel) {
|
||||||
// console.log(`[IndexedDB] Fetching ${item.modelName}`);
|
//
|
||||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||||
loader.load(blobUrl, (gltf) => {
|
loader.load(blobUrl, (gltf) => {
|
||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
@ -103,7 +102,7 @@ async function loadInitialFloorItems(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch from Backend
|
// Fetch from Backend
|
||||||
// console.log(`[Backend] Fetching ${item.modelName}`);
|
//
|
||||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
|
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
|
||||||
loader.load(modelUrl, async (gltf) => {
|
loader.load(modelUrl, async (gltf) => {
|
||||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||||
|
@ -121,7 +120,7 @@ async function loadInitialFloorItems(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// console.log(`Item ${item.modelName} is not near`);
|
//
|
||||||
setFloorItems((prevItems) => [
|
setFloorItems((prevItems) => [
|
||||||
...(prevItems || []),
|
...(prevItems || []),
|
||||||
{
|
{
|
||||||
|
@ -187,7 +186,7 @@ function processLoadedModel(
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (item.eventData.type === "vehicle") {
|
if (item.eventData.type === "Vehicle") {
|
||||||
const vehicleEvent: VehicleEventSchema = {
|
const vehicleEvent: VehicleEventSchema = {
|
||||||
modelUuid: item.modelUuid,
|
modelUuid: item.modelUuid,
|
||||||
modelName: item.modelName,
|
modelName: item.modelName,
|
||||||
|
@ -202,11 +201,11 @@ function processLoadedModel(
|
||||||
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
|
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
|
||||||
action: {
|
action: {
|
||||||
actionUuid: THREE.MathUtils.generateUUID(),
|
actionUuid: THREE.MathUtils.generateUUID(),
|
||||||
actionName: "Vehicle Action",
|
actionName: "Action 1",
|
||||||
actionType: "travel",
|
actionType: "travel",
|
||||||
unLoadDuration: 5,
|
unLoadDuration: 5,
|
||||||
loadCapacity: 10,
|
loadCapacity: 10,
|
||||||
steeringAngle:0,
|
steeringAngle: 0,
|
||||||
pickUpPoint: null,
|
pickUpPoint: null,
|
||||||
unLoadPoint: null,
|
unLoadPoint: null,
|
||||||
triggers: []
|
triggers: []
|
||||||
|
@ -254,7 +253,7 @@ function processLoadedModel(
|
||||||
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
|
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
|
||||||
action: {
|
action: {
|
||||||
actionUuid: THREE.MathUtils.generateUUID(),
|
actionUuid: THREE.MathUtils.generateUUID(),
|
||||||
actionName: "Process Action",
|
actionName: "Action 1",
|
||||||
actionType: "process",
|
actionType: "process",
|
||||||
processTime: 10,
|
processTime: 10,
|
||||||
swapMaterial: "material-id",
|
swapMaterial: "material-id",
|
||||||
|
@ -279,11 +278,11 @@ function processLoadedModel(
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
actionUuid: THREE.MathUtils.generateUUID(),
|
actionUuid: THREE.MathUtils.generateUUID(),
|
||||||
actionName: "Pick and Place",
|
actionName: "Action 1",
|
||||||
actionType: "pickAndPlace",
|
actionType: "pickAndPlace",
|
||||||
process: {
|
process: {
|
||||||
startPoint: [0, 0, 0],
|
startPoint: null,
|
||||||
endPoint: [0, 0, 0]
|
endPoint: null
|
||||||
},
|
},
|
||||||
triggers: []
|
triggers: []
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,22 +186,59 @@ async function handleModelLoad(
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: 'transfer',
|
type: 'transfer',
|
||||||
speed: 1,
|
speed: 1,
|
||||||
points: data.points.map((point: THREE.Vector3, index: number) => ({
|
points: data.points.map((point: THREE.Vector3, index: number) => {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
const triggers: TriggerSchema[] = [];
|
||||||
position: [point.x, point.y, point.z],
|
|
||||||
rotation: [0, 0, 0],
|
if (data.points && index < data.points.length - 1) {
|
||||||
action: {
|
triggers.push({
|
||||||
actionUuid: THREE.MathUtils.generateUUID(),
|
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||||
actionName: `Action ${index}`,
|
triggerName: `Trigger 1`,
|
||||||
actionType: 'default',
|
triggerType: "onComplete",
|
||||||
material: 'Default Material',
|
delay: 0,
|
||||||
delay: 0,
|
triggeredAsset: {
|
||||||
spawnInterval: 5,
|
triggeredModel: {
|
||||||
spawnCount: 1,
|
modelName: newFloorItem.modelName,
|
||||||
triggers: []
|
modelUuid: newFloorItem.modelUuid
|
||||||
|
},
|
||||||
|
triggeredPoint: {
|
||||||
|
pointName: `Point`,
|
||||||
|
pointUuid: ""
|
||||||
|
},
|
||||||
|
triggeredAction: {
|
||||||
|
actionName: `Action 1`,
|
||||||
|
actionUuid: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}))
|
|
||||||
|
return {
|
||||||
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
|
position: [point.x, point.y, point.z],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
action: {
|
||||||
|
actionUuid: THREE.MathUtils.generateUUID(),
|
||||||
|
actionName: `Action 1`,
|
||||||
|
actionType: 'default',
|
||||||
|
material: 'Default Material',
|
||||||
|
delay: 0,
|
||||||
|
spawnInterval: 5,
|
||||||
|
spawnCount: 1,
|
||||||
|
triggers: triggers
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < ConveyorEvent.points.length - 1; i++) {
|
||||||
|
const currentPoint = ConveyorEvent.points[i];
|
||||||
|
const nextPoint = ConveyorEvent.points[i + 1];
|
||||||
|
|
||||||
|
if (currentPoint.action.triggers.length > 0) {
|
||||||
|
currentPoint.action.triggers[0].triggeredAsset!.triggeredPoint!.pointUuid = nextPoint.uuid;
|
||||||
|
currentPoint.action.triggers[0].triggeredAsset!.triggeredAction!.actionUuid = nextPoint.action.actionUuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
addEvent(ConveyorEvent);
|
addEvent(ConveyorEvent);
|
||||||
eventData.points = ConveyorEvent.points.map(point => ({
|
eventData.points = ConveyorEvent.points.map(point => ({
|
||||||
uuid: point.uuid,
|
uuid: point.uuid,
|
||||||
|
@ -228,7 +265,7 @@ async function handleModelLoad(
|
||||||
actionType: "travel",
|
actionType: "travel",
|
||||||
unLoadDuration: 5,
|
unLoadDuration: 5,
|
||||||
loadCapacity: 10,
|
loadCapacity: 10,
|
||||||
steeringAngle:0,
|
steeringAngle: 0,
|
||||||
pickUpPoint: null,
|
pickUpPoint: null,
|
||||||
unLoadPoint: null,
|
unLoadPoint: null,
|
||||||
triggers: []
|
triggers: []
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default async function assetManager(
|
||||||
const taskId = ++currentTaskId; // Increment taskId for each call
|
const taskId = ++currentTaskId; // Increment taskId for each call
|
||||||
activePromises.set(taskId, true); // Mark task as active
|
activePromises.set(taskId, true); // Mark task as active
|
||||||
|
|
||||||
// console.log("Received message from worker:", data);
|
//
|
||||||
|
|
||||||
if (data.toRemove.length > 0) {
|
if (data.toRemove.length > 0) {
|
||||||
data.toRemove.forEach((uuid: string) => {
|
data.toRemove.forEach((uuid: string) => {
|
||||||
|
@ -58,7 +58,7 @@ export default async function assetManager(
|
||||||
// Check Three.js Cache
|
// Check Three.js Cache
|
||||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||||
if (cachedModel) {
|
if (cachedModel) {
|
||||||
// console.log(`[Cache] Fetching ${item.modelName}`);
|
//
|
||||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
|
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export default async function assetManager(
|
||||||
// Check IndexedDB
|
// Check IndexedDB
|
||||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||||
if (indexedDBModel) {
|
if (indexedDBModel) {
|
||||||
// console.log(`[IndexedDB] Fetching ${item.modelName}`);
|
//
|
||||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||||
loader.load(
|
loader.load(
|
||||||
blobUrl,
|
blobUrl,
|
||||||
|
@ -86,7 +86,7 @@ export default async function assetManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch from Backend
|
// Fetch from Backend
|
||||||
// console.log(`[Backend] Fetching ${item.modelName}`);
|
//
|
||||||
loader.load(
|
loader.load(
|
||||||
modelUrl,
|
modelUrl,
|
||||||
async (gltf) => {
|
async (gltf) => {
|
||||||
|
@ -114,14 +114,14 @@ export default async function assetManager(
|
||||||
|
|
||||||
const existingModel = itemsGroup?.current?.getObjectByProperty("uuid", item.modelUuid);
|
const existingModel = itemsGroup?.current?.getObjectByProperty("uuid", item.modelUuid);
|
||||||
if (existingModel) {
|
if (existingModel) {
|
||||||
// console.log(`Model ${item.modelName} already exists in the scene.`);
|
//
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = gltf;
|
const model = gltf;
|
||||||
model.uuid = item.modelUuid;
|
model.uuid = item.modelUuid;
|
||||||
model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid };
|
model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid, eventData: item.eventData };
|
||||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||||
model.position.set(...item.position);
|
model.position.set(...item.position);
|
||||||
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useFrame, useThree } from "@react-three/fiber";
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
|
import { useFloorItems, useSelectedAssets, useSocketStore, useStartSimulation, useToggleView } from "../../../../store/store";
|
||||||
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import * as Types from "../../../../types/world/worldTypes";
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
@ -9,6 +9,8 @@ import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifie
|
||||||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||||
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
import { snapControls } from "../../../../utils/handleSnap";
|
||||||
|
|
||||||
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
|
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
|
||||||
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||||
|
@ -16,9 +18,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
|
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
const { floorItems, setFloorItems } = useFloorItems();
|
const { floorItems, setFloorItems } = useFloorItems();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const itemsData = useRef<Types.FloorItems>([]);
|
const itemsData = useRef<Types.FloorItems>([]);
|
||||||
|
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("")
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
|
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
|
||||||
|
@ -35,6 +56,15 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
const onPointerMove = () => {
|
const onPointerMove = () => {
|
||||||
isMoving = true;
|
isMoving = true;
|
||||||
};
|
};
|
||||||
|
const onKeyUp = (event: KeyboardEvent) => {
|
||||||
|
// When any modifier is released, reset snap
|
||||||
|
const isModifierKey =
|
||||||
|
event.key === "Control" || event.key === "Shift";
|
||||||
|
|
||||||
|
if (isModifierKey) {
|
||||||
|
setKeyEvent("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onPointerUp = (event: PointerEvent) => {
|
const onPointerUp = (event: PointerEvent) => {
|
||||||
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
|
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
|
||||||
|
@ -56,18 +86,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
setMovedObjects([]);
|
setMovedObjects([]);
|
||||||
itemsData.current = [];
|
itemsData.current = [];
|
||||||
}
|
}
|
||||||
|
setKeyEvent("")
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
const keyCombination = detectModifierKeys(event);
|
const keyCombination = detectModifierKeys(event);
|
||||||
|
|
||||||
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
|
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
|
||||||
|
|
||||||
|
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||||
|
// update state here
|
||||||
|
setKeyEvent(keyCombination)
|
||||||
|
} else {
|
||||||
|
setKeyEvent("")
|
||||||
|
}
|
||||||
|
|
||||||
if (keyCombination === "G") {
|
if (keyCombination === "G") {
|
||||||
if (selectedAssets.length > 0) {
|
if (selectedAssets.length > 0) {
|
||||||
moveAssets();
|
moveAssets();
|
||||||
itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid));
|
itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCombination === "ESCAPE") {
|
if (keyCombination === "ESCAPE") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -90,6 +130,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
canvasElement.addEventListener("pointermove", onPointerMove);
|
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||||
canvasElement.addEventListener("pointerup", onPointerUp);
|
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||||
canvasElement.addEventListener("keydown", onKeyDown);
|
canvasElement.addEventListener("keydown", onKeyDown);
|
||||||
|
canvasElement?.addEventListener("keyup", onKeyUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -97,12 +138,11 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
canvasElement.removeEventListener("pointermove", onPointerMove);
|
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||||
canvasElement.removeEventListener("pointerup", onPointerUp);
|
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||||
canvasElement.removeEventListener("keydown", onKeyDown);
|
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||||
|
canvasElement?.removeEventListener("keyup", onKeyUp);
|
||||||
};
|
};
|
||||||
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);
|
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]);
|
||||||
|
|
||||||
const gridSize = 0.25;
|
let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25;
|
||||||
const moveSpeed = 0.25;
|
|
||||||
const isGridSnap = false;
|
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (movedObjects.length > 0) {
|
if (movedObjects.length > 0) {
|
||||||
|
@ -113,10 +153,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
if (point) {
|
if (point) {
|
||||||
let targetX = point.x;
|
let targetX = point.x;
|
||||||
let targetZ = point.z;
|
let targetZ = point.z;
|
||||||
|
if (keyEvent === "Ctrl") {
|
||||||
|
targetX = snapControls(targetX, "Ctrl");
|
||||||
|
targetZ = snapControls(targetZ, "Ctrl");
|
||||||
|
} else if (keyEvent === "Ctrl+Shift") {
|
||||||
|
targetX = snapControls(targetX, "Ctrl+Shift");
|
||||||
|
targetZ = snapControls(targetZ, "Ctrl+Shift");
|
||||||
|
} else if (keyEvent === "Shift") {
|
||||||
|
targetX = snapControls(targetX, "Shift");
|
||||||
|
targetZ = snapControls(targetZ, "Shift");
|
||||||
|
} else {
|
||||||
|
|
||||||
if (isGridSnap) {
|
|
||||||
targetX = Math.round(point.x / gridSize) * gridSize;
|
|
||||||
targetZ = Math.round(point.z / gridSize) * gridSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = new THREE.Vector3();
|
const position = new THREE.Vector3();
|
||||||
|
@ -190,10 +237,21 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (productData) {
|
if (productData) {
|
||||||
useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
|
const event = useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
|
||||||
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
||||||
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
|
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
newFloorItem.eventData = eventData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,9 +261,6 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
return updatedItems;
|
return updatedItems;
|
||||||
});
|
});
|
||||||
|
|
||||||
const email = localStorage.getItem("email");
|
|
||||||
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
|
||||||
|
|
||||||
//REST
|
//REST
|
||||||
|
|
||||||
// await setFloorItemApi(
|
// await setFloorItemApi(
|
||||||
|
@ -253,6 +308,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
setMovedObjects([]);
|
setMovedObjects([]);
|
||||||
setRotatedObjects([]);
|
setRotatedObjects([]);
|
||||||
setSelectedAssets([]);
|
setSelectedAssets([]);
|
||||||
|
setKeyEvent("")
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import * as Types from "../../../../types/world/worldTypes";
|
||||||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||||
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
|
||||||
function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) {
|
function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) {
|
||||||
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||||
|
@ -15,10 +16,28 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
|
||||||
|
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
const { floorItems, setFloorItems } = useFloorItems();
|
const { floorItems, setFloorItems } = useFloorItems();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const itemsData = useRef<Types.FloorItems>([]);
|
const itemsData = useRef<Types.FloorItems>([]);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
|
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -190,10 +209,21 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (productData) {
|
if (productData) {
|
||||||
useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
|
const event = useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
|
||||||
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
||||||
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
|
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
newFloorItem.eventData = eventData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,9 +233,6 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
|
||||||
return updatedItems;
|
return updatedItems;
|
||||||
});
|
});
|
||||||
|
|
||||||
const email = localStorage.getItem("email");
|
|
||||||
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
|
||||||
|
|
||||||
//REST
|
//REST
|
||||||
|
|
||||||
// await setFloorItemApi(
|
// await setFloorItemApi(
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||||
import useModuleStore from "../../../../../store/useModuleStore";
|
import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
|
||||||
import { TransformControls } from "@react-three/drei";
|
import { TransformControls } from "@react-three/drei";
|
||||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||||
import {
|
import {
|
||||||
useSelectedEventSphere,
|
useSelectedEventSphere,
|
||||||
useSelectedEventData,
|
useSelectedEventData,
|
||||||
|
useIsDragging,
|
||||||
|
useIsRotating,
|
||||||
} from "../../../../../store/simulation/useSimulationStore";
|
} from "../../../../../store/simulation/useSimulationStore";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
|
||||||
function PointsCreator() {
|
function PointsCreator() {
|
||||||
|
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||||
|
const { subModule } = useSubModuleStore();
|
||||||
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
|
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const transformRef = useRef<any>(null);
|
const transformRef = useRef<any>(null);
|
||||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||||
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
||||||
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
|
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
|
||||||
const { selectedEventData, setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
|
const { selectedEventData,setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
|
||||||
|
const { isDragging } = useIsDragging();
|
||||||
|
const { isRotating } = useIsRotating();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEventSphere) {
|
if (selectedEventSphere) {
|
||||||
const eventData = getEventByModelUuid(
|
const eventData = getEventByModelUuid(
|
||||||
selectedEventSphere.userData.modelUuid
|
selectedEventSphere.userData.modelUuid
|
||||||
);
|
);
|
||||||
|
|
||||||
if (eventData) {
|
if (eventData) {
|
||||||
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
|
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,6 +80,53 @@ function PointsCreator() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
|
||||||
|
let drag = false;
|
||||||
|
let isMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = () => {
|
||||||
|
isMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = () => {
|
||||||
|
if (selectedEventSphere && !drag) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersects = raycaster
|
||||||
|
.intersectObjects(scene.children, true)
|
||||||
|
.filter(
|
||||||
|
(intersect) =>
|
||||||
|
intersect.object.name === ('Event-Sphere')
|
||||||
|
);
|
||||||
|
if (intersects.length === 0) {
|
||||||
|
clearSelectedEventSphere();
|
||||||
|
setTransformMode(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (subModule === 'mechanics') {
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
};
|
||||||
|
|
||||||
|
}, [gl, subModule, selectedEventSphere]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{activeModule === "simulation" && (
|
{activeModule === "simulation" && (
|
||||||
|
@ -80,7 +135,10 @@ function PointsCreator() {
|
||||||
{events.map((event, i) => {
|
{events.map((event, i) => {
|
||||||
if (event.type === "transfer") {
|
if (event.type === "transfer") {
|
||||||
return (
|
return (
|
||||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
<group
|
||||||
|
key={i}
|
||||||
|
position={new THREE.Vector3(...event.position)}
|
||||||
|
>
|
||||||
{event.points.map((point, j) => (
|
{event.points.map((point, j) => (
|
||||||
<mesh
|
<mesh
|
||||||
name="Event-Sphere"
|
name="Event-Sphere"
|
||||||
|
@ -94,14 +152,16 @@ function PointsCreator() {
|
||||||
}}
|
}}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
if (selectedEventData?.data.type !== 'vehicle') {
|
if (selectedEventData?.data.type !== 'vehicle') {
|
||||||
clearSelectedEventSphere();
|
// clearSelectedEventSphere();
|
||||||
}
|
}
|
||||||
setTransformMode(null);
|
setTransformMode(null);
|
||||||
}}
|
}}
|
||||||
key={`${i}-${j}`}
|
key={`${i}-${j}`}
|
||||||
position={new THREE.Vector3(...point.position)}
|
position={new THREE.Vector3(...point.position)}
|
||||||
// rotation={new THREE.Euler(...point.rotation)}
|
userData={{
|
||||||
userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }}
|
modelUuid: event.modelUuid,
|
||||||
|
pointUuid: point.uuid,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<sphereGeometry args={[0.1, 16, 16]} />
|
<sphereGeometry args={[0.1, 16, 16]} />
|
||||||
<meshStandardMaterial color="orange" />
|
<meshStandardMaterial color="orange" />
|
||||||
|
@ -111,7 +171,10 @@ function PointsCreator() {
|
||||||
);
|
);
|
||||||
} else if (event.type === "vehicle") {
|
} else if (event.type === "vehicle") {
|
||||||
return (
|
return (
|
||||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
<group
|
||||||
|
key={i}
|
||||||
|
position={new THREE.Vector3(...event.position)}
|
||||||
|
>
|
||||||
<mesh
|
<mesh
|
||||||
name="Event-Sphere"
|
name="Event-Sphere"
|
||||||
uuid={event.point.uuid}
|
uuid={event.point.uuid}
|
||||||
|
@ -122,13 +185,11 @@ function PointsCreator() {
|
||||||
sphereRefs.current[event.point.uuid]
|
sphereRefs.current[event.point.uuid]
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onPointerMissed={() => {
|
|
||||||
clearSelectedEventSphere();
|
|
||||||
setTransformMode(null);
|
|
||||||
}}
|
|
||||||
position={new THREE.Vector3(...event.point.position)}
|
position={new THREE.Vector3(...event.point.position)}
|
||||||
// rotation={new THREE.Euler(...event.point.rotation)}
|
userData={{
|
||||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
modelUuid: event.modelUuid,
|
||||||
|
pointUuid: event.point.uuid,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<sphereGeometry args={[0.1, 16, 16]} />
|
<sphereGeometry args={[0.1, 16, 16]} />
|
||||||
<meshStandardMaterial color="blue" />
|
<meshStandardMaterial color="blue" />
|
||||||
|
@ -137,7 +198,10 @@ function PointsCreator() {
|
||||||
);
|
);
|
||||||
} else if (event.type === "roboticArm") {
|
} else if (event.type === "roboticArm") {
|
||||||
return (
|
return (
|
||||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
<group
|
||||||
|
key={i}
|
||||||
|
position={new THREE.Vector3(...event.position)}
|
||||||
|
>
|
||||||
<mesh
|
<mesh
|
||||||
name="Event-Sphere"
|
name="Event-Sphere"
|
||||||
uuid={event.point.uuid}
|
uuid={event.point.uuid}
|
||||||
|
@ -149,12 +213,13 @@ function PointsCreator() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
clearSelectedEventSphere();
|
|
||||||
setTransformMode(null);
|
setTransformMode(null);
|
||||||
}}
|
}}
|
||||||
position={new THREE.Vector3(...event.point.position)}
|
position={new THREE.Vector3(...event.point.position)}
|
||||||
// rotation={new THREE.Euler(...event.point.rotation)}
|
userData={{
|
||||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
modelUuid: event.modelUuid,
|
||||||
|
pointUuid: event.point.uuid,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<sphereGeometry args={[0.1, 16, 16]} />
|
<sphereGeometry args={[0.1, 16, 16]} />
|
||||||
<meshStandardMaterial color="green" />
|
<meshStandardMaterial color="green" />
|
||||||
|
@ -163,7 +228,10 @@ function PointsCreator() {
|
||||||
);
|
);
|
||||||
} else if (event.type === "machine") {
|
} else if (event.type === "machine") {
|
||||||
return (
|
return (
|
||||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
<group
|
||||||
|
key={i}
|
||||||
|
position={new THREE.Vector3(...event.position)}
|
||||||
|
>
|
||||||
<mesh
|
<mesh
|
||||||
name="Event-Sphere"
|
name="Event-Sphere"
|
||||||
uuid={event.point.uuid}
|
uuid={event.point.uuid}
|
||||||
|
@ -175,12 +243,14 @@ function PointsCreator() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
clearSelectedEventSphere();
|
// clearSelectedEventSphere();
|
||||||
setTransformMode(null);
|
setTransformMode(null);
|
||||||
}}
|
}}
|
||||||
position={new THREE.Vector3(...event.point.position)}
|
position={new THREE.Vector3(...event.point.position)}
|
||||||
// rotation={new THREE.Euler(...event.point.rotation)}
|
userData={{
|
||||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
modelUuid: event.modelUuid,
|
||||||
|
pointUuid: event.point.uuid,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<sphereGeometry args={[0.1, 16, 16]} />
|
<sphereGeometry args={[0.1, 16, 16]} />
|
||||||
<meshStandardMaterial color="purple" />
|
<meshStandardMaterial color="purple" />
|
||||||
|
@ -208,4 +278,4 @@ function PointsCreator() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PointsCreator;
|
export default PointsCreator;
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { useMachineStore } from '../../../../../store/simulation/useMachineStore';
|
||||||
|
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
|
|
||||||
|
|
||||||
|
interface MachineAnimatorProps {
|
||||||
|
currentPhase: string;
|
||||||
|
handleCallBack: () => void;
|
||||||
|
reset: () => void;
|
||||||
|
machineStatus: (modelId: string, status: string) => void;
|
||||||
|
processingTime: number;
|
||||||
|
machineUuid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machineUuid, machineStatus, reset }: MachineAnimatorProps) => {
|
||||||
|
const animationStarted = useRef<boolean>(false);
|
||||||
|
const isPausedRef = useRef<boolean>(false);
|
||||||
|
const startTimeRef = useRef<number>(0);
|
||||||
|
const animationFrameId = useRef<number | null>(null);
|
||||||
|
const pauseTimeRef = useRef<number | null>(null);
|
||||||
|
const { isPaused } = usePauseButtonStore();
|
||||||
|
const { removeCurrentAction } = useMachineStore();
|
||||||
|
const { isReset, setReset } = useResetButtonStore();
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
const isPlayingRef = useRef<boolean>(false);
|
||||||
|
const isResetRef = useRef<boolean>(false)
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isPausedRef.current = isPaused;
|
||||||
|
}, [isPaused]);
|
||||||
|
useEffect(() => {
|
||||||
|
isPlayingRef.current = isPlaying;
|
||||||
|
}, [isPlaying]);
|
||||||
|
useEffect(() => {
|
||||||
|
isResetRef.current = isReset;
|
||||||
|
}, [isReset]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (isReset || !isPlaying) {
|
||||||
|
reset();
|
||||||
|
setReset(false);
|
||||||
|
startTimeRef.current = 0;
|
||||||
|
isPausedRef.current = false;
|
||||||
|
pauseTimeRef.current = 0;
|
||||||
|
animationFrameId.current = null;
|
||||||
|
animationStarted.current = false;
|
||||||
|
removeCurrentAction(machineUuid)
|
||||||
|
}
|
||||||
|
}, [isReset, isPlaying])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentPhase === 'processing' && !animationStarted.current && machineUuid) {
|
||||||
|
animationStarted.current = true;
|
||||||
|
startTimeRef.current = performance.now();
|
||||||
|
animationFrameId.current = requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
}, [currentPhase]);
|
||||||
|
|
||||||
|
function step(time: number) {
|
||||||
|
if (!isPausedRef.current || !isResetRef.current) {
|
||||||
|
if (animationFrameId.current) {
|
||||||
|
cancelAnimationFrame(animationFrameId.current);
|
||||||
|
animationFrameId.current = null;
|
||||||
|
}
|
||||||
|
if (isPausedRef.current) {
|
||||||
|
if (!pauseTimeRef.current) {
|
||||||
|
pauseTimeRef.current = performance.now();
|
||||||
|
}
|
||||||
|
animationFrameId.current = requestAnimationFrame(step);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pauseTimeRef.current) {
|
||||||
|
const pauseDuration = performance.now() - pauseTimeRef.current;
|
||||||
|
startTimeRef.current += pauseDuration;
|
||||||
|
pauseTimeRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsed = time - startTimeRef.current;
|
||||||
|
const processedTime = processingTime * 1000;
|
||||||
|
if (elapsed < processedTime) {
|
||||||
|
machineStatus(machineUuid, "Machine is currently processing the task");
|
||||||
|
animationFrameId.current = requestAnimationFrame(step);
|
||||||
|
} else {
|
||||||
|
removeCurrentAction(machineUuid);
|
||||||
|
animationStarted.current = false;
|
||||||
|
handleCallBack();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MachineAnimator;
|
|
@ -1,8 +1,65 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useMachineStore } from '../../../../../store/simulation/useMachineStore';
|
||||||
|
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
|
import MachineAnimator from '../animator/machineAnimator';
|
||||||
|
|
||||||
|
function MachineInstance({ machineDetail }: any) {
|
||||||
|
const [currentPhase, setCurrentPhase] = useState<string>('idle');
|
||||||
|
let isIncrememtable = useRef<boolean>(true);
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore();
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setMachineState(machineDetail.modelUuid, 'idle');
|
||||||
|
setMachineActive(machineDetail.modelUuid, false);
|
||||||
|
isIncrememtable.current = true;
|
||||||
|
setCurrentPhase("idle");
|
||||||
|
}
|
||||||
|
const increment = () => {
|
||||||
|
if (isIncrememtable.current) {
|
||||||
|
addCurrentAction(machineDetail.modelUuid, "machine-action-2468-1357-8024")
|
||||||
|
isIncrememtable.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function machineStatus(modelId: string, status: string) {
|
||||||
|
// console.log(`${modelId} , ${status}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPlaying) {
|
||||||
|
if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) {
|
||||||
|
setTimeout(() => {
|
||||||
|
increment();
|
||||||
|
}, 2000);
|
||||||
|
machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.')
|
||||||
|
} else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) {
|
||||||
|
setCurrentPhase("processing");
|
||||||
|
setMachineState(machineDetail.modelUuid, 'running');
|
||||||
|
setMachineActive(machineDetail.modelUuid, true);
|
||||||
|
machineStatus(machineDetail.modelUuid, "Machine started processing")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}, [currentPhase, isPlaying, machines])
|
||||||
|
|
||||||
|
function handleCallBack() {
|
||||||
|
if (currentPhase == "processing") {
|
||||||
|
setMachineState(machineDetail.modelUuid, 'idle');
|
||||||
|
setMachineActive(machineDetail.modelUuid, false);
|
||||||
|
setCurrentPhase("idle")
|
||||||
|
isIncrememtable.current = true;
|
||||||
|
machineStatus(machineDetail.modelUuid, "Machine has completed the processing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log('currentPhase: ', currentPhase);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function MachineInstance() {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<MachineAnimator processingTime={machineDetail.point.action.processTime} handleCallBack={handleCallBack} currentPhase={currentPhase} machineUuid={machineDetail.modelUuid} machineStatus={machineStatus} reset={reset} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import MachineInstance from './machineInstance/machineInstance'
|
import MachineInstance from './machineInstance/machineInstance'
|
||||||
|
import { useMachineStore } from '../../../../store/simulation/useMachineStore';
|
||||||
|
|
||||||
function MachineInstances() {
|
function MachineInstances() {
|
||||||
|
const { machines } = useMachineStore();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{machines.map((val: MachineStatus) => (
|
||||||
<MachineInstance />
|
<MachineInstance key={val.modelUuid} machineDetail={val} />
|
||||||
|
))}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,49 @@
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import MachineInstances from './instances/machineInstances'
|
import MachineInstances from './instances/machineInstances'
|
||||||
|
import { useMachineStore } from '../../../store/simulation/useMachineStore'
|
||||||
|
import { useSelectedProduct } from '../../../store/simulation/useSimulationStore';
|
||||||
|
|
||||||
function Machine() {
|
function Machine() {
|
||||||
|
const { addMachine, addCurrentAction, removeMachine, machines } = useMachineStore();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
|
||||||
|
const machineSample: MachineEventSchema[] = [
|
||||||
|
{
|
||||||
|
modelUuid: "machine-1234-5678-9012",
|
||||||
|
modelName: "CNC Milling Machine",
|
||||||
|
position: [10, 0, 5],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
state: "idle",
|
||||||
|
type: "machine",
|
||||||
|
point: {
|
||||||
|
uuid: "machine-point-9876-5432-1098",
|
||||||
|
position: [10, 0.5, 5.2],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
action: {
|
||||||
|
actionUuid: "machine-action-2468-1357-8024",
|
||||||
|
actionName: "Metal Processing",
|
||||||
|
actionType: "process",
|
||||||
|
processTime: 10,
|
||||||
|
swapMaterial: "steel",
|
||||||
|
triggers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
removeMachine(machineSample[0].modelUuid);
|
||||||
|
addMachine(selectedProduct.productId, machineSample[0]);
|
||||||
|
|
||||||
|
// addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid);
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
// console.log('machines: ', machines);
|
||||||
|
}, [machines])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,17 @@ function AddOrRemoveEventsInProducts() {
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
if (!canvasElement) return;
|
if (!canvasElement) return;
|
||||||
|
|
||||||
let intersects = raycaster.intersectObjects(scene.children, true);
|
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") &&
|
||||||
|
!(intersect.object?.parent?.name.includes('zones')) &&
|
||||||
|
!(intersect.object?.parent?.name.includes('Zone'))
|
||||||
|
);
|
||||||
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
|
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;
|
let currentObject = intersects[0].object;
|
||||||
|
|
||||||
|
@ -116,6 +126,7 @@ function AddOrRemoveEventsInProducts() {
|
||||||
};
|
};
|
||||||
|
|
||||||
}, [gl, subModule, selectedProduct, selectedAsset]);
|
}, [gl, subModule, selectedProduct, selectedAsset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<></>
|
<></>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProd
|
||||||
import { getAllProductsApi } from '../../../services/simulation/getallProductsApi';
|
import { getAllProductsApi } from '../../../services/simulation/getallProductsApi';
|
||||||
|
|
||||||
function Products() {
|
function Products() {
|
||||||
const { products, addProduct, setProducts } = useProductStore();
|
const { addProduct, setProducts } = useProductStore();
|
||||||
const { setSelectedProduct } = useSelectedProduct();
|
const { setSelectedProduct } = useSelectedProduct();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -27,9 +27,6 @@ function Products() {
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
|
|
|
@ -1,68 +1,220 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
import { useFrame } from '@react-three/fiber';
|
||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import * as THREE from 'three';
|
||||||
import * as THREE from "three"
|
import { Line } from '@react-three/drei';
|
||||||
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
import {
|
||||||
|
useAnimationPlaySpeed,
|
||||||
|
usePauseButtonStore,
|
||||||
|
usePlayButtonStore,
|
||||||
|
useResetButtonStore
|
||||||
|
} from '../../../../../store/usePlayButtonStore';
|
||||||
|
|
||||||
function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) {
|
function RoboticArmAnimator({
|
||||||
const { armBots } = useArmBotStore();
|
HandleCallback,
|
||||||
const { scene } = useThree();
|
restPosition,
|
||||||
const restSpeed = 0.1;
|
ikSolver,
|
||||||
const restPosition = new THREE.Vector3(0, 1, -1.6);
|
targetBone,
|
||||||
const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null);
|
armBot,
|
||||||
const initialStartPositionRef = useRef<THREE.Vector3 | null>(null);
|
logStatus,
|
||||||
const [initialProgress, setInitialProgress] = useState(0);
|
path
|
||||||
const [progress, setProgress] = useState(0);
|
}: any) {
|
||||||
const [needsInitialMovement, setNeedsInitialMovement] = useState(true);
|
const progressRef = useRef(0);
|
||||||
const [isInitializing, setIsInitializing] = useState(true);
|
const curveRef = useRef<THREE.Vector3[] | null>(null);
|
||||||
const { isPlaying } = usePlayButtonStore();
|
|
||||||
const statusRef = useRef("idle");
|
|
||||||
// Create a ref for initialProgress
|
|
||||||
const initialProgressRef = useRef(0);
|
|
||||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||||
|
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
|
||||||
|
const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
|
||||||
|
|
||||||
|
// Zustand stores
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { isPaused } = usePauseButtonStore();
|
||||||
|
const { isReset } = useResetButtonStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
|
||||||
|
// Update path state whenever `path` prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setCurrentPath(path);
|
||||||
setCurrentPath(path)
|
}, [path]);
|
||||||
}, [path])
|
|
||||||
|
|
||||||
|
// Reset logic when `isPlaying` changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isPlaying) {
|
||||||
|
setCurrentPath([]);
|
||||||
|
curveRef.current = null;
|
||||||
|
}
|
||||||
|
}, [isPlaying]);
|
||||||
|
|
||||||
}, [currentPath])
|
// Handle circle points based on armBot position
|
||||||
|
useEffect(() => {
|
||||||
|
const points = generateRingPoints(1.6, 64)
|
||||||
|
setCirclePoints(points);
|
||||||
|
}, [armBot.position]);
|
||||||
|
|
||||||
|
|
||||||
|
function generateRingPoints(radius: any, segments: any) {
|
||||||
|
const points: [number, number, number][] = [];
|
||||||
|
for (let i = 0; i < segments; i++) {
|
||||||
|
// Calculate angle for current segment
|
||||||
|
const angle = (i / segments) * Math.PI * 2;
|
||||||
|
// Calculate x and z coordinates (y remains the same for a flat ring)
|
||||||
|
const x = Math.cos(angle) * radius;
|
||||||
|
const z = Math.sin(angle) * radius;
|
||||||
|
points.push([x, 1.5, z]);
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const [x, y, z] = points[i];
|
||||||
|
if (
|
||||||
|
Math.abs(x - nearestPoint[0]) < epsilon &&
|
||||||
|
Math.abs(y - nearestPoint[1]) < epsilon &&
|
||||||
|
Math.abs(z - nearestPoint[2]) < epsilon
|
||||||
|
) {
|
||||||
|
return i; // Found the matching index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // Not found
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Handle nearest points and final path (including arc points)
|
||||||
|
useEffect(() => {
|
||||||
|
if (circlePoints.length > 0 && currentPath.length > 0) {
|
||||||
|
const start = currentPath[0];
|
||||||
|
const end = currentPath[currentPath.length - 1];
|
||||||
|
|
||||||
|
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
|
||||||
|
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
|
||||||
|
|
||||||
|
const findNearest = (target: [number, number, number]) => {
|
||||||
|
return circlePoints.reduce((nearest, point) => {
|
||||||
|
const distance = Math.hypot(
|
||||||
|
target[0] - point[0],
|
||||||
|
target[1] - point[1],
|
||||||
|
target[2] - point[2]
|
||||||
|
);
|
||||||
|
const nearestDistance = Math.hypot(
|
||||||
|
target[0] - nearest[0],
|
||||||
|
target[1] - nearest[1],
|
||||||
|
target[2] - nearest[2]
|
||||||
|
);
|
||||||
|
return distance < nearestDistance ? point : nearest;
|
||||||
|
}, circlePoints[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nearestToStart = findNearest(raisedStart);
|
||||||
|
|
||||||
|
const nearestToEnd = findNearest(raisedEnd);
|
||||||
|
|
||||||
|
const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
|
||||||
|
|
||||||
|
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
|
||||||
|
|
||||||
|
// Find clockwise and counter-clockwise distances
|
||||||
|
const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64;
|
||||||
|
|
||||||
|
const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64;
|
||||||
|
|
||||||
|
const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
|
||||||
|
|
||||||
|
// Collect arc points between start and end
|
||||||
|
let arcPoints: [number, number, number][] = [];
|
||||||
|
|
||||||
|
if (clockwiseIsShorter) {
|
||||||
|
if (indexOfNearestStart <= indexOfNearestEnd) {
|
||||||
|
arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
|
||||||
|
} else {
|
||||||
|
// Wrap around
|
||||||
|
arcPoints = [
|
||||||
|
...circlePoints.slice(indexOfNearestStart, 64),
|
||||||
|
...circlePoints.slice(0, indexOfNearestEnd + 1)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (indexOfNearestStart >= indexOfNearestEnd) {
|
||||||
|
for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
|
||||||
|
arcPoints.push(circlePoints[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
|
||||||
|
arcPoints.push(circlePoints[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue your custom path logic
|
||||||
|
const pathVectors = [
|
||||||
|
new THREE.Vector3(start[0], start[1], start[2]), // start
|
||||||
|
new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up
|
||||||
|
new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start
|
||||||
|
...arcPoints.map(point => new THREE.Vector3(point[0], raisedStart[1], point[2])),
|
||||||
|
new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end
|
||||||
|
new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end
|
||||||
|
new THREE.Vector3(end[0], end[1], end[2]) // end
|
||||||
|
];
|
||||||
|
|
||||||
|
const customCurve = new THREE.CatmullRomCurve3(pathVectors, false, 'centripetal', 1);
|
||||||
|
const generatedPoints = customCurve.getPoints(100);
|
||||||
|
setCustomCurvePoints(generatedPoints);
|
||||||
|
}
|
||||||
|
}, [circlePoints, currentPath]);
|
||||||
|
|
||||||
|
// Frame update for animation
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
if (!ikSolver || !currentPath || currentPath.length === 0) return;
|
if (!ikSolver) return;
|
||||||
|
|
||||||
const bone = ikSolver.mesh.skeleton.bones.find(
|
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||||
(b: any) => b.name === targetBone
|
|
||||||
);
|
|
||||||
if (!bone) return;
|
if (!bone) return;
|
||||||
|
|
||||||
// Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it
|
if (isPlaying) {
|
||||||
const curve = new THREE.CatmullRomCurve3(
|
if (!isPaused && customCurvePoints && currentPath.length > 0) {
|
||||||
currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2]))
|
const curvePoints = customCurvePoints;
|
||||||
);
|
const speedAdjustedProgress = progressRef.current + (speed * armBot.speed);
|
||||||
|
const index = Math.floor(speedAdjustedProgress);
|
||||||
|
|
||||||
|
if (index >= curvePoints.length) {
|
||||||
|
// Reached the end of the curve
|
||||||
|
HandleCallback();
|
||||||
|
setCurrentPath([]);
|
||||||
|
curveRef.current = null;
|
||||||
|
progressRef.current = 0;
|
||||||
|
} else {
|
||||||
|
const point = curvePoints[index];
|
||||||
|
bone.position.copy(point);
|
||||||
|
progressRef.current = speedAdjustedProgress;
|
||||||
|
}
|
||||||
|
} else if (isPaused) {
|
||||||
|
logStatus(armBot.modelUuid, 'Simulation Paused');
|
||||||
|
}
|
||||||
|
|
||||||
const next = initialProgressRef.current + delta * 0.5;
|
ikSolver.update();
|
||||||
if (next >= 1) {
|
} else if (!isPlaying && currentPath.length === 0) {
|
||||||
// bone.position.copy(restPosition);
|
// Not playing anymore, reset to rest
|
||||||
HandleCallback(); // Call the callback when the path is completed
|
bone.position.copy(restPosition);
|
||||||
initialProgressRef.current = 0; // Set ref to 1 when done
|
ikSolver.update();
|
||||||
} else {
|
|
||||||
const point = curve.getPoint(next); // Get the interpolated point from the curve
|
|
||||||
bone.position.copy(point); // Update the bone position along the curve
|
|
||||||
initialProgressRef.current = next; // Update progress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ikSolver.update();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<></>
|
<>
|
||||||
)
|
{customCurvePoints && currentPath && isPlaying && (
|
||||||
|
<mesh rotation={armBot.rotation} position={armBot.position}>
|
||||||
|
<Line
|
||||||
|
points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
|
||||||
|
color="green"
|
||||||
|
lineWidth={5}
|
||||||
|
dashed={false}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
<mesh position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
|
<ringGeometry args={[1.59, 1.61, 64]} />
|
||||||
|
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RoboticArmAnimator;
|
export default RoboticArmAnimator;
|
||||||
|
|
|
@ -1,61 +1,141 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import IKInstance from '../ikInstance/ikInstance';
|
import IKInstance from '../ikInstance/ikInstance';
|
||||||
import RoboticArmAnimator from '../animator/roboticArmAnimator';
|
import RoboticArmAnimator from '../animator/roboticArmAnimator';
|
||||||
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
||||||
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb";
|
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import { useFloorItems } from '../../../../../store/store';
|
import { useFloorItems } from '../../../../../store/store';
|
||||||
import useModuleStore from '../../../../../store/useModuleStore';
|
import useModuleStore from '../../../../../store/useModuleStore';
|
||||||
import { Vector3 } from "three";
|
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
import { useSelectedAction, useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
|
||||||
|
import { useProductStore } from '../../../../../store/simulation/useProductStore';
|
||||||
|
|
||||||
interface Process {
|
|
||||||
triggerId: string;
|
|
||||||
startPoint?: Vector3;
|
|
||||||
endPoint?: Vector3;
|
|
||||||
speed: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) {
|
function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
|
|
||||||
const { isPlaying } = usePlayButtonStore();
|
|
||||||
const [currentPhase, setCurrentPhase] = useState<(string)>("init");
|
const [currentPhase, setCurrentPhase] = useState<(string)>("init");
|
||||||
const { scene } = useThree();
|
|
||||||
const targetBone = "Target";
|
|
||||||
const { activeModule } = useModuleStore();
|
|
||||||
const [ikSolver, setIkSolver] = useState<any>(null);
|
|
||||||
const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
|
|
||||||
const { floorItems } = useFloorItems();
|
|
||||||
const groupRef = useRef<any>(null);
|
|
||||||
const [processes, setProcesses] = useState<Process[]>([]);
|
|
||||||
const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] })
|
|
||||||
const restPosition = new THREE.Vector3(0, 1, -1.6);
|
|
||||||
let armBotCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null)
|
|
||||||
const [path, setPath] = useState<[number, number, number][]>([]);
|
const [path, setPath] = useState<[number, number, number][]>([]);
|
||||||
|
const [ikSolver, setIkSolver] = useState<any>(null);
|
||||||
|
const { scene } = useThree();
|
||||||
|
const restPosition = new THREE.Vector3(0, 1.75, -1.6);
|
||||||
|
const targetBone = "Target";
|
||||||
|
const groupRef = useRef<any>(null);
|
||||||
|
const pauseTimeRef = useRef<number | null>(null);
|
||||||
|
const isPausedRef = useRef<boolean>(false);
|
||||||
|
let startTime: number;
|
||||||
|
//zustand
|
||||||
|
const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
|
||||||
|
const { products, getActionByUuid } = useProductStore();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { floorItems } = useFloorItems();
|
||||||
|
const { activeModule } = useModuleStore();
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { isReset, setReset } = useResetButtonStore();
|
||||||
|
const { isPaused } = usePauseButtonStore();
|
||||||
|
const { selectedAction } = useSelectedAction();
|
||||||
|
|
||||||
|
function firstFrame() {
|
||||||
|
startTime = performance.now();
|
||||||
|
step();
|
||||||
|
}
|
||||||
|
function step() {
|
||||||
|
if (isPausedRef.current) {
|
||||||
|
if (!pauseTimeRef.current) {
|
||||||
|
pauseTimeRef.current = performance.now();
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => step());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pauseTimeRef.current) {
|
||||||
|
const pauseDuration = performance.now() - pauseTimeRef.current;
|
||||||
|
startTime += pauseDuration;
|
||||||
|
pauseTimeRef.current = null;
|
||||||
|
}
|
||||||
|
const elapsedTime = performance.now() - startTime;
|
||||||
|
if (elapsedTime < 1500) {
|
||||||
|
// Wait until 1500ms has passed
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentPhase === "picking") {
|
||||||
|
|
||||||
|
setArmBotActive(armBot.modelUuid, true);
|
||||||
|
setArmBotState(armBot.modelUuid, "running");
|
||||||
|
setCurrentPhase("start-to-end");
|
||||||
|
startTime = 0
|
||||||
|
const startPoint = armBot.point.actions[0].process.startPoint;
|
||||||
|
const endPoint = armBot.point.actions[0].process.endPoint;
|
||||||
|
if (startPoint && endPoint) {
|
||||||
|
let curve = createCurveBetweenTwoPoints(
|
||||||
|
new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]),
|
||||||
|
new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]));
|
||||||
|
if (curve) {
|
||||||
|
logStatus(armBot.modelUuid, "picking the object");
|
||||||
|
setPath(curve.points.map(point => [point.x, point.y, point.z]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
|
||||||
|
} else if (currentPhase === "dropping") {
|
||||||
|
|
||||||
|
setArmBotActive(armBot.modelUuid, true);
|
||||||
|
setArmBotState(armBot.modelUuid, "running");
|
||||||
|
setCurrentPhase("end-to-rest");
|
||||||
|
startTime = 0;
|
||||||
|
const endPoint = armBot.point.actions[0].process.endPoint;
|
||||||
|
if (endPoint) {
|
||||||
|
|
||||||
|
let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition);
|
||||||
|
if (curve) {
|
||||||
|
logStatus(armBot.modelUuid, "dropping the object");
|
||||||
|
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
isPausedRef.current = isPaused;
|
||||||
|
}, [isPaused]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let armItems = floorItems?.filter((val: any) =>
|
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
|
||||||
val.modelUuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d"
|
|
||||||
);
|
if (targetMesh) {
|
||||||
// Get the first matching item
|
targetMesh.visible = activeModule !== "simulation"
|
||||||
let armItem = armItems?.[0];
|
|
||||||
if (armItem) {
|
|
||||||
const targetMesh = scene?.getObjectByProperty("uuid", armItem.modelUuid);
|
|
||||||
if (targetMesh) {
|
|
||||||
targetMesh.visible = activeModule !== "simulation"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetBones = ikSolver?.mesh.skeleton.bones.find(
|
const targetBones = ikSolver?.mesh.skeleton.bones.find(
|
||||||
(b: any) => b.name === targetBone
|
(b: any) => b.name === targetBone
|
||||||
);
|
);
|
||||||
|
if (isReset) {
|
||||||
|
|
||||||
|
logStatus(armBot.modelUuid, "Simulation Play Reset Successfully")
|
||||||
|
removeCurrentAction(armBot.modelUuid)
|
||||||
|
setArmBotActive(armBot.modelUuid, true)
|
||||||
|
setArmBotState(armBot.modelUuid, "running")
|
||||||
|
setCurrentPhase("init-to-rest");
|
||||||
|
isPausedRef.current = false
|
||||||
|
pauseTimeRef.current = null
|
||||||
|
isPausedRef.current = false
|
||||||
|
startTime = 0
|
||||||
|
if (targetBones) {
|
||||||
|
let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
|
||||||
|
if (curve) {
|
||||||
|
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setReset(false);
|
||||||
|
logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
|
||||||
|
}
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
//Moving armBot from initial point to rest position.
|
|
||||||
if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") {
|
|
||||||
|
|
||||||
setArmBotActive(robot.modelUuid, true)
|
//Moving armBot from initial point to rest position.
|
||||||
setArmBotState(robot.modelUuid, "running")
|
if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") {
|
||||||
|
|
||||||
|
setArmBotActive(armBot.modelUuid, true)
|
||||||
|
setArmBotState(armBot.modelUuid, "running")
|
||||||
setCurrentPhase("init-to-rest");
|
setCurrentPhase("init-to-rest");
|
||||||
if (targetBones) {
|
if (targetBones) {
|
||||||
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
|
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
|
||||||
|
@ -63,21 +143,26 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) {
|
||||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logStatus(robot.modelUuid, "Moving armBot from initial point to rest position.")
|
logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
|
||||||
}
|
}
|
||||||
//Waiting for trigger.
|
//Waiting for trigger.
|
||||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) {
|
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) {
|
||||||
logStatus(robot.modelUuid, "Waiting to trigger CurrentAction")
|
logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction")
|
||||||
setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
addCurrentAction(robot.modelUuid, 'action-003');
|
addCurrentAction(armBot.modelUuid, selectedAction?.actionId);
|
||||||
|
console.log('selectedAction?.actionId: ', selectedAction?.actionId);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) {
|
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) {
|
||||||
if (robot.currentAction) {
|
if (armBot.currentAction) {
|
||||||
setArmBotActive(robot.modelUuid, true);
|
|
||||||
setArmBotState(robot.modelUuid, "running");
|
setArmBotActive(armBot.modelUuid, true);
|
||||||
|
setArmBotState(armBot.modelUuid, "running");
|
||||||
setCurrentPhase("rest-to-start");
|
setCurrentPhase("rest-to-start");
|
||||||
const startPoint = robot.point.actions[0].process.startPoint;
|
let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId)
|
||||||
|
console.log('actiondata: ', actiondata);
|
||||||
|
const startPoint = armBot.point.actions[0].process.startPoint;
|
||||||
if (startPoint) {
|
if (startPoint) {
|
||||||
let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
|
let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
|
||||||
if (curve) {
|
if (curve) {
|
||||||
|
@ -85,105 +170,109 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logStatus(robot.modelUuid, "Moving armBot from rest point to start position.")
|
logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.")
|
||||||
}
|
}
|
||||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) {
|
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) {
|
||||||
setArmBotActive(robot.modelUuid, true);
|
requestAnimationFrame(firstFrame);
|
||||||
setArmBotState(robot.modelUuid, "running");
|
// setArmBotActive(armBot.modelUuid, true);
|
||||||
setCurrentPhase("start-to-end");
|
// setArmBotState(armBot.modelUuid, "running");
|
||||||
const startPoint = robot.point.actions[0].process.startPoint;
|
// setCurrentPhase("start-to-end");
|
||||||
const endPoint = robot.point.actions[0].process.endPoint;
|
// const startPoint = armBot.point.actions[0].process.startPoint;
|
||||||
if (startPoint && endPoint) {
|
// const endPoint = armBot.point.actions[0].process.endPoint;
|
||||||
let curve = createCurveBetweenTwoPoints(
|
// if (startPoint && endPoint) {
|
||||||
new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]),
|
// let curve = createCurveBetweenTwoPoints(
|
||||||
new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])
|
// new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]),
|
||||||
);
|
// new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]));
|
||||||
if (curve) {
|
// if (curve) {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
logStatus(robot.modelUuid, "picking the object");
|
// logStatus(armBot.modelUuid, "picking the object");
|
||||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
// setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
logStatus(robot.modelUuid, "Moving armBot from start point to end position.")
|
// logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
|
||||||
}
|
}
|
||||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) {
|
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) {
|
||||||
setArmBotActive(robot.modelUuid, true);
|
requestAnimationFrame(firstFrame);
|
||||||
setArmBotState(robot.modelUuid, "running");
|
// setArmBotActive(armBot.modelUuid, true);
|
||||||
setCurrentPhase("end-to-rest");
|
// setArmBotState(armBot.modelUuid, "running");
|
||||||
const endPoint = robot.point.actions[0].process.endPoint;
|
// setCurrentPhase("end-to-rest");
|
||||||
if (endPoint) {
|
// const endPoint = armBot.point.actions[0].process.endPoint;
|
||||||
let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition
|
// if (endPoint) {
|
||||||
);
|
// let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition);
|
||||||
if (curve) {
|
// if (curve) {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
logStatus(robot.modelUuid, "dropping the object");
|
// logStatus(armBot.modelUuid, "dropping the object");
|
||||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
// setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
logStatus(robot.modelUuid, "Moving armBot from end point to rest position.")
|
// logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logStatus(armBot.modelUuid, "Simulation Play Stopped")
|
||||||
|
setArmBotActive(armBot.modelUuid, false)
|
||||||
|
setArmBotState(armBot.modelUuid, "idle")
|
||||||
|
setCurrentPhase("init");
|
||||||
|
setPath([])
|
||||||
|
removeCurrentAction(armBot.modelUuid)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [currentPhase, robot, isPlaying, ikSolver])
|
}, [currentPhase, armBot, isPlaying, ikSolver, isReset])
|
||||||
|
|
||||||
|
|
||||||
function createCurveBetweenTwoPoints(p1: any, p2: any) {
|
function createCurveBetweenTwoPoints(p1: any, p2: any) {
|
||||||
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
|
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
|
||||||
mid.y += 0.5;
|
// mid.y += 0.5;
|
||||||
|
|
||||||
const points = [p1, mid, p2];
|
const points = [p1, mid, p2];
|
||||||
return new THREE.CatmullRomCurve3(points);
|
return new THREE.CatmullRomCurve3(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const HandleCallback = () => {
|
const HandleCallback = () => {
|
||||||
if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") {
|
if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") {
|
||||||
logStatus(robot.modelUuid, "Callback triggered: rest");
|
logStatus(armBot.modelUuid, "Callback triggered: rest");
|
||||||
setArmBotActive(robot.modelUuid, false)
|
setArmBotActive(armBot.modelUuid, false)
|
||||||
setArmBotState(robot.modelUuid, "idle")
|
setArmBotState(armBot.modelUuid, "idle")
|
||||||
setCurrentPhase("rest");
|
setCurrentPhase("rest");
|
||||||
setPath([])
|
setPath([])
|
||||||
}
|
}
|
||||||
else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") {
|
else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") {
|
||||||
logStatus(robot.modelUuid, "Callback triggered: pick.");
|
logStatus(armBot.modelUuid, "Callback triggered: pick.");
|
||||||
setArmBotActive(robot.modelUuid, false)
|
setArmBotActive(armBot.modelUuid, false)
|
||||||
setArmBotState(robot.modelUuid, "idle")
|
setArmBotState(armBot.modelUuid, "idle")
|
||||||
setCurrentPhase("picking");
|
setCurrentPhase("picking");
|
||||||
setPath([])
|
setPath([])
|
||||||
}
|
}
|
||||||
else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") {
|
else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") {
|
||||||
logStatus(robot.modelUuid, "Callback triggered: drop.");
|
logStatus(armBot.modelUuid, "Callback triggered: drop.");
|
||||||
setArmBotActive(robot.modelUuid, false)
|
setArmBotActive(armBot.modelUuid, false)
|
||||||
setArmBotState(robot.modelUuid, "idle")
|
setArmBotState(armBot.modelUuid, "idle")
|
||||||
setCurrentPhase("dropping");
|
setCurrentPhase("dropping");
|
||||||
setPath([])
|
setPath([])
|
||||||
}
|
}
|
||||||
else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") {
|
else if (armBot.isActive && armBot.state == "running" && currentPhase == "end-to-rest") {
|
||||||
logStatus(robot.modelUuid, "Callback triggered: rest, cycle completed.");
|
logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed.");
|
||||||
setArmBotActive(robot.modelUuid, false)
|
setArmBotActive(armBot.modelUuid, false)
|
||||||
setArmBotState(robot.modelUuid, "idle")
|
setArmBotState(armBot.modelUuid, "idle")
|
||||||
setCurrentPhase("rest");
|
setCurrentPhase("rest");
|
||||||
setPath([])
|
setPath([])
|
||||||
removeCurrentAction(robot.modelUuid)
|
removeCurrentAction(armBot.modelUuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const logStatus = (id: string, status: string) => {
|
const logStatus = (id: string, status: string) => {
|
||||||
// console.log(id + "," + status);
|
//
|
||||||
console.log( status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IKInstance modelUrl={armModel} setIkSolver={setIkSolver} ikSolver={ikSolver} robot={robot} groupRef={groupRef} processes={processes}
|
<IKInstance modelUrl={armModel} setIkSolver={setIkSolver} ikSolver={ikSolver} armBot={armBot} groupRef={groupRef} />
|
||||||
setArmBotCurvePoints={setArmBotCurvePoints} />
|
<RoboticArmAnimator HandleCallback={HandleCallback} restPosition={restPosition} ikSolver={ikSolver} targetBone={targetBone} armBot={armBot}
|
||||||
<RoboticArmAnimator armUuid={robot?.modelUuid} HandleCallback={HandleCallback}
|
logStatus={logStatus} path={path} currentPhase={currentPhase} />
|
||||||
currentPhase={currentPhase} targetBone={targetBone} ikSolver={ikSolver} robot={robot}
|
|
||||||
logStatus={logStatus} groupRef={groupRef} processes={processes} armBotCurveRef={armBotCurveRef} path={path} />
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RoboticArmInstance;
|
export default RoboticArmInstance;
|
||||||
|
|
|
@ -3,20 +3,19 @@ import * as THREE from "three";
|
||||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
|
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
|
||||||
import { useFrame, useLoader, useThree } from "@react-three/fiber";
|
import { useLoader, useThree } from "@react-three/fiber";
|
||||||
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
|
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
|
||||||
|
import { TransformControls } from '@react-three/drei';
|
||||||
|
|
||||||
type IKInstanceProps = {
|
type IKInstanceProps = {
|
||||||
modelUrl: string;
|
modelUrl: string;
|
||||||
ikSolver: any;
|
ikSolver: any;
|
||||||
setIkSolver: any
|
setIkSolver: any
|
||||||
robot: any;
|
armBot: any;
|
||||||
groupRef: React.RefObject<THREE.Group>;
|
groupRef: any;
|
||||||
processes: any;
|
|
||||||
setArmBotCurvePoints: any
|
|
||||||
};
|
};
|
||||||
function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) {
|
function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) {
|
||||||
|
const { scene } = useThree()
|
||||||
const { scene } = useThree();
|
|
||||||
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
|
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
|
||||||
const draco = new DRACOLoader();
|
const draco = new DRACOLoader();
|
||||||
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
|
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
|
||||||
|
@ -25,6 +24,8 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
|
||||||
const cloned = useMemo(() => clone(gltf?.scene), [gltf]);
|
const cloned = useMemo(() => clone(gltf?.scene), [gltf]);
|
||||||
const targetBoneName = "Target";
|
const targetBoneName = "Target";
|
||||||
const skinnedMeshName = "link_0";
|
const skinnedMeshName = "link_0";
|
||||||
|
const [selectedArm, setSelectedArm] = useState<THREE.Group>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gltf) return;
|
if (!gltf) return;
|
||||||
const OOI: any = {};
|
const OOI: any = {};
|
||||||
|
@ -66,15 +67,19 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
|
||||||
setIkSolver(solver);
|
setIkSolver(solver);
|
||||||
|
|
||||||
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
||||||
|
// groupRef.current.add(helper);
|
||||||
|
|
||||||
// scene.add(helper)
|
setSelectedArm(OOI.Target_Bone);
|
||||||
|
|
||||||
|
scene.add(helper)
|
||||||
|
|
||||||
}, [gltf]);
|
}, [gltf]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<group ref={groupRef} position={robot.position} rotation={robot.rotation}>
|
<group ref={groupRef} position={armBot.position} rotation={armBot.rotation} onClick={() => {
|
||||||
|
setSelectedArm(groupRef.current?.getObjectByName(targetBoneName))
|
||||||
|
}}>
|
||||||
<primitive
|
<primitive
|
||||||
uuid={"ArmBot-X200"}
|
uuid={"ArmBot-X200"}
|
||||||
object={cloned}
|
object={cloned}
|
||||||
|
@ -82,6 +87,7 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
|
||||||
name={`arm-bot11`}
|
name={`arm-bot11`}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
|
{/* {selectedArm && <TransformControls object={selectedArm} />} */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import React from 'react'
|
|
||||||
import RoboticArmInstance from './armInstance/roboticArmInstance';
|
import RoboticArmInstance from './armInstance/roboticArmInstance';
|
||||||
import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
|
import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
|
||||||
|
|
||||||
|
@ -8,9 +7,8 @@ function RoboticArmInstances() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{armBots?.map((robot: ArmBotStatus) => (
|
{armBots?.map((robot: ArmBotStatus) => (
|
||||||
<RoboticArmInstance key={robot.modelUuid} robot={robot} />
|
<RoboticArmInstance key={robot.modelUuid} armBot={robot} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,26 @@ import { useEffect } from "react";
|
||||||
import RoboticArmInstances from "./instances/roboticArmInstances";
|
import RoboticArmInstances from "./instances/roboticArmInstances";
|
||||||
import { useArmBotStore } from "../../../store/simulation/useArmBotStore";
|
import { useArmBotStore } from "../../../store/simulation/useArmBotStore";
|
||||||
import { useFloorItems } from "../../../store/store";
|
import { useFloorItems } from "../../../store/store";
|
||||||
|
import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore";
|
||||||
|
import { useProductStore } from "../../../store/simulation/useProductStore";
|
||||||
|
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||||
|
import ArmBotUI from "../ui/arm/armBotUI";
|
||||||
|
|
||||||
function RoboticArm() {
|
function RoboticArm() {
|
||||||
const { armBots, addArmBot, removeArmBot } = useArmBotStore();
|
const { armBots, addArmBot, clearArmBots } = useArmBotStore();
|
||||||
|
const { products, getProductById } = useProductStore();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { selectedEventSphere } = useSelectedEventSphere();
|
||||||
|
const { selectedEventData } = useSelectedEventData();
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { floorItems } = useFloorItems();
|
const { floorItems } = useFloorItems();
|
||||||
|
|
||||||
|
|
||||||
const armBotStatusSample: RoboticArmEventSchema[] = [
|
const armBotStatusSample: RoboticArmEventSchema[] = [
|
||||||
{
|
{
|
||||||
state: "idle",
|
state: "idle",
|
||||||
modelUuid: "3abf5d46-b59e-4e6b-9c02-a4634b64b82d",
|
modelUuid: "8790b4d5-fb6e-49e0-8161-04945fe3fdc4",
|
||||||
modelName: "ArmBot-X200",
|
modelName: "ArmBot-X200",
|
||||||
position: [0.20849215906958463, 0, 0.32079278127773675],
|
position: [4.317833205016363, 0, -3.2829924989068715],
|
||||||
rotation: [-1.3768690876192207e-15, 1.4883085074751308, 1.5407776675834467e-15],
|
rotation: [-1.3768690876192207e-15, 1.4883085074751308, 1.5407776675834467e-15],
|
||||||
type: "roboticArm",
|
type: "roboticArm",
|
||||||
speed: 1.5,
|
speed: 1.5,
|
||||||
|
@ -29,19 +37,9 @@ function RoboticArm() {
|
||||||
process: {
|
process: {
|
||||||
startPoint: [-1, 2, 1],
|
startPoint: [-1, 2, 1],
|
||||||
endPoint: [-2, 1, -1],
|
endPoint: [-2, 1, -1],
|
||||||
|
// startPoint: [-2, 1, -1],
|
||||||
|
// endPoint: [-1, 2, 1],
|
||||||
},
|
},
|
||||||
// process: {
|
|
||||||
// "startPoint": [
|
|
||||||
// 0.37114476008711866,
|
|
||||||
// 1.9999999999999998,
|
|
||||||
// 1.8418816116721384
|
|
||||||
// ],
|
|
||||||
// "endPoint": [
|
|
||||||
// -0.42197069459490777,
|
|
||||||
// 1,
|
|
||||||
// -3.159515927851809
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
triggers: [
|
triggers: [
|
||||||
{
|
{
|
||||||
triggerUuid: "trigger-001",
|
triggerUuid: "trigger-001",
|
||||||
|
@ -95,7 +93,7 @@ function RoboticArm() {
|
||||||
position: [95.94347308985614, 0, 6.742905194869091],
|
position: [95.94347308985614, 0, 6.742905194869091],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
type: "roboticArm",
|
type: "roboticArm",
|
||||||
speed: 1.5,
|
speed: 0.1,
|
||||||
point: {
|
point: {
|
||||||
uuid: "point-123",
|
uuid: "point-123",
|
||||||
position: [0, 1.5, 0],
|
position: [0, 1.5, 0],
|
||||||
|
@ -106,8 +104,10 @@ function RoboticArm() {
|
||||||
actionName: "Pick Component",
|
actionName: "Pick Component",
|
||||||
actionType: "pickAndPlace",
|
actionType: "pickAndPlace",
|
||||||
process: {
|
process: {
|
||||||
startPoint: [2.52543010919071, 0, 8.433681161200905],
|
// startPoint: [2.52543010919071, 0, 8.433681161200905],
|
||||||
endPoint: [95.3438373267953, 0, 9.0279187421610025],
|
// endPoint: [95.3438373267953, 0, 9.0279187421610025],
|
||||||
|
startPoint: null,
|
||||||
|
endPoint: null,
|
||||||
},
|
},
|
||||||
triggers: [
|
triggers: [
|
||||||
{
|
{
|
||||||
|
@ -158,20 +158,32 @@ function RoboticArm() {
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (selectedProduct.productId) {
|
||||||
removeArmBot(armBotStatusSample[0].modelUuid);
|
const product = getProductById(selectedProduct.productId);
|
||||||
addArmBot('123', armBotStatusSample[0]);
|
if (product) {
|
||||||
// addArmBot('123', armBotStatusSample[1]);
|
clearArmBots();
|
||||||
// addCurrentAction('armbot-xyz-001', 'action-001');
|
product.eventDatas.forEach(events => {
|
||||||
}, []);
|
if (events.type === 'roboticArm') {
|
||||||
|
addArmBot(selectedProduct.productId, events);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedProduct, products]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
}, [armBots])
|
||||||
}, [armBots]);
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, [selectedEventData, selectedEventSphere, isPlaying]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RoboticArmInstances />
|
<RoboticArmInstances />
|
||||||
|
{selectedEventSphere && selectedEventData?.data.type === "roboticArm" &&
|
||||||
|
< ArmBotUI />
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,20 +23,20 @@ function Simulation() {
|
||||||
}, [events])
|
}, [events])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('products: ', products);
|
console.log('products: ', products);
|
||||||
}, [products])
|
}, [products])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
|
<Products />
|
||||||
|
|
||||||
{activeModule === 'simulation' &&
|
{activeModule === 'simulation' &&
|
||||||
|
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<Points />
|
<Points />
|
||||||
|
|
||||||
<Products />
|
|
||||||
|
|
||||||
<Materials />
|
<Materials />
|
||||||
|
|
||||||
<Trigger />
|
<Trigger />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useSubModuleStore } from "../../../../store/useModuleStore";
|
import { useSubModuleStore } from "../../../../store/useModuleStore";
|
||||||
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
|
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
|
||||||
|
@ -7,21 +7,28 @@ import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||||
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
||||||
import { handleAddEventToProduct } from "../../events/points/functions/handleAddEventToProduct";
|
import { handleAddEventToProduct } from "../../events/points/functions/handleAddEventToProduct";
|
||||||
|
import { QuadraticBezierLine } from "@react-three/drei";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
import { useDeleteTool } from "../../../../store/store";
|
||||||
|
|
||||||
interface ConnectionLine {
|
interface ConnectionLine {
|
||||||
id: string;
|
id: string;
|
||||||
start: THREE.Vector3;
|
startPointUuid: string;
|
||||||
end: THREE.Vector3;
|
endPointUuid: string;
|
||||||
mid: THREE.Vector3;
|
|
||||||
trigger: TriggerSchema;
|
trigger: TriggerSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TriggerConnector() {
|
function TriggerConnector() {
|
||||||
const { gl, raycaster, scene } = useThree();
|
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||||
const { subModule } = useSubModuleStore();
|
const { subModule } = useSubModuleStore();
|
||||||
const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, addEvent, getEventByModelUuid } = useProductStore();
|
const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, removeTrigger, addEvent, getEventByModelUuid, getProductById } = useProductStore();
|
||||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
|
||||||
|
const groupRefs = useRef<Record<string, any>>({});
|
||||||
|
const [helperlineColor, setHelperLineColor] = useState<string>("red");
|
||||||
|
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null);
|
||||||
|
const { deleteTool } = useDeleteTool();
|
||||||
|
|
||||||
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
|
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
|
||||||
productId: string;
|
productId: string;
|
||||||
|
@ -32,52 +39,99 @@ function TriggerConnector() {
|
||||||
|
|
||||||
const [connections, setConnections] = useState<ConnectionLine[]>([]);
|
const [connections, setConnections] = useState<ConnectionLine[]>([]);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newConnections: ConnectionLine[] = [];
|
const newConnections: ConnectionLine[] = [];
|
||||||
|
|
||||||
products.forEach(product => {
|
const product = getProductById(selectedProduct.productId);
|
||||||
product.eventDatas.forEach(event => {
|
if (!product || products.length === 0) return;
|
||||||
if ('points' in event) {
|
|
||||||
event.points.forEach(point => {
|
|
||||||
if ('action' in point && point.action?.triggers) {
|
|
||||||
point.action.triggers.forEach(trigger => {
|
|
||||||
if (trigger.triggeredAsset) {
|
|
||||||
const targetPoint = getPointByUuid(
|
|
||||||
product.productId,
|
|
||||||
trigger.triggeredAsset.triggeredModel.modelUuid,
|
|
||||||
trigger.triggeredAsset.triggeredPoint.pointUuid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (targetPoint) {
|
product.eventDatas.forEach(event => {
|
||||||
const startPos = new THREE.Vector3(...point.position);
|
// Handle Conveyor points
|
||||||
const endPos = new THREE.Vector3(...targetPoint.position);
|
if (event.type === "transfer" && 'points' in event) {
|
||||||
const midPos = new THREE.Vector3()
|
event.points.forEach(point => {
|
||||||
.addVectors(startPos, endPos)
|
if (point.action?.triggers) {
|
||||||
.multiplyScalar(0.5)
|
point.action.triggers.forEach(trigger => {
|
||||||
.add(new THREE.Vector3(0, 2, 0));
|
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||||
|
newConnections.push({
|
||||||
newConnections.push({
|
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||||
id: `${point.uuid}-${targetPoint.uuid}-${trigger.triggerUuid}`,
|
startPointUuid: point.uuid,
|
||||||
start: startPos,
|
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||||
end: endPos,
|
trigger
|
||||||
mid: midPos,
|
});
|
||||||
trigger
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
// Handle Vehicle point
|
||||||
|
else if (event.type === "vehicle" && 'point' in event) {
|
||||||
|
const point = event.point;
|
||||||
|
if (point.action?.triggers) {
|
||||||
|
point.action.triggers.forEach(trigger => {
|
||||||
|
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||||
|
newConnections.push({
|
||||||
|
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||||
|
startPointUuid: point.uuid,
|
||||||
|
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||||
|
trigger
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
// Handle Robotic Arm points
|
||||||
|
else if (event.type === "roboticArm" && 'point' in event) {
|
||||||
|
const point = event.point;
|
||||||
|
point.actions?.forEach(action => {
|
||||||
|
action.triggers?.forEach(trigger => {
|
||||||
|
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||||
|
newConnections.push({
|
||||||
|
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||||
|
startPointUuid: point.uuid,
|
||||||
|
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||||
|
trigger
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Handle Machine point
|
||||||
|
else if (event.type === "machine" && 'point' in event) {
|
||||||
|
const point = event.point;
|
||||||
|
if (point.action?.triggers) {
|
||||||
|
point.action.triggers.forEach(trigger => {
|
||||||
|
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||||
|
newConnections.push({
|
||||||
|
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||||
|
startPointUuid: point.uuid,
|
||||||
|
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||||
|
trigger
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setConnections(newConnections);
|
setConnections(newConnections);
|
||||||
}, [products]);
|
}, [products, selectedProduct.productId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('connections: ', connections);
|
|
||||||
}, connections)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
|
@ -111,15 +165,31 @@ function TriggerConnector() {
|
||||||
if (drag) return;
|
if (drag) return;
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
const intersects = raycaster
|
||||||
if (intersects.length === 0) return;
|
.intersectObjects(scene.children, true)
|
||||||
|
.filter(
|
||||||
|
(intersect) =>
|
||||||
|
intersect.object.name === ('Event-Sphere')
|
||||||
|
);
|
||||||
|
if (intersects.length === 0) {
|
||||||
|
setFirstSelectedPoint(null);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const currentObject = intersects[0].object;
|
const currentObject = intersects[0].object;
|
||||||
if (!currentObject || currentObject.name !== 'Event-Sphere') return;
|
if (!currentObject || currentObject.name !== 'Event-Sphere') {
|
||||||
|
setFirstSelectedPoint(null);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const modelUuid = currentObject.userData.modelUuid;
|
const modelUuid = currentObject.userData.modelUuid;
|
||||||
const pointUuid = currentObject.userData.pointUuid;
|
const pointUuid = currentObject.userData.pointUuid;
|
||||||
|
|
||||||
|
if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) {
|
||||||
|
setFirstSelectedPoint(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedProduct && getIsEventInProduct(selectedProduct.productId, modelUuid)) {
|
if (selectedProduct && getIsEventInProduct(selectedProduct.productId, modelUuid)) {
|
||||||
|
|
||||||
const point = getPointByUuid(
|
const point = getPointByUuid(
|
||||||
|
@ -128,7 +198,12 @@ function TriggerConnector() {
|
||||||
pointUuid
|
pointUuid
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!point) return;
|
const event = getEventByModelUuid(selectedProduct.productId, modelUuid);
|
||||||
|
|
||||||
|
if (!point || !event) {
|
||||||
|
setFirstSelectedPoint(null);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let actionUuid: string | undefined;
|
let actionUuid: string | undefined;
|
||||||
if ('action' in point && point.action) {
|
if ('action' in point && point.action) {
|
||||||
|
@ -152,12 +227,12 @@ function TriggerConnector() {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
triggeredAsset: {
|
triggeredAsset: {
|
||||||
triggeredModel: {
|
triggeredModel: {
|
||||||
modelName: currentObject.parent?.parent?.name || 'Unknown',
|
modelName: event.modelName || 'Unknown',
|
||||||
modelUuid: modelUuid
|
modelUuid: modelUuid
|
||||||
},
|
},
|
||||||
triggeredPoint: {
|
triggeredPoint: {
|
||||||
pointName: currentObject.name,
|
pointName: 'Point',
|
||||||
pointUuid: pointUuid
|
pointUuid: point.uuid
|
||||||
},
|
},
|
||||||
triggeredAction: actionUuid ? {
|
triggeredAction: actionUuid ? {
|
||||||
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
|
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
|
||||||
|
@ -167,7 +242,16 @@ function TriggerConnector() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (firstSelectedPoint.actionUuid) {
|
if (firstSelectedPoint.actionUuid) {
|
||||||
addTrigger(firstSelectedPoint.actionUuid, trigger);
|
const event = addTrigger(selectedProduct.productId, firstSelectedPoint.actionUuid, trigger);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setFirstSelectedPoint(null);
|
setFirstSelectedPoint(null);
|
||||||
}
|
}
|
||||||
|
@ -184,7 +268,12 @@ function TriggerConnector() {
|
||||||
pointUuid
|
pointUuid
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!point) return;
|
const event = getEventByModelUuid(selectedProduct.productId, modelUuid);
|
||||||
|
|
||||||
|
if (!point || !event) {
|
||||||
|
setFirstSelectedPoint(null);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let actionUuid: string | undefined;
|
let actionUuid: string | undefined;
|
||||||
if ('action' in point && point.action) {
|
if ('action' in point && point.action) {
|
||||||
|
@ -200,12 +289,12 @@ function TriggerConnector() {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
triggeredAsset: {
|
triggeredAsset: {
|
||||||
triggeredModel: {
|
triggeredModel: {
|
||||||
modelName: currentObject.parent?.parent?.name || 'Unknown',
|
modelName: event.modelName || 'Unknown',
|
||||||
modelUuid: modelUuid
|
modelUuid: modelUuid
|
||||||
},
|
},
|
||||||
triggeredPoint: {
|
triggeredPoint: {
|
||||||
pointName: currentObject.name,
|
pointName: 'Point',
|
||||||
pointUuid: pointUuid
|
pointUuid: point.uuid
|
||||||
},
|
},
|
||||||
triggeredAction: actionUuid ? {
|
triggeredAction: actionUuid ? {
|
||||||
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
|
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
|
||||||
|
@ -215,13 +304,24 @@ function TriggerConnector() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (firstSelectedPoint.actionUuid) {
|
if (firstSelectedPoint.actionUuid) {
|
||||||
addTrigger(firstSelectedPoint.actionUuid, trigger);
|
const event = addTrigger(selectedProduct.productId, firstSelectedPoint.actionUuid, trigger);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setFirstSelectedPoint(null);
|
setFirstSelectedPoint(null);
|
||||||
|
} else if (firstSelectedPoint) {
|
||||||
|
setFirstSelectedPoint(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (subModule === 'simulations') {
|
if (subModule === 'simulations' && !deleteTool) {
|
||||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
@ -235,11 +335,149 @@ function TriggerConnector() {
|
||||||
canvasElement.removeEventListener('contextmenu', handleRightClick);
|
canvasElement.removeEventListener('contextmenu', handleRightClick);
|
||||||
};
|
};
|
||||||
|
|
||||||
}, [gl, subModule, selectedProduct, firstSelectedPoint]);
|
}, [gl, subModule, selectedProduct, firstSelectedPoint, deleteTool]);
|
||||||
|
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (firstSelectedPoint) {
|
||||||
|
raycaster.setFromCamera(pointer, 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.parent?.name.includes("Zone") &&
|
||||||
|
!(intersect.object.type === "GridHelper") &&
|
||||||
|
!(intersect.object.type === "Line2")
|
||||||
|
);
|
||||||
|
|
||||||
|
let point: THREE.Vector3 | null = null;
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
point = intersects[0].point;
|
||||||
|
if (point.y < 0.05) {
|
||||||
|
point = new THREE.Vector3(point.x, 0.05, point.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sphereIntersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === ('Event-Sphere'));
|
||||||
|
|
||||||
|
if (sphereIntersects.length > 0 && sphereIntersects[0].object.uuid === firstSelectedPoint.pointUuid) {
|
||||||
|
setHelperLineColor('red');
|
||||||
|
setCurrentLine(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPoint = getWorldPositionFromScene(firstSelectedPoint.pointUuid);
|
||||||
|
|
||||||
|
if (point && startPoint) {
|
||||||
|
if (sphereIntersects.length > 0) {
|
||||||
|
point = sphereIntersects[0].object.getWorldPosition(new THREE.Vector3());
|
||||||
|
}
|
||||||
|
const distance = startPoint.distanceTo(point);
|
||||||
|
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||||
|
const midPoint = new THREE.Vector3(
|
||||||
|
(startPoint.x + point.x) / 2,
|
||||||
|
Math.max(startPoint.y, point.y) + heightFactor,
|
||||||
|
(startPoint.z + point.z) / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
const endPoint = point;
|
||||||
|
|
||||||
|
setCurrentLine({
|
||||||
|
start: startPoint,
|
||||||
|
mid: midPoint,
|
||||||
|
end: endPoint,
|
||||||
|
});
|
||||||
|
|
||||||
|
setHelperLineColor(sphereIntersects.length > 0 ? "#6cf542" : "red");
|
||||||
|
} else {
|
||||||
|
setCurrentLine(null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCurrentLine(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
|
||||||
|
const pointObj = scene.getObjectByProperty("uuid", pointUuid);
|
||||||
|
if (!pointObj) return null;
|
||||||
|
|
||||||
|
const worldPosition = new THREE.Vector3();
|
||||||
|
pointObj.getWorldPosition(worldPosition);
|
||||||
|
return worldPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeConnection = (connection: ConnectionLine) => {
|
||||||
|
if (connection.trigger.triggerUuid) {
|
||||||
|
const event = removeTrigger(selectedProduct.productId, connection.trigger.triggerUuid);
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<group name="simulationConnectionGroup" >
|
||||||
</>
|
{connections.map((connection) => {
|
||||||
|
const startPoint = getWorldPositionFromScene(connection.startPointUuid);
|
||||||
|
const endPoint = getWorldPositionFromScene(connection.endPointUuid);
|
||||||
|
|
||||||
|
if (!startPoint || !endPoint) return null;
|
||||||
|
|
||||||
|
const distance = startPoint.distanceTo(endPoint);
|
||||||
|
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||||
|
const midPoint = new THREE.Vector3(
|
||||||
|
(startPoint.x + endPoint.x) / 2,
|
||||||
|
Math.max(startPoint.y, endPoint.y) + heightFactor,
|
||||||
|
(startPoint.z + endPoint.z) / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QuadraticBezierLine
|
||||||
|
key={connection.id}
|
||||||
|
ref={(el) => (groupRefs.current[connection.id] = el!)}
|
||||||
|
start={startPoint.toArray()}
|
||||||
|
end={endPoint.toArray()}
|
||||||
|
mid={midPoint.toArray()}
|
||||||
|
color={deleteTool && hoveredLineKey === connection.id ? "red" : "#42a5f5"}
|
||||||
|
lineWidth={4}
|
||||||
|
dashed={deleteTool && hoveredLineKey === connection.id ? false : true}
|
||||||
|
dashSize={0.75}
|
||||||
|
dashScale={20}
|
||||||
|
onPointerOver={() => setHoveredLineKey(connection.id)}
|
||||||
|
onPointerOut={() => setHoveredLineKey(null)}
|
||||||
|
onClick={() => {
|
||||||
|
if (deleteTool) {
|
||||||
|
setHoveredLineKey(null);
|
||||||
|
setCurrentLine(null);
|
||||||
|
removeConnection(connection);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
userData={connection.trigger}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{currentLine && (
|
||||||
|
<QuadraticBezierLine
|
||||||
|
start={currentLine.start.toArray()}
|
||||||
|
end={currentLine.end.toArray()}
|
||||||
|
mid={currentLine.mid.toArray()}
|
||||||
|
color={helperlineColor}
|
||||||
|
lineWidth={4}
|
||||||
|
dashed
|
||||||
|
dashSize={1}
|
||||||
|
dashScale={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface PickDropProps {
|
||||||
actionType: "pick" | "drop";
|
actionType: "pick" | "drop";
|
||||||
actionUuid: string;
|
actionUuid: string;
|
||||||
gltfScene: THREE.Group;
|
gltfScene: THREE.Group;
|
||||||
selectedPoint: THREE.Mesh | null;
|
|
||||||
handlePointerDown: (e: ThreeEvent<PointerEvent>) => void;
|
handlePointerDown: (e: ThreeEvent<PointerEvent>) => void;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,10 @@ const PickDropPoints: React.FC<PickDropProps> = ({
|
||||||
actionType,
|
actionType,
|
||||||
actionUuid,
|
actionUuid,
|
||||||
gltfScene,
|
gltfScene,
|
||||||
selectedPoint,
|
|
||||||
handlePointerDown,
|
handlePointerDown,
|
||||||
isSelected,
|
isSelected,
|
||||||
}) => {
|
}) => {
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
ref={groupRef}
|
ref={groupRef}
|
||||||
|
@ -36,15 +34,24 @@ const PickDropPoints: React.FC<PickDropProps> = ({
|
||||||
: new THREE.Vector3(0, 0, 0)
|
: new THREE.Vector3(0, 0, 0)
|
||||||
}
|
}
|
||||||
onPointerDown={(e) => {
|
onPointerDown={(e) => {
|
||||||
e.stopPropagation(); // Important to prevent event bubbling
|
|
||||||
|
e.stopPropagation(); // Prevent event bubbling
|
||||||
if (!isSelected) return;
|
if (!isSelected) return;
|
||||||
handlePointerDown(e);
|
handlePointerDown(e);
|
||||||
}}
|
}}
|
||||||
userData={{ modelUuid, pointUuid, actionType, actionUuid }}
|
userData={{ modelUuid, pointUuid, actionType, actionUuid }}
|
||||||
>
|
>
|
||||||
<primitive
|
<primitive
|
||||||
object={gltfScene.clone()}
|
object={(() => {
|
||||||
position={[0, 0, 0]} // Ensure this stays at origin
|
const cloned = gltfScene.clone();
|
||||||
|
cloned.traverse((child: any) => {
|
||||||
|
if (child.isMesh) {
|
||||||
|
child.userData = { modelUuid, pointUuid, actionType, actionUuid };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return cloned;
|
||||||
|
})()}
|
||||||
|
position={[0, 0, 0]}
|
||||||
scale={[0.5, 0.5, 0.5]}
|
scale={[0.5, 0.5, 0.5]}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useSelectedAction, useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
|
||||||
|
import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
|
||||||
|
import { useGLTF } from '@react-three/drei';
|
||||||
|
import { useThree } from '@react-three/fiber';
|
||||||
|
import { useProductStore } from '../../../../store/simulation/useProductStore';
|
||||||
|
import PickDropPoints from './PickDropPoints';
|
||||||
|
import useDraggableGLTF from './useDraggableGLTF';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import armPick from "../../../../assets/gltf-glb/arm_ui_pick.glb";
|
||||||
|
import armDrop from "../../../../assets/gltf-glb/arm_ui_drop.glb";
|
||||||
|
import useModuleStore from '../../../../store/useModuleStore';
|
||||||
|
|
||||||
|
type Positions = {
|
||||||
|
pick: [number, number, number];
|
||||||
|
drop: [number, number, number];
|
||||||
|
default: [number, number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArmBotUI = () => {
|
||||||
|
const { getEventByModelUuid } = useProductStore();
|
||||||
|
const { selectedEventData } = useSelectedEventData();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { armBots, updateStartPoint, updateEndPoint } = useArmBotStore();
|
||||||
|
const { scene } = useThree();
|
||||||
|
const { selectedAction } = useSelectedAction();
|
||||||
|
|
||||||
|
const armUiPick = useGLTF(armPick) as any;
|
||||||
|
const armUiDrop = useGLTF(armDrop) as any;
|
||||||
|
|
||||||
|
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]);
|
||||||
|
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]);
|
||||||
|
const [selectedArmBotData, setSelectedArmBotData] = useState<any>(null);
|
||||||
|
|
||||||
|
// Fetch and setup selected ArmBot data
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEventData?.data.type === "roboticArm") {
|
||||||
|
const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid);
|
||||||
|
|
||||||
|
if (selectedArmBot?.type === "roboticArm") {
|
||||||
|
setSelectedArmBotData(selectedArmBot);
|
||||||
|
const defaultPositions = getDefaultPositions(selectedArmBot.modelUuid);
|
||||||
|
const matchingAction = armBots?.flatMap((robot: ArmBotStatus) => robot.point.actions)
|
||||||
|
.find((action) => action.actionUuid === selectedAction.actionId);
|
||||||
|
if (matchingAction) {
|
||||||
|
const startPoint = matchingAction.process.startPoint;
|
||||||
|
const pickPosition = (!startPoint || (Array.isArray(startPoint) && startPoint.every(v => v === 0)))
|
||||||
|
? defaultPositions.pick
|
||||||
|
: startPoint;
|
||||||
|
|
||||||
|
const endPoint = matchingAction.process.endPoint;
|
||||||
|
const dropPosition = (!endPoint || (Array.isArray(endPoint) && endPoint.every(v => v === 0)))
|
||||||
|
? defaultPositions.drop
|
||||||
|
: endPoint;
|
||||||
|
|
||||||
|
setStartPosition(pickPosition);
|
||||||
|
setEndPosition(dropPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedEventData, selectedProduct, getEventByModelUuid, selectedAction]);
|
||||||
|
|
||||||
|
|
||||||
|
function getDefaultPositions(modelUuid: string): Positions {
|
||||||
|
const modelData = getEventByModelUuid(selectedProduct.productId, modelUuid);
|
||||||
|
|
||||||
|
if (modelData?.type === "roboticArm") {
|
||||||
|
const baseX = modelData.point.position?.[0] || 0;
|
||||||
|
const baseY = modelData.point.position?.[1] || 0;;
|
||||||
|
const baseZ = modelData.point.position?.[2] || 0;
|
||||||
|
return {
|
||||||
|
pick: [baseX, baseY, baseZ + 0.5],
|
||||||
|
drop: [baseX, baseY, baseZ - 0.5],
|
||||||
|
default: [baseX, baseY, baseZ],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pick: [0.5, 1.5, 0],
|
||||||
|
drop: [-0.5, 1.5, 0],
|
||||||
|
default: [0, 1.5, 0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalPosition(parentUuid: string, worldPosArray: [number, number, number] | null): [number, number, number] | null {
|
||||||
|
if (worldPosArray) {
|
||||||
|
const worldPos = new THREE.Vector3(...worldPosArray);
|
||||||
|
const parentObject = scene.getObjectByProperty('uuid', parentUuid);
|
||||||
|
|
||||||
|
if (parentObject) {
|
||||||
|
const localPos = worldPos.clone();
|
||||||
|
parentObject.worldToLocal(localPos);
|
||||||
|
return [localPos.x, localPos.y, localPos.z];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePointToState = (obj: THREE.Object3D) => {
|
||||||
|
const { modelUuid, actionType, actionUuid } = obj.userData;
|
||||||
|
const newPosition = new THREE.Vector3();
|
||||||
|
obj.getWorldPosition(newPosition);
|
||||||
|
const worldPositionArray = newPosition.toArray() as [number, number, number];
|
||||||
|
|
||||||
|
if (selectedEventData?.data.type === "roboticArm") {
|
||||||
|
const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid);
|
||||||
|
|
||||||
|
const armBot = selectedArmBot?.modelUuid === modelUuid ? selectedArmBot : null;
|
||||||
|
if (!armBot) return;
|
||||||
|
|
||||||
|
if (armBot.type === "roboticArm") {
|
||||||
|
armBot?.point?.actions?.map((action: any) => {
|
||||||
|
if (action.actionUuid === actionUuid) {
|
||||||
|
const updatedProcess = { ...action.process };
|
||||||
|
|
||||||
|
if (actionType === "pick") {
|
||||||
|
updatedProcess.startPoint = getLocalPosition(modelUuid, worldPositionArray);
|
||||||
|
setStartPosition(updatedProcess.startPoint)
|
||||||
|
updateStartPoint(modelUuid, actionUuid, updatedProcess.startPoint);
|
||||||
|
} else if (actionType === "drop") {
|
||||||
|
updatedProcess.endPoint = getLocalPosition(modelUuid, worldPositionArray);
|
||||||
|
setEndPosition(updatedProcess.endPoint)
|
||||||
|
updateEndPoint(modelUuid, actionUuid, updatedProcess.endPoint);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
process: updatedProcess,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
||||||
|
}, [armBots])
|
||||||
|
|
||||||
|
const { handlePointerDown } = useDraggableGLTF(updatePointToState);
|
||||||
|
|
||||||
|
if (!selectedArmBotData || !Array.isArray(selectedArmBotData.point?.actions)) {
|
||||||
|
return null; // avoid rendering if no data yet
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{selectedArmBotData.point.actions.map((action: any) => {
|
||||||
|
if (action.actionUuid === selectedAction.actionId) {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={action.actionUuid}>
|
||||||
|
<group
|
||||||
|
position={new THREE.Vector3(...selectedArmBotData.position)}
|
||||||
|
rotation={new THREE.Euler(...selectedArmBotData.rotation)}
|
||||||
|
>
|
||||||
|
<PickDropPoints
|
||||||
|
position={startPosition}
|
||||||
|
modelUuid={selectedArmBotData.modelUuid}
|
||||||
|
pointUuid={selectedArmBotData.point.uuid}
|
||||||
|
actionType="pick"
|
||||||
|
actionUuid={action.actionUuid}
|
||||||
|
gltfScene={armUiPick.scene}
|
||||||
|
handlePointerDown={handlePointerDown}
|
||||||
|
isSelected={true}
|
||||||
|
/>
|
||||||
|
<PickDropPoints
|
||||||
|
position={endPosition}
|
||||||
|
modelUuid={selectedArmBotData.modelUuid}
|
||||||
|
pointUuid={selectedArmBotData.point.uuid}
|
||||||
|
actionType="drop"
|
||||||
|
actionUuid={action.actionUuid}
|
||||||
|
gltfScene={armUiDrop.scene}
|
||||||
|
handlePointerDown={handlePointerDown}
|
||||||
|
isSelected={true}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null; // important! must return something
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArmBotUI;
|
|
@ -1,11 +1,11 @@
|
||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { ThreeEvent, useThree } from "@react-three/fiber";
|
import { ThreeEvent, useThree } from "@react-three/fiber";
|
||||||
|
|
||||||
type OnUpdateCallback = (object: THREE.Object3D) => void;
|
type OnUpdateCallback = (object: THREE.Object3D) => void;
|
||||||
|
|
||||||
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
||||||
const { camera, gl, controls, scene } = useThree();
|
const { camera, gl, controls } = useThree();
|
||||||
const activeObjRef = useRef<THREE.Object3D | null>(null);
|
const activeObjRef = useRef<THREE.Object3D | null>(null);
|
||||||
const planeRef = useRef<THREE.Plane>(
|
const planeRef = useRef<THREE.Plane>(
|
||||||
new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
||||||
|
@ -15,6 +15,7 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
||||||
|
|
||||||
const raycaster = new THREE.Raycaster();
|
const raycaster = new THREE.Raycaster();
|
||||||
const pointer = new THREE.Vector2();
|
const pointer = new THREE.Vector2();
|
||||||
|
const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3());
|
||||||
|
|
||||||
const handlePointerDown = (e: ThreeEvent<PointerEvent>) => {
|
const handlePointerDown = (e: ThreeEvent<PointerEvent>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -33,10 +34,10 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
||||||
|
|
||||||
activeObjRef.current = obj;
|
activeObjRef.current = obj;
|
||||||
initialPositionRef.current.copy(obj.position);
|
initialPositionRef.current.copy(obj.position);
|
||||||
|
|
||||||
|
|
||||||
// Get world position
|
// Get world position
|
||||||
const objectWorldPos = new THREE.Vector3();
|
setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
|
||||||
obj.getWorldPosition(objectWorldPos);
|
|
||||||
|
|
||||||
// Set plane at the object's Y level
|
// Set plane at the object's Y level
|
||||||
planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
|
planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
|
||||||
|
@ -61,52 +62,56 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
||||||
|
|
||||||
const handlePointerMove = (e: PointerEvent) => {
|
const handlePointerMove = (e: PointerEvent) => {
|
||||||
if (!activeObjRef.current) return;
|
if (!activeObjRef.current) return;
|
||||||
|
|
||||||
// Check if Shift key is pressed
|
// Check if Shift key is pressed
|
||||||
const isShiftKeyPressed = e.shiftKey;
|
const isShiftKeyPressed = e.shiftKey;
|
||||||
|
|
||||||
// Get the mouse position relative to the canvas
|
// Get the mouse position relative to the canvas
|
||||||
const rect = gl.domElement.getBoundingClientRect();
|
const rect = gl.domElement.getBoundingClientRect();
|
||||||
pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||||
pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||||
|
|
||||||
// Update raycaster to point to the mouse position
|
// Update raycaster to point to the mouse position
|
||||||
raycaster.setFromCamera(pointer, camera);
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
|
||||||
// Create a vector to store intersection point
|
// Create a vector to store intersection point
|
||||||
const intersection = new THREE.Vector3();
|
const intersection = new THREE.Vector3();
|
||||||
const intersects = raycaster.ray.intersectPlane(planeRef.current, intersection);
|
const intersects = raycaster.ray.intersectPlane(
|
||||||
|
planeRef.current,
|
||||||
|
intersection
|
||||||
|
);
|
||||||
if (!intersects) return;
|
if (!intersects) return;
|
||||||
|
|
||||||
// Add offset for dragging
|
// Add offset for dragging
|
||||||
intersection.add(offsetRef.current);
|
intersection.add(offsetRef.current);
|
||||||
console.log('intersection: ', intersection);
|
|
||||||
|
|
||||||
// Get the parent's world matrix if exists
|
// Get the parent's world matrix if exists
|
||||||
const parent = activeObjRef.current.parent;
|
const parent = activeObjRef.current.parent;
|
||||||
const targetPosition = new THREE.Vector3();
|
const targetPosition = new THREE.Vector3();
|
||||||
|
|
||||||
|
// OnPointerDown
|
||||||
|
initialPositionRef.current.copy(objectWorldPos);
|
||||||
|
|
||||||
|
// OnPointerMove
|
||||||
if (isShiftKeyPressed) {
|
if (isShiftKeyPressed) {
|
||||||
console.log('isShiftKeyPressed: ', isShiftKeyPressed);
|
const { x: initialX, y: initialY } = initialPositionRef.current;
|
||||||
// For Y-axis only movement, maintain original X and Z
|
const { x: objectX, z: objectZ } = objectWorldPos;
|
||||||
console.log('initialPositionRef: ', initialPositionRef);
|
|
||||||
console.log('intersection.y: ', intersection);
|
const deltaX = intersection.x - initialX;
|
||||||
targetPosition.set(
|
|
||||||
initialPositionRef.current.x,
|
targetPosition.set(objectX, initialY + deltaX, objectZ);
|
||||||
intersection.y,
|
|
||||||
initialPositionRef.current.z
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// For free movement
|
// For free movement
|
||||||
targetPosition.copy(intersection);
|
targetPosition.copy(intersection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert world position to local if object is nested inside a parent
|
// Convert world position to local if object is nested inside a parent
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.worldToLocal(targetPosition);
|
parent.worldToLocal(targetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update object position
|
// Update object position
|
||||||
|
|
||||||
activeObjRef.current.position.copy(targetPosition);
|
activeObjRef.current.position.copy(targetPosition);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -126,6 +131,3 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
||||||
|
|
||||||
return { handlePointerDown };
|
return { handlePointerDown };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
import startPoint from "../../../../assets/gltf-glb/arrow_green.glb";
|
import startPoint from "../../../../assets/gltf-glb/arrow_green.glb";
|
||||||
import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
|
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
|
||||||
import { useGLTF } from '@react-three/drei';
|
import { useGLTF } from '@react-three/drei';
|
||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import { useSelectedEventSphere } from '../../../../store/simulation/useSimulationStore';
|
import { useSelectedEventSphere, useIsDragging, useIsRotating } from '../../../../store/simulation/useSimulationStore';
|
||||||
import { useVehicleStore } from '../../../../store/simulation/useVehicleStore';
|
import { useVehicleStore } from '../../../../store/simulation/useVehicleStore';
|
||||||
import * as Types from "../../../../types/world/worldTypes";
|
import { useProductStore } from '../../../../store/simulation/useProductStore';
|
||||||
|
import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
|
||||||
|
import { upsertProductOrEventApi } from '../../../../services/simulation/UpsertProductOrEventApi';
|
||||||
|
|
||||||
const VehicleUI = () => {
|
const VehicleUI = () => {
|
||||||
const { scene: startScene } = useGLTF(startPoint) as any;
|
const { scene: startScene } = useGLTF(startPoint) as any;
|
||||||
const { scene: endScene } = useGLTF(startEnd) as any;
|
const { scene: endScene } = useGLTF(startEnd) as any;
|
||||||
|
@ -14,67 +18,60 @@ const VehicleUI = () => {
|
||||||
const endMarker = useRef<THREE.Group>(null);
|
const endMarker = useRef<THREE.Group>(null);
|
||||||
const prevMousePos = useRef({ x: 0, y: 0 });
|
const prevMousePos = useRef({ x: 0, y: 0 });
|
||||||
const { selectedEventSphere } = useSelectedEventSphere();
|
const { selectedEventSphere } = useSelectedEventSphere();
|
||||||
const { vehicles, updateVehicle } = useVehicleStore();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { getVehicleById } = useVehicleStore();
|
||||||
|
const { updateEvent } = useProductStore();
|
||||||
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]);
|
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]);
|
||||||
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]);
|
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]);
|
||||||
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]);
|
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]);
|
||||||
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
|
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
|
||||||
const [isDragging, setIsDragging] = useState<"start" | "end" | null>(null);
|
const { isDragging, setIsDragging } = useIsDragging();
|
||||||
const [isRotating, setIsRotating] = useState<"start" | "end" | null>(null);
|
const { isRotating, setIsRotating } = useIsRotating();
|
||||||
const { raycaster } = useThree();
|
const { raycaster } = useThree();
|
||||||
const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0));
|
const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0));
|
||||||
const state: Types.ThreeState = useThree();
|
const state: Types.ThreeState = useThree();
|
||||||
const controls: any = state.controls;
|
const controls: any = state.controls;
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productId: string,
|
||||||
|
organization: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productId: productId,
|
||||||
|
organization: organization,
|
||||||
|
eventDatas: eventData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedEventSphere) return;
|
if (!selectedEventSphere) return;
|
||||||
const selectedVehicle = vehicles.find(
|
const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
|
||||||
(vehicle: any) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedVehicle?.point?.action) {
|
if (selectedVehicle?.point?.action) {
|
||||||
const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action;
|
const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action;
|
||||||
|
|
||||||
if (pickUpPoint) {
|
if (pickUpPoint) {
|
||||||
const pickupPosition = new THREE.Vector3(
|
setStartPosition([pickUpPoint.position.x, 0, pickUpPoint.position.z]);
|
||||||
pickUpPoint.position.x,
|
setStartRotation([pickUpPoint.rotation.x, pickUpPoint.rotation.y, pickUpPoint.rotation.z]);
|
||||||
pickUpPoint.position.y,
|
|
||||||
pickUpPoint.position.z
|
|
||||||
);
|
|
||||||
const pickupRotation = new THREE.Vector3(
|
|
||||||
pickUpPoint.rotation.x,
|
|
||||||
pickUpPoint.rotation.y,
|
|
||||||
pickUpPoint.rotation.z
|
|
||||||
);
|
|
||||||
pickupPosition.y = 0;
|
|
||||||
setStartPosition([pickupPosition.x, 0, pickupPosition.z]);
|
|
||||||
setStartRotation([pickupRotation.x, pickupRotation.y, pickupRotation.z]);
|
|
||||||
} else {
|
} else {
|
||||||
const defaultLocal = new THREE.Vector3(0, 0, 1.5);
|
const defaultLocal = new THREE.Vector3(0, 0, 1.5);
|
||||||
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
|
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
|
||||||
defaultWorld.y = 0;
|
|
||||||
setStartPosition([defaultWorld.x, 0, defaultWorld.z]);
|
setStartPosition([defaultWorld.x, 0, defaultWorld.z]);
|
||||||
setStartRotation([0, 0, 0]);
|
setStartRotation([0, 0, 0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unLoadPoint) {
|
if (unLoadPoint) {
|
||||||
const unLoadPosition = new THREE.Vector3(
|
setEndPosition([unLoadPoint.position.x, 0, unLoadPoint.position.z]);
|
||||||
unLoadPoint.position.x,
|
setEndRotation([unLoadPoint.rotation.x, unLoadPoint.rotation.y, unLoadPoint.rotation.z]);
|
||||||
unLoadPoint.position.y,
|
|
||||||
unLoadPoint.position.z
|
|
||||||
);
|
|
||||||
const unLoadRotation = new THREE.Vector3(
|
|
||||||
unLoadPoint.rotation.x,
|
|
||||||
unLoadPoint.rotation.y,
|
|
||||||
unLoadPoint.position.z
|
|
||||||
);
|
|
||||||
unLoadPosition.y = 0; // Force y to 0
|
|
||||||
setEndPosition([unLoadPosition.x, 0, unLoadPosition.z]);
|
|
||||||
setEndRotation([unLoadRotation.x, unLoadRotation.y, unLoadRotation.z]);
|
|
||||||
} else {
|
} else {
|
||||||
const defaultLocal = new THREE.Vector3(0, 0, -1.5);
|
const defaultLocal = new THREE.Vector3(0, 0, -1.5);
|
||||||
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
|
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
|
||||||
defaultWorld.y = 0; // Force y to 0
|
|
||||||
setEndPosition([defaultWorld.x, 0, defaultWorld.z]);
|
setEndPosition([defaultWorld.x, 0, defaultWorld.z]);
|
||||||
setEndRotation([0, 0, 0]);
|
setEndRotation([0, 0, 0]);
|
||||||
}
|
}
|
||||||
|
@ -87,7 +84,6 @@ const VehicleUI = () => {
|
||||||
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
|
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
|
||||||
|
|
||||||
if (intersects) {
|
if (intersects) {
|
||||||
intersectPoint.y = 0; // Force y to 0
|
|
||||||
if (isDragging === "start") {
|
if (isDragging === "start") {
|
||||||
setStartPosition([intersectPoint.x, 0, intersectPoint.z]);
|
setStartPosition([intersectPoint.x, 0, intersectPoint.z]);
|
||||||
}
|
}
|
||||||
|
@ -108,12 +104,12 @@ const VehicleUI = () => {
|
||||||
|
|
||||||
if (marker) {
|
if (marker) {
|
||||||
const rotationSpeed = 10;
|
const rotationSpeed = 10;
|
||||||
marker.rotation.y += deltaX * rotationSpeed;
|
|
||||||
if (isRotating === 'start') {
|
if (isRotating === 'start') {
|
||||||
setStartRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
|
const y = startRotation[1] + deltaX * rotationSpeed;
|
||||||
|
setStartRotation([0, y, 0]);
|
||||||
} else {
|
} else {
|
||||||
|
const y = endRotation[1] + deltaX * rotationSpeed;
|
||||||
setEndRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
|
setEndRotation([0, y, 0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -142,43 +138,34 @@ const VehicleUI = () => {
|
||||||
setIsRotating(null);
|
setIsRotating(null);
|
||||||
|
|
||||||
if (selectedEventSphere?.userData.modelUuid) {
|
if (selectedEventSphere?.userData.modelUuid) {
|
||||||
const updatedVehicle = vehicles.find(
|
const updatedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
|
||||||
(vehicle) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedVehicle) {
|
if (updatedVehicle) {
|
||||||
updateVehicle(selectedEventSphere.userData.modelUuid, {
|
const event = updateEvent(selectedProduct.productId, selectedEventSphere.userData.modelUuid, {
|
||||||
point: {
|
point: {
|
||||||
...updatedVehicle.point,
|
...updatedVehicle.point,
|
||||||
action: {
|
action: {
|
||||||
...updatedVehicle.point?.action,
|
...updatedVehicle.point?.action,
|
||||||
pickUpPoint: {
|
pickUpPoint: {
|
||||||
position: {
|
position: { x: startPosition[0], y: startPosition[1], z: startPosition[2], },
|
||||||
x: startPosition[0],
|
rotation: { x: 0, y: startRotation[1], z: 0, },
|
||||||
y: startPosition[1],
|
|
||||||
z: startPosition[2],
|
|
||||||
},
|
|
||||||
rotation: {
|
|
||||||
x: startRotation[0],
|
|
||||||
y: startRotation[1],
|
|
||||||
z: startRotation[2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
unLoadPoint: {
|
unLoadPoint: {
|
||||||
position: {
|
position: { x: endPosition[0], y: endPosition[1], z: endPosition[2], },
|
||||||
x: endPosition[0],
|
rotation: { x: 0, y: endRotation[1], z: 0, },
|
||||||
y: endPosition[1],
|
|
||||||
z: endPosition[2],
|
|
||||||
},
|
|
||||||
rotation: {
|
|
||||||
x: endRotation[0],
|
|
||||||
y: endRotation[1],
|
|
||||||
z: endRotation[2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productId,
|
||||||
|
organization,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -208,6 +195,7 @@ const VehicleUI = () => {
|
||||||
object={startScene}
|
object={startScene}
|
||||||
ref={startMarker}
|
ref={startMarker}
|
||||||
position={startPosition}
|
position={startPosition}
|
||||||
|
rotation={startRotation}
|
||||||
onPointerDown={(e: any) => {
|
onPointerDown={(e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handlePointerDown(e, "start", "start");
|
handlePointerDown(e, "start", "start");
|
||||||
|
@ -224,6 +212,7 @@ const VehicleUI = () => {
|
||||||
object={endScene}
|
object={endScene}
|
||||||
ref={endMarker}
|
ref={endMarker}
|
||||||
position={endPosition}
|
position={endPosition}
|
||||||
|
rotation={endRotation}
|
||||||
onPointerDown={(e: any) => {
|
onPointerDown={(e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handlePointerDown(e, "end", "end");
|
handlePointerDown(e, "end", "end");
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface VehicleAnimatorProps {
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
currentPhase: string;
|
currentPhase: string;
|
||||||
agvUuid: number;
|
agvUuid: number;
|
||||||
agvDetail: any;
|
agvDetail: VehicleStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset }: VehicleAnimatorProps) {
|
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset }: VehicleAnimatorProps) {
|
||||||
|
@ -25,14 +25,14 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
||||||
const completedRef = useRef<boolean>(false);
|
const completedRef = useRef<boolean>(false);
|
||||||
const isPausedRef = useRef<boolean>(false);
|
const isPausedRef = useRef<boolean>(false);
|
||||||
const pauseTimeRef = useRef<number | null>(null);
|
const pauseTimeRef = useRef<number | null>(null);
|
||||||
|
const [progress, setProgress] = useState<number>(0);
|
||||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||||
const [progress, setProgress] = useState<number>(0);
|
|
||||||
const { scene } = useThree();
|
const { scene } = useThree();
|
||||||
let startTime: number;
|
let startTime: number;
|
||||||
let fixedInterval: number;
|
let fixedInterval: number;
|
||||||
let coveredDistance = progressRef.current;
|
let coveredDistance = progressRef.current;
|
||||||
let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number };
|
let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number } | undefined;
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -66,11 +66,12 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
||||||
setReset(false);
|
setReset(false);
|
||||||
setRestingRotation(true);
|
setRestingRotation(true);
|
||||||
decrementVehicleLoad(agvDetail.modelUuid, 0);
|
decrementVehicleLoad(agvDetail.modelUuid, 0);
|
||||||
|
isPausedRef.current = false;
|
||||||
|
pauseTimeRef.current = 0;
|
||||||
const object = scene.getObjectByProperty('uuid', agvUuid);
|
const object = scene.getObjectByProperty('uuid', agvUuid);
|
||||||
console.log('currentPhase: ', currentPhase);
|
|
||||||
if (object) {
|
if (object) {
|
||||||
object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]);
|
object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]);
|
||||||
object.rotation.set(objectRotation.x, objectRotation.y, objectRotation.z);
|
object.rotation.set(agvDetail.rotation[0], agvDetail.rotation[1], agvDetail.rotation[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isReset, isPlaying])
|
}, [isReset, isPlaying])
|
||||||
|
@ -132,7 +133,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressRef.current >= totalDistance) {
|
if (progressRef.current >= totalDistance) {
|
||||||
if (restRotation) {
|
if (restRotation && objectRotation) {
|
||||||
const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z));
|
const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z));
|
||||||
object.quaternion.slerp(targetQuaternion, delta * 2);
|
object.quaternion.slerp(targetQuaternion, delta * 2);
|
||||||
const angleDiff = object.quaternion.angleTo(targetQuaternion);
|
const angleDiff = object.quaternion.angleTo(targetQuaternion);
|
||||||
|
|
|
@ -30,7 +30,8 @@ function VehicleInstance({ agvDetail }: any) {
|
||||||
);
|
);
|
||||||
|
|
||||||
function vehicleStatus(modelId: string, status: string) {
|
function vehicleStatus(modelId: string, status: string) {
|
||||||
// console.log(`${modelId} , ${status});
|
// console.log(`${modelId} , ${status}`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to reset everything
|
// Function to reset everything
|
||||||
|
@ -44,7 +45,7 @@ function VehicleInstance({ agvDetail }: any) {
|
||||||
const increment = () => {
|
const increment = () => {
|
||||||
if (isIncrememtable.current) {
|
if (isIncrememtable.current) {
|
||||||
|
|
||||||
incrementVehicleLoad(agvDetail.modelUuid, 2);
|
incrementVehicleLoad(agvDetail.modelUuid, 10);
|
||||||
isIncrememtable.current = false;
|
isIncrememtable.current = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +70,7 @@ function VehicleInstance({ agvDetail }: any) {
|
||||||
increment();
|
increment();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
|
||||||
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) {
|
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) {
|
||||||
const toDrop = computePath(
|
const toDrop = computePath(
|
||||||
agvDetail.point.action.pickUpPoint.position,
|
agvDetail.point.action.pickUpPoint.position,
|
||||||
|
|
|
@ -9,10 +9,8 @@ function VehicleInstances() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{vehicles.map((val: any, i: any) =>
|
{vehicles.map((val: VehicleStatus) =>
|
||||||
|
<VehicleInstance agvDetail={val} key={val.modelUuid} />
|
||||||
<VehicleInstance agvDetail={val} key={i} />
|
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,220 +1,48 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import VehicleInstances from "./instances/vehicleInstances";
|
import VehicleInstances from "./instances/vehicleInstances";
|
||||||
import { useVehicleStore } from "../../../store/simulation/useVehicleStore";
|
import { useVehicleStore } from "../../../store/simulation/useVehicleStore";
|
||||||
import { useFloorItems } from "../../../store/store";
|
import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore";
|
||||||
import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
|
|
||||||
import VehicleUI from "../ui/vehicle/vehicleUI";
|
import VehicleUI from "../ui/vehicle/vehicleUI";
|
||||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||||
function Vehicles() {
|
import { useProductStore } from "../../../store/simulation/useProductStore";
|
||||||
|
|
||||||
const { vehicles, addVehicle } = useVehicleStore();
|
function Vehicles() {
|
||||||
|
const { products, getProductById } = useProductStore();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { vehicles, addVehicle, clearvehicles } = useVehicleStore();
|
||||||
const { selectedEventSphere } = useSelectedEventSphere();
|
const { selectedEventSphere } = useSelectedEventSphere();
|
||||||
const { selectedEventData } = useSelectedEventData();
|
const { selectedEventData } = useSelectedEventData();
|
||||||
const { floorItems } = useFloorItems();
|
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
|
||||||
const [vehicleStatusSample, setVehicleStatusSample] = useState<
|
|
||||||
VehicleEventSchema[]
|
|
||||||
>([
|
|
||||||
{
|
|
||||||
modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74",
|
|
||||||
modelName: "AGV",
|
|
||||||
position: [97.9252965204558, 0, 37.96138815638661],
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
state: "idle",
|
|
||||||
type: "vehicle",
|
|
||||||
speed: 2.5,
|
|
||||||
point: {
|
|
||||||
uuid: "point-789",
|
|
||||||
position: [0, 1, 0],
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
action: {
|
|
||||||
actionUuid: "action-456",
|
|
||||||
actionName: "Deliver to Zone A",
|
|
||||||
actionType: "travel",
|
|
||||||
unLoadDuration: 10,
|
|
||||||
loadCapacity: 2,
|
|
||||||
steeringAngle:0,
|
|
||||||
pickUpPoint: { position: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } },
|
|
||||||
unLoadPoint: { position: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } },
|
|
||||||
triggers: [
|
|
||||||
{
|
|
||||||
triggerUuid: "trig-001",
|
|
||||||
triggerName: "Start Travel",
|
|
||||||
triggerType: "onComplete",
|
|
||||||
delay: 0,
|
|
||||||
triggeredAsset: {
|
|
||||||
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
|
|
||||||
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
|
|
||||||
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
triggerUuid: "trig-002",
|
|
||||||
triggerName: "Complete Travel",
|
|
||||||
triggerType: "onComplete",
|
|
||||||
delay: 2,
|
|
||||||
triggeredAsset: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelUuid: "b06960bb-3d2e-41f7-a646-335f389c68b4",
|
|
||||||
modelName: "AGV",
|
|
||||||
position: [89.61609306554463, 0, 33.634136622267356],
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
state: "idle",
|
|
||||||
type: "vehicle",
|
|
||||||
speed: 2.5,
|
|
||||||
point: {
|
|
||||||
uuid: "point-789",
|
|
||||||
position: [0, 1, 0],
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
action: {
|
|
||||||
actionUuid: "action-456",
|
|
||||||
actionName: "Deliver to Zone A",
|
|
||||||
actionType: "travel",
|
|
||||||
unLoadDuration: 10,
|
|
||||||
loadCapacity: 2,
|
|
||||||
steeringAngle:0,
|
|
||||||
pickUpPoint: null,
|
|
||||||
unLoadPoint: null,
|
|
||||||
triggers: [
|
|
||||||
{
|
|
||||||
triggerUuid: "trig-001",
|
|
||||||
triggerName: "Start Travel",
|
|
||||||
triggerType: "onStart",
|
|
||||||
delay: 0,
|
|
||||||
triggeredAsset: {
|
|
||||||
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
|
|
||||||
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
|
|
||||||
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
triggerUuid: "trig-002",
|
|
||||||
triggerName: "Complete Travel",
|
|
||||||
triggerType: "onComplete",
|
|
||||||
delay: 2,
|
|
||||||
triggeredAsset: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// modelUuid: "cd7d0584-0684-42b4-b051-9e882c1914aa",
|
|
||||||
// modelName: "AGV",
|
|
||||||
// position: [105.90938758014703, 0, 31.584209911095215],
|
|
||||||
// rotation: [0, 0, 0],
|
|
||||||
// state: "idle",
|
|
||||||
// type: "vehicle",
|
|
||||||
// speed: 2.5,
|
|
||||||
// point: {
|
|
||||||
// uuid: "point-789",
|
|
||||||
// position: [0, 1, 0],
|
|
||||||
// rotation: [0, 0, 0],
|
|
||||||
// action: {
|
|
||||||
// actionUuid: "action-456",
|
|
||||||
// actionName: "Deliver to Zone A",
|
|
||||||
// actionType: "travel",
|
|
||||||
// unLoadDuration: 10,
|
|
||||||
// loadCapacity: 2,
|
|
||||||
// steeringAngle:0,
|
|
||||||
// pickUpPoint: null,
|
|
||||||
// unLoadPoint: null,
|
|
||||||
// triggers: [
|
|
||||||
// {
|
|
||||||
// triggerUuid: "trig-001",
|
|
||||||
// triggerName: "Start Travel",
|
|
||||||
// triggerType: "onStart",
|
|
||||||
// delay: 0,
|
|
||||||
// triggeredAsset: {
|
|
||||||
// triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
|
|
||||||
// triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
|
|
||||||
// triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// triggerUuid: "trig-002",
|
|
||||||
// triggerName: "Complete Travel",
|
|
||||||
// triggerType: "onComplete",
|
|
||||||
// delay: 2,
|
|
||||||
// triggeredAsset: null
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79",
|
|
||||||
// modelName: "forklift",
|
|
||||||
// position: [98.85729337188162, 0, 38.36616546567653],
|
|
||||||
// rotation: [0, 0, 0],
|
|
||||||
// state: "idle",
|
|
||||||
// type: "vehicle",
|
|
||||||
// speed: 2.5,
|
|
||||||
// point: {
|
|
||||||
// uuid: "point-789",
|
|
||||||
// position: [0, 1, 0],
|
|
||||||
// rotation: [0, 0, 0],
|
|
||||||
// action: {
|
|
||||||
// actionUuid: "action-456",
|
|
||||||
// actionName: "Deliver to Zone A",
|
|
||||||
// actionType: "travel",
|
|
||||||
// unLoadDuration: 15,
|
|
||||||
// loadCapacity: 5,
|
|
||||||
// steeringAngle:0,
|
|
||||||
// pickUpPoint: null,
|
|
||||||
// unLoadPoint: null,
|
|
||||||
// triggers: [
|
|
||||||
// {
|
|
||||||
// triggerUuid: "trig-001",
|
|
||||||
// triggerName: "Start Travel",
|
|
||||||
// triggerType: "onStart",
|
|
||||||
// delay: 0,
|
|
||||||
// triggeredAsset: {
|
|
||||||
// triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
|
|
||||||
// triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
|
|
||||||
// triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// triggerUuid: "trig-002",
|
|
||||||
// triggerName: "Complete Travel",
|
|
||||||
// triggerType: "onComplete",
|
|
||||||
// delay: 2,
|
|
||||||
// triggeredAsset: null
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('vehicles: ', vehicles);
|
if (selectedProduct.productId) {
|
||||||
|
const product = getProductById(selectedProduct.productId);
|
||||||
|
if (product) {
|
||||||
|
clearvehicles();
|
||||||
|
product.eventDatas.forEach(events => {
|
||||||
|
if (events.type === 'vehicle') {
|
||||||
|
addVehicle(selectedProduct.productId, events);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedProduct, products]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//
|
||||||
}, [vehicles])
|
}, [vehicles])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
addVehicle("123", vehicleStatusSample[0]);
|
|
||||||
addVehicle('123', vehicleStatusSample[1]);
|
|
||||||
// addVehicle('123', vehicleStatusSample[2]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<VehicleInstances />
|
<VehicleInstances />
|
||||||
|
|
||||||
{selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying &&
|
{selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying &&
|
||||||
< VehicleUI />
|
< VehicleUI />
|
||||||
}
|
}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Vehicles;
|
export default Vehicles;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,7 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||||
|
|
||||||
const { setRightSelect } = useRightSelected();
|
const { setRightSelect } = useRightSelected();
|
||||||
const { editWidgetOptions, setEditWidgetOptions } =
|
const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore();
|
||||||
useEditWidgetOptionsStore();
|
|
||||||
const { rightClickSelected, setRightClickSelected } = useRightClickSelected();
|
const { rightClickSelected, setRightClickSelected } = useRightClickSelected();
|
||||||
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
|
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
|
||||||
const { setFloatingWidget } = useFloatingWidget();
|
const { setFloatingWidget } = useFloatingWidget();
|
||||||
|
@ -76,8 +75,6 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
const { setSelectedChartId } = useWidgetStore();
|
const { setSelectedChartId } = useWidgetStore();
|
||||||
const [waitingPanels, setWaitingPanels] = useState(null);
|
const [waitingPanels, setWaitingPanels] = useState(null);
|
||||||
|
|
||||||
console.log("waitingPanels: ", waitingPanels);
|
|
||||||
|
|
||||||
OuterClick({
|
OuterClick({
|
||||||
contextClassName: [
|
contextClassName: [
|
||||||
"chart-container",
|
"chart-container",
|
||||||
|
@ -145,109 +142,109 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
});
|
});
|
||||||
}, [selectedZone]);
|
}, [selectedZone]);
|
||||||
|
|
||||||
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
// const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
try {
|
// try {
|
||||||
const email = localStorage.getItem("email") ?? "";
|
// const email = localStorage.getItem("email") ?? "";
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
// const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
const data = event.dataTransfer.getData("text/plain");
|
// const data = event.dataTransfer.getData("text/plain");
|
||||||
if (widgetSubOption === "3D") return;
|
// if (widgetSubOption === "3D") return;
|
||||||
if (!data || selectedZone.zoneName === "") return;
|
// if (!data || selectedZone.zoneName === "") return;
|
||||||
|
|
||||||
const droppedData = JSON.parse(data);
|
// const droppedData = JSON.parse(data);
|
||||||
const canvasElement = document.getElementById("real-time-vis-canvas");
|
// const canvasElement = document.getElementById("real-time-vis-canvas");
|
||||||
if (!canvasElement) throw new Error("Canvas element not found");
|
// if (!canvasElement) throw new Error("Canvas element not found");
|
||||||
|
|
||||||
const rect = canvasElement.getBoundingClientRect();
|
// const rect = canvasElement.getBoundingClientRect();
|
||||||
const relativeX = event.clientX - rect.left;
|
// const relativeX = event.clientX - rect.left;
|
||||||
const relativeY = event.clientY - rect.top;
|
// const relativeY = event.clientY - rect.top;
|
||||||
|
|
||||||
// Widget dimensions
|
// // Widget dimensions
|
||||||
const widgetWidth = droppedData.width ?? 125;
|
// const widgetWidth = droppedData.width ?? 125;
|
||||||
const widgetHeight = droppedData.height ?? 100;
|
// const widgetHeight = droppedData.height ?? 100;
|
||||||
|
|
||||||
// Center the widget at cursor
|
// // Center the widget at cursor
|
||||||
const centerOffsetX = widgetWidth / 2;
|
// const centerOffsetX = widgetWidth / 2;
|
||||||
const centerOffsetY = widgetHeight / 2;
|
// const centerOffsetY = widgetHeight / 2;
|
||||||
|
|
||||||
const adjustedX = relativeX - centerOffsetX;
|
// const adjustedX = relativeX - centerOffsetX;
|
||||||
const adjustedY = relativeY - centerOffsetY;
|
// const adjustedY = relativeY - centerOffsetY;
|
||||||
|
|
||||||
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
// const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
||||||
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
// const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||||
|
|
||||||
let finalY = 0;
|
// let finalY = 0;
|
||||||
let finalX = 0;
|
// let finalX = 0;
|
||||||
|
|
||||||
if (activeProp1 === "top") {
|
// if (activeProp1 === "top") {
|
||||||
finalY = adjustedY;
|
// finalY = adjustedY;
|
||||||
} else {
|
// } else {
|
||||||
finalY = rect.height - (adjustedY + widgetHeight);
|
// finalY = rect.height - (adjustedY + widgetHeight);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (activeProp2 === "left") {
|
// if (activeProp2 === "left") {
|
||||||
finalX = adjustedX;
|
// finalX = adjustedX;
|
||||||
} else {
|
// } else {
|
||||||
finalX = rect.width - (adjustedX + widgetWidth);
|
// finalX = rect.width - (adjustedX + widgetWidth);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Clamp to boundaries
|
// // Clamp to boundaries
|
||||||
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
|
// finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
|
||||||
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
|
// finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
|
||||||
|
|
||||||
const boundedPosition = {
|
// const boundedPosition = {
|
||||||
...finalPosition,
|
// ...finalPosition,
|
||||||
[activeProp1]: finalY,
|
// [activeProp1]: finalY,
|
||||||
[activeProp2]: finalX,
|
// [activeProp2]: finalX,
|
||||||
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
// [activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
||||||
[activeProp2 === "left" ? "right" : "left"]: "auto",
|
// [activeProp2 === "left" ? "right" : "left"]: "auto",
|
||||||
};
|
// };
|
||||||
|
|
||||||
const newObject = {
|
// const newObject = {
|
||||||
...droppedData,
|
// ...droppedData,
|
||||||
id: generateUniqueId(),
|
// id: generateUniqueId(),
|
||||||
position: boundedPosition,
|
// position: boundedPosition,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const existingZone =
|
// const existingZone =
|
||||||
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
// useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||||
if (!existingZone) {
|
// if (!existingZone) {
|
||||||
useDroppedObjectsStore
|
// useDroppedObjectsStore
|
||||||
.getState()
|
// .getState()
|
||||||
.setZone(selectedZone.zoneName, selectedZone.zoneId);
|
// .setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const addFloatingWidget = {
|
// const addFloatingWidget = {
|
||||||
organization,
|
// organization,
|
||||||
widget: newObject,
|
// widget: newObject,
|
||||||
zoneId: selectedZone.zoneId,
|
// zoneId: selectedZone.zoneId,
|
||||||
};
|
// };
|
||||||
|
|
||||||
if (visualizationSocket) {
|
// if (visualizationSocket) {
|
||||||
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
// visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
||||||
}
|
// }
|
||||||
|
|
||||||
useDroppedObjectsStore
|
// useDroppedObjectsStore
|
||||||
.getState()
|
// .getState()
|
||||||
.addObject(selectedZone.zoneName, newObject);
|
// .addObject(selectedZone.zoneName, newObject);
|
||||||
|
|
||||||
const droppedObjectsStore = useDroppedObjectsStore.getState();
|
// const droppedObjectsStore = useDroppedObjectsStore.getState();
|
||||||
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
// const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
||||||
|
|
||||||
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
|
// if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
|
||||||
console.log(
|
// console.log(
|
||||||
`Objects for Zone ${selectedZone.zoneId}:`,
|
// `Objects for Zone ${selectedZone.zoneId}:`,
|
||||||
currentZone.objects
|
// currentZone.objects
|
||||||
);
|
// );
|
||||||
setFloatingWidget(currentZone.objects);
|
// setFloatingWidget(currentZone.objects);
|
||||||
} else {
|
// } else {
|
||||||
console.warn("Zone not found or zoneId mismatch");
|
// console.warn("Zone not found or zoneId mismatch");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error in handleDrop:", error);
|
// console.error("Error in handleDrop:", error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
@ -304,16 +301,7 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
<div
|
<div ref={containerRef} className="realTime-viz">
|
||||||
ref={containerRef}
|
|
||||||
id="real-time-vis-canvas"
|
|
||||||
className={`realTime-viz canvas ${isPlaying ? "playingFlase" : ""}`}
|
|
||||||
style={{
|
|
||||||
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
|
|
||||||
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
|
||||||
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="realTime-viz-wrapper">
|
<div className="realTime-viz-wrapper">
|
||||||
{openConfirmationPopup && (
|
{openConfirmationPopup && (
|
||||||
<RenderOverlay>
|
<RenderOverlay>
|
||||||
|
@ -324,20 +312,6 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</RenderOverlay>
|
</RenderOverlay>
|
||||||
)}
|
)}
|
||||||
<div
|
|
||||||
className="scene-container"
|
|
||||||
style={{
|
|
||||||
height: "100%",
|
|
||||||
width: "100%",
|
|
||||||
borderRadius:
|
|
||||||
isPlaying || activeModule !== "visualization" ? "" : "6px",
|
|
||||||
}}
|
|
||||||
role="application"
|
|
||||||
onDrop={(event) => handleDrop(event)}
|
|
||||||
onDragOver={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<Scene />
|
|
||||||
</div>
|
|
||||||
{activeModule === "visualization" && selectedZone.zoneName !== "" && (
|
{activeModule === "visualization" && selectedZone.zoneName !== "" && (
|
||||||
<DroppedObjects />
|
<DroppedObjects />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { generateUniqueId } from "../../../functions/generateUniqueId";
|
||||||
|
import { useDroppedObjectsStore } from "../../../store/visualization/useDroppedObjectsStore";
|
||||||
|
import { determinePosition } from "./determinePosition";
|
||||||
|
import { getActiveProperties } from "./getActiveProperties";
|
||||||
|
|
||||||
|
interface HandleDropProps {
|
||||||
|
widgetSubOption: any;
|
||||||
|
visualizationSocket: any;
|
||||||
|
selectedZone: any;
|
||||||
|
setFloatingWidget: (value: any) => void;
|
||||||
|
event: React.DragEvent<HTMLDivElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createHandleDrop = ({
|
||||||
|
widgetSubOption,
|
||||||
|
visualizationSocket,
|
||||||
|
selectedZone,
|
||||||
|
setFloatingWidget,
|
||||||
|
event,
|
||||||
|
}: HandleDropProps) => {
|
||||||
|
event.preventDefault();
|
||||||
|
try {
|
||||||
|
const email = localStorage.getItem("email") ?? "";
|
||||||
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
|
const data = event.dataTransfer.getData("text/plain");
|
||||||
|
if (widgetSubOption === "3D") return;
|
||||||
|
if (!data || selectedZone.zoneName === "") return;
|
||||||
|
|
||||||
|
const droppedData = JSON.parse(data);
|
||||||
|
const canvasElement = document.getElementById("real-time-vis-canvas");
|
||||||
|
if (!canvasElement) throw new Error("Canvas element not found");
|
||||||
|
|
||||||
|
const rect = canvasElement.getBoundingClientRect();
|
||||||
|
const relativeX = event.clientX - rect.left;
|
||||||
|
const relativeY = event.clientY - rect.top;
|
||||||
|
|
||||||
|
// Widget dimensions
|
||||||
|
const widgetWidth = droppedData.width ?? 125;
|
||||||
|
const widgetHeight = droppedData.height ?? 100;
|
||||||
|
|
||||||
|
// Center the widget at cursor
|
||||||
|
const centerOffsetX = widgetWidth / 2;
|
||||||
|
const centerOffsetY = widgetHeight / 2;
|
||||||
|
|
||||||
|
const adjustedX = relativeX - centerOffsetX;
|
||||||
|
const adjustedY = relativeY - centerOffsetY;
|
||||||
|
|
||||||
|
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
||||||
|
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||||
|
|
||||||
|
let finalY = 0;
|
||||||
|
let finalX = 0;
|
||||||
|
|
||||||
|
if (activeProp1 === "top") {
|
||||||
|
finalY = adjustedY;
|
||||||
|
} else {
|
||||||
|
finalY = rect.height - (adjustedY + widgetHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeProp2 === "left") {
|
||||||
|
finalX = adjustedX;
|
||||||
|
} else {
|
||||||
|
finalX = rect.width - (adjustedX + widgetWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to boundaries
|
||||||
|
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
|
||||||
|
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
|
||||||
|
|
||||||
|
const boundedPosition = {
|
||||||
|
...finalPosition,
|
||||||
|
[activeProp1]: finalY,
|
||||||
|
[activeProp2]: finalX,
|
||||||
|
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
||||||
|
[activeProp2 === "left" ? "right" : "left"]: "auto",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newObject = {
|
||||||
|
...droppedData,
|
||||||
|
id: generateUniqueId(),
|
||||||
|
position: boundedPosition,
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingZone =
|
||||||
|
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||||
|
if (!existingZone) {
|
||||||
|
useDroppedObjectsStore
|
||||||
|
.getState()
|
||||||
|
.setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addFloatingWidget = {
|
||||||
|
organization,
|
||||||
|
widget: newObject,
|
||||||
|
zoneId: selectedZone.zoneId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (visualizationSocket) {
|
||||||
|
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
useDroppedObjectsStore
|
||||||
|
.getState()
|
||||||
|
.addObject(selectedZone.zoneName, newObject);
|
||||||
|
|
||||||
|
const droppedObjectsStore = useDroppedObjectsStore.getState();
|
||||||
|
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
||||||
|
|
||||||
|
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
|
||||||
|
console.log(
|
||||||
|
`Objects for Zone ${selectedZone.zoneId}:`,
|
||||||
|
currentZone.objects
|
||||||
|
);
|
||||||
|
setFloatingWidget(currentZone.objects);
|
||||||
|
} else {
|
||||||
|
console.warn("Zone not found or zoneId mismatch");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in handleDrop:", error);
|
||||||
|
}
|
||||||
|
};
|
|
@ -13,6 +13,7 @@ import {
|
||||||
useWallItems,
|
useWallItems,
|
||||||
useZones,
|
useZones,
|
||||||
useLoadingProgress,
|
useLoadingProgress,
|
||||||
|
useWidgetSubOption,
|
||||||
} from "../store/store";
|
} from "../store/store";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
||||||
|
@ -22,12 +23,19 @@ import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
||||||
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
||||||
import { useSelectedUserStore } from "../store/useCollabStore";
|
import { useSelectedUserStore } from "../store/useCollabStore";
|
||||||
import FollowPerson from "../components/templates/FollowPerson";
|
import FollowPerson from "../components/templates/FollowPerson";
|
||||||
import ProductionCapacity from "../components/ui/analysis/ProductionCapacity";
|
import Scene from "../modules/scene/scene";
|
||||||
import ThroughputSummary from "../components/ui/analysis/ThroughputSummary";
|
import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
|
||||||
import ROISummary from "../components/ui/analysis/ROISummary";
|
import { useSelectedZoneStore } from "../store/visualization/useZoneStore";
|
||||||
|
import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore";
|
||||||
|
import { useLogger } from "../components/ui/log/LoggerContext";
|
||||||
|
import RenderOverlay from "../components/templates/Overlay";
|
||||||
|
import LogList from "../components/ui/log/LogList";
|
||||||
|
import Footer from "../components/footer/Footer";
|
||||||
|
|
||||||
const Project: React.FC = () => {
|
const Project: React.FC = () => {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
const echo = useLogger();
|
||||||
|
|
||||||
const { activeModule, setActiveModule } = useModuleStore();
|
const { activeModule, setActiveModule } = useModuleStore();
|
||||||
const { loadingProgress } = useLoadingProgress();
|
const { loadingProgress } = useLoadingProgress();
|
||||||
const { setUserName } = useUserName();
|
const { setUserName } = useUserName();
|
||||||
|
@ -50,42 +58,79 @@ const Project: React.FC = () => {
|
||||||
setOrganization(Organization);
|
setOrganization(Organization);
|
||||||
setUserName(name);
|
setUserName(name);
|
||||||
}
|
}
|
||||||
|
echo.info("Log in success full");
|
||||||
} else {
|
} else {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { isPlaying } = usePlayButtonStore();
|
// global store
|
||||||
const { toggleThreeD } = useThreeDStore();
|
const { toggleThreeD } = useThreeDStore();
|
||||||
|
|
||||||
|
// simulation store
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
|
||||||
|
// collaboration store
|
||||||
const { selectedUser } = useSelectedUserStore();
|
const { selectedUser } = useSelectedUserStore();
|
||||||
|
const { isLogListVisible } = useLogger();
|
||||||
|
|
||||||
|
// real-time visualization store
|
||||||
|
const { widgetSubOption } = useWidgetSubOption();
|
||||||
|
const { visualizationSocket } = useSocketStore();
|
||||||
|
const { selectedZone } = useSelectedZoneStore();
|
||||||
|
const { setFloatingWidget } = useFloatingWidget();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="project-main">
|
<div className="project-main">
|
||||||
{/* <div className="analysis">
|
{!selectedUser && (
|
||||||
<div className="analysis-wrapper">
|
|
||||||
<ProductionCapacity />
|
|
||||||
<ThroughputSummary />
|
|
||||||
</div>
|
|
||||||
<ROISummary />
|
|
||||||
</div> */}
|
|
||||||
<KeyPressListener />
|
|
||||||
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
|
||||||
{!isPlaying && (
|
|
||||||
<>
|
<>
|
||||||
{toggleThreeD && <ModuleToggle />}
|
<KeyPressListener />
|
||||||
<SideBarLeft />
|
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
||||||
<SideBarRight />
|
{!isPlaying && (
|
||||||
|
<>
|
||||||
|
{toggleThreeD && <ModuleToggle />}
|
||||||
|
<SideBarLeft />
|
||||||
|
<SideBarRight />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<RealTimeVisulization />
|
||||||
|
{activeModule === "market" && <MarketPlace />}
|
||||||
|
{activeModule !== "market" && <Tools />}
|
||||||
|
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* <RenderOverlay>
|
<div
|
||||||
<MenuBar setOpenMenu={setOpenMenu} />
|
className="scene-container"
|
||||||
</RenderOverlay> */}
|
id="real-time-vis-canvas"
|
||||||
{activeModule === "market" && <MarketPlace />}
|
style={{
|
||||||
<RealTimeVisulization />
|
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
|
||||||
{activeModule !== "market" && <Tools />}
|
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
||||||
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
||||||
{/* {<SimulationPlayer />} */}
|
borderRadius:
|
||||||
|
isPlaying || activeModule !== "visualization" ? "" : "6px",
|
||||||
|
}}
|
||||||
|
role="application"
|
||||||
|
onDrop={(event) =>
|
||||||
|
createHandleDrop({
|
||||||
|
widgetSubOption,
|
||||||
|
visualizationSocket,
|
||||||
|
selectedZone,
|
||||||
|
setFloatingWidget,
|
||||||
|
event,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onDragOver={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
|
<Scene />
|
||||||
|
</div>
|
||||||
{selectedUser && <FollowPerson />}
|
{selectedUser && <FollowPerson />}
|
||||||
|
{isLogListVisible && (
|
||||||
|
<RenderOverlay>
|
||||||
|
<LogList />
|
||||||
|
</RenderOverlay>
|
||||||
|
)}
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||||
|
|
||||||
|
export const renameProductApi = async (body: { productName: string, productId: string, organization: string }) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${url_Backend_dwinzo}/api/v2/productRename`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to rename product");
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -10,6 +10,7 @@ interface ArmBotStore {
|
||||||
modelUuid: string,
|
modelUuid: string,
|
||||||
updates: Partial<Omit<ArmBotStatus, 'modelUuid' | 'productId'>>
|
updates: Partial<Omit<ArmBotStatus, 'modelUuid' | 'productId'>>
|
||||||
) => void;
|
) => void;
|
||||||
|
clearArmBots: () => void;
|
||||||
|
|
||||||
addCurrentAction: (modelUuid: string, actionUuid: string) => void;
|
addCurrentAction: (modelUuid: string, actionUuid: string) => void;
|
||||||
removeCurrentAction: (modelUuid: string) => void;
|
removeCurrentAction: (modelUuid: string) => void;
|
||||||
|
@ -39,14 +40,17 @@ export const useArmBotStore = create<ArmBotStore>()(
|
||||||
|
|
||||||
addArmBot: (productId, event) => {
|
addArmBot: (productId, event) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.armBots.push({
|
const exists = state.armBots.some(a => a.modelUuid === event.modelUuid);
|
||||||
...event,
|
if (!exists) {
|
||||||
productId,
|
state.armBots.push({
|
||||||
isActive: false,
|
...event,
|
||||||
idleTime: 0,
|
productId,
|
||||||
activeTime: 0,
|
isActive: false,
|
||||||
state: 'idle',
|
idleTime: 0,
|
||||||
});
|
activeTime: 0,
|
||||||
|
state: 'idle',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -65,6 +69,12 @@ export const useArmBotStore = create<ArmBotStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearArmBots: () => {
|
||||||
|
set((state) => {
|
||||||
|
state.armBots = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
addCurrentAction: (modelUuid, actionUuid) => {
|
addCurrentAction: (modelUuid, actionUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
||||||
|
|
|
@ -10,6 +10,7 @@ interface ConveyorStore {
|
||||||
modelUuid: string,
|
modelUuid: string,
|
||||||
updates: Partial<Omit<ConveyorStatus, 'modelUuid' | 'productId'>>
|
updates: Partial<Omit<ConveyorStatus, 'modelUuid' | 'productId'>>
|
||||||
) => void;
|
) => void;
|
||||||
|
clearConveyors: () => void;
|
||||||
|
|
||||||
setConveyorActive: (modelUuid: string, isActive: boolean) => void;
|
setConveyorActive: (modelUuid: string, isActive: boolean) => void;
|
||||||
setConveyorState: (modelUuid: string, newState: ConveyorStatus['state']) => void;
|
setConveyorState: (modelUuid: string, newState: ConveyorStatus['state']) => void;
|
||||||
|
@ -30,14 +31,17 @@ export const useConveyorStore = create<ConveyorStore>()(
|
||||||
|
|
||||||
addConveyor: (productId, event) => {
|
addConveyor: (productId, event) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.conveyors.push({
|
const exists = state.conveyors.some(c => c.modelUuid === event.modelUuid);
|
||||||
...event,
|
if (!exists) {
|
||||||
productId,
|
state.conveyors.push({
|
||||||
isActive: false,
|
...event,
|
||||||
idleTime: 0,
|
productId,
|
||||||
activeTime: 0,
|
isActive: false,
|
||||||
state: 'idle',
|
idleTime: 0,
|
||||||
});
|
activeTime: 0,
|
||||||
|
state: 'idle',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -56,6 +60,12 @@ export const useConveyorStore = create<ConveyorStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearConveyors: () => {
|
||||||
|
set((state) => {
|
||||||
|
state.conveyors = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setConveyorActive: (modelUuid, isActive) => {
|
setConveyorActive: (modelUuid, isActive) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const conveyor = state.conveyors.find(c => c.modelUuid === modelUuid);
|
const conveyor = state.conveyors.find(c => c.modelUuid === modelUuid);
|
||||||
|
|
|
@ -7,7 +7,7 @@ type EventsStore = {
|
||||||
// Event-level actions
|
// Event-level actions
|
||||||
addEvent: (event: EventsSchema) => void;
|
addEvent: (event: EventsSchema) => void;
|
||||||
removeEvent: (modelUuid: string) => void;
|
removeEvent: (modelUuid: string) => void;
|
||||||
updateEvent: (modelUuid: string, updates: Partial<EventsSchema>) => void;
|
updateEvent: (modelUuid: string, updates: Partial<EventsSchema>) => EventsSchema | undefined;
|
||||||
|
|
||||||
// Point-level actions
|
// Point-level actions
|
||||||
addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void;
|
addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void;
|
||||||
|
@ -49,7 +49,9 @@ export const useEventsStore = create<EventsStore>()(
|
||||||
// Event-level actions
|
// Event-level actions
|
||||||
addEvent: (event) => {
|
addEvent: (event) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.events.push(event);
|
if (!state.events.some(e => 'modelUuid' in e && e.modelUuid === event.modelUuid)) {
|
||||||
|
state.events.push(event);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -60,12 +62,15 @@ export const useEventsStore = create<EventsStore>()(
|
||||||
},
|
},
|
||||||
|
|
||||||
updateEvent: (modelUuid, updates) => {
|
updateEvent: (modelUuid, updates) => {
|
||||||
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||||
if (event) {
|
if (event) {
|
||||||
Object.assign(event, updates);
|
Object.assign(event, updates);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Point-level actions
|
// Point-level actions
|
||||||
|
@ -73,9 +78,14 @@ export const useEventsStore = create<EventsStore>()(
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||||
if (event && 'points' in event) {
|
if (event && 'points' in event) {
|
||||||
(event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
|
const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid);
|
||||||
|
if (!existingPoint) {
|
||||||
|
(event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
|
||||||
|
}
|
||||||
} else if (event && 'point' in event) {
|
} else if (event && 'point' in event) {
|
||||||
(event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
|
if (!(event as any).point || (event as any).point.uuid !== point.uuid) {
|
||||||
|
(event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -110,14 +120,15 @@ export const useEventsStore = create<EventsStore>()(
|
||||||
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||||
if (event && 'points' in event) {
|
if (event && 'points' in event) {
|
||||||
const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid);
|
const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid);
|
||||||
if (point) {
|
if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
|
||||||
point.action = action as any;
|
point.action = action as any;
|
||||||
}
|
}
|
||||||
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
|
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
|
||||||
if ('action' in (event as any).point) {
|
const point = (event as any).point;
|
||||||
(event as any).point.action = action;
|
if ('action' in point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
|
||||||
} else if ('actions' in (event as any).point) {
|
point.action = action;
|
||||||
(event as any).point.actions.push(action);
|
} else if ('actions' in point && !point.actions.some((a: any) => a.actionUuid === action.actionUuid)) {
|
||||||
|
point.actions.push(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -180,18 +191,22 @@ export const useEventsStore = create<EventsStore>()(
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
if (point.action && point.action.actionUuid === actionUuid) {
|
if (point.action && point.action.actionUuid === actionUuid) {
|
||||||
point.action.triggers.push(trigger);
|
if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
|
||||||
|
point.action.triggers.push(trigger);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ('point' in event) {
|
} else if ('point' in event) {
|
||||||
const point = (event as any).point;
|
const point: MachinePointSchema | VehiclePointSchema = (event as any).point;
|
||||||
if ('action' in point && point.action.actionUuid === actionUuid) {
|
if ('action' in point && point.action.actionUuid === actionUuid) {
|
||||||
point.action.triggers.push(trigger);
|
if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
|
||||||
|
point.action.triggers.push(trigger);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else if ('actions' in point) {
|
} else if ('actions' in point) {
|
||||||
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
const action = (point as RoboticArmPointSchema).actions.find((a) => a.actionUuid === actionUuid);
|
||||||
if (action) {
|
if (action && !action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
|
||||||
action.triggers.push(trigger);
|
action.triggers.push(trigger);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,23 +4,23 @@ import { immer } from 'zustand/middleware/immer';
|
||||||
interface MachineStore {
|
interface MachineStore {
|
||||||
machines: MachineStatus[];
|
machines: MachineStatus[];
|
||||||
|
|
||||||
// Actions
|
|
||||||
addMachine: (productId: string, machine: MachineEventSchema) => void;
|
addMachine: (productId: string, machine: MachineEventSchema) => void;
|
||||||
removeMachine: (modelUuid: string) => void;
|
removeMachine: (modelUuid: string) => void;
|
||||||
updateMachine: (
|
updateMachine: (
|
||||||
modelUuid: string,
|
modelUuid: string,
|
||||||
updates: Partial<Omit<MachineStatus, 'modelUuid' | 'productId'>>
|
updates: Partial<Omit<MachineStatus, 'modelUuid' | 'productId'>>
|
||||||
) => void;
|
) => void;
|
||||||
|
clearMachines: () => void;
|
||||||
|
|
||||||
|
addCurrentAction: (modelUuid: string, actionUuid: string) => void;
|
||||||
|
removeCurrentAction: (modelUuid: string) => void;
|
||||||
|
|
||||||
// Status updates
|
|
||||||
setMachineActive: (modelUuid: string, isActive: boolean) => void;
|
setMachineActive: (modelUuid: string, isActive: boolean) => void;
|
||||||
setMachineState: (modelUuid: string, newState: MachineStatus['state']) => void;
|
setMachineState: (modelUuid: string, newState: MachineStatus['state']) => void;
|
||||||
|
|
||||||
// Time tracking
|
|
||||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
|
|
||||||
// Helpers
|
|
||||||
getMachineById: (modelUuid: string) => MachineStatus | undefined;
|
getMachineById: (modelUuid: string) => MachineStatus | undefined;
|
||||||
getMachinesByProduct: (productId: string) => MachineStatus[];
|
getMachinesByProduct: (productId: string) => MachineStatus[];
|
||||||
getMachinesBystate: (state: string) => MachineStatus[];
|
getMachinesBystate: (state: string) => MachineStatus[];
|
||||||
|
@ -32,17 +32,19 @@ export const useMachineStore = create<MachineStore>()(
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
machines: [],
|
machines: [],
|
||||||
|
|
||||||
// Actions
|
|
||||||
addMachine: (productId, machine) => {
|
addMachine: (productId, machine) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.machines.push({
|
const exists = state.machines.some(m => m.modelUuid === machine.modelUuid);
|
||||||
...machine,
|
if (!exists) {
|
||||||
productId,
|
state.machines.push({
|
||||||
isActive: false,
|
...machine,
|
||||||
idleTime: 0,
|
productId,
|
||||||
activeTime: 0,
|
isActive: false,
|
||||||
state: 'idle',
|
idleTime: 0,
|
||||||
});
|
activeTime: 0,
|
||||||
|
state: 'idle',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -61,7 +63,36 @@ export const useMachineStore = create<MachineStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Status updates
|
clearMachines: () => {
|
||||||
|
set((state) => {
|
||||||
|
state.machines = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addCurrentAction: (modelUuid) => {
|
||||||
|
set((state) => {
|
||||||
|
const armBot = state.machines.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (armBot) {
|
||||||
|
const action = armBot.point.action;
|
||||||
|
if (action) {
|
||||||
|
armBot.currentAction = {
|
||||||
|
actionUuid: action.actionUuid,
|
||||||
|
actionName: action.actionName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeCurrentAction: (modelUuid) => {
|
||||||
|
set((state) => {
|
||||||
|
const armBot = state.machines.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (armBot) {
|
||||||
|
armBot.currentAction = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setMachineActive: (modelUuid, isActive) => {
|
setMachineActive: (modelUuid, isActive) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const machine = state.machines.find(m => m.modelUuid === modelUuid);
|
const machine = state.machines.find(m => m.modelUuid === modelUuid);
|
||||||
|
@ -80,7 +111,6 @@ export const useMachineStore = create<MachineStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Time tracking
|
|
||||||
incrementActiveTime: (modelUuid, incrementBy) => {
|
incrementActiveTime: (modelUuid, incrementBy) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const machine = state.machines.find(m => m.modelUuid === modelUuid);
|
const machine = state.machines.find(m => m.modelUuid === modelUuid);
|
||||||
|
@ -99,7 +129,6 @@ export const useMachineStore = create<MachineStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Helpers
|
|
||||||
getMachineById: (modelUuid) => {
|
getMachineById: (modelUuid) => {
|
||||||
return get().machines.find(m => m.modelUuid === modelUuid);
|
return get().machines.find(m => m.modelUuid === modelUuid);
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,27 +33,30 @@ type ProductsStore = {
|
||||||
pointUuid: string,
|
pointUuid: string,
|
||||||
action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']
|
action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']
|
||||||
) => EventsSchema | undefined;
|
) => EventsSchema | undefined;
|
||||||
removeAction: (actionUuid: string) => EventsSchema | undefined;
|
removeAction: (productId: string, actionUuid: string) => EventsSchema | undefined;
|
||||||
updateAction: (
|
updateAction: (
|
||||||
|
productId: string,
|
||||||
actionUuid: string,
|
actionUuid: string,
|
||||||
updates: Partial<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']>
|
updates: Partial<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']>
|
||||||
) => EventsSchema | undefined;
|
) => EventsSchema | undefined;
|
||||||
|
|
||||||
// Trigger-level actions
|
// Trigger-level actions
|
||||||
addTrigger: (
|
addTrigger: (
|
||||||
|
productId: string,
|
||||||
actionUuid: string,
|
actionUuid: string,
|
||||||
trigger: TriggerSchema
|
trigger: TriggerSchema
|
||||||
) => void;
|
) => EventsSchema | undefined;
|
||||||
removeTrigger: (triggerUuid: string) => void;
|
removeTrigger: (productId: string, triggerUuid: string) => EventsSchema | undefined;
|
||||||
updateTrigger: (
|
updateTrigger: (
|
||||||
|
productId: string,
|
||||||
triggerUuid: string,
|
triggerUuid: string,
|
||||||
updates: Partial<TriggerSchema>
|
updates: Partial<TriggerSchema>
|
||||||
) => void;
|
) => EventsSchema | undefined;
|
||||||
|
|
||||||
// Renaming functions
|
// Renaming functions
|
||||||
renameProduct: (productId: string, newName: string) => void;
|
renameProduct: (productId: string, newName: string) => void;
|
||||||
renameAction: (actionUuid: string, newName: string) => EventsSchema | undefined;
|
renameAction: (productId: string, actionUuid: string, newName: string) => EventsSchema | undefined;
|
||||||
renameTrigger: (triggerUuid: string, newName: string) => void;
|
renameTrigger: (productId: string, triggerUuid: string, newName: string) => void;
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined;
|
getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined;
|
||||||
|
@ -71,12 +74,15 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
// Product-level actions
|
// Product-level actions
|
||||||
addProduct: (productName, productId) => {
|
addProduct: (productName, productId) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newProduct = {
|
const existingProduct = state.products.find(p => p.productId === productId);
|
||||||
productName,
|
if (!existingProduct) {
|
||||||
productId: productId,
|
const newProduct = {
|
||||||
eventDatas: []
|
productName,
|
||||||
};
|
productId: productId,
|
||||||
state.products.push(newProduct);
|
eventDatas: []
|
||||||
|
};
|
||||||
|
state.products.push(newProduct);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -106,7 +112,10 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const product = state.products.find(p => p.productId === productId);
|
const product = state.products.find(p => p.productId === productId);
|
||||||
if (product) {
|
if (product) {
|
||||||
product.eventDatas.push(event);
|
const existingEvent = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === event.modelUuid);
|
||||||
|
if (!existingEvent) {
|
||||||
|
product.eventDatas.push(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -120,7 +129,7 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteEvent: (modelUuid: string) => {
|
deleteEvent: (modelUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
for (const product of state.products) {
|
||||||
product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid);
|
product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid);
|
||||||
|
@ -150,9 +159,15 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
if (product) {
|
if (product) {
|
||||||
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||||
if (event && 'points' in event) {
|
if (event && 'points' in event) {
|
||||||
(event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
|
const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid);
|
||||||
|
if (!existingPoint) {
|
||||||
|
(event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
|
||||||
|
}
|
||||||
} else if (event && 'point' in event) {
|
} else if (event && 'point' in event) {
|
||||||
(event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
|
const existingPoint = (event as any).point?.uuid === point.uuid;
|
||||||
|
if (!existingPoint) {
|
||||||
|
(event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -198,17 +213,22 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||||
if (event && 'points' in event) {
|
if (event && 'points' in event) {
|
||||||
const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid);
|
const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid);
|
||||||
if (point) {
|
if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
|
||||||
point.action = action as any;
|
point.action = action as any;
|
||||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
}
|
}
|
||||||
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
|
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
|
||||||
if ('action' in (event as any).point) {
|
if ('action' in (event as any).point) {
|
||||||
(event as any).point.action = action;
|
if (!(event as any).point.action || (event as any).point.action.actionUuid !== action.actionUuid) {
|
||||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
(event as any).point.action = action;
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
} else if ('actions' in (event as any).point) {
|
} else if ('actions' in (event as any).point) {
|
||||||
(event as any).point.actions.push(action);
|
const existingAction = (event as any).point.actions.find((a: any) => a.actionUuid === action.actionUuid);
|
||||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
if (!existingAction) {
|
||||||
|
(event as any).point.actions.push(action);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,10 +236,11 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
return updatedEvent;
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeAction: (actionUuid: string) => {
|
removeAction: (productId, actionUuid) => {
|
||||||
let updatedEvent: EventsSchema | undefined;
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
// Handle ConveyorEventSchema
|
// Handle ConveyorEventSchema
|
||||||
|
@ -248,10 +269,11 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
return updatedEvent;
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAction: (actionUuid, updates) => {
|
updateAction: (productId, actionUuid, updates) => {
|
||||||
let updatedEvent: EventsSchema | undefined;
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
|
@ -283,26 +305,40 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trigger-level actions
|
// Trigger-level actions
|
||||||
addTrigger: (actionUuid, trigger) => {
|
addTrigger: (productId, actionUuid, trigger) => {
|
||||||
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
if (point.action && point.action.actionUuid === actionUuid) {
|
if (point.action && point.action.actionUuid === actionUuid) {
|
||||||
point.action.triggers.push(trigger);
|
const existingTrigger = point.action.triggers.find(t => t.triggerUuid === trigger.triggerUuid);
|
||||||
|
if (!existingTrigger) {
|
||||||
|
point.action.triggers.push(trigger);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ('point' in event) {
|
} else if ('point' in event) {
|
||||||
const point = (event as any).point;
|
const point = (event as any).point;
|
||||||
if ('action' in point && point.action.actionUuid === actionUuid) {
|
if ('action' in point && point.action.actionUuid === actionUuid) {
|
||||||
point.action.triggers.push(trigger);
|
const existingTrigger = point.action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid);
|
||||||
|
if (!existingTrigger) {
|
||||||
|
point.action.triggers.push(trigger);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else if ('actions' in point) {
|
} else if ('actions' in point) {
|
||||||
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
||||||
if (action) {
|
if (action) {
|
||||||
action.triggers.push(trigger);
|
const existingTrigger = action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid);
|
||||||
|
if (!existingTrigger) {
|
||||||
|
action.triggers.push(trigger);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,26 +346,41 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeTrigger: (triggerUuid) => {
|
removeTrigger: (productId, triggerUuid) => {
|
||||||
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
if (point.action && 'triggers' in point.action) {
|
if (point.action && 'triggers' in point.action) {
|
||||||
point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid);
|
const Trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid);
|
||||||
|
if (Trigger) {
|
||||||
|
point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ('point' in event) {
|
} else if ('point' in event) {
|
||||||
const point = (event as any).point;
|
const point = (event as any).point;
|
||||||
if ('action' in point && 'triggers' in point.action) {
|
if ('action' in point && 'triggers' in point.action) {
|
||||||
point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
|
const Trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||||
|
if (Trigger) {
|
||||||
|
point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
} else if ('actions' in point) {
|
} else if ('actions' in point) {
|
||||||
for (const action of point.actions) {
|
for (const action of point.actions) {
|
||||||
if ('triggers' in action) {
|
if ('triggers' in action) {
|
||||||
action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
|
const Trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||||
|
if (Trigger) {
|
||||||
|
action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,11 +388,14 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTrigger: (triggerUuid, updates) => {
|
updateTrigger: (productId, triggerUuid, updates) => {
|
||||||
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
|
@ -349,6 +403,7 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid);
|
const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid);
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
Object.assign(trigger, updates);
|
Object.assign(trigger, updates);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,6 +414,7 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
Object.assign(trigger, updates);
|
Object.assign(trigger, updates);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if ('actions' in point) {
|
} else if ('actions' in point) {
|
||||||
|
@ -367,6 +423,7 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
Object.assign(trigger, updates);
|
Object.assign(trigger, updates);
|
||||||
|
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,6 +433,7 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Renaming functions
|
// Renaming functions
|
||||||
|
@ -388,10 +446,11 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renameAction: (actionUuid, newName) => {
|
renameAction: (productId, actionUuid, newName) => {
|
||||||
let updatedEvent: EventsSchema | undefined;
|
let updatedEvent: EventsSchema | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
|
@ -422,9 +481,10 @@ export const useProductStore = create<ProductsStore>()(
|
||||||
return updatedEvent;
|
return updatedEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
renameTrigger: (triggerUuid, newName) => {
|
renameTrigger: (productId, triggerUuid, newName) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
for (const product of state.products) {
|
const product = state.products.find(p => p.productId === productId);
|
||||||
|
if (product) {
|
||||||
for (const event of product.eventDatas) {
|
for (const event of product.eventDatas) {
|
||||||
if ('points' in event) {
|
if ('points' in event) {
|
||||||
for (const point of (event as ConveyorEventSchema).points) {
|
for (const point of (event as ConveyorEventSchema).points) {
|
||||||
|
|
|
@ -114,4 +114,36 @@ export const useSelectedAction = create<SelectedActionState>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IsDraggingState {
|
||||||
|
isDragging: "start" | "end" | null;
|
||||||
|
setIsDragging: (state: "start" | "end" | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useIsDragging = create<IsDraggingState>()(
|
||||||
|
immer((set) => ({
|
||||||
|
isDragging: null,
|
||||||
|
setIsDragging: (state) => {
|
||||||
|
set((s) => {
|
||||||
|
s.isDragging = state;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IsRotatingState {
|
||||||
|
isRotating: "start" | "end" | null;
|
||||||
|
setIsRotating: (state: "start" | "end" | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useIsRotating = create<IsRotatingState>()(
|
||||||
|
immer((set) => ({
|
||||||
|
isRotating: null,
|
||||||
|
setIsRotating: (state) => {
|
||||||
|
set((s) => {
|
||||||
|
s.isRotating = state;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}))
|
||||||
);
|
);
|
|
@ -4,26 +4,22 @@ import { immer } from 'zustand/middleware/immer';
|
||||||
interface StorageUnitStore {
|
interface StorageUnitStore {
|
||||||
storageUnits: StorageUnitStatus[];
|
storageUnits: StorageUnitStatus[];
|
||||||
|
|
||||||
// Actions
|
|
||||||
addStorageUnit: (productId: string, storageUnit: StorageEventSchema) => void;
|
addStorageUnit: (productId: string, storageUnit: StorageEventSchema) => void;
|
||||||
removeStorageUnit: (modelUuid: string) => void;
|
removeStorageUnit: (modelUuid: string) => void;
|
||||||
updateStorageUnit: (
|
updateStorageUnit: (
|
||||||
modelUuid: string,
|
modelUuid: string,
|
||||||
updates: Partial<Omit<StorageUnitStatus, 'modelUuid' | 'productId'>>
|
updates: Partial<Omit<StorageUnitStatus, 'modelUuid' | 'productId'>>
|
||||||
) => void;
|
) => void;
|
||||||
|
clearStorageUnits: () => void;
|
||||||
|
|
||||||
// Status updates
|
|
||||||
setStorageUnitActive: (modelUuid: string, isActive: boolean) => void;
|
setStorageUnitActive: (modelUuid: string, isActive: boolean) => void;
|
||||||
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
|
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
|
||||||
|
|
||||||
// Load updates
|
|
||||||
updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void;
|
updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void;
|
||||||
|
|
||||||
// Time tracking
|
|
||||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
|
|
||||||
// Helpers
|
|
||||||
getStorageUnitById: (modelUuid: string) => StorageUnitStatus | undefined;
|
getStorageUnitById: (modelUuid: string) => StorageUnitStatus | undefined;
|
||||||
getStorageUnitsByProduct: (productId: string) => StorageUnitStatus[];
|
getStorageUnitsByProduct: (productId: string) => StorageUnitStatus[];
|
||||||
getStorageUnitsBystate: (state: string) => StorageUnitStatus[];
|
getStorageUnitsBystate: (state: string) => StorageUnitStatus[];
|
||||||
|
@ -37,18 +33,20 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
storageUnits: [],
|
storageUnits: [],
|
||||||
|
|
||||||
// Actions
|
|
||||||
addStorageUnit: (productId, storageUnit) => {
|
addStorageUnit: (productId, storageUnit) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.storageUnits.push({
|
const exists = state.storageUnits.some(s => s.modelUuid === storageUnit.modelUuid);
|
||||||
...storageUnit,
|
if (!exists) {
|
||||||
productId,
|
state.storageUnits.push({
|
||||||
isActive: false,
|
...storageUnit,
|
||||||
idleTime: 0,
|
productId,
|
||||||
activeTime: 0,
|
isActive: false,
|
||||||
currentLoad: 0,
|
idleTime: 0,
|
||||||
state: 'idle',
|
activeTime: 0,
|
||||||
});
|
currentLoad: 0,
|
||||||
|
state: 'idle',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,7 +65,12 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Status updates
|
clearStorageUnits: () => {
|
||||||
|
set(() => ({
|
||||||
|
storageUnits: [],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
setStorageUnitActive: (modelUuid, isActive) => {
|
setStorageUnitActive: (modelUuid, isActive) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||||
|
@ -86,7 +89,6 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load updates
|
|
||||||
updateStorageUnitLoad: (modelUuid, incrementBy) => {
|
updateStorageUnitLoad: (modelUuid, incrementBy) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||||
|
@ -96,7 +98,6 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Time tracking
|
|
||||||
incrementActiveTime: (modelUuid, incrementBy) => {
|
incrementActiveTime: (modelUuid, incrementBy) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||||
|
@ -115,7 +116,6 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Helpers
|
|
||||||
getStorageUnitById: (modelUuid) => {
|
getStorageUnitById: (modelUuid) => {
|
||||||
return get().storageUnits.find(s => s.modelUuid === modelUuid);
|
return get().storageUnits.find(s => s.modelUuid === modelUuid);
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,7 @@ interface VehiclesStore {
|
||||||
modelUuid: string,
|
modelUuid: string,
|
||||||
updates: Partial<Omit<VehicleStatus, 'modelUuid' | 'productId'>>
|
updates: Partial<Omit<VehicleStatus, 'modelUuid' | 'productId'>>
|
||||||
) => void;
|
) => void;
|
||||||
|
clearvehicles: () => void;
|
||||||
|
|
||||||
setVehicleActive: (modelUuid: string, isActive: boolean) => void;
|
setVehicleActive: (modelUuid: string, isActive: boolean) => void;
|
||||||
updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void;
|
updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void;
|
||||||
|
@ -41,15 +42,18 @@ export const useVehicleStore = create<VehiclesStore>()(
|
||||||
|
|
||||||
addVehicle: (productId, event) => {
|
addVehicle: (productId, event) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.vehicles.push({
|
const exists = state.vehicles.some(v => v.modelUuid === event.modelUuid);
|
||||||
...event,
|
if (!exists) {
|
||||||
productId,
|
state.vehicles.push({
|
||||||
isActive: false,
|
...event,
|
||||||
idleTime: 0,
|
productId,
|
||||||
activeTime: 0,
|
isActive: false,
|
||||||
currentLoad: 0,
|
idleTime: 0,
|
||||||
distanceTraveled: 0,
|
activeTime: 0,
|
||||||
});
|
currentLoad: 0,
|
||||||
|
distanceTraveled: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -68,6 +72,12 @@ export const useVehicleStore = create<VehiclesStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearvehicles: () => {
|
||||||
|
set((state) => {
|
||||||
|
state.vehicles = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setVehicleActive: (modelUuid, isActive) => {
|
setVehicleActive: (modelUuid, isActive) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets";
|
|
||||||
import { useSocketStore } from "../store";
|
import { useSocketStore } from "../store";
|
||||||
import useChartStore from "./useChartStore";
|
import useChartStore from "./useChartStore";
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,11 @@ $text-button-color-dark: #f3f3fd;
|
||||||
// background colors
|
// background colors
|
||||||
// ---------- light mode ----------
|
// ---------- light mode ----------
|
||||||
$background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%);
|
$background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%);
|
||||||
$background-color-solid-gradient: linear-gradient(-45deg, #fcfdfd 0%, #fcfdfd 100%);
|
$background-color-solid-gradient: linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#fcfdfd 0%,
|
||||||
|
#fcfdfd 100%
|
||||||
|
);
|
||||||
$background-color-solid: #fcfdfd;
|
$background-color-solid: #fcfdfd;
|
||||||
$background-color-secondary: #fcfdfd4d;
|
$background-color-secondary: #fcfdfd4d;
|
||||||
$background-color-accent: #6f42c1;
|
$background-color-accent: #6f42c1;
|
||||||
|
@ -45,7 +49,11 @@ $background-radial-gray-gradient: radial-gradient(
|
||||||
|
|
||||||
// ---------- dark mode ----------
|
// ---------- dark mode ----------
|
||||||
$background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%);
|
$background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%);
|
||||||
$background-color-solid-gradient-dark: linear-gradient(-45deg, #333333 0%, #2d2437 100%);
|
$background-color-solid-gradient-dark: linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#333333 0%,
|
||||||
|
#2d2437 100%
|
||||||
|
);
|
||||||
$background-color-solid-dark: #19191d;
|
$background-color-solid-dark: #19191d;
|
||||||
$background-color-secondary-dark: #19191d99;
|
$background-color-secondary-dark: #19191d99;
|
||||||
$background-color-accent-dark: #6f42c1;
|
$background-color-accent-dark: #6f42c1;
|
||||||
|
@ -104,6 +112,21 @@ $color3: #b186ff;
|
||||||
$color4: #8752e8;
|
$color4: #8752e8;
|
||||||
$color5: #c7a8ff;
|
$color5: #c7a8ff;
|
||||||
|
|
||||||
|
// log indication colors
|
||||||
|
// ------------ text -------------
|
||||||
|
$log-default-text-color: #6f42c1;
|
||||||
|
$log-info-text-color: #488ef6;
|
||||||
|
$log-warn-text-color: #f3a50c;
|
||||||
|
$log-error-text-color: #f65648;
|
||||||
|
$log-success-text-color: #43c06d;
|
||||||
|
|
||||||
|
// ------------ background -------------
|
||||||
|
$log-default-backgroung-color: #6e42c133;
|
||||||
|
$log-info-background-color: #488ef633;
|
||||||
|
$log-warn-background-color: #f3a50c33;
|
||||||
|
$log-error-background-color: #f6564833;
|
||||||
|
$log-success-background-color: #43c06d33;
|
||||||
|
|
||||||
// old variables
|
// old variables
|
||||||
$accent-color: #6f42c1;
|
$accent-color: #6f42c1;
|
||||||
$accent-color-dark: #c4abf1;
|
$accent-color-dark: #c4abf1;
|
||||||
|
|
|
@ -37,11 +37,9 @@
|
||||||
|
|
||||||
// old colors
|
// old colors
|
||||||
--accent-color: #{$accent-color};
|
--accent-color: #{$accent-color};
|
||||||
--highlight-accent-color: #{$highlight-accent-color};
|
|
||||||
--accent-gradient-color: #{$acent-gradient};
|
--accent-gradient-color: #{$acent-gradient};
|
||||||
--faint-gradient-color: #{$faint-gradient};
|
--faint-gradient-color: #{$faint-gradient};
|
||||||
--background-color-gray: #{$background-color-gray};
|
--background-color-gray: #{$background-color-gray};
|
||||||
--border-color: #{$border-color};
|
|
||||||
--shadow-main-light: #{$shadow-color};
|
--shadow-main-light: #{$shadow-color};
|
||||||
--box-shadow-light: 0px 2px 4px var(--shadow-main-light);
|
--box-shadow-light: 0px 2px 4px var(--shadow-main-light);
|
||||||
--box-shadow-medium: 0px 4px 8px var(--shadow-main-light);
|
--box-shadow-medium: 0px 4px 8px var(--shadow-main-light);
|
||||||
|
@ -75,7 +73,7 @@
|
||||||
--background-radial-gray-gradient: #{$background-radial-gray-gradient-dark};
|
--background-radial-gray-gradient: #{$background-radial-gray-gradient-dark};
|
||||||
|
|
||||||
// border colors
|
// border colors
|
||||||
--border-color: #{$border-color};
|
--border-color: #{$border-color-dark};
|
||||||
--input-border-color: #{$input-border-color-dark};
|
--input-border-color: #{$input-border-color-dark};
|
||||||
--border-color-accent: #{$border-color-accent-dark};
|
--border-color-accent: #{$border-color-accent-dark};
|
||||||
|
|
||||||
|
@ -89,11 +87,9 @@
|
||||||
|
|
||||||
// old colors
|
// old colors
|
||||||
--accent-color: #{$accent-color-dark};
|
--accent-color: #{$accent-color-dark};
|
||||||
--highlight-accent-color: #{$highlight-accent-color-dark};
|
|
||||||
--accent-gradient-color: #{$acent-gradient-dark};
|
--accent-gradient-color: #{$acent-gradient-dark};
|
||||||
--faint-gradient-color: #{$faint-gradient-dark};
|
--faint-gradient-color: #{$faint-gradient-dark};
|
||||||
--background-color-gray: #{$background-color-gray-dark};
|
--background-color-gray: #{$background-color-gray-dark};
|
||||||
--border-color: #{$border-color-dark};
|
|
||||||
--shadow-main-dark: #{$shadow-color};
|
--shadow-main-dark: #{$shadow-color};
|
||||||
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark);
|
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark);
|
||||||
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark);
|
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark);
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
@use "../abstracts/variables" as *;
|
@use "../abstracts/variables" as *;
|
||||||
@use "../abstracts/mixins" as *;
|
@use "../abstracts/mixins" as *;
|
||||||
|
|
||||||
section, .section{
|
section,
|
||||||
padding: 4px;
|
.section {
|
||||||
outline: 1px solid var(--border-color);
|
padding: 4px;
|
||||||
outline-offset: -1px;
|
outline: 1px solid var(--border-color);
|
||||||
border-radius: #{$border-radius-large};
|
outline-offset: -1px;
|
||||||
background: var(--background-color);
|
border-radius: #{$border-radius-large};
|
||||||
margin: 4px 0;
|
background: var(--background-color);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-container {
|
||||||
|
width: calc(100% - (320px + 270px + 90px));
|
||||||
|
height: calc(100% - (250px));
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: calc(270px + 45px);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: $box-shadow-medium;
|
||||||
|
background: var(--background-color-solid);
|
||||||
|
canvas {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,3 @@ input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button
|
||||||
input[type="password"]::-webkit-inner-spin-button { /* Just in case */
|
input[type="password"]::-webkit-inner-spin-button { /* Just in case */
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button{
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,311 +0,0 @@
|
||||||
.roiSummary-container {
|
|
||||||
.roiSummary-wrapper {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
.product-info {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playBack {
|
|
||||||
display: flex;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 6px;
|
|
||||||
|
|
||||||
.info {
|
|
||||||
span {
|
|
||||||
font-size: var(--font-size-xlarge);
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
color: #31C756;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.roi-details {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.progress-wrapper {
|
|
||||||
width: 250px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 3px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.key {
|
|
||||||
font-size: var(--font-size-xlarge);
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.roi-progress {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metrics {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.metric-item {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #00FF56;
|
|
||||||
background: #436D51;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 4px 6px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-label {
|
|
||||||
font-size: 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-value {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-wrapper {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.metric-item {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
border: 1px solid var(--Grays-Gray-6, #F2F2F7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cost-breakdown {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
border: 1px solid var(--text-disabled);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
.breakdown-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
.section-wrapper {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-number {
|
|
||||||
font-size: 20px;
|
|
||||||
color: #00aaff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: var(--font-size-regular);
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
transform: rotate(90deg);
|
|
||||||
transition: transform 0.2s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-icon.open {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.breakdown-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 8px;
|
|
||||||
text-align: left;
|
|
||||||
border-top: 1px solid var(--text-disabled);
|
|
||||||
border-bottom: 1px solid var(--text-disabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
th:first-child,
|
|
||||||
td:first-child {
|
|
||||||
border-left: 1px solid var(--text-disabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
th:last-child,
|
|
||||||
td:last-child {
|
|
||||||
border-right: 1px solid var(--text-disabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-row,
|
|
||||||
.net-profit-row {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips-section {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
.tip-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.tip-title {
|
|
||||||
color: var(--text-color);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-description {
|
|
||||||
span {
|
|
||||||
font-size: var(--font-size-xlarge);
|
|
||||||
color: #34C759;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
color: #34C759;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
color: #488EF6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.get-tips-button {
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 8px;
|
|
||||||
display: inline-block;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
color: var(--background-color);
|
|
||||||
padding: 4px 6px;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.semi-circle-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 125px;
|
|
||||||
overflow-y: hidden;
|
|
||||||
position: relative;
|
|
||||||
.semi-circle {
|
|
||||||
width: 100%;
|
|
||||||
height: 250px;
|
|
||||||
border-radius: 50%;
|
|
||||||
position: relative;
|
|
||||||
transition: background 0.5s ease;
|
|
||||||
}
|
|
||||||
.progress-cover {
|
|
||||||
position: absolute;
|
|
||||||
width: 75%;
|
|
||||||
height: 75%;
|
|
||||||
top: 12.5%;
|
|
||||||
left: 12.5%;
|
|
||||||
background: #000000cc;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.label-wrapper {
|
|
||||||
.label {
|
|
||||||
font-size: var(--font-size-xxxlarge);
|
|
||||||
}
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0%);
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #333;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Breakdown Table Open/Close Logic
|
|
||||||
|
|
||||||
.breakdown-table-wrapper {
|
|
||||||
&.closed {
|
|
||||||
max-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.open {
|
|
||||||
max-height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.breakdown-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,279 +0,0 @@
|
||||||
.analysis {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: start;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
// pointer-events: none;
|
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
.analysis-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysis-card {
|
|
||||||
min-width: 333px;
|
|
||||||
background: var(--background-color);
|
|
||||||
border-radius: 20px;
|
|
||||||
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
.analysis-card-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
background: var(--background-color);
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.main-header {
|
|
||||||
line-height: 20px;
|
|
||||||
font-size: var(--font-size-regular);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.throughput-value {
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar-wrapper {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
position: relative;
|
|
||||||
// width: 36px;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #FBEBD7;
|
|
||||||
|
|
||||||
.bar-fill {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #FC9D2F;
|
|
||||||
border-radius: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-fill.full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-fill.partial {
|
|
||||||
width: 0; // inline style will override this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.metrics-section {
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid var(--background-color-gray);
|
|
||||||
|
|
||||||
.metric {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.throughoutSummary {
|
|
||||||
.throughoutSummary-wrapper {
|
|
||||||
.process-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 16px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.throughput-value {
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.value {
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Let the text take available space */
|
|
||||||
}
|
|
||||||
|
|
||||||
.lineChart {
|
|
||||||
max-width: 200px;
|
|
||||||
height: 100px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.assetUsage {
|
|
||||||
text-align: right;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px; // Space between cards
|
|
||||||
margin-top: 24px;
|
|
||||||
|
|
||||||
.footer-card {
|
|
||||||
width: 100%;
|
|
||||||
background: var(--background-color-gray);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
width: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
font-size: var(--font-size-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: end;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shiftUtilization {
|
|
||||||
.value-container {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: var(--font-size-xlarge);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
border-radius: 6px;
|
|
||||||
height: 5px;
|
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
background: #F3C64D;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
background: #67B3F4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
background: #7981F5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-indicator {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.shift-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
/* Align items vertically */
|
|
||||||
&:nth-child(1) {
|
|
||||||
.indicator {
|
|
||||||
|
|
||||||
background: #F3C64D;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
.indicator {
|
|
||||||
|
|
||||||
background: #67B3F4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
.indicator {
|
|
||||||
|
|
||||||
background: #7981F5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
display: inline-block;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
@use "../abstracts/variables" as *;
|
||||||
|
@use "../abstracts/mixins" as *;
|
||||||
|
|
||||||
|
.labeled-button-container {
|
||||||
|
@include flex-space-between;
|
||||||
|
padding: 6px 12px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 2px 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: #{$border-radius-large};
|
||||||
|
color: var(--text-button-color);
|
||||||
|
background: var(--background-color-button);
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
@use "../../abstracts/variables" as *;
|
||||||
|
@use "../../abstracts/mixins" as *;
|
||||||
|
|
||||||
|
.footer-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 2px 12px;
|
||||||
|
|
||||||
|
.selection-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.selector-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
.selector {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.logs-detail,
|
||||||
|
.version {
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 3px 6px;
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-detail {
|
||||||
|
padding: 2px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
.log-icon {
|
||||||
|
@include flex-center;
|
||||||
|
}
|
||||||
|
.log-message {
|
||||||
|
max-width: 40vw;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
font-size: var(--font-size-tiny);
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
.icon{
|
||||||
|
@include flex-center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -639,21 +639,6 @@ input[type="number"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.labeled-button-container {
|
|
||||||
@include flex-space-between;
|
|
||||||
padding: 6px 12px;
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 2px 32px;
|
|
||||||
border: none;
|
|
||||||
border-radius: #{$border-radius-large};
|
|
||||||
color: var(--text-button-color);
|
|
||||||
background: var(--background-color-button);
|
|
||||||
transition: all 0.2s;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-field-container {
|
.value-field-container {
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
@use "../../abstracts/variables" as *;
|
||||||
|
@use "../../abstracts/mixins" as *;
|
||||||
|
|
||||||
|
.log-list-container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: var(--background-color-secondary);
|
||||||
|
|
||||||
|
.log-list-wrapper {
|
||||||
|
height: 50%;
|
||||||
|
min-width: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 5;
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 14px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
.log-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.log-header-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
@include flex-center;
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
cursor: pointer;
|
||||||
|
svg {
|
||||||
|
scale: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-nav-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.log-nav {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-nav.active {
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
color: var(--text-button-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 18px 10px;
|
||||||
|
border-radius: 16px;
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
|
||||||
|
.log-entry {
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.log-icon {
|
||||||
|
@include flex-center;
|
||||||
|
}
|
||||||
|
.log-entry-message-container {
|
||||||
|
@include flex-space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
.message-time {
|
||||||
|
font-size: var(--font-size-tiny);
|
||||||
|
font-weight: 300;
|
||||||
|
opacity: 0.8;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.log-entry-message{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,557 @@
|
||||||
|
@use "../../abstracts/variables" as *;
|
||||||
|
@use "../../abstracts/mixins" as *;
|
||||||
|
|
||||||
|
.analysis {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.analysis-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.analysis-card {
|
||||||
|
min-width: 333px;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 8px;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
.analysis-card-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background-color);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.main-header {
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: var(--font-size-regular);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.throughput-value {
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fbebd7;
|
||||||
|
|
||||||
|
.bar-fill {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #fc9d2f;
|
||||||
|
border-radius: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-fill.full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-fill.partial {
|
||||||
|
width: 0; // inline style will override this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-section {
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--background-color-gray);
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.throughoutSummary-wrapper {
|
||||||
|
.process-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.throughput-value {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let the text take available space */
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineChart {
|
||||||
|
max-width: 200px;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.assetUsage {
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // Space between cards
|
||||||
|
margin-top: 24px;
|
||||||
|
|
||||||
|
.footer-card {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background-color-gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: var(--font-size-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiftUtilization {
|
||||||
|
.value-container {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: var(--font-size-xlarge);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
border-radius: 6px;
|
||||||
|
height: 5px;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
background: #f3c64d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
background: #67b3f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
background: #7981f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-indicator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.shift-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
/* Align items vertically */
|
||||||
|
&:nth-child(1) {
|
||||||
|
.indicator {
|
||||||
|
background: #f3c64d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
.indicator {
|
||||||
|
background: #67b3f4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
.indicator {
|
||||||
|
background: #7981f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.roiSummary-wrapper {
|
||||||
|
max-width: 470px;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playBack {
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
span {
|
||||||
|
font-size: var(--font-size-xlarge);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
color: #31c756;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.roi-details {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.progress-wrapper {
|
||||||
|
width: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.key {
|
||||||
|
font-size: var(--font-size-xlarge);
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.roi-progress {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.metric-item {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: #{$border-radius-xxx};
|
||||||
|
border: 1px solid #00ff56;
|
||||||
|
background: #17eb5e42;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 4px 8px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
opacity: 0.8;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 300;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.metric-item {
|
||||||
|
border-radius: #{$border-radius-large};
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-breakdown {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: #{$border-radius-extra-large};
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.breakdown-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.section-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-number {
|
||||||
|
color: #00aaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: var(--font-size-regular);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition: transform 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon.open {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border-radius: #{$border-radius-large};
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.tip-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tip-title {
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-description {
|
||||||
|
span {
|
||||||
|
font-size: var(--font-size-xlarge);
|
||||||
|
color: #34c759;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
color: #34c759;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
color: #488ef6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-tips-button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
color: var(--text-button-color);
|
||||||
|
background: var(--background-color-button);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: #{$border-radius-large};
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.semi-circle-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 125px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
position: relative;
|
||||||
|
.semi-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.progress-cover {
|
||||||
|
position: absolute;
|
||||||
|
width: 75%;
|
||||||
|
height: 75%;
|
||||||
|
top: 12.5%;
|
||||||
|
left: 12.5%;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-wrapper {
|
||||||
|
.label {
|
||||||
|
font-size: var(--font-size-xxxlarge);
|
||||||
|
}
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0%);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-table-wrapper {
|
||||||
|
&.closed {
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breakdown Table Open/Close Logic
|
||||||
|
|
|
@ -32,10 +32,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls-container {
|
.controls-container {
|
||||||
@include flex-center;
|
@include flex-space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
.header{
|
||||||
|
@include flex-center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 0 8px;
|
||||||
|
svg{
|
||||||
|
scale: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
.production-details,
|
.production-details,
|
||||||
.controls-wrapper {
|
.controls-wrapper {
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
|
@ -72,7 +79,7 @@
|
||||||
|
|
||||||
.expand-icon-container {
|
.expand-icon-container {
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
padding: 6px 8px;
|
padding: 0 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +314,22 @@
|
||||||
font-size: var(--font-size-tiny);
|
font-size: var(--font-size-tiny);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timmer {
|
||||||
|
width: auto;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: var(--font-size-tiny);
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-displayer {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-displayer {
|
||||||
|
width: auto;
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.start-displayer {
|
.start-displayer {
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
|
@ -347,6 +370,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.simulation-player-container.open {
|
.simulation-player-container.open {
|
||||||
|
|
||||||
|
.start-displayer,
|
||||||
|
.end-displayer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.timmer {
|
.timmer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
transition: width 0.2s;
|
transition: width 0.2s;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
z-index: #{$z-index-default};
|
z-index: 2;
|
||||||
outline: 1px solid var(--border-color);
|
outline: 1px solid var(--border-color);
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
|
|
||||||
|
@ -124,6 +124,8 @@
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border-radius: #{$border-radius-medium};
|
border-radius: #{$border-radius-medium};
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
|
@ -58,12 +58,8 @@
|
||||||
fill: var(--icon-default-color-active);
|
fill: var(--icon-default-color-active);
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
rect {
|
filter: saturate(0.8);
|
||||||
stroke: var(--icon-default-color);
|
background: var(--background-color-accent);
|
||||||
}
|
|
||||||
circle {
|
|
||||||
fill: var(--icon-default-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,7 +416,7 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
path {
|
path {
|
||||||
stroke: var(--text-button-color);
|
stroke: var(--text-button-color);
|
||||||
stroke-width: 1.3;
|
strokeWidth: 1.3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -686,7 +682,7 @@
|
||||||
|
|
||||||
path {
|
path {
|
||||||
stroke: var(--accent-color);
|
stroke: var(--accent-color);
|
||||||
stroke-width: 1.5px;
|
strokeWidth: 1.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -714,10 +710,10 @@
|
||||||
|
|
||||||
.add-button {
|
.add-button {
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
padding: 2px 4px;
|
padding: 4px 8px;
|
||||||
background: var(--background-color-button);
|
background: var(--background-color-button);
|
||||||
color: var(--text-button-color);
|
color: var(--text-button-color);
|
||||||
border-radius: #{$border-radius-small};
|
border-radius: #{$border-radius-large};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -832,10 +828,10 @@
|
||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--accent-color);
|
background: var(--background-color-accent);
|
||||||
|
|
||||||
path {
|
path {
|
||||||
stroke: var(--primary-color);
|
stroke: var(--text-button-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1003,10 +999,10 @@
|
||||||
border-radius: 8px 0 0 8px;
|
border-radius: 8px 0 0 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--accent-color);
|
background: var(--background-color-accent);
|
||||||
|
|
||||||
path {
|
path {
|
||||||
stroke: var(--primary-color);
|
stroke: var(--text-button-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1067,7 +1063,13 @@
|
||||||
.dropdown-content-container {
|
.dropdown-content-container {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
.value-field-container {
|
||||||
|
padding: 6px;
|
||||||
|
.dropdown {
|
||||||
|
min-width: 44px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
.input-range-container {
|
.input-range-container {
|
||||||
.input-container {
|
.input-container {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
@use 'components/button';
|
@use 'components/button';
|
||||||
@use 'components/form';
|
@use 'components/form';
|
||||||
@use 'components/input';
|
@use 'components/input';
|
||||||
@use 'components/layouts';
|
|
||||||
@use 'components/lists';
|
@use 'components/lists';
|
||||||
@use 'components/moduleToggle';
|
@use 'components/moduleToggle';
|
||||||
@use 'components/templates';
|
@use 'components/templates';
|
||||||
|
@ -22,11 +21,12 @@
|
||||||
@use 'components/visualization/ui/styledWidgets';
|
@use 'components/visualization/ui/styledWidgets';
|
||||||
@use 'components/visualization/floating/common';
|
@use 'components/visualization/floating/common';
|
||||||
@use 'components/marketPlace/marketPlace';
|
@use 'components/marketPlace/marketPlace';
|
||||||
@use 'components/simulation/simulation';
|
|
||||||
@use 'components/menu/menu';
|
@use 'components/menu/menu';
|
||||||
@use 'components/confirmationPopUp';
|
@use 'components/confirmationPopUp';
|
||||||
@use 'components/analysis/analysis';
|
@use 'components/simulation/simulation';
|
||||||
@use 'components/analysis/ROISummary.scss';
|
@use 'components/simulation/analysis';
|
||||||
|
@use 'components/logs/logs';
|
||||||
|
@use 'components/footer/footer.scss';
|
||||||
|
|
||||||
// layout
|
// layout
|
||||||
@use 'layout/loading';
|
@use 'layout/loading';
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
// Main Container
|
// Main Container
|
||||||
.realTime-viz {
|
.realTime-viz {
|
||||||
background: #131313;
|
|
||||||
box-shadow: $box-shadow-medium;
|
|
||||||
width: calc(100% - (320px + 270px + 90px));
|
width: calc(100% - (320px + 270px + 90px));
|
||||||
height: calc(100% - (250px));
|
height: calc(100% - (250px));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -12,8 +10,8 @@
|
||||||
left: calc(270px + 45px);
|
left: calc(270px + 45px);
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
border-radius: #{$border-radius-medium};
|
border-radius: #{$border-radius-medium};
|
||||||
transition: all 0.2s;
|
z-index: 2;
|
||||||
z-index: #{$z-index-default};
|
pointer-events: none;
|
||||||
|
|
||||||
.realTime-viz-wrapper {
|
.realTime-viz-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -39,10 +37,6 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-container {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -74,6 +68,8 @@
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
transform: translate(-50%, -10%);
|
transform: translate(-50%, -10%);
|
||||||
transition: transform 0.5s linear;
|
transition: transform 0.5s linear;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -367,6 +363,7 @@
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
box-shadow: #{$box-shadow-medium};
|
box-shadow: #{$box-shadow-medium};
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--icon-default-color) !important;
|
stroke: var(--icon-default-color) !important;
|
||||||
|
@ -426,10 +423,8 @@
|
||||||
|
|
||||||
path {
|
path {
|
||||||
stroke: #f65648;
|
stroke: #f65648;
|
||||||
stroke-width: 1.3;
|
strokeWidth: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,17 +773,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.panel-content {
|
.panel-content {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* RIGHT */
|
/* RIGHT */
|
||||||
.panel-content.right-opening {
|
.panel-content.right-opening {
|
||||||
animation: rightExpand 0.5s ease-in-out forwards;
|
animation: rightExpand 0.5s ease-in-out forwards;
|
||||||
|
@ -913,9 +901,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Add button
|
// Add button
|
||||||
|
|
||||||
.extra-Bs-addopening {
|
.extra-Bs-addopening {
|
||||||
|
@ -926,7 +911,6 @@
|
||||||
animation: slideUp 0.3s ease forwards;
|
animation: slideUp 0.3s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes slideDown {
|
@keyframes slideDown {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface TriggerSchema {
|
||||||
delay: number;
|
delay: number;
|
||||||
triggeredAsset: {
|
triggeredAsset: {
|
||||||
triggeredModel: { modelName: string, modelUuid: string };
|
triggeredModel: { modelName: string, modelUuid: string };
|
||||||
triggeredPoint: { pointName: string, pointUuid: string };
|
triggeredPoint: { pointName: string, pointUuid: string } | null;
|
||||||
triggeredAction: { actionName: string, actionUuid: string } | null;
|
triggeredAction: { actionName: string, actionUuid: string } | null;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ interface StoragePointSchema {
|
||||||
actionType: "store";
|
actionType: "store";
|
||||||
materials: { materialName: string; materialId: string; }[];
|
materials: { materialName: string; materialId: string; }[];
|
||||||
storageCapacity: number;
|
storageCapacity: number;
|
||||||
|
triggers: TriggerSchema[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +144,10 @@ interface MachineStatus extends MachineEventSchema {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
idleTime: number;
|
idleTime: number;
|
||||||
activeTime: number;
|
activeTime: number;
|
||||||
|
currentAction?: {
|
||||||
|
actionUuid: string;
|
||||||
|
actionName: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArmBotStatus extends RoboticArmEventSchema {
|
interface ArmBotStatus extends RoboticArmEventSchema {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
export function snapControls(value: number, event: string): number {
|
||||||
|
const CTRL_DISTANCE = 1; // Snap to whole numbers when Ctrl is pressed
|
||||||
|
const SHIFT_DISTANCE = 0.01; // Snap to half-step increments when Shift is pressed
|
||||||
|
const CTRL_SHIFT_DISTANCE = 0.1; // Snap to fine increments when both Ctrl and Shift are pressed
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case "Ctrl":
|
||||||
|
return Math.round(value / CTRL_DISTANCE) * CTRL_DISTANCE;
|
||||||
|
|
||||||
|
case "Shift":
|
||||||
|
return Math.round(value / SHIFT_DISTANCE) * SHIFT_DISTANCE;
|
||||||
|
|
||||||
|
case "Ctrl+Shift":
|
||||||
|
const base = Math.floor(value / CTRL_DISTANCE) * CTRL_DISTANCE;
|
||||||
|
const offset =
|
||||||
|
Math.round((value - base) / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE;
|
||||||
|
return base + offset;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return value; // No snapping if no modifier key is pressed
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue