diff --git a/app/.env b/app/.env index 03273ec..c50d174 100644 --- a/app/.env +++ b/app/.env @@ -2,23 +2,10 @@ PORT=8200 # Base URL for the server socket API, used for real-time communication (e.g., WebSockets). -<<<<<<< HEAD -# REACT_APP_SERVER_SOCKET_API_BASE_URL=192.168.0.110:8000 REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:8000 # Base URL for the server REST API, used for HTTP requests to the backend server. -# REACT_APP_SERVER_REST_API_BASE_URL=192.168.0.110:5000 -# REACT_APP_SERVER_REST_API_BASE_URL=192.168.0.102:5000 REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:5000 -======= -REACT_APP_SERVER_SOCKET_API_BASE_URL=192.168.0.102:8000 -# REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:8000 - -# Base URL for the server REST API, used for HTTP requests to the backend server. -# REACT_APP_SERVER_REST_API_BASE_URL=192.168.0.110:5000 -REACT_APP_SERVER_REST_API_BASE_URL=192.168.0.102:5000 -# REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:5000 ->>>>>>> b76213f49f2d37e58096a02208d134c8b861d1f2 # Base URL for the server marketplace, used for market place model blob. REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011 diff --git a/app/src/app.tsx b/app/src/app.tsx index 2158dad..b545413 100644 --- a/app/src/app.tsx +++ b/app/src/app.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Dashboard from "./pages/Dashboard"; import Project from "./pages/Project"; @@ -7,17 +7,6 @@ import "./styles/main.scss"; import { LoggerProvider } from "./components/ui/log/LoggerContext"; const App: React.FC = () => { - useEffect(() => { - const handlePopState = () => { - window.location.reload(); - }; - - window.addEventListener("popstate", handlePopState); - - return () => { - window.removeEventListener("popstate", handlePopState); - }; - }, []); return ( diff --git a/app/src/components/layout/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx similarity index 89% rename from app/src/components/layout/Dashboard/DashboardCard.tsx rename to app/src/components/Dashboard/DashboardCard.tsx index 7adc59f..75378b1 100644 --- a/app/src/components/layout/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -1,11 +1,11 @@ import React, { useState, useRef } from "react"; -import { KebabIcon } from "../../icons/ExportCommonIcons"; -import img from "../../../assets/image/image.png"; +import img from "../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; -import { useProjectName } from "../../../store/builder/store"; -import { viewProject } from "../../../services/dashboard/viewProject"; import { getUserData } from "./functions/getUserData"; -import OuterClick from "../../../utils/outerClick"; +import { useProjectName } from "../../store/builder/store"; +import { viewProject } from "../../services/dashboard/viewProject"; +import OuterClick from "../../utils/outerClick"; +import { KebabIcon } from "../icons/ExportCommonIcons"; interface DashBoardCardProps { projectName: string; @@ -42,6 +42,7 @@ const DashboardCard: React.FC = ({ }; const handleOptionClick = async (option: string) => { + console.log('option: ', option); switch (option) { case "delete": if (handleDeleteProject) { @@ -112,7 +113,7 @@ const DashboardCard: React.FC = ({ {isKebabOpen && (
- {["rename", "delete", "duplicate", "open in new tab"].map((option) => ( + {["rename", "restore", "delete", "duplicate", "open in new tab"].map((option) => (
)} diff --git a/app/src/components/footer/shortcutHelper.tsx b/app/src/components/footer/shortcutHelper.tsx index 8a15b2e..405963e 100644 --- a/app/src/components/footer/shortcutHelper.tsx +++ b/app/src/components/footer/shortcutHelper.tsx @@ -29,8 +29,8 @@ import { DublicateIcon, DuplicateInstanceIcon, PlayIcon, - BrowserIcon, } from "../icons/ShortcutIcons"; +import { CloseIcon, EyeCloseIcon } from "../icons/ExportCommonIcons"; interface ShortcutItem { keys: string[]; @@ -44,8 +44,15 @@ interface ShortcutGroup { items: ShortcutItem[]; } -const ShortcutHelper = () => { +interface ShortcutHelperProps { + setShowShortcuts: (value: boolean) => void; +} + +const ShortcutHelper: React.FC = ({ + setShowShortcuts, +}) => { const shortcuts: ShortcutGroup[] = [ + // Essential { category: "Essential", items: [ @@ -80,13 +87,20 @@ const ShortcutHelper = () => { icon: , }, { - keys: ["CTRL", "+", "?"], + keys: ["CTRL", "+", "SHIFT", "+", "?"], name: "Info", description: "Show Shortcut Info", icon: , }, + { + keys: ["L"], + name: "Log", + description: "Show Log list", + icon: , + }, ], }, + // Tools { category: "Tools", items: [ @@ -152,6 +166,7 @@ const ShortcutHelper = () => { }, ], }, + // View & Navigation { category: "View & Navigation", items: [ @@ -187,6 +202,7 @@ const ShortcutHelper = () => { }, ], }, + // Module Switching { category: "Module Switching", items: [ @@ -216,6 +232,7 @@ const ShortcutHelper = () => { }, ], }, + // Selection { category: "Selection", items: [ @@ -245,6 +262,7 @@ const ShortcutHelper = () => { }, ], }, + // Simulation { category: "Simulation", items: [ @@ -254,16 +272,11 @@ const ShortcutHelper = () => { description: "Play Simulation", icon: , }, - ], - }, - { - category: "Miscellaneous", - items: [ { - keys: ["F5", "F11", "F12", "CTRL", "+", "R"], - name: "Browser Defaults", - description: "Reserved for browser defaults", - icon: , + keys: ["H"], + name: "Hide Player", + description: "Hide Simulation Player", + icon: , }, ], }, @@ -273,14 +286,25 @@ const ShortcutHelper = () => { React.useState("Essential"); const activeShortcuts = - shortcuts.find((group) => group.category === activeCategory)?.items || []; + shortcuts.find((group) => group.category === activeCategory)?.items ?? []; return (
+
{shortcuts.map((group) => ( -
diff --git a/app/src/components/layout/controls/ControlsPlayer.tsx b/app/src/components/layout/controls/ControlsPlayer.tsx new file mode 100644 index 0000000..484cd35 --- /dev/null +++ b/app/src/components/layout/controls/ControlsPlayer.tsx @@ -0,0 +1,89 @@ +import React, { useEffect } from "react"; +import useCameraModeStore, { + usePlayButtonStore, +} from "../../../store/usePlayButtonStore"; +import useModuleStore from "../../../store/useModuleStore"; +import { PlayIcon } from "../../icons/ShortcutIcons"; +import InputToggle from "../../ui/inputs/InputToggle"; +import { EyeCloseIcon, WalkIcon } from "../../icons/ExportCommonIcons"; +import { ExitIcon } from "../../icons/SimulationIcons"; +import { useCamMode } from "../../../store/builder/store"; +import { usePlayerStore } from "../../../store/useUIToggleStore"; + +const ControlsPlayer: React.FC = () => { + const { setIsPlaying } = usePlayButtonStore(); + const { activeModule } = useModuleStore(); + const { walkMode, toggleWalkMode } = useCameraModeStore(); + const { hidePlayer, setHidePlayer } = usePlayerStore(); + const { camMode } = useCamMode(); + + const changeCamMode = () => { + toggleWalkMode(); + echo.log("switch camera mode to first person"); + // Simulate "/" keypress + const slashKeyEvent = new KeyboardEvent("keydown", { + key: "/", + code: "Slash", + keyCode: 191, // for compatibility + which: 191, + bubbles: true, + cancelable: true, + }); + document.dispatchEvent(slashKeyEvent); + }; + useEffect(() => { + if (camMode === "ThirdPerson") { + toggleWalkMode(); + } else return; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [camMode]); + + return ( +
+ {!hidePlayer && ( +
+ +
Running {activeModule}...
+
+ )} + +
+ {!hidePlayer && activeModule === "builder" && ( +
+ + +
+ )} + + +
+
+ ); +}; + +export default ControlsPlayer; diff --git a/app/src/components/layout/sidebarLeft/Header.tsx b/app/src/components/layout/sidebarLeft/Header.tsx index f26f4fa..c2c1bff 100644 --- a/app/src/components/layout/sidebarLeft/Header.tsx +++ b/app/src/components/layout/sidebarLeft/Header.tsx @@ -2,7 +2,7 @@ import React from "react"; import { ToggleSidebarIcon } from "../../icons/HeaderIcons"; import { LogoIcon } from "../../icons/Logo"; import FileMenu from "../../ui/FileMenu"; -import useToggleStore from "../../../store/useUIToggleStore"; +import {useToggleStore} from "../../../store/useUIToggleStore"; import useModuleStore from "../../../store/useModuleStore"; const Header: React.FC = () => { @@ -20,6 +20,7 @@ const Header: React.FC = () => {
- -
-
- {multipleAction && selectedPointData && - selectedPointData.actions.map((action: any) => ( -
- - {selectedPointData.actions.length > 1 && ( - - )} -
- ))} - {!multipleAction && selectedPointData && ( -
- -
- )} -
- {multipleAction && ( - - )} -
- - + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + const event = renameAction( + selectedProduct.productId, + selectedAction.actionId, + newName ); + setSelectedAction(selectedAction.actionId, newName); + if (event) { + upsertProductOrEventApi({ + productName: selectedProduct.productName, + productId: selectedProduct.productId, + organization: organization, + eventDatas: event, + }); + } + }; + + const handleActionSelect = (actionUuid: string, actionName: string) => { + setSelectedAction(actionUuid, actionName); + }; + + return ( +
+
+
+
Actions
+ + +
+
+
+ {multipleAction && + selectedPointData?.actions?.map((action: any) => ( +
+ + {selectedPointData?.actions?.length > 1 && ( + + )} +
+ ))} + {!multipleAction && selectedPointData?.action && ( +
+ +
+ )} +
+ {multipleAction && ( + + )} +
+
+
+ ); }; 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 index 027dc0b..18c01bc 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -11,7 +11,7 @@ import Trigger from "../trigger/Trigger"; import { useSelectedAction, useSelectedEventData, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import ActionsList from "../components/ActionsList"; -import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; function ConveyorMechanics() { const [activeOption, setActiveOption] = useState<"default" | "spawn" | "swap" | "delay" | "despawn">("default"); diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx index 8437612..8247d54 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -6,7 +6,7 @@ import { useSelectedAction, useSelectedEventData, useSelectedProduct } from "../ import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import ProcessAction from "../actions/ProcessAction"; import ActionsList from "../components/ActionsList"; -import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; function MachineMechanics() { const [activeOption, setActiveOption] = useState<"default" | "process">("default"); diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx index 2295161..a5eecac 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -12,7 +12,7 @@ import { import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import PickAndPlaceAction from "../actions/PickAndPlaceAction"; import ActionsList from "../components/ActionsList"; -import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; function RoboticArmMechanics() { const [activeOption, setActiveOption] = useState<"default" | "pickAndPlace">( diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx index 564a53f..202daa7 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -4,7 +4,7 @@ import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import StorageAction from "../actions/StorageAction"; import ActionsList from "../components/ActionsList"; -import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import { useStorageUnitStore } from "../../../../../../store/simulation/useStorageUnitStore"; import { useSelectedAction, useSelectedEventData, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index baa7541..2894e33 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -12,7 +12,7 @@ import { import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import TravelAction from "../actions/TravelAction"; import ActionsList from "../components/ActionsList"; -import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useVehicleStore } from "../../../../../../store/simulation/useVehicleStore"; function 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 da36384..fb0b97a 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -1,395 +1,512 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { - AddIcon, - RemoveIcon, - ResizeHeightIcon, + AddIcon, + RemoveIcon, + ResizeHeightIcon, } from "../../../../../icons/ExportCommonIcons"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../../../functions/handleResizePannel"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; -import { useSelectedAction, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; -import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; +import { + useSelectedAction, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; type TriggerProps = { - selectedPointData?: PointsScheme | undefined; - type?: 'Conveyor' | 'Vehicle' | 'RoboticArm' | 'Machine' | 'StorageUnit'; + selectedPointData?: PointsScheme | undefined; + type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit"; }; const Trigger = ({ selectedPointData, type }: TriggerProps) => { - const [currentAction, setCurrentAction] = useState(); - const { selectedProduct } = useSelectedProduct(); - const { getActionByUuid, getEventByModelUuid, getPointByUuid, getTriggerByUuid, addTrigger, removeTrigger, updateTrigger, renameTrigger, getProductById } = useProductStore(); - const [triggers, setTriggers] = useState([]); - const [selectedTrigger, setSelectedTrigger] = useState(); - const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete"); - const triggersContainerRef = useRef(null); - const { selectedAction } = useSelectedAction(); + const [currentAction, setCurrentAction] = useState(); + const { selectedProduct } = useSelectedProduct(); + const { + getActionByUuid, + getEventByModelUuid, + getPointByUuid, + getTriggerByUuid, + addTrigger, + removeTrigger, + updateTrigger, + renameTrigger, + getProductById, + } = useProductStore(); + const [triggers, setTriggers] = useState([]); + const [selectedTrigger, setSelectedTrigger] = useState< + TriggerSchema | undefined + >(); + const [activeOption, setActiveOption] = useState< + "onComplete" | "onStart" | "onStop" | "delay" | "onError" + >("onComplete"); + const triggersContainerRef = useRef(null); + const { selectedAction } = useSelectedAction(); - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; - useEffect(() => { - if (!selectedPointData || !selectedProduct) return; + useEffect(() => { + if (!selectedPointData || !selectedProduct) return; - let actionUuid: string | undefined; + let actionUuid: string | undefined; - if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') { - actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid; - } else if (type === 'RoboticArm' && selectedAction && selectedAction.actionId) { - actionUuid = selectedAction.actionId; - } - - setCurrentAction(actionUuid); - }, [selectedPointData, selectedProduct, type, selectedAction]); - - const updateBackend = ( - productName: string, - productId: string, - organization: string, - eventData: EventsSchema - ) => { - upsertProductOrEventApi({ - productName: productName, - productId: productId, - organization: organization, - eventDatas: eventData - }) + if ( + type === "Conveyor" || + type === "Vehicle" || + type === "Machine" || + type === "StorageUnit" + ) { + actionUuid = ( + selectedPointData as + | ConveyorPointSchema + | VehiclePointSchema + | MachinePointSchema + | StoragePointSchema + ).action?.actionUuid; + } else if ( + type === "RoboticArm" && + selectedAction && + selectedAction.actionId + ) { + actionUuid = selectedAction.actionId; } - useEffect(() => { - if (!currentAction || !selectedProduct) return; - const action = getActionByUuid(selectedProduct.productId, currentAction); - const actionTriggers = action?.triggers || []; - setTriggers(actionTriggers); - setSelectedTrigger(actionTriggers[0]); - }, [currentAction, selectedProduct]); + setCurrentAction(actionUuid); + }, [selectedPointData, selectedProduct, type, selectedAction]); - const triggeredModel = useMemo(() => { - if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredModel?.modelUuid) return undefined; - return getEventByModelUuid(selectedProduct.productId, selectedTrigger.triggeredAsset.triggeredModel.modelUuid); - }, [selectedProduct, selectedTrigger]); + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData, + }); + }; - const triggeredPoint = useMemo(() => { - if (!selectedProduct || !triggeredModel || !selectedTrigger?.triggeredAsset?.triggeredPoint?.pointUuid) return undefined; - return getPointByUuid( - selectedProduct.productId, - triggeredModel.modelUuid, - selectedTrigger.triggeredAsset.triggeredPoint.pointUuid - ); - }, [selectedProduct, triggeredModel, selectedTrigger]); + useEffect(() => { + if (!currentAction || !selectedProduct) return; + const action = getActionByUuid(selectedProduct.productId, currentAction); + const actionTriggers = action?.triggers || []; + setTriggers(actionTriggers); + setSelectedTrigger(actionTriggers[0]); + }, [currentAction, selectedProduct]); - const triggeredAction = useMemo(() => { - if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredAction?.actionUuid) return undefined; - return getActionByUuid( - selectedProduct.productId, - selectedTrigger.triggeredAsset.triggeredAction.actionUuid - ); - }, [selectedProduct, selectedTrigger]); - - const modelOptions = getProductById(selectedProduct.productId)?.eventDatas || []; - - const pointOptions: PointsScheme[] = useMemo(() => { - if (!triggeredModel) return []; - - const model = modelOptions.find(m => m.modelUuid === triggeredModel.modelUuid); - if (!model) return []; - - if ('points' in model) { - return (model as ConveyorEventSchema).points; - } else if ('point' in model) { - return [(model as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point]; - } - return []; - }, [triggeredModel, modelOptions]); - - const actionOptions: any = useMemo(() => { - if (!triggeredPoint) return []; - const point = pointOptions.find((p) => p.uuid === triggeredPoint.uuid); - if (!point) return []; - - if ('action' in point) { - const typedPoint = point as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema; - return typedPoint.action ? [typedPoint.action] : []; - } else if ('actions' in point) { - const typedPoint = point as RoboticArmPointSchema; - return typedPoint.actions; - } - return []; - }, [triggeredPoint, pointOptions]); - - const handleModelSelect = (option: string, triggerUuid: string) => { - if (!selectedProduct) return; - - const selectedModel = modelOptions.find(m => m.modelName === option); - if (!selectedModel) return; - - const event = updateTrigger(selectedProduct.productId, triggerUuid, { - triggeredAsset: { - triggeredModel: { - modelName: selectedModel.modelName, - modelUuid: selectedModel.modelUuid - }, - triggeredPoint: null, - triggeredAction: null - } - }); - - if (event) { - const updatedTrigger = getTriggerByUuid(selectedProduct.productId, triggerUuid); - setSelectedTrigger(updatedTrigger); - - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; - - const handlePointSelect = (option: string, triggerUuid: string) => { - if (!selectedProduct || !selectedTrigger) return; - - const pointUuid = pointOptions.find(p => `Point ${p.uuid.slice(0, 4)}` === option)?.uuid; - if (!pointUuid) return; - - if (selectedTrigger.triggeredAsset?.triggeredModel) { - const event = updateTrigger(selectedProduct.productId, triggerUuid, { - triggeredAsset: { - ...selectedTrigger.triggeredAsset, - triggeredPoint: { - pointName: option, - pointUuid: pointUuid - }, - triggeredAction: null - } - }); - - if (event) { - const updatedTrigger = getTriggerByUuid(selectedProduct.productId, triggerUuid); - setSelectedTrigger(updatedTrigger); - - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - } - }; - - - const handleActionSelect = (option: string, triggerUuid: string) => { - if (!selectedProduct || !selectedTrigger) return; - - const selectedAction = actionOptions.find((a: any) => a.actionName === option); - - if (!selectedAction) return; - - if (selectedTrigger.triggeredAsset?.triggeredPoint) { - const event = updateTrigger(selectedProduct.productId, triggerUuid, { - triggeredAsset: { - ...selectedTrigger.triggeredAsset, - triggeredAction: { - actionName: option, - actionUuid: selectedAction.actionUuid - } - } - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - } - }; - - const handleAddTrigger = () => { - if (!selectedProduct || !currentAction) return; - - const newTrigger: TriggerSchema = { - triggerUuid: THREE.MathUtils.generateUUID(), - triggerName: `New Trigger ${triggers.length + 1}`, - triggerType: activeOption, - delay: 0, - triggeredAsset: null - }; - - const event = addTrigger(selectedProduct.productId, currentAction, newTrigger); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - - const updatedAction = getActionByUuid(selectedProduct.productId, currentAction); - const updatedTriggers = updatedAction?.triggers || []; - - setTriggers(updatedTriggers); - setSelectedTrigger(newTrigger); - }; - - const handleRemoveTrigger = (triggerUuid: string) => { - if (!selectedProduct || !currentAction) return; - - const event = removeTrigger(selectedProduct.productId, triggerUuid); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - - const index = triggers.findIndex(t => t.triggerUuid === triggerUuid); - const newTriggers = triggers.filter(t => t.triggerUuid !== triggerUuid); - setTriggers(newTriggers); - - if (selectedTrigger?.triggerUuid === triggerUuid) { - const nextTrigger = newTriggers[index] || newTriggers[index - 1]; - setSelectedTrigger(nextTrigger); - } - }; - - const handleTriggerRename = (triggerUuid: string, newName: string) => { - if (!selectedProduct) return; - const event = renameTrigger(selectedProduct.productId, triggerUuid, newName); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; - - const handleTriggerTypeChange = (option: string) => { - if (!selectedTrigger || !selectedProduct) return; - - const validTypes: Array = ["onComplete", "onStart", "onStop", "delay", "onError"]; - if (!validTypes.includes(option as TriggerSchema['triggerType'])) return; - - setActiveOption(option as TriggerSchema['triggerType']); - const event = updateTrigger(selectedProduct.productId, selectedTrigger.triggerUuid, { - triggerType: option as TriggerSchema['triggerType'] - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; - - return ( -
-
-
Trigger
- -
-
-
-
- {triggers.map((trigger) => ( -
setSelectedTrigger(trigger)} - > - - {triggers.length > 1 && ( - - )} -
- ))} -
- -
- - {selectedTrigger && ( -
-
{selectedTrigger.triggerName}
- - -
- (option.modelName))]} - onSelect={(option) => { handleModelSelect(option, selectedTrigger.triggerUuid) }} - /> - (`Point ${option.uuid.slice(0, 4)}`))]} - onSelect={(option) => { handlePointSelect(option, selectedTrigger.triggerUuid) }} - /> - (option.actionName))]} - onSelect={(option) => { handleActionSelect(option, selectedTrigger.triggerUuid) }} - /> -
-
- )} -
-
+ const triggeredModel = useMemo(() => { + if ( + !selectedProduct || + !selectedTrigger?.triggeredAsset?.triggeredModel?.modelUuid + ) + return undefined; + return getEventByModelUuid( + selectedProduct.productId, + selectedTrigger.triggeredAsset.triggeredModel.modelUuid ); + }, [selectedProduct, selectedTrigger]); + + const triggeredPoint = useMemo(() => { + if ( + !selectedProduct || + !triggeredModel || + !selectedTrigger?.triggeredAsset?.triggeredPoint?.pointUuid + ) + return undefined; + return getPointByUuid( + selectedProduct.productId, + triggeredModel.modelUuid, + selectedTrigger.triggeredAsset.triggeredPoint.pointUuid + ); + }, [selectedProduct, triggeredModel, selectedTrigger]); + + const triggeredAction = useMemo(() => { + if ( + !selectedProduct || + !selectedTrigger?.triggeredAsset?.triggeredAction?.actionUuid + ) + return undefined; + return getActionByUuid( + selectedProduct.productId, + selectedTrigger.triggeredAsset.triggeredAction.actionUuid + ); + }, [selectedProduct, selectedTrigger]); + + const modelOptions = + getProductById(selectedProduct.productId)?.eventDatas || []; + + const pointOptions: PointsScheme[] = useMemo(() => { + if (!triggeredModel) return []; + + const model = modelOptions.find( + (m) => m.modelUuid === triggeredModel.modelUuid + ); + if (!model) return []; + + if ("points" in model) { + return (model as ConveyorEventSchema).points; + } else if ("point" in model) { + return [ + ( + model as + | VehicleEventSchema + | RoboticArmEventSchema + | MachineEventSchema + | StorageEventSchema + ).point, + ]; + } + return []; + }, [triggeredModel, modelOptions]); + + const actionOptions: any = useMemo(() => { + if (!triggeredPoint) return []; + const point = pointOptions.find((p) => p.uuid === triggeredPoint.uuid); + if (!point) return []; + + if ("action" in point) { + const typedPoint = point as + | ConveyorPointSchema + | VehiclePointSchema + | MachinePointSchema + | StoragePointSchema; + return typedPoint.action ? [typedPoint.action] : []; + } else if ("actions" in point) { + const typedPoint = point as RoboticArmPointSchema; + return typedPoint.actions; + } + return []; + }, [triggeredPoint, pointOptions]); + + const handleModelSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct) return; + + const selectedModel = modelOptions.find((m) => m.modelName === option); + if (!selectedModel) return; + + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + triggeredModel: { + modelName: selectedModel.modelName, + modelUuid: selectedModel.modelUuid, + }, + triggeredPoint: null, + triggeredAction: null, + }, + }); + + if (event) { + const updatedTrigger = getTriggerByUuid( + selectedProduct.productId, + triggerUuid + ); + setSelectedTrigger(updatedTrigger); + + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + const handlePointSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct || !selectedTrigger) return; + + const pointUuid = pointOptions.find( + (p) => `Point ${p.uuid.slice(0, 4)}` === option + )?.uuid; + if (!pointUuid) return; + + if (selectedTrigger.triggeredAsset?.triggeredModel) { + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + ...selectedTrigger.triggeredAsset, + triggeredPoint: { + pointName: option, + pointUuid: pointUuid, + }, + triggeredAction: null, + }, + }); + + if (event) { + const updatedTrigger = getTriggerByUuid( + selectedProduct.productId, + triggerUuid + ); + setSelectedTrigger(updatedTrigger); + + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; + + const handleActionSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct || !selectedTrigger) return; + + const selectedAction = actionOptions.find( + (a: any) => a.actionName === option + ); + + if (!selectedAction) return; + + if (selectedTrigger.triggeredAsset?.triggeredPoint) { + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + ...selectedTrigger.triggeredAsset, + triggeredAction: { + actionName: option, + actionUuid: selectedAction.actionUuid, + }, + }, + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; + + const handleAddTrigger = () => { + if (!selectedProduct || !currentAction) return; + + const newTrigger: TriggerSchema = { + triggerUuid: THREE.MathUtils.generateUUID(), + triggerName: `New Trigger ${triggers.length + 1}`, + triggerType: activeOption, + delay: 0, + triggeredAsset: null, + }; + + const event = addTrigger( + selectedProduct.productId, + currentAction, + newTrigger + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + + const updatedAction = getActionByUuid( + selectedProduct.productId, + currentAction + ); + const updatedTriggers = updatedAction?.triggers || []; + + setTriggers(updatedTriggers); + setSelectedTrigger(newTrigger); + }; + + const handleRemoveTrigger = (triggerUuid: string) => { + if (!selectedProduct || !currentAction) return; + + const event = removeTrigger(selectedProduct.productId, triggerUuid); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + + const index = triggers.findIndex((t) => t.triggerUuid === triggerUuid); + const newTriggers = triggers.filter((t) => t.triggerUuid !== triggerUuid); + setTriggers(newTriggers); + + if (selectedTrigger?.triggerUuid === triggerUuid) { + const nextTrigger = newTriggers[index] || newTriggers[index - 1]; + setSelectedTrigger(nextTrigger); + } + }; + + const handleTriggerRename = (triggerUuid: string, newName: string) => { + if (!selectedProduct) return; + const event = renameTrigger( + selectedProduct.productId, + triggerUuid, + newName + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + const handleTriggerTypeChange = (option: string) => { + if (!selectedTrigger || !selectedProduct) return; + + const validTypes: Array = [ + "onComplete", + "onStart", + "onStop", + "delay", + "onError", + ]; + if (!validTypes.includes(option as TriggerSchema["triggerType"])) return; + + setActiveOption(option as TriggerSchema["triggerType"]); + const event = updateTrigger( + selectedProduct.productId, + selectedTrigger.triggerUuid, + { + triggerType: option as TriggerSchema["triggerType"], + } + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + return ( +
+
+
Trigger
+ +
+
+
+
+ {triggers.map((trigger) => ( +
setSelectedTrigger(trigger)} + > + + {triggers.length > 1 && ( + + )} +
+ ))} +
+ +
+ + {selectedTrigger && ( +
+
{selectedTrigger.triggerName}
+ + +
+ option.modelName)]} + onSelect={(option) => { + handleModelSelect(option, selectedTrigger.triggerUuid); + }} + /> + `Point ${option.uuid.slice(0, 4)}` + ), + ]} + onSelect={(option) => { + handlePointSelect(option, selectedTrigger.triggerUuid); + }} + /> + option.actionName), + ]} + onSelect={(option) => { + handleActionSelect(option, selectedTrigger.triggerUuid); + }} + /> +
+
+ )} +
+
+ ); }; export default Trigger; diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 6609e92..316269c 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -17,11 +17,18 @@ import RenderOverlay from "../../../templates/Overlay"; import EditWidgetOption from "../../../ui/menu/EditWidgetOption"; import { handleAddEventToProduct } from "../../../../modules/simulation/events/points/functions/handleAddEventToProduct"; import { useEventsStore } from "../../../../store/simulation/useEventsStore"; -import { deleteEventDataApi } from "../../../../services/simulation/deleteEventDataApi"; -import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; -import { deleteProductApi } from "../../../../services/simulation/deleteProductApi"; -import { renameProductApi } from "../../../../services/simulation/renameProductApi"; +import { deleteEventDataApi } from "../../../../services/simulation/products/deleteEventDataApi"; +import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; +import { deleteProductApi } from "../../../../services/simulation/products/deleteProductApi"; +import { renameProductApi } from "../../../../services/simulation/products/renameProductApi"; import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences"; +import ComparePopUp from "../../../ui/compareVersion/Compare"; +import { + useCompareStore, + useSaveVersion, +} from "../../../../store/builder/store"; +import CompareLayOut from "../../../ui/compareVersion/CompareLayOut"; +import {useToggleStore} from "../../../../store/useUIToggleStore"; interface Event { modelName: string; @@ -49,7 +56,7 @@ const Simulations: React.FC = () => { renameProduct, addEvent, removeEvent, - getProductById + getProductById, } = useProductStore(); const { selectedProduct, setSelectedProduct } = useSelectedProduct(); const { getEventByModelUuid } = useEventsStore(); @@ -58,6 +65,16 @@ const Simulations: React.FC = () => { const organization = email!.split("@")[1].split(".")[0]; const [openObjects, setOpenObjects] = useState(true); const [processes, setProcesses] = useState(); + const { setToggleUI } = useToggleStore(); + + const { comparePopUp, setComparePopUp } = useCompareStore(); + const { isVersionSaved, setIsVersionSaved } = useSaveVersion(); + + const handleSaveVersion = () => { + setIsVersionSaved(true); + setComparePopUp(false); + setToggleUI(false, false); + }; const handleAddProduct = () => { const id = generateUUID(); @@ -122,21 +139,21 @@ const Simulations: React.FC = () => { const selectedProductData = getProductById(selectedProduct.productId); if (selectedProductData) { - determineExecutionMachineSequences([selectedProductData]) - .then((sequences) => { + determineExecutionMachineSequences([selectedProductData]).then( + (sequences) => { sequences.forEach((sequence) => { const events: Event[] = sequence.map((event) => ({ modelName: event.modelName, - modelId: event.modelUuid + modelId: event.modelUuid, })) || []; processes.push(events); - }) - setProcesses(processes) - }) - + }); + setProcesses(processes); + } + ); } - }, [selectedProduct.productId, products]) + }, [selectedProduct.productId, products]); return (
@@ -145,7 +162,11 @@ const Simulations: React.FC = () => {
Products
-
@@ -158,10 +179,11 @@ const Simulations: React.FC = () => { {products.map((product, index) => (
{/* eslint-disable-next-line */}
{
{products.length > 1 && (
{openObjects && - processes?.map((process, index) => + processes?.map((process, index) => (
{process.map((event, index) => ( ))}
- ) - } + ))}
@@ -233,9 +256,9 @@ const Simulations: React.FC = () => { Click 'Compare' to review and analyze the layout differences between them.
-
+
+
@@ -258,6 +281,12 @@ const Simulations: React.FC = () => { /> )} + + {comparePopUp && ( + + + + )} ); }; diff --git a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx index 73a630c..cc11c57 100644 --- a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx +++ b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx @@ -7,25 +7,22 @@ import { LocationIcon, } from "../../../icons/ExportCommonIcons"; import RenameInput from "../../../ui/inputs/RenameInput"; +import { useVersionStore } from "../../../../store/builder/store"; +import { generateUniqueId } from "../../../../functions/generateUniqueId"; const VersionHistory = () => { const userName = localStorage.getItem("userName") ?? "Anonymous"; - - const initialVersions = [ - { - versionName: "v1.0", - timestamp: "April 09, 2025", - savedBy: userName, - }, - ]; - - const [versions, setVersions] = useState(initialVersions); - const [selectedVersion, setSelectedVersion] = useState(initialVersions[0]); + const { versions, addVersion, setVersions, updateVersion } = + useVersionStore(); + const [selectedVersion, setSelectedVersion] = useState( + versions.length > 0 ? versions[0] : null + ); const addNewVersion = () => { - const newVersionNumber = versions.length + 1; const newVersion = { - versionName: `v${newVersionNumber}.0`, + id: generateUniqueId(), + versionLabel: `v${versions.length + 1}.0`, + versionName: "", timestamp: new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", @@ -34,21 +31,24 @@ const VersionHistory = () => { savedBy: userName, }; - const updated = [newVersion, ...versions]; - setVersions(updated); + const newVersions = [newVersion, ...versions]; + addVersion(newVersion); setSelectedVersion(newVersion); + setVersions(newVersions); }; const handleSelectVersion = (version: any) => { setSelectedVersion(version); - const reordered = [version, ...versions.filter((v) => v !== version)]; + const reordered = [version, ...versions.filter((v) => v.id !== version.id)]; setVersions(reordered); }; - const handleTimestampChange = (newTimestamp: string, index: number) => { - const updatedVersions = [...versions]; - updatedVersions[index].timestamp = newTimestamp; - setVersions(updatedVersions); + const handleVersionNameChange = (newName: string, versionId: string) => { + const updated = versions.map((v) => + v.id === versionId ? { ...v, versionName: newName } : v + ); + setVersions(updated); + updateVersion(versionId, { versionName: newName }); }; return ( @@ -57,13 +57,17 @@ const VersionHistory = () => {
Version History
- -
+
-
+
@@ -78,48 +82,63 @@ const VersionHistory = () => {
{/* Current Version Display */} -
-
- -
-
-
- Current Version ({selectedVersion.versionName}) + {selectedVersion && ( +
+
+
-
- {versions.length} Saved History +
+
+ Current Version ({selectedVersion.versionLabel}) +
+
+ {versions.length} Saved History +
-
+ )} {/* Versions List */}
- {versions.map((version, index) => ( -
- - ))} + + )) + )}
); diff --git a/app/src/components/layout/sidebarRight/versionHisory/VersionSaved.tsx b/app/src/components/layout/sidebarRight/versionHisory/VersionSaved.tsx new file mode 100644 index 0000000..07f62d2 --- /dev/null +++ b/app/src/components/layout/sidebarRight/versionHisory/VersionSaved.tsx @@ -0,0 +1,207 @@ +import React, { useState, useEffect, useRef } from "react"; +import { useVersionStore } from "../../../../store/builder/store"; +import { + CloseIcon, + FinishEditIcon, + RenameVersionIcon, + SaveIcon, + SaveVersionIcon, +} from "../../../icons/ExportCommonIcons"; +import RenderOverlay from "../../../templates/Overlay"; + +const VersionSaved = () => { + const { versions, updateVersion } = useVersionStore(); + const [isEditing, setIsEditing] = useState(false); + const [shouldDismiss, setShouldDismiss] = useState(false); + const [showNotification, setShowNotification] = useState(false); + const [newName, setNewName] = useState(""); + const [description, setDescription] = useState(""); + const [showEditedFinish, setShowEditedFinish] = useState(false); + const [editedVersionName, setEditedVersionName] = useState(""); + const prevVersionCount = useRef(versions.length); + const dismissTimerRef = useRef(null); + + const latestVersion = versions?.[0]; + + useEffect(() => { + return () => { + if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current); + }; + }, []); + + useEffect(() => { + if (versions.length > prevVersionCount.current && latestVersion) { + setShowNotification(true); + setShouldDismiss(false); + setIsEditing(false); + setNewName(latestVersion.versionName ?? ""); + setDescription(latestVersion.description ?? ""); + setEditedVersionName(latestVersion.versionName ?? ""); // Initialize editedVersionName + + if (!isEditing) { + startDismissTimer(); + } + + prevVersionCount.current = versions.length; + } else if (versions.length < prevVersionCount.current) { + prevVersionCount.current = versions.length; + } + }, [versions, isEditing, latestVersion]); + + const startDismissTimer = (delay = 5000) => { + if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current); + dismissTimerRef.current = setTimeout(() => { + setShouldDismiss(true); + }, delay); + }; + + useEffect(() => { + if (shouldDismiss) { + const timer = setTimeout(() => setShowNotification(false), 200); + return () => clearTimeout(timer); + } + }, [shouldDismiss]); + + const handleEditName = () => { + if (!latestVersion) return; + + setIsEditing(true); + setNewName(latestVersion.versionName ?? ""); + setDescription(latestVersion.description ?? ""); + if (dismissTimerRef.current) { + clearTimeout(dismissTimerRef.current); + dismissTimerRef.current = null; + } + }; + + const handleFinishEdit = () => { + if (!latestVersion) return; + + const updatedName = + (newName.trim() || latestVersion.versionName) ?? latestVersion.timestamp; + updateVersion(latestVersion.id, { + versionName: updatedName, + description, + }); + + setEditedVersionName(updatedName); + setIsEditing(false); + setShowEditedFinish(true); + + setTimeout(() => { + setShowEditedFinish(false); + }, 5000); + + startDismissTimer(); + }; + + const handleCancel = () => { + setIsEditing(false); + startDismissTimer(); + }; + + const handleClose = () => { + setShouldDismiss(true); + if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current); + }; + + if (!showNotification || !latestVersion) return null; + + return ( +
+ {!isEditing && !showEditedFinish && ( +
+
+
+
+ +
+ Saved New Version +
+ +
+ +
+ +
+
+ New Version Created {latestVersion.versionLabel}{" "} + {latestVersion.timestamp.toUpperCase()} +
+ +
+
+
+ )} + + {isEditing && ( + +
+
+
+ +
Rename Version
+
+
+
+ setNewName(e.target.value)} + placeholder="Enter new version name" + /> +
+ by @{latestVersion.savedBy}{" "} + {new Date(latestVersion.timestamp).toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "2-digit", + hour: "numeric", + minute: "2-digit", + })} +
+
+
+