Merge branch 'main' into v2

This commit is contained in:
Jerald-Golden-B 2025-05-03 18:36:51 +05:30
commit b85ff07a15
38 changed files with 2194 additions and 1592 deletions

View File

@ -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

View File

@ -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 />;
}
};

View File

@ -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"
/>

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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;

View File

@ -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"

View File

@ -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>

View File

@ -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}>

View File

@ -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();

View File

@ -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>

View File

@ -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>
)}
{/* )} */}
</>
);
};

View File

@ -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;
}

View File

@ -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';

View File

@ -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();

View File

@ -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;

View File

@ -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 (

View File

@ -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;

View File

@ -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;

View File

@ -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 (
<>

View File

@ -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("/");
}

View File

@ -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;

View File

@ -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};

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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;