Merge branch 'main' into v2
This commit is contained in:
commit
b85ff07a15
|
@ -1,25 +1,7 @@
|
|||
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 />;
|
||||
}
|
||||
};
|
||||
import { GetLogIcon } from "./getLogIcons";
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const { logs, setIsLogListVisible } = useLogger();
|
||||
|
@ -43,15 +25,22 @@ const Footer: React.FC = () => {
|
|||
</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 className="bg-dummy left-top"></div>
|
||||
<div className="bg-dummy right-bottom"></div>
|
||||
<div className="log-container">
|
||||
<button
|
||||
className={`logs-detail ${lastLog ? lastLog.type : ""}`}
|
||||
onClick={() => setIsLogListVisible(true)}
|
||||
>
|
||||
{lastLog ? (
|
||||
<>
|
||||
<span className="log-icon">{GetLogIcon(lastLog.type)}</span>
|
||||
<span className="log-message">{lastLog.message}</span>
|
||||
</>
|
||||
) : (
|
||||
"There are no logs to display at the moment."
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="version">
|
||||
V 0.01
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { ErrorIcon, LogIcon, LogInfoIcon, SucessIcon, WarningIcon } from "../icons/LogIcons";
|
||||
import { LogType } from "../ui/log/LoggerContext";
|
||||
|
||||
export const GetLogIcon = (type: LogType): JSX.Element => {
|
||||
switch (type) {
|
||||
case "info":
|
||||
return <LogInfoIcon />;
|
||||
case "log":
|
||||
return <LogIcon />;
|
||||
case "error":
|
||||
return <ErrorIcon />;
|
||||
case "warning":
|
||||
return <WarningIcon />;
|
||||
case "success":
|
||||
return <SucessIcon />;
|
||||
default:
|
||||
return <LogIcon />;
|
||||
}
|
||||
};
|
|
@ -826,7 +826,7 @@ export const LogListIcon = () => {
|
|||
>
|
||||
<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"
|
||||
stroke="var(--text-color)"
|
||||
strokeWidth="1.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
@ -939,8 +939,8 @@ export const ErrorIcon = () => {
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="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"
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
export const LogInfoIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="7" cy="7" r="4" fill="white" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.8327 6.99935C12.8327 10.221 10.221 12.8327 6.99935 12.8327C3.77769 12.8327 1.16602 10.221 1.16602 6.99935C1.16602 3.77769 3.77769 1.16602 6.99935 1.16602C10.221 1.16602 12.8327 3.77769 12.8327 6.99935ZM6.99935 10.3535C7.24097 10.3535 7.43685 10.1576 7.43685 9.91602V6.41602C7.43685 6.1744 7.24097 5.97852 6.99935 5.97852C6.75773 5.97852 6.56185 6.1744 6.56185 6.41602V9.91602C6.56185 10.1576 6.75773 10.3535 6.99935 10.3535ZM6.99935 4.08268C7.32152 4.08268 7.58268 4.34385 7.58268 4.66602C7.58268 4.98818 7.32152 5.24935 6.99935 5.24935C6.67717 5.24935 6.41602 4.98818 6.41602 4.66602C6.41602 4.34385 6.67717 4.08268 6.99935 4.08268Z"
|
||||
fill="#1773fd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const WarningIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="7" cy="7" r="5" fill="#FFFFFF" />
|
||||
<path
|
||||
d="M6.99935 1.16602C5.84563 1.16602 4.71781 1.50813 3.75853 2.14911C2.79924 2.79008 2.05157 3.70113 1.61005 4.76703C1.16854 5.83293 1.05302 7.00582 1.2781 8.13737C1.50318 9.26893 2.05876 10.3083 2.87456 11.1241C3.69037 11.9399 4.72977 12.4955 5.86132 12.7206C6.99288 12.9457 8.16577 12.8302 9.23167 12.3886C10.2976 11.9471 11.2086 11.1995 11.8496 10.2402C12.4906 9.28089 12.8327 8.15307 12.8327 6.99935C12.8327 6.2333 12.6818 5.47476 12.3886 4.76703C12.0955 4.0593 11.6658 3.41623 11.1241 2.87456C10.5825 2.33288 9.9394 1.9032 9.23167 1.61005C8.52394 1.3169 7.7654 1.16602 6.99935 1.16602ZM6.41602 4.08268C6.41602 3.92797 6.47748 3.7796 6.58687 3.6702C6.69627 3.56081 6.84464 3.49935 6.99935 3.49935C7.15406 3.49935 7.30243 3.56081 7.41183 3.6702C7.52123 3.7796 7.58268 3.92797 7.58268 4.08268V7.58268C7.58268 7.73739 7.52123 7.88576 7.41183 7.99516C7.30243 8.10456 7.15406 8.16601 6.99935 8.16601C6.84464 8.16601 6.69627 8.10456 6.58687 7.99516C6.47748 7.88576 6.41602 7.73739 6.41602 7.58268V4.08268ZM6.99935 11.0827C6.82629 11.0827 6.65712 11.0314 6.51323 10.9352C6.36933 10.8391 6.25718 10.7024 6.19096 10.5425C6.12473 10.3826 6.1074 10.2067 6.14116 10.037C6.17493 9.86724 6.25826 9.71133 6.38063 9.58896C6.503 9.46659 6.65891 9.38326 6.82865 9.34949C6.99838 9.31573 7.17431 9.33306 7.3342 9.39929C7.49408 9.46551 7.63074 9.57766 7.72689 9.72156C7.82303 9.86545 7.87435 10.0346 7.87435 10.2077C7.87435 10.4397 7.78216 10.6623 7.61807 10.8264C7.45398 10.9905 7.23142 11.0827 6.99935 11.0827Z"
|
||||
fill="#EDAE01"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ErrorIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="7" cy="7" r="5" fill="white" />
|
||||
<path
|
||||
d="M6.99935 1.16602C3.78518 1.16602 1.16602 3.78518 1.16602 6.99935C1.16602 10.2135 3.78518 12.8327 6.99935 12.8327C10.2135 12.8327 12.8327 10.2135 12.8327 6.99935C12.8327 3.78518 10.2135 1.16602 6.99935 1.16602ZM8.95935 8.34102C9.12852 8.51018 9.12852 8.79018 8.95935 8.95935C8.87185 9.04685 8.76102 9.08768 8.65018 9.08768C8.53935 9.08768 8.42852 9.04685 8.34102 8.95935L6.99935 7.61768L5.65768 8.95935C5.57018 9.04685 5.45935 9.08768 5.34852 9.08768C5.23768 9.08768 5.12685 9.04685 5.03935 8.95935C4.87018 8.79018 4.87018 8.51018 5.03935 8.34102L6.38102 6.99935L5.03935 5.65768C4.87018 5.48852 4.87018 5.20852 5.03935 5.03935C5.20852 4.87018 5.48852 4.87018 5.65768 5.03935L6.99935 6.38102L8.34102 5.03935C8.51018 4.87018 8.79018 4.87018 8.95935 5.03935C9.12852 5.20852 9.12852 5.48852 8.95935 5.65768L7.61768 6.99935L8.95935 8.34102Z"
|
||||
fill="#FD4E22"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SucessIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="7" cy="7" r="4" fill="white" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.8327 6.99935C12.8327 10.221 10.221 12.8327 6.99935 12.8327C3.77769 12.8327 1.16602 10.221 1.16602 6.99935C1.16602 3.77769 3.77769 1.16602 6.99935 1.16602C10.221 1.16602 12.8327 3.77769 12.8327 6.99935ZM9.35036 5.23166C9.52122 5.40251 9.52122 5.67952 9.35036 5.85036L6.43369 8.76702C6.26283 8.93788 5.98587 8.93788 5.81499 8.76702L4.64832 7.60036C4.47747 7.4295 4.47747 7.15253 4.64832 6.98167C4.81918 6.81082 5.09619 6.81082 5.26704 6.98167L6.12435 7.83894L7.42798 6.53531L8.73167 5.23166C8.90253 5.0608 9.1795 5.0608 9.35036 5.23166Z"
|
||||
fill="#2EAC41"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 1C5.81331 1 4.65328 1.35189 3.66658 2.01118C2.67989 2.67047 1.91085 3.60754 1.45673 4.7039C1.0026 5.80025 0.88378 7.00665 1.11529 8.17054C1.3468 9.33443 1.91825 10.4035 2.75736 11.2426C3.59648 12.0818 4.66557 12.6532 5.82946 12.8847C6.99335 13.1162 8.19975 12.9974 9.2961 12.5433C10.3925 12.0891 11.3295 11.3201 11.9888 10.3334C12.6481 9.34673 13 8.18669 13 7C13 6.21207 12.8448 5.43185 12.5433 4.7039C12.2417 3.97595 11.7998 3.31451 11.2426 2.75736C10.6855 2.20021 10.0241 1.75825 9.2961 1.45672C8.56815 1.15519 7.78793 1 7 1Z"
|
||||
fill="#A73CF9"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.63086 10.3252C3.63086 9.95291 3.93268 9.6511 4.30498 9.6511H9.69798C10.0703 9.6511 10.3721 9.95291 10.3721 10.3252C10.3721 10.6975 10.0703 10.9993 9.69798 10.9993H4.30498C3.93268 10.9993 3.63086 10.6975 3.63086 10.3252ZM4.78165 3.78142C4.51838 3.51816 4.09156 3.51818 3.8283 3.78144C3.56504 4.04471 3.56505 4.47154 3.82832 4.73479L5.37406 6.28048L3.82832 7.82618C3.56505 8.08943 3.56504 8.51628 3.8283 8.77953C4.09156 9.04277 4.51838 9.04277 4.78165 8.77953L6.8041 6.75715C6.93056 6.63075 7.00155 6.45926 7.00155 6.28048C7.00155 6.1017 6.93056 5.93021 6.8041 5.80381L4.78165 3.78142Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
File diff suppressed because one or more lines are too long
|
@ -1,15 +1,15 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
AddIcon,
|
||||
ArrowIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
AddIcon,
|
||||
ArrowIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../functions/handleResizePannel";
|
||||
import {
|
||||
useSelectedAsset,
|
||||
useSelectedProduct,
|
||||
useSelectedAsset,
|
||||
useSelectedProduct,
|
||||
} from "../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
import { generateUUID } from "three/src/math/MathUtils";
|
||||
|
@ -22,206 +22,222 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP
|
|||
import { deleteProductApi } from "../../../../services/simulation/deleteProductApi";
|
||||
|
||||
interface Event {
|
||||
pathName: string;
|
||||
pathName: string;
|
||||
}
|
||||
|
||||
interface ListProps {
|
||||
val: Event;
|
||||
val: Event;
|
||||
}
|
||||
|
||||
const List: React.FC<ListProps> = ({ val }) => {
|
||||
return (
|
||||
<div className="process-container">
|
||||
<div className="value">{val.pathName}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="process-container">
|
||||
<div className="value">{val.pathName}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Simulations: React.FC = () => {
|
||||
const productsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, } = useProductStore();
|
||||
const { selectedProduct, setSelectedProduct } = useSelectedProduct();
|
||||
const { getEventByModelUuid } = useEventsStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
const [openObjects, setOpenObjects] = useState(true);
|
||||
const productsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
products,
|
||||
addProduct,
|
||||
removeProduct,
|
||||
renameProduct,
|
||||
addEvent,
|
||||
removeEvent,
|
||||
} = useProductStore();
|
||||
const { selectedProduct, setSelectedProduct } = useSelectedProduct();
|
||||
const { getEventByModelUuid } = useEventsStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
const [openObjects, setOpenObjects] = useState(true);
|
||||
|
||||
const handleAddProduct = () => {
|
||||
const id = generateUUID();
|
||||
const name = `Product ${products.length + 1}`;
|
||||
addProduct(name, id);
|
||||
upsertProductOrEventApi({ productName: name, productId: id, organization: organization });
|
||||
};
|
||||
const handleAddProduct = () => {
|
||||
const id = generateUUID();
|
||||
const name = `Product ${products.length + 1}`;
|
||||
addProduct(name, id);
|
||||
upsertProductOrEventApi({
|
||||
productName: name,
|
||||
productId: id,
|
||||
organization: organization,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveProduct = (productId: string) => {
|
||||
const currentIndex = products.findIndex((p) => p.productId === productId);
|
||||
const isSelected = selectedProduct.productId === productId;
|
||||
const handleRemoveProduct = (productId: string) => {
|
||||
const currentIndex = products.findIndex((p) => p.productId === productId);
|
||||
const isSelected = selectedProduct.productId === productId;
|
||||
|
||||
const updatedProducts = products.filter((p) => p.productId !== productId);
|
||||
const updatedProducts = products.filter((p) => p.productId !== productId);
|
||||
|
||||
if (isSelected) {
|
||||
if (updatedProducts.length > 0) {
|
||||
let newSelectedIndex = currentIndex;
|
||||
if (currentIndex >= updatedProducts.length) {
|
||||
newSelectedIndex = updatedProducts.length - 1;
|
||||
}
|
||||
setSelectedProduct(
|
||||
updatedProducts[newSelectedIndex].productId,
|
||||
updatedProducts[newSelectedIndex].productName
|
||||
);
|
||||
} else {
|
||||
setSelectedProduct("", "");
|
||||
}
|
||||
if (isSelected) {
|
||||
if (updatedProducts.length > 0) {
|
||||
let newSelectedIndex = currentIndex;
|
||||
if (currentIndex >= updatedProducts.length) {
|
||||
newSelectedIndex = updatedProducts.length - 1;
|
||||
}
|
||||
setSelectedProduct(
|
||||
updatedProducts[newSelectedIndex].productId,
|
||||
updatedProducts[newSelectedIndex].productName
|
||||
);
|
||||
} else {
|
||||
setSelectedProduct("", "");
|
||||
}
|
||||
}
|
||||
|
||||
removeProduct(productId);
|
||||
deleteProductApi(productId, organization);
|
||||
};
|
||||
removeProduct(productId);
|
||||
deleteProductApi(productId, organization);
|
||||
};
|
||||
|
||||
const handleRenameProduct = (productId: string, newName: string) => {
|
||||
renameProduct(productId, newName);
|
||||
if (selectedProduct.productId === productId) {
|
||||
setSelectedProduct(productId, newName);
|
||||
}
|
||||
};
|
||||
const handleRenameProduct = (productId: string, newName: string) => {
|
||||
renameProduct(productId, newName);
|
||||
if (selectedProduct.productId === productId) {
|
||||
setSelectedProduct(productId, newName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveEventFromProduct = () => {
|
||||
if (selectedAsset) {
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
deleteEventDataApi({
|
||||
productId: selectedProduct.productId,
|
||||
modelUuid: selectedAsset.modelUuid,
|
||||
organization: organization
|
||||
});
|
||||
removeEvent(selectedProduct.productId, selectedAsset.modelUuid);
|
||||
clearSelectedAsset();
|
||||
}
|
||||
};
|
||||
const handleRemoveEventFromProduct = () => {
|
||||
if (selectedAsset) {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
deleteEventDataApi({
|
||||
productId: selectedProduct.productId,
|
||||
modelUuid: selectedAsset.modelUuid,
|
||||
organization: organization,
|
||||
});
|
||||
removeEvent(selectedProduct.productId, selectedAsset.modelUuid);
|
||||
clearSelectedAsset();
|
||||
}
|
||||
};
|
||||
|
||||
const selectedProductData = products.find(
|
||||
(product) => product.productId === selectedProduct.productId
|
||||
);
|
||||
const selectedProductData = products.find(
|
||||
(product) => product.productId === selectedProduct.productId
|
||||
);
|
||||
|
||||
const events: Event[] = selectedProductData?.eventDatas.map((event) => ({
|
||||
pathName: event.modelName,
|
||||
const events: Event[] =
|
||||
selectedProductData?.eventDatas.map((event) => ({
|
||||
pathName: event.modelName,
|
||||
})) || [];
|
||||
|
||||
return (
|
||||
<div className="simulations-container">
|
||||
<div className="header">Simulations</div>
|
||||
<div className="add-product-container">
|
||||
<div className="actions section">
|
||||
<div className="header">
|
||||
<div className="header-value">Products</div>
|
||||
<div className="add-button" onClick={handleAddProduct}>
|
||||
<AddIcon /> Add
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={productsContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={product.productId}
|
||||
className={`list-item ${selectedProduct.productId === product.productId
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="value"
|
||||
onClick={() =>
|
||||
setSelectedProduct(product.productId, product.productName)
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="products"
|
||||
checked={selectedProduct.productId === product.productId}
|
||||
readOnly
|
||||
/>
|
||||
<RenameInput
|
||||
value={product.productName}
|
||||
onRename={(newName) =>
|
||||
handleRenameProduct(product.productId, newName)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{products.length > 1 && (
|
||||
<div
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveProduct(product.productId)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e) => handleResize(e, productsContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="simulation-process section">
|
||||
<button
|
||||
className="collapse-header-container"
|
||||
onClick={() => setOpenObjects(!openObjects)}
|
||||
>
|
||||
<div className="header">Events</div>
|
||||
<div className="arrow-container">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{openObjects &&
|
||||
events.map((event, index) => <List key={index} val={event} />)}
|
||||
</div>
|
||||
|
||||
<div className="compare-simulations-container">
|
||||
<div className="compare-simulations-header">
|
||||
Need to Compare Layout?
|
||||
</div>
|
||||
<div className="content">
|
||||
Click <span>'Compare'</span> to review and analyze the layout
|
||||
differences between them.
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="button" value={"Compare"} className="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAsset && (
|
||||
<RenderOverlay>
|
||||
<EditWidgetOption
|
||||
options={["Add to Product", "Remove from Product"]}
|
||||
onClick={(option) => {
|
||||
if (option === "Add to Product") {
|
||||
handleAddEventToProduct({
|
||||
event: getEventByModelUuid(selectedAsset.modelUuid),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
clearSelectedAsset
|
||||
});
|
||||
} else {
|
||||
handleRemoveEventFromProduct();
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<div className="simulations-container">
|
||||
<div className="header">Simulations</div>
|
||||
<div className="add-product-container">
|
||||
<div className="actions section">
|
||||
<div className="header">
|
||||
<div className="header-value">Products</div>
|
||||
<button className="add-button" onClick={handleAddProduct}>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={productsContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={product.productId}
|
||||
className={`list-item ${
|
||||
selectedProduct.productId === product.productId
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<div
|
||||
className="value"
|
||||
onClick={() =>
|
||||
setSelectedProduct(product.productId, product.productName)
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="products"
|
||||
checked={selectedProduct.productId === product.productId}
|
||||
readOnly
|
||||
/>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
<RenameInput
|
||||
value={product.productName}
|
||||
onRename={(newName) =>
|
||||
handleRenameProduct(product.productId, newName)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{products.length > 1 && (
|
||||
<button
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveProduct(product.productId)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e: any) => handleResize(e, productsContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="simulation-process section">
|
||||
<button
|
||||
className="collapse-header-container"
|
||||
onClick={() => setOpenObjects(!openObjects)}
|
||||
>
|
||||
<div className="header">Events</div>
|
||||
<div className="arrow-container">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{openObjects &&
|
||||
events.map((event, index) => (
|
||||
<List key={`${index}-${event.pathName}`} val={event} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="compare-simulations-container">
|
||||
<div className="compare-simulations-header">
|
||||
Need to Compare Layout?
|
||||
</div>
|
||||
<div className="content">
|
||||
Click '<span>Compare</span>' to review and analyze the layout
|
||||
differences between them.
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="button" value={"Compare"} className="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAsset && (
|
||||
<RenderOverlay>
|
||||
<EditWidgetOption
|
||||
options={["Add to Product", "Remove from Product"]}
|
||||
onClick={(option) => {
|
||||
if (option === "Add to Product") {
|
||||
handleAddEventToProduct({
|
||||
event: getEventByModelUuid(selectedAsset.modelUuid),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
clearSelectedAsset,
|
||||
});
|
||||
} else {
|
||||
handleRemoveEventFromProduct();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Simulations;
|
||||
|
|
|
@ -1,21 +1,81 @@
|
|||
import React, { useState } from "react";
|
||||
import { ProductionCapacityIcon } from "../../icons/analysis";
|
||||
import React from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LineElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
} from "chart.js";
|
||||
import { PowerIcon, ProductionCapacityIcon } from "../../icons/analysis";
|
||||
|
||||
const ProductionCapacity = ({
|
||||
progressPercent = 50,
|
||||
avgProcessTime = "28.4 Secs/unit",
|
||||
machineUtilization = "78%",
|
||||
throughputValue = 128,
|
||||
timeRange = { startTime: "08:00 AM", endTime: "09:00 AM" },
|
||||
}) => {
|
||||
const totalBars = 6;
|
||||
const barsToFill = Math.floor((progressPercent / 100) * totalBars);
|
||||
const partialFillPercent =
|
||||
((progressPercent / 100) * totalBars - barsToFill) * 100;
|
||||
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);
|
||||
|
||||
const ThroughputSummary = () => {
|
||||
// Define all data internally within the component
|
||||
const timeRange = {
|
||||
startTime: "08:00 AM",
|
||||
endTime: "09:00 AM",
|
||||
};
|
||||
|
||||
const throughputData = {
|
||||
labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50"],
|
||||
data: [5, 10, 8, 10, 12, 10],
|
||||
totalThroughput: 1240,
|
||||
assetUsage: 85,
|
||||
};
|
||||
|
||||
const energyConsumption = {
|
||||
energyConsumed: 456,
|
||||
unit: "KWH",
|
||||
};
|
||||
|
||||
// Dynamic shift data
|
||||
const shiftUtilization = [
|
||||
{ shift: 1, percentage: 30, color: "#F3C64D" },
|
||||
{ shift: 2, percentage: 40, color: "#67B3F4" },
|
||||
{ shift: 3, percentage: 30, color: "#7981F5" },
|
||||
];
|
||||
|
||||
// Chart data configuration
|
||||
const chartData = {
|
||||
labels: throughputData.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Units/hour",
|
||||
data: throughputData.data,
|
||||
borderColor: "#B392F0",
|
||||
tension: 0.4,
|
||||
pointRadius: 0, // Hide points
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
display: false, // hide X axis completely
|
||||
},
|
||||
y: {
|
||||
display: false, // hide Y axis completely
|
||||
min: 0, // force Y axis to start at 0
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="productionCapacity-container analysis-card">
|
||||
<div className="productionCapacity-wrapper analysis-card-wrapper">
|
||||
<div className="production analysis-card">
|
||||
<div className="production-wrapper analysis-card-wrapper">
|
||||
<div className="card-header">
|
||||
<div className="header">
|
||||
<div className="main-header">Production Capacity</div>
|
||||
|
@ -30,34 +90,63 @@ const ProductionCapacity = ({
|
|||
|
||||
<div className="process-container">
|
||||
<div className="throughput-value">
|
||||
<span className="value">{throughputValue}</span> Units/hour
|
||||
<span className="value">{throughputData.totalThroughput}</span>{" "}
|
||||
Units/hour
|
||||
</div>
|
||||
|
||||
{/* Dynamic Progress Bar */}
|
||||
<div className="progress-bar-wrapper">
|
||||
{[...Array(totalBars)].map((_, i) => (
|
||||
<div className="progress-bar" key={i}>
|
||||
{i < barsToFill ? (
|
||||
<div className="bar-fill full" />
|
||||
) : i === barsToFill ? (
|
||||
<div
|
||||
className="bar-fill partial"
|
||||
style={{ width: `${partialFillPercent}%` }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
<div className="lineChart">
|
||||
<div className="assetUsage">
|
||||
<div className="key">Asset usage</div>
|
||||
<div className="value">{throughputData.assetUsage}%</div>
|
||||
</div>
|
||||
<Line data={chartData} options={chartOptions} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="metrics-section">
|
||||
<div className="metric">
|
||||
<span className="label">Avg. Process Time</span>
|
||||
<span className="value">{avgProcessTime}</span>
|
||||
<div className="footer">
|
||||
<div className="energyConsumption footer-card">
|
||||
<div className="header">Energy Consumption</div>
|
||||
<div className="value-container">
|
||||
<div className="energy-icon">
|
||||
<PowerIcon />
|
||||
</div>
|
||||
<div className="value-wrapper">
|
||||
<div className="value">{energyConsumption.energyConsumed}</div>
|
||||
<div className="unit">{energyConsumption.unit}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="metric">
|
||||
<span className="label">Machine Utilization</span>
|
||||
<span className="value">{machineUtilization}</span>
|
||||
<div className="shiftUtilization footer-card">
|
||||
<div className="header">Shift Utilization</div>
|
||||
<div className="value-container">
|
||||
<div className="value">{throughputData.assetUsage}%</div>
|
||||
|
||||
<div className="progress-wrapper">
|
||||
{/* Dynamically create progress bars based on shiftUtilization array */}
|
||||
{shiftUtilization.map((shift, index) => (
|
||||
<div
|
||||
key={shift.shift}
|
||||
className={`progress shift-${shift.shift}`}
|
||||
style={{
|
||||
width: `${shift.percentage}%`,
|
||||
backgroundColor: shift.color,
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="progress-indicator">
|
||||
{/* Dynamically create shift indicators with random colors */}
|
||||
{shiftUtilization.map((shift, index) => (
|
||||
<div className="shift-wrapper" key={shift.shift}>
|
||||
<span
|
||||
className={`indicator shift-${shift.shift}`}
|
||||
style={{ backgroundColor: shift.color }} // Random color for indicator
|
||||
></span>
|
||||
<label>Shift {shift.shift}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,4 +154,6 @@ const ProductionCapacity = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default ProductionCapacity;
|
||||
export default ThroughputSummary;
|
||||
|
||||
// Can we add dot pattern to background of lineChart and remove axis
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import React, { useState } from "react";
|
||||
import { ROISummaryIcon } from "../../icons/analysis";
|
||||
import {
|
||||
CostBreakDownIcon,
|
||||
LightBulpIcon,
|
||||
ROISummaryIcon,
|
||||
ROISummaryProductName,
|
||||
SonarCrownIcon,
|
||||
} from "../../icons/analysis";
|
||||
import SemiCircleProgress from "./SemiCircleProgress";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
|
@ -77,10 +83,12 @@ const ROISummary = ({
|
|||
</div>
|
||||
</div>
|
||||
<div className="product-info">
|
||||
<ROISummaryProductName />
|
||||
<div className="product-label">Product :</div>
|
||||
<div className="product-name">{roiSummaryData.productName}</div>
|
||||
</div>
|
||||
<div className="playBack">
|
||||
<SonarCrownIcon />
|
||||
<div className="icon"></div>
|
||||
<div className="info">
|
||||
<span>+{roiSummaryData.roiPercentage}%</span> ROI with payback in
|
||||
|
@ -109,7 +117,9 @@ const ROISummary = ({
|
|||
</div>
|
||||
</div>
|
||||
<div className="metric-item net-profit">
|
||||
<span className="metric-label">Net Profit</span>
|
||||
<span className="metric-label">
|
||||
<span>↑</span> Net Profit
|
||||
</span>
|
||||
<span className="metric-value">{roiSummaryData.netProfit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -117,7 +127,7 @@ const ROISummary = ({
|
|||
<div className="cost-breakdown">
|
||||
<div className="breakdown-header" onClick={toggleTable}>
|
||||
<div className="section-wrapper">
|
||||
<span className="section-number">①</span>
|
||||
<CostBreakDownIcon />
|
||||
<span className="section-title">Cost Breakdown</span>
|
||||
</div>
|
||||
|
||||
|
@ -169,13 +179,19 @@ const ROISummary = ({
|
|||
</div>
|
||||
<div className="tips-section">
|
||||
<div className="tip-header">
|
||||
<span className="lightbulb-icon">💡</span>
|
||||
<span className="lightbulb-icon">
|
||||
<LightBulpIcon />
|
||||
</span>
|
||||
<span className="tip-title">How to improve ROI?</span>
|
||||
</div>
|
||||
<div className="tip-description">
|
||||
Increase CNC utilization by <span className="highlight">10%</span>{" "}
|
||||
to shave <span className="highlight">0.5</span> months of payback
|
||||
period
|
||||
<div className="placeHolder-wrapper">
|
||||
<div className="placeHolder"></div>
|
||||
<div className="placeHolder"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="get-tips-button">
|
||||
<div className="btn">Get ROI Boost Tips</div>
|
||||
|
|
|
@ -1,25 +1,50 @@
|
|||
import React from "react";
|
||||
|
||||
const SemiCircleProgress = () => {
|
||||
const progress = 50;
|
||||
const SemiCircleProgress = ({ progress = 60, years = 4.02 }) => {
|
||||
const clampedProgress = Math.min(Math.max(progress, 0), 100);
|
||||
const gradientProgress = clampedProgress * 0.5;
|
||||
const radius = 80;
|
||||
const strokeWidth = 26;
|
||||
const circumference = Math.PI * radius;
|
||||
const strokeDashoffset =
|
||||
circumference - (clampedProgress / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="semi-circle-wrapper">
|
||||
<div
|
||||
className="semi-circle"
|
||||
style={{
|
||||
background: `conic-gradient(from 270deg, skyblue 0% ${gradientProgress}%, lightgray ${gradientProgress}% 100%)`,
|
||||
}}
|
||||
>
|
||||
<div className="progress-cover"></div>
|
||||
</div>
|
||||
<div className="svg-half-donut">
|
||||
<svg width="200" height="100" viewBox="0 0 200 100">
|
||||
{/* Background track */}
|
||||
<path
|
||||
d="M20,100 A80,80 0 0,1 180,100"
|
||||
fill="none"
|
||||
stroke="#6F6F7A"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
{/* Progress track */}
|
||||
<path
|
||||
d="M20,100 A80,80 0 0,1 180,100"
|
||||
fill="none"
|
||||
stroke="url(#paint0_linear_4200_2273)"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_4200_2273"
|
||||
x1="26.7278"
|
||||
y1="279.417"
|
||||
x2="298.886"
|
||||
y2="65.0002"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D3795" />
|
||||
<stop offset="0.535019" stopColor="#2A9BCF" />
|
||||
<stop offset="1" stopColor="#0DB3F4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div className="label-wrapper">
|
||||
<div className="label">{clampedProgress}%</div>
|
||||
<div className="label">{years}</div>
|
||||
<div className="label-content">Years</div>
|
||||
</div>
|
||||
<div className="content">you're on track to hit it by July 2029</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,102 +1,23 @@
|
|||
import React from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LineElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
} from "chart.js";
|
||||
import { PowerIcon, ThroughputSummaryIcon } from "../../icons/analysis";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||
ProductionCapacityIcon,
|
||||
ThroughputSummaryIcon,
|
||||
} from "../../icons/analysis";
|
||||
|
||||
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);
|
||||
|
||||
// Helper function to generate random colors
|
||||
const getRandomColor = () => {
|
||||
const letters = "0123456789ABCDEF";
|
||||
let color = "#";
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
};
|
||||
|
||||
const ThroughputSummary = () => {
|
||||
// Define all data internally within the component
|
||||
const timeRange = {
|
||||
startTime: "08:00 AM",
|
||||
endTime: "09:00 AM",
|
||||
};
|
||||
|
||||
const throughputData = {
|
||||
labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50", "09:00"],
|
||||
data: [100, 120, 110, 130, 125, 128, 132],
|
||||
totalThroughput: 1240,
|
||||
assetUsage: 85,
|
||||
};
|
||||
|
||||
const energyConsumption = {
|
||||
energyConsumed: 456,
|
||||
unit: "KWH",
|
||||
};
|
||||
|
||||
// Dynamic shift data
|
||||
const shiftUtilization = [
|
||||
{ shift: 1, percentage: 30 },
|
||||
{ shift: 2, percentage: 40 },
|
||||
{ shift: 3, percentage: 30 },
|
||||
];
|
||||
|
||||
// Chart data configuration
|
||||
const chartData = {
|
||||
labels: throughputData.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Units/hour",
|
||||
data: throughputData.data,
|
||||
borderColor: "#B392F0",
|
||||
tension: 0.4,
|
||||
pointRadius: 0, // Hide points
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const ProductionCapacity = ({
|
||||
progressPercent = 50,
|
||||
avgProcessTime = "28.4 Secs/unit",
|
||||
machineUtilization = "78%",
|
||||
throughputValue = 128,
|
||||
timeRange = { startTime: "08:00 AM", endTime: "09:00 AM" },
|
||||
}) => {
|
||||
const totalBars = 6;
|
||||
const barsToFill = Math.floor((progressPercent / 100) * totalBars);
|
||||
const partialFillPercent =
|
||||
((progressPercent / 100) * totalBars - barsToFill) * 100;
|
||||
|
||||
return (
|
||||
<div className="throughoutSummary analysis-card">
|
||||
<div className="throughoutSummary-wrapper analysis-card-wrapper">
|
||||
<div className="throughtputSummary-container analysis-card">
|
||||
<div className="throughtputSummary-wrapper analysis-card-wrapper">
|
||||
<div className="card-header">
|
||||
<div className="header">
|
||||
<div className="main-header">Throughput Summary</div>
|
||||
|
@ -111,63 +32,34 @@ const ThroughputSummary = () => {
|
|||
|
||||
<div className="process-container">
|
||||
<div className="throughput-value">
|
||||
<span className="value">{throughputData.totalThroughput}</span>{" "}
|
||||
Units/hour
|
||||
<span className="value">{throughputValue}</span> Units/hour
|
||||
</div>
|
||||
<div className="lineChart">
|
||||
<div className="assetUsage">
|
||||
<div className="key">Asset usage</div>
|
||||
<div className="value">{throughputData.assetUsage}%</div>
|
||||
</div>
|
||||
<Line data={chartData} options={chartOptions} />
|
||||
|
||||
{/* Dynamic Progress Bar */}
|
||||
<div className="progress-bar-wrapper">
|
||||
{[...Array(totalBars)].map((_, i) => (
|
||||
<div className="progress-bar" key={i}>
|
||||
{i < barsToFill ? (
|
||||
<div className="bar-fill full" />
|
||||
) : i === barsToFill ? (
|
||||
<div
|
||||
className="bar-fill partial"
|
||||
style={{ width: `${partialFillPercent}%` }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer">
|
||||
<div className="energyConsumption footer-card">
|
||||
<div className="header">Energy Consumption</div>
|
||||
<div className="value-container">
|
||||
<div className="energy-icon">
|
||||
<PowerIcon />
|
||||
</div>
|
||||
<div className="value-wrapper">
|
||||
<div className="value">{energyConsumption.energyConsumed}</div>
|
||||
<div className="unit">{energyConsumption.unit}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="metrics-section">
|
||||
<div className="metric">
|
||||
<span className="label">Avg. Process Time</span>
|
||||
<span className="value">{avgProcessTime}</span>
|
||||
</div>
|
||||
<div className="shiftUtilization footer-card">
|
||||
<div className="header">Shift Utilization</div>
|
||||
<div className="value-container">
|
||||
<div className="value">{throughputData.assetUsage}%</div>
|
||||
|
||||
<div className="progress-wrapper">
|
||||
{/* Dynamically create progress bars based on shiftUtilization array */}
|
||||
{shiftUtilization.map((shift, index) => (
|
||||
<div
|
||||
key={shift.shift}
|
||||
className={`progress shift-${shift.shift}`}
|
||||
style={{
|
||||
width: `${shift.percentage}%`,
|
||||
backgroundColor: getAvatarColor(index),
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="progress-indicator">
|
||||
{/* Dynamically create shift indicators with random colors */}
|
||||
{shiftUtilization.map((shift, index) => (
|
||||
<div className="shift-wrapper" key={shift.shift}>
|
||||
<span
|
||||
className={`indicator shift-${shift.shift}`}
|
||||
style={{ backgroundColor: getAvatarColor(index) }} // Random color for indicator
|
||||
></span>
|
||||
<label>Shift {shift.shift}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="metric">
|
||||
<span className="label">Machine Utilization</span>
|
||||
<span className="value">{machineUtilization}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -175,4 +67,4 @@ const ThroughputSummary = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default ThroughputSummary;
|
||||
export default ProductionCapacity;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import React, { useState } from "react";
|
||||
import { RenameIcon } from "../../icons/ContextMenuIcons";
|
||||
import {
|
||||
useLeftData,
|
||||
useTopData,
|
||||
} from "../../../store/visualization/useZone3DWidgetStore";
|
||||
|
||||
type RenameTooltipProps = {
|
||||
name: string;
|
||||
onSubmit: (newName: string) => void;
|
||||
};
|
||||
|
||||
const RenameTooltip: React.FC<RenameTooltipProps> = ({ name, onSubmit }) => {
|
||||
const [value, setValue] = useState(name);
|
||||
|
||||
const { top } = useTopData();
|
||||
const { left } = useLeftData();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
onSubmit(value.trim());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rename-tool-tip"
|
||||
style={{
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
}}
|
||||
>
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<RenameIcon />
|
||||
</div>
|
||||
<div className="name">Name</div>
|
||||
</div>
|
||||
<form className="input" onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenameTooltip;
|
|
@ -27,6 +27,7 @@ interface ZoneItem {
|
|||
id: string;
|
||||
name: string;
|
||||
assets?: Asset[];
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
interface ListProps {
|
||||
|
@ -157,7 +158,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
|||
{items?.map((item) => (
|
||||
<React.Fragment key={`zone-${item.id}`}>
|
||||
<li className="list-container">
|
||||
<div className="list-item">
|
||||
<div className={`list-item ${item.active ? "active" : ""}`}>
|
||||
<div className="zone-header">
|
||||
<button
|
||||
className="value"
|
||||
|
|
|
@ -1,34 +1,15 @@
|
|||
// LogList.tsx
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
LogListIcon,
|
||||
LogInfoIcon,
|
||||
WarningIcon,
|
||||
ErrorIcon,
|
||||
CloseIcon,
|
||||
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
|
||||
import { LogListIcon, CloseIcon } from "../../icons/ExportCommonIcons"; // Adjust path as needed
|
||||
import { useLogger } from "./LoggerContext";
|
||||
import { GetLogIcon } from "../../footer/getLogIcons";
|
||||
|
||||
const LogList: React.FC = () => {
|
||||
const { logs, clear, setIsLogListVisible } = useLogger();
|
||||
const [selectedTab, setSelectedTab] = useState<
|
||||
"all" | "info" | "warning" | "error" | "log"
|
||||
"all" | "info" | "warning" | "error" | "log" | "success"
|
||||
>("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 =
|
||||
|
@ -42,6 +23,7 @@ const LogList: React.FC = () => {
|
|||
className="log-list-container"
|
||||
onClick={() => setIsLogListVisible(false)}
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<div
|
||||
className="log-list-wrapper"
|
||||
onClick={(e) => {
|
||||
|
@ -55,37 +37,51 @@ const LogList: React.FC = () => {
|
|||
</div>
|
||||
<div className="head">Log List</div>
|
||||
</div>
|
||||
<button className="close" onClick={() => setIsLogListVisible(false)}>
|
||||
<button
|
||||
title="close-btn"
|
||||
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 className="log-nav-container">
|
||||
<div className="log-nav-wrapper">
|
||||
{["all", "info", "warning", "error"].map((type) => (
|
||||
<button
|
||||
title="log-type"
|
||||
key={type}
|
||||
className={`log-nav ${selectedTab === type ? "active" : ""}`}
|
||||
onClick={() => setSelectedTab(type as any)}
|
||||
>
|
||||
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button title="clear-btn" className="clear-button" onClick={clear}>
|
||||
clear
|
||||
</button>
|
||||
</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)}
|
||||
{filteredLogs.length > 0 ? (
|
||||
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 className="no-log">There are no logs to display at the moment.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { createContext, useContext, useState, useCallback } from "react";
|
||||
import React, { createContext, useContext, useState, useCallback, useMemo } from "react";
|
||||
import { MathUtils } from "three";
|
||||
|
||||
export type LogType = "log" | "info" | "warning" | "error";
|
||||
export type LogType = "log" | "info" | "warning" | "error" | "success";
|
||||
|
||||
export interface LogEntry {
|
||||
id: string;
|
||||
|
@ -18,6 +19,7 @@ interface LoggerContextValue {
|
|||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
success: (message: string) => void;
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
|
@ -29,37 +31,34 @@ export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
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(),
|
||||
id: MathUtils.generateUUID(),
|
||||
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([]);
|
||||
},
|
||||
};
|
||||
const loggerMethods: LoggerContextValue = useMemo(
|
||||
() => ({
|
||||
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),
|
||||
success: (message: string) => addLog("success", message),
|
||||
clear: () => setLogs([]),
|
||||
}),
|
||||
[logs, setLogs, isLogListVisible, setIsLogListVisible, addLog]
|
||||
);
|
||||
|
||||
return (
|
||||
<LoggerContext.Provider value={loggerMethods}>
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
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();
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
useLeftData,
|
||||
useTopData,
|
||||
|
@ -16,29 +16,25 @@ const EditWidgetOption: React.FC<EditWidgetOptionProps> = ({
|
|||
const { top } = useTopData();
|
||||
const { left } = useLeftData();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [top, left]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="editWidgetOptions-wrapper"
|
||||
className="context-menu-options-wrapper"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
zIndex: 10000,
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<div className="editWidgetOptions">
|
||||
<div className="context-menu-options">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
<button
|
||||
className="option"
|
||||
key={index}
|
||||
key={`${index}-${option}`}
|
||||
onClick={() => onClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,8 +19,8 @@ import {
|
|||
} 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 ProductionCapacity from "../analysis/ThroughputSummary";
|
||||
import ThroughputSummary from "../analysis/ProductionCapacity";
|
||||
import ROISummary from "../analysis/ROISummary";
|
||||
|
||||
const SimulationPlayer: React.FC = () => {
|
||||
|
@ -64,8 +64,6 @@ const SimulationPlayer: React.FC = () => {
|
|||
|
||||
const handleMouseDown = () => {
|
||||
isDragging.current = true;
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
|
@ -80,15 +78,16 @@ const SimulationPlayer: React.FC = () => {
|
|||
|
||||
const handleMouseUp = () => {
|
||||
isDragging.current = false;
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
// Slider function ends
|
||||
|
||||
|
@ -109,24 +108,6 @@ const SimulationPlayer: React.FC = () => {
|
|||
{ name: "process 9", completed: 90 }, // 90% completed
|
||||
{ name: "process 10", completed: 30 }, // 30% completed
|
||||
];
|
||||
// Move getRandomColor out of render
|
||||
const getRandomColor = () => {
|
||||
const letters = "0123456789ABCDEF";
|
||||
let color = "#";
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
};
|
||||
|
||||
// Store colors for each process item
|
||||
const [_, setProcessColors] = useState<string[]>([]);
|
||||
|
||||
// Generate colors on mount or when process changes
|
||||
useEffect(() => {
|
||||
const generatedColors = process.map(() => getRandomColor());
|
||||
setProcessColors(generatedColors);
|
||||
}, []);
|
||||
|
||||
const intervals = [10, 20, 30, 40, 50, 60]; // in minutes
|
||||
const totalSegments = intervals.length;
|
||||
|
@ -218,7 +199,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "simulations" && (
|
||||
{subModule !== "analysis" && (
|
||||
<div className="header">
|
||||
<InfoIcon />
|
||||
{playSimulation
|
||||
|
@ -281,7 +262,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
const segmentProgress = (index / totalSegments) * 100;
|
||||
const isFilled = progress >= segmentProgress;
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<React.Fragment key={`${index}-${label}`}>
|
||||
<div className="label-dot-wrapper">
|
||||
<div className="label">{label} mins</div>
|
||||
<div
|
||||
|
@ -330,25 +311,29 @@ const SimulationPlayer: React.FC = () => {
|
|||
<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 className="custom-slider-wrapper">
|
||||
<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 className="speed-label max-value">4x</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -360,6 +345,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
className="process-wrapper"
|
||||
style={{ padding: expand ? "0px" : "5px 35px" }}
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<div
|
||||
className="process-container"
|
||||
ref={processWrapperRef}
|
||||
|
@ -367,7 +353,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
>
|
||||
{process.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
key={`${index}-${item.name}`}
|
||||
className="process"
|
||||
style={{
|
||||
width: `${item.completed}%`,
|
||||
|
@ -387,13 +373,17 @@ const SimulationPlayer: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="analysis">
|
||||
<div className="analysis-wrapper">
|
||||
<ProductionCapacity />
|
||||
<ThroughputSummary />
|
||||
{/* {subModule === "analysis" && ( */}
|
||||
{subModule === "analysis" && (
|
||||
<div className="analysis">
|
||||
<div className="analysis-wrapper">
|
||||
<ProductionCapacity />
|
||||
<ThroughputSummary />
|
||||
</div>
|
||||
<ROISummary />
|
||||
</div>
|
||||
<ROISummary />
|
||||
</div>
|
||||
)}
|
||||
{/* )} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './app';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
useDeletePointOrLine,
|
||||
useMovePoint,
|
||||
useActiveLayer,
|
||||
useSocketStore,
|
||||
useWallVisibility,
|
||||
useRoofVisibility,
|
||||
useShadows,
|
||||
|
@ -48,15 +47,11 @@ import FloorGroupAilse from "./groups/floorGroupAisle";
|
|||
import Draw from "./functions/draw";
|
||||
import WallsAndWallItems from "./groups/wallsAndWallItems";
|
||||
import Ground from "../scene/environment/ground";
|
||||
// import ZoneGroup from "../groups/zoneGroup1";
|
||||
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
|
||||
import Layer2DVisibility from "./geomentries/layers/layer2DVisibility";
|
||||
import DrieHtmlTemp from "../visualization/mqttTemp/drieHtmlTemp";
|
||||
import ZoneGroup from "./groups/zoneGroup";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import MeasurementTool from "../scene/tools/measurementTool";
|
||||
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
||||
import ProductionCapacity from "../../components/ui/analysis/ProductionCapacity";
|
||||
|
||||
export default function Builder() {
|
||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||
|
@ -113,21 +108,19 @@ export default function Builder() {
|
|||
|
||||
const [selectedItemsIndex, setSelectedItemsIndex] =
|
||||
useState<Types.Number | null>(null); // State for tracking the index of the selected item.
|
||||
const { activeLayer, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
|
||||
const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
|
||||
const { activeLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
|
||||
const { toggleView } = useToggleView(); // State for toggling between 2D and 3D.
|
||||
const { toolMode, setToolMode } = useToolMode();
|
||||
const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { socket } = useSocketStore();
|
||||
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
|
||||
const { wallVisibility, setWallVisibility } = useWallVisibility();
|
||||
const { shadows, setShadows } = useShadows();
|
||||
const { renderDistance, setRenderDistance } = useRenderDistance();
|
||||
const { limitDistance, setLimitDistance } = useLimitDistance();
|
||||
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||
const { walls, setWalls } = useWalls();
|
||||
const { setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
|
||||
const { setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { setRoofVisibility } = useRoofVisibility();
|
||||
const { setWallVisibility } = useWallVisibility();
|
||||
const { setShadows } = useShadows();
|
||||
const { setRenderDistance } = useRenderDistance();
|
||||
const { setLimitDistance } = useLimitDistance();
|
||||
const { setUpdateScene } = useUpdateScene();
|
||||
const { setWalls } = useWalls();
|
||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
// const loader = new GLTFLoader();
|
||||
// const dracoLoader = new DRACOLoader();
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteTool, useFloorItems, useLoadingProgress, useRenderDistance, useSelectedFloorItem, useSelectedItem, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store";
|
||||
import {
|
||||
useActiveTool,
|
||||
useAsset3dWidget,
|
||||
useCamMode,
|
||||
useDeletableFloorItem,
|
||||
useDeleteTool,
|
||||
useFloorItems,
|
||||
useLoadingProgress,
|
||||
useRenderDistance,
|
||||
useSelectedFloorItem,
|
||||
useSelectedItem,
|
||||
useSocketStore,
|
||||
useToggleView,
|
||||
useTransformMode,
|
||||
} from "../../../store/store";
|
||||
import assetVisibility from "../geomentries/assets/assetVisibility";
|
||||
import { useEffect } from "react";
|
||||
import * as THREE from "three";
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import assetManager, {
|
||||
cancelOngoingTasks,
|
||||
cancelOngoingTasks,
|
||||
} from "../geomentries/assets/assetManager";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
|
@ -18,313 +32,422 @@ import useModuleStore from "../../../store/useModuleStore";
|
|||
// import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";
|
||||
import { useEventsStore } from "../../../store/simulation/useEventsStore";
|
||||
|
||||
const assetManagerWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", import.meta.url));
|
||||
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
|
||||
const assetManagerWorker = new Worker(
|
||||
new URL(
|
||||
"../../../services/factoryBuilder/webWorkers/assetManagerWorker.js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
const gltfLoaderWorker = new Worker(
|
||||
new URL(
|
||||
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane, }: any) => {
|
||||
const state: Types.ThreeState = useThree();
|
||||
const { raycaster, controls }: any = state;
|
||||
const { renderDistance } = useRenderDistance();
|
||||
const { toggleView } = useToggleView();
|
||||
const { floorItems, setFloorItems } = useFloorItems();
|
||||
const { camMode } = useCamMode();
|
||||
const { deleteTool } = useDeleteTool();
|
||||
const { setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { transformMode } = useTransformMode();
|
||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
const { addEvent } = useEventsStore();
|
||||
const FloorItemsGroup = ({
|
||||
itemsGroup,
|
||||
hoveredDeletableFloorItem,
|
||||
AttachedObject,
|
||||
floorGroup,
|
||||
tempLoader,
|
||||
isTempLoader,
|
||||
plane,
|
||||
}: any) => {
|
||||
const state: Types.ThreeState = useThree();
|
||||
const { raycaster, controls }: any = state;
|
||||
const { renderDistance } = useRenderDistance();
|
||||
const { toggleView } = useToggleView();
|
||||
const { floorItems, setFloorItems } = useFloorItems();
|
||||
const { camMode } = useCamMode();
|
||||
const { deleteTool } = useDeleteTool();
|
||||
const { setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { transformMode } = useTransformMode();
|
||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
const { addEvent } = useEventsStore();
|
||||
|
||||
dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/");
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
dracoLoader.setDecoderPath(
|
||||
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
|
||||
);
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
let totalAssets = 0;
|
||||
let loadedAssets = 0;
|
||||
let totalAssets = 0;
|
||||
let loadedAssets = 0;
|
||||
|
||||
const updateLoadingProgress = (progress: number) => {
|
||||
if (progress < 100) {
|
||||
setLoadingProgress(progress);
|
||||
} else if (progress === 100) {
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(100);
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(0);
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
const updateLoadingProgress = (progress: number) => {
|
||||
if (progress < 100) {
|
||||
setLoadingProgress(progress);
|
||||
} else if (progress === 100) {
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(100);
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(0);
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
getFloorAssets(organization).then((data) => {
|
||||
if (data.length > 0) {
|
||||
const uniqueItems = (data as Types.FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID));
|
||||
totalAssets = uniqueItems.length;
|
||||
if (totalAssets === 0) {
|
||||
updateLoadingProgress(100);
|
||||
return;
|
||||
}
|
||||
gltfLoaderWorker.postMessage({ floorItems: data });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
getFloorAssets(organization).then((data) => {
|
||||
if (data.length > 0) {
|
||||
const uniqueItems = (data as Types.FloorItems).filter(
|
||||
(item, index, self) =>
|
||||
index === self.findIndex((t) => t.modelfileID === item.modelfileID)
|
||||
);
|
||||
totalAssets = uniqueItems.length;
|
||||
if (totalAssets === 0) {
|
||||
updateLoadingProgress(100);
|
||||
return;
|
||||
}
|
||||
gltfLoaderWorker.postMessage({ floorItems: data });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
loadInitialFloorItems(
|
||||
itemsGroup,
|
||||
setFloorItems,
|
||||
addEvent,
|
||||
renderDistance
|
||||
);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
|
||||
gltfLoaderWorker.onmessage = async (event) => {
|
||||
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
|
||||
const blobUrl = URL.createObjectURL(event.data.modelBlob);
|
||||
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(event.data.modelID, gltf);
|
||||
|
||||
loadedAssets++;
|
||||
const progress = Math.round((loadedAssets / totalAssets) * 100);
|
||||
updateLoadingProgress(progress);
|
||||
|
||||
if (loadedAssets === totalAssets) {
|
||||
loadInitialFloorItems(
|
||||
itemsGroup,
|
||||
setFloorItems,
|
||||
addEvent,
|
||||
renderDistance
|
||||
);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
gltfLoaderWorker.onmessage = async (event) => {
|
||||
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
|
||||
const blobUrl = URL.createObjectURL(event.data.modelBlob);
|
||||
useEffect(() => {
|
||||
assetManagerWorker.onmessage = async (event) => {
|
||||
cancelOngoingTasks(); // Cancel the ongoing process
|
||||
await assetManager(event.data, itemsGroup, loader);
|
||||
};
|
||||
}, [assetManagerWorker]);
|
||||
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(event.data.modelID, gltf);
|
||||
useEffect(() => {
|
||||
if (toggleView) return;
|
||||
|
||||
loadedAssets++;
|
||||
const progress = Math.round((loadedAssets / totalAssets) * 100);
|
||||
updateLoadingProgress(progress);
|
||||
const uuids: string[] = [];
|
||||
itemsGroup.current?.children.forEach((child: any) => {
|
||||
uuids.push(child.uuid);
|
||||
});
|
||||
const cameraPosition = state.camera.position;
|
||||
|
||||
if (loadedAssets === totalAssets) {
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
assetManagerWorker.postMessage({
|
||||
floorItems,
|
||||
cameraPosition,
|
||||
uuids,
|
||||
renderDistance,
|
||||
});
|
||||
}, [camMode, renderDistance]);
|
||||
|
||||
useEffect(() => {
|
||||
assetManagerWorker.onmessage = async (event) => {
|
||||
cancelOngoingTasks(); // Cancel the ongoing process
|
||||
await assetManager(event.data, itemsGroup, loader);
|
||||
};
|
||||
}, [assetManagerWorker]);
|
||||
useEffect(() => {
|
||||
const controls: any = state.controls;
|
||||
const camera: any = state.camera;
|
||||
|
||||
useEffect(() => {
|
||||
if (controls) {
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
|
||||
const handleChange = () => {
|
||||
if (toggleView) return;
|
||||
|
||||
const uuids: string[] = [];
|
||||
itemsGroup.current?.children.forEach((child: any) => { uuids.push(child.uuid); });
|
||||
const cameraPosition = state.camera.position;
|
||||
itemsGroup.current?.children.forEach((child: any) => {
|
||||
uuids.push(child.uuid);
|
||||
});
|
||||
const cameraPosition = camera.position;
|
||||
|
||||
assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance, });
|
||||
}, [camMode, renderDistance]);
|
||||
assetManagerWorker.postMessage({
|
||||
floorItems,
|
||||
cameraPosition,
|
||||
uuids,
|
||||
renderDistance,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const controls: any = state.controls;
|
||||
const camera: any = state.camera;
|
||||
|
||||
if (controls) {
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
|
||||
const handleChange = () => {
|
||||
if (toggleView) return;
|
||||
|
||||
const uuids: string[] = [];
|
||||
itemsGroup.current?.children.forEach((child: any) => { uuids.push(child.uuid); });
|
||||
const cameraPosition = camera.position;
|
||||
|
||||
assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance, });
|
||||
};
|
||||
|
||||
const startInterval = () => {
|
||||
if (!intervalId) {
|
||||
intervalId = setInterval(handleChange, 50);
|
||||
}
|
||||
};
|
||||
|
||||
const stopInterval = () => {
|
||||
handleChange();
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
controls.addEventListener("rest", handleChange);
|
||||
controls.addEventListener("rest", stopInterval);
|
||||
controls.addEventListener("control", startInterval);
|
||||
controls.addEventListener("controlend", stopInterval);
|
||||
|
||||
return () => {
|
||||
controls.removeEventListener("rest", handleChange);
|
||||
controls.removeEventListener("rest", stopInterval);
|
||||
controls.removeEventListener("control", startInterval);
|
||||
controls.removeEventListener("controlend", stopInterval);
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
const startInterval = () => {
|
||||
if (!intervalId) {
|
||||
intervalId = setInterval(handleChange, 50);
|
||||
}
|
||||
}, [state.controls, floorItems, toggleView, renderDistance]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = state.gl.domElement;
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isLeftMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = async (evt: any) => {
|
||||
if (controls) {
|
||||
(controls as any).enabled = true;
|
||||
}
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
if (drag) return;
|
||||
|
||||
if (deleteTool) {
|
||||
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, socket);
|
||||
}
|
||||
const Mode = transformMode;
|
||||
|
||||
if (Mode !== null || activeTool === "cursor") {
|
||||
if (!itemsGroup.current) return;
|
||||
let intersects = raycaster.intersectObjects(
|
||||
itemsGroup.current.children,
|
||||
true
|
||||
);
|
||||
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
|
||||
// let currentObject = intersects[0].object;
|
||||
// while (currentObject) {
|
||||
// if (currentObject.name === "Scene") {
|
||||
// break;
|
||||
// }
|
||||
// currentObject = currentObject.parent as THREE.Object3D;
|
||||
// }
|
||||
// if (currentObject) {
|
||||
// AttachedObject.current = currentObject as any;
|
||||
// setSelectedFloorItem(AttachedObject.current!);
|
||||
// }
|
||||
} else {
|
||||
const target = controls.getTarget(new THREE.Vector3());
|
||||
await controls.setTarget(target.x, 0, target.z, true);
|
||||
setSelectedFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDblClick = async (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
if (drag) return;
|
||||
|
||||
const Mode = transformMode;
|
||||
|
||||
if (Mode !== null || activeTool === "cursor") {
|
||||
if (!itemsGroup.current) return;
|
||||
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
|
||||
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
|
||||
let currentObject = intersects[0].object;
|
||||
|
||||
while (currentObject) {
|
||||
if (currentObject.name === "Scene") {
|
||||
break;
|
||||
}
|
||||
currentObject = currentObject.parent as THREE.Object3D;
|
||||
}
|
||||
if (currentObject) {
|
||||
AttachedObject.current = currentObject as any;
|
||||
// controls.fitToSphere(AttachedObject.current!, true);
|
||||
|
||||
const bbox = new THREE.Box3().setFromObject(AttachedObject.current);
|
||||
const size = bbox.getSize(new THREE.Vector3());
|
||||
const center = bbox.getCenter(new THREE.Vector3());
|
||||
|
||||
const front = new THREE.Vector3(0, 0, 1);
|
||||
AttachedObject.current.localToWorld(front);
|
||||
front.sub(AttachedObject.current.position).normalize();
|
||||
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
const newPosition = center.clone().addScaledVector(front, distance);
|
||||
|
||||
controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
||||
controls.setTarget(center.x, center.y, center.z, true);
|
||||
controls.fitToBox(AttachedObject.current!, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, });
|
||||
|
||||
setSelectedFloorItem(AttachedObject.current!);
|
||||
}
|
||||
} else {
|
||||
const target = controls.getTarget(new THREE.Vector3());
|
||||
await controls.setTarget(target.x, 0, target.z, true);
|
||||
setSelectedFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDrop = (event: any) => {
|
||||
if (!event.dataTransfer?.files[0]) return;
|
||||
|
||||
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
|
||||
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, addEvent, plane);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (event: any) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
if (activeModule === "builder") {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("dblclick", onDblClick);
|
||||
canvasElement.addEventListener("drop", onDrop);
|
||||
canvasElement.addEventListener("dragover", onDragOver);
|
||||
} else {
|
||||
if (controls) {
|
||||
const target = controls.getTarget(new THREE.Vector3());
|
||||
controls.setTarget(target.x, 0, target.z, true);
|
||||
setSelectedFloorItem(null);
|
||||
}
|
||||
const stopInterval = () => {
|
||||
handleChange();
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("dblclick", onDblClick);
|
||||
canvasElement.removeEventListener("drop", onDrop);
|
||||
canvasElement.removeEventListener("dragover", onDragOver);
|
||||
};
|
||||
}, [deleteTool, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool, activeModule,]);
|
||||
controls.addEventListener("rest", handleChange);
|
||||
controls.addEventListener("rest", stopInterval);
|
||||
controls.addEventListener("control", startInterval);
|
||||
controls.addEventListener("controlend", stopInterval);
|
||||
|
||||
useFrame(() => {
|
||||
if (controls)
|
||||
// assetVisibility(itemsGroup, state.camera.position, renderDistance);
|
||||
if (deleteTool && activeModule === "builder") {
|
||||
DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem);
|
||||
} else if (!deleteTool) {
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
return () => {
|
||||
controls.removeEventListener("rest", handleChange);
|
||||
controls.removeEventListener("rest", stopInterval);
|
||||
controls.removeEventListener("control", startInterval);
|
||||
controls.removeEventListener("controlend", stopInterval);
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [state.controls, floorItems, toggleView, renderDistance]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = state.gl.domElement;
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
console.log('isLeftMouseDown: ', isLeftMouseDown);
|
||||
if (isLeftMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = async (evt: any) => {
|
||||
if (controls) {
|
||||
(controls as any).enabled = true;
|
||||
}
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
if (drag) return;
|
||||
|
||||
if (deleteTool) {
|
||||
DeleteFloorItems(
|
||||
itemsGroup,
|
||||
hoveredDeletableFloorItem,
|
||||
setFloorItems,
|
||||
socket
|
||||
);
|
||||
}
|
||||
const Mode = transformMode;
|
||||
|
||||
if (Mode !== null || activeTool === "cursor") {
|
||||
if (!itemsGroup.current) return;
|
||||
let intersects = raycaster.intersectObjects(
|
||||
itemsGroup.current.children,
|
||||
true
|
||||
);
|
||||
if (
|
||||
intersects.length > 0 &&
|
||||
intersects[0]?.object?.parent?.parent?.position &&
|
||||
intersects[0]?.object?.parent?.parent?.scale &&
|
||||
intersects[0]?.object?.parent?.parent?.rotation
|
||||
) {
|
||||
// let currentObject = intersects[0].object;
|
||||
// while (currentObject) {
|
||||
// if (currentObject.name === "Scene") {
|
||||
// break;
|
||||
// }
|
||||
// currentObject = currentObject.parent as THREE.Object3D;
|
||||
// }
|
||||
// if (currentObject) {
|
||||
// AttachedObject.current = currentObject as any;
|
||||
// setSelectedFloorItem(AttachedObject.current!);
|
||||
// }
|
||||
} else {
|
||||
const target = controls.getTarget(new THREE.Vector3());
|
||||
await controls.setTarget(target.x, 0, target.z, true);
|
||||
setSelectedFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDblClick = async (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
if (drag) return;
|
||||
|
||||
const Mode = transformMode;
|
||||
|
||||
if (Mode !== null || activeTool === "cursor") {
|
||||
if (!itemsGroup.current) return;
|
||||
let intersects = raycaster.intersectObjects(
|
||||
itemsGroup.current.children,
|
||||
true
|
||||
);
|
||||
if (
|
||||
intersects.length > 0 &&
|
||||
intersects[0]?.object?.parent?.parent?.position &&
|
||||
intersects[0]?.object?.parent?.parent?.scale &&
|
||||
intersects[0]?.object?.parent?.parent?.rotation
|
||||
) {
|
||||
let currentObject = intersects[0].object;
|
||||
|
||||
while (currentObject) {
|
||||
if (currentObject.name === "Scene") {
|
||||
break;
|
||||
}
|
||||
currentObject = currentObject.parent as THREE.Object3D;
|
||||
}
|
||||
});
|
||||
if (currentObject) {
|
||||
AttachedObject.current = currentObject as any;
|
||||
// controls.fitToSphere(AttachedObject.current!, true);
|
||||
|
||||
return <group ref={itemsGroup} name="itemsGroup"></group>;
|
||||
const bbox = new THREE.Box3().setFromObject(
|
||||
AttachedObject.current
|
||||
);
|
||||
const size = bbox.getSize(new THREE.Vector3());
|
||||
const center = bbox.getCenter(new THREE.Vector3());
|
||||
|
||||
const front = new THREE.Vector3(0, 0, 1);
|
||||
AttachedObject.current.localToWorld(front);
|
||||
front.sub(AttachedObject.current.position).normalize();
|
||||
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
const newPosition = center
|
||||
.clone()
|
||||
.addScaledVector(front, distance);
|
||||
|
||||
controls.setPosition(
|
||||
newPosition.x,
|
||||
newPosition.y,
|
||||
newPosition.z,
|
||||
true
|
||||
);
|
||||
controls.setTarget(center.x, center.y, center.z, true);
|
||||
controls.fitToBox(AttachedObject.current!, true, {
|
||||
cover: true,
|
||||
paddingTop: 5,
|
||||
paddingLeft: 5,
|
||||
paddingBottom: 5,
|
||||
paddingRight: 5,
|
||||
});
|
||||
|
||||
setSelectedFloorItem(AttachedObject.current!);
|
||||
}
|
||||
} else {
|
||||
const target = controls.getTarget(new THREE.Vector3());
|
||||
await controls.setTarget(target.x, 0, target.z, true);
|
||||
setSelectedFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDrop = (event: any) => {
|
||||
if (!event.dataTransfer?.files[0]) return;
|
||||
|
||||
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
|
||||
addAssetModel(
|
||||
raycaster,
|
||||
state.camera,
|
||||
state.pointer,
|
||||
floorGroup,
|
||||
setFloorItems,
|
||||
itemsGroup,
|
||||
isTempLoader,
|
||||
tempLoader,
|
||||
socket,
|
||||
selectedItem,
|
||||
setSelectedItem,
|
||||
addEvent,
|
||||
plane
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (event: any) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
if (activeModule === "builder") {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("dblclick", onDblClick);
|
||||
canvasElement.addEventListener("drop", onDrop);
|
||||
canvasElement.addEventListener("dragover", onDragOver);
|
||||
} else {
|
||||
if (controls) {
|
||||
const target = controls.getTarget(new THREE.Vector3());
|
||||
controls.setTarget(target.x, 0, target.z, true);
|
||||
setSelectedFloorItem(null);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("dblclick", onDblClick);
|
||||
canvasElement.removeEventListener("drop", onDrop);
|
||||
canvasElement.removeEventListener("dragover", onDragOver);
|
||||
};
|
||||
}, [
|
||||
deleteTool,
|
||||
transformMode,
|
||||
controls,
|
||||
selectedItem,
|
||||
state.camera,
|
||||
state.pointer,
|
||||
activeTool,
|
||||
activeModule,
|
||||
]);
|
||||
|
||||
useFrame(() => {
|
||||
if (controls)
|
||||
if (deleteTool && activeModule === "builder") {
|
||||
// assetVisibility(itemsGroup, state.camera.position, renderDistance);
|
||||
DeletableHoveredFloorItems(
|
||||
state,
|
||||
itemsGroup,
|
||||
hoveredDeletableFloorItem,
|
||||
setDeletableFloorItem
|
||||
);
|
||||
} else if (!deleteTool) {
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return <group ref={itemsGroup} name="itemsGroup"></group>;
|
||||
};
|
||||
|
||||
export default FloorItemsGroup;
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as CONSTANTS from "../../../types/world/worldConstants";
|
|||
|
||||
const Ground = ({ grid, plane }: any) => {
|
||||
const { toggleView } = useToggleView();
|
||||
const savedTheme: string | null = localStorage.getItem("theme");
|
||||
const { planeValue, gridValue } = useTileDistance();
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,255 +1,276 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||
import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
|
||||
import useModuleStore, {
|
||||
useSubModuleStore,
|
||||
} from "../../../../../store/useModuleStore";
|
||||
import { TransformControls } from "@react-three/drei";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import {
|
||||
useSelectedEventSphere,
|
||||
useSelectedEventData,
|
||||
useSelectedEventSphere,
|
||||
useSelectedEventData,
|
||||
} 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 { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
|
||||
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 { setSelectedEventData, clearSelectedEventData } =
|
||||
useSelectedEventData();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventSphere) {
|
||||
const eventData = getEventByModelUuid(
|
||||
selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
useEffect(() => {
|
||||
if (selectedEventSphere) {
|
||||
const eventData = getEventByModelUuid(
|
||||
selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
|
||||
if (eventData) {
|
||||
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
|
||||
} else {
|
||||
clearSelectedEventData();
|
||||
}
|
||||
} else {
|
||||
clearSelectedEventData();
|
||||
}
|
||||
}, [selectedEventSphere]);
|
||||
if (eventData) {
|
||||
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
|
||||
} else {
|
||||
clearSelectedEventData();
|
||||
}
|
||||
} else {
|
||||
clearSelectedEventData();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedEventSphere) return;
|
||||
if (keyCombination === "G") {
|
||||
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
|
||||
}
|
||||
if (keyCombination === "R") {
|
||||
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
|
||||
let point = JSON.parse(
|
||||
JSON.stringify(getPointByUuid(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid))
|
||||
);
|
||||
if (point) {
|
||||
point.position = [selectedEventSphere.position.x, selectedEventSphere.position.y, selectedEventSphere.position.z,];
|
||||
updatePoint(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid, point);
|
||||
}
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedEventSphere) return;
|
||||
if (keyCombination === "G") {
|
||||
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
|
||||
}
|
||||
if (keyCombination === "R") {
|
||||
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
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" && (
|
||||
<>
|
||||
<group name="EventPointsGroup">
|
||||
{events.map((event, i) => {
|
||||
if (event.type === "transfer") {
|
||||
return (
|
||||
<group
|
||||
key={i}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
{event.points.map((point, j) => (
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
key={`${i}-${j}`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === "vehicle") {
|
||||
return (
|
||||
<group
|
||||
key={i}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="blue" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === "roboticArm") {
|
||||
return (
|
||||
<group
|
||||
key={i}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="green" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === "machine") {
|
||||
return (
|
||||
<group
|
||||
key={i}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="purple" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</group>
|
||||
{selectedEventSphere && transformMode && (
|
||||
<TransformControls
|
||||
ref={transformRef}
|
||||
object={selectedEventSphere}
|
||||
mode={transformMode}
|
||||
onMouseUp={(e) => {
|
||||
updatePointToState(selectedEventSphere);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
|
||||
let point = JSON.parse(
|
||||
JSON.stringify(
|
||||
getPointByUuid(
|
||||
selectedEventSphere.userData.modelUuid,
|
||||
selectedEventSphere.userData.pointUuid
|
||||
)
|
||||
)
|
||||
);
|
||||
if (point) {
|
||||
point.position = [
|
||||
selectedEventSphere.position.x,
|
||||
selectedEventSphere.position.y,
|
||||
selectedEventSphere.position.z,
|
||||
];
|
||||
updatePoint(
|
||||
selectedEventSphere.userData.modelUuid,
|
||||
selectedEventSphere.userData.pointUuid,
|
||||
point
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [gl, subModule, selectedEventSphere]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<group name="EventPointsGroup">
|
||||
{events.map((event, index) => {
|
||||
if (event.type === "transfer") {
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${event.modelUuid}`}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
{event.points.map((point, j) => (
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
key={`${index}-${point.uuid}`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === "vehicle") {
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${event.modelUuid}`}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="blue" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === "roboticArm") {
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${event.modelUuid}`}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="green" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === "machine") {
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${event.modelUuid}`}
|
||||
position={event.position}
|
||||
rotation={event.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[event.point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{
|
||||
modelUuid: event.modelUuid,
|
||||
pointUuid: event.point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="purple" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</group>
|
||||
{selectedEventSphere && transformMode && (
|
||||
<TransformControls
|
||||
ref={transformRef}
|
||||
object={selectedEventSphere}
|
||||
mode={transformMode}
|
||||
onMouseUp={(e) => {
|
||||
updatePointToState(selectedEventSphere);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PointsCreator;
|
||||
export default PointsCreator;
|
||||
|
|
|
@ -1,220 +1,261 @@
|
|||
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 React, { useEffect, 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';
|
||||
useAnimationPlaySpeed,
|
||||
usePauseButtonStore,
|
||||
usePlayButtonStore,
|
||||
useResetButtonStore,
|
||||
} from "../../../../../store/usePlayButtonStore";
|
||||
|
||||
function RoboticArmAnimator({
|
||||
HandleCallback,
|
||||
restPosition,
|
||||
ikSolver,
|
||||
targetBone,
|
||||
armBot,
|
||||
logStatus,
|
||||
path
|
||||
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);
|
||||
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();
|
||||
// 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]);
|
||||
// Update path state whenever `path` prop changes
|
||||
useEffect(() => {
|
||||
setCurrentPath(path);
|
||||
}, [path]);
|
||||
|
||||
// Reset logic when `isPlaying` changes
|
||||
useEffect(() => {
|
||||
if (!isPlaying) {
|
||||
setCurrentPath([]);
|
||||
curveRef.current = null;
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
// 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;
|
||||
// Reset logic when `isPlaying` changes
|
||||
useEffect(() => {
|
||||
if (!isPlaying) {
|
||||
setCurrentPath([]);
|
||||
curveRef.current = null;
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
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
|
||||
}
|
||||
// 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),
|
||||
];
|
||||
}
|
||||
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);
|
||||
} else if (indexOfNearestStart >= indexOfNearestEnd) {
|
||||
for (
|
||||
let i = indexOfNearestStart;
|
||||
i !== (indexOfNearestEnd - 1 + 64) % 64;
|
||||
i = (i - 1 + 64) % 64
|
||||
) {
|
||||
arcPoints.push(circlePoints[i]);
|
||||
}
|
||||
}, [circlePoints, currentPath]);
|
||||
}
|
||||
|
||||
// Frame update for animation
|
||||
useFrame((_, delta) => {
|
||||
if (!ikSolver) return;
|
||||
// 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 bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||
if (!bone) return;
|
||||
const customCurve = new THREE.CatmullRomCurve3(
|
||||
pathVectors,
|
||||
false,
|
||||
"centripetal",
|
||||
1
|
||||
);
|
||||
const generatedPoints = customCurve.getPoints(100);
|
||||
setCustomCurvePoints(generatedPoints);
|
||||
}
|
||||
}, [circlePoints, currentPath]);
|
||||
|
||||
if (isPlaying) {
|
||||
if (!isPaused && customCurvePoints && currentPath.length > 0) {
|
||||
const curvePoints = customCurvePoints;
|
||||
const speedAdjustedProgress = progressRef.current + (speed * armBot.speed);
|
||||
const index = Math.floor(speedAdjustedProgress);
|
||||
// Frame update for animation
|
||||
useFrame((_, delta) => {
|
||||
if (!ikSolver) return;
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
ikSolver.update();
|
||||
} else if (!isPlaying && currentPath.length === 0) {
|
||||
// Not playing anymore, reset to rest
|
||||
bone.position.copy(restPosition);
|
||||
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> */}
|
||||
</>
|
||||
const bone = ikSolver.mesh.skeleton.bones.find(
|
||||
(b: any) => b.name === targetBone
|
||||
);
|
||||
if (!bone) return;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
ikSolver.update();
|
||||
} else if (!isPlaying && currentPath.length === 0) {
|
||||
// Not playing anymore, reset to rest
|
||||
bone.position.copy(restPosition);
|
||||
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]}
|
||||
visible={false}
|
||||
>
|
||||
<ringGeometry args={[1.59, 1.61, 64]} />
|
||||
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RoboticArmAnimator;
|
||||
|
|
|
@ -67,13 +67,12 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
|
|||
setIkSolver(solver);
|
||||
|
||||
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
||||
// groupRef.current.add(helper);
|
||||
|
||||
setSelectedArm(OOI.Target_Bone);
|
||||
|
||||
// scene.add(helper);
|
||||
|
||||
}, [gltf]);
|
||||
}, [cloned, gltf, setIkSolver]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -58,7 +58,7 @@ const Project: React.FC = () => {
|
|||
setOrganization(Organization);
|
||||
setUserName(name);
|
||||
}
|
||||
echo.info("Log in success full");
|
||||
echo.warn("Log in success full");
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
|
|
|
@ -115,17 +115,23 @@ $color5: #c7a8ff;
|
|||
// log indication colors
|
||||
// ------------ text -------------
|
||||
$log-default-text-color: #6f42c1;
|
||||
$log-info-text-color: #488ef6;
|
||||
$log-info-text-color: #1773fd;
|
||||
$log-warn-text-color: #f3a50c;
|
||||
$log-error-text-color: #f65648;
|
||||
$log-success-text-color: #43c06d;
|
||||
$log-error-text-color: #fc230f;
|
||||
$log-success-text-color: #23a84f;
|
||||
// ----------- dark ---------------
|
||||
$log-default-text-color-dark: #b18ef1;
|
||||
$log-info-text-color-dark: #7eb0fa;
|
||||
$log-warn-text-color-dark: #ffaa00;
|
||||
$log-error-text-color-dark: #ff887d;
|
||||
$log-success-text-color-dark: #43ff81;
|
||||
|
||||
// ------------ background -------------
|
||||
$log-default-backgroung-color: #6e42c133;
|
||||
$log-info-background-color: #488ef633;
|
||||
$log-info-background-color: #1773fd5d;
|
||||
$log-warn-background-color: #f3a50c33;
|
||||
$log-error-background-color: #f6564833;
|
||||
$log-success-background-color: #43c06d33;
|
||||
$log-error-background-color: #fc230f33;
|
||||
$log-success-background-color: #0ef75b33;
|
||||
|
||||
// old variables
|
||||
$accent-color: #6f42c1;
|
||||
|
|
|
@ -35,6 +35,18 @@
|
|||
--icon-default-color: #{$icon-default-color};
|
||||
--icon-default-color-active: #{$icon-default-color-active};
|
||||
|
||||
// log colors
|
||||
--default-text-color: #{$log-default-text-color};
|
||||
--log-info-text-color: #{$log-info-text-color};
|
||||
--log-warn-text-color: #{$log-warn-text-color};
|
||||
--log-error-text-color: #{$log-error-text-color};
|
||||
--log-success-text-color: #{$log-success-text-color};
|
||||
--log-default-background-color: #{$log-default-backgroung-color};
|
||||
--log-info-background-color: #{$log-info-background-color};
|
||||
--log-warn-background-color: #{$log-warn-background-color};
|
||||
--log-error-background-color: #{$log-error-background-color};
|
||||
--log-success-background-color: #{$log-success-background-color};
|
||||
|
||||
// old colors
|
||||
--accent-color: #{$accent-color};
|
||||
--accent-gradient-color: #{$acent-gradient};
|
||||
|
@ -85,6 +97,18 @@
|
|||
--icon-default-color: #{$icon-default-color-dark};
|
||||
--icon-default-color-active: #{$icon-default-color-active-dark};
|
||||
|
||||
// log colors
|
||||
--default-text-color: #{$log-default-text-color-dark};
|
||||
--log-info-text-color: #{$log-info-text-color-dark};
|
||||
--log-warn-text-color: #{$log-warn-text-color-dark};
|
||||
--log-error-text-color: #{$log-error-text-color-dark};
|
||||
--log-success-text-color: #{$log-success-text-color-dark};
|
||||
--log-default-background-color: #{$log-default-backgroung-color};
|
||||
--log-info-background-color: #{$log-info-background-color};
|
||||
--log-warn-background-color: #{$log-warn-background-color};
|
||||
--log-error-background-color: #{$log-error-background-color};
|
||||
--log-success-background-color: #{$log-success-background-color};
|
||||
|
||||
// old colors
|
||||
--accent-color: #{$accent-color-dark};
|
||||
--accent-gradient-color: #{$acent-gradient-dark};
|
||||
|
@ -115,7 +139,6 @@
|
|||
}
|
||||
|
||||
body {
|
||||
background: var(--background-color);
|
||||
--font-size-tiny: #{$tiny};
|
||||
--font-size-small: #{$small};
|
||||
--font-size-regular: #{$regular};
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
padding: 3px 6px;
|
||||
border-radius: 12px;
|
||||
color: var(--text-color);
|
||||
backdrop-filter: blur(14px);
|
||||
|
||||
.selector {
|
||||
color: var(--text-color);
|
||||
|
@ -33,24 +34,55 @@
|
|||
.logs-wrapper {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
position: relative;
|
||||
|
||||
// dummy
|
||||
.bg-dummy{
|
||||
background: var(--background-color-solid);
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
.bg-dummy.left-top{
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
width: 60%;
|
||||
height: 16px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.bg-dummy.right-bottom{
|
||||
right: 68px;
|
||||
bottom: 0;
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 12px;
|
||||
@include flex-center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.logs-detail,
|
||||
.version {
|
||||
@include flex-center;
|
||||
border-radius: 12px;
|
||||
background: var(--background-color);
|
||||
padding: 3px 6px;
|
||||
height: 100%;
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.logs-detail {
|
||||
padding: 2px 12px;
|
||||
cursor: pointer;
|
||||
outline: 0 solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
.log-icon {
|
||||
@include flex-center;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
max-width: 40vw;
|
||||
white-space: nowrap;
|
||||
|
@ -60,12 +92,51 @@
|
|||
}
|
||||
|
||||
.version {
|
||||
background: var(--background-color);
|
||||
font-size: var(--font-size-tiny);
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
.icon{
|
||||
|
||||
.icon {
|
||||
@include flex-center;
|
||||
}
|
||||
}
|
||||
|
||||
.log {
|
||||
background: var(--log-default-background-color);
|
||||
outline-color: var(--default-text-color);
|
||||
.log-message {
|
||||
color: var(--default-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
background: var(--log-info-background-color);
|
||||
outline-color: var(--log-info-text-color);
|
||||
.log-message {
|
||||
color: var(--log-info-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background: var(--log-error-background-color);
|
||||
outline-color: var(--log-error-text-color);
|
||||
.log-message {
|
||||
color: var(--log-error-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: var(--log-warn-background-color);
|
||||
outline-color: var(--log-warn-text-color);
|
||||
.log-message {
|
||||
color: var(--log-warn-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
background: var(--log-success-background-color);
|
||||
.log-message {
|
||||
color: var(--log-success-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
@use "../abstracts/variables" as *;
|
||||
@use "../abstracts/mixins" as *;
|
||||
|
||||
.rename-tool-tip {
|
||||
position: absolute;
|
||||
background: var(--background-color);
|
||||
padding: 10px 16px;
|
||||
width: 260px;
|
||||
border-radius: #{$border-radius-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
z-index: 100;
|
||||
.header {
|
||||
@include flex-center;
|
||||
gap: 8px;
|
||||
.icon {
|
||||
@include flex-center;
|
||||
}
|
||||
.name {
|
||||
color: var(--text-color);
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
@use "../abstracts/mixins" as *;
|
||||
|
||||
.dropdown-list-container {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
.lists-container {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
@ -44,7 +42,7 @@
|
|||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: #{$border-radius-large};
|
||||
.zone-header{
|
||||
.zone-header {
|
||||
@include flex-center;
|
||||
.value {
|
||||
width: 100%;
|
||||
|
@ -62,16 +60,10 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&:first-child{
|
||||
background: var(--highlight-accent-color);
|
||||
.input-value{
|
||||
color: var(--highlight-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.active {
|
||||
background: var(--highlight-accent-color);
|
||||
.input-value{
|
||||
.input-value {
|
||||
color: var(--highlight-text-color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,11 @@
|
|||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: var(--background-color-secondary);
|
||||
@include flex-center;
|
||||
|
||||
.log-list-wrapper {
|
||||
height: 50%;
|
||||
min-width: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 60%;
|
||||
min-width: 55%;
|
||||
z-index: 5;
|
||||
background: var(--background-color);
|
||||
padding: 14px 12px;
|
||||
|
@ -31,6 +28,10 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
.icon {
|
||||
@include flex-center;
|
||||
scale: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
|
@ -38,47 +39,66 @@
|
|||
height: 28px;
|
||||
width: 28px;
|
||||
cursor: pointer;
|
||||
border-radius: #{$border-radius-medium};
|
||||
svg {
|
||||
scale: 1.6;
|
||||
}
|
||||
&:hover {
|
||||
background: var(--background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.log-nav-container {
|
||||
@include flex-space-between;
|
||||
align-items: flex-end;
|
||||
.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);
|
||||
}
|
||||
}
|
||||
.clear-button{
|
||||
margin: 0 6px;
|
||||
padding: 4px 12px;
|
||||
color: var(--text-disabled);
|
||||
border-radius: #{$border-radius-large};
|
||||
&:hover{
|
||||
font-weight: 300;
|
||||
color: var(--text-color);
|
||||
background: var(--background-color-solid-gradient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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%;
|
||||
height: calc(100% - 80px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: var(--background-color);
|
||||
padding: 18px 10px;
|
||||
padding: 10px;
|
||||
border-radius: 16px;
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
overflow: auto;
|
||||
|
||||
.log-entry {
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: var(--font-size-small);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.log-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
@include flex-center;
|
||||
}
|
||||
.log-entry-message-container {
|
||||
|
@ -90,8 +110,9 @@
|
|||
font-weight: 300;
|
||||
opacity: 0.8;
|
||||
text-wrap: nowrap;
|
||||
height: 100%;
|
||||
}
|
||||
.log-entry-message{
|
||||
.log-entry-message {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -101,5 +122,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.no-log{
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,18 +11,12 @@
|
|||
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;
|
||||
margin: 8px;
|
||||
pointer-events: all;
|
||||
|
||||
.analysis-card-wrapper {
|
||||
|
@ -47,6 +41,13 @@
|
|||
line-height: 20px;
|
||||
font-size: var(--font-size-regular);
|
||||
}
|
||||
|
||||
.sub-header {
|
||||
font-weight: 300;
|
||||
font-size: var(--font-size-tiny);
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.process-container {
|
||||
|
@ -97,7 +98,7 @@
|
|||
|
||||
.metrics-section {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--background-color-gray);
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
.metric {
|
||||
display: flex;
|
||||
|
@ -116,7 +117,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.throughoutSummary-wrapper {
|
||||
|
||||
.production-wrapper {
|
||||
.process-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -127,7 +129,7 @@
|
|||
|
||||
.throughput-value {
|
||||
font-size: var(--font-size-small);
|
||||
flex: 1;
|
||||
flex: 0.8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
@ -139,15 +141,24 @@
|
|||
}
|
||||
|
||||
.lineChart {
|
||||
flex: 1.2;
|
||||
max-width: 200px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
background-image: radial-gradient(#8d8d8da4 1px, transparent 1px);
|
||||
background-size: 10px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
.assetUsage {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
.key,
|
||||
.value {
|
||||
font-size: var(--font-size-regular);
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
|
@ -163,12 +174,13 @@
|
|||
|
||||
.footer-card {
|
||||
width: 100%;
|
||||
background: var(--background-color-gray);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
background: var(--background-color);
|
||||
border-radius: #{$border-radius-large};
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 12px;
|
||||
outline: 1px solid var(--border-color);
|
||||
|
||||
&:first-child {
|
||||
width: 85%;
|
||||
|
@ -225,7 +237,7 @@
|
|||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 6px;
|
||||
|
||||
padding-top: 3px;
|
||||
.shift-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -251,7 +263,8 @@
|
|||
}
|
||||
|
||||
label {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 200;
|
||||
font-size: var(--font-size-tiny);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -268,19 +281,29 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roiSummary-wrapper {
|
||||
max-width: 470px;
|
||||
background-color: var(--background-color);
|
||||
|
||||
.product-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.playBack {
|
||||
display: flex;
|
||||
background-color: var(--background-color);
|
||||
border-radius: 12px;
|
||||
padding: 6px;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border-radius: 66px;
|
||||
background: var(--background-color);
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
svg {
|
||||
scale: 0.8;
|
||||
}
|
||||
|
||||
.info {
|
||||
span {
|
||||
|
@ -313,10 +336,12 @@
|
|||
flex-direction: column;
|
||||
gap: 3px;
|
||||
align-items: center;
|
||||
font-weight: 300;
|
||||
|
||||
.key {
|
||||
font-size: var(--font-size-xlarge);
|
||||
color: var(--accent-color);
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-large);
|
||||
color: #28b9f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,8 +388,9 @@
|
|||
gap: 6px;
|
||||
|
||||
.metric-item {
|
||||
padding: 8px;
|
||||
border-radius: #{$border-radius-large};
|
||||
background-color: var(--background-color);
|
||||
background: var(--background-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
@ -372,10 +398,12 @@
|
|||
}
|
||||
|
||||
.cost-breakdown {
|
||||
background-color: var(--background-color);
|
||||
background: var(--background-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
max-height: 20vh;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
|
||||
.breakdown-header {
|
||||
display: flex;
|
||||
|
@ -385,7 +413,7 @@
|
|||
|
||||
.section-wrapper {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
@ -427,6 +455,7 @@
|
|||
text-align: left;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
@ -434,8 +463,8 @@
|
|||
}
|
||||
|
||||
.tips-section {
|
||||
background-color: var(--background-color);
|
||||
border-radius: #{$border-radius-large};
|
||||
background: var(--background-color);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -445,7 +474,10 @@
|
|||
.tip-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: 4px;
|
||||
.lightbulb-icon{
|
||||
@include flex-center;
|
||||
}
|
||||
.tip-title {
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
|
@ -490,43 +522,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.semi-circle-wrapper {
|
||||
width: 100%;
|
||||
height: 125px;
|
||||
overflow-y: hidden;
|
||||
.svg-half-donut {
|
||||
position: relative;
|
||||
.semi-circle {
|
||||
|
||||
.label-wrapper {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
bottom: 2px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.label-wrapper {
|
||||
.label {
|
||||
font-size: var(--font-size-xxxlarge);
|
||||
.label {
|
||||
font-size: var(--font-size-xxlarge);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -554,4 +565,3 @@
|
|||
}
|
||||
|
||||
// Breakdown Table Open/Close Logic
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
.simulation-player-wrapper {
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
z-index: 2;
|
||||
transform: translate(-50%, 0);
|
||||
|
@ -35,11 +35,11 @@
|
|||
@include flex-space-between;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
.header{
|
||||
.header {
|
||||
@include flex-center;
|
||||
gap: 6px;
|
||||
padding: 0 8px;
|
||||
svg{
|
||||
svg {
|
||||
scale: 1.3;
|
||||
}
|
||||
}
|
||||
|
@ -144,14 +144,21 @@
|
|||
|
||||
&::after {
|
||||
content: "";
|
||||
background-color: #e5e5ea;
|
||||
background: var(--background-color-solid);
|
||||
opacity: 0.4;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.custom-slider-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0 26px;
|
||||
background: transparent;
|
||||
border-radius: #{$border-radius-large};
|
||||
}
|
||||
.custom-slider {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
@ -185,11 +192,13 @@
|
|||
|
||||
.marker {
|
||||
position: absolute;
|
||||
background-color: var(--text-disabled);
|
||||
background: var(--background-color-solid);
|
||||
opacity: 0.6;
|
||||
width: 2px;
|
||||
height: 12px;
|
||||
border-radius: 1px;
|
||||
top: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.marker.marker-10 {
|
||||
|
@ -299,6 +308,37 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.open {
|
||||
.start-displayer,
|
||||
.end-displayer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timmer {
|
||||
display: none;
|
||||
}
|
||||
.progresser-wrapper {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.time-displayer {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.processDisplayer {
|
||||
padding: 0 8px;
|
||||
background: transparent;
|
||||
|
||||
.process-player {
|
||||
width: 0;
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.processDisplayer {
|
||||
|
@ -314,22 +354,6 @@
|
|||
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;
|
||||
|
@ -351,12 +375,12 @@
|
|||
border-width: 1px;
|
||||
background: var(--background-color-accent, #6f42c1);
|
||||
}
|
||||
.process-wrapper{
|
||||
.process-wrapper {
|
||||
.process-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
|
||||
.process {
|
||||
height: 5px;
|
||||
border-radius: 4px;
|
||||
|
@ -368,35 +392,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.simulation-player-container.open {
|
||||
|
||||
.start-displayer,
|
||||
.end-displayer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timmer {
|
||||
display: none;
|
||||
}
|
||||
.progresser-wrapper {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.time-displayer {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.processDisplayer {
|
||||
padding: 0 8px;
|
||||
background: transparent;
|
||||
|
||||
.process-player {
|
||||
width: 0;
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
width: fit-content;
|
||||
transition: width 0.2s;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(20px);
|
||||
z-index: 2;
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
|
@ -31,10 +31,12 @@
|
|||
.activeDropicon {
|
||||
@include flex-center;
|
||||
gap: 2px;
|
||||
// stylelint-disable-next-line
|
||||
interpolate-size: allow-keywords;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
animation: expandWidth 0.2s ease-in-out forwards;
|
||||
will-change: width;
|
||||
|
||||
.tool-button {
|
||||
@include flex-center;
|
||||
|
@ -44,9 +46,11 @@
|
|||
border-radius: #{$border-radius-medium};
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,9 +74,11 @@
|
|||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.drop-down-container {
|
||||
|
@ -179,11 +185,11 @@
|
|||
font-weight: 500;
|
||||
background: var(--accent-color);
|
||||
color: var(--highlight-accent-color);
|
||||
&::after{
|
||||
&::after {
|
||||
animation: pulse 1s ease-out infinite;
|
||||
}
|
||||
}
|
||||
&::after{
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
@ -195,14 +201,14 @@
|
|||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%{
|
||||
0% {
|
||||
opacity: 0;
|
||||
scale: .5;
|
||||
scale: 0.5;
|
||||
}
|
||||
50%{
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
100% {
|
||||
opacity: 0;
|
||||
scale: 2;
|
||||
}
|
||||
|
@ -218,4 +224,4 @@
|
|||
width: fit-content;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
top: 32px;
|
||||
left: 8px;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(15px);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
|
@ -250,7 +250,7 @@
|
|||
top: 32px;
|
||||
right: 8px;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(15px);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
|
@ -363,7 +363,7 @@
|
|||
height: 34px;
|
||||
width: 34px;
|
||||
border-radius: #{$border-radius-circle};
|
||||
background: var(--background-color-secondary);
|
||||
background: var(--background-color-solid-gradient);
|
||||
backdrop-filter: blur(12px);
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
|
|
|
@ -67,8 +67,8 @@
|
|||
min-width: 150px;
|
||||
z-index: 3;
|
||||
transform: translate(-50%, -10%);
|
||||
transition: transform 0.5s linear;
|
||||
pointer-events: all;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
|
@ -243,6 +243,7 @@
|
|||
&:hover {
|
||||
background: var(--highlight-accent-color);
|
||||
width: 100%;
|
||||
|
||||
.label {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
@ -619,6 +620,7 @@
|
|||
.label {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
background: var(--highlight-accent-color);
|
||||
width: 100%;
|
||||
|
||||
|
@ -738,10 +740,10 @@
|
|||
outline-color: #ffe3e0;
|
||||
}
|
||||
|
||||
.editWidgetOptions {
|
||||
.context-menu-options {
|
||||
position: absolute;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(20px);
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -749,6 +751,7 @@
|
|||
overflow: hidden;
|
||||
padding: 4px;
|
||||
min-width: 150px;
|
||||
outline: 1px solid var(--border-color);
|
||||
|
||||
.option {
|
||||
padding: 4px 10px;
|
||||
|
@ -933,4 +936,4 @@
|
|||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,16 +48,16 @@
|
|||
max-width: 350px;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--accent-color);
|
||||
border: 1px solid var(--highlight-text-color);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
background: transparent;
|
||||
color: var(--accent-color);
|
||||
color: var(--highlight-text-color);
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
.google-icon {
|
||||
color: var(--accent-color);
|
||||
color: var(--highlight-text-color);
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
|
@ -89,7 +89,7 @@
|
|||
border-radius: #{$border-radius-extra-large};
|
||||
background: var(--background-color);
|
||||
font-size: 14px;
|
||||
color: var(--input-text-color);
|
||||
color: var(--text-button-color);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--accent-color);
|
||||
|
@ -120,8 +120,8 @@
|
|||
.continue-button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: var(--accent-gradient-color);
|
||||
color: var(--background-color);
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
@ -158,7 +158,7 @@
|
|||
text-align: center;
|
||||
|
||||
.link {
|
||||
color: var(--accent-color);
|
||||
color: var(--highlight-text-color);
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
|
Loading…
Reference in New Issue