diff --git a/app/src/components/icons/ContextMenuIcons.tsx b/app/src/components/icons/ContextMenuIcons.tsx index a1ced1b..217ad8f 100644 --- a/app/src/components/icons/ContextMenuIcons.tsx +++ b/app/src/components/icons/ContextMenuIcons.tsx @@ -42,44 +42,46 @@ export function FlipXAxisIcon() { } export function FlipYAxisIcon() { - <svg - width="12" - height="12" - viewBox="0 0 12 12" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M9.0568 11H2.94321C2.09213 11 1.6666 11 1.52703 10.7288C1.38746 10.4576 1.6348 10.1113 2.12947 9.41877L2.6942 8.62814C2.91415 8.32021 3.02413 8.16625 3.18565 8.08313C3.34718 8 3.53639 8 3.9148 8H8.08525C8.46365 8 8.65285 8 8.8144 8.08313C8.9759 8.16625 9.0859 8.32021 9.30585 8.62814L9.87055 9.41876C10.3653 10.1113 10.6126 10.4576 10.473 10.7288C10.3335 11 9.9079 11 9.0568 11Z" - fill="var(--text-color)" - /> - <path - d="M2.99935 3.15388L2.99936 3.1539C3.11318 3.31326 3.18452 3.41252 3.2461 3.48409C3.30271 3.54989 3.33407 3.57152 3.3572 3.58344L2.99935 3.15388ZM2.99935 3.15388L2.43462 2.36329C2.43462 2.36329 2.43462 2.36328 2.43462 2.36328C2.17987 2.00663 2.01464 1.7736 1.92406 1.59704C1.88093 1.51299 1.86801 1.46687 1.86465 1.44495C1.88444 1.43495 1.92948 1.41866 2.02293 1.4049C2.21925 1.37599 2.50491 1.375 2.94321 1.375L9.0568 1.375C9.49511 1.375 9.78078 1.37599 9.9771 1.4049C10.0705 1.41866 10.1156 1.43494 10.1354 1.44495C10.132 1.46687 10.1191 1.51299 10.076 1.59705C9.98539 1.7736 9.82016 2.00663 9.5654 2.36328L9.5654 2.36329L9.0007 3.15389L9.00069 3.15389M2.99935 3.15388L9.00069 3.15389M9.00069 3.15389C8.88688 3.31325 8.81553 3.41252 8.75395 3.48409C8.69737 3.54985 8.666 3.5715 8.64287 3.58343C8.61972 3.59534 8.5839 3.60831 8.49735 3.61614C8.40331 3.62465 8.28108 3.625 8.08525 3.625L3.9148 3.625C3.71896 3.625 3.59672 3.62465 3.50269 3.61614M9.00069 3.15389L3.50269 3.61614M3.50269 3.61614C3.41623 3.60832 3.38041 3.59537 3.35729 3.58348L3.50269 3.61614ZM1.86435 1.42845C1.86468 1.42867 1.86476 1.43108 1.86363 1.43501C1.86345 1.4302 1.86402 1.42823 1.86435 1.42845ZM1.85597 1.4499C1.85343 1.45311 1.85142 1.45444 1.85105 1.4543C1.85068 1.45416 1.85195 1.45255 1.85597 1.4499ZM10.149 1.45429C10.1486 1.45443 10.1466 1.4531 10.1441 1.44989C10.1481 1.45254 10.1493 1.45415 10.149 1.45429ZM10.1364 1.435C10.1353 1.43107 10.1353 1.42867 10.1357 1.42845C10.136 1.42823 10.1366 1.4302 10.1364 1.435Z" - stroke="var(--text-color)" - strokeWidth="0.75" - /> - <path - opacity="0.5" - d="M7 6H5" - stroke="var(--text-color)" - strokeWidth="0.75" - strokeLinecap="round" - /> - <path - opacity="0.5" - d="M3 6H1" - stroke="var(--text-color)" - strokeWidth="0.75" - strokeLinecap="round" - /> - <path - opacity="0.5" - d="M11 6H9" - stroke="var(--text-color)" - strokeWidth="0.75" - strokeLinecap="round" - /> - </svg>; + return ( + <svg + width="12" + height="12" + viewBox="0 0 12 12" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9.0568 11H2.94321C2.09213 11 1.6666 11 1.52703 10.7288C1.38746 10.4576 1.6348 10.1113 2.12947 9.41877L2.6942 8.62814C2.91415 8.32021 3.02413 8.16625 3.18565 8.08313C3.34718 8 3.53639 8 3.9148 8H8.08525C8.46365 8 8.65285 8 8.8144 8.08313C8.9759 8.16625 9.0859 8.32021 9.30585 8.62814L9.87055 9.41876C10.3653 10.1113 10.6126 10.4576 10.473 10.7288C10.3335 11 9.9079 11 9.0568 11Z" + fill="var(--text-color)" + /> + <path + d="M2.99935 3.15388L2.99936 3.1539C3.11318 3.31326 3.18452 3.41252 3.2461 3.48409C3.30271 3.54989 3.33407 3.57152 3.3572 3.58344L2.99935 3.15388ZM2.99935 3.15388L2.43462 2.36329C2.43462 2.36329 2.43462 2.36328 2.43462 2.36328C2.17987 2.00663 2.01464 1.7736 1.92406 1.59704C1.88093 1.51299 1.86801 1.46687 1.86465 1.44495C1.88444 1.43495 1.92948 1.41866 2.02293 1.4049C2.21925 1.37599 2.50491 1.375 2.94321 1.375L9.0568 1.375C9.49511 1.375 9.78078 1.37599 9.9771 1.4049C10.0705 1.41866 10.1156 1.43494 10.1354 1.44495C10.132 1.46687 10.1191 1.51299 10.076 1.59705C9.98539 1.7736 9.82016 2.00663 9.5654 2.36328L9.5654 2.36329L9.0007 3.15389L9.00069 3.15389M2.99935 3.15388L9.00069 3.15389M9.00069 3.15389C8.88688 3.31325 8.81553 3.41252 8.75395 3.48409C8.69737 3.54985 8.666 3.5715 8.64287 3.58343C8.61972 3.59534 8.5839 3.60831 8.49735 3.61614C8.40331 3.62465 8.28108 3.625 8.08525 3.625L3.9148 3.625C3.71896 3.625 3.59672 3.62465 3.50269 3.61614M9.00069 3.15389L3.50269 3.61614M3.50269 3.61614C3.41623 3.60832 3.38041 3.59537 3.35729 3.58348L3.50269 3.61614ZM1.86435 1.42845C1.86468 1.42867 1.86476 1.43108 1.86363 1.43501C1.86345 1.4302 1.86402 1.42823 1.86435 1.42845ZM1.85597 1.4499C1.85343 1.45311 1.85142 1.45444 1.85105 1.4543C1.85068 1.45416 1.85195 1.45255 1.85597 1.4499ZM10.149 1.45429C10.1486 1.45443 10.1466 1.4531 10.1441 1.44989C10.1481 1.45254 10.1493 1.45415 10.149 1.45429ZM10.1364 1.435C10.1353 1.43107 10.1353 1.42867 10.1357 1.42845C10.136 1.42823 10.1366 1.4302 10.1364 1.435Z" + stroke="var(--text-color)" + strokeWidth="0.75" + /> + <path + opacity="0.5" + d="M7 6H5" + stroke="var(--text-color)" + strokeWidth="0.75" + strokeLinecap="round" + /> + <path + opacity="0.5" + d="M3 6H1" + stroke="var(--text-color)" + strokeWidth="0.75" + strokeLinecap="round" + /> + <path + opacity="0.5" + d="M11 6H9" + stroke="var(--text-color)" + strokeWidth="0.75" + strokeLinecap="round" + /> + </svg> + ); } export function FlipZAxisIcon() { return ( diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index 6e73c0e..fcf7202 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -168,20 +168,6 @@ export function AddIcon() { ); } -export function RmoveIcon() { - return ( - <svg - width="12" - height="12" - viewBox="0 0 12 12" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <path d="M3 6.5H9" stroke="var(--text-color)" strokeLinecap="round" /> - </svg> - ); -} - export function CloseIcon() { return ( <svg diff --git a/app/src/components/icons/Logo.tsx b/app/src/components/icons/Logo.tsx index 06ed750..d15398c 100644 --- a/app/src/components/icons/Logo.tsx +++ b/app/src/components/icons/Logo.tsx @@ -124,7 +124,6 @@ export function LogoIconLarge() { fill="none" xmlns="http://www.w3.org/2000/svg" > - <circle cx="45" cy="45" r="45" fill="#FCFDFD" /> <circle cx="45.1957" cy="45.1957" diff --git a/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx b/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx index b89bfb2..ed22a9a 100644 --- a/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx +++ b/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx @@ -23,16 +23,16 @@ const MarketPlaceBanner = () => { <path d="M167.189 2C154.638 36.335 104.466 106.204 4.18872 111" stroke="white" - stroke-width="3" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="3" + strokeLinecap="round" + strokeLinejoin="round" /> <path d="M10.662 118.326L1.59439 111.524L9.47334 103.374" stroke="white" - stroke-width="3" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="3" + strokeLinecap="round" + strokeLinejoin="round" /> </svg> </div> diff --git a/app/src/components/layout/Dashboard/SidePannel.tsx b/app/src/components/layout/Dashboard/SidePannel.tsx index d44c72c..5d69ffc 100644 --- a/app/src/components/layout/Dashboard/SidePannel.tsx +++ b/app/src/components/layout/Dashboard/SidePannel.tsx @@ -11,7 +11,7 @@ import { import { SettingsIcon, TrashIcon } from "../../icons/ExportCommonIcons"; const SidePannel: React.FC = () => { - const userName = localStorage.getItem("userName") || "Anonymous"; + const userName = localStorage.getItem("userName") ?? "Anonymous"; return ( <div className="side-pannel-container"> <div className="side-pannel-header"> diff --git a/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx b/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx index 078f27d..4774bad 100644 --- a/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx +++ b/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx @@ -17,12 +17,12 @@ const ConfirmationPopup: React.FC<ConfirmationPopupProps> = ({ <div className="confirmation-modal"> <p className="message">{message}</p> <div className="buttton-wrapper"> - <div className="confirmation-button" onClick={onConfirm}> + <button className="confirmation-button" onClick={onConfirm}> OK - </div> - <div className="confirmation-button" onClick={onCancel}> + </button> + <button className="confirmation-button" onClick={onCancel}> Cancel - </div> + </button> </div> </div> </div> diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx index dc412f7..e0b56d4 100644 --- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx +++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx @@ -5,8 +5,8 @@ import Header from "./Header"; import useToggleStore from "../../../store/useUIToggleStore"; import Assets from "./Assets"; import useModuleStore from "../../../store/useModuleStore"; -import Widgets from ".//visualization/widgets/Widgets"; -import Templates from "../../../modules//visualization/template/Templates"; +import Widgets from "./visualization/widgets/Widgets"; +import Templates from "../../../modules/visualization/template/Templates"; import Search from "../../ui/inputs/Search"; const SideBarLeft: React.FC = () => { diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx index 9d09291..0e10cf1 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useMemo } from "react"; import { Chart } from "chart.js/auto"; -// import { useThemeStore } from "../../../../../store/useThemeStore"; // Define Props Interface interface ChartComponentProps { @@ -29,7 +28,6 @@ const ChartComponent = ({ data: propsData, }: ChartComponentProps) => { const canvasRef = useRef<HTMLCanvasElement>(null); - // const { themeColor } = useThemeStore(); // Memoize Theme Colors to Prevent Unnecessary Recalculations // const buttonActionColor = useMemo( @@ -66,7 +64,7 @@ const ChartComponent = ({ // Memoize Chart Font Style const chartFontStyle = useMemo( () => ({ - family: fontFamily || "Arial", + family: fontFamily ?? "Arial", size: fontSizeValue, weight: fontWeightValue, color: "#2B3344", diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx index d4a64ae..655e641 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import ToggleHeader from "../../../../ui/inputs/ToggleHeader"; import Widgets2D from "./Widgets2D"; import Widgets3D from "./Widgets3D"; @@ -6,7 +5,6 @@ import WidgetsFloating from "./WidgetsFloating"; import { useWidgetSubOption } from "../../../../../store/store"; const Widgets = () => { - const [activeOption, setActiveOption] = useState("2D"); const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); const handleToggleClick = (option: string) => { diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx index 50d9712..4c085aa 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx @@ -5,45 +5,14 @@ import { GlobeIcon, WalletIcon, } from "../../../../icons/3dChartIcons"; -import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard"; +import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard"; import WarehouseThroughput from "../../../../../modules//visualization/widgets/floating/cards/WarehouseThroughput"; -import ProductivityDashboard from "../../../../../modules//visualization/widgets/floating/cards/ProductivityDashboard"; import FleetEfficiency from "../../../../../modules//visualization/widgets/floating/cards/FleetEfficiency"; -interface Widget { - id: string; - name: string; -} - const WidgetsFloating = () => { - // const [widgets, setWidgets] = useState<Widget[]>([ - // { id: "1", name: "Working State Widget" }, - // { id: "2", name: "Floating Widget 2" }, - // { id: "3", name: "Floating Widget 3" }, - // { id: "4", name: "Floating Widget 4" }, - // ]); - - // Function to handle drag start - const handleDragStart = ( - e: React.DragEvent<HTMLDivElement>, - widget: Widget - ) => { - e.dataTransfer.setData("application/json", JSON.stringify(widget)); - }; - return ( <div className="floatingWidgets-wrapper widgets-wrapper"> - {/* {widgets.map((widget) => ( - <div - key={widget.id} - className="floating" - draggable - onDragStart={(e) => handleDragStart(e, widget)} - > - {widget.name} - </div> - ))} */} {/* Floating 1 */} <SimpleCard header={"Today’s Earnings"} diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index 277637a..cb9b5dc 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import Header from "./Header"; import useModuleStore, { useSubModuleStore, @@ -14,6 +14,10 @@ import Visualization from "./visualization/Visualization"; import Analysis from "./analysis/Analysis"; import Simulations from "./simulation/Simulations"; import { useSelectedFloorItem } from "../../../store/store"; +import { + useSelectedEventData, + useSelectedEventSphere, +} from "../../../store/simulation/useSimulationStore"; import GlobalProperties from "./properties/GlobalProperties"; import AsstePropertiies from "./properties/AssetProperties"; import ZoneProperties from "./properties/ZoneProperties"; @@ -24,88 +28,70 @@ const SideBarRight: React.FC = () => { const { toggleUI } = useToggleStore(); const { subModule, setSubModule } = useSubModuleStore(); const { selectedFloorItem } = useSelectedFloorItem(); + const { selectedEventData } = useSelectedEventData(); + const { selectedEventSphere } = useSelectedEventSphere(); // Reset activeList whenever activeModule changes useEffect(() => { if (activeModule !== "simulation") setSubModule("properties"); - if (activeModule === "simulation") setSubModule("mechanics"); - }, [activeModule]); + if (activeModule === "simulation") setSubModule("simulations"); + }, [activeModule, setSubModule]); - // romove late - const dummyData = { - assetType: "store", - selectedPoint: { - name: "Point A", - uuid: "123e4567-e89b-12d3-a456-426614174000", - actions: [ - { - uuid: "action-1", - name: "Action One", - }, - { - uuid: "action-2", - name: "Action Two", - }, - { - uuid: "action-3", - name: "Action Three", - }, - ], - }, - selectedItem: { - item: { - uuid: "item-1", - name: "Item One", - isUsed: false, - }, - }, - setSelectedPoint: (value: string) => { - console.log(`Selected point updated to: ${value}`); - }, - selectedActionSphere: "Sphere A", - }; + useEffect(() => { + if ( + activeModule !== "mechanics" && + selectedEventData && + selectedEventSphere + ) { + setSubModule("mechanics"); + } else if (!selectedEventData && !selectedEventSphere) { + if (activeModule === "simulation") { + setSubModule("simulations"); + } + } + }, [activeModule, selectedEventData, selectedEventSphere, setSubModule]); return ( <div className="sidebar-right-wrapper"> <Header /> {toggleUI && ( <div className="sidebar-actions-container"> - {/* {activeModule === "builder" && ( */} - <div - className={`sidebar-action-list ${ - subModule === "properties" ? "active" : "" - }`} - onClick={() => setSubModule("properties")} - > - <PropertiesIcon isActive={subModule === "properties"} /> - </div> - {/* )} */} + {activeModule !== "simulation" && ( + <button + className={`sidebar-action-list ${ + subModule === "properties" ? "active" : "" + }`} + onClick={() => setSubModule("properties")} + > + <PropertiesIcon isActive={subModule === "properties"} /> + </button> + )} {activeModule === "simulation" && ( <> - <div - className={`sidebar-action-list ${ - subModule === "mechanics" ? "active" : "" - }`} - onClick={() => setSubModule("mechanics")} - > - <MechanicsIcon isActive={subModule === "mechanics"} /> - </div> - <div + <button className={`sidebar-action-list ${ subModule === "simulations" ? "active" : "" }`} onClick={() => setSubModule("simulations")} > <SimulationIcon isActive={subModule === "simulations"} /> - </div> - <div + </button> + <button + className={`sidebar-action-list ${ + subModule === "mechanics" ? "active" : "" + }`} + onClick={() => setSubModule("mechanics")} + > + <MechanicsIcon isActive={subModule === "mechanics"} /> + </button> + <button className={`sidebar-action-list ${ subModule === "analysis" ? "active" : "" }`} onClick={() => setSubModule("analysis")} > <AnalysisIcon isActive={subModule === "analysis"} /> - </div> + </button> </> )} </div> @@ -141,13 +127,19 @@ const SideBarRight: React.FC = () => { </div> )} {/* simulation */} - {toggleUI && activeModule === "simulation" && ( <> + {subModule === "simulations" && ( + <div className="sidebar-right-container"> + <div className="sidebar-right-content-container"> + <Simulations /> + </div> + </div> + )} {subModule === "mechanics" && ( <div className="sidebar-right-container"> <div className="sidebar-right-content-container"> - <EventProperties {...dummyData} /> + <EventProperties /> </div> </div> )} @@ -158,16 +150,8 @@ const SideBarRight: React.FC = () => { </div> </div> )} - {subModule === "simulations" && ( - <div className="sidebar-right-container"> - <div className="sidebar-right-content-container"> - <Simulations /> - </div> - </div> - )} </> )} - {/* realtime visualization */} {toggleUI && activeModule === "visualization" && <Visualization />} </div> diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index c3ec6e7..847d035 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -1,208 +1,114 @@ -import React, { useRef, useState } from "react"; -import InputWithDropDown from "../../../../ui/inputs/InputWithDropDown"; -import LabledDropdown from "../../../../ui/inputs/LabledDropdown"; +import React, { useEffect, useState } from "react"; import { - AddIcon, - RemoveIcon, - ResizeHeightIcon, -} from "../../../../icons/ExportCommonIcons"; -import RenameInput from "../../../../ui/inputs/RenameInput"; -import { handleResize } from "../../../../../functions/handleResizePannel"; -import { handleActionToggle } from "./functions/handleActionToggle"; -import { handleDeleteAction } from "./functions/handleDeleteAction"; -import DefaultAction from "./actions/DefaultAction"; -import SpawnAction from "./actions/SpawnAction"; -import SwapAction from "./actions/SwapAction"; -import DespawnAction from "./actions/DespawnAction"; -import TravelAction from "./actions/TravelAction"; -import PickAndPlaceAction from "./actions/PickAndPlaceAction"; -import ProcessAction from "./actions/ProcessAction"; -import StorageAction from "./actions/StorageAction"; -import Trigger from "./trigger/Trigger"; + useSelectedEventData, + useSelectedEventSphere, + useSelectedProduct, +} from "../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../store/simulation/useProductStore"; +import ConveyorMechanics from "./mechanics/conveyorMechanics"; +import VehicleMechanics from "./mechanics/vehicleMechanics"; +import RoboticArmMechanics from "./mechanics/roboticArmMechanics"; +import MachineMechanics from "./mechanics/machineMechanics"; +import StorageMechanics from "./mechanics/storageMechanics"; +import { AddIcon } from "../../../../icons/ExportCommonIcons"; -interface EventPropertiesProps { - assetType: string; - selectedPoint: { - name: string; - uuid: string; - actions: { - uuid: string; - name: string; - }[]; +const EventProperties: React.FC = () => { + const { selectedEventData } = useSelectedEventData(); + const { getEventByModelUuid } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>( + null + ); + const [assetType, setAssetType] = useState<string | null>(null); + const { products } = useProductStore(); + const { selectedEventSphere } = useSelectedEventSphere(); + useEffect(() => { + const event = getCurrentEventData(); + setCurrentEventData(event); + + const type = determineAssetType(event); + setAssetType(type); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedEventData, selectedProduct]); + + const getCurrentEventData = () => { + if (!selectedEventData?.data || !selectedProduct) return null; + return ( + getEventByModelUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid + ) ?? null + ); }; - selectedItem: { - item: { - uuid: string; - name: string; - } | null; - }; - setSelectedPoint: (value: string) => void; - selectedActionSphere: string; -} -const EventProperties: React.FC<EventPropertiesProps> = ({ - assetType, - selectedPoint, - selectedItem, - setSelectedPoint, - selectedActionSphere, -}) => { - const actionsContainerRef = useRef<HTMLDivElement>(null); + const determineAssetType = (event: EventsSchema | null) => { + if (!event) return null; - const [activeOption, setActiveOption] = useState("default"); - const [dummyactiveOption, setTypeOption] = useState("default"); - - const getAvailableActions = () => { - if (assetType === "conveyor") { - return { - defaultOption: "default", - options: ["default", "spawn", "swap", "despawn"], - }; - } - if (assetType === "vehicle") { - return { - defaultOption: "travel", - options: ["travel"], - }; - } - if (assetType === "roboticArm") { - return { - defaultOption: "pickAndPlace", - options: ["pickAndPlace"], - }; - } - if (assetType === "machine") { - return { - defaultOption: "process", - options: ["process"], - }; - } - if (assetType === "store") { - return { - defaultOption: "store", - options: ["store", "spawn"], - }; - } else { - return { - defaultOption: "default", - options: ["default"], - }; + switch (event.type) { + case "transfer": + return "conveyor"; + case "vehicle": + return "vehicle"; + case "roboticArm": + return "roboticArm"; + case "machine": + return "machine"; + case "storageUnit": + return "storageUnit"; + default: + return null; } }; return ( <div className="event-proprties-wrapper"> - <div className="header"> - <div className="header-value">{selectedPoint.name}</div> - </div> - <div className="global-props"> - <div className="property-list-container"> - {/* <div className="property-item"> - <LabledDropdown - defaultOption={assetType} - options={[]} - onSelect={(option) => setTypeOption(option)} - /> - </div> */} - <div className="property-item"> - <InputWithDropDown - label="Speed" - value="0.5" - min={0} - step={0.1} - defaultValue="0.5" - max={10} - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - </div> - <div className="property-item"> - <InputWithDropDown - label="Delay" - value="0.5" - min={0} - step={0.1} - defaultValue="0.5" - max={10} - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - </div> - </div> - </div> - <div className="actions-list-container"> - <div className="actions"> + {currentEventData && ( + <> <div className="header"> - <div className="header-value">Actions</div> - <div className="add-button" onClick={() => {}}> - <AddIcon /> Add + <div className="header-value"> + {selectedEventData?.data.modelName} </div> </div> - <div - className="lists-main-container" - ref={actionsContainerRef} - style={{ height: "120px" }} - > - <div className="list-container"> - {selectedPoint?.actions.map((action) => ( - <div - key={action.uuid} - className={`list-item ${ - selectedItem.item?.uuid === action.uuid ? "active" : "" - }`} - > - <div - className="value" - onClick={() => handleActionToggle(action.uuid)} - > - <RenameInput value={action.name} /> - </div> - {selectedPoint?.actions.length > 1 && ( - <div - className="remove-button" - onClick={() => handleDeleteAction(action.uuid)} - > - <RemoveIcon /> - </div> - )} - </div> + {assetType === "conveyor" && <ConveyorMechanics />} + {assetType === "vehicle" && <VehicleMechanics />} + {assetType === "roboticArm" && <RoboticArmMechanics />} + {assetType === "machine" && <MachineMechanics />} + {assetType === "storageUnit" && <StorageMechanics />} + </> + )} + {!currentEventData && selectedEventSphere && ( + <div className="no-event-selected"> + <p> + <strong>Oops!</strong> It looks like this object doesn't have an + event assigned yet. To continue, please link it to one of the + products below. + </p> + + <div className="products-list"> + <p> + <strong>Here are some products you can add it to:</strong> + </p> + <ul> + {products.map((product) => ( + <li key={product.productId}> + <button> + <AddIcon /> + {product.productName} + </button> + </li> ))} - </div> - <div - className="resize-icon" - id="action-resize" - onMouseDown={(e) => handleResize(e, actionsContainerRef)} - > - <ResizeHeightIcon /> - </div> + </ul> </div> </div> - </div> - <div className="selected-actions-details"> - <div className="selected-actions-header"> - <RenameInput value="Action Name" /> + )} + {!selectedEventSphere && ( + <div className="no-event-selected"> + <p> + <strong>Oops!</strong> It looks like you haven't selected an event + point yet. Please select an event to view its properties. + </p> </div> - <div className="selected-actions-list"> - <LabledDropdown - defaultOption={getAvailableActions().defaultOption} - options={getAvailableActions().options} - onSelect={(option) => setActiveOption(option)} - /> - {activeOption === "default" && <DefaultAction />} {/* done */} - {activeOption === "spawn" && <SpawnAction />} {/* done */} - {activeOption === "swap" && <SwapAction />} {/* done */} - {activeOption === "despawn" && <DespawnAction />} {/* done */} - {activeOption === "travel" && <TravelAction />} {/* done */} - {activeOption === "pickAndPlace" && <PickAndPlaceAction />} {/* done */} - {activeOption === "process" && <ProcessAction />} {/* done */} - {activeOption === "store" && <StorageAction />} {/* done */} - </div> - </div> - <div className="tirgger"> - <Trigger /> - </div> + )} </div> ); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx new file mode 100644 index 0000000..6c33583 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface DelayActionProps { + value: string; + defaultValue: string; + min: number; + max: number; + onChange: (value: string) => void; +} + +const DelayAction: React.FC<DelayActionProps> = ({ + value, + defaultValue, + min, + max, + onChange, +}) => { + return ( + <InputWithDropDown + label="Delay" + value={value} + min={min} + step={0.1} + defaultValue={defaultValue} + max={max} + activeOption="s" + onClick={() => {}} + onChange={onChange} + /> + ); +}; + +export default DelayAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx index a0f081f..88cbd2e 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx @@ -1,20 +1,8 @@ import React from "react"; -import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; const DespawnAction: React.FC = () => { return ( <> - <InputWithDropDown - label="Delay" - value="" - min={0} - step={0.1} - max={10} - defaultValue="0" - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> </> ); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx index 9574669..175a824 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx @@ -1,13 +1,25 @@ import React from "react"; import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; -const PickAndPlaceAction: React.FC = () => { - return ( - <> - <EyeDropInput label="Pick Point" value="na" onChange={() => {}} /> - <EyeDropInput label="Unload Point" value="na" onChange={() => {}} /> - </> - ); +interface PickAndPlaceActionProps { + pickPointValue: string; + pickPointOnChange: (value: string) => void; + placePointValue: string; + placePointOnChange: (value: string) => void; +} + +const PickAndPlaceAction: React.FC<PickAndPlaceActionProps> = ({ + pickPointValue, + pickPointOnChange, + placePointValue, + placePointOnChange, +}) => { + return ( + <> + <EyeDropInput label="Pick Point" value={pickPointValue} onChange={pickPointOnChange} /> + <EyeDropInput label="Unload Point" value={placePointValue} onChange={placePointOnChange} /> + </> + ); }; export default PickAndPlaceAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx index a27894e..331cf1b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx @@ -2,23 +2,47 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import SwapAction from "./SwapAction"; -const ProcessAction: React.FC = () => { - return ( - <> - <InputWithDropDown - label="Process Time" - value="6" - min={0} - step={1} - max={10} - defaultValue="0" - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - <SwapAction /> - </> - ); +interface ProcessActionProps { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + swapOptions: string[]; + swapDefaultOption: string; + onSwapSelect: (value: string) => void; +} + +const ProcessAction: React.FC<ProcessActionProps> = ({ + value, + min, + max, + defaultValue, + onChange, + swapOptions, + swapDefaultOption, + onSwapSelect, +}) => { + return ( + <> + <InputWithDropDown + label="Process Time" + value={value} + min={min} + step={1} + max={max} + defaultValue={defaultValue} + activeOption="s" + onClick={() => { }} + onChange={onChange} + /> + <SwapAction + options={swapOptions} + defaultOption={swapDefaultOption} + onSelect={onSwapSelect} + /> + </> + ); }; export default ProcessAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx index 0c37cdb..7d8002e 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx @@ -1,35 +1,72 @@ import React from "react"; import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; -const SpawnAction: React.FC = () => { - return ( - <> - <InputWithDropDown - label="Spawn interval" - value="0" - min={0} - step={1} - defaultValue="0" - max={10} - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - <InputWithDropDown - label="Spawn count" - value="0" - min={0} - step={1} - defaultValue="0" - max={10} - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - <PreviewSelectionWithUpload /> - </> - ); +interface SpawnActionProps { + onChangeInterval: (value: string) => void; + onChangeCount: (value: string) => void; + defaultOption: string; + options: string[]; + onSelect: (option: string) => void; + intervalValue: string; + countValue: string; + intervalMin: number; + intervalMax: number; + intervalDefaultValue: string; + countMin: number; + countMax: number; + countDefaultValue: string; +} + +const SpawnAction: React.FC<SpawnActionProps> = ({ + onChangeInterval, + onChangeCount, + defaultOption, + options, + onSelect, + intervalValue, + countValue, + intervalMin, + intervalMax, + intervalDefaultValue, + countMin, + countMax, + countDefaultValue, +}) => { + return ( + <> + <InputWithDropDown + label="Spawn interval" + value={intervalValue} + min={intervalMin} + step={1} + defaultValue={intervalDefaultValue} + max={intervalMax} + activeOption="s" + onClick={() => { }} + onChange={onChangeInterval} + /> + <InputWithDropDown + label="Spawn count" + value={countValue} + min={countMin} + step={1} + defaultValue={countDefaultValue} + max={countMax} + activeOption="s" + onClick={() => { }} + onChange={onChangeCount} + /> + {/* <PreviewSelectionWithUpload /> */} + <LabledDropdown + label="Presets" + defaultOption={defaultOption} + options={options} + onSelect={onSelect} + /> + </> + ); }; export default SpawnAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx index ab2109b..5c9bf86 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx @@ -1,20 +1,28 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -const StorageAction: React.FC = () => { - return ( - <InputWithDropDown - label="Storage Capacity" - value="" - min={0} - step={0.1} - max={10} - defaultValue="0" - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - ); +interface StorageActionProps { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; +} + +const StorageAction: React.FC<StorageActionProps> = ({ value, min, max, defaultValue, onChange }) => { + return ( + <InputWithDropDown + label="Storage Capacity" + value={value} + min={min} + step={1} + max={max} + defaultValue={defaultValue} + activeOption="s" + onClick={() => { }} + onChange={onChange} + /> + ); }; export default StorageAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx index 2e18d80..5eaf991 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx @@ -1,11 +1,24 @@ import React from "react"; import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; -const SwapAction: React.FC = () => { +interface SwapActionProps { + onSelect: (option: string) => void; + defaultOption: string; + options: string[]; +} + +const SwapAction: React.FC<SwapActionProps> = ({ + onSelect, + defaultOption, + options, +}) => { return ( - <> - <PreviewSelectionWithUpload /> - </> + <PreviewSelectionWithUpload + label="Presets" + defaultOption={defaultOption} + options={options} + onSelect={onSelect} + /> ); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx index ee4bda0..49eb683 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx @@ -2,35 +2,77 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; -const TravelAction: React.FC = () => { - return ( - <> - <InputWithDropDown - label="Load Capacity" - value="" - min={0} - step={0.1} - max={10} - defaultValue="0" - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - <InputWithDropDown - label="Unload Duration" - value="" - min={0} - step={0.1} - max={10} - defaultValue="0" - activeOption="s" - onClick={() => {}} - onChange={(value) => console.log(value)} - /> - <EyeDropInput label="Pick Point" value="na" onChange={() => {}} /> - <EyeDropInput label="Unload Point" value="na" onChange={() => {}} /> - </> - ); +interface TravelActionProps { + loadCapacity: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + unloadDuration: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + pickPoint?: { + value: string; + onChange: (value: string) => void; + }; + unloadPoint?: { + value: string; + onChange: (value: string) => void; + }; +} + +const TravelAction: React.FC<TravelActionProps> = ({ + loadCapacity, + unloadDuration, + pickPoint, + unloadPoint, +}) => { + return ( + <> + <InputWithDropDown + label="Load Capacity" + value={loadCapacity.value} + min={loadCapacity.min} + max={loadCapacity.max} + defaultValue={loadCapacity.defaultValue} + step={0.1} + activeOption="s" + onClick={() => { }} + onChange={loadCapacity.onChange} + /> + <InputWithDropDown + label="Unload Duration" + value={unloadDuration.value} + min={unloadDuration.min} + max={unloadDuration.max} + defaultValue={unloadDuration.defaultValue} + step={0.1} + activeOption="s" + onClick={() => { }} + onChange={unloadDuration.onChange} + /> + {pickPoint && ( + <EyeDropInput + label="Pick Point" + value={pickPoint.value} + onChange={pickPoint.onChange} + /> + )} + {unloadPoint && ( + <EyeDropInput + label="Unload Point" + value={unloadPoint.value} + onChange={unloadPoint.onChange} + /> + )} + </> + ); }; export default TravelAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx new file mode 100644 index 0000000..6d2c3de --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx @@ -0,0 +1,202 @@ +import React, { useRef } from "react"; +import { + AddIcon, + RemoveIcon, + ResizeHeightIcon, +} from "../../../../../icons/ExportCommonIcons"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import { handleResize } from "../../../../../../functions/handleResizePannel"; +import { + useSelectedAction, + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { MathUtils } from "three"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; + +interface ActionsListProps { + setSelectedPointData: (data: any) => void; // You can replace `any` with a more specific type if you have one + selectedPointData: any; // You can replace `any` with a more specific type if you have one + // ui control props + multipleAction?: boolean; +} + +const ActionsList: React.FC<ActionsListProps> = ({ + setSelectedPointData, + selectedPointData, + multipleAction = false, +}) => { + const actionsContainerRef = useRef<HTMLDivElement>(null); + + // store + const { selectedEventData } = useSelectedEventData(); + const { updateAction, addAction, removeAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAction, setSelectedAction, clearSelectedAction } = + useSelectedAction(); + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "pickAndPlace" as const, + process: { + startPoint: null, + endPoint: null, + }, + triggers: [] as TriggerSchema[], + }; + + addAction( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + const updatedPoint = { + ...selectedPointData, + actions: [...selectedPointData.actions, newAction], + }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + removeAction(actionUuid); + const newActions = selectedPointData.actions.filter( + (a: any) => a.actionUuid !== actionUuid + ); + + const updatedPoint = { + ...selectedPointData, + actions: newActions, + }; + setSelectedPointData(updatedPoint); + + if (selectedAction.actionId === actionUuid) { + if (newActions.length > 0) { + setSelectedAction(newActions[0].actionUuid, newActions[0].actionName); + } else { + clearSelectedAction(); + } + } + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + updateAction(selectedAction.actionId, { actionName: newName }); + + if (selectedPointData?.actions) { + const updatedActions = selectedPointData.actions.map((action: any) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); + } else { + // write logic for single action + return; + } + }; + + const handleActionSelect = (actionUuid: string, actionName: string) => { + setSelectedAction(actionUuid, actionName); + }; + + return ( + <div className="actions-list-container"> + <div className="actions"> + <div className="header"> + <div className="header-value">Actions</div> + + <button + className="add-button" + onClick={() => handleAddAction()} + disabled={!multipleAction} + > + <AddIcon /> Add + </button> + </div> + <div + className="lists-main-container" + ref={actionsContainerRef} + style={{ height: "120px" }} + > + <div className="list-container"> + {multipleAction && + selectedPointData.actions.map((action: any) => ( + <div + key={action.actionUuid} + className={`list-item ${ + selectedAction.actionId === action.actionUuid + ? "active" + : "" + }`} + > + <button + className="value" + onClick={() => + handleActionSelect(action.actionUuid, action.actionName) + } + > + <RenameInput + value={action.actionName} + onRename={handleRenameAction} + /> + </button> + {selectedPointData.actions.length > 1 && ( + <button + className="remove-button" + onClick={() => handleDeleteAction(action.actionUuid)} + > + <RemoveIcon /> + </button> + )} + </div> + ))} + {!multipleAction && selectedPointData && ( + <div + key={selectedPointData.action.actionUuid} + className={`list-item active`} + > + <button + className="value" + onClick={() => + handleActionSelect( + selectedPointData.action.actionUuid, + selectedPointData.action.actionName + ) + } + > + <RenameInput + value={selectedPointData.action.actionName} + onRename={handleRenameAction} + /> + </button> + </div> + )} + </div> + {multipleAction && ( + <button + className="resize-icon" + id="action-resize" + onMouseDown={(e: any) => handleResize(e, actionsContainerRef)} + > + <ResizeHeightIcon /> + </button> + )} + </div> + </div> + </div> + ); +}; + +export default ActionsList; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx new file mode 100644 index 0000000..b370b3e --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -0,0 +1,224 @@ +import { useEffect, useState } from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import DelayAction from "../actions/DelayAction"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import DespawnAction from "../actions/DespawnAction"; +import SwapAction from "../actions/SwapAction"; +import SpawnAction from "../actions/SpawnAction"; +import DefaultAction from "../actions/DefaultAction"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import ActionsList from "../components/ActionsList"; + +function ConveyorMechanics() { + const [activeOption, setActiveOption] = useState< + "default" | "spawn" | "swap" | "delay" | "despawn" + >("default"); + const [selectedPointData, setSelectedPointData] = useState< + ConveyorPointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as ConveyorPointSchema | undefined; + if (point && "action" in point) { + setSelectedPointData(point); + setActiveOption( + point.action.actionType as + | "default" + | "spawn" + | "swap" + | "delay" + | "despawn" + ); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + speed: parseFloat(value), + }); + }; + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as + | "default" + | "spawn" + | "swap" + | "delay" + | "despawn"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleSpawnCountChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + spawnCount: value === "inherit" ? "inherit" : parseFloat(value), + }); + }; + + const handleSpawnIntervalChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + spawnInterval: value === "inherit" ? "inherit" : parseFloat(value), + }); + }; + + const handleMaterialSelect = (material: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { material }); + }; + + const handleDelayChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + delay: value === "inherit" ? "inherit" : parseFloat(value), + }); + }; + + const availableActions = { + defaultOption: "default", + options: ["default", "spawn", "swap", "delay", "despawn"], + }; + + // Get current values from store + const currentSpeed = + selectedEventData?.data.type === "transfer" + ? selectedEventData.data.speed.toString() + : "0.5"; + + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentMaterial = selectedPointData + ? selectedPointData.action.material + : "Default material"; + + const currentSpawnCount = selectedPointData + ? selectedPointData.action.spawnCount?.toString() || "1" + : "1"; + + const currentSpawnInterval = selectedPointData + ? selectedPointData.action.spawnInterval?.toString() || "1" + : "1"; + + const currentDelay = selectedPointData + ? selectedPointData.action.delay?.toString() || "0" + : "0"; + + return ( + <> + {selectedEventData && ( + <> + <div key={selectedPointData?.uuid} className="global-props"> + <div className="property-list-container"> + <div className="property-item"> + <InputWithDropDown + label="Speed" + value={currentSpeed} + min={0} + step={0.1} + defaultValue={"0.5"} + max={10} + activeOption="m/s" + onClick={() => {}} + onChange={handleSpeedChange} + /> + </div> + </div> + </div> + + <ActionsList + setSelectedPointData={setSelectedPointData} + selectedPointData={selectedPointData} + /> + + <div className="selected-actions-details"> + <div className="selected-actions-header"> + <RenameInput + value={currentActionName} + onRename={handleRenameAction} + /> + </div> + <div className="selected-actions-list"> + <LabledDropdown + defaultOption={ + selectedPointData + ? selectedPointData.action.actionType + : "default" + } + options={availableActions.options} + onSelect={handleActionTypeChange} + /> + {activeOption === "default" && <DefaultAction />} + {activeOption === "spawn" && ( + <SpawnAction + onChangeCount={handleSpawnCountChange} + options={["Default material", "Material 1", "Material 2"]} + defaultOption={currentMaterial} + onSelect={handleMaterialSelect} + onChangeInterval={handleSpawnIntervalChange} + intervalValue={currentSpawnInterval} + countValue={currentSpawnCount} + intervalMin={1} + intervalMax={60} + intervalDefaultValue="1" + countMin={1} + countMax={100} + countDefaultValue="1" + /> + )} + {activeOption === "swap" && ( + <SwapAction + options={["Default material", "Material 1", "Material 2"]} + defaultOption={currentMaterial} + onSelect={handleMaterialSelect} + /> + )} + {activeOption === "despawn" && <DespawnAction />} + {activeOption === "delay" && ( + <DelayAction + value={currentDelay} + defaultValue="0" + min={0} + max={60} + onChange={handleDelayChange} + /> + )} + </div> + </div> + <div className="tirgger"> + <Trigger /> + </div> + </> + )} + </> + ); +} + +export default ConveyorMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx new file mode 100644 index 0000000..fa2cfdf --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from "react"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import ProcessAction from "../actions/ProcessAction"; +import ActionsList from "../components/ActionsList"; + +function MachineMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "process">( + "default" + ); + const [selectedPointData, setSelectedPointData] = useState< + MachinePointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as MachinePointSchema | undefined; + if (point && "action" in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "process"); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "process"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleProcessTimeChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + processTime: parseFloat(value), + }); + }; + + const handleMaterialSelect = (material: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + swapMaterial: material, + }); + }; + + // Get current values from store + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentProcessTime = selectedPointData + ? selectedPointData.action.processTime.toString() + : "1"; + + const currentMaterial = selectedPointData + ? selectedPointData.action.swapMaterial + : "Default material"; + + const availableActions = { + defaultOption: "process", + options: ["process"], + }; + + return ( + <> + {selectedEventData && ( + <> + <div className="selected-actions-details"> + <div className="selected-actions-header"> + <RenameInput + value={currentActionName} + onRename={handleRenameAction} + /> + </div> + <ActionsList + setSelectedPointData={setSelectedPointData} + selectedPointData={selectedPointData} + /> + <div className="selected-actions-list"> + <LabledDropdown + defaultOption="process" + options={availableActions.options} + onSelect={handleActionTypeChange} + /> + {activeOption === "process" && ( + <ProcessAction + value={currentProcessTime} + min={0.1} + max={60} + defaultValue="1" + onChange={handleProcessTimeChange} + swapOptions={["Default material", "Material 1", "Material 2"]} + swapDefaultOption={currentMaterial} + onSwapSelect={handleMaterialSelect} + /> + )} + </div> + </div> + <div className="tirgger"> + <Trigger /> + </div> + </> + )} + </> + ); +} + +export default MachineMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx new file mode 100644 index 0000000..7c20ce5 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -0,0 +1,194 @@ +import { useEffect, useState } from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, + useSelectedAction, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import PickAndPlaceAction from "../actions/PickAndPlaceAction"; +import ActionsList from "../components/ActionsList"; + +function RoboticArmMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "pickAndPlace">( + "default" + ); + const [selectedPointData, setSelectedPointData] = useState< + RoboticArmPointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAction, setSelectedAction, clearSelectedAction } = + useSelectedAction(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as RoboticArmPointSchema | undefined; + if (point?.actions) { + setSelectedPointData(point); + setActiveOption( + point.actions[0].actionType as "default" | "pickAndPlace" + ); + if (point.actions.length > 0 && !selectedAction.actionId) { + setSelectedAction( + point.actions[0].actionUuid, + point.actions[0].actionName + ); + } + } + } else { + clearSelectedAction(); + } + }, [ + clearSelectedAction, + getPointByUuid, + selectedAction.actionId, + selectedEventData, + selectedProduct, + setSelectedAction, + ]); + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + updateAction(selectedAction.actionId, { actionName: newName }); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); + } + }; + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + speed: parseFloat(value), + }); + }; + + const handlePickPointChange = (value: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + + updateAction(selectedAction.actionId, { + process: { + startPoint: [x, y, z] as [number, number, number], + endPoint: + selectedPointData.actions.find( + (a) => a.actionUuid === selectedAction.actionId + )?.process.endPoint || null, + }, + }); + }; + + const handlePlacePointChange = (value: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + + updateAction(selectedAction.actionId, { + process: { + startPoint: + selectedPointData.actions.find( + (a) => a.actionUuid === selectedAction.actionId + )?.process.startPoint || null, + endPoint: [x, y, z] as [number, number, number], + }, + }); + }; + + const availableActions = { + defaultOption: "pickAndPlace", + options: ["pickAndPlace"], + }; + + const currentSpeed = + selectedEventData?.data.type === "roboticArm" + ? selectedEventData.data.speed.toString() + : "0.5"; + + const currentAction = selectedPointData?.actions.find( + (a) => a.actionUuid === selectedAction.actionId + ); + const currentPickPoint = currentAction?.process.startPoint + ? `${currentAction.process.startPoint[0]},${currentAction.process.startPoint[1]},${currentAction.process.startPoint[2]}` + : ""; + const currentPlacePoint = currentAction?.process.endPoint + ? `${currentAction.process.endPoint[0]},${currentAction.process.endPoint[1]},${currentAction.process.endPoint[2]}` + : ""; + + return ( + <> + {selectedEventData && selectedPointData && ( + <> + <div className="global-props"> + <div className="property-list-container"> + <div className="property-item"> + <InputWithDropDown + label="Speed" + value={currentSpeed} + min={0} + step={0.1} + defaultValue={"0.5"} + max={10} + activeOption="m/s" + onClick={() => {}} + onChange={handleSpeedChange} + /> + </div> + </div> + </div> + + <ActionsList + setSelectedPointData={setSelectedPointData} + selectedPointData={selectedPointData} + multipleAction + /> + + {selectedAction.actionId && currentAction && ( + <div className="selected-actions-details"> + <div className="selected-actions-header"> + <RenameInput + value={selectedAction.actionName} + onRename={handleRenameAction} + /> + </div> + <div className="selected-actions-list"> + <LabledDropdown + defaultOption={activeOption} + options={availableActions.options} + onSelect={() => {}} + disabled={true} + /> + <PickAndPlaceAction + pickPointValue={currentPickPoint} + pickPointOnChange={handlePickPointChange} + placePointValue={currentPlacePoint} + placePointOnChange={handlePlacePointChange} + /> + </div> + <div className="tirgger"> + <Trigger /> + </div> + </div> + )} + </> + )} + </> + ); +} + +export default RoboticArmMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx new file mode 100644 index 0000000..d92ed80 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from "react"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import StorageAction from "../actions/StorageAction"; +import ActionsList from "../components/ActionsList"; + +function StorageMechanics() { + const [activeOption, setActiveOption] = useState< + "default" | "store" | "spawn" + >("default"); + const [selectedPointData, setSelectedPointData] = useState< + StoragePointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as StoragePointSchema | undefined; + if (point && "action" in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "store" | "spawn"); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "store" | "spawn"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleCapacityChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + storageCapacity: parseInt(value), + }); + }; + + // Get current values from store + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentCapacity = selectedPointData + ? selectedPointData.action.storageCapacity.toString() + : "0"; + + const availableActions = { + defaultOption: "store", + options: ["store", "spawn"], + }; + + return ( + <> + {selectedEventData && ( + <> + <ActionsList + setSelectedPointData={setSelectedPointData} + selectedPointData={selectedPointData} + /> + <div className="selected-actions-details"> + <div className="selected-actions-header"> + <RenameInput + value={currentActionName} + onRename={handleRenameAction} + /> + </div> + <div className="selected-actions-list"> + <LabledDropdown + defaultOption={activeOption} + options={availableActions.options} + onSelect={handleActionTypeChange} + /> + {activeOption === "store" && ( + <StorageAction + value={currentCapacity} + defaultValue="0" + min={0} + max={20} + onChange={handleCapacityChange} + /> + )} + {activeOption === "spawn" && ( + <div className="spawn-options"> + <p>Spawn configuration options would go here</p> + </div> + )} + </div> + </div> + <div className="tirgger"> + <Trigger /> + </div> + </> + )} + </> + ); +} + +export default StorageMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx new file mode 100644 index 0000000..4524ceb --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -0,0 +1,199 @@ +import { useEffect, useState } from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import TravelAction from "../actions/TravelAction"; +import ActionsList from "../components/ActionsList"; + +function VehicleMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "travel">( + "default" + ); + const [selectedPointData, setSelectedPointData] = useState< + VehiclePointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as VehiclePointSchema | undefined; + + if (point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "travel"); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + speed: parseFloat(value), + }); + }; + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "travel"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleLoadCapacityChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + loadCapacity: parseFloat(value), + }); + }; + + const handleUnloadDurationChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + unLoadDuration: parseFloat(value), + }); + }; + + const handlePickPointChange = (value: string) => { + if (!selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + updateAction(selectedPointData.action.actionUuid, { + pickUpPoint: { x, y, z }, + }); + }; + + const handleUnloadPointChange = (value: string) => { + if (!selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + updateAction(selectedPointData.action.actionUuid, { + unLoadPoint: { x, y, z }, + }); + }; + + // Get current values from store + const currentSpeed = + selectedEventData?.data.type === "vehicle" + ? selectedEventData.data.speed.toString() + : "0.5"; + + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentLoadCapacity = selectedPointData + ? selectedPointData.action.loadCapacity.toString() + : "1"; + + const currentUnloadDuration = selectedPointData + ? selectedPointData.action.unLoadDuration.toString() + : "1"; + + const currentPickPoint = selectedPointData?.action.pickUpPoint + ? `${selectedPointData.action.pickUpPoint.x},${selectedPointData.action.pickUpPoint.y},${selectedPointData.action.pickUpPoint.z}` + : ""; + + const currentUnloadPoint = selectedPointData?.action.unLoadPoint + ? `${selectedPointData.action.unLoadPoint.x},${selectedPointData.action.unLoadPoint.y},${selectedPointData.action.unLoadPoint.z}` + : ""; + + const availableActions = { + defaultOption: "travel", + options: ["travel"], + }; + + return ( + <> + {selectedEventData && ( + <> + <div className="global-props"> + <div className="property-list-container"> + <div className="property-item"> + <InputWithDropDown + label="Speed" + value={currentSpeed} + min={0} + step={0.1} + defaultValue={"0.5"} + max={10} + activeOption="m/s" + onClick={() => {}} + onChange={handleSpeedChange} + /> + </div> + </div> + </div> + <ActionsList + setSelectedPointData={setSelectedPointData} + selectedPointData={selectedPointData} + /> + <div className="selected-actions-details"> + <div className="selected-actions-header"> + <RenameInput + value={currentActionName} + onRename={handleRenameAction} + /> + </div> + <div className="selected-actions-list"> + <LabledDropdown + defaultOption="travel" + options={availableActions.options} + onSelect={handleActionTypeChange} + /> + + {activeOption === "travel" && ( + <TravelAction + loadCapacity={{ + value: currentLoadCapacity, + min: 1, + max: 100, + defaultValue: "1", + onChange: handleLoadCapacityChange, + }} + unloadDuration={{ + value: currentUnloadDuration, + min: 1, + max: 60, + defaultValue: "1", + onChange: handleUnloadDurationChange, + }} + // pickPoint={{ + // value: currentPickPoint, + // onChange: handlePickPointChange, + // }} + // unloadPoint={{ + // value: currentUnloadPoint, + // onChange: handleUnloadPointChange, + // }} + /> + )} + </div> + </div> + <div className="tirgger"> + <Trigger /> + </div> + </> + )} + </> + ); +} + +export default VehicleMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index f287b63..3b2549a 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -1,11 +1,19 @@ -import React, { useState } from "react"; -import { AddIcon, RemoveIcon } from "../../../../../icons/ExportCommonIcons"; +import React, { useRef, useState } from "react"; +import { + AddIcon, + RemoveIcon, + ResizeHeightIcon, +} from "../../../../../icons/ExportCommonIcons"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import { handleResize } from "../../../../../../functions/handleResizePannel"; const Trigger: React.FC = () => { // State to hold the list of triggers - const [triggers, setTriggers] = useState<string[]>([]); + const [triggers, setTriggers] = useState<string[]>(["Trigger 1"]); + const [selectedTrigger, setSelectedTrigger] = useState<string>("Trigger 1"); const [activeOption, setActiveOption] = useState("onComplete"); + const triggersContainerRef = useRef<HTMLDivElement>(null); // States for dropdowns const [triggeredModel, setTriggeredModel] = useState<string[]>([]); @@ -35,70 +43,94 @@ const Trigger: React.FC = () => { <div className="trigger-wrapper"> <div className="header"> <div className="title">Trigger</div> - <div + <button className="add-button" onClick={addTrigger} style={{ cursor: "pointer" }} > <AddIcon /> Add - </div> + </button> </div> <div className="trigger-list"> - {/* Map over triggers and render them */} - {triggers.map((trigger, index) => ( - <div key={index} className="trigger-item"> - <div className="trigger-name"> - {trigger} + <div + className="lists-main-container" + ref={triggersContainerRef} + style={{ height: "120px" }} + > + <div className="list-container"> + {triggers.map((trigger: any, index: number) => ( <div - className="remove-button" - onClick={() => removeTrigger(index)} - style={{ cursor: "pointer" }} + key={index} + className={`list-item ${ + selectedTrigger === trigger ? "active" : "" + }`} + onClick={() => setSelectedTrigger(trigger)} > - <RemoveIcon /> + <button className="value" onClick={() => {}}> + <RenameInput value={trigger} onRename={() => {}} /> + </button> + {triggers.length > 1 && ( + <button + className="remove-button" + onClick={() => removeTrigger(index)} + > + <RemoveIcon /> + </button> + )} </div> + ))} + </div> + <button + className="resize-icon" + id="action-resize" + onMouseDown={(e: any) => handleResize(e, triggersContainerRef)} + > + <ResizeHeightIcon /> + </button> + </div> + <div className="trigger-item"> + <div className="trigger-name">{selectedTrigger}</div> + <LabledDropdown + defaultOption={activeOption} + options={["onComplete", "onStart", "onStop", "delay"]} + onSelect={(option) => setActiveOption(option)} + /> + <div className="trigger-options"> + <div> + <LabledDropdown + defaultOption={triggeredModel[0] || "Select Model"} + options={["Model 1", "Model 2", "Model 3"]} + onSelect={(option) => { + const newModel = [...triggeredModel]; + newModel[0] = option; + setTriggeredModel(newModel); + }} + /> </div> - <LabledDropdown - defaultOption={activeOption} - options={["onComplete", "onStart", "onStop", "delay"]} - onSelect={(option) => setActiveOption(option)} - /> - <div className="trigger-options"> - <div> - <LabledDropdown - defaultOption={triggeredModel[index] || "Select Model"} - options={["Model 1", "Model 2", "Model 3"]} - onSelect={(option) => { - const newModel = [...triggeredModel]; - newModel[index] = option; - setTriggeredModel(newModel); - }} - /> - </div> - <div> - <LabledDropdown - defaultOption={triggeredPoint[index] || "Select Point"} - options={["Point 1", "Point 2", "Point 3"]} - onSelect={(option) => { - const newPoint = [...triggeredPoint]; - newPoint[index] = option; - setTriggeredPoint(newPoint); - }} - /> - </div> - <div> - <LabledDropdown - defaultOption={triggeredAction[index] || "Select Action"} - options={["Action 1", "Action 2", "Action 3"]} - onSelect={(option) => { - const newAction = [...triggeredAction]; - newAction[index] = option; - setTriggeredAction(newAction); - }} - /> - </div> + <div> + <LabledDropdown + defaultOption={triggeredPoint[0] || "Select Point"} + options={["Point 1", "Point 2", "Point 3"]} + onSelect={(option) => { + const newPoint = [...triggeredPoint]; + newPoint[0] = option; + setTriggeredPoint(newPoint); + }} + /> + </div> + <div> + <LabledDropdown + defaultOption={triggeredAction[0] || "Select Action"} + options={["Action 1", "Action 2", "Action 3"]} + onSelect={(option) => { + const newAction = [...triggeredAction]; + newAction[0] = option; + setTriggeredAction(newAction); + }} + /> </div> </div> - ))} + </div> </div> </div> ); diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 2a12734..926ce44 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -1,147 +1,204 @@ -import React, { useRef, useState } from "react"; +import React, { useEffect, useRef } 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 } from "../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../store/simulation/useProductStore"; +import { generateUUID } from "three/src/math/MathUtils"; +import RenderOverlay from "../../../templates/Overlay"; +import EditWidgetOption from "../../../ui/menu/EditWidgetOption"; +import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; -interface Path { - pathName: string; // Represents the name of the path - Children: string[]; // Represents the list of child points +interface Event { + pathName: string; } -interface DropListProps { - val: Path; // Use the Path interface for the val prop +interface ListProps { + val: Event; } -const DropList: React.FC<DropListProps> = ({ val }) => { - const [openDrop, setOpenDrop] = useState(false); - return ( - <div className="process-container"> - <div - className="value" - onClick={() => { - setOpenDrop(!openDrop); - }} - > - {val.pathName} - <div className={`arrow-container${openDrop ? " active" : ""}`}> - <ArrowIcon /> - </div> - </div> - {val.Children && openDrop && ( - <div className="children-drop"> - {val.Children.map((child, index) => ( - <div key={index} className="value"> - {child} +const List: React.FC<ListProps> = ({ val }) => { + return ( + <div className="process-container"> + <div className="value"> + {val.pathName} </div> - ))} </div> - )} - </div> - ); + ); }; const Simulations: React.FC = () => { - const productsContainerRef = useRef<HTMLDivElement>(null); - const [productsList, setProductsList] = useState<string[]>([]); - const [selectedItem, setSelectedItem] = useState<string>(); + const productsContainerRef = useRef<HTMLDivElement>(null); + const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent } = useProductStore(); + const { selectedProduct, setSelectedProduct } = useSelectedProduct(); + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); - const handleAddAction = () => { - setProductsList([...productsList, `Product ${productsList.length + 1}`]); - }; + const handleAddProduct = () => { + addProduct(`Product ${products.length + 1}`, generateUUID()); + }; - const handleRemoveAction = (index: number) => { - setProductsList(productsList.filter((_, i) => i !== index)); - if (selectedItem === productsList[index]) { - setSelectedItem(""); - } - }; + const handleRemoveProduct = (productId: string) => { + const currentIndex = products.findIndex(p => p.productId === productId); + const isSelected = selectedProduct.productId === productId; - const Value = [ - { pathName: "Path 1", Children: ["Point 1", "Point 2"] }, - { pathName: "Path 2", Children: ["Point 1", "Point 2"] }, - { pathName: "Path 3", Children: ["Point 1", "Point 2"] }, - ]; + const updatedProducts = products.filter(p => p.productId !== productId); - return ( - <div className="simulations-container"> - <div className="header">Simulations</div> - <div className="add-product-container"> - <div className="actions"> - <div className="header"> - <div className="header-value">Products</div> - <div className="add-button" onClick={handleAddAction}> - <AddIcon /> Add - </div> - </div> - <div - className="lists-main-container" - ref={productsContainerRef} - style={{ height: "120px" }} - > - <div className="list-container"> - {productsList.map((action, index) => ( - <div - key={index} - className={`list-item ${ - selectedItem === action ? "active" : "" - }`} - > - <div - className="value" - onClick={() => setSelectedItem(action)} - > - <input type="radio" name="products" id="products" /> - <RenameInput value={action} /> - </div> - <div - className="remove-button" - onClick={() => handleRemoveAction(index)} - > - <RemoveIcon /> - </div> + 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); + }; + + const handleRenameProduct = (productId: string, newName: string) => { + renameProduct(productId, newName); + if (selectedProduct.productId === productId) { + setSelectedProduct(productId, newName); + } + }; + + const handleAddEventToProduct = () => { + if (selectedAsset) { + addEvent(selectedProduct.productId, selectedAsset); + // upsertProductOrEventApi({ + // productName: selectedProduct.productName, + // productId: selectedProduct.productId, + // eventDatas: selectedAsset + // }); + clearSelectedAsset(); + } + }; + + const handleRemoveEventFromProduct = () => { + if (selectedAsset) { + removeEvent(selectedProduct.productId, selectedAsset.modelUuid); + clearSelectedAsset(); + } + }; + + const selectedProductData = products.find( + (product) => product.productId === selectedProduct.productId + ); + + 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"> + <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"> + <div className="collapse-header-container"> + <div className="header">Events</div> + <div className="arrow-container"> + <ArrowIcon /> + </div> + </div> + {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> - <div - className="resize-icon" - id="action-resize" - onMouseDown={(e) => handleResize(e, productsContainerRef)} - > - <ResizeHeightIcon /> - </div> - </div> + + {selectedAsset && + <RenderOverlay> + <EditWidgetOption + options={['Add to Product', 'Remove from Product']} + onClick={(option) => { + if (option === 'Add to Product') { + handleAddEventToProduct(); + } else { + handleRemoveEventFromProduct(); + } + }} + /> + </RenderOverlay> + } </div> - <div className="simulation-process"> - <div className="collapse-header-container"> - <div className="header">Operations</div> - <div className="arrow-container"> - <ArrowIcon /> - </div> - </div> - {Value.map((val, index) => ( - <DropList key={index} val={val} /> - ))} - </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> - </div> - ); + ); }; export default Simulations; diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index 04a569a..234b936 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -1,9 +1,9 @@ import { useState, useEffect, useRef } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; -import ChartComponent from "../../../sidebarLeft//visualization/widgets/ChartComponent"; +import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent"; import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; import { WalletIcon } from "../../../../icons/3dChartIcons"; -import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard"; +import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard"; interface Widget { id: string; diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 70b1461..21ea6a7 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -15,7 +15,7 @@ import { } from "../icons/ExportToolsIcons"; import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons"; import useModuleStore, { useThreeDStore } from "../../store/useModuleStore"; -import { handleSaveTemplate } from "../../modules//visualization/functions/handleSaveTemplate"; +import { handleSaveTemplate } from "../../modules/visualization/functions/handleSaveTemplate"; import { usePlayButtonStore } from "../../store/usePlayButtonStore"; import useTemplateStore from "../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; diff --git a/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx b/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx index e2c2936..53e03ce 100644 --- a/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx +++ b/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx @@ -1,47 +1,72 @@ import React, { useState } from "react"; -import LabledDropdown from "./LabledDropdown"; import { ArrowIcon } from "../../icons/ExportCommonIcons"; +import LabledDropdown from "./LabledDropdown"; -const PreviewSelectionWithUpload: React.FC = () => { - const [showPreview, setSetshowPreview] = useState(false); +interface PreviewSelectionWithUploadProps { + preview?: boolean; + upload?: boolean; + label?: string; + onSelect: (option: string) => void; + defaultOption: string; + options: string[]; +} + +const PreviewSelectionWithUpload: React.FC<PreviewSelectionWithUploadProps> = ({ + preview = false, + upload = false, + onSelect, + label, + defaultOption, + options, +}) => { + const [showPreview, setShowPreview] = useState(false); return ( <div className="preview-selection-with-upload-wrapper"> - <div - className="input-header-container" - onClick={() => setSetshowPreview(!showPreview)} - > - <div className="input-header">Preview</div> - <div - className="arrow-container" - style={{ rotate: showPreview ? "0deg" : "90deg" }} - > - <ArrowIcon /> - </div> - </div> - {showPreview && ( - <div className="canvas-wrapper"> - <div className="canvas-container"></div> + {preview && ( + <> + <button + className="input-header-container" + onClick={() => setShowPreview(!showPreview)} + > + <div className="input-header">Preview</div> + <div + className="arrow-container" + style={{ rotate: showPreview ? "0deg" : "90deg" }} + > + <ArrowIcon /> + </div> + </button> + {showPreview && ( + <div className="canvas-wrapper"> + <div className="canvas-container"></div> + </div> + )} + </> + )} + {upload && ( + <div className="asset-selection-container"> + <div className="upload-custom-asset-button"> + <div className="title">Upload Product</div> + <input + type="file" + accept=".glb, .gltf" + id="simulation-product-upload" + /> + <label + className="upload-button" + htmlFor="simulation-product-upload" + > + Upload here + </label> + </div> </div> )} - <div className="asset-selection-container"> - <div className="upload-custom-asset-button"> - <div className="title">Upload Product</div> - <input - type="file" - accept=".glb, .gltf" - id="simulation-product-upload" - /> - <label className="upload-button" htmlFor="simulation-product-upload"> - Upload here - </label> - </div> - <LabledDropdown - label="Presets" - defaultOption={"Default material"} - options={["Default material", "Product 1", "Product 2"]} - onSelect={(option) => console.log(option)} - /> - </div> + <LabledDropdown + label={label} + defaultOption={defaultOption} + options={options} + onSelect={onSelect} + /> </div> ); }; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index cac0b43..06419af 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -10,7 +10,7 @@ import { ArrowIcon, EyeIcon, LockIcon, - RmoveIcon, + RemoveIcon, } from "../../icons/ExportCommonIcons"; import { useThree } from "@react-three/fiber"; import { useFloorItems, useZoneAssetId, useZones } from "../../../store/store"; @@ -142,9 +142,6 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => { ) ); } - - console.log('newName: ', newName); - } const checkZoneNameDuplicate = (name: string) => { return zones.some( @@ -184,7 +181,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => { </div> {remove && ( <div className="remove option"> - <RmoveIcon /> + <RemoveIcon /> </div> )} {item.assets && item.assets.length > 0 && ( @@ -221,7 +218,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => { </div> {remove && ( <div className="remove option"> - <RmoveIcon /> + <RemoveIcon /> </div> )} </div> diff --git a/app/src/components/ui/menu/EditWidgetOption.tsx b/app/src/components/ui/menu/EditWidgetOption.tsx index 6fd1e94..eb937c8 100644 --- a/app/src/components/ui/menu/EditWidgetOption.tsx +++ b/app/src/components/ui/menu/EditWidgetOption.tsx @@ -1,23 +1,20 @@ import React, { useEffect } from "react"; import { - useEditWidgetOptionsStore, useLeftData, - useRightClickSelected, - useRightSelected, useTopData, } from "../../../store/visualization/useZone3DWidgetStore"; interface EditWidgetOptionProps { options: string[]; + onClick: (option: string) => void; } const EditWidgetOption: React.FC<EditWidgetOptionProps> = ({ options, + onClick }) => { const { top } = useTopData(); const { left } = useLeftData(); - const { setRightSelect } = useRightSelected(); - const { setEditWidgetOptions } = useEditWidgetOptionsStore(); useEffect(() => { @@ -38,10 +35,7 @@ const EditWidgetOption: React.FC<EditWidgetOptionProps> = ({ <div className="option" key={index} - onClick={(e) => { - setRightSelect(option); - setEditWidgetOptions(false); - }} + onClick={() => onClick(option)} > {option} </div> diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts index e88dc3c..c46c0e7 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts @@ -8,10 +8,13 @@ import * as Types from "../../../types/world/worldTypes"; import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils'; import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi'; import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi'; +import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator'; async function loadInitialFloorItems( itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, + addEvent: (event: EventsSchema) => void, + renderDistance: number ): Promise<void> { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -63,14 +66,14 @@ async function loadInitialFloorItems( const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z); - if (cameraPosition.distanceTo(itemPosition) < 50) { + if (cameraPosition.distanceTo(itemPosition) < renderDistance) { await new Promise<void>(async (resolve) => { // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { // console.log(`[Cache] Fetching ${item.modelname}`); - processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); return; @@ -85,7 +88,7 @@ async function loadInitialFloorItems( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -106,7 +109,7 @@ async function loadInitialFloorItems( const modelBlob = await fetch(modelUrl).then((res) => res.blob()); await storeGLTF(item.modelfileID!, modelBlob); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -148,8 +151,9 @@ function processLoadedModel( item: Types.FloorItemType, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, + addEvent: (event: EventsSchema) => void, ) { - const model = gltf; + const model = gltf.clone(); model.uuid = item.modeluuid; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid }; @@ -182,6 +186,242 @@ function processLoadedModel( }, ]); + if (item.modelfileID === "a1ee92554935007b10b3eb05") { + const data = PointsCalculator( + 'Vehicle', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const vehicleEvent: VehicleEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "vehicle", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Vehicle Action", + actionType: "travel", + unLoadDuration: 5, + loadCapacity: 10, + pickUpPoint: null, + unLoadPoint: null, + triggers: [] + } + } + }; + addEvent(vehicleEvent); + } else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") { + const data = PointsCalculator( + 'Conveyor', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const ConveyorEvent: ConveyorEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "transfer", + speed: 1, + points: data.points.map((point: THREE.Vector3, index: number) => ({ + uuid: THREE.MathUtils.generateUUID(), + position: [point.x, point.y, point.z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action ${index + 1}`, + actionType: 'default', + material: 'Default material', + delay: 0, + spawnInterval: 5, + spawnCount: 1, + triggers: [] + } + })) + }; + addEvent(ConveyorEvent); + } else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") { + // const data = PointsCalculator( + // 'Conveyor', + // gltf.clone(), + // new THREE.Vector3(...model.rotation) + // ); + + // if (!data || !data.points) return; + + // const points: ConveyorPointSchema[] = data.points.map((point: THREE.Vector3, index: number) => { + // const actionUuid = THREE.MathUtils.generateUUID(); + // return { + // uuid: THREE.MathUtils.generateUUID(), + // position: [point.x, point.y, point.z], + // rotation: [0, 0, 0], + // action: { + // actionUuid, + // actionName: `Action ${index}`, + // actionType: 'default', + // material: 'inherit', + // delay: 0, + // spawnInterval: 5, + // spawnCount: 1, + // triggers: [] + // } + // }; + // }); + + // points.forEach((point, index) => { + // if (index < points.length - 1) { + // const nextPoint = points[index + 1]; + // point.action.triggers.push({ + // triggerUuid: THREE.MathUtils.generateUUID(), + // triggerName: `Trigger 1`, + // triggerType: "onComplete", + // delay: 0, + // triggeredAsset: { + // triggeredModel: { + // modelName: item.modelname, + // modelUuid: item.modeluuid + // }, + // triggeredPoint: { + // pointName: `Point ${index + 1}`, + // pointUuid: nextPoint.uuid + // }, + // triggeredAction: { + // actionName: nextPoint.action.actionName, + // actionUuid: nextPoint.action.actionUuid + // } + // } + // }); + // } + // }); + + // const ConveyorEvent: ConveyorEventSchema = { + // modelUuid: item.modeluuid, + // modelName: item.modelname, + // position: item.position, + // rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + // state: "idle", + // type: "transfer", + // speed: 1, + // points + // }; + // addEvent(ConveyorEvent); + } else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") { + const data = PointsCalculator( + 'Conveyor', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const ConveyorEvent: ConveyorEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "transfer", + speed: 1, + points: data.points.map((point: THREE.Vector3, index: number) => ({ + uuid: THREE.MathUtils.generateUUID(), + position: [point.x, point.y, point.z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action ${index}`, + actionType: 'default', + material: 'inherit', + delay: 0, + spawnInterval: 5, + spawnCount: 1, + triggers: [] + } + })) + }; + addEvent(ConveyorEvent); + } else if (item.modelfileID === "29dee78715ad5b853f5c346d") { + const data = PointsCalculator( + 'StaticMachine', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const machineEvent: MachineEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "machine", + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Process Action", + actionType: "process", + processTime: 10, + swapMaterial: "material-id", + triggers: [] + } + } + }; + addEvent(machineEvent); + } else if (item.modelfileID === "52e6681fbb743a890d96c914") { + const data = PointsCalculator( + 'ArmBot', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const roboticArmEvent: RoboticArmEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "roboticArm", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Pick and Place", + actionType: "pickAndPlace", + process: { + startPoint: [0, 0, 0], + endPoint: [0, 0, 0] + }, + triggers: [] + } + ] + } + }; + addEvent(roboticArmEvent); + } + gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index e7f83e2..e5ff1c1 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -51,7 +51,7 @@ 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 DrieHtmlTemp from "../visualization/mqttTemp/drieHtmlTemp"; import ZoneGroup from "./groups/zoneGroup"; import useModuleStore from "../../store/useModuleStore"; import MeasurementTool from "../scene/tools/measurementTool"; diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index 193dd41..d7c278c 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -10,7 +10,7 @@ import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils'; import { Socket } from 'socket.io-client'; import * as CONSTANTS from '../../../../types/world/worldConstants'; import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; -import PointsCalculator from '../../../simulation/events/points/pointsCalculator'; +import PointsCalculator from '../../../simulation/events/points/functions/pointsCalculator'; async function addAssetModel( raycaster: THREE.Raycaster, @@ -202,7 +202,7 @@ async function handleModelLoad( actionUuid: THREE.MathUtils.generateUUID(), actionName: `Action ${index}`, actionType: 'default', - material: 'inherit', + material: 'Default Material', delay: 0, spawnInterval: 5, spawnCount: 1, @@ -226,9 +226,8 @@ async function handleModelLoad( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Vehicle Action", + actionName: "Action 1", actionType: "travel", - material: null, unLoadDuration: 5, loadCapacity: 10, pickUpPoint: null, @@ -254,11 +253,11 @@ async function handleModelLoad( actions: [ { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Pick and Place", + actionName: "Action 1", actionType: "pickAndPlace", process: { - startPoint: [0, 0, 0], - endPoint: [0, 0, 0] + startPoint: null, + endPoint: null }, triggers: [] } @@ -266,7 +265,7 @@ async function handleModelLoad( } }; addEvent(roboticArmEvent); - } else if (selectedItem.type === "Machine") { + } else if (selectedItem.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: newFloorItem.modeluuid, modelName: newFloorItem.modelname, @@ -280,10 +279,10 @@ async function handleModelLoad( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Process Action", + actionName: "Action 1", actionType: "process", processTime: 10, - swapMaterial: "material-id", + swapMaterial: "Default Material", triggers: [] } } diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index 2a3c2cc..f3f5050 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -75,7 +75,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject gltfLoaderWorker.postMessage({ floorItems: data }); } else { gltfLoaderWorker.postMessage({ floorItems: [] }); - loadInitialFloorItems(itemsGroup, setFloorItems); + loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance); updateLoadingProgress(100); } }); @@ -94,7 +94,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject updateLoadingProgress(progress); if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems); + loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance); updateLoadingProgress(100); } }); diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index d3245fb..dd5607a 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -2,90 +2,107 @@ import * as THREE from "three"; import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing"; import { BlendFunction } from "postprocessing"; import { - useDeletableFloorItem, - useSelectedWallItem, - useSelectedFloorItem, + useDeletableFloorItem, + useSelectedWallItem, + useSelectedFloorItem, } from "../../../store/store"; import * as Types from "../../../types/world/worldTypes"; import * as CONSTANTS from "../../../types/world/worldConstants"; import { useEffect } from "react"; +import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; export default function PostProcessing() { - const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); - const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); - const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); + const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); + const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedEventSphere } = useSelectedEventSphere(); - function flattenChildren(children: any[]) { - const allChildren: any[] = []; - children.forEach((child) => { - allChildren.push(child); - if (child.children && child.children.length > 0) { - allChildren.push(...flattenChildren(child.children)); - } - }); - return allChildren; - } + function flattenChildren(children: any[]) { + const allChildren: any[] = []; + children.forEach((child) => { + allChildren.push(child); + if (child.children && child.children.length > 0) { + allChildren.push(...flattenChildren(child.children)); + } + }); + return allChildren; + } - return ( - <> - <EffectComposer autoClear={false}> - <N8AO - color="black" - aoRadius={20} - intensity={7} - distanceFalloff={4} - aoSamples={32} - denoiseRadius={6} - denoiseSamples={16} - /> - {deletableFloorItem && ( - <Outline - selection={flattenChildren(deletableFloorItem.children)} - selectionLayer={10} - width={3000} - blendFunction={BlendFunction.ALPHA} - edgeStrength={5} - resolutionScale={2} - pulseSpeed={0} - visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor} - hiddenEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor} - blur={true} - xRay={true} - /> - )} - {selectedWallItem && ( - <Outline - selection={selectedWallItem.children[1].children[0].children.filter( - (child: Types.Mesh) => child.name !== "CSG_REF" - )} - selectionLayer={10} - width={3000} - blendFunction={BlendFunction.ALPHA} - edgeStrength={5} - resolutionScale={2} - pulseSpeed={0} - visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} - hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} - blur={true} - xRay={true} - /> - )} - {selectedFloorItem && ( - <Outline - selection={flattenChildren(selectedFloorItem.children)} - selectionLayer={10} - width={3000} - blendFunction={BlendFunction.ALPHA} - edgeStrength={5} - resolutionScale={2} - pulseSpeed={0} - visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} - hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} - blur={true} - xRay={true} - /> - )} - </EffectComposer> - </> - ); + return ( + <> + <EffectComposer autoClear={false}> + <N8AO + color="black" + aoRadius={20} + intensity={7} + distanceFalloff={4} + aoSamples={32} + denoiseRadius={6} + denoiseSamples={16} + /> + {deletableFloorItem && ( + <Outline + selection={flattenChildren(deletableFloorItem.children)} + selectionLayer={10} + width={3000} + blendFunction={BlendFunction.ALPHA} + edgeStrength={5} + resolutionScale={2} + pulseSpeed={0} + visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor} + hiddenEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor} + blur={true} + xRay={true} + /> + )} + {selectedWallItem && ( + <Outline + selection={selectedWallItem.children[1].children[0].children.filter( + (child: Types.Mesh) => child.name !== "CSG_REF" + )} + selectionLayer={10} + width={3000} + blendFunction={BlendFunction.ALPHA} + edgeStrength={5} + resolutionScale={2} + pulseSpeed={0} + visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} + hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} + blur={true} + xRay={true} + /> + )} + {selectedFloorItem && ( + <Outline + selection={flattenChildren(selectedFloorItem.children)} + selectionLayer={10} + width={3000} + blendFunction={BlendFunction.ALPHA} + edgeStrength={5} + resolutionScale={2} + pulseSpeed={0} + visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} + hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} + blur={true} + xRay={true} + /> + )} + {selectedEventSphere && ( + <Outline + selection={[selectedEventSphere]} + selectionLayer={10} + width={1000} + blendFunction={BlendFunction.ALPHA} + edgeStrength={10} + resolutionScale={2} + pulseSpeed={0} + visibleEdgeColor={0x6f42c1} + hiddenEdgeColor={0x6f42c1} + blur={true} + xRay={true} + /> + )} + </EffectComposer> + </> + ); } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 6564032..0f2c489 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -9,28 +9,35 @@ import Simulation from "../simulation/simulation"; import Collaboration from "../collaboration/collaboration"; export default function Scene() { - const map = useMemo(() => [ - { name: "forward", keys: ["ArrowUp", "w", "W"] }, - { name: "backward", keys: ["ArrowDown", "s", "S"] }, - { name: "left", keys: ["ArrowLeft", "a", "A"] }, - { name: "right", keys: ["ArrowRight", "d", "D"] },], - []); + const map = useMemo( + () => [ + { name: "forward", keys: ["ArrowUp", "w", "W"] }, + { name: "backward", keys: ["ArrowDown", "s", "S"] }, + { name: "left", keys: ["ArrowLeft", "a", "A"] }, + { name: "right", keys: ["ArrowRight", "d", "D"] }, + ], + [] + ); - return ( - <KeyboardControls map={map}> - <Canvas eventPrefix="client" gl={{ powerPreference: "high-performance", antialias: true }} onContextMenu={(e) => { e.preventDefault(); }}> + return ( + <KeyboardControls map={map}> + <Canvas + eventPrefix="client" + gl={{ powerPreference: "high-performance", antialias: true }} + onContextMenu={(e) => { + e.preventDefault(); + }} + > + <Setup /> - <Setup /> + <Collaboration /> - <Collaboration /> + <Builder /> - <Builder /> + <Simulation /> - <Simulation /> - - <Visualization /> - - </Canvas> - </KeyboardControls> - ); + <Visualization /> + </Canvas> + </KeyboardControls> + ); } diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 335f1f5..bc2f4db 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -4,20 +4,37 @@ import { useEventsStore } from '../../../../../store/simulation/useEventsStore'; import useModuleStore from '../../../../../store/useModuleStore'; import { TransformControls } from '@react-three/drei'; import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys'; +import { useSelectedEventSphere, useSelectedEventData } from '../../../../../store/simulation/useSimulationStore'; function PointsCreator() { - const { events, updatePoint, getPointByUuid } = useEventsStore(); + const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore(); const { activeModule } = useModuleStore(); const transformRef = useRef<any>(null); const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); - const [selectedPoint, setSelectedPoint] = useState<THREE.Mesh | 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); + if (eventData) { + setSelectedEventData( + eventData, + selectedEventSphere.userData.pointUuid + ); + } else { + clearSelectedEventData(); + } + } else { + clearSelectedEventData(); + } + }, [selectedEventSphere]); useEffect(() => { - setTransformMode(null); const handleKeyDown = (e: KeyboardEvent) => { const keyCombination = detectModifierKeys(e); - if (!selectedPoint) return; + if (!selectedEventSphere) return; if (keyCombination === "G") { setTransformMode((prev) => (prev === "translate" ? null : "translate")); } @@ -28,13 +45,13 @@ function PointsCreator() { window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [selectedPoint]); + }, [selectedEventSphere]); - const updatePointToState = (selectedPoint: THREE.Mesh) => { - let point = JSON.parse(JSON.stringify(getPointByUuid(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid))); + const updatePointToState = (selectedEventSphere: THREE.Mesh) => { + let point = JSON.parse(JSON.stringify(getPointByUuid(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid))); if (point) { - point.position = [selectedPoint.position.x, selectedPoint.position.y, selectedPoint.position.z]; - updatePoint(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid, point) + point.position = [selectedEventSphere.position.x, selectedEventSphere.position.y, selectedEventSphere.position.z]; + updatePoint(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid, point) } } @@ -49,14 +66,16 @@ function PointsCreator() { <group key={i} position={new THREE.Vector3(...event.position)}> {event.points.map((point, j) => ( <mesh + name='Event-Sphere' uuid={point.uuid} ref={(el) => (sphereRefs.current[point.uuid] = el!)} onClick={(e) => { e.stopPropagation(); - setSelectedPoint(sphereRefs.current[point.uuid]); + setSelectedEventSphere(sphereRefs.current[point.uuid]); }} onPointerMissed={() => { - setSelectedPoint(null); + clearSelectedEventSphere(); + setTransformMode(null); }} key={`${i}-${j}`} position={new THREE.Vector3(...point.position)} @@ -72,14 +91,16 @@ function PointsCreator() { return ( <group key={i} position={new THREE.Vector3(...event.position)}> <mesh + name='Event-Sphere' uuid={event.point.uuid} ref={(el) => (sphereRefs.current[event.point.uuid] = el!)} onClick={(e) => { e.stopPropagation(); - setSelectedPoint(sphereRefs.current[event.point.uuid]); + setSelectedEventSphere(sphereRefs.current[event.point.uuid]); }} onPointerMissed={() => { - setSelectedPoint(null); + clearSelectedEventSphere(); + setTransformMode(null); }} position={new THREE.Vector3(...event.point.position)} userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} @@ -93,14 +114,16 @@ function PointsCreator() { return ( <group key={i} position={new THREE.Vector3(...event.position)}> <mesh + name='Event-Sphere' uuid={event.point.uuid} ref={(el) => (sphereRefs.current[event.point.uuid] = el!)} onClick={(e) => { e.stopPropagation(); - setSelectedPoint(sphereRefs.current[event.point.uuid]); + setSelectedEventSphere(sphereRefs.current[event.point.uuid]); }} onPointerMissed={() => { - setSelectedPoint(null); + clearSelectedEventSphere(); + setTransformMode(null); }} position={new THREE.Vector3(...event.point.position)} userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} @@ -114,14 +137,16 @@ function PointsCreator() { return ( <group key={i} position={new THREE.Vector3(...event.position)}> <mesh + name='Event-Sphere' uuid={event.point.uuid} ref={(el) => (sphereRefs.current[event.point.uuid] = el!)} onClick={(e) => { e.stopPropagation(); - setSelectedPoint(sphereRefs.current[event.point.uuid]); + setSelectedEventSphere(sphereRefs.current[event.point.uuid]); }} onPointerMissed={() => { - setSelectedPoint(null); + clearSelectedEventSphere(); + setTransformMode(null); }} position={new THREE.Vector3(...event.point.position)} userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} @@ -136,8 +161,8 @@ function PointsCreator() { } })} </group> - {(selectedPoint && transformMode) && - <TransformControls ref={transformRef} object={selectedPoint} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedPoint) }} /> + {(selectedEventSphere && transformMode) && + <TransformControls ref={transformRef} object={selectedEventSphere} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedEventSphere) }} /> } </> } diff --git a/app/src/modules/simulation/events/points/pointsCalculator.ts b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts similarity index 96% rename from app/src/modules/simulation/events/points/pointsCalculator.ts rename to app/src/modules/simulation/events/points/functions/pointsCalculator.ts index 86d368e..ccc2dbb 100644 --- a/app/src/modules/simulation/events/points/pointsCalculator.ts +++ b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { Group } from '../../../../types/world/worldTypes'; +import { Group } from '../../../../../types/world/worldTypes'; function PointsCalculator( type: string, diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx new file mode 100644 index 0000000..edb825f --- /dev/null +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function MachineInstance() { + return ( + <> + </> + ) +} + +export default MachineInstance \ No newline at end of file diff --git a/app/src/modules/simulation/machine/instances/machineInstances.tsx b/app/src/modules/simulation/machine/instances/machineInstances.tsx new file mode 100644 index 0000000..b0c2c9f --- /dev/null +++ b/app/src/modules/simulation/machine/instances/machineInstances.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import MachineInstance from './machineInstance/machineInstance' + +function MachineInstances() { + return ( + <> + + <MachineInstance /> + + </> + ) +} + +export default MachineInstances \ No newline at end of file diff --git a/app/src/modules/simulation/machine/machine.tsx b/app/src/modules/simulation/machine/machine.tsx new file mode 100644 index 0000000..32c3b8e --- /dev/null +++ b/app/src/modules/simulation/machine/machine.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import MachineInstances from './instances/machineInstances' + +function Machine() { + return ( + <> + + <MachineInstances /> + + </> + ) +} + +export default Machine \ No newline at end of file diff --git a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx new file mode 100644 index 0000000..9eececc --- /dev/null +++ b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx @@ -0,0 +1,124 @@ +import { useThree } from '@react-three/fiber' +import { useEffect } from 'react' +import { Object3D } from 'three'; +import { useSubModuleStore } from '../../../../store/useModuleStore'; +import { useLeftData, useTopData } from '../../../../store/visualization/useZone3DWidgetStore'; +import { useEventsStore } from '../../../../store/simulation/useEventsStore'; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; + +function AddOrRemoveEventsInProducts() { + const { gl, raycaster, scene } = useThree(); + const { subModule } = useSubModuleStore(); + const { setTop } = useTopData(); + const { setLeft } = useLeftData(); + const { getIsEventInProduct } = useProductStore(); + const { getEventByModelUuid } = useEventsStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAsset, setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); + + useEffect(() => { + + const canvasElement = gl.domElement; + + let drag = false; + let isRightMouseDown = false; + + const onMouseDown = (evt: MouseEvent) => { + if (selectedAsset) { + clearSelectedAsset(); + } + if (evt.button === 2) { + isRightMouseDown = true; + drag = false; + } + }; + + const onMouseUp = (evt: MouseEvent) => { + if (evt.button === 2) { + isRightMouseDown = false; + } + } + + const onMouseMove = () => { + if (isRightMouseDown) { + drag = true; + } + }; + + const handleRightClick = (evt: MouseEvent) => { + if (drag) return; + evt.preventDefault(); + const canvasElement = gl.domElement; + if (!canvasElement) return; + + let intersects = raycaster.intersectObjects(scene.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 Object3D; + } + if (currentObject) { + const isInProduct = getIsEventInProduct(selectedProduct.productId, currentObject.uuid); + + if (isInProduct) { + const event = getEventByModelUuid(currentObject.uuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } else { + const event = getEventByModelUuid(currentObject.uuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } + + } + } else { + clearSelectedAsset() + } + + }; + + if (subModule === 'simulations') { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener('contextmenu', handleRightClick); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener('contextmenu', handleRightClick); + }; + + }, [gl, subModule, selectedProduct, selectedAsset]); + return ( + <></> + ) +} + +export default AddOrRemoveEventsInProducts \ No newline at end of file diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx new file mode 100644 index 0000000..38175e2 --- /dev/null +++ b/app/src/modules/simulation/products/products.tsx @@ -0,0 +1,43 @@ +import * as THREE from 'three'; +import { useEffect } from 'react'; +import { useProductStore } from '../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; +import AddOrRemoveEventsInProducts from './events/addOrRemoveEventsInProducts'; +import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProductOrEventApi'; +import { getAllProductsApi } from '../../../services/simulation/getallProductsApi'; + +function Products() { + const { products, addProduct } = useProductStore(); + const { setSelectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (products.length === 0) { + const id = THREE.MathUtils.generateUUID(); + const name = 'Product 1'; + addProduct(name, id); + // upsertProductOrEventApi({ productName: name, productId: id }).then((data) => { + // console.log('data: ', data); + // }); + setSelectedProduct(id, name); + } + }, [products]) + + useEffect(() => { + // const email = localStorage.getItem('email') + // const organization = (email!.split("@")[1]).split(".")[0]; + // console.log(organization); + // getAllProductsApi(organization).then((data) => { + // console.log('data: ', data); + // }) + }, []) + + return ( + <> + + <AddOrRemoveEventsInProducts /> + + </> + ) +} + +export default Products \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 0cd4fe2..fd7590e 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -1,6 +1,64 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from "three" +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; + +function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) { + const { armBots } = useArmBotStore(); + const { scene } = useThree(); + const restSpeed = 0.1; + const restPosition = new THREE.Vector3(0, 2, 1.6); + const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null); + const initialStartPositionRef = useRef<THREE.Vector3 | null>(null); + const [initialProgress, setInitialProgress] = useState(0); + const [progress, setProgress] = useState(0); + const [needsInitialMovement, setNeedsInitialMovement] = useState(true); + const [isInitializing, setIsInitializing] = useState(true); + const { isPlaying } = usePlayButtonStore(); + const statusRef = useRef("idle"); + // Create a ref for initialProgress + const initialProgressRef = useRef(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + + useEffect(() => { + setCurrentPath(path) + }, [path]) + + useEffect(() => { + + }, [currentPath]) + + useFrame((_, delta) => { + if (!ikSolver || !currentPath || currentPath.length === 0) return; + + const bone = ikSolver.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); + if (!bone) return; + + // Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it + const curve = new THREE.CatmullRomCurve3( + currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2])) + ); + + const next = initialProgressRef.current + delta * 0.5; + if (next >= 1) { + bone.position.copy(restPosition); + HandleCallback(); // Call the callback when the path is completed + initialProgressRef.current = 0; // Set ref to 1 when done + } else { + + const point = curve.getPoint(next); // Get the interpolated point from the curve + bone.position.copy(point); // Update the bone position along the curve + initialProgressRef.current = next; // Update progress + } + + ikSolver.update(); + }); + + -function RoboticArmAnimator() { return ( <></> ) diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 2817906..86eee6a 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -1,14 +1,179 @@ -import React from 'react' +import React, { useEffect, useRef, useState } from 'react' import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb"; +import { useThree } from "@react-three/fiber"; +import { useFloorItems } from '../../../../../store/store'; +import useModuleStore from '../../../../../store/useModuleStore'; +import { Vector3 } from "three"; +import * as THREE from "three"; + +interface Process { + triggerId: string; + startPoint?: Vector3; + endPoint?: Vector3; + speed: number; +} +function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { + + const { isPlaying } = usePlayButtonStore(); + const [currentPhase, setCurrentPhase] = useState<(string)>("init"); + const { scene } = useThree(); + const targetBone = "Target"; + const { activeModule } = useModuleStore(); + const [ikSolver, setIkSolver] = useState<any>(null); + const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { floorItems } = useFloorItems(); + const groupRef = useRef<any>(null); + const [processes, setProcesses] = useState<Process[]>([]); + const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] }) + const restPosition = new THREE.Vector3(0, 2, 1.6); + let armBotCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null) + const [path, setPath] = useState<[number, number, number][]>([]); + + useEffect(() => { + let armItems = floorItems?.filter((val: any) => + val.modeluuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d" + ); + // Get the first matching item + let armItem = armItems?.[0]; + if (armItem) { + const targetMesh = scene?.getObjectByProperty("uuid", armItem.modeluuid); + if (targetMesh) { + targetMesh.visible = activeModule !== "simulation" + } + } + const targetBones = ikSolver?.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); + + if (isPlaying) { + //Moving armBot from initial point to rest position. + if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") { + + setArmBotActive(robot.modelUuid, true) + setArmBotState(robot.modelUuid, "running") + setCurrentPhase("init-to-rest"); + if (targetBones) { + let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from init to rest") + } + //Waiting for trigger. + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) { + console.log("trigger"); + setTimeout(() => { + addCurrentAction(robot.modelUuid, 'action-003'); + }, 3000); + } + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) { + if (robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("rest-to-start"); + const startPoint = robot.point.actions[0].process.startPoint; + if (startPoint) { + let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + } + logStatus(robot.modelUuid, "Starting from rest to start") + } + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("start-to-end"); + const startPoint = robot.point.actions[0].process.startPoint; + const endPoint = robot.point.actions[0].process.endPoint; + if (startPoint && endPoint) { + let curve = createCurveBetweenTwoPoints( + new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), + new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]) + ); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from start to end") + } + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("end-to-rest"); + const endPoint = robot.point.actions[0].process.endPoint; + if (endPoint) { + let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition + ); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from end to rest") + } + } + + }, [currentPhase, robot, isPlaying, ikSolver]) + + + function createCurveBetweenTwoPoints(p1: any, p2: any) { + const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); + mid.y += 0.5; + + const points = [p1, mid, p2]; + return new THREE.CatmullRomCurve3(points); + } + + + const HandleCallback = () => { + if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") { + console.log("Callback triggered: rest"); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("rest"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") { + console.log("Callback triggered: pick."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("picking"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") { + console.log("Callback triggered: drop."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("dropping"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") { + console.log("Callback triggered: rest, cycle completed."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("rest"); + setPath([]) + removeCurrentAction(robot.modelUuid) + } + } + const logStatus = (id: string, status: string) => { + console.log(id +","+ status); + } -function RoboticArmInstance() { return ( <> - <IKInstance /> - - <RoboticArmAnimator /> + <IKInstance modelUrl={armModel} setIkSolver={setIkSolver} ikSolver={ikSolver} robot={robot} groupRef={groupRef} processes={processes} + setArmBotCurvePoints={setArmBotCurvePoints} /> + <RoboticArmAnimator armUuid={robot?.modelUuid} HandleCallback={HandleCallback} + currentPhase={currentPhase} targetBone={targetBone} ikSolver={ikSolver} robot={robot} + logStatus={logStatus} groupRef={groupRef} processes={processes} armBotCurveRef={armBotCurveRef} path={path} /> </> ) diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index 52a8610..a8d8782 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -1,8 +1,87 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as THREE from "three"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { clone } from "three/examples/jsm/utils/SkeletonUtils"; +import { useFrame, useLoader, useThree } from "@react-three/fiber"; +import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver"; +type IKInstanceProps = { + modelUrl: string; + ikSolver: any; + setIkSolver: any + robot: any; + groupRef: React.RefObject<THREE.Group>; + processes: any; + setArmBotCurvePoints: any +}; +function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) { + const { scene } = useThree(); + const gltf = useLoader(GLTFLoader, modelUrl, (loader) => { + const draco = new DRACOLoader(); + draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/"); + loader.setDRACOLoader(draco); + }); + const cloned = useMemo(() => clone(gltf?.scene), [gltf]); + const targetBoneName = "Target"; + const skinnedMeshName = "link_0"; + useEffect(() => { + if (!gltf) return; + const OOI: any = {}; + cloned.traverse((n: any) => { + if (n.name === targetBoneName) OOI.Target_Bone = n; + if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n; + }); + if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return; + const iks = [ + { + target: 7, + effector: 6, + links: [ + { + index: 5, + enabled: true, + rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0), + rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0), + }, + { + index: 4, + enabled: true, + rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0), + rotationMax: new THREE.Vector3(0, 0, 0), + }, + { + index: 3, + enabled: true, + rotationMin: new THREE.Vector3(0, 0, 0), + rotationMax: new THREE.Vector3(2, 0, 0), + }, + { index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) }, + { index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) }, + ], + }, + ]; + + const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks); + setIkSolver(solver); + + const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05); + + // scene.add(groupRef.current) + + + }, [gltf]); -function IKInstance() { return ( - <></> + <> + <group ref={groupRef} position={robot.position}> + <primitive + uuid={"ArmBot-X200"} + object={cloned} + scale={[1, 1, 1]} + name={`arm-bot11`} + /> + </group> + </> ) } diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx deleted file mode 100644 index d44ddd2..0000000 --- a/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import IKInstance from './ikInstance/ikInstance'; - -function IkInstances() { - return ( - <> - - <IKInstance /> - - </> - ) -} - -export default IkInstances; \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx index 6e8a70a..1089fa5 100644 --- a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx +++ b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx @@ -1,11 +1,15 @@ import React from 'react' import RoboticArmInstance from './armInstance/roboticArmInstance'; +import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; function RoboticArmInstances() { + const { armBots } = useArmBotStore(); + return ( <> - - <RoboticArmInstance /> + {armBots?.map((robot: ArmBotStatus) => ( + <RoboticArmInstance key={robot.modelUuid} robot={robot} /> + ))} </> ) diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx index 1270d93..dcf01da 100644 --- a/app/src/modules/simulation/roboticArm/roboticArm.tsx +++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx @@ -1,15 +1,166 @@ -import React from 'react' -import RoboticArmInstances from './instances/roboticArmInstances'; -import IkInstances from './instances/ikInstances'; +import { useEffect } from "react"; +import RoboticArmInstances from "./instances/roboticArmInstances"; +import { useArmBotStore } from "../../../store/simulation/useArmBotStore"; +import { useFloorItems } from "../../../store/store"; function RoboticArm() { + const { armBots, addArmBot, removeArmBot } = useArmBotStore(); + const { floorItems } = useFloorItems(); + + const armBotStatusSample: RoboticArmEventSchema[] = [ + { + state: "idle", + modelUuid: "armbot-xyz-001", + modelName: "ArmBot-X200", + position: [91.94347308985614, 0, 6.742905194869091], + rotation: [0, 0, 0], + type: "roboticArm", + speed: 1.5, + point: { + uuid: "point-123", + position: [0, 1.5, 0], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: "action-003", + actionName: "Pick Component", + actionType: "pickAndPlace", + process: { + startPoint: [5.52543010919071, 1, -8.433681161200905], + endPoint: [10.52543010919071, 1, -12.433681161200905], + }, + triggers: [ + { + triggerUuid: "trigger-001", + triggerName: "Start Trigger", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "Conveyor A1", + modelUuid: "conveyor-01", + }, + triggeredPoint: { + pointName: "Start Point", + pointUuid: "conveyor-01-point-001", + }, + triggeredAction: { + actionName: "Move Forward", + actionUuid: "conveyor-action-01", + }, + }, + }, + { + triggerUuid: "trigger-002", + triggerName: "Complete Trigger", + triggerType: "onComplete", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "StaticMachine B2", + modelUuid: "machine-02", + }, + triggeredPoint: { + pointName: "Receive Point", + pointUuid: "machine-02-point-001", + }, + triggeredAction: { + actionName: "Process Part", + actionUuid: "machine-action-01", + }, + }, + }, + ], + }, + ], + }, + }, + { + state: "idle", + modelUuid: "armbot-xyz-002", + modelName: "ArmBot-X200", + position: [95.94347308985614, 0, 6.742905194869091], + rotation: [0, 0, 0], + type: "roboticArm", + speed: 1.5, + point: { + uuid: "point-123", + position: [0, 1.5, 0], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: "action-001", + actionName: "Pick Component", + actionType: "pickAndPlace", + process: { + startPoint: [2.52543010919071, 0, 8.433681161200905], + endPoint: [95.3438373267953, 0, 9.0279187421610025], + }, + triggers: [ + { + triggerUuid: "trigger-001", + triggerName: "Start Trigger", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "Conveyor A1", + modelUuid: "conveyor-01", + }, + triggeredPoint: { + pointName: "Start Point", + pointUuid: "conveyor-01-point-001", + }, + triggeredAction: { + actionName: "Move Forward", + actionUuid: "conveyor-action-01", + }, + }, + }, + { + triggerUuid: "trigger-002", + triggerName: "Complete Trigger", + triggerType: "onComplete", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "StaticMachine B2", + modelUuid: "machine-02", + }, + triggeredPoint: { + pointName: "Receive Point", + pointUuid: "machine-02-point-001", + }, + triggeredAction: { + actionName: "Process Part", + actionUuid: "machine-action-01", + }, + }, + }, + ], + }, + ], + }, + }, + ]; + + useEffect(() => { + + removeArmBot(armBotStatusSample[0].modelUuid); + addArmBot('123', armBotStatusSample[0]); + // addArmBot('123', armBotStatusSample[1]); + // addCurrentAction('armbot-xyz-001', 'action-001'); + }, []); + + useEffect(() => { + // + }, [armBots]); + return ( <> - <RoboticArmInstances /> - </> - ) + ); } -export default RoboticArm; \ No newline at end of file +export default RoboticArm; diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index b7bf36d..5ca0ec5 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -5,8 +5,16 @@ import Vehicles from './vehicle/vehicles'; import Points from './events/points/points'; import Conveyor from './conveyor/conveyor'; import RoboticArm from './roboticArm/roboticArm'; +import Materials from './materials/materials'; +import Machine from './machine/machine'; +import StorageUnit from './storageUnit/storageUnit'; +import Simulator from './simulator/simulator'; +import Products from './products/products'; +import Trigger from './triggers/trigger'; +import useModuleStore from '../../store/useModuleStore'; function Simulation() { + const { activeModule } = useModuleStore(); const { events } = useEventsStore(); const { products } = useProductStore(); @@ -21,16 +29,36 @@ function Simulation() { return ( <> - <Points /> + {activeModule === 'simulation' && - <Vehicles /> + <> - <RoboticArm /> + <Points /> - <Conveyor /> + <Products /> + + <Materials /> + + <Trigger /> + + <Conveyor /> + + <Vehicles /> + + <RoboticArm /> + + <Machine /> + + <StorageUnit /> + + <Simulator /> + + </> + + } </> - ) + ); } -export default Simulation \ No newline at end of file +export default Simulation; diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx new file mode 100644 index 0000000..c4f1c40 --- /dev/null +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -0,0 +1,18 @@ +import { useEffect } from 'react' +import { useProductStore } from '../../../store/simulation/useProductStore' + +function Simulator() { + const { products } = useProductStore(); + + useEffect(() => { + // console.log('products: ', products); + }, [products]) + + return ( + <> + + </> + ) +} + +export default Simulator \ No newline at end of file diff --git a/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx b/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx new file mode 100644 index 0000000..29d404e --- /dev/null +++ b/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function storageUnitInstance() { + return ( + <> + </> + ) +} + +export default storageUnitInstance \ No newline at end of file diff --git a/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx b/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx new file mode 100644 index 0000000..d79b5d8 --- /dev/null +++ b/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import StorageUnitInstance from './storageUnitInstance/storageUnitInstance' + +function StorageUnitInstances() { + return ( + <> + + <StorageUnitInstance /> + + </> + ) +} + +export default StorageUnitInstances \ No newline at end of file diff --git a/app/src/modules/simulation/storageUnit/storageUnit.tsx b/app/src/modules/simulation/storageUnit/storageUnit.tsx new file mode 100644 index 0000000..eee0875 --- /dev/null +++ b/app/src/modules/simulation/storageUnit/storageUnit.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import StorageUnitInstances from './instances/storageUnitInstances' + +function StorageUnit() { + return ( + <> + + <StorageUnitInstances /> + + </> + ) +} + +export default StorageUnit \ No newline at end of file diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx new file mode 100644 index 0000000..f836ea4 --- /dev/null +++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx @@ -0,0 +1,103 @@ +import { useEffect } from "react"; +import { useThree } from "@react-three/fiber"; +import { useSubModuleStore } from "../../../../store/useModuleStore"; +import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../store/simulation/useProductStore"; +import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; + +function TriggerConnector() { + const { gl, raycaster, scene } = useThree(); + const { subModule } = useSubModuleStore(); + const { getPointByUuid, getIsEventInProduct } = useProductStore(); + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + + const canvasElement = gl.domElement; + + let drag = false; + let isRightMouseDown = false; + + const onMouseDown = (evt: MouseEvent) => { + if (selectedAsset) { + clearSelectedAsset(); + } + if (evt.button === 2) { + isRightMouseDown = true; + drag = false; + } + }; + + const onMouseUp = (evt: MouseEvent) => { + if (evt.button === 2) { + isRightMouseDown = false; + } + } + + const onMouseMove = () => { + if (isRightMouseDown) { + drag = true; + } + }; + + const handleRightClick = (evt: MouseEvent) => { + if (drag) return; + evt.preventDefault(); + const canvasElement = gl.domElement; + if (!canvasElement) return; + + let intersects = raycaster.intersectObjects(scene.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; + + if (currentObject && currentObject.name === 'Event-Sphere') { + + const isInProduct = getIsEventInProduct( + selectedProduct.productId, + currentObject.userData.modelUuid + ); + + // You left Here + + if (isInProduct) { + + const event = getPointByUuid( + selectedProduct.productId, + currentObject.userData.modelUuid, + currentObject.userData.pointUuid + ); + console.log('event: ', event); + } else { + + } + } + + } else { + + } + }; + + if (subModule === 'simulations') { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener('contextmenu', handleRightClick); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener('contextmenu', handleRightClick); + }; + + }, [gl, subModule]); + + return ( + <> + </> + ) +} + +export default TriggerConnector \ No newline at end of file diff --git a/app/src/modules/simulation/triggers/temp.md b/app/src/modules/simulation/triggers/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/modules/simulation/triggers/trigger.tsx b/app/src/modules/simulation/triggers/trigger.tsx new file mode 100644 index 0000000..110da2e --- /dev/null +++ b/app/src/modules/simulation/triggers/trigger.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import TriggerConnector from './connector/triggerConnector' + +function Trigger() { + return ( + <> + + <TriggerConnector /> + + </> + ) +} + +export default Trigger \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index cf5fd81..a0eeabd 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -1,62 +1,172 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useFrame, useThree } from '@react-three/fiber'; +import { useFloorItems } from '../../../../../store/store'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; interface VehicleAnimatorProps { - path: [number, number, number][]; - handleCallBack: () => void; - currentPhase: string; - agvUuid: number + path: [number, number, number][]; + handleCallBack: () => void; + currentPhase: string; + agvUuid: number; + agvDetail: any; } +function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail }: VehicleAnimatorProps) { + const { decrementVehicleLoad, vehicles } = useVehicleStore(); + const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset } = useResetButtonStore(); + const [restRotation, setRestingRotation] = useState<boolean>(true); + const [progress, setProgress] = useState<number>(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + const progressRef = useRef<number>(0); + const movingForward = useRef<boolean>(true); + const completedRef = useRef<boolean>(false); + let startTime: number; + let pausedTime: number; + let fixedInterval: number; -function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid }: VehicleAnimatorProps) { - const [progress, setProgress] = useState<number>(0) - const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const { scene } = useThree(); + useEffect(() => { + if (currentPhase === 'stationed-pickup' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'pickup-drop' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'drop-pickup' && path.length > 0) { + setCurrentPath(path); + } + }, [currentPhase, path]); - useEffect(() => { + useEffect(() => { + setProgress(0); + completedRef.current = false; + }, [currentPath]); - if (currentPhase === 'stationed-pickup' && path.length > 0) { - setCurrentPath(path); + useFrame((_, delta) => { + const object = scene.getObjectByProperty('uuid', agvUuid); + if (!object || currentPath.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + let coveredDistance = progressRef.current; + let accumulatedDistance = 0; + let index = 0; + + while ( + index < distances.length && + coveredDistance > accumulatedDistance + distances[index] + ) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); + const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); + const rotationSpeed = 2.0; + const currentAngle = object.rotation.y; + + let angleDifference = targetAngle - currentAngle; + if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; + if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + + const maxRotationStep = rotationSpeed * delta; + object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); + + const isAligned = Math.abs(angleDifference) < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * agvDetail.speed); + coveredDistance = progressRef.current; + + const t = (coveredDistance - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation) { + const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); + object.quaternion.slerp(targetQuaternion, delta * 2); + const angleDiff = object.quaternion.angleTo(targetQuaternion); + if (angleDiff < 0.01) { + let objectRotation = agvDetail.point.rotation + object.rotation.set(objectRotation[0], objectRotation[1], objectRotation[2]); + setRestingRotation(false); } + return; + } + } - }, [currentPhase, path]) + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(firstFrame); + } + } + }); - useFrame((_, delta) => { - if (!path || path.length < 2) return; + function firstFrame() { + const unLoadDuration = agvDetail.point.action.unLoadDuration; + const droppedMaterial = agvDetail.currentLoad; + fixedInterval = (unLoadDuration / droppedMaterial) * 1000; + startTime = performance.now(); + step(droppedMaterial); + } - const object = scene.getObjectByProperty("uuid", agvUuid) - if (!object) return; + function step(droppedMaterial: number) { + const elapsedTime = performance.now() - startTime; - setProgress(prev => { - const next = prev + delta * 0.1; // speed - return next >= 1 ? 1 : next; - }); + if (elapsedTime >= fixedInterval) { + console.log('fixedInterval: ', fixedInterval); + console.log('elapsedTime: ', elapsedTime); + let droppedMat = droppedMaterial - 1; + decrementVehicleLoad(agvDetail.modelUuid, 1); + if (droppedMat === 0) return; + startTime = performance.now(); + requestAnimationFrame(() => step(droppedMat)); + } else { + requestAnimationFrame(() => step(droppedMaterial)); + } + } - const totalSegments = path.length - 1; - const segmentIndex = Math.floor(progress * totalSegments); - const t = progress * totalSegments - segmentIndex; - - const start = path[segmentIndex]; - const end = path[segmentIndex + 1] || start; - - // Directly set position without creating a new Vector3 - object.position.x = start[0] + (end[0] - start[0]) * t; - object.position.y = start[1] + (end[1] - start[1]) * t; - object.position.z = start[2] + (end[2] - start[2]) * t; - }); - // useFrame(() => { - // if (currentPath.length === 0) return; - // const object = scene.getObjectByProperty("uuid", agvUuid); - // if (!object) return; - - - - // }) - return ( + return ( + <> + {currentPath.length > 0 && ( <> + <Line points={currentPath} color="blue" lineWidth={3} /> + {currentPath.map((point, index) => ( + <mesh key={index} position={point}> + <sphereGeometry args={[0.1, 16, 16]} /> + <meshStandardMaterial color="red" /> + </mesh> + ))} </> - ) + )} + </> + ); } -export default VehicleAnimator \ No newline at end of file +export default VehicleAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index bf767ec..d0691f9 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,66 +1,123 @@ -import React, { useCallback, useEffect, useState } from 'react' -import VehicleAnimator from '../animator/vehicleAnimator' -import * as THREE from "three"; +import React, { useCallback, useEffect, useState } from 'react'; +import VehicleAnimator from '../animator/vehicleAnimator'; +import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/store'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; -function VehicleInstance({ agvDetails }: any) { - const { navMesh } = useNavMesh(); - const { isPlaying } = usePlayButtonStore(); - const { setVehicleActive, setVehicleState } = useVehicleStore(); - const [currentPhase, setCurrentPhase] = useState<(string)>("stationed"); - const [path, setPath] = useState<[number, number, number][]>([]); +function VehicleInstance({ agvDetail }: any) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad } = useVehicleStore(); + const [currentPhase, setCurrentPhase] = useState<string>('stationed'); + const [path, setPath] = useState<[number, number, number][]>([]); - const computePath = useCallback((start: any, end: any) => { + const computePath = useCallback( + (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + return ( + segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || [] + ); + } catch { + return []; + } + }, + [navMesh] + ); + function vehicleStatus(modelid: string, status: string) { + // console.log(`AGV ${modelid}: ${status}`); + } - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); - return ( - segmentPath?.map( - ({ x, y, z }) => [x, y + 0.1, z] as [number, number, number] - ) || [] - ); - } catch { - return []; - } - }, [navMesh]); - - useEffect(() => { - - - if (isPlaying) { - if (!agvDetails.isActive && agvDetails.state == "idle" && currentPhase == "stationed") { - const toPickupPath = computePath(new THREE.Vector3(agvDetails.position[0], agvDetails.position[1], agvDetails.position[2]), agvDetails.point.action.pickUpPoint); - setPath(toPickupPath) - setVehicleActive(agvDetails.modelUuid, true) - setVehicleState(agvDetails.modelUuid, "running") - setCurrentPhase("stationed-pickup") - // - } - } - }, [agvDetails, currentPhase, path, isPlaying]) - - function handleCallBack() { - if (currentPhase === "stationed-pickup") { - setVehicleActive(agvDetails.modelUuid, false) - setVehicleState(agvDetails.modelUuid, "idle") - setCurrentPhase("picking") - setPath([]) + useEffect(() => { + if (isPlaying) { + if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { + const toPickupPath = computePath( + new THREE.Vector3(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]), + agvDetail.point.action.pickUpPoint + ); + setPath(toPickupPath); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('stationed-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); + return; + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'picking' + ) { + + setTimeout(() => { + incrementVehicleLoad(agvDetail.modelUuid, 2); + }, 5000); + + if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { + const toDrop = computePath( + agvDetail.point.action.pickUpPoint, + agvDetail.point.action.unLoadPoint + ); + setPath(toDrop); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('pickup-drop'); + vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); } + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'dropping' && + agvDetail.currentLoad === 0 + ) { + const dropToPickup = computePath( + agvDetail.point.action.unLoadPoint, + agvDetail.point.action.pickUpPoint + ); + setPath(dropToPickup); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('drop-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point'); + } } + }, [vehicles, currentPhase, path, isPlaying]); + function handleCallBack() { + if (currentPhase === 'stationed-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); + } else if (currentPhase === 'pickup-drop') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('dropping'); + vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); + setPath([]); + } else if (currentPhase === 'drop-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + setPath([]); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); + } + } - return ( - <> - - <VehicleAnimator path={path} handleCallBack={handleCallBack} currentPhase={currentPhase} agvUuid={agvDetails?.modelUuid} /> - - </> - ) + return ( + <> + <VehicleAnimator + path={path} + handleCallBack={handleCallBack} + currentPhase={currentPhase} + agvUuid={agvDetail?.modelUuid} + agvDetail={agvDetail} + /> + </> + ); } -export default VehicleInstance \ No newline at end of file +export default VehicleInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 0848883..2a0070b 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -1,15 +1,14 @@ import React from 'react' import VehicleInstance from './instance/vehicleInstance' -import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'; +import { useVehicleStore } from '../../../../store/simulation/useVehicleStore' function VehicleInstances() { const { vehicles } = useVehicleStore(); - return ( <> {vehicles.map((val: any, i: any) => - <VehicleInstance agvDetails={val} key={i} /> + <VehicleInstance agvDetail={val} key={i} /> )} </> diff --git a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx index fa0d9dd..370304e 100644 --- a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx @@ -17,7 +17,7 @@ export default function PolygonGenerator({ useEffect(() => { let allLines = arrayLinesToObject(lines.current); const wallLines = allLines?.filter((line) => line?.type === "WallLine"); - const aisleLines = allLines?.filter((line) => line?.type === "AisleLine"); + const aisleLines = allLines?.filter((line) => line?.type === "AisleLine") const wallPoints = wallLines .map((pair) => pair?.line.map((vals) => vals.position)) @@ -39,9 +39,10 @@ export default function PolygonGenerator({ ); const polygons = turf.polygonize(turf.featureCollection(lineFeatures)); + renderWallGeometry(wallPoints); - if (polygons.features.length > 1) { + if (polygons.features.length > 0) { polygons.features.forEach((feature) => { if (feature.geometry.type === "Polygon") { diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 3364717..f0b4d17 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -1,17 +1,19 @@ import React, { useEffect } from 'react' import VehicleInstances from './instances/vehicleInstances'; - import { useVehicleStore } from '../../../store/simulation/useVehicleStore'; +import { useFloorItems } from '../../../store/store'; function Vehicles() { const { vehicles, addVehicle } = useVehicleStore(); + const { floorItems } = useFloorItems(); + const vehicleStatusSample: VehicleEventSchema[] = [ { - modelUuid: "veh-123", - modelName: "Autonomous Truck A1", - position: [10, 0, 5], + modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74", + modelName: "AGV", + position: [97.9252965204558, 0, 37.96138815638661], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -24,11 +26,10 @@ function Vehicles() { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", - material: "crate", - unLoadDuration: 15, - loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, - unLoadPoint: { x: 20, y: 0, z: 10 }, + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, + unLoadPoint: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, triggers: [ { triggerUuid: "trig-001", @@ -53,9 +54,51 @@ function Vehicles() { } }, { - modelUuid: "veh-123", - modelName: "Autonomous Truck A1", - position: [10, 0, 5], + modelUuid: "b06960bb-3d2e-41f7-a646-335f389c68b4", + modelName: "AGV", + position: [89.61609306554463, 0, 33.634136622267356], + rotation: [0, 0, 0], + state: "idle", + type: "vehicle", + speed: 2.5, + point: { + uuid: "point-789", + position: [0, 1, 0], + rotation: [0, 0, 0], + action: { + actionUuid: "action-456", + actionName: "Deliver to Zone A", + actionType: "travel", + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 90, y: 0, z: 28 }, + unLoadPoint: { x: 20, y: 0, z: 10 }, + triggers: [ + { + triggerUuid: "trig-001", + triggerName: "Start Travel", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" }, + triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" }, + triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" } + } + }, + { + triggerUuid: "trig-002", + triggerName: "Complete Travel", + triggerType: "onComplete", + delay: 2, + triggeredAsset: null + } + ] + } + } + }, { + modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79", + modelName: "forklift", + position: [98.85729337188162, 0, 38.36616546567653], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -68,10 +111,9 @@ function Vehicles() { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", - material: "crate", unLoadDuration: 15, loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, unLoadPoint: { x: 20, y: 0, z: 10 }, triggers: [ { @@ -101,19 +143,18 @@ function Vehicles() { useEffect(() => { addVehicle('123', vehicleStatusSample[0]); - addVehicle('123', vehicleStatusSample[1]); + // addVehicle('123', vehicleStatusSample[1]); + // addVehicle('123', vehicleStatusSample[2]); }, []) useEffect(() => { - // console.log('vehicles: ', vehicles); + console.log('vehicles: ', vehicles); }, [vehicles]) return ( <> - <VehicleInstances /> - </> ) } diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index cd2c57c..05f7371 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -12,15 +12,12 @@ import { useFloatingWidget, } from "../../store/visualization/useDroppedObjectsStore"; import { - useAsset3dWidget, useSocketStore, useWidgetSubOption, - useZones, } from "../../store/store"; import { getZone2dData } from "../../services/visulization/zone/getZoneData"; import { generateUniqueId } from "../../functions/generateUniqueId"; import { determinePosition } from "./functions/determinePosition"; -import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets"; import SocketRealTimeViz from "./socket/realTimeVizSocket.dev"; import RenderOverlay from "../../components/templates/Overlay"; import ConfirmationPopup from "../../components/layout/confirmationPopup/ConfirmationPopup"; @@ -68,20 +65,15 @@ const RealTimeVisulization: React.FC = () => { const containerRef = useRef<HTMLDivElement>(null); const { isPlaying } = usePlayButtonStore(); const { activeModule } = useModuleStore(); - const [droppedObjects, setDroppedObjects] = useState<any[]>([]); const [zonesData, setZonesData] = useState<FormattedZoneData>({}); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - const { rightSelect, setRightSelect } = useRightSelected(); - const { editWidgetOptions, setEditWidgetOptions } = - useEditWidgetOptionsStore(); + const { setRightSelect } = useRightSelected(); + const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore(); const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false); - - // const [floatingWidgets, setFloatingWidgets] = useState<Record<string, { zoneName: string; zoneId: string; objects: any[] }>>({}); - const { floatingWidget, setFloatingWidget } = useFloatingWidget(); - const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); - const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); + const { setFloatingWidget } = useFloatingWidget(); + const { widgetSubOption } = useWidgetSubOption(); const { visualizationSocket } = useSocketStore(); const { setSelectedChartId } = useWidgetStore(); @@ -99,11 +91,10 @@ const RealTimeVisulization: React.FC = () => { useEffect(() => { async function GetZoneData() { - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); - // console.log('response: ', response); if (!Array.isArray(response)) { return; @@ -125,7 +116,9 @@ const RealTimeVisulization: React.FC = () => { {} ); setZonesData(formattedData); - } catch (error) {} + } catch (error) { + console.log(error); + } } GetZoneData(); @@ -151,12 +144,10 @@ const RealTimeVisulization: React.FC = () => { }); }, [selectedZone]); - // useEffect(() => {}, [floatingWidgets]); - const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => { event.preventDefault(); try { - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; const data = event.dataTransfer.getData("text/plain"); @@ -172,8 +163,8 @@ const RealTimeVisulization: React.FC = () => { const relativeY = event.clientY - rect.top; // Widget dimensions - const widgetWidth = droppedData.width || 125; - const widgetHeight = droppedData.height || 100; + const widgetWidth = droppedData.width ?? 125; + const widgetHeight = droppedData.height ?? 100; // Center the widget at cursor const centerOffsetX = widgetWidth / 2; @@ -275,7 +266,7 @@ const RealTimeVisulization: React.FC = () => { return () => { document.removeEventListener("mousedown", handleClickOutside); }; - }, [setRightClickSelected]); + }, [setRightClickSelected, setRightSelect]); const [canvasDimensions, setCanvasDimensions] = useState({ width: 0, @@ -340,6 +331,7 @@ const RealTimeVisulization: React.FC = () => { borderRadius: isPlaying || activeModule !== "visualization" ? "" : "6px", }} + role="application" onDrop={(event) => handleDrop(event)} onDragOver={(event) => event.preventDefault()} > @@ -362,6 +354,10 @@ const RealTimeVisulization: React.FC = () => { "RotateY", "Delete", ]} + onClick={(e) => { + setRightSelect(e); + setEditWidgetOptions(false); + }} /> )} diff --git a/app/src/modules/visualization/visualization.tsx b/app/src/modules/visualization/visualization.tsx index e5b1692..77956f4 100644 --- a/app/src/modules/visualization/visualization.tsx +++ b/app/src/modules/visualization/visualization.tsx @@ -3,9 +3,8 @@ import Dropped3dWidgets from './widgets/3d/Dropped3dWidget' import ZoneCentreTarget from './zone/zoneCameraTarget' import ZoneAssets from './zone/zoneAssets' import MqttEvents from '../../services/factoryBuilder/mqtt/mqttEvents' -import DrieHtmlTemp from './mqttTemp/drieHtmlTemp' -const Visualization = () => { +const Visualization:React.FC = () => { return ( <> diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index a5a7c57..19393f9 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -2,7 +2,11 @@ import React, { useState, FormEvent } from "react"; import { useNavigate } from "react-router-dom"; import { LogoIconLarge } from "../components/icons/Logo"; import { EyeIcon } from "../components/icons/ExportCommonIcons"; -import { useLoadingProgress, useOrganization, useUserName } from "../store/store"; +import { + useLoadingProgress, + useOrganization, + useUserName, +} from "../store/store"; import { signInApi } from "../services/factoryBuilder/signInSignUp/signInApi"; import { signUpApi } from "../services/factoryBuilder/signInSignUp/signUpApi"; @@ -21,7 +25,7 @@ const UserAuth: React.FC = () => { const handleLogin = async (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); - const organization = (email.split("@")[1]).split(".")[0]; + const organization = email.split("@")[1].split(".")[0]; try { const res = await signInApi(email, password, organization); @@ -39,7 +43,7 @@ const UserAuth: React.FC = () => { } else if (res.message === "User Not Found!!! Kindly signup...") { setError("Account not found"); } - } catch (error) { } + } catch (error) {} }; const handleRegister = async (e: FormEvent) => { @@ -47,7 +51,7 @@ const UserAuth: React.FC = () => { if (email && password && userName) { setError(""); try { - const organization = (email.split("@")[1]).split(".")[0]; + const organization = email.split("@")[1].split(".")[0]; const res = await signUpApi(userName, email, password, organization); if (res.message === "New User created") { @@ -56,123 +60,121 @@ const UserAuth: React.FC = () => { if (res.message === "User already exists") { setError("User already exists"); } - } catch (error) { } + } catch (error) {} } else { setError("Please fill all the fields!"); } }; return ( - <> - <div className="auth-container"> - <div className="logo-icon"> - <LogoIconLarge /> - </div> - <h1>Welcome to Dwinzo</h1> - <p> - {isSignIn ? ( - <> - Don’t have an account?{" "} - <span - className="link" - onClick={() => setIsSignIn(false)} - style={{ cursor: "pointer" }} - > - Register here! - </span> - </> - ) : ( - <> - Already have an account?{" "} - <span - className="link" - onClick={() => setIsSignIn(true)} - style={{ cursor: "pointer" }} - > - Login here! - </span> - </> - )} - </p> + <div className="auth-container"> + <div className="logo-icon"> + <LogoIconLarge /> + </div> + <h1>Welcome to Dwinzo</h1> + <p> + {isSignIn ? ( + <> + Don’t have an account?{" "} + <span + className="link" + onClick={() => setIsSignIn(false)} + style={{ cursor: "pointer" }} + > + Register here! + </span> + </> + ) : ( + <> + Already have an account?{" "} + <span + className="link" + onClick={() => setIsSignIn(true)} + style={{ cursor: "pointer" }} + > + Login here! + </span> + </> + )} + </p> - <button className="google-login"> - <span className="google-icon">G</span> Continue with Google - </button> + <button className="google-login"> + <span className="google-icon">G</span> Continue with Google + </button> - {error && <div className="error-message">🛈 {error}</div>} + {error && <div className="error-message">🛈 {error}</div>} - <form - onSubmit={isSignIn ? handleLogin : handleRegister} - className="auth-form" - > - {!isSignIn && ( - <input - type="text" - value={userName} - placeholder="Username" - onChange={(e) => setUserName(e.target.value)} - required - /> - )} + <form + onSubmit={isSignIn ? handleLogin : handleRegister} + className="auth-form" + > + {!isSignIn && ( <input - type="email" - name="email" - value={email} - placeholder="Email" - onChange={(e) => setEmail(e.target.value)} + type="text" + value={userName} + placeholder="Username" + onChange={(e) => setUserName(e.target.value)} required /> - <div className="password-container"> - <input - name="password" - type={showPassword ? "text" : "password"} - value={password} - placeholder="Password" - onChange={(e) => setPassword(e.target.value)} - required - /> - <button - type="button" - className="toggle-password" - onClick={() => setShowPassword(!showPassword)} - > - <EyeIcon isClosed={showPassword} /> - </button> - </div> - {!isSignIn && ( - <div className="policy-checkbox"> - <input type="checkbox" name="" id="" required /> - <div className="label"> - I have read and agree to the terms of service - </div> - </div> - )} - <button type="submit" className="continue-button"> - {isSignIn ? "Continue" : "Register"} + )} + <input + type="email" + name="email" + value={email} + placeholder="Email" + onChange={(e) => setEmail(e.target.value)} + required + /> + <div className="password-container"> + <input + name="password" + type={showPassword ? "text" : "password"} + value={password} + placeholder="Password" + onChange={(e) => setPassword(e.target.value)} + required + /> + <button + type="button" + className="toggle-password" + onClick={() => setShowPassword(!showPassword)} + > + <EyeIcon isClosed={showPassword} /> </button> - </form> - <p className="policy"> - By signing up for, or logging into, an account, you agree to our{" "} - <span - className="link" - onClick={() => navigate("/privacy")} - style={{ cursor: "pointer" }} - > - privacy policy - </span>{" "} - &{" "} - <span - className="link" - onClick={() => navigate("/terms")} - style={{ cursor: "pointer" }} - > - terms of service - </span>{" "} - whether you read them or not. You can also find these terms on our - website. - </p> - </div> - </> + </div> + {!isSignIn && ( + <div className="policy-checkbox"> + <input type="checkbox" name="" id="" required /> + <div className="label"> + I have read and agree to the terms of service + </div> + </div> + )} + <button type="submit" className="continue-button"> + {isSignIn ? "Continue" : "Register"} + </button> + </form> + <p className="policy"> + By signing up for, or logging into, an account, you agree to our{" "} + <span + className="link" + onClick={() => navigate("/privacy")} + style={{ cursor: "pointer" }} + > + privacy policy + </span>{" "} + &{" "} + <span + className="link" + onClick={() => navigate("/terms")} + style={{ cursor: "pointer" }} + > + terms of service + </span>{" "} + whether you read them or not. You can also find these terms on our + website. + </p> + </div> ); }; diff --git a/app/src/services/simulation/UpsertProductOrEventApi.ts b/app/src/services/simulation/UpsertProductOrEventApi.ts new file mode 100644 index 0000000..e2f45d1 --- /dev/null +++ b/app/src/services/simulation/UpsertProductOrEventApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const upsertProductOrEventApi = async (body: any) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/UpsertProductOrEvent`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to add product or event"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/deleteEventDataApi.ts b/app/src/services/simulation/deleteEventDataApi.ts new file mode 100644 index 0000000..f263065 --- /dev/null +++ b/app/src/services/simulation/deleteEventDataApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteEventDataApi = async (body: any) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/EventDataDelete`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to delete event data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/deleteProductDataApi.ts b/app/src/services/simulation/deleteProductDataApi.ts new file mode 100644 index 0000000..06718f8 --- /dev/null +++ b/app/src/services/simulation/deleteProductDataApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteProductDataApi = async (productId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productDataDelete?productId=${productId}&organization=${organization}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to delete product data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/getProductApi.ts b/app/src/services/simulation/getProductApi.ts new file mode 100644 index 0000000..cf80013 --- /dev/null +++ b/app/src/services/simulation/getProductApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getProductApi = async (productId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productDatas?productId=${productId}&organization=${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch product data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/simulation/getallProductsApi.ts b/app/src/services/simulation/getallProductsApi.ts new file mode 100644 index 0000000..46627f9 --- /dev/null +++ b/app/src/services/simulation/getallProductsApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getAllProductsApi = async ( organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/AllProducts/${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch all products data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/simulation/temp.md b/app/src/services/simulation/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/services/visulization/zone/getSelect2dZoneData.ts b/app/src/services/visulization/zone/getSelect2dZoneData.ts index b2c39e9..00d4dfe 100644 --- a/app/src/services/visulization/zone/getSelect2dZoneData.ts +++ b/app/src/services/visulization/zone/getSelect2dZoneData.ts @@ -7,7 +7,7 @@ export const getSelect2dZoneData = async ( ) => { try { const response = await fetch( - `${url_Backend_dwinzo}/api/v2/Zone/visualization/${ZoneId}?organization=${organization}`, + `${url_Backend_dwinzo}/api/v2/ZoneVisualization/${ZoneId}?organization=${organization}`, { method: "GET", headers: { diff --git a/app/src/store/simulation/useArmBotStore.ts b/app/src/store/simulation/useArmBotStore.ts index 493a068..642762f 100644 --- a/app/src/store/simulation/useArmBotStore.ts +++ b/app/src/store/simulation/useArmBotStore.ts @@ -17,8 +17,11 @@ interface ArmBotStore { addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void; removeAction: (modelUuid: string, actionUuid: string) => void; - setArmBotActive: (modelUuid: string, isActive: boolean) => void; + updateStartPoint: (modelUuid: string, actionUuid: string, startPoint: [number, number, number] | null) => void; + updateEndPoint: (modelUuid: string, actionUuid: string, endPoint: [number, number, number] | null) => void; + setArmBotActive: (modelUuid: string, isActive: boolean) => void; + setArmBotState: (modelUuid: string, newState: ArmBotStatus['state']) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; @@ -72,7 +75,6 @@ export const useArmBotStore = create<ArmBotStore>()( actionUuid: action.actionUuid, actionName: action.actionName, }; - armBot.isActive = true; } } }); @@ -83,7 +85,6 @@ export const useArmBotStore = create<ArmBotStore>()( const armBot = state.armBots.find(a => a.modelUuid === modelUuid); if (armBot) { armBot.currentAction = undefined; - armBot.isActive = false; } }); }, @@ -106,6 +107,30 @@ export const useArmBotStore = create<ArmBotStore>()( }); }, + updateStartPoint: (modelUuid, actionUuid, startPoint) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + const action = armBot.point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + action.process.startPoint = startPoint; + } + } + }); + }, + + updateEndPoint: (modelUuid, actionUuid, endPoint) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + const action = armBot.point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + action.process.endPoint = endPoint; + } + } + }); + }, + setArmBotActive: (modelUuid, isActive) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); @@ -115,6 +140,15 @@ export const useArmBotStore = create<ArmBotStore>()( }); }, + setArmBotState: (modelUuid, newState) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + armBot.state = newState; + } + }); + }, + incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts new file mode 100644 index 0000000..56a35a7 --- /dev/null +++ b/app/src/store/simulation/useMaterialStore.ts @@ -0,0 +1,76 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +type MaterialsStore = { + materials: MaterialsSchema; + + addMaterial: (material: MaterialSchema) => void; + removeMaterial: (materialId: string) => void; + updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => void; + + setStartTime: (materialId: string, startTime: string) => void; + setEndTime: (materialId: string, endTime: string) => void; + setCost: (materialId: string, cost: number) => void; + setWeight: (materialId: string, weight: number) => void; + + getMaterialById: (materialId: string) => MaterialSchema | undefined; +}; + +export const useMaterialStore = create<MaterialsStore>()( + immer((set, get) => ({ + materials: [], + + addMaterial: (material) => { + set((state) => { + state.materials.push(material); + }); + }, + + removeMaterial: (materialId) => { + set((state) => { + state.materials = state.materials.filter(m => m.materialId !== materialId); + }); + }, + + updateMaterial: (materialId, updates) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) { + Object.assign(material, updates); + } + }); + }, + + setStartTime: (materialId, startTime) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.startTime = startTime; + }); + }, + + setEndTime: (materialId, endTime) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.endTime = endTime; + }); + }, + + setCost: (materialId, cost) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.cost = cost; + }); + }, + + setWeight: (materialId, weight) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.weight = weight; + }); + }, + + getMaterialById: (materialId) => { + return get().materials.find(m => m.materialId === materialId); + }, + })) +); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 41a13f6..8ec74cf 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -7,7 +7,7 @@ type ProductsStore = { // Product-level actions addProduct: (productName: string, productId: string) => void; removeProduct: (productId: string) => void; - updateProduct: (productId: string, updates: Partial<{ productName: string; eventsData: EventsSchema[] }>) => void; + updateProduct: (productId: string, updates: Partial<{ productName: string; eventDatas: EventsSchema[] }>) => void; // Event-level actions addEvent: (productId: string, event: EventsSchema) => void; @@ -48,8 +48,18 @@ type ProductsStore = { updates: Partial<TriggerSchema> ) => void; + // Renaming functions + renameProduct: (productId: string, newName: string) => void; + renameAction: (actionUuid: string, newName: string) => void; + renameTrigger: (triggerUuid: string, newName: string) => void; + // Helper functions - getProductById: (productId: string) => { productName: string; productId: string; eventsData: EventsSchema[] } | undefined; + getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined; + getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined; + getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined; + getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined; + getTriggerByUuid: (productId: string, triggerUuid: string) => TriggerSchema | undefined; + getIsEventInProduct: (productId: string, modelUuid: string) => boolean; }; export const useProductStore = create<ProductsStore>()( @@ -62,7 +72,7 @@ export const useProductStore = create<ProductsStore>()( const newProduct = { productName, productId: productId, - eventsData: [] + eventDatas: [] }; state.products.push(newProduct); }); @@ -88,7 +98,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventsData.push(event); + product.eventDatas.push(event); } }); }, @@ -97,7 +107,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventsData = product.eventsData.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); + product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); } }); }, @@ -106,7 +116,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event) { Object.assign(event, updates); } @@ -119,7 +129,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); } else if (event && 'point' in event) { @@ -133,7 +143,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points = (event as ConveyorEventSchema).points.filter(p => p.uuid !== pointUuid); } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { @@ -147,7 +157,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); if (point) { @@ -165,7 +175,7 @@ export const useProductStore = create<ProductsStore>()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); if (point) { @@ -185,7 +195,7 @@ export const useProductStore = create<ProductsStore>()( removeAction: (actionUuid: string) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { // Handle ConveyorEventSchema for (const point of (event as ConveyorEventSchema).points) { @@ -209,7 +219,7 @@ export const useProductStore = create<ProductsStore>()( updateAction: (actionUuid, updates) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -239,7 +249,7 @@ export const useProductStore = create<ProductsStore>()( addTrigger: (actionUuid, trigger) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -268,7 +278,7 @@ export const useProductStore = create<ProductsStore>()( removeTrigger: (triggerUuid) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -295,7 +305,7 @@ export const useProductStore = create<ProductsStore>()( updateTrigger: (triggerUuid, updates) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -331,9 +341,170 @@ export const useProductStore = create<ProductsStore>()( }); }, + // Renaming functions + renameProduct: (productId, newName) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + product.productName = newName; + } + }); + }, + + renameAction: (actionUuid, newName) => { + set((state) => { + for (const product of state.products) { + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + point.action.actionName = newName; + return; + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && point.action.actionUuid === actionUuid) { + point.action.actionName = newName; + return; + } else if ('actions' in point) { + const action = point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action) { + action.actionName = newName; + return; + } + } + } + } + } + }); + }, + + renameTrigger: (triggerUuid, newName) => { + set((state) => { + for (const product of state.products) { + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && 'triggers' in point.action) { + const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); + if (trigger) { + trigger.triggerName = newName; + return; + } + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && 'triggers' in point.action) { + const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (trigger) { + trigger.triggerName = newName; + return; + } + } else if ('actions' in point) { + for (const action of point.actions) { + if ('triggers' in action) { + const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (trigger) { + trigger.triggerName = newName; + return; + } + } + } + } + } + } + } + }); + }, + // Helper functions getProductById: (productId) => { return get().products.find(p => p.productId === productId); + }, + + getEventByModelUuid: (productId, modelUuid) => { + const product = get().getProductById(productId); + if (!product) return undefined; + return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + }, + + getPointByUuid: (productId, modelUuid, pointUuid) => { + const event = get().getEventByModelUuid(productId, modelUuid); + if (!event) return undefined; + + if ('points' in event) { + return (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); + } else if ('point' in event && (event as any).point.uuid === pointUuid) { + return (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point; + } + return undefined; + }, + + getActionByUuid: (productId, actionUuid) => { + const product = get().products.find(p => p.productId === productId); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return point.action; + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && point.action?.actionUuid === actionUuid) { + return point.action; + } else if ('actions' in point) { + const action = point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action) return action; + } + } + } + return undefined; + }, + + getTriggerByUuid: (productId, triggerUuid) => { + const product = get().products.find(p => p.productId === productId); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } else if ('actions' in point) { + for (const action of point.actions) { + for (const trigger of action.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } + } + } + return undefined; + }, + + getIsEventInProduct: (productId, modelUuid) => { + const product = get().getProductById(productId); + if (!product) return false; + return product.eventDatas.some(e => 'modelUuid' in e && e.modelUuid === modelUuid); } })) ); diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts new file mode 100644 index 0000000..5085688 --- /dev/null +++ b/app/src/store/simulation/useSimulationStore.ts @@ -0,0 +1,117 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import * as THREE from 'three'; + +interface SelectedEventSphereState { + selectedEventSphere: THREE.Mesh | null; + setSelectedEventSphere: (mesh: THREE.Mesh | null) => void; + clearSelectedEventSphere: () => void; +} + +export const useSelectedEventSphere = create<SelectedEventSphereState>()( + immer((set) => ({ + selectedEventSphere: null, + setSelectedEventSphere: (mesh) => { + set((state) => { + state.selectedEventSphere = mesh; + }); + }, + clearSelectedEventSphere: () => { + set((state) => { + state.selectedEventSphere = null; + }); + }, + })) +); + +interface SelectedEventDataState { + selectedEventData: { data: EventsSchema; selectedPoint: string } | undefined; + setSelectedEventData: (data: EventsSchema, selectedPoint: string) => void; + clearSelectedEventData: () => void; +} + +export const useSelectedEventData = create<SelectedEventDataState>()( + immer((set) => ({ + selectedEventData: undefined, + setSelectedEventData: (data, selectedPoint) => { + set((state) => { + state.selectedEventData = { data, selectedPoint }; + }); + }, + clearSelectedEventData: () => { + set((state) => { + state.selectedEventData = undefined; + }); + }, + })) +); + +interface SelectedAssetState { + selectedAsset: EventsSchema | undefined; + setSelectedAsset: (EventData: EventsSchema) => void; + clearSelectedAsset: () => void; +} + +export const useSelectedAsset = create<SelectedAssetState>()( + immer((set) => ({ + selectedAsset: undefined, + setSelectedAsset: (EventData) => { + set((state) => { + state.selectedAsset = EventData; + }); + }, + clearSelectedAsset: () => { + set((state) => { + state.selectedAsset = undefined; + }); + }, + })) +); + +interface SelectedProductState { + selectedProduct: { productId: string; productName: string }; + setSelectedProduct: (productId: string, productName: string) => void; + clearSelectedProduct: () => void; +} + +export const useSelectedProduct = create<SelectedProductState>()( + immer((set) => ({ + selectedProduct: { productId: '', productName: '' }, + setSelectedProduct: (productId, productName) => { + set((state) => { + state.selectedProduct.productId = productId; + state.selectedProduct.productName = productName; + }); + }, + clearSelectedProduct: () => { + set((state) => { + state.selectedProduct.productId = ''; + state.selectedProduct.productName = ''; + }); + }, + })) +); + +interface SelectedActionState { + selectedAction: { actionId: string; actionName: string }; + setSelectedAction: (actionId: string, actionName: string) => void; + clearSelectedAction: () => void; +} + +export const useSelectedAction = create<SelectedActionState>()( + immer((set) => ({ + selectedAction: { actionId: '', actionName: '' }, + setSelectedAction: (actionId, actionName) => { + set((state) => { + state.selectedAction.actionId = actionId; + state.selectedAction.actionName = actionName; + }); + }, + clearSelectedAction: () => { + set((state) => { + state.selectedAction.actionId = ''; + state.selectedAction.actionName = ''; + }); + }, + })) +); \ No newline at end of file diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index ce28916..449ceb7 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -1,3 +1,4 @@ + import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; @@ -88,7 +89,7 @@ export const useVehicleStore = create<VehiclesStore>()( set((state) => { const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); if (vehicle) { - vehicle.currentLoad = decrementBy; + vehicle.currentLoad -= decrementBy; } }); }, diff --git a/app/src/store/useModuleStore.ts b/app/src/store/useModuleStore.ts index 1012792..3cc1d00 100644 --- a/app/src/store/useModuleStore.ts +++ b/app/src/store/useModuleStore.ts @@ -13,14 +13,17 @@ const useModuleStore = create<ModuleStore>((set) => ({ export default useModuleStore; // New store for subModule + +type SubModule = 'properties' | 'simulations' | 'mechanics' | 'analysis' | 'zoneProperties'; + interface SubModuleStore { - subModule: string; - setSubModule: (subModule: string) => void; + subModule: SubModule; + setSubModule: (subModule: SubModule) => void; } const useSubModuleStore = create<SubModuleStore>((set) => ({ subModule: "properties", // Initial subModule state - setSubModule: (subModule) => set({ subModule }), // Update subModule state + setSubModule: (value) => set({ subModule: value }), // Update subModule state })); export { useSubModuleStore }; diff --git a/app/src/styles/abstracts/placeholders.scss b/app/src/styles/abstracts/placeholders.scss deleted file mode 100644 index 18f28f9..0000000 --- a/app/src/styles/abstracts/placeholders.scss +++ /dev/null @@ -1,6 +0,0 @@ -// center a element -%centered { - display: flex; - justify-content: center; - align-items: center; -} diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss index 44e5627..6bb3d57 100644 --- a/app/src/styles/abstracts/variables.scss +++ b/app/src/styles/abstracts/variables.scss @@ -1,123 +1,132 @@ -/* ======================================================================== - Global SCSS Variables - ======================================================================== - This file contains the global variables used across the project for - colors, typography, spacing, shadows, and other design tokens. - ======================================================================== */ - @use "functions"; -// ======================================================================== -// Font Imports -// ======================================================================== @import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Josefin+Sans:ital,wght@0,100..700;1,100..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"); -// ======================================================================== -// Colors -// ======================================================================== +// new variables -// Text colors -$text-color: #2b3344; // Primary text color -$text-disabled: #b7b7c6; // Disabled text color -$input-text-color: #595965; // Input field text color +// text colors +// ---------- light mode ---------- +$text-color: #2b3344; +$text-disabled: #b7b7c6; +$input-text-color: #595965; +$highlight-text-color: #6f42c1; -$text-color-dark: #f3f3fd; // Primary text color for dark mode -$text-disabled-dark: #6f6f7a; // Disabled text color for dark mode -$input-text-color-dark: #b5b5c8; // Input field text color for dark mode +// ---------- dark mode ---------- +$text-color-dark: #f3f3fd; +$text-disabled-dark: #6f6f7a; +$input-text-color-dark: #b5b5c8; +$highlight-text-color-dark: #B392F0; -// Accent colors -$accent-color: #6f42c1; // Primary accent color -$accent-color-dark: #c4abf1; // Primary accent color for dark mode -$highlight-accent-color: #e0dfff; // Highlighted accent for light mode -$highlight-accent-color-dark: #403e6a; // Highlighted accent for dark mode +// background colors +// ---------- light mode ---------- +$background-color: linear-gradient(-45deg, #FCFDFDCC 0%, #FCFDFD99 100%); +$background-color-secondary: #FCFDFD4D; +$background-color-accent: #6f42c1; +$background-color-button: #6f42c1; +$background-color-drop-down: #6F42C14D; +$background-color-input: #FFFFFF4D; +$background-color-input-focus: #F2F2F7; +$background-color-drop-down-gradient: linear-gradient(-45deg, #75649366 0%, #40257266 100%); +$background-color-selected: #E0DFFF; +$background-radial-gray-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%); -// Background colors -$background-color: #fcfdfd; // Main background color -$background-color-dark: #19191d; // Main background color for dark mode -$background-color-secondary: #e1e0ff80; // Secondary background color -$background-color-secondary-dark: #39394f99; // Secondary background color for dark mode -$background-color-gray: #f3f3f3; // Main background color -$background-color-gray-dark: #232323; // Main background color for dark mode +// ---------- dark mode ---------- +$background-color-dark: linear-gradient(-45deg, #333333B3 0%, #2D2437B3 100%); +$background-color-secondary-dark: #19191D99; +$background-color-accent-dark: #6f42c1; +$background-color-button-dark: #6f42c1; +$background-color-drop-down-dark: #50505080; +$background-color-input-dark: #FFFFFF33; +$background-color-input-focus-dark: #333333; +$background-color-drop-down-gradient-dark: linear-gradient(-45deg, #8973B166 0%, #53427366 100%); +$background-color-selected-dark: #403E66; +$background-radial-gray-gradient-dark: radial-gradient(circle, #31373b 0%, #48494b 46%, #52415c 100%); -// Border colors -$border-color: #e0dfff; // Default border color -$border-color-dark: #403e6a; // Border color for dark mode +// border colors +// ---------- light mode ---------- +$border-color: #E0DFFF; +$border-color-accent: #6F42C1; -// Shadow color -$shadow-color: #3c3c431a; // Shadow base color for light and dark mode -$shadow-color-dark: #8f8f8f1a; // Shadow base color for light and dark mode +// ---------- dark mode ---------- +$border-color-dark: #564B69; +$border-color-accent-dark: #6F42C1; -// Gradients -$acent-gradient-dark: linear-gradient( - 90deg, - #b392f0 0%, - #a676ff 100% -); // Dark mode accent gradient -$acent-gradient: linear-gradient( - 90deg, - #6f42c1 0%, - #925df3 100% -); // Light mode accent gradient +// highlight colors +// ---------- light mode ---------- +$highlight-accent-color: #E0DFFF; +$highlight-secondary-color: #6F42C1; + +// ---------- dark mode ---------- +$highlight-accent-color-dark: #403E6A; +$highlight-secondary-color-dark: #C4ABF1; + +// colors +$color1: #A392CD; +$color2: #7b4cd3; +$color3: #B186FF; +$color4: #8752E8; +$color5: #C7A8FF; + + +// old variables +$accent-color: #6f42c1; +$accent-color-dark: #c4abf1; +$highlight-accent-color: #e0dfff; +$highlight-accent-color-dark: #403e6a; + +$background-color: #fcfdfd; +$background-color-dark: #19191d; +$background-color-secondary: #e1e0ff80; +$background-color-secondary-dark: #39394f99; +$background-color-gray: #f3f3f3; +$background-color-gray-dark: #232323; + +$border-color: #e0dfff; +$border-color-dark: #403e6a; + +$shadow-color: #3c3c431a; +$shadow-color-dark: #8f8f8f1a; + +$acent-gradient-dark: linear-gradient(90deg, #b392f0 0%, #a676ff 100%); +$acent-gradient: linear-gradient(90deg, #6f42c1 0%, #925df3 100%); $faint-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%); $faint-gradient-dark: radial-gradient(circle, #31373b 0%, #48494b 46%, #52415c 100%); -// ======================================================================== -// Typography -// ======================================================================== +$font-inter: "Inter", sans-serif; +$font-josefin-sans: "Josefin Sans", sans-serif; +$font-poppins: "Poppins", sans-serif; +$font-roboto: "Roboto", sans-serif; -// Font Family Variables -$font-inter: "Inter", sans-serif; // Inter font -$font-josefin-sans: "Josefin Sans", sans-serif; // Josefin Sans font -$font-poppins: "Poppins", sans-serif; // Poppins font -$font-roboto: "Roboto", sans-serif; // Roboto font +$tiny: 0.625rem; +$small: 0.75rem; +$regular: 0.8rem; +$large: 1rem; +$xlarge: 1.125rem; +$xxlarge: 1.5rem; +$xxxlarge: 2rem; -// Font sizes (converted to rem using a utility function) -$tiny: 0.625rem; // Extra small text (10px) -$small: 0.75rem; // Small text (12px) -$regular: 0.8rem; // Default text size (14px) -$large: 1rem; // Large text size (16px) -$xlarge: 1.125rem; // Extra large text size (18px) -$xxlarge: 1.5rem; // Double extra large text size (24px) -$xxxlarge: 2rem; // Triple extra large text size (32px) +$thin-weight: 300; +$regular-weight: 400; +$medium-weight: 500; +$bold-weight: 600; -// Font weights -$thin-weight: 300; // Regular font weight -$regular-weight: 400; // Regular font weight -$medium-weight: 500; // Medium font weight -$bold-weight: 600; // Bold font weight +$z-index-drei-html: 1; +$z-index-default: 1; +$z-index-marketplace: 2; +$z-index-tools: 3; +$z-index-negative: -1; +$z-index-ui-base: 10; +$z-index-ui-overlay: 20; +$z-index-ui-popup: 30; +$z-index-ui-highest: 50; -// ======================================================================== -// Z-Index Levels -// ======================================================================== +$box-shadow-light: 0px 2px 4px $shadow-color; +$box-shadow-medium: 0px 4px 8px $shadow-color; +$box-shadow-heavy: 0px 8px 16px $shadow-color; -// Z-index variables for layering -$z-index-drei-html: 1; // For drei's Html components -$z-index-default: 1; // For drei's Html components -$z-index-marketplace: 2; // For drei's Html components -$z-index-tools: 3; // For drei's Html components -$z-index-negative: -1; // For drei's Html components -$z-index-ui-base: 10; // Base UI elements -$z-index-ui-overlay: 20; // Overlay UI elements (e.g., modals, tooltips) -$z-index-ui-popup: 30; // Popups, dialogs, or higher-priority UI elements -$z-index-ui-highest: 50; // Highest priority elements (e.g., notifications, loading screens) - -// ======================================================================== -// Shadows -// ======================================================================== - -// Box shadow variables -$box-shadow-light: 0px 2px 4px $shadow-color; // Light shadow -$box-shadow-medium: 0px 4px 8px $shadow-color; // Medium shadow -$box-shadow-heavy: 0px 8px 16px $shadow-color; // Heavy shadow - -// ======================================================================== -// Border Radius -// ======================================================================== - -// Border radius variables -$border-radius-small: 4px; // Small rounded corners -$border-radius-medium: 6px; // Medium rounded corners -$border-radius-large: 12px; // Large rounded corners -$border-radius-circle: 50%; // Fully circular -$border-radius-extra-large: 20px; // Extra-large rounded corners +$border-radius-small: 4px; +$border-radius-medium: 6px; +$border-radius-large: 12px; +$border-radius-circle: 50%; +$border-radius-extra-large: 20px; diff --git a/app/src/styles/base/global.scss b/app/src/styles/base/global.scss new file mode 100644 index 0000000..3ba017e --- /dev/null +++ b/app/src/styles/base/global.scss @@ -0,0 +1,5 @@ +section, .section{ + padding: 12px; + outline: 1px solid var(--border-color); + border-radius: 16px; +} diff --git a/app/src/styles/base/reset.scss b/app/src/styles/base/reset.scss index 82d286e..ab77f9a 100644 --- a/app/src/styles/base/reset.scss +++ b/app/src/styles/base/reset.scss @@ -12,3 +12,10 @@ input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button input[type="password"]::-webkit-inner-spin-button { /* Just in case */ display: none; } + +button{ + border: none; + outline: none; + background: none; + cursor: pointer; +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index cab078e..a102feb 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -366,15 +366,66 @@ min-height: 50vh; padding-bottom: 12px; position: relative; - display: flex; - flex-direction: column; + overflow: auto; .sidebar-right-content-container { border-bottom: 1px solid var(--border-color); - // flex: 1; height: calc(100% - 36px); position: relative; - overflow: auto; + width: 320px; + .no-event-selected { + color: #666; + padding: 1.8rem 1rem; + grid-column: 1 / -1; + .products-list { + padding-top: 1rem; + .products-list-title { + text-align: start; + color: var(--accent-color); + font-size: var(--font-size-regular); + } + ul { + li { + text-align: start; + margin: 8px 0; + padding: 2px 0; + text-decoration: none; + &::marker { + content: ""; + } + button { + width: fit-content; + position: relative; + transition: all 0.2s ease; + @include flex-center; + gap: 4px; + &:before { + content: ""; + position: absolute; + left: 0; + bottom: -4px; + background: var(--accent-color); + height: 1px; + width: 0%; + transition: all 0.3s ease; + } + } + &:hover { + button { + path { + stroke: var(--accent-color); + stroke-width: 1.5px; + } + color: var(--accent-color); + &:before { + width: 100%; + } + } + } + } + } + } + } } } @@ -675,10 +726,14 @@ color: var(--primary-color); border-radius: #{$border-radius-small}; cursor: pointer; - + outline: none; + border: none; path { stroke: var(--primary-color); } + &:disabled { + background-color: var(--text-disabled); + } } } @@ -706,7 +761,7 @@ } .selected-actions-list { margin-bottom: 8px; - .eye-dropper-input-container{ + .eye-dropper-input-container { padding: 6px 12px; .regularDropdown-container { padding: 5px 8px; @@ -747,19 +802,21 @@ width: 100%; margin: 2px 0; border-radius: #{$border-radius-small}; - } + .value { + display: flex; + justify-content: flex-start; + align-items: center; + min-width: 80%; + gap: 6px; + .input-value { + text-align: start; + } - .value { - display: flex; - justify-content: flex-start; - align-items: center; - min-width: 80%; - gap: 6px; - - input { - width: fit-content; - outline: none; - accent-color: var(--accent-color); + input { + width: fit-content; + outline: none; + accent-color: var(--accent-color); + } } } @@ -797,6 +854,7 @@ @include flex-center; padding: 4px; cursor: grab; + width: 100%; &:active { cursor: grabbing; diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 5e46dd4..fe77adb 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -1,12 +1,12 @@ // abstracts @use 'abstracts/variables'; @use 'abstracts/mixins'; -@use 'abstracts/placeholders'; @use 'abstracts/functions'; // base @use 'base/reset'; @use 'base/typography'; +@use 'base/global'; @use 'base/base'; // components diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 0d4f98d..1efa2d4 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -776,13 +776,13 @@ border-radius: 6px; overflow: hidden; padding: 4px; - min-width: 150px; .option { padding: 4px 10px; border-radius: #{$border-radius-small}; color: var(--text-color); + text-wrap: nowrap; cursor: pointer; &:hover { @@ -794,8 +794,8 @@ color: #f65648; &:hover { - background-color: #f65648; - color: white; + background-color: #f657484d; + color: #f65648; } } } diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index f6b0a0c..3293699 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -25,7 +25,7 @@ interface ConveyorPointSchema { action: { actionUuid: string; actionName: string; - actionType: "default" | "spawn" | "swap" | "despawn"; + actionType: "default" | "spawn" | "swap" | "delay" | "despawn"; material: string; delay: number | "inherit"; spawnInterval: number | "inherit"; @@ -42,7 +42,6 @@ interface VehiclePointSchema { actionUuid: string; actionName: string; actionType: "travel"; - material: string | null; unLoadDuration: number; loadCapacity: number; pickUpPoint: { x: number; y: number, z: number } | null; @@ -119,12 +118,14 @@ interface StorageEventSchema extends AssetEventSchema { point: StoragePointSchema; } +type PointsScheme = ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema; + type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema; type productsSchema = { productName: string; productId: string; - eventsData: EventsSchema[]; + eventDatas: EventsSchema[]; }[] @@ -133,6 +134,7 @@ interface ConveyorStatus extends ConveyorEventSchema { isActive: boolean; idleTime: number; activeTime: number; + } interface MachineStatus extends MachineEventSchema { @@ -168,4 +170,17 @@ interface StorageUnitStatus extends StorageEventSchema { idleTime: number; activeTime: number; currentLoad: number; -} \ No newline at end of file +} + +interface MaterialSchema { + materialId: string; + materialName: string; + materialType: string; + isActive: boolean; + startTime?: string; + endTime?: string; + cost?: number; + weight?: number; +} + +type MaterialsSchema = MaterialSchema[]; \ No newline at end of file