v2 #74
|
@ -3,23 +3,20 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
|||
import Dashboard from "./pages/Dashboard";
|
||||
import Project from "./pages/Project";
|
||||
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 = () => {
|
||||
return (
|
||||
<ToastProvider>
|
||||
<LoggerProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={<UserAuth />}
|
||||
/>
|
||||
<Route path="/" element={<UserAuth />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/project" element={<Project />} />
|
||||
</Routes>
|
||||
</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
|
||||
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
|
||||
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 (
|
||||
<svg
|
||||
width="20"
|
||||
|
@ -814,3 +814,136 @@ export const SpeedIcon = () => {
|
|||
</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
|
||||
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"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<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"
|
||||
fill="var(--icon-default-color-active)"
|
||||
stroke="var(--icon-default-color-active)"
|
||||
fill="var(--text-color)"
|
||||
stroke="var(--text-color)"
|
||||
strokeWidth="0.571429"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M4.78125 4.10407H2.49554V1.81836"
|
||||
stroke="var(--icon-default-color-active)"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
@ -158,7 +158,7 @@ export function PlayStopIcon() {
|
|||
>
|
||||
<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"
|
||||
stroke="var(--icon-default-color-active)"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
@ -177,7 +177,7 @@ export function ExitIcon() {
|
|||
>
|
||||
<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"
|
||||
stroke="var(--icon-default-color-active)"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
|
|
@ -19,7 +19,7 @@ const Header: React.FC = () => {
|
|||
<FileMenu />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<button
|
||||
className={`toggle-sidebar-ui-button ${!toggleUI ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
if (activeModule !== "market") {
|
||||
|
@ -29,7 +29,7 @@ const Header: React.FC = () => {
|
|||
}}
|
||||
>
|
||||
<ToggleSidebarIcon />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import { AI_Icon } from "../../../icons/ExportCommonIcons";
|
||||
import { AIIcon } from "../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
|
||||
import { AnalysisPresetsType } from "../../../../types/analysis";
|
||||
import RenderAnalysisInputs from "./RenderAnalysisInputs";
|
||||
|
@ -13,53 +13,24 @@ const Analysis: React.FC = () => {
|
|||
|
||||
const AnalysisPresets: AnalysisPresetsType = {
|
||||
"Throughput time": [
|
||||
{ type: "default", inputs: { label: "Height", activeOption: "mm" } },
|
||||
{ type: "default", inputs: { label: "Width", activeOption: "mm" } },
|
||||
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } },
|
||||
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Raw Material", activeOption: "mm" },
|
||||
},
|
||||
{
|
||||
type: "range",
|
||||
inputs: { label: "Material flow", activeOption: "m/min" },
|
||||
},
|
||||
{ type: "default", inputs: { label: "Cycle time", activeOption: "s" } },
|
||||
{ type: "default", inputs: { label: "machines / lines", activeOption: "item" } },
|
||||
{ type: "default", inputs: { label: "Machine uptime", activeOption: "%" } },
|
||||
],
|
||||
"Production capacity": [
|
||||
{ type: "default", inputs: { label: "Height", activeOption: "mm" } },
|
||||
{ type: "default", inputs: { label: "Width", activeOption: "mm" } },
|
||||
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } },
|
||||
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
|
||||
{
|
||||
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)" },
|
||||
},
|
||||
{ type: "default", inputs: { label: "Shift length", activeOption: "hr" } },
|
||||
{ type: "default", inputs: { label: "Shifts / day", activeOption: "unit" } },
|
||||
{ type: "default", inputs: { label: "Working days / year", activeOption: "days" } },
|
||||
{ type: "default", inputs: { label: "Yield rate", activeOption: "%" } },
|
||||
],
|
||||
ROI: [
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Equipment Cost", activeOption: "INR" },
|
||||
inputs: { label: "Selling price", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Employee Salary", activeOption: "INR" },
|
||||
inputs: { label: "Material cost", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
|
@ -67,7 +38,7 @@ const Analysis: React.FC = () => {
|
|||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Cost per unit", activeOption: "INR" },
|
||||
inputs: { label: "Maintenance cost", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
|
@ -75,20 +46,19 @@ const Analysis: React.FC = () => {
|
|||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Upkeep Cost", activeOption: "INR" },
|
||||
inputs: { label: "Fixed costs", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Working Hours", activeOption: "Hrs" },
|
||||
inputs: { label: "Salvage value", activeOption: "Hrs" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Power Usage", activeOption: "KWH" },
|
||||
inputs: { label: "Production period", activeOption: "yrs" },
|
||||
},
|
||||
{ type: "default", inputs: { label: "KWH", activeOption: "Mos" } },
|
||||
{
|
||||
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="header">Object</div>
|
||||
<div className="generate-report-button">
|
||||
<AI_Icon /> Generate Report
|
||||
<AIIcon /> Generate Report
|
||||
</div>
|
||||
<div className="analysis-content-container section">
|
||||
<div className="dropdown-header-container">
|
||||
|
@ -121,7 +91,7 @@ const Analysis: React.FC = () => {
|
|||
/>
|
||||
<div className="buttons-container">
|
||||
<input type="button" value={"Clear"} className="cancel" />
|
||||
<input type="button" value={"Calculate"} className="submit" />
|
||||
<input type="button" value={"Update"} className="submit" />
|
||||
</div>
|
||||
<div className="create-custom-analysis-container">
|
||||
<div className="custom-analysis-header">Create Custom Analysis</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import InputRange from "../../../ui/inputs/InputRange";
|
||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||
import { AI_Icon } from "../../../icons/ExportCommonIcons";
|
||||
import { AIIcon } from "../../../icons/ExportCommonIcons";
|
||||
import LabeledButton from "../../../ui/inputs/LabledButton";
|
||||
import {
|
||||
useAzimuth,
|
||||
|
@ -225,7 +225,7 @@ const GlobalProperties: React.FC = () => {
|
|||
<section>
|
||||
<div className="header">Environment</div>
|
||||
<div className="optimize-button" onClick={optimizeScene}>
|
||||
<AI_Icon />
|
||||
<AIIcon />
|
||||
Optimize
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
useSelectedEventData,
|
||||
useSelectedEventSphere,
|
||||
useSelectedProduct,
|
||||
useSelectedEventData,
|
||||
useSelectedEventSphere,
|
||||
useSelectedProduct,
|
||||
} from "../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
||||
import ConveyorMechanics from "./mechanics/conveyorMechanics";
|
||||
|
@ -15,114 +15,118 @@ import { handleAddEventToProduct } from "../../../../../modules/simulation/event
|
|||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||
|
||||
const EventProperties: React.FC = () => {
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getEventByModelUuid } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(null);
|
||||
const [assetType, setAssetType] = useState<string | null>(null);
|
||||
const { products, addEvent } = useProductStore();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getEventByModelUuid } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(
|
||||
null
|
||||
);
|
||||
const [assetType, setAssetType] = useState<string | null>(null);
|
||||
const { products, addEvent } = useProductStore();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
|
||||
useEffect(() => {
|
||||
const event = getCurrentEventData();
|
||||
setCurrentEventData(event);
|
||||
useEffect(() => {
|
||||
const event = getCurrentEventData();
|
||||
setCurrentEventData(event);
|
||||
|
||||
const type = determineAssetType(event);
|
||||
setAssetType(type);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [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 type = determineAssetType(event);
|
||||
setAssetType(type);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedEventData, selectedProduct]);
|
||||
|
||||
const getCurrentEventData = () => {
|
||||
if (!selectedEventData?.data || !selectedProduct) 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>
|
||||
<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 >
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
|
@ -35,7 +35,7 @@ const ActionsList: React.FC<ActionsListProps> = ({
|
|||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedAction.actionId) return;
|
||||
const event = renameAction(selectedAction.actionId, newName);
|
||||
const event = renameAction(selectedProduct.productId, selectedAction.actionId, newName);
|
||||
|
||||
if (event) {
|
||||
upsertProductOrEventApi({
|
||||
|
|
|
@ -72,7 +72,7 @@ function ConveyorMechanics() {
|
|||
const validOption = option as | "default" | "spawn" | "swap" | "delay" | "despawn";
|
||||
setActiveOption(validOption);
|
||||
|
||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
actionType: validOption,
|
||||
});
|
||||
|
||||
|
@ -88,7 +88,7 @@ function ConveyorMechanics() {
|
|||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedPointData.action.actionUuid, { actionName: newName });
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName });
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
|
@ -102,7 +102,7 @@ function ConveyorMechanics() {
|
|||
|
||||
const handleSpawnCountChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
spawnCount: value === "inherit" ? "inherit" : parseFloat(value),
|
||||
});
|
||||
|
||||
|
@ -118,7 +118,7 @@ function ConveyorMechanics() {
|
|||
|
||||
const handleSpawnIntervalChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
spawnInterval: value === "inherit" ? "inherit" : parseFloat(value),
|
||||
});
|
||||
|
||||
|
@ -134,7 +134,7 @@ function ConveyorMechanics() {
|
|||
|
||||
const handleMaterialSelect = (material: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedPointData.action.actionUuid, { material });
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { material });
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
|
@ -148,7 +148,7 @@ function ConveyorMechanics() {
|
|||
|
||||
const handleDelayChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
delay: value === "inherit" ? "inherit" : parseFloat(value),
|
||||
});
|
||||
|
||||
|
@ -271,7 +271,7 @@ function ConveyorMechanics() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'Conveyor'}/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useSelectedEventData, useSelectedProduct } from "../../../../../../stor
|
|||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import ProcessAction from "../actions/ProcessAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||
|
||||
function MachineMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "process">("default");
|
||||
|
@ -14,6 +15,9 @@ function MachineMechanics() {
|
|||
const { getPointByUuid, updateAction } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
|
@ -28,31 +32,54 @@ function MachineMechanics() {
|
|||
}
|
||||
}, [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) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const validOption = option as "process";
|
||||
setActiveOption(validOption);
|
||||
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
actionType: validOption,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedPointData) return;
|
||||
updateAction(selectedPointData.action.actionUuid, { actionName: newName });
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName });
|
||||
};
|
||||
|
||||
const handleProcessTimeChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
processTime: parseFloat(value),
|
||||
});
|
||||
};
|
||||
|
||||
const handleMaterialSelect = (material: string) => {
|
||||
if (!selectedPointData) return;
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
swapMaterial: material,
|
||||
});
|
||||
};
|
||||
|
@ -110,7 +137,7 @@ function MachineMechanics() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'Machine'} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
|
|
@ -59,7 +59,7 @@ function RoboticArmMechanics() {
|
|||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedAction.actionId) return;
|
||||
const event = updateAction(selectedAction.actionId, { actionName: newName });
|
||||
const event = updateAction(selectedProduct.productId, selectedAction.actionId, { actionName: newName });
|
||||
|
||||
if (selectedPointData) {
|
||||
const updatedActions = selectedPointData.actions.map((action) =>
|
||||
|
@ -101,7 +101,7 @@ function RoboticArmMechanics() {
|
|||
if (!selectedAction.actionId || !selectedPointData) return;
|
||||
const [x, y, z] = value.split(",").map(Number);
|
||||
|
||||
const event = updateAction(selectedAction.actionId, {
|
||||
const event = updateAction(selectedProduct.productId, selectedAction.actionId, {
|
||||
process: {
|
||||
startPoint: [x, y, z] as [number, number, number],
|
||||
endPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.endPoint || null,
|
||||
|
@ -122,7 +122,7 @@ function RoboticArmMechanics() {
|
|||
if (!selectedAction.actionId || !selectedPointData) return;
|
||||
const [x, y, z] = value.split(",").map(Number);
|
||||
|
||||
const event = updateAction(selectedAction.actionId, {
|
||||
const event = updateAction(selectedProduct.productId, selectedAction.actionId, {
|
||||
process: {
|
||||
startPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.startPoint || null,
|
||||
endPoint: [x, y, z] as [number, number, number],
|
||||
|
@ -181,7 +181,7 @@ function RoboticArmMechanics() {
|
|||
const handleDeleteAction = (actionUuid: string) => {
|
||||
if (!selectedPointData) return;
|
||||
|
||||
const event = removeAction(actionUuid);
|
||||
const event = removeAction(selectedProduct.productId, actionUuid);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
|
@ -280,7 +280,7 @@ function RoboticArmMechanics() {
|
|||
/>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'RoboticArm'} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useSelectedEventData, useSelectedProduct } from "../../../../../../stor
|
|||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import StorageAction from "../actions/StorageAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||
|
||||
function StorageMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default");
|
||||
|
@ -14,6 +15,9 @@ function StorageMechanics() {
|
|||
const { getPointByUuid, updateAction } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
|
@ -28,26 +32,67 @@ function StorageMechanics() {
|
|||
}
|
||||
}, [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) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const validOption = option as "store" | "spawn";
|
||||
setActiveOption(validOption);
|
||||
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
actionType: validOption,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
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) => {
|
||||
if (!selectedPointData) return;
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
storageCapacity: parseInt(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Get current values from store
|
||||
|
@ -101,7 +146,7 @@ function StorageMechanics() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'StorageUnit'} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import TravelAction from "../actions/TravelAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||
|
||||
function VehicleMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "travel">("default");
|
||||
|
@ -18,8 +19,11 @@ function VehicleMechanics() {
|
|||
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
if (selectedEventData && selectedEventData.data.type === 'vehicle') {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productId,
|
||||
selectedEventData.data.modelUuid,
|
||||
|
@ -33,11 +37,34 @@ function VehicleMechanics() {
|
|||
}
|
||||
}, [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) => {
|
||||
if (!selectedEventData) return;
|
||||
updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, {
|
||||
const event = updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, {
|
||||
speed: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionTypeChange = (option: string) => {
|
||||
|
@ -45,28 +72,64 @@ function VehicleMechanics() {
|
|||
const validOption = option as "travel";
|
||||
setActiveOption(validOption);
|
||||
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
actionType: validOption,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
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) => {
|
||||
if (!selectedPointData) return;
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
loadCapacity: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnloadDurationChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
updateAction(selectedPointData.action.actionUuid, {
|
||||
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
|
||||
unLoadDuration: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePickPointChange = (value: string) => {
|
||||
|
@ -172,7 +235,7 @@ function VehicleMechanics() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'Vehicle'} />
|
||||
</div>
|
||||
</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 {
|
||||
AddIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
AddIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
} from "../../../../../icons/ExportCommonIcons";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
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 = () => {
|
||||
// State to hold the list of triggers
|
||||
const [triggers, setTriggers] = useState<string[]>(["Trigger 1"]);
|
||||
const [selectedTrigger, setSelectedTrigger] = useState<string>("Trigger 1");
|
||||
const [activeOption, setActiveOption] = useState("onComplete");
|
||||
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
||||
type TriggerProps = {
|
||||
selectedPointData?: PointsScheme | undefined;
|
||||
type?: 'Conveyor' | 'Vehicle' | 'RoboticArm' | 'Machine' | 'StorageUnit';
|
||||
};
|
||||
|
||||
// States for dropdowns
|
||||
const [triggeredModel, setTriggeredModel] = useState<string[]>([]);
|
||||
const [triggeredPoint, setTriggeredPoint] = useState<string[]>([]);
|
||||
const [triggeredAction, setTriggeredAction] = useState<string[]>([]);
|
||||
const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||
const [currentAction, setCurrentAction] = useState<string | undefined>();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
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 addTrigger = (): void => {
|
||||
const newTrigger = `Trigger ${triggers.length + 1}`;
|
||||
setTriggers([...triggers, newTrigger]); // Add new trigger to the state
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
// Initialize the states for the new trigger
|
||||
setTriggeredModel([...triggeredModel, ""]);
|
||||
setTriggeredPoint([...triggeredPoint, ""]);
|
||||
setTriggeredAction([...triggeredAction, ""]);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!selectedPointData || !selectedProduct) return;
|
||||
|
||||
// Function to handle removing a trigger
|
||||
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));
|
||||
};
|
||||
let actionUuid: string | undefined;
|
||||
|
||||
return (
|
||||
<div className="trigger-wrapper">
|
||||
<div className="header">
|
||||
<div className="title">Trigger</div>
|
||||
<button
|
||||
className="add-button"
|
||||
onClick={addTrigger}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
</div>
|
||||
<div className="trigger-list">
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={triggersContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{triggers.map((trigger: any, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`list-item ${
|
||||
selectedTrigger === trigger ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSelectedTrigger(trigger)}
|
||||
>
|
||||
<button className="value" onClick={() => {}}>
|
||||
<RenameInput value={trigger} onRename={() => {}} />
|
||||
if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') {
|
||||
actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid;
|
||||
} else if (type === 'RoboticArm') {
|
||||
actionUuid = (selectedPointData as RoboticArmPointSchema).actions[0]?.actionUuid;
|
||||
}
|
||||
|
||||
setCurrentAction(actionUuid);
|
||||
}, [selectedPointData, selectedProduct, type]);
|
||||
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productId: string,
|
||||
organization: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productId: productId,
|
||||
organization: organization,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAction || !selectedProduct) return;
|
||||
const action = getActionByUuid(selectedProduct.productId, currentAction);
|
||||
const actionTriggers = action?.triggers || [];
|
||||
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>
|
||||
{triggers.length > 1 && (
|
||||
<button
|
||||
className="remove-button"
|
||||
onClick={() => removeTrigger(index)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div className="trigger-list">
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={triggersContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<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>
|
||||
<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;
|
||||
|
|
|
@ -36,7 +36,6 @@ import {
|
|||
import useToggleStore from "../../store/useUIToggleStore";
|
||||
import {
|
||||
use3DWidget,
|
||||
useDroppedObjectsStore,
|
||||
useFloatingWidget,
|
||||
} from "../../store/visualization/useDroppedObjectsStore";
|
||||
|
||||
|
@ -57,20 +56,18 @@ const Tools: React.FC = () => {
|
|||
|
||||
const { widgets3D } = use3DWidget();
|
||||
|
||||
const zones = useDroppedObjectsStore((state) => state.zones);
|
||||
|
||||
// wall options
|
||||
const { toggleView, setToggleView } = useToggleView();
|
||||
const { setDeleteTool } = useDeleteTool();
|
||||
const { setAddAction } = useAddAction();
|
||||
const { setSelectedWallItem } = useSelectedWallItem();
|
||||
|
||||
const { transformMode, setTransformMode } = useTransformMode();
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { movePoint, setMovePoint } = useMovePoint();
|
||||
const { toolMode, setToolMode } = useToolMode();
|
||||
const { setTransformMode } = useTransformMode();
|
||||
const { setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { setMovePoint } = useMovePoint();
|
||||
const { setToolMode } = useToolMode();
|
||||
const { activeTool, setActiveTool } = useActiveTool();
|
||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
||||
const { setRefTextUpdate } = useRefTextUpdate();
|
||||
|
||||
// Reset activeTool whenever activeModule changes
|
||||
useEffect(() => {
|
||||
|
@ -209,268 +206,266 @@ const Tools: React.FC = () => {
|
|||
return (
|
||||
<>
|
||||
{!isPlaying ? (
|
||||
<>
|
||||
<div className="tools-container">
|
||||
<div className="drop-down-icons">
|
||||
<div className="activeDropicon">
|
||||
{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 className="tools-container">
|
||||
<div className="drop-down-icons">
|
||||
<div className="activeDropicon">
|
||||
{activeSubTool == "cursor" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "play" ? "active" : ""
|
||||
activeTool === "cursor" ? "active" : ""
|
||||
}`}
|
||||
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>
|
||||
{activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
</div>
|
||||
{!toggleThreeD && activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`toggle-threed-button${
|
||||
toggleThreeD ? " toggled" : ""
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-wall" ? "active" : ""
|
||||
}`}
|
||||
onClick={toggleSwitch}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-wall");
|
||||
}}
|
||||
title="Wall"
|
||||
>
|
||||
<div
|
||||
className={`toggle-option${!toggleThreeD ? " active" : ""}`}
|
||||
>
|
||||
2d
|
||||
</div>
|
||||
<div
|
||||
className={`toggle-option${toggleThreeD ? " active" : ""}`}
|
||||
>
|
||||
3d
|
||||
</div>
|
||||
<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
|
||||
className={`tool-button ${
|
||||
activeTool === "play" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsPlaying(!isPlaying);
|
||||
}}
|
||||
>
|
||||
<PlayIcon isActive={activeTool === "play"} />
|
||||
</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" && (
|
||||
<div className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||
<button className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||
X
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import { ROISummaryIcon } from "../../icons/analysis";
|
||||
import SemiCircleProgress from "./SemiCircleProgress";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
const ROISummary = ({
|
||||
roiSummaryData = {
|
||||
|
@ -121,7 +122,7 @@ const ROISummary = ({
|
|||
</div>
|
||||
|
||||
<span className={`expand-icon ${isTableOpen ? "open" : ""}`}>
|
||||
{isTableOpen ? "⌵" : "⌵"}
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</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,
|
||||
ExpandIcon,
|
||||
HourlySimulationIcon,
|
||||
InfoIcon,
|
||||
MonthlyROI,
|
||||
SpeedIcon,
|
||||
StartIcon,
|
||||
} from "../../icons/ExportCommonIcons";
|
||||
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 MAX_SPEED = 8; // Maximum speed
|
||||
|
||||
const isDragging = useRef(false);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const [expand, setExpand] = useState(true);
|
||||
const [playSimulation, setPlaySimulation] = useState(false);
|
||||
|
||||
const { speed, setSpeed } = useAnimationPlaySpeed();
|
||||
const [playSimulation, setPlaySimulation] = useState(false);
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const isDragging = useRef(false);
|
||||
const { setActiveTool } = useActiveTool();
|
||||
const { isPaused, setIsPaused } = usePauseButtonStore();
|
||||
const { isReset, setReset } = useResetButtonStore();
|
||||
const { subModule } = useSubModuleStore();
|
||||
|
||||
// Button functions
|
||||
const handleReset = () => {
|
||||
|
@ -114,7 +120,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
};
|
||||
|
||||
// Store colors for each process item
|
||||
const [processColors, setProcessColors] = useState<string[]>([]);
|
||||
const [_, setProcessColors] = useState<string[]>([]);
|
||||
|
||||
// Generate colors on mount or when process changes
|
||||
useEffect(() => {
|
||||
|
@ -159,210 +165,236 @@ const SimulationPlayer: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="simulation-player-wrapper">
|
||||
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
||||
<div className="controls-container">
|
||||
<div className="production-details">
|
||||
{/* hourlySimulation */}
|
||||
<div className="hourly-wrapper production-wrapper">
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<HourlySimulationIcon />
|
||||
<>
|
||||
<div className="simulation-player-wrapper">
|
||||
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
||||
<div className="controls-container">
|
||||
{subModule === "analysis" && (
|
||||
<div className="production-details">
|
||||
{/* hourlySimulation */}
|
||||
<div className="hourly-wrapper production-wrapper">
|
||||
<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 className="label">Hourly Simulation</div>
|
||||
</div>
|
||||
<div className="progress-wrapper">
|
||||
<div
|
||||
className="progress"
|
||||
style={{ width: hourlySimulation }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
{/* dailyProduction */}
|
||||
<div className="dailyProduction-wrapper production-wrapper">
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<DailyProductionIcon />
|
||||
{/* dailyProduction */}
|
||||
<div className="dailyProduction-wrapper production-wrapper">
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<DailyProductionIcon />
|
||||
</div>
|
||||
<div className="label">Daily Production</div>
|
||||
</div>
|
||||
<div className="progress-wrapper">
|
||||
<div
|
||||
className="progress"
|
||||
style={{ width: dailyProduction }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="label">Daily Production</div>
|
||||
</div>
|
||||
<div className="progress-wrapper">
|
||||
<div
|
||||
className="progress"
|
||||
style={{ width: dailyProduction }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
{/* monthlyROI */}
|
||||
<div className="monthlyROI-wrapper production-wrapper">
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<MonthlyROI />
|
||||
{/* monthlyROI */}
|
||||
<div className="monthlyROI-wrapper production-wrapper">
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<MonthlyROI />
|
||||
</div>
|
||||
<div className="label">Monthly ROI</div>
|
||||
</div>
|
||||
<div className="progress-wrapper">
|
||||
<div
|
||||
className="progress"
|
||||
style={{ width: monthlyROI }}
|
||||
></div>
|
||||
</div>{" "}
|
||||
</div>
|
||||
<div className="label">Monthly ROI</div>
|
||||
</div>
|
||||
<div className="progress-wrapper">
|
||||
<div className="progress" style={{ width: monthlyROI }}></div>
|
||||
</div>{" "}
|
||||
</div>
|
||||
</div>
|
||||
<div className="controls-wrapper">
|
||||
<button
|
||||
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 />
|
||||
)}
|
||||
{subModule === "simulations" && (
|
||||
<div className="header">
|
||||
<InfoIcon />
|
||||
{playSimulation
|
||||
? "Paused - system idle."
|
||||
: "Running simulation..."}
|
||||
</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">
|
||||
)}
|
||||
<div className="controls-wrapper">
|
||||
<button
|
||||
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>
|
||||
{subModule === "analysis" && (
|
||||
<button
|
||||
className={`slider-handle ${isDragging ? "dragging" : ""}`}
|
||||
style={{ left: `${calculateHandlePosition()}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
className="expand-icon-container"
|
||||
onClick={() => setExpand(!expand)}
|
||||
>
|
||||
{speed.toFixed(1)}x
|
||||
<ExpandIcon isActive={!expand} />
|
||||
</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 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 className="progresser-wrapper">
|
||||
<div className="time-displayer">
|
||||
<div className="start-time-wrappper">
|
||||
<div className="icon">
|
||||
<StartIcon />
|
||||
</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>
|
||||
{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 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 { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator';
|
||||
|
||||
async function loadInitialFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
|
@ -72,7 +71,7 @@ async function loadInitialFloorItems(
|
|||
// Check Three.js Cache
|
||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${item.modelName}`);
|
||||
//
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
|
@ -82,7 +81,7 @@ async function loadInitialFloorItems(
|
|||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||
if (indexedDBModel) {
|
||||
// console.log(`[IndexedDB] Fetching ${item.modelName}`);
|
||||
//
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
|
@ -103,7 +102,7 @@ async function loadInitialFloorItems(
|
|||
}
|
||||
|
||||
// Fetch from Backend
|
||||
// console.log(`[Backend] Fetching ${item.modelName}`);
|
||||
//
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
|
||||
loader.load(modelUrl, async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
|
@ -121,7 +120,7 @@ async function loadInitialFloorItems(
|
|||
);
|
||||
});
|
||||
} else {
|
||||
// console.log(`Item ${item.modelName} is not near`);
|
||||
//
|
||||
setFloorItems((prevItems) => [
|
||||
...(prevItems || []),
|
||||
{
|
||||
|
@ -187,7 +186,7 @@ function processLoadedModel(
|
|||
},
|
||||
]);
|
||||
|
||||
if (item.eventData.type === "vehicle") {
|
||||
if (item.eventData.type === "Vehicle") {
|
||||
const vehicleEvent: VehicleEventSchema = {
|
||||
modelUuid: item.modelUuid,
|
||||
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],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Vehicle Action",
|
||||
actionName: "Action 1",
|
||||
actionType: "travel",
|
||||
unLoadDuration: 5,
|
||||
loadCapacity: 10,
|
||||
steeringAngle:0,
|
||||
steeringAngle: 0,
|
||||
pickUpPoint: null,
|
||||
unLoadPoint: null,
|
||||
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],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Process Action",
|
||||
actionName: "Action 1",
|
||||
actionType: "process",
|
||||
processTime: 10,
|
||||
swapMaterial: "material-id",
|
||||
|
@ -279,11 +278,11 @@ function processLoadedModel(
|
|||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Pick and Place",
|
||||
actionName: "Action 1",
|
||||
actionType: "pickAndPlace",
|
||||
process: {
|
||||
startPoint: [0, 0, 0],
|
||||
endPoint: [0, 0, 0]
|
||||
startPoint: null,
|
||||
endPoint: null
|
||||
},
|
||||
triggers: []
|
||||
}
|
||||
|
|
|
@ -186,22 +186,59 @@ async function handleModelLoad(
|
|||
state: "idle",
|
||||
type: 'transfer',
|
||||
speed: 1,
|
||||
points: data.points.map((point: THREE.Vector3, index: number) => ({
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [point.x, point.y, point.z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: `Action ${index}`,
|
||||
actionType: 'default',
|
||||
material: 'Default Material',
|
||||
delay: 0,
|
||||
spawnInterval: 5,
|
||||
spawnCount: 1,
|
||||
triggers: []
|
||||
points: data.points.map((point: THREE.Vector3, index: number) => {
|
||||
const triggers: TriggerSchema[] = [];
|
||||
|
||||
if (data.points && index < data.points.length - 1) {
|
||||
triggers.push({
|
||||
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||
triggerName: `Trigger 1`,
|
||||
triggerType: "onComplete",
|
||||
delay: 0,
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: newFloorItem.modelName,
|
||||
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);
|
||||
eventData.points = ConveyorEvent.points.map(point => ({
|
||||
uuid: point.uuid,
|
||||
|
@ -228,7 +265,7 @@ async function handleModelLoad(
|
|||
actionType: "travel",
|
||||
unLoadDuration: 5,
|
||||
loadCapacity: 10,
|
||||
steeringAngle:0,
|
||||
steeringAngle: 0,
|
||||
pickUpPoint: null,
|
||||
unLoadPoint: null,
|
||||
triggers: []
|
||||
|
|
|
@ -18,7 +18,7 @@ export default async function assetManager(
|
|||
const taskId = ++currentTaskId; // Increment taskId for each call
|
||||
activePromises.set(taskId, true); // Mark task as active
|
||||
|
||||
// console.log("Received message from worker:", data);
|
||||
//
|
||||
|
||||
if (data.toRemove.length > 0) {
|
||||
data.toRemove.forEach((uuid: string) => {
|
||||
|
@ -58,7 +58,7 @@ export default async function assetManager(
|
|||
// Check Three.js Cache
|
||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${item.modelName}`);
|
||||
//
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
|
||||
return;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export default async function assetManager(
|
|||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||
if (indexedDBModel) {
|
||||
// console.log(`[IndexedDB] Fetching ${item.modelName}`);
|
||||
//
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(
|
||||
blobUrl,
|
||||
|
@ -86,7 +86,7 @@ export default async function assetManager(
|
|||
}
|
||||
|
||||
// Fetch from Backend
|
||||
// console.log(`[Backend] Fetching ${item.modelName}`);
|
||||
//
|
||||
loader.load(
|
||||
modelUrl,
|
||||
async (gltf) => {
|
||||
|
@ -114,14 +114,14 @@ export default async function assetManager(
|
|||
|
||||
const existingModel = itemsGroup?.current?.getObjectByProperty("uuid", item.modelUuid);
|
||||
if (existingModel) {
|
||||
// console.log(`Model ${item.modelName} already exists in the scene.`);
|
||||
//
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const model = gltf;
|
||||
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.position.set(...item.position);
|
||||
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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 { 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 { toast } from "react-toastify";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
@ -9,6 +9,8 @@ import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifie
|
|||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
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) {
|
||||
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||
|
@ -16,9 +18,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
|
||||
const { toggleView } = useToggleView();
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const { floorItems, setFloorItems } = useFloorItems();
|
||||
const { socket } = useSocketStore();
|
||||
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(() => {
|
||||
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
|
||||
|
@ -35,6 +56,15 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
const onPointerMove = () => {
|
||||
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) => {
|
||||
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
|
||||
|
@ -56,18 +86,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
setMovedObjects([]);
|
||||
itemsData.current = [];
|
||||
}
|
||||
setKeyEvent("")
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
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 (selectedAssets.length > 0) {
|
||||
moveAssets();
|
||||
itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid));
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCombination === "ESCAPE") {
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -90,6 +130,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||
canvasElement.addEventListener("keydown", onKeyDown);
|
||||
canvasElement?.addEventListener("keyup", onKeyUp);
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
@ -97,12 +138,11 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||
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;
|
||||
const moveSpeed = 0.25;
|
||||
const isGridSnap = false;
|
||||
let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25;
|
||||
|
||||
useFrame(() => {
|
||||
if (movedObjects.length > 0) {
|
||||
|
@ -113,10 +153,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
if (point) {
|
||||
let targetX = point.x;
|
||||
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();
|
||||
|
@ -190,10 +237,21 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
})
|
||||
}
|
||||
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],
|
||||
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;
|
||||
});
|
||||
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||
|
||||
//REST
|
||||
|
||||
// await setFloorItemApi(
|
||||
|
@ -253,6 +308,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
|||
setMovedObjects([]);
|
||||
setRotatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
setKeyEvent("")
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -8,6 +8,7 @@ import * as Types from "../../../../types/world/worldTypes";
|
|||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
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) {
|
||||
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||
|
@ -15,10 +16,28 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
|
|||
|
||||
const { toggleView } = useToggleView();
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const { floorItems, setFloorItems } = useFloorItems();
|
||||
const { socket } = useSocketStore();
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -190,10 +209,21 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
|
|||
})
|
||||
}
|
||||
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],
|
||||
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;
|
||||
});
|
||||
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||
|
||||
//REST
|
||||
|
||||
// await setFloorItemApi(
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||
import useModuleStore from "../../../../../store/useModuleStore";
|
||||
import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
|
||||
import { TransformControls } from "@react-three/drei";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import {
|
||||
useSelectedEventSphere,
|
||||
useSelectedEventData,
|
||||
useIsDragging,
|
||||
useIsRotating,
|
||||
} from "../../../../../store/simulation/useSimulationStore";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
|
||||
function PointsCreator() {
|
||||
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const transformRef = useRef<any>(null);
|
||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
||||
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
|
||||
const { selectedEventData, setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
|
||||
const { selectedEventData,setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
|
||||
const { isDragging } = useIsDragging();
|
||||
const { isRotating } = useIsRotating();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventSphere) {
|
||||
const eventData = getEventByModelUuid(
|
||||
selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
|
||||
if (eventData) {
|
||||
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
|
||||
} 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 (
|
||||
<>
|
||||
{activeModule === "simulation" && (
|
||||
|
@ -80,7 +135,10 @@ function PointsCreator() {
|
|||
{events.map((event, i) => {
|
||||
if (event.type === "transfer") {
|
||||
return (
|
||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
||||
<group
|
||||
key={i}
|
||||
position={new THREE.Vector3(...event.position)}
|
||||
>
|
||||
{event.points.map((point, j) => (
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
|
@ -94,14 +152,16 @@ function PointsCreator() {
|
|||
}}
|
||||
onPointerMissed={() => {
|
||||
if (selectedEventData?.data.type !== 'vehicle') {
|
||||
clearSelectedEventSphere();
|
||||
// clearSelectedEventSphere();
|
||||
}
|
||||
setTransformMode(null);
|
||||
}}
|
||||
key={`${i}-${j}`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
// rotation={new THREE.Euler(...point.rotation)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
|
@ -111,7 +171,10 @@ function PointsCreator() {
|
|||
);
|
||||
} else if (event.type === "vehicle") {
|
||||
return (
|
||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
||||
<group
|
||||
key={i}
|
||||
position={new THREE.Vector3(...event.position)}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
|
@ -122,13 +185,11 @@ function PointsCreator() {
|
|||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
// rotation={new THREE.Euler(...event.point.rotation)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="blue" />
|
||||
|
@ -137,7 +198,10 @@ function PointsCreator() {
|
|||
);
|
||||
} else if (event.type === "roboticArm") {
|
||||
return (
|
||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
||||
<group
|
||||
key={i}
|
||||
position={new THREE.Vector3(...event.position)}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
|
@ -149,12 +213,13 @@ function PointsCreator() {
|
|||
);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
// rotation={new THREE.Euler(...event.point.rotation)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="green" />
|
||||
|
@ -163,7 +228,10 @@ function PointsCreator() {
|
|||
);
|
||||
} else if (event.type === "machine") {
|
||||
return (
|
||||
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
|
||||
<group
|
||||
key={i}
|
||||
position={new THREE.Vector3(...event.position)}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
|
@ -175,12 +243,14 @@ function PointsCreator() {
|
|||
);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
clearSelectedEventSphere();
|
||||
// clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
// rotation={new THREE.Euler(...event.point.rotation)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="purple" />
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<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 MachineInstance from './machineInstance/machineInstance'
|
||||
import { useMachineStore } from '../../../../store/simulation/useMachineStore';
|
||||
|
||||
function MachineInstances() {
|
||||
const { machines } = useMachineStore();
|
||||
return (
|
||||
<>
|
||||
|
||||
<MachineInstance />
|
||||
{machines.map((val: MachineStatus) => (
|
||||
<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 { useMachineStore } from '../../../store/simulation/useMachineStore'
|
||||
import { useSelectedProduct } from '../../../store/simulation/useSimulationStore';
|
||||
|
||||
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 (
|
||||
<>
|
||||
|
||||
|
|
|
@ -53,7 +53,17 @@ function AddOrRemoveEventsInProducts() {
|
|||
const canvasElement = gl.domElement;
|
||||
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) {
|
||||
let currentObject = intersects[0].object;
|
||||
|
||||
|
@ -116,6 +126,7 @@ function AddOrRemoveEventsInProducts() {
|
|||
};
|
||||
|
||||
}, [gl, subModule, selectedProduct, selectedAsset]);
|
||||
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProd
|
|||
import { getAllProductsApi } from '../../../services/simulation/getallProductsApi';
|
||||
|
||||
function Products() {
|
||||
const { products, addProduct, setProducts } = useProductStore();
|
||||
const { addProduct, setProducts } = useProductStore();
|
||||
const { setSelectedProduct } = useSelectedProduct();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -27,9 +27,6 @@ function Products() {
|
|||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
|
|
|
@ -1,68 +1,220 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import * as THREE from "three"
|
||||
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { Line } from '@react-three/drei';
|
||||
import {
|
||||
useAnimationPlaySpeed,
|
||||
usePauseButtonStore,
|
||||
usePlayButtonStore,
|
||||
useResetButtonStore
|
||||
} from '../../../../../store/usePlayButtonStore';
|
||||
|
||||
function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) {
|
||||
const { armBots } = useArmBotStore();
|
||||
const { scene } = useThree();
|
||||
const restSpeed = 0.1;
|
||||
const restPosition = new THREE.Vector3(0, 1, -1.6);
|
||||
const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null);
|
||||
const initialStartPositionRef = useRef<THREE.Vector3 | null>(null);
|
||||
const [initialProgress, setInitialProgress] = useState(0);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [needsInitialMovement, setNeedsInitialMovement] = useState(true);
|
||||
const [isInitializing, setIsInitializing] = useState(true);
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const statusRef = useRef("idle");
|
||||
// Create a ref for initialProgress
|
||||
const initialProgressRef = useRef(0);
|
||||
function RoboticArmAnimator({
|
||||
HandleCallback,
|
||||
restPosition,
|
||||
ikSolver,
|
||||
targetBone,
|
||||
armBot,
|
||||
logStatus,
|
||||
path
|
||||
}: any) {
|
||||
const progressRef = useRef(0);
|
||||
const curveRef = useRef<THREE.Vector3[] | null>(null);
|
||||
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(() => {
|
||||
setCurrentPath(path);
|
||||
}, [path]);
|
||||
|
||||
setCurrentPath(path)
|
||||
}, [path])
|
||||
|
||||
// Reset logic when `isPlaying` changes
|
||||
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) => {
|
||||
if (!ikSolver || !currentPath || currentPath.length === 0) return;
|
||||
if (!ikSolver) return;
|
||||
|
||||
const bone = ikSolver.mesh.skeleton.bones.find(
|
||||
(b: any) => b.name === targetBone
|
||||
);
|
||||
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||
if (!bone) return;
|
||||
|
||||
// Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it
|
||||
const curve = new THREE.CatmullRomCurve3(
|
||||
currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2]))
|
||||
);
|
||||
if (isPlaying) {
|
||||
if (!isPaused && customCurvePoints && currentPath.length > 0) {
|
||||
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;
|
||||
if (next >= 1) {
|
||||
// bone.position.copy(restPosition);
|
||||
HandleCallback(); // Call the callback when the path is completed
|
||||
initialProgressRef.current = 0; // Set ref to 1 when done
|
||||
} 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();
|
||||
} else if (!isPlaying && currentPath.length === 0) {
|
||||
// Not playing anymore, reset to rest
|
||||
bone.position.copy(restPosition);
|
||||
ikSolver.update();
|
||||
}
|
||||
|
||||
ikSolver.update();
|
||||
});
|
||||
|
||||
|
||||
|
||||
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;
|
|
@ -1,61 +1,141 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import IKInstance from '../ikInstance/ikInstance';
|
||||
import RoboticArmAnimator from '../animator/roboticArmAnimator';
|
||||
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
||||
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useFloorItems } from '../../../../../store/store';
|
||||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
import { Vector3 } 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 { 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 [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(() => {
|
||||
let armItems = floorItems?.filter((val: any) =>
|
||||
val.modelUuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d"
|
||||
);
|
||||
// Get the first matching item
|
||||
let armItem = armItems?.[0];
|
||||
if (armItem) {
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", armItem.modelUuid);
|
||||
if (targetMesh) {
|
||||
targetMesh.visible = activeModule !== "simulation"
|
||||
}
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
|
||||
if (targetMesh) {
|
||||
targetMesh.visible = activeModule !== "simulation"
|
||||
}
|
||||
|
||||
const targetBones = ikSolver?.mesh.skeleton.bones.find(
|
||||
(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) {
|
||||
//Moving armBot from initial point to rest position.
|
||||
if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") {
|
||||
|
||||
setArmBotActive(robot.modelUuid, true)
|
||||
setArmBotState(robot.modelUuid, "running")
|
||||
//Moving armBot from initial point to rest position.
|
||||
if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") {
|
||||
|
||||
setArmBotActive(armBot.modelUuid, true)
|
||||
setArmBotState(armBot.modelUuid, "running")
|
||||
setCurrentPhase("init-to-rest");
|
||||
if (targetBones) {
|
||||
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]));
|
||||
}
|
||||
}
|
||||
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.
|
||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) {
|
||||
logStatus(robot.modelUuid, "Waiting to trigger CurrentAction")
|
||||
setTimeout(() => {
|
||||
addCurrentAction(robot.modelUuid, 'action-003');
|
||||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) {
|
||||
logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction")
|
||||
const timeoutId = setTimeout(() => {
|
||||
addCurrentAction(armBot.modelUuid, selectedAction?.actionId);
|
||||
console.log('selectedAction?.actionId: ', selectedAction?.actionId);
|
||||
}, 3000);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) {
|
||||
if (robot.currentAction) {
|
||||
setArmBotActive(robot.modelUuid, true);
|
||||
setArmBotState(robot.modelUuid, "running");
|
||||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) {
|
||||
if (armBot.currentAction) {
|
||||
|
||||
setArmBotActive(armBot.modelUuid, true);
|
||||
setArmBotState(armBot.modelUuid, "running");
|
||||
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) {
|
||||
let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
|
||||
if (curve) {
|
||||
|
@ -85,103 +170,107 @@ 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) {
|
||||
setArmBotActive(robot.modelUuid, true);
|
||||
setArmBotState(robot.modelUuid, "running");
|
||||
setCurrentPhase("start-to-end");
|
||||
const startPoint = robot.point.actions[0].process.startPoint;
|
||||
const endPoint = robot.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) {
|
||||
setTimeout(() => {
|
||||
logStatus(robot.modelUuid, "picking the object");
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
logStatus(robot.modelUuid, "Moving armBot from start point to end position.")
|
||||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) {
|
||||
requestAnimationFrame(firstFrame);
|
||||
// setArmBotActive(armBot.modelUuid, true);
|
||||
// setArmBotState(armBot.modelUuid, "running");
|
||||
// setCurrentPhase("start-to-end");
|
||||
// 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) {
|
||||
// setTimeout(() => {
|
||||
// logStatus(armBot.modelUuid, "picking the object");
|
||||
// setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
// }, 1500)
|
||||
// }
|
||||
// }
|
||||
// logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
|
||||
}
|
||||
else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) {
|
||||
setArmBotActive(robot.modelUuid, true);
|
||||
setArmBotState(robot.modelUuid, "running");
|
||||
setCurrentPhase("end-to-rest");
|
||||
const endPoint = robot.point.actions[0].process.endPoint;
|
||||
if (endPoint) {
|
||||
let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition
|
||||
);
|
||||
if (curve) {
|
||||
setTimeout(() => {
|
||||
logStatus(robot.modelUuid, "dropping the object");
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
logStatus(robot.modelUuid, "Moving armBot from end point to rest position.")
|
||||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) {
|
||||
requestAnimationFrame(firstFrame);
|
||||
// setArmBotActive(armBot.modelUuid, true);
|
||||
// setArmBotState(armBot.modelUuid, "running");
|
||||
// setCurrentPhase("end-to-rest");
|
||||
// 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) {
|
||||
// setTimeout(() => {
|
||||
// logStatus(armBot.modelUuid, "dropping the object");
|
||||
// setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
// }, 1500)
|
||||
// }
|
||||
// }
|
||||
// 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) {
|
||||
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
|
||||
mid.y += 0.5;
|
||||
|
||||
// mid.y += 0.5;
|
||||
const points = [p1, mid, p2];
|
||||
return new THREE.CatmullRomCurve3(points);
|
||||
}
|
||||
|
||||
|
||||
const HandleCallback = () => {
|
||||
if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") {
|
||||
logStatus(robot.modelUuid, "Callback triggered: rest");
|
||||
setArmBotActive(robot.modelUuid, false)
|
||||
setArmBotState(robot.modelUuid, "idle")
|
||||
if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") {
|
||||
logStatus(armBot.modelUuid, "Callback triggered: rest");
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("rest");
|
||||
setPath([])
|
||||
}
|
||||
else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") {
|
||||
logStatus(robot.modelUuid, "Callback triggered: pick.");
|
||||
setArmBotActive(robot.modelUuid, false)
|
||||
setArmBotState(robot.modelUuid, "idle")
|
||||
else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") {
|
||||
logStatus(armBot.modelUuid, "Callback triggered: pick.");
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("picking");
|
||||
setPath([])
|
||||
}
|
||||
else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") {
|
||||
logStatus(robot.modelUuid, "Callback triggered: drop.");
|
||||
setArmBotActive(robot.modelUuid, false)
|
||||
setArmBotState(robot.modelUuid, "idle")
|
||||
else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") {
|
||||
logStatus(armBot.modelUuid, "Callback triggered: drop.");
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("dropping");
|
||||
setPath([])
|
||||
}
|
||||
else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") {
|
||||
logStatus(robot.modelUuid, "Callback triggered: rest, cycle completed.");
|
||||
setArmBotActive(robot.modelUuid, false)
|
||||
setArmBotState(robot.modelUuid, "idle")
|
||||
else if (armBot.isActive && armBot.state == "running" && currentPhase == "end-to-rest") {
|
||||
logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed.");
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("rest");
|
||||
setPath([])
|
||||
removeCurrentAction(robot.modelUuid)
|
||||
removeCurrentAction(armBot.modelUuid)
|
||||
}
|
||||
}
|
||||
const logStatus = (id: string, status: string) => {
|
||||
// console.log(id + "," + status);
|
||||
console.log( status);
|
||||
//
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IKInstance modelUrl={armModel} setIkSolver={setIkSolver} ikSolver={ikSolver} robot={robot} groupRef={groupRef} processes={processes}
|
||||
setArmBotCurvePoints={setArmBotCurvePoints} />
|
||||
<RoboticArmAnimator armUuid={robot?.modelUuid} HandleCallback={HandleCallback}
|
||||
currentPhase={currentPhase} targetBone={targetBone} ikSolver={ikSolver} robot={robot}
|
||||
logStatus={logStatus} groupRef={groupRef} processes={processes} armBotCurveRef={armBotCurveRef} path={path} />
|
||||
|
||||
<IKInstance modelUrl={armModel} setIkSolver={setIkSolver} ikSolver={ikSolver} armBot={armBot} groupRef={groupRef} />
|
||||
<RoboticArmAnimator HandleCallback={HandleCallback} restPosition={restPosition} ikSolver={ikSolver} targetBone={targetBone} armBot={armBot}
|
||||
logStatus={logStatus} path={path} currentPhase={currentPhase} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,20 +3,19 @@ import * as THREE from "three";
|
|||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
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 { TransformControls } from '@react-three/drei';
|
||||
|
||||
type IKInstanceProps = {
|
||||
modelUrl: string;
|
||||
ikSolver: any;
|
||||
setIkSolver: any
|
||||
robot: any;
|
||||
groupRef: React.RefObject<THREE.Group>;
|
||||
processes: any;
|
||||
setArmBotCurvePoints: any
|
||||
armBot: any;
|
||||
groupRef: any;
|
||||
};
|
||||
function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) {
|
||||
|
||||
const { scene } = useThree();
|
||||
function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) {
|
||||
const { scene } = useThree()
|
||||
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
|
||||
const draco = new DRACOLoader();
|
||||
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 targetBoneName = "Target";
|
||||
const skinnedMeshName = "link_0";
|
||||
const [selectedArm, setSelectedArm] = useState<THREE.Group>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!gltf) return;
|
||||
const OOI: any = {};
|
||||
|
@ -66,15 +67,19 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
|
|||
setIkSolver(solver);
|
||||
|
||||
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]);
|
||||
|
||||
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
|
||||
uuid={"ArmBot-X200"}
|
||||
object={cloned}
|
||||
|
@ -82,6 +87,7 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
|
|||
name={`arm-bot11`}
|
||||
/>
|
||||
</group>
|
||||
{/* {selectedArm && <TransformControls object={selectedArm} />} */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from 'react'
|
||||
import RoboticArmInstance from './armInstance/roboticArmInstance';
|
||||
import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
|
||||
|
||||
|
@ -8,9 +7,8 @@ function RoboticArmInstances() {
|
|||
return (
|
||||
<>
|
||||
{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 { useArmBotStore } from "../../../store/simulation/useArmBotStore";
|
||||
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() {
|
||||
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 armBotStatusSample: RoboticArmEventSchema[] = [
|
||||
{
|
||||
state: "idle",
|
||||
modelUuid: "3abf5d46-b59e-4e6b-9c02-a4634b64b82d",
|
||||
modelUuid: "8790b4d5-fb6e-49e0-8161-04945fe3fdc4",
|
||||
modelName: "ArmBot-X200",
|
||||
position: [0.20849215906958463, 0, 0.32079278127773675],
|
||||
position: [4.317833205016363, 0, -3.2829924989068715],
|
||||
rotation: [-1.3768690876192207e-15, 1.4883085074751308, 1.5407776675834467e-15],
|
||||
type: "roboticArm",
|
||||
speed: 1.5,
|
||||
|
@ -29,19 +37,9 @@ function RoboticArm() {
|
|||
process: {
|
||||
startPoint: [-1, 2, 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: [
|
||||
{
|
||||
triggerUuid: "trigger-001",
|
||||
|
@ -95,7 +93,7 @@ function RoboticArm() {
|
|||
position: [95.94347308985614, 0, 6.742905194869091],
|
||||
rotation: [0, 0, 0],
|
||||
type: "roboticArm",
|
||||
speed: 1.5,
|
||||
speed: 0.1,
|
||||
point: {
|
||||
uuid: "point-123",
|
||||
position: [0, 1.5, 0],
|
||||
|
@ -106,8 +104,10 @@ function RoboticArm() {
|
|||
actionName: "Pick Component",
|
||||
actionType: "pickAndPlace",
|
||||
process: {
|
||||
startPoint: [2.52543010919071, 0, 8.433681161200905],
|
||||
endPoint: [95.3438373267953, 0, 9.0279187421610025],
|
||||
// startPoint: [2.52543010919071, 0, 8.433681161200905],
|
||||
// endPoint: [95.3438373267953, 0, 9.0279187421610025],
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
},
|
||||
triggers: [
|
||||
{
|
||||
|
@ -158,20 +158,32 @@ function RoboticArm() {
|
|||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProduct.productId) {
|
||||
const product = getProductById(selectedProduct.productId);
|
||||
if (product) {
|
||||
clearArmBots();
|
||||
product.eventDatas.forEach(events => {
|
||||
if (events.type === 'roboticArm') {
|
||||
addArmBot(selectedProduct.productId, events);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [selectedProduct, products]);
|
||||
|
||||
removeArmBot(armBotStatusSample[0].modelUuid);
|
||||
addArmBot('123', armBotStatusSample[0]);
|
||||
// addArmBot('123', armBotStatusSample[1]);
|
||||
// addCurrentAction('armbot-xyz-001', 'action-001');
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
}, [armBots])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [armBots]);
|
||||
}, [selectedEventData, selectedEventSphere, isPlaying]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RoboticArmInstances />
|
||||
{selectedEventSphere && selectedEventData?.data.type === "roboticArm" &&
|
||||
< ArmBotUI />
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,20 +23,20 @@ function Simulation() {
|
|||
}, [events])
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('products: ', products);
|
||||
console.log('products: ', products);
|
||||
}, [products])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<Products />
|
||||
|
||||
{activeModule === 'simulation' &&
|
||||
|
||||
<>
|
||||
|
||||
<Points />
|
||||
|
||||
<Products />
|
||||
|
||||
<Materials />
|
||||
|
||||
<Trigger />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { useSubModuleStore } from "../../../../store/useModuleStore";
|
||||
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
|
||||
|
@ -7,21 +7,28 @@ import { useProductStore } from "../../../../store/simulation/useProductStore";
|
|||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
||||
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 {
|
||||
id: string;
|
||||
start: THREE.Vector3;
|
||||
end: THREE.Vector3;
|
||||
mid: THREE.Vector3;
|
||||
startPointUuid: string;
|
||||
endPointUuid: string;
|
||||
trigger: TriggerSchema;
|
||||
}
|
||||
|
||||
function TriggerConnector() {
|
||||
const { gl, raycaster, scene } = useThree();
|
||||
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||
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 { 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<{
|
||||
productId: string;
|
||||
|
@ -32,52 +39,99 @@ function TriggerConnector() {
|
|||
|
||||
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(() => {
|
||||
const newConnections: ConnectionLine[] = [];
|
||||
|
||||
products.forEach(product => {
|
||||
product.eventDatas.forEach(event => {
|
||||
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
|
||||
);
|
||||
const product = getProductById(selectedProduct.productId);
|
||||
if (!product || products.length === 0) return;
|
||||
|
||||
if (targetPoint) {
|
||||
const startPos = new THREE.Vector3(...point.position);
|
||||
const endPos = new THREE.Vector3(...targetPoint.position);
|
||||
const midPos = new THREE.Vector3()
|
||||
.addVectors(startPos, endPos)
|
||||
.multiplyScalar(0.5)
|
||||
.add(new THREE.Vector3(0, 2, 0));
|
||||
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${targetPoint.uuid}-${trigger.triggerUuid}`,
|
||||
start: startPos,
|
||||
end: endPos,
|
||||
mid: midPos,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
}
|
||||
product.eventDatas.forEach(event => {
|
||||
// Handle Conveyor points
|
||||
if (event.type === "transfer" && 'points' in event) {
|
||||
event.points.forEach(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 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);
|
||||
}, [products]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('connections: ', connections);
|
||||
}, connections)
|
||||
}, [products, selectedProduct.productId]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
@ -111,15 +165,31 @@ function TriggerConnector() {
|
|||
if (drag) return;
|
||||
evt.preventDefault();
|
||||
|
||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||||
if (intersects.length === 0) return;
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter(
|
||||
(intersect) =>
|
||||
intersect.object.name === ('Event-Sphere')
|
||||
);
|
||||
if (intersects.length === 0) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
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 pointUuid = currentObject.userData.pointUuid;
|
||||
|
||||
if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedProduct && getIsEventInProduct(selectedProduct.productId, modelUuid)) {
|
||||
|
||||
const point = getPointByUuid(
|
||||
|
@ -128,7 +198,12 @@ function TriggerConnector() {
|
|||
pointUuid
|
||||
);
|
||||
|
||||
if (!point) return;
|
||||
const event = getEventByModelUuid(selectedProduct.productId, modelUuid);
|
||||
|
||||
if (!point || !event) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
let actionUuid: string | undefined;
|
||||
if ('action' in point && point.action) {
|
||||
|
@ -152,12 +227,12 @@ function TriggerConnector() {
|
|||
delay: 0,
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: currentObject.parent?.parent?.name || 'Unknown',
|
||||
modelName: event.modelName || 'Unknown',
|
||||
modelUuid: modelUuid
|
||||
},
|
||||
triggeredPoint: {
|
||||
pointName: currentObject.name,
|
||||
pointUuid: pointUuid
|
||||
pointName: 'Point',
|
||||
pointUuid: point.uuid
|
||||
},
|
||||
triggeredAction: actionUuid ? {
|
||||
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
|
||||
|
@ -167,7 +242,16 @@ function TriggerConnector() {
|
|||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -184,7 +268,12 @@ function TriggerConnector() {
|
|||
pointUuid
|
||||
);
|
||||
|
||||
if (!point) return;
|
||||
const event = getEventByModelUuid(selectedProduct.productId, modelUuid);
|
||||
|
||||
if (!point || !event) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
let actionUuid: string | undefined;
|
||||
if ('action' in point && point.action) {
|
||||
|
@ -200,12 +289,12 @@ function TriggerConnector() {
|
|||
delay: 0,
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: currentObject.parent?.parent?.name || 'Unknown',
|
||||
modelName: event.modelName || 'Unknown',
|
||||
modelUuid: modelUuid
|
||||
},
|
||||
triggeredPoint: {
|
||||
pointName: currentObject.name,
|
||||
pointUuid: pointUuid
|
||||
pointName: 'Point',
|
||||
pointUuid: point.uuid
|
||||
},
|
||||
triggeredAction: actionUuid ? {
|
||||
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
|
||||
|
@ -215,13 +304,24 @@ function TriggerConnector() {
|
|||
};
|
||||
|
||||
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);
|
||||
} else if (firstSelectedPoint) {
|
||||
setFirstSelectedPoint(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (subModule === 'simulations') {
|
||||
if (subModule === 'simulations' && !deleteTool) {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
|
@ -235,11 +335,149 @@ function TriggerConnector() {
|
|||
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 (
|
||||
<>
|
||||
</>
|
||||
<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";
|
||||
actionUuid: string;
|
||||
gltfScene: THREE.Group;
|
||||
selectedPoint: THREE.Mesh | null;
|
||||
|
||||
handlePointerDown: (e: ThreeEvent<PointerEvent>) => void;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
@ -21,12 +21,10 @@ const PickDropPoints: React.FC<PickDropProps> = ({
|
|||
actionType,
|
||||
actionUuid,
|
||||
gltfScene,
|
||||
selectedPoint,
|
||||
handlePointerDown,
|
||||
isSelected,
|
||||
}) => {
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
|
||||
return (
|
||||
<group
|
||||
ref={groupRef}
|
||||
|
@ -36,15 +34,24 @@ const PickDropPoints: React.FC<PickDropProps> = ({
|
|||
: new THREE.Vector3(0, 0, 0)
|
||||
}
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation(); // Important to prevent event bubbling
|
||||
|
||||
e.stopPropagation(); // Prevent event bubbling
|
||||
if (!isSelected) return;
|
||||
handlePointerDown(e);
|
||||
}}
|
||||
userData={{ modelUuid, pointUuid, actionType, actionUuid }}
|
||||
>
|
||||
<primitive
|
||||
object={gltfScene.clone()}
|
||||
position={[0, 0, 0]} // Ensure this stays at origin
|
||||
object={(() => {
|
||||
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]}
|
||||
/>
|
||||
</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 { ThreeEvent, useThree } from "@react-three/fiber";
|
||||
|
||||
type OnUpdateCallback = (object: THREE.Object3D) => void;
|
||||
|
||||
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 planeRef = useRef<THREE.Plane>(
|
||||
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 pointer = new THREE.Vector2();
|
||||
const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3());
|
||||
|
||||
const handlePointerDown = (e: ThreeEvent<PointerEvent>) => {
|
||||
e.stopPropagation();
|
||||
|
@ -34,9 +35,9 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
|||
activeObjRef.current = obj;
|
||||
initialPositionRef.current.copy(obj.position);
|
||||
|
||||
|
||||
// Get world position
|
||||
const objectWorldPos = new THREE.Vector3();
|
||||
obj.getWorldPosition(objectWorldPos);
|
||||
setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
|
||||
|
||||
// Set plane at the object's Y level
|
||||
planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
|
||||
|
@ -75,27 +76,30 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
|||
|
||||
// Create a vector to store intersection point
|
||||
const intersection = new THREE.Vector3();
|
||||
const intersects = raycaster.ray.intersectPlane(planeRef.current, intersection);
|
||||
const intersects = raycaster.ray.intersectPlane(
|
||||
planeRef.current,
|
||||
intersection
|
||||
);
|
||||
if (!intersects) return;
|
||||
|
||||
// Add offset for dragging
|
||||
intersection.add(offsetRef.current);
|
||||
console.log('intersection: ', intersection);
|
||||
|
||||
// Get the parent's world matrix if exists
|
||||
const parent = activeObjRef.current.parent;
|
||||
const targetPosition = new THREE.Vector3();
|
||||
|
||||
// OnPointerDown
|
||||
initialPositionRef.current.copy(objectWorldPos);
|
||||
|
||||
// OnPointerMove
|
||||
if (isShiftKeyPressed) {
|
||||
console.log('isShiftKeyPressed: ', isShiftKeyPressed);
|
||||
// For Y-axis only movement, maintain original X and Z
|
||||
console.log('initialPositionRef: ', initialPositionRef);
|
||||
console.log('intersection.y: ', intersection);
|
||||
targetPosition.set(
|
||||
initialPositionRef.current.x,
|
||||
intersection.y,
|
||||
initialPositionRef.current.z
|
||||
);
|
||||
const { x: initialX, y: initialY } = initialPositionRef.current;
|
||||
const { x: objectX, z: objectZ } = objectWorldPos;
|
||||
|
||||
const deltaX = intersection.x - initialX;
|
||||
|
||||
targetPosition.set(objectX, initialY + deltaX, objectZ);
|
||||
} else {
|
||||
// For free movement
|
||||
targetPosition.copy(intersection);
|
||||
|
@ -107,6 +111,7 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
|||
}
|
||||
|
||||
// Update object position
|
||||
|
||||
activeObjRef.current.position.copy(targetPosition);
|
||||
};
|
||||
|
||||
|
@ -126,6 +131,3 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
|||
|
||||
return { handlePointerDown };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import startPoint from "../../../../assets/gltf-glb/arrow_green.glb";
|
||||
import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
|
||||
import * as THREE from "three";
|
||||
import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
|
||||
import { useGLTF } from '@react-three/drei';
|
||||
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 * 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 { scene: startScene } = useGLTF(startPoint) as any;
|
||||
const { scene: endScene } = useGLTF(startEnd) as any;
|
||||
|
@ -14,67 +18,60 @@ const VehicleUI = () => {
|
|||
const endMarker = useRef<THREE.Group>(null);
|
||||
const prevMousePos = useRef({ x: 0, y: 0 });
|
||||
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 [endPosition, setEndPosition] = 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 [isDragging, setIsDragging] = useState<"start" | "end" | null>(null);
|
||||
const [isRotating, setIsRotating] = useState<"start" | "end" | null>(null);
|
||||
const { isDragging, setIsDragging } = useIsDragging();
|
||||
const { isRotating, setIsRotating } = useIsRotating();
|
||||
const { raycaster } = useThree();
|
||||
const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0));
|
||||
const state: Types.ThreeState = useThree();
|
||||
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(() => {
|
||||
if (!selectedEventSphere) return;
|
||||
const selectedVehicle = vehicles.find(
|
||||
(vehicle: any) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
|
||||
|
||||
if (selectedVehicle?.point?.action) {
|
||||
const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action;
|
||||
|
||||
if (pickUpPoint) {
|
||||
const pickupPosition = new THREE.Vector3(
|
||||
pickUpPoint.position.x,
|
||||
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]);
|
||||
setStartPosition([pickUpPoint.position.x, 0, pickUpPoint.position.z]);
|
||||
setStartRotation([pickUpPoint.rotation.x, pickUpPoint.rotation.y, pickUpPoint.rotation.z]);
|
||||
} else {
|
||||
const defaultLocal = new THREE.Vector3(0, 0, 1.5);
|
||||
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
|
||||
defaultWorld.y = 0;
|
||||
setStartPosition([defaultWorld.x, 0, defaultWorld.z]);
|
||||
setStartRotation([0, 0, 0]);
|
||||
}
|
||||
|
||||
if (unLoadPoint) {
|
||||
const unLoadPosition = new THREE.Vector3(
|
||||
unLoadPoint.position.x,
|
||||
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]);
|
||||
setEndPosition([unLoadPoint.position.x, 0, unLoadPoint.position.z]);
|
||||
setEndRotation([unLoadPoint.rotation.x, unLoadPoint.rotation.y, unLoadPoint.rotation.z]);
|
||||
} else {
|
||||
const defaultLocal = new THREE.Vector3(0, 0, -1.5);
|
||||
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
|
||||
defaultWorld.y = 0; // Force y to 0
|
||||
setEndPosition([defaultWorld.x, 0, defaultWorld.z]);
|
||||
setEndRotation([0, 0, 0]);
|
||||
}
|
||||
|
@ -87,7 +84,6 @@ const VehicleUI = () => {
|
|||
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
|
||||
|
||||
if (intersects) {
|
||||
intersectPoint.y = 0; // Force y to 0
|
||||
if (isDragging === "start") {
|
||||
setStartPosition([intersectPoint.x, 0, intersectPoint.z]);
|
||||
}
|
||||
|
@ -108,12 +104,12 @@ const VehicleUI = () => {
|
|||
|
||||
if (marker) {
|
||||
const rotationSpeed = 10;
|
||||
marker.rotation.y += deltaX * rotationSpeed;
|
||||
if (isRotating === 'start') {
|
||||
setStartRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
|
||||
const y = startRotation[1] + deltaX * rotationSpeed;
|
||||
setStartRotation([0, y, 0]);
|
||||
} else {
|
||||
|
||||
setEndRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
|
||||
const y = endRotation[1] + deltaX * rotationSpeed;
|
||||
setEndRotation([0, y, 0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -142,43 +138,34 @@ const VehicleUI = () => {
|
|||
setIsRotating(null);
|
||||
|
||||
if (selectedEventSphere?.userData.modelUuid) {
|
||||
const updatedVehicle = vehicles.find(
|
||||
(vehicle) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
const updatedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
|
||||
|
||||
if (updatedVehicle) {
|
||||
updateVehicle(selectedEventSphere.userData.modelUuid, {
|
||||
const event = updateEvent(selectedProduct.productId, selectedEventSphere.userData.modelUuid, {
|
||||
point: {
|
||||
...updatedVehicle.point,
|
||||
action: {
|
||||
...updatedVehicle.point?.action,
|
||||
pickUpPoint: {
|
||||
position: {
|
||||
x: startPosition[0],
|
||||
y: startPosition[1],
|
||||
z: startPosition[2],
|
||||
},
|
||||
rotation: {
|
||||
x: startRotation[0],
|
||||
y: startRotation[1],
|
||||
z: startRotation[2],
|
||||
},
|
||||
position: { x: startPosition[0], y: startPosition[1], z: startPosition[2], },
|
||||
rotation: { x: 0, y: startRotation[1], z: 0, },
|
||||
},
|
||||
unLoadPoint: {
|
||||
position: {
|
||||
x: endPosition[0],
|
||||
y: endPosition[1],
|
||||
z: endPosition[2],
|
||||
},
|
||||
rotation: {
|
||||
x: endRotation[0],
|
||||
y: endRotation[1],
|
||||
z: endRotation[2],
|
||||
},
|
||||
position: { x: endPosition[0], y: endPosition[1], z: endPosition[2], },
|
||||
rotation: { x: 0, y: endRotation[1], z: 0, },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -208,6 +195,7 @@ const VehicleUI = () => {
|
|||
object={startScene}
|
||||
ref={startMarker}
|
||||
position={startPosition}
|
||||
rotation={startRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
e.stopPropagation();
|
||||
handlePointerDown(e, "start", "start");
|
||||
|
@ -224,6 +212,7 @@ const VehicleUI = () => {
|
|||
object={endScene}
|
||||
ref={endMarker}
|
||||
position={endPosition}
|
||||
rotation={endRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
e.stopPropagation();
|
||||
handlePointerDown(e, "end", "end");
|
||||
|
|
|
@ -11,7 +11,7 @@ interface VehicleAnimatorProps {
|
|||
reset: () => void;
|
||||
currentPhase: string;
|
||||
agvUuid: number;
|
||||
agvDetail: any;
|
||||
agvDetail: VehicleStatus;
|
||||
}
|
||||
|
||||
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 isPausedRef = useRef<boolean>(false);
|
||||
const pauseTimeRef = useRef<number | null>(null);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const { scene } = useThree();
|
||||
let startTime: number;
|
||||
let fixedInterval: number;
|
||||
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(() => {
|
||||
|
@ -66,11 +66,12 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||
setReset(false);
|
||||
setRestingRotation(true);
|
||||
decrementVehicleLoad(agvDetail.modelUuid, 0);
|
||||
isPausedRef.current = false;
|
||||
pauseTimeRef.current = 0;
|
||||
const object = scene.getObjectByProperty('uuid', agvUuid);
|
||||
console.log('currentPhase: ', currentPhase);
|
||||
if (object) {
|
||||
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])
|
||||
|
@ -132,7 +133,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
if (restRotation) {
|
||||
if (restRotation && objectRotation) {
|
||||
const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z));
|
||||
object.quaternion.slerp(targetQuaternion, delta * 2);
|
||||
const angleDiff = object.quaternion.angleTo(targetQuaternion);
|
||||
|
|
|
@ -30,7 +30,8 @@ function VehicleInstance({ agvDetail }: any) {
|
|||
);
|
||||
|
||||
function vehicleStatus(modelId: string, status: string) {
|
||||
// console.log(`${modelId} , ${status});
|
||||
// console.log(`${modelId} , ${status}`);
|
||||
|
||||
}
|
||||
|
||||
// Function to reset everything
|
||||
|
@ -44,7 +45,7 @@ function VehicleInstance({ agvDetail }: any) {
|
|||
const increment = () => {
|
||||
if (isIncrememtable.current) {
|
||||
|
||||
incrementVehicleLoad(agvDetail.modelUuid, 2);
|
||||
incrementVehicleLoad(agvDetail.modelUuid, 10);
|
||||
isIncrememtable.current = false;
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +70,7 @@ function VehicleInstance({ agvDetail }: any) {
|
|||
increment();
|
||||
}, 5000);
|
||||
|
||||
|
||||
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) {
|
||||
const toDrop = computePath(
|
||||
agvDetail.point.action.pickUpPoint.position,
|
||||
|
|
|
@ -9,10 +9,8 @@ function VehicleInstances() {
|
|||
return (
|
||||
<>
|
||||
|
||||
{vehicles.map((val: any, i: any) =>
|
||||
|
||||
<VehicleInstance agvDetail={val} key={i} />
|
||||
|
||||
{vehicles.map((val: VehicleStatus) =>
|
||||
<VehicleInstance agvDetail={val} key={val.modelUuid} />
|
||||
)}
|
||||
|
||||
</>
|
||||
|
|
|
@ -1,220 +1,48 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import VehicleInstances from "./instances/vehicleInstances";
|
||||
import { useVehicleStore } from "../../../store/simulation/useVehicleStore";
|
||||
import { useFloorItems } from "../../../store/store";
|
||||
import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
|
||||
import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import VehicleUI from "../ui/vehicle/vehicleUI";
|
||||
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 { selectedEventData } = useSelectedEventData();
|
||||
const { floorItems } = useFloorItems();
|
||||
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(() => {
|
||||
// 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])
|
||||
|
||||
useEffect(() => {
|
||||
addVehicle("123", vehicleStatusSample[0]);
|
||||
addVehicle('123', vehicleStatusSample[1]);
|
||||
// addVehicle('123', vehicleStatusSample[2]);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<VehicleInstances />
|
||||
|
||||
{selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying &&
|
||||
< VehicleUI />
|
||||
}
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Vehicles;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -66,8 +66,7 @@ const RealTimeVisulization: React.FC = () => {
|
|||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
|
||||
const { setRightSelect } = useRightSelected();
|
||||
const { editWidgetOptions, setEditWidgetOptions } =
|
||||
useEditWidgetOptionsStore();
|
||||
const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore();
|
||||
const { rightClickSelected, setRightClickSelected } = useRightClickSelected();
|
||||
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
|
||||
const { setFloatingWidget } = useFloatingWidget();
|
||||
|
@ -76,8 +75,6 @@ const RealTimeVisulization: React.FC = () => {
|
|||
const { setSelectedChartId } = useWidgetStore();
|
||||
const [waitingPanels, setWaitingPanels] = useState(null);
|
||||
|
||||
console.log("waitingPanels: ", waitingPanels);
|
||||
|
||||
OuterClick({
|
||||
contextClassName: [
|
||||
"chart-container",
|
||||
|
@ -145,109 +142,109 @@ const RealTimeVisulization: React.FC = () => {
|
|||
});
|
||||
}, [selectedZone]);
|
||||
|
||||
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
// const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
||||
// 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 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 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;
|
||||
// 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;
|
||||
// // 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;
|
||||
// // Center the widget at cursor
|
||||
// const centerOffsetX = widgetWidth / 2;
|
||||
// const centerOffsetY = widgetHeight / 2;
|
||||
|
||||
const adjustedX = relativeX - centerOffsetX;
|
||||
const adjustedY = relativeY - centerOffsetY;
|
||||
// const adjustedX = relativeX - centerOffsetX;
|
||||
// const adjustedY = relativeY - centerOffsetY;
|
||||
|
||||
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
||||
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||
// const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
||||
// const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||
|
||||
let finalY = 0;
|
||||
let finalX = 0;
|
||||
// let finalY = 0;
|
||||
// let finalX = 0;
|
||||
|
||||
if (activeProp1 === "top") {
|
||||
finalY = adjustedY;
|
||||
} else {
|
||||
finalY = rect.height - (adjustedY + widgetHeight);
|
||||
}
|
||||
// if (activeProp1 === "top") {
|
||||
// finalY = adjustedY;
|
||||
// } else {
|
||||
// finalY = rect.height - (adjustedY + widgetHeight);
|
||||
// }
|
||||
|
||||
if (activeProp2 === "left") {
|
||||
finalX = adjustedX;
|
||||
} else {
|
||||
finalX = rect.width - (adjustedX + widgetWidth);
|
||||
}
|
||||
// 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));
|
||||
// // 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 boundedPosition = {
|
||||
// ...finalPosition,
|
||||
// [activeProp1]: finalY,
|
||||
// [activeProp2]: finalX,
|
||||
// [activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
||||
// [activeProp2 === "left" ? "right" : "left"]: "auto",
|
||||
// };
|
||||
|
||||
const newObject = {
|
||||
...droppedData,
|
||||
id: generateUniqueId(),
|
||||
position: boundedPosition,
|
||||
};
|
||||
// const newObject = {
|
||||
// ...droppedData,
|
||||
// id: generateUniqueId(),
|
||||
// position: boundedPosition,
|
||||
// };
|
||||
|
||||
const existingZone =
|
||||
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||
if (!existingZone) {
|
||||
useDroppedObjectsStore
|
||||
.getState()
|
||||
.setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||
}
|
||||
// const existingZone =
|
||||
// useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||
// if (!existingZone) {
|
||||
// useDroppedObjectsStore
|
||||
// .getState()
|
||||
// .setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||
// }
|
||||
|
||||
const addFloatingWidget = {
|
||||
organization,
|
||||
widget: newObject,
|
||||
zoneId: selectedZone.zoneId,
|
||||
};
|
||||
// const addFloatingWidget = {
|
||||
// organization,
|
||||
// widget: newObject,
|
||||
// zoneId: selectedZone.zoneId,
|
||||
// };
|
||||
|
||||
if (visualizationSocket) {
|
||||
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
||||
}
|
||||
// if (visualizationSocket) {
|
||||
// visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
||||
// }
|
||||
|
||||
useDroppedObjectsStore
|
||||
.getState()
|
||||
.addObject(selectedZone.zoneName, newObject);
|
||||
// useDroppedObjectsStore
|
||||
// .getState()
|
||||
// .addObject(selectedZone.zoneName, newObject);
|
||||
|
||||
const droppedObjectsStore = useDroppedObjectsStore.getState();
|
||||
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
// 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);
|
||||
// }
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
|
@ -304,16 +301,7 @@ const RealTimeVisulization: React.FC = () => {
|
|||
}
|
||||
`}
|
||||
</style>
|
||||
<div
|
||||
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 ref={containerRef} className="realTime-viz">
|
||||
<div className="realTime-viz-wrapper">
|
||||
{openConfirmationPopup && (
|
||||
<RenderOverlay>
|
||||
|
@ -324,20 +312,6 @@ const RealTimeVisulization: React.FC = () => {
|
|||
/>
|
||||
</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 !== "" && (
|
||||
<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,
|
||||
useZones,
|
||||
useLoadingProgress,
|
||||
useWidgetSubOption,
|
||||
} from "../store/store";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
||||
|
@ -22,12 +23,19 @@ import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
|||
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
||||
import { useSelectedUserStore } from "../store/useCollabStore";
|
||||
import FollowPerson from "../components/templates/FollowPerson";
|
||||
import ProductionCapacity from "../components/ui/analysis/ProductionCapacity";
|
||||
import ThroughputSummary from "../components/ui/analysis/ThroughputSummary";
|
||||
import ROISummary from "../components/ui/analysis/ROISummary";
|
||||
import Scene from "../modules/scene/scene";
|
||||
import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
|
||||
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 = () => {
|
||||
let navigate = useNavigate();
|
||||
const echo = useLogger();
|
||||
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setUserName } = useUserName();
|
||||
|
@ -50,42 +58,79 @@ const Project: React.FC = () => {
|
|||
setOrganization(Organization);
|
||||
setUserName(name);
|
||||
}
|
||||
echo.info("Log in success full");
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
// global store
|
||||
const { toggleThreeD } = useThreeDStore();
|
||||
|
||||
// simulation store
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
// collaboration store
|
||||
const { selectedUser } = useSelectedUserStore();
|
||||
const { isLogListVisible } = useLogger();
|
||||
|
||||
// real-time visualization store
|
||||
const { widgetSubOption } = useWidgetSubOption();
|
||||
const { visualizationSocket } = useSocketStore();
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { setFloatingWidget } = useFloatingWidget();
|
||||
|
||||
return (
|
||||
<div className="project-main">
|
||||
{/* <div className="analysis">
|
||||
<div className="analysis-wrapper">
|
||||
<ProductionCapacity />
|
||||
<ThroughputSummary />
|
||||
</div>
|
||||
<ROISummary />
|
||||
</div> */}
|
||||
<KeyPressListener />
|
||||
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
||||
{!isPlaying && (
|
||||
{!selectedUser && (
|
||||
<>
|
||||
{toggleThreeD && <ModuleToggle />}
|
||||
<SideBarLeft />
|
||||
<SideBarRight />
|
||||
<KeyPressListener />
|
||||
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
||||
{!isPlaying && (
|
||||
<>
|
||||
{toggleThreeD && <ModuleToggle />}
|
||||
<SideBarLeft />
|
||||
<SideBarRight />
|
||||
</>
|
||||
)}
|
||||
<RealTimeVisulization />
|
||||
{activeModule === "market" && <MarketPlace />}
|
||||
{activeModule !== "market" && <Tools />}
|
||||
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
||||
</>
|
||||
)}
|
||||
{/* <RenderOverlay>
|
||||
<MenuBar setOpenMenu={setOpenMenu} />
|
||||
</RenderOverlay> */}
|
||||
{activeModule === "market" && <MarketPlace />}
|
||||
<RealTimeVisulization />
|
||||
{activeModule !== "market" && <Tools />}
|
||||
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
||||
{/* {<SimulationPlayer />} */}
|
||||
<div
|
||||
className="scene-container"
|
||||
id="real-time-vis-canvas"
|
||||
style={{
|
||||
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
|
||||
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
||||
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
||||
borderRadius:
|
||||
isPlaying || activeModule !== "visualization" ? "" : "6px",
|
||||
}}
|
||||
role="application"
|
||||
onDrop={(event) =>
|
||||
createHandleDrop({
|
||||
widgetSubOption,
|
||||
visualizationSocket,
|
||||
selectedZone,
|
||||
setFloatingWidget,
|
||||
event,
|
||||
})
|
||||
}
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
>
|
||||
<Scene />
|
||||
</div>
|
||||
{selectedUser && <FollowPerson />}
|
||||
{isLogListVisible && (
|
||||
<RenderOverlay>
|
||||
<LogList />
|
||||
</RenderOverlay>
|
||||
)}
|
||||
<Footer />
|
||||
</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,
|
||||
updates: Partial<Omit<ArmBotStatus, 'modelUuid' | 'productId'>>
|
||||
) => void;
|
||||
clearArmBots: () => void;
|
||||
|
||||
addCurrentAction: (modelUuid: string, actionUuid: string) => void;
|
||||
removeCurrentAction: (modelUuid: string) => void;
|
||||
|
@ -39,14 +40,17 @@ export const useArmBotStore = create<ArmBotStore>()(
|
|||
|
||||
addArmBot: (productId, event) => {
|
||||
set((state) => {
|
||||
state.armBots.push({
|
||||
...event,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
const exists = state.armBots.some(a => a.modelUuid === event.modelUuid);
|
||||
if (!exists) {
|
||||
state.armBots.push({
|
||||
...event,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -65,6 +69,12 @@ export const useArmBotStore = create<ArmBotStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
clearArmBots: () => {
|
||||
set((state) => {
|
||||
state.armBots = [];
|
||||
});
|
||||
},
|
||||
|
||||
addCurrentAction: (modelUuid, actionUuid) => {
|
||||
set((state) => {
|
||||
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
||||
|
|
|
@ -10,6 +10,7 @@ interface ConveyorStore {
|
|||
modelUuid: string,
|
||||
updates: Partial<Omit<ConveyorStatus, 'modelUuid' | 'productId'>>
|
||||
) => void;
|
||||
clearConveyors: () => void;
|
||||
|
||||
setConveyorActive: (modelUuid: string, isActive: boolean) => void;
|
||||
setConveyorState: (modelUuid: string, newState: ConveyorStatus['state']) => void;
|
||||
|
@ -30,14 +31,17 @@ export const useConveyorStore = create<ConveyorStore>()(
|
|||
|
||||
addConveyor: (productId, event) => {
|
||||
set((state) => {
|
||||
state.conveyors.push({
|
||||
...event,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
const exists = state.conveyors.some(c => c.modelUuid === event.modelUuid);
|
||||
if (!exists) {
|
||||
state.conveyors.push({
|
||||
...event,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -56,6 +60,12 @@ export const useConveyorStore = create<ConveyorStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
clearConveyors: () => {
|
||||
set((state) => {
|
||||
state.conveyors = [];
|
||||
});
|
||||
},
|
||||
|
||||
setConveyorActive: (modelUuid, isActive) => {
|
||||
set((state) => {
|
||||
const conveyor = state.conveyors.find(c => c.modelUuid === modelUuid);
|
||||
|
|
|
@ -7,7 +7,7 @@ type EventsStore = {
|
|||
// Event-level actions
|
||||
addEvent: (event: EventsSchema) => void;
|
||||
removeEvent: (modelUuid: string) => void;
|
||||
updateEvent: (modelUuid: string, updates: Partial<EventsSchema>) => void;
|
||||
updateEvent: (modelUuid: string, updates: Partial<EventsSchema>) => EventsSchema | undefined;
|
||||
|
||||
// Point-level actions
|
||||
addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void;
|
||||
|
@ -49,7 +49,9 @@ export const useEventsStore = create<EventsStore>()(
|
|||
// Event-level actions
|
||||
addEvent: (event) => {
|
||||
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) => {
|
||||
let updatedEvent: EventsSchema | undefined;
|
||||
set((state) => {
|
||||
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||
if (event) {
|
||||
Object.assign(event, updates);
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
}
|
||||
});
|
||||
return updatedEvent;
|
||||
},
|
||||
|
||||
// Point-level actions
|
||||
|
@ -73,9 +78,14 @@ export const useEventsStore = create<EventsStore>()(
|
|||
set((state) => {
|
||||
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||
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) {
|
||||
(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);
|
||||
if (event && 'points' in event) {
|
||||
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;
|
||||
}
|
||||
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
|
||||
if ('action' in (event as any).point) {
|
||||
(event as any).point.action = action;
|
||||
} else if ('actions' in (event as any).point) {
|
||||
(event as any).point.actions.push(action);
|
||||
const point = (event as any).point;
|
||||
if ('action' in point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
|
||||
point.action = 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) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
point.action.triggers.push(trigger);
|
||||
if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
|
||||
point.action.triggers.push(trigger);
|
||||
}
|
||||
return;
|
||||
} else if ('actions' in point) {
|
||||
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
||||
if (action) {
|
||||
const action = (point as RoboticArmPointSchema).actions.find((a) => a.actionUuid === actionUuid);
|
||||
if (action && !action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
|
||||
action.triggers.push(trigger);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -4,23 +4,23 @@ import { immer } from 'zustand/middleware/immer';
|
|||
interface MachineStore {
|
||||
machines: MachineStatus[];
|
||||
|
||||
// Actions
|
||||
addMachine: (productId: string, machine: MachineEventSchema) => void;
|
||||
removeMachine: (modelUuid: string) => void;
|
||||
updateMachine: (
|
||||
modelUuid: string,
|
||||
updates: Partial<Omit<MachineStatus, 'modelUuid' | 'productId'>>
|
||||
) => void;
|
||||
clearMachines: () => void;
|
||||
|
||||
addCurrentAction: (modelUuid: string, actionUuid: string) => void;
|
||||
removeCurrentAction: (modelUuid: string) => void;
|
||||
|
||||
// Status updates
|
||||
setMachineActive: (modelUuid: string, isActive: boolean) => void;
|
||||
setMachineState: (modelUuid: string, newState: MachineStatus['state']) => void;
|
||||
|
||||
// Time tracking
|
||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
||||
|
||||
// Helpers
|
||||
getMachineById: (modelUuid: string) => MachineStatus | undefined;
|
||||
getMachinesByProduct: (productId: string) => MachineStatus[];
|
||||
getMachinesBystate: (state: string) => MachineStatus[];
|
||||
|
@ -32,17 +32,19 @@ export const useMachineStore = create<MachineStore>()(
|
|||
immer((set, get) => ({
|
||||
machines: [],
|
||||
|
||||
// Actions
|
||||
addMachine: (productId, machine) => {
|
||||
set((state) => {
|
||||
state.machines.push({
|
||||
...machine,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
const exists = state.machines.some(m => m.modelUuid === machine.modelUuid);
|
||||
if (!exists) {
|
||||
state.machines.push({
|
||||
...machine,
|
||||
productId,
|
||||
isActive: false,
|
||||
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) => {
|
||||
set((state) => {
|
||||
const machine = state.machines.find(m => m.modelUuid === modelUuid);
|
||||
|
@ -80,7 +111,6 @@ export const useMachineStore = create<MachineStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Time tracking
|
||||
incrementActiveTime: (modelUuid, incrementBy) => {
|
||||
set((state) => {
|
||||
const machine = state.machines.find(m => m.modelUuid === modelUuid);
|
||||
|
@ -99,7 +129,6 @@ export const useMachineStore = create<MachineStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Helpers
|
||||
getMachineById: (modelUuid) => {
|
||||
return get().machines.find(m => m.modelUuid === modelUuid);
|
||||
},
|
||||
|
|
|
@ -33,27 +33,30 @@ type ProductsStore = {
|
|||
pointUuid: string,
|
||||
action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']
|
||||
) => EventsSchema | undefined;
|
||||
removeAction: (actionUuid: string) => EventsSchema | undefined;
|
||||
removeAction: (productId: string, actionUuid: string) => EventsSchema | undefined;
|
||||
updateAction: (
|
||||
productId: string,
|
||||
actionUuid: string,
|
||||
updates: Partial<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']>
|
||||
) => EventsSchema | undefined;
|
||||
|
||||
// Trigger-level actions
|
||||
addTrigger: (
|
||||
productId: string,
|
||||
actionUuid: string,
|
||||
trigger: TriggerSchema
|
||||
) => void;
|
||||
removeTrigger: (triggerUuid: string) => void;
|
||||
) => EventsSchema | undefined;
|
||||
removeTrigger: (productId: string, triggerUuid: string) => EventsSchema | undefined;
|
||||
updateTrigger: (
|
||||
productId: string,
|
||||
triggerUuid: string,
|
||||
updates: Partial<TriggerSchema>
|
||||
) => void;
|
||||
) => EventsSchema | undefined;
|
||||
|
||||
// Renaming functions
|
||||
renameProduct: (productId: string, newName: string) => void;
|
||||
renameAction: (actionUuid: string, newName: string) => EventsSchema | undefined;
|
||||
renameTrigger: (triggerUuid: string, newName: string) => void;
|
||||
renameAction: (productId: string, actionUuid: string, newName: string) => EventsSchema | undefined;
|
||||
renameTrigger: (productId: string, triggerUuid: string, newName: string) => void;
|
||||
|
||||
// Helper functions
|
||||
getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined;
|
||||
|
@ -71,12 +74,15 @@ export const useProductStore = create<ProductsStore>()(
|
|||
// Product-level actions
|
||||
addProduct: (productName, productId) => {
|
||||
set((state) => {
|
||||
const newProduct = {
|
||||
productName,
|
||||
productId: productId,
|
||||
eventDatas: []
|
||||
};
|
||||
state.products.push(newProduct);
|
||||
const existingProduct = state.products.find(p => p.productId === productId);
|
||||
if (!existingProduct) {
|
||||
const newProduct = {
|
||||
productName,
|
||||
productId: productId,
|
||||
eventDatas: []
|
||||
};
|
||||
state.products.push(newProduct);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -106,7 +112,10 @@ export const useProductStore = create<ProductsStore>()(
|
|||
set((state) => {
|
||||
const product = state.products.find(p => p.productId === productId);
|
||||
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) => {
|
||||
for (const product of state.products) {
|
||||
product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid);
|
||||
|
@ -150,9 +159,15 @@ export const useProductStore = create<ProductsStore>()(
|
|||
if (product) {
|
||||
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
|
||||
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) {
|
||||
(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);
|
||||
if (event && 'points' in event) {
|
||||
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;
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
}
|
||||
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
|
||||
if ('action' in (event as any).point) {
|
||||
(event as any).point.action = action;
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
if (!(event as any).point.action || (event as any).point.action.actionUuid !== action.actionUuid) {
|
||||
(event as any).point.action = action;
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
}
|
||||
} else if ('actions' in (event as any).point) {
|
||||
(event as any).point.actions.push(action);
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
const existingAction = (event as any).point.actions.find((a: any) => a.actionUuid === action.actionUuid);
|
||||
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;
|
||||
},
|
||||
|
||||
removeAction: (actionUuid: string) => {
|
||||
removeAction: (productId, actionUuid) => {
|
||||
let updatedEvent: EventsSchema | undefined;
|
||||
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) {
|
||||
if ('points' in event) {
|
||||
// Handle ConveyorEventSchema
|
||||
|
@ -248,10 +269,11 @@ export const useProductStore = create<ProductsStore>()(
|
|||
return updatedEvent;
|
||||
},
|
||||
|
||||
updateAction: (actionUuid, updates) => {
|
||||
updateAction: (productId, actionUuid, updates) => {
|
||||
let updatedEvent: EventsSchema | undefined;
|
||||
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) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
|
@ -283,26 +305,40 @@ export const useProductStore = create<ProductsStore>()(
|
|||
},
|
||||
|
||||
// Trigger-level actions
|
||||
addTrigger: (actionUuid, trigger) => {
|
||||
addTrigger: (productId, actionUuid, trigger) => {
|
||||
let updatedEvent: EventsSchema | undefined;
|
||||
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) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else if ('point' in event) {
|
||||
const point = (event as any).point;
|
||||
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;
|
||||
} else if ('actions' in point) {
|
||||
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -310,26 +346,41 @@ export const useProductStore = create<ProductsStore>()(
|
|||
}
|
||||
}
|
||||
});
|
||||
return updatedEvent;
|
||||
},
|
||||
|
||||
removeTrigger: (triggerUuid) => {
|
||||
removeTrigger: (productId, triggerUuid) => {
|
||||
let updatedEvent: EventsSchema | undefined;
|
||||
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) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
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) {
|
||||
const point = (event as any).point;
|
||||
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) {
|
||||
for (const action of point.actions) {
|
||||
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) => {
|
||||
for (const product of state.products) {
|
||||
const product = state.products.find(p => p.productId === productId);
|
||||
if (product) {
|
||||
for (const event of product.eventDatas) {
|
||||
if ('points' in event) {
|
||||
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);
|
||||
if (trigger) {
|
||||
Object.assign(trigger, updates);
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -359,6 +414,7 @@ export const useProductStore = create<ProductsStore>()(
|
|||
const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||
if (trigger) {
|
||||
Object.assign(trigger, updates);
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
return;
|
||||
}
|
||||
} else if ('actions' in point) {
|
||||
|
@ -367,6 +423,7 @@ export const useProductStore = create<ProductsStore>()(
|
|||
const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||
if (trigger) {
|
||||
Object.assign(trigger, updates);
|
||||
updatedEvent = JSON.parse(JSON.stringify(event));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -376,6 +433,7 @@ export const useProductStore = create<ProductsStore>()(
|
|||
}
|
||||
}
|
||||
});
|
||||
return updatedEvent;
|
||||
},
|
||||
|
||||
// Renaming functions
|
||||
|
@ -388,10 +446,11 @@ export const useProductStore = create<ProductsStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
renameAction: (actionUuid, newName) => {
|
||||
renameAction: (productId, actionUuid, newName) => {
|
||||
let updatedEvent: EventsSchema | undefined;
|
||||
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) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
|
@ -422,9 +481,10 @@ export const useProductStore = create<ProductsStore>()(
|
|||
return updatedEvent;
|
||||
},
|
||||
|
||||
renameTrigger: (triggerUuid, newName) => {
|
||||
renameTrigger: (productId, triggerUuid, newName) => {
|
||||
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) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
|
|
|
@ -115,3 +115,35 @@ 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 {
|
||||
storageUnits: StorageUnitStatus[];
|
||||
|
||||
// Actions
|
||||
addStorageUnit: (productId: string, storageUnit: StorageEventSchema) => void;
|
||||
removeStorageUnit: (modelUuid: string) => void;
|
||||
updateStorageUnit: (
|
||||
modelUuid: string,
|
||||
updates: Partial<Omit<StorageUnitStatus, 'modelUuid' | 'productId'>>
|
||||
) => void;
|
||||
clearStorageUnits: () => void;
|
||||
|
||||
// Status updates
|
||||
setStorageUnitActive: (modelUuid: string, isActive: boolean) => void;
|
||||
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
|
||||
|
||||
// Load updates
|
||||
updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void;
|
||||
|
||||
// Time tracking
|
||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
||||
|
||||
// Helpers
|
||||
getStorageUnitById: (modelUuid: string) => StorageUnitStatus | undefined;
|
||||
getStorageUnitsByProduct: (productId: string) => StorageUnitStatus[];
|
||||
getStorageUnitsBystate: (state: string) => StorageUnitStatus[];
|
||||
|
@ -37,18 +33,20 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
|||
immer((set, get) => ({
|
||||
storageUnits: [],
|
||||
|
||||
// Actions
|
||||
addStorageUnit: (productId, storageUnit) => {
|
||||
set((state) => {
|
||||
state.storageUnits.push({
|
||||
...storageUnit,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
currentLoad: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
const exists = state.storageUnits.some(s => s.modelUuid === storageUnit.modelUuid);
|
||||
if (!exists) {
|
||||
state.storageUnits.push({
|
||||
...storageUnit,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
currentLoad: 0,
|
||||
state: 'idle',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -67,7 +65,12 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Status updates
|
||||
clearStorageUnits: () => {
|
||||
set(() => ({
|
||||
storageUnits: [],
|
||||
}));
|
||||
},
|
||||
|
||||
setStorageUnitActive: (modelUuid, isActive) => {
|
||||
set((state) => {
|
||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||
|
@ -86,7 +89,6 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Load updates
|
||||
updateStorageUnitLoad: (modelUuid, incrementBy) => {
|
||||
set((state) => {
|
||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||
|
@ -96,7 +98,6 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Time tracking
|
||||
incrementActiveTime: (modelUuid, incrementBy) => {
|
||||
set((state) => {
|
||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||
|
@ -115,7 +116,6 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Helpers
|
||||
getStorageUnitById: (modelUuid) => {
|
||||
return get().storageUnits.find(s => s.modelUuid === modelUuid);
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ interface VehiclesStore {
|
|||
modelUuid: string,
|
||||
updates: Partial<Omit<VehicleStatus, 'modelUuid' | 'productId'>>
|
||||
) => void;
|
||||
clearvehicles: () => void;
|
||||
|
||||
setVehicleActive: (modelUuid: string, isActive: boolean) => void;
|
||||
updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void;
|
||||
|
@ -41,15 +42,18 @@ export const useVehicleStore = create<VehiclesStore>()(
|
|||
|
||||
addVehicle: (productId, event) => {
|
||||
set((state) => {
|
||||
state.vehicles.push({
|
||||
...event,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
currentLoad: 0,
|
||||
distanceTraveled: 0,
|
||||
});
|
||||
const exists = state.vehicles.some(v => v.modelUuid === event.modelUuid);
|
||||
if (!exists) {
|
||||
state.vehicles.push({
|
||||
...event,
|
||||
productId,
|
||||
isActive: false,
|
||||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
currentLoad: 0,
|
||||
distanceTraveled: 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -68,6 +72,12 @@ export const useVehicleStore = create<VehiclesStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
clearvehicles: () => {
|
||||
set((state) => {
|
||||
state.vehicles = [];
|
||||
});
|
||||
},
|
||||
|
||||
setVehicleActive: (modelUuid, isActive) => {
|
||||
set((state) => {
|
||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { create } from "zustand";
|
||||
import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets";
|
||||
import { useSocketStore } from "../store";
|
||||
import useChartStore from "./useChartStore";
|
||||
|
||||
|
|
|
@ -22,7 +22,11 @@ $text-button-color-dark: #f3f3fd;
|
|||
// background colors
|
||||
// ---------- light mode ----------
|
||||
$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-secondary: #fcfdfd4d;
|
||||
$background-color-accent: #6f42c1;
|
||||
|
@ -45,7 +49,11 @@ $background-radial-gray-gradient: radial-gradient(
|
|||
|
||||
// ---------- dark mode ----------
|
||||
$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-secondary-dark: #19191d99;
|
||||
$background-color-accent-dark: #6f42c1;
|
||||
|
@ -104,6 +112,21 @@ $color3: #b186ff;
|
|||
$color4: #8752e8;
|
||||
$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
|
||||
$accent-color: #6f42c1;
|
||||
$accent-color-dark: #c4abf1;
|
||||
|
|
|
@ -37,11 +37,9 @@
|
|||
|
||||
// old colors
|
||||
--accent-color: #{$accent-color};
|
||||
--highlight-accent-color: #{$highlight-accent-color};
|
||||
--accent-gradient-color: #{$acent-gradient};
|
||||
--faint-gradient-color: #{$faint-gradient};
|
||||
--background-color-gray: #{$background-color-gray};
|
||||
--border-color: #{$border-color};
|
||||
--shadow-main-light: #{$shadow-color};
|
||||
--box-shadow-light: 0px 2px 4px 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};
|
||||
|
||||
// border colors
|
||||
--border-color: #{$border-color};
|
||||
--border-color: #{$border-color-dark};
|
||||
--input-border-color: #{$input-border-color-dark};
|
||||
--border-color-accent: #{$border-color-accent-dark};
|
||||
|
||||
|
@ -89,11 +87,9 @@
|
|||
|
||||
// old colors
|
||||
--accent-color: #{$accent-color-dark};
|
||||
--highlight-accent-color: #{$highlight-accent-color-dark};
|
||||
--accent-gradient-color: #{$acent-gradient-dark};
|
||||
--faint-gradient-color: #{$faint-gradient-dark};
|
||||
--background-color-gray: #{$background-color-gray-dark};
|
||||
--border-color: #{$border-color-dark};
|
||||
--shadow-main-dark: #{$shadow-color};
|
||||
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark);
|
||||
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark);
|
||||
|
|
|
@ -1,11 +1,30 @@
|
|||
@use "../abstracts/variables" as *;
|
||||
@use "../abstracts/mixins" as *;
|
||||
|
||||
section, .section{
|
||||
padding: 4px;
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
border-radius: #{$border-radius-large};
|
||||
background: var(--background-color);
|
||||
margin: 4px 0;
|
||||
section,
|
||||
.section {
|
||||
padding: 4px;
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
border-radius: #{$border-radius-large};
|
||||
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 */
|
||||
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 {
|
||||
margin-bottom: 6px;
|
||||
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 {
|
||||
@include flex-center;
|
||||
@include flex-space-between;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
|
||||
.header{
|
||||
@include flex-center;
|
||||
gap: 6px;
|
||||
padding: 0 8px;
|
||||
svg{
|
||||
scale: 1.3;
|
||||
}
|
||||
}
|
||||
.production-details,
|
||||
.controls-wrapper {
|
||||
@include flex-center;
|
||||
|
@ -72,7 +79,7 @@
|
|||
|
||||
.expand-icon-container {
|
||||
@include flex-center;
|
||||
padding: 6px 8px;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -307,6 +314,22 @@
|
|||
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 {
|
||||
bottom: 4px;
|
||||
left: 16px;
|
||||
|
@ -347,6 +370,12 @@
|
|||
}
|
||||
|
||||
.simulation-player-container.open {
|
||||
|
||||
.start-displayer,
|
||||
.end-displayer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timmer {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
transition: width 0.2s;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: #{$z-index-default};
|
||||
z-index: 2;
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
|
||||
|
@ -124,6 +124,8 @@
|
|||
padding: 4px;
|
||||
border-radius: #{$border-radius-medium};
|
||||
background: var(--background-color);
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
gap: 5px;
|
||||
position: relative;
|
||||
|
||||
|
|
|
@ -58,12 +58,8 @@
|
|||
fill: var(--icon-default-color-active);
|
||||
}
|
||||
&:hover {
|
||||
rect {
|
||||
stroke: var(--icon-default-color);
|
||||
}
|
||||
circle {
|
||||
fill: var(--icon-default-color);
|
||||
}
|
||||
filter: saturate(0.8);
|
||||
background: var(--background-color-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -420,7 +416,7 @@
|
|||
outline: none;
|
||||
path {
|
||||
stroke: var(--text-button-color);
|
||||
stroke-width: 1.3;
|
||||
strokeWidth: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -686,7 +682,7 @@
|
|||
|
||||
path {
|
||||
stroke: var(--accent-color);
|
||||
stroke-width: 1.5px;
|
||||
strokeWidth: 1.5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -714,10 +710,10 @@
|
|||
|
||||
.add-button {
|
||||
@include flex-center;
|
||||
padding: 2px 4px;
|
||||
padding: 4px 8px;
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
border-radius: #{$border-radius-small};
|
||||
border-radius: #{$border-radius-large};
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: none;
|
||||
|
@ -832,10 +828,10 @@
|
|||
transform: translateX(4px);
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-color);
|
||||
background: var(--background-color-accent);
|
||||
|
||||
path {
|
||||
stroke: var(--primary-color);
|
||||
stroke: var(--text-button-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1003,10 +999,10 @@
|
|||
border-radius: 8px 0 0 8px;
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-color);
|
||||
background: var(--background-color-accent);
|
||||
|
||||
path {
|
||||
stroke: var(--primary-color);
|
||||
stroke: var(--text-button-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1067,7 +1063,13 @@
|
|||
.dropdown-content-container {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.value-field-container {
|
||||
padding: 6px;
|
||||
.dropdown {
|
||||
min-width: 44px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.input-range-container {
|
||||
.input-container {
|
||||
width: 75%;
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
@use 'components/button';
|
||||
@use 'components/form';
|
||||
@use 'components/input';
|
||||
@use 'components/layouts';
|
||||
@use 'components/lists';
|
||||
@use 'components/moduleToggle';
|
||||
@use 'components/templates';
|
||||
|
@ -22,11 +21,12 @@
|
|||
@use 'components/visualization/ui/styledWidgets';
|
||||
@use 'components/visualization/floating/common';
|
||||
@use 'components/marketPlace/marketPlace';
|
||||
@use 'components/simulation/simulation';
|
||||
@use 'components/menu/menu';
|
||||
@use 'components/confirmationPopUp';
|
||||
@use 'components/analysis/analysis';
|
||||
@use 'components/analysis/ROISummary.scss';
|
||||
@use 'components/simulation/simulation';
|
||||
@use 'components/simulation/analysis';
|
||||
@use 'components/logs/logs';
|
||||
@use 'components/footer/footer.scss';
|
||||
|
||||
// layout
|
||||
@use 'layout/loading';
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
// Main Container
|
||||
.realTime-viz {
|
||||
background: #131313;
|
||||
box-shadow: $box-shadow-medium;
|
||||
width: calc(100% - (320px + 270px + 90px));
|
||||
height: calc(100% - (250px));
|
||||
position: absolute;
|
||||
|
@ -12,8 +10,8 @@
|
|||
left: calc(270px + 45px);
|
||||
transform: translate(0, -50%);
|
||||
border-radius: #{$border-radius-medium};
|
||||
transition: all 0.2s;
|
||||
z-index: #{$z-index-default};
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
|
||||
.realTime-viz-wrapper {
|
||||
width: 100%;
|
||||
|
@ -39,10 +37,6 @@
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
.scene-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -74,6 +68,8 @@
|
|||
z-index: 3;
|
||||
transform: translate(-50%, -10%);
|
||||
transition: transform 0.5s linear;
|
||||
pointer-events: all;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -367,6 +363,7 @@
|
|||
border-radius: 2px;
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
pointer-events: all;
|
||||
|
||||
svg {
|
||||
stroke: var(--icon-default-color) !important;
|
||||
|
@ -426,10 +423,8 @@
|
|||
|
||||
path {
|
||||
stroke: #f65648;
|
||||
stroke-width: 1.3;
|
||||
strokeWidth: 1.3;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -778,17 +773,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.panel-content {
|
||||
background: var(--background-color);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* RIGHT */
|
||||
.panel-content.right-opening {
|
||||
animation: rightExpand 0.5s ease-in-out forwards;
|
||||
|
@ -913,9 +901,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Add button
|
||||
|
||||
.extra-Bs-addopening {
|
||||
|
@ -926,7 +911,6 @@
|
|||
animation: slideUp 0.3s ease forwards;
|
||||
}
|
||||
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
|
@ -13,7 +13,7 @@ interface TriggerSchema {
|
|||
delay: number;
|
||||
triggeredAsset: {
|
||||
triggeredModel: { modelName: string, modelUuid: string };
|
||||
triggeredPoint: { pointName: string, pointUuid: string };
|
||||
triggeredPoint: { pointName: string, pointUuid: string } | null;
|
||||
triggeredAction: { actionName: string, actionUuid: string } | null;
|
||||
} | null;
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ interface StoragePointSchema {
|
|||
actionType: "store";
|
||||
materials: { materialName: string; materialId: string; }[];
|
||||
storageCapacity: number;
|
||||
triggers: TriggerSchema[];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -143,6 +144,10 @@ interface MachineStatus extends MachineEventSchema {
|
|||
isActive: boolean;
|
||||
idleTime: number;
|
||||
activeTime: number;
|
||||
currentAction?: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
};
|
||||
}
|
||||
|
||||
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