diff --git a/app/package-lock.json b/app/package-lock.json index b5a2eda..a1bf231 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -38,6 +38,7 @@ "mqtt": "^5.10.4", "postprocessing": "^6.36.4", "prompt-sync": "^4.2.0", + "r3f-perf": "^7.2.3", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", "react-dom": "^18.3.1", @@ -3371,6 +3372,15 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", @@ -7073,6 +7083,23 @@ "react": ">= 16.8.0" } }, + "node_modules/@utsubo/events": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@utsubo/events/-/events-0.1.7.tgz", + "integrity": "sha512-WB/GEj/0h27Bz8rJ0+CBtNz5mLT79ne1OjB7PUM4n0qLBqEDwm6yBzZC3j6tasHjlBPJDYZiBVIA1glaMlgZ5g==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.7" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -17894,6 +17921,64 @@ "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", "license": "ISC" }, + "node_modules/r3f-perf": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/r3f-perf/-/r3f-perf-7.2.3.tgz", + "integrity": "sha512-4+P/N/bnO9D8nzdm3suL/NjPZK/HHdjwpvajhi8j7eB41i2ECN6lX9RXiKSpHzpsDi2ui1tBj6q7/sz5opoqXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-icons": "^1.3.0", + "@react-three/drei": "^9.103.0", + "@stitches/react": "^1.2.8", + "@utsubo/events": "^0.1.7", + "zustand": "~4.5.2" + }, + "peerDependencies": { + "@react-three/fiber": ">=8.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "@react-three/fiber": { + "optional": true + }, + "dom": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/r3f-perf/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", diff --git a/app/package.json b/app/package.json index 16c4ac0..aec1d38 100644 --- a/app/package.json +++ b/app/package.json @@ -33,6 +33,7 @@ "mqtt": "^5.10.4", "postprocessing": "^6.36.4", "prompt-sync": "^4.2.0", + "r3f-perf": "^7.2.3", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", "react-dom": "^18.3.1", diff --git a/app/src/components/footer/shortcutHelper.tsx b/app/src/components/footer/shortcutHelper.tsx index 405963e..afbaa0e 100644 --- a/app/src/components/footer/shortcutHelper.tsx +++ b/app/src/components/footer/shortcutHelper.tsx @@ -30,7 +30,11 @@ import { DuplicateInstanceIcon, PlayIcon, } from "../icons/ShortcutIcons"; -import { CloseIcon, EyeCloseIcon } from "../icons/ExportCommonIcons"; +import { + CloseIcon, + EyeCloseIcon, + PerformanceStatsIcon, +} from "../icons/ExportCommonIcons"; interface ShortcutItem { keys: string[]; @@ -76,8 +80,8 @@ const ShortcutHelper: React.FC = ({ }, { keys: ["CTRL", "+", "H"], - name: "Help", - description: "Open Help", + name: "Version History", + description: "Open Version History", icon: , }, { @@ -278,6 +282,12 @@ const ShortcutHelper: React.FC = ({ description: "Hide Simulation Player", icon: , }, + { + keys: ["F1"], + name: "Stats", + description: "Show Scene Stats", + icon: , + }, ], }, ]; diff --git a/app/src/components/icons/ContextMenuIcons.tsx b/app/src/components/icons/ContextMenuIcons.tsx index 3519c70..a48adf2 100644 --- a/app/src/components/icons/ContextMenuIcons.tsx +++ b/app/src/components/icons/ContextMenuIcons.tsx @@ -164,7 +164,7 @@ export function RenameIcon() { export function FocusIcon() { return ( - + ); @@ -239,7 +239,7 @@ export function PasteIcon() { export function ModifiersIcon() { return ( - + ); @@ -249,7 +249,7 @@ export function DeleteIcon() { return ( - + @@ -264,7 +264,7 @@ export function MoveIcon() { return ( - + diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index e7aa8a2..00f7f22 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -710,6 +710,27 @@ export const ExpandIcon = ({ isActive }: { isActive: boolean }) => { ); }; +export const ExpandIcon2 = () => { + return ( + + + + + ); +}; + export const StartIcon = () => { return ( { ); }; - export const SuccessIcon = () => { return ( - - - + + + - - ) -} - + ); +}; export const AlertIcon = () => { return ( - - - + + + - - ) -} + ); +}; export const NavigationIcon = () => { return ( - - - - + + + + - - ) -} + ); +}; export const HangTagIcon = () => { return ( - + - + @@ -1379,140 +1457,363 @@ export const HangTagIcon = () => { - - ) -} + ); +}; export const DecalInfoIcon = () => { return ( - - - - + + + + - - ) -} - + ); +}; export const LayeringBottomIcon = () => { return ( - - - - + + + + - ) -} + ); +}; export const LayeringTopIcon = () => { return ( - - - - + + + + - - ) -} + ); +}; export const ValueUpdateIcon = () => { return ( - - - + + + - - - ) -} - + ); +}; export const ListTaskIcon = () => { return ( - - + + - ) -} + ); +}; export const LocationPinIcon = () => { return ( - - - + + + - ) -} + ); +}; export const ClockThreeIcon = () => { return ( - - + + - - ) -} + ); +}; export const SlectedTickIcon = () => { return ( - - - + + + - ) -} + ); +}; export const HourGlassIcon = () => { return ( - - + + - ) -} + ); +}; export const TargetIcon = () => { return ( - - + + - ) -} + ); +}; export const ForkLiftIcon = () => { return ( - + - - - - - + + + + + - - - - - + + + + + - + - - + + - + - - ) -} + ); +}; export const RightHalfFillCircleIcon = () => { return ( - - + + - ) -} \ No newline at end of file + ); +}; + +export const PerformanceStatsIcon = () => { + return ( + + + + ); +}; diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index f09af98..f5e0c72 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -75,13 +75,13 @@ function MainScene() { clearComparisonProduct(); setIsVersionSaved(false); } - }, [activeModule]) + }, [activeModule, clearComparisonProduct, setIsVersionSaved]) useEffect(() => { if (versionHistory.length > 0) { setSelectedVersion(versionHistory[0]) } - }, [versionHistory]) + }, [setSelectedVersion, versionHistory]) const handleSelectVersion = (option: string) => { const version = versionHistory.find((version) => version.versionName === option); @@ -140,7 +140,7 @@ function MainScene() { {!selectedUser && ( <> - {/* {loadingProgress > 0 && } */} + {loadingProgress > 0 && } {!isPlaying && ( <> {toggleThreeD && !isVersionSaved && } @@ -155,7 +155,7 @@ function MainScene() { )} {(isPlaying) && activeModule === "simulation" && - loadingProgress == 0 && } + loadingProgress === 0 && } {(isPlaying) && activeModule !== "simulation" && } @@ -188,7 +188,7 @@ function MainScene() { } onDragOver={(event) => event.preventDefault()} > - {/* */} + {selectedProduct && selectedVersion && isVersionSaved && !isPlaying && activeModule === "simulation" && ( diff --git a/app/src/components/layout/sidebarLeft/Outline.tsx b/app/src/components/layout/sidebarLeft/Outline.tsx index c23e77c..ad47614 100644 --- a/app/src/components/layout/sidebarLeft/Outline.tsx +++ b/app/src/components/layout/sidebarLeft/Outline.tsx @@ -4,54 +4,92 @@ import DropDownList from "../../ui/list/DropDownList"; import { useSceneContext } from "../../../modules/scene/sceneContext"; import { isPointInsidePolygon } from "../../../functions/isPointInsidePolygon"; +interface AssetData { + id: string; + name: string; + position?: []; + rotation?: {}; +} + interface ZoneData { id: string; name: string; - assets: { id: string; name: string; position?: []; rotation?: {} }[]; + assets: AssetData[]; } const Outline: React.FC = () => { const [searchValue, setSearchValue] = useState(""); - const [zoneDataList, setZoneDataList] = useState([]); - const [buildingsList, setBuildingsList] = useState<{ id: string; name: string }[]>([]); - const [isLayersOpen, setIsLayersOpen] = useState(true); - const [isBuildingsOpen, setIsBuildingsOpen] = useState(false); - const [isZonesOpen, setIsZonesOpen] = useState(false); + const [sceneAssetsDataList, setSceneAssetsDataList] = useState< + ZoneData[] | AssetData[] + >([]); + // const [buildingsList, setBuildingsList] = useState<{ id: string; name: string }[]>([]); + // const [isLayersOpen, setIsLayersOpen] = useState(true); + // const [isBuildingsOpen, setIsBuildingsOpen] = useState(false); + const [isZonesOpen, setIsZonesOpen] = useState(true); const { assetStore, zoneStore } = useSceneContext(); const { assets } = assetStore(); const { zones } = zoneStore(); - useEffect(() => { - const updatedZoneList: ZoneData[] = zones?.map((zone: any) => { - const polygon2D = zone.points.map((p: any) => [p.position[0], p.position[2]]); - const assetsInZone = assets.filter((item: any) => { - const [x, , z] = item.position; - return isPointInsidePolygon([x, z], polygon2D as [number, number][]); - }) - .map((item: any) => ({ - id: item.modelUuid, - name: item.modelName, - position: item.position, - rotation: item.rotation, - })); + const assignedAssets = new Set(); - - return { - id: zone.zoneUuid, - name: zone.zoneName, - assets: assetsInZone, - }; - }); + const updatedZoneList: ZoneData[] = + zones?.map((zone: any) => { + const polygon2D = zone.points.map((p: any) => [ + p.position[0], + p.position[2], + ]); + const assetsInZone = assets + .filter((item: any) => { + const [x, , z] = item.position; + const inside = isPointInsidePolygon( + [x, z], + polygon2D as [number, number][] + ); + if (inside) assignedAssets.add(item.modelUuid); + return inside; + }) + .map((item: any) => ({ + id: item.modelUuid, + name: item.modelName, + position: item.position, + rotation: item.rotation, + })); - setZoneDataList(updatedZoneList); + return { + id: zone.zoneUuid, + name: zone.zoneName, + assets: assetsInZone, + }; + }) ?? []; + + // Collect unassigned assets + const unassignedAssets = assets + .filter((item: any) => !assignedAssets.has(item.modelUuid)) + .map((item: any) => ({ + id: item.modelUuid, + name: item.modelName, + position: item.position, + rotation: item.rotation, + })); + + // Add as a separate "zone" + if (unassignedAssets.length > 0) { + updatedZoneList.push({ + id: "unassigned-zone", + name: "Unassigned", + assets: unassignedAssets, + }); + } + + setSceneAssetsDataList(updatedZoneList); }, [zones, assets]); const handleSearchChange = (value: string) => { setSearchValue(value); }; - const dropdownItems = [{ id: "1", name: "Ground Floor" }]; + // const dropdownItems = [{ id: "1", name: "Ground Floor" }]; return (
@@ -63,7 +101,7 @@ const Outline: React.FC = () => {
) : (
-
+ {/*
{ showFocusIcon={true} remove /> -
+
*/}
- setIsBuildingsOpen((prev) => !prev)} showKebabMenu={false} showAddIcon={false} - /> + /> */} setIsZonesOpen((prev) => !prev)} showKebabMenu={false} diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 6607937..7d50298 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -4,130 +4,134 @@ import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import { RemoveIcon } from "../../../icons/ExportCommonIcons"; import PositionInput from "../customInput/PositionInputs"; import RotationInput from "../customInput/RotationInput"; -import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store"; +import { + useSelectedFloorItem, + useObjectPosition, + useObjectRotation, +} from "../../../../store/builder/store"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; interface UserData { - id: number; - label: string; - value: string; + id: number; + label: string; + value: string; } const AssetProperties: React.FC = () => { - const [userData, setUserData] = useState([]); - const { selectedFloorItem } = useSelectedFloorItem(); - const { objectPosition } = useObjectPosition(); - const { objectRotation } = useObjectRotation(); - const { assetStore } = useSceneContext(); - const { assets, setCurrentAnimation } = assetStore(); - const { loopAnimation } = useBuilderStore(); - const [hoveredIndex, setHoveredIndex] = useState(null); + const [userData, setUserData] = useState([]); + const { selectedFloorItem } = useSelectedFloorItem(); + const { objectPosition } = useObjectPosition(); + const { objectRotation } = useObjectRotation(); + const { assetStore } = useSceneContext(); + const { assets, setCurrentAnimation } = assetStore(); + const { loopAnimation } = useBuilderStore(); + const [hoveredIndex, setHoveredIndex] = useState(null); - const handleAddUserData = () => { - }; + const handleAddUserData = () => { + setUserData([]); + }; - const handleUserDataChange = (id: number, newValue: string) => { - }; + const handleUserDataChange = (id: number, newValue: string) => {}; - const handleRemoveUserData = (id: number) => { - }; + const handleRemoveUserData = (id: number) => {}; - const handleAnimationClick = (animation: string) => { - if (selectedFloorItem) { - setCurrentAnimation(selectedFloorItem.uuid, animation, true, loopAnimation, true); - } + const handleAnimationClick = (animation: string) => { + if (selectedFloorItem) { + setCurrentAnimation( + selectedFloorItem.uuid, + animation, + true, + loopAnimation, + true + ); } + }; - if (!selectedFloorItem) return null; + if (!selectedFloorItem) return null; - return ( -
- {/* Name */} -
{selectedFloorItem.userData.modelName}
-
- {objectPosition && - { }} - value1={parseFloat(objectPosition.x.toFixed(5))} - value2={parseFloat(objectPosition.z.toFixed(5))} - /> - } - {objectRotation && - { }} - value={parseFloat(objectRotation.y.toFixed(5))} - /> - } -
+ return ( +
+ {/* Name */} +
{selectedFloorItem.userData.modelName}
+
+ {objectPosition && ( + {}} + value1={parseFloat(objectPosition.x.toFixed(5))} + value2={parseFloat(objectPosition.z.toFixed(5))} + /> + )} + {objectRotation && ( + {}} + value={parseFloat(objectRotation.y.toFixed(5))} + /> + )} +
-
-
Render settings
- - -
+
Render settings
+
+ + +
-
-
User Data
- {userData.map((data) => ( -
- handleUserDataChange(data.id, newValue)} - /> -
handleRemoveUserData(data.id)} - > - -
-
- ))} - - {/* Add new user data */} -
- + Add -
-
-
- {selectedFloorItem.uuid &&
Animations
} - {assets.map((asset) => ( -
- {asset.modelUuid === selectedFloorItem.uuid && - asset.animations && - asset.animations.length > 0 && - asset.animations.map((animation, index) => ( -
-
handleAnimationClick(animation)} - onMouseEnter={() => setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(null)} - style={{ - height: "20px", - width: "100%", - borderRadius: "5px", - background: - hoveredIndex === index - ? "#7b4cd3" - : "transparent", - }} - > - {animation.charAt(0).toUpperCase() + - animation.slice(1).toLowerCase()} -
-
- ))} -
- ))} +
+
User Data
+ {userData.map((data) => ( +
+ handleUserDataChange(data.id, newValue)} + /> +
handleRemoveUserData(data.id)} + > +
+
+ ))} + + {/* Add new user data */} +
+ + Add
- ); +
+
Animations
+
+ {assets.map((asset) => ( + <> + {asset.modelUuid === selectedFloorItem.uuid && + asset.animations && + asset.animations.length > 0 && + asset.animations.map((animation, index) => ( +
+
handleAnimationClick(animation)} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + className="animations-list" + style={{ + background: + hoveredIndex === index + ? "#7b4cd3" + : "var(--background-color)", + }} + > + {animation.charAt(0).toUpperCase() + + animation.slice(1).toLowerCase()} +
+
+ ))} + + ))} +
+
+ ); }; export default AssetProperties; 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 4d280d2..4046d89 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx @@ -3,53 +3,51 @@ import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; interface StorageActionProps { - type: "store" | "spawn" | "default"; - value: string; + maxCapacity: string; + spawnedCount: string; min: number; max?: number; - defaultValue: string; + maxCapacityDefault: string; + spawnedCountCefault: string; currentMaterialType: string; handleCapacityChange: (value: string) => void; + handleSpawnCountChange: (value: string) => void; handleMaterialTypeChange: (value: string) => void; } -const StorageAction: React.FC = ({ type, value, min, max, defaultValue, currentMaterialType, handleCapacityChange, handleMaterialTypeChange }) => { +const StorageAction: React.FC = ({ maxCapacity, spawnedCount, min, max, maxCapacityDefault, spawnedCountCefault, currentMaterialType, handleCapacityChange, handleSpawnCountChange, handleMaterialTypeChange }) => { return ( <> - {type === 'store' && - { }} - onChange={handleCapacityChange} - /> - } - {type === 'spawn' && - <> - { }} - onChange={handleCapacityChange} - /> - - - } + { }} + onChange={handleCapacityChange} + /> + { }} + onChange={handleSpawnCountChange} + /> + ); }; 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 990d046..20ce08a 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -125,26 +125,6 @@ function ConveyorMechanics() { } }; - const handleRenameAction = (newName: string) => { - if (!selectedPointData) return; - - setActionName(newName); - const event = updateAction( - selectedProduct.productUuid, - selectedPointData.action.actionUuid, - { actionName: newName } - ); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - } - }; - const handleSpawnCountChange = (value: string) => { if (!selectedPointData) return; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx index f5d1229..3df0401 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx @@ -17,7 +17,7 @@ function CraneMechanics() { const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); - const { getPointByUuid, updateAction, addAction, removeAction } = productStore(); + const { getPointByUuid, addAction, removeAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); @@ -62,36 +62,6 @@ function CraneMechanics() { }); }; - const handleRenameAction = (newName: string) => { - if (!selectedAction.actionId || !selectedPointData) return; - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - { actionName: newName } - ); - - const updatedActions = selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId - ? { ...action, actionName: newName } - : action - ); - - setSelectedPointData({ - ...selectedPointData, - actions: updatedActions, - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - } - }; - const handleAddAction = () => { if (!selectedEventData || !selectedPointData) return; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 4b29f8c..22bdbbf 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -47,6 +47,7 @@ function HumanMechanics() { if (point?.actions?.length) { setSelectedPointData(point); const firstAction = point.actions[0]; + setSelectedAction(firstAction.actionUuid, firstAction.actionName); setCurrentAction(firstAction); setSpeed(( getEventByModelUuid( @@ -142,7 +143,6 @@ function HumanMechanics() { if (isNaN(numericValue)) return; const updatedEvent = { - ...selectedEventData.data, speed: numericValue } as HumanEventSchema; 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 0c7c6e3..dea6ef5 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -29,7 +29,7 @@ function RoboticArmMechanics() { const { projectId } = useParams(); useEffect(() => { - if (selectedEventData) { + if (selectedEventData && selectedEventData.data.type === 'roboticArm') { const point = getPointByUuid( selectedProduct.productUuid, selectedEventData.data.modelUuid, @@ -71,36 +71,6 @@ function RoboticArmMechanics() { }); }; - const handleRenameAction = (newName: string) => { - if (!selectedAction.actionId || !selectedPointData) return; - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - { actionName: newName } - ); - - const updatedActions = selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId - ? { ...action, actionName: newName } - : action - ); - - setSelectedPointData({ - ...selectedPointData, - actions: updatedActions, - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - } - }; - const handleSpeedChange = (value: string) => { if (!selectedEventData) return; 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 796dbc5..bf264fb 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -1,4 +1,5 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; +import { MathUtils } from "three"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; @@ -6,58 +7,78 @@ import StorageAction from "../actions/StorageAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; -import * as THREE from 'three'; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useParams } from "react-router-dom"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; function StorageMechanics() { - const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default"); + const [activeOption, setActiveOption] = useState<"store" | "spawn">("store"); + const [currentCapacity, setCurrentCapacity] = useState("1"); + const [spawnedCount, setSpawnedCount] = useState("0"); + const [spawnedMaterial, setSpawnedMaterial] = useState("Default material"); const [selectedPointData, setSelectedPointData] = useState(); const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); - const { getPointByUuid, updateAction } = productStore(); + const { getPointByUuid, updateAction, updateEvent, getEventByModelUuid, getActionByUuid, addAction, removeAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); - const updateSelectedPointData = () => { - if (selectedEventData && selectedProduct) { - const point = getPointByUuid( - selectedProduct.productUuid, - selectedEventData?.data.modelUuid, - selectedEventData?.selectedPoint - ) as StoragePointSchema | undefined; - if (point && "action" in point) { - setSelectedPointData(point); - const uiOption = point.action.actionType === "retrieve" ? "spawn" : point.action.actionType; - setActiveOption(uiOption as "store" | "spawn"); - setSelectedAction(point.action.actionUuid, point.action.actionName); - } - } - }; - useEffect(() => { - if (selectedEventData) { + if (selectedEventData && selectedEventData.data.type === "storageUnit") { const point = getPointByUuid( selectedProduct.productUuid, - selectedEventData?.data.modelUuid, - selectedEventData?.selectedPoint + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint ) as StoragePointSchema | undefined; - if (point && "action" in point) { + + if (point?.actions?.length) { setSelectedPointData(point); - const uiOption = point.action.actionType === "retrieve" ? "spawn" : point.action.actionType; - setActiveOption(uiOption as "store" | "spawn"); - setSelectedAction(point.action.actionUuid, point.action.actionName); + const firstAction = point.actions[0]; + + const eventData = getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid + ) as StorageEventSchema | undefined; + + setCurrentCapacity(eventData?.storageCapacity?.toString() || "1"); + setSpawnedCount(eventData?.storageCount?.toString() || "0"); + setSpawnedMaterial(eventData?.materialType?.toString() || "Default material"); + setSelectedAction(firstAction.actionUuid, firstAction.actionName); + setActiveOption(firstAction.actionType === "retrieve" ? "spawn" : "store"); } } else { clearSelectedAction(); } - }, [selectedProduct, selectedEventData]); + }, [selectedEventData, selectedProduct]); + + useEffect(() => { + if (selectedEventData && selectedEventData.data.type === "storageUnit" && selectedAction.actionId) { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as StoragePointSchema | undefined; + + const actionUuid = selectedAction.actionId || point?.actions[0].actionUuid || ''; + + const newCurrentAction = getActionByUuid(selectedProduct.productUuid, actionUuid); + + if (newCurrentAction && (newCurrentAction.actionType === 'store' || newCurrentAction.actionType === 'retrieve')) { + if (!selectedAction.actionId) { + setSelectedAction(newCurrentAction.actionUuid, newCurrentAction.actionName); + } + const uiOption = newCurrentAction.actionType === "retrieve" ? "spawn" : "store"; + setActiveOption(uiOption); + } else { + clearSelectedAction(); + } + } + }, [selectedAction, selectedProduct, selectedEventData]); const updateBackend = ( productName: string, @@ -75,48 +96,63 @@ function StorageMechanics() { } const handleActionTypeChange = (option: string) => { - if (!selectedEventData || !selectedPointData) return; - const internalOption = actionTypeMap[option as keyof typeof actionTypeMap] as "store" | "retrieve"; + if (!selectedAction.actionId || !selectedPointData) return; + const internalOption = option === "spawn" ? "retrieve" : "store"; + + const updatedAction = { + actionType: internalOption as "store" | "retrieve" + }; + + const updatedActions = selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? { + ...action, + actionType: updatedAction.actionType + } : action + ); + + const updatedPoint = { ...selectedPointData, actions: updatedActions }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + + setSelectedPointData(updatedPoint); setActiveOption(option as "store" | "spawn"); - - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - actionType: internalOption, - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - updateSelectedPointData(); - } - }; - - const handleRenameAction = (newName: string) => { - if (!selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { actionName: newName }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - updateSelectedPointData(); - } }; const handleCapacityChange = (value: string) => { - if (!selectedEventData || !selectedPointData) return; - const newCapacity = parseInt(value); + if (!selectedEventData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - storageCapacity: newCapacity, - }); + const numericValue = parseInt(value); + if (isNaN(numericValue)) return; + + const updatedEvent = { + storageCapacity: numericValue + } as StorageEventSchema; + + const currentCount = parseInt(spawnedCount); + if (currentCount > numericValue) { + updatedEvent.storageCount = numericValue; + setSpawnedCount(numericValue.toString()); + } + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + updatedEvent + ); if (event) { updateBackend( @@ -125,25 +161,54 @@ function StorageMechanics() { projectId || '', event ); - updateSelectedPointData(); } + + setCurrentCapacity(value); }; - const createNewMaterial = (materialType: string): { materialType: string; materialId: string } | null => { - if (!selectedEventData || !selectedPointData) return null; - const materialId = THREE.MathUtils.generateUUID(); - return { - materialType, - materialId - }; + const handleSpawnCountChange = (value: string) => { + if (!selectedEventData) return; + + const numericValue = parseInt(value); + if (isNaN(numericValue)) return; + + const maxCapacity = parseInt(currentCapacity); + if (numericValue > maxCapacity) return; + + const updatedEvent = { + storageCount: numericValue + } as StorageEventSchema; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + updatedEvent + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + + setSpawnedCount(value); }; const handleMaterialTypeChange = (value: string) => { - if (!selectedEventData || !selectedPointData) return; + if (!selectedEventData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - materialType: value, - }); + const updatedEvent = { + materialType: value + } as StorageEventSchema; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + updatedEvent + ); if (event) { updateBackend( @@ -152,70 +217,114 @@ function StorageMechanics() { projectId || '', event ); - updateSelectedPointData(); } + + setSpawnedMaterial(value); }; - const currentActionName = useMemo(() => - selectedPointData ? selectedPointData.action.actionName : "Action Name", - [selectedPointData] - ); + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; - const currentCapacity = useMemo(() => - selectedPointData ? selectedPointData.action.storageCapacity.toString() : "0", - [selectedPointData] - ); + const newAction: StorageAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "store", + triggers: [], + }; - const currentMaterialType = useMemo(() => - selectedPointData?.action.materialType || "Default material", - [selectedPointData] - ); + const updatedActions = [...(selectedPointData.actions || []), newAction]; + const updatedPoint = { ...selectedPointData, actions: updatedActions }; - const availableActions = { - defaultOption: "store", - options: ["store", "spawn"], + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); }; - const actionTypeMap = { - spawn: "retrieve", - store: "store" + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData || !actionUuid) return; + + const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== actionUuid); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; + + const event = removeAction( + selectedProduct.productUuid, + actionUuid + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setSelectedPointData(updatedPoint); + + const index = selectedPointData.actions.findIndex((a) => a.actionUuid === selectedAction.actionId); + const nextAction = updatedPoint.actions[index] || updatedPoint.actions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + } else { + clearSelectedAction(); + } }; return ( <> - {selectedEventData && ( -
- -
-
- -
-
- - -
-
-
- -
-
+ {selectedEventData && selectedEventData.data.type === "storageUnit" && ( + <> +
+ +
+
+ + + {selectedAction.actionId && ( +
+
+ +
+
+ +
+
+ +
+
+ )} +
+ )} ); 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 fada871..181252b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -119,26 +119,6 @@ function VehicleMechanics() { } }; - const handleRenameAction = (newName: string) => { - if (!selectedPointData) return; - - setActionName(newName); - const event = updateAction( - selectedProduct.productUuid, - selectedPointData.action.actionUuid, - { actionName: newName } - ); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - } - }; - const handleLoadCapacityChange = (value: string) => { if (!selectedPointData) return; 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 0697c7b..23c060c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -13,7 +13,7 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; type TriggerProps = { selectedPointData?: PointsScheme | undefined; - type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit" | "Human"; + type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit" | "Human" | "Crane"; }; const Trigger = ({ selectedPointData, type }: TriggerProps) => { @@ -36,9 +36,9 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { 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" || type === "Human") && selectedAction.actionId) { + if (type === "Conveyor" || type === "Vehicle" || type === "Machine") { + actionUuid = (selectedPointData as | ConveyorPointSchema | VehiclePointSchema | MachinePointSchema).action?.actionUuid; + } else if ((type === "RoboticArm" || type === "Human" || type === "StorageUnit" || type === 'Crane') && selectedAction.actionId) { actionUuid = selectedAction.actionId; } diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx index cfcd693..2aeacc2 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react' +import { useState } from 'react' import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/ExportCommonIcons' -import NavigateCatagory from '../NavigateCatagory' +// import NavigateCatagory from '../NavigateCatagory' const Hrm = () => { const [selectedCard, setSelectedCard] = useState(0); @@ -105,6 +105,7 @@ const Hrm = () => {
setSelectedCard(index)} + key={index} >
diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx index d3df99d..18f5cb0 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react' -import NavigateCatagory from '../../NavigateCatagory' +import { useState } from 'react' +// import NavigateCatagory from '../../NavigateCatagory' import { EyeIcon, ForkLiftIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons'; import assetImage from "../../../../../../assets/image/asset-image.png" const AssetManagement = () => { - const [selectedCategory, setSelectedCategory] = useState("All Assets"); + // const [selectedCategory, setSelectedCategory] = useState("All Assets"); const [expandedAssetId, setExpandedAssetId] = useState(null); const dummyAssets = [ @@ -56,7 +56,7 @@ const AssetManagement = () => {
{dummyAssets.map((asset, index) => ( -
+
diff --git a/app/src/components/templates/CreateNewWindow.tsx b/app/src/components/templates/CreateNewWindow.tsx new file mode 100644 index 0000000..91085ab --- /dev/null +++ b/app/src/components/templates/CreateNewWindow.tsx @@ -0,0 +1,183 @@ +import React, { ReactNode, useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; + +type NewWindowProps = { + children: ReactNode; + title?: string; + width?: number; + height?: number; + left?: number; + top?: number; + center?: boolean; + features?: Partial<{ + toolbar: boolean; + menubar: boolean; + scrollbars: boolean; + resizable: boolean; + location: boolean; + status: boolean; + }>; + onClose?: () => void; + copyStyles?: boolean; + noopener?: boolean; + className?: string; + theme?: string | null; +}; + +export const RenderInNewWindow: React.FC = ({ + children, + title = "New Window", + width = 900, + height = 700, + left, + top, + center = true, + features, + onClose, + copyStyles = true, + noopener = true, + className, + theme = "light", +}) => { + const [mounted, setMounted] = useState(false); + const childWindowRef = useRef(null); + const containerElRef = useRef(null); + + useEffect(() => { + if (typeof window === "undefined") return; + + const screenLeft = window.screenLeft ?? window.screenX ?? 0; + const screenTop = window.screenTop ?? window.screenY ?? 0; + const availWidth = window.outerWidth ?? window.innerWidth; + const availHeight = window.outerHeight ?? window.innerHeight; + + const finalLeft = + center && availWidth + ? Math.max(0, screenLeft + (availWidth - width) / 2) + : left ?? 100; + + const finalTop = + center && availHeight + ? Math.max(0, screenTop + (availHeight - height) / 2) + : top ?? 100; + + const baseFeatures = [ + `width=${Math.floor(width)}`, + `height=${Math.floor(height)}`, + `left=${Math.floor(finalLeft)}`, + `top=${Math.floor(finalTop)}`, + ]; + + const featureFlags = features ?? { + toolbar: false, + menubar: false, + scrollbars: true, + resizable: true, + location: false, + status: false, + }; + + Object.entries(featureFlags).forEach(([k, v]) => + baseFeatures.push(`${k}=${v ? "yes" : "no"}`) + ); + + const newWin = window.open("", "_blank", baseFeatures.join(",")); + if (!newWin) { + console.warn("Popup blocked or failed to open window."); + onClose?.(); + return; + } + + if (noopener) { + try { + newWin.opener = null; + } catch {} + } + + newWin.document.open(); + newWin.document.write(` + + + + ${title} + + + +`); + newWin.document.close(); + + if (copyStyles) { + const head = newWin.document.head; + Array.from(document.styleSheets).forEach((styleSheet) => { + try { + if ((styleSheet as CSSStyleSheet).cssRules) { + const newStyleEl = newWin.document.createElement("style"); + const rules = Array.from( + (styleSheet as CSSStyleSheet).cssRules + ).map((r) => r.cssText); + newStyleEl.appendChild( + newWin.document.createTextNode(rules.join("\n")) + ); + head.appendChild(newStyleEl); + } + } catch { + const ownerNode = styleSheet.ownerNode as HTMLElement | null; + if (ownerNode && ownerNode.tagName === "LINK") { + const link = ownerNode as HTMLLinkElement; + const newLink = newWin.document.createElement("link"); + newLink.rel = link.rel; + newLink.href = link.href; + newLink.media = link.media; + head.appendChild(newLink); + } + } + }); + } + + const container = newWin.document.createElement("div"); + if (className) container.className = className; + newWin.document.body.appendChild(container); + + newWin.document.title = title; + + // Handle child window close + const handleChildUnload = () => { + onClose?.(); + }; + newWin.addEventListener("beforeunload", handleChildUnload); + + // 👇 Handle parent refresh/close → auto close child + const handleParentUnload = () => { + try { + newWin.close(); + } catch {} + }; + window.addEventListener("beforeunload", handleParentUnload); + + childWindowRef.current = newWin; + containerElRef.current = container; + setMounted(true); + + return () => { + newWin.removeEventListener("beforeunload", handleChildUnload); + window.removeEventListener("beforeunload", handleParentUnload); + try { + newWin.close(); + } catch {} + childWindowRef.current = null; + containerElRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const w = childWindowRef.current; + if (w && !w.closed) { + w.document.title = title; + } + }, [title]); + + if (!mounted || !containerElRef.current) return null; + + return createPortal(children, containerElRef.current); +}; diff --git a/app/src/components/ui/inputs/RegularDropDown.tsx b/app/src/components/ui/inputs/RegularDropDown.tsx index f1be25c..b198ede 100644 --- a/app/src/components/ui/inputs/RegularDropDown.tsx +++ b/app/src/components/ui/inputs/RegularDropDown.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from "react"; +import { createPortal } from "react-dom"; interface DropdownProps { header: string; @@ -14,32 +15,35 @@ const RegularDropDown: React.FC = ({ options, onSelect, search = true, - onClick, - onChange, }) => { const [isOpen, setIsOpen] = useState(false); const [selectedOption, setSelectedOption] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); // State to store search term - const [filteredOptions, setFilteredOptions] = useState(options); // State for filtered options - const dropdownRef = useRef(null); // Ref for the dropdown container + const [searchTerm, setSearchTerm] = useState(""); + const [filteredOptions, setFilteredOptions] = useState(options); + const dropdownRef = useRef(null); + const [position, setPosition] = useState<{ top: number; left: number; width: number }>({ + top: 0, + left: 0, + width: 0, + }); - // Reset selectedOption when the dropdown closes + // Reset when closed useEffect(() => { if (!isOpen) { setSelectedOption(null); - setSearchTerm(""); // Clear the search term when the dropdown closes - setFilteredOptions(options); // Reset filtered options when the dropdown closes + setSearchTerm(""); + setFilteredOptions(options); } }, [isOpen, options]); - // Reset selectedOption when the header prop changes + // Reset when header changes useEffect(() => { setSelectedOption(null); - setSearchTerm(""); // Reset search term if header changes - setFilteredOptions(options); // Reset options if header changes + setSearchTerm(""); + setFilteredOptions(options); }, [header, options]); - // Close dropdown if clicked outside + // Close if clicked outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -49,77 +53,88 @@ const RegularDropDown: React.FC = ({ setIsOpen(false); } }; - document.addEventListener("click", handleClickOutside); - - return () => { - document.removeEventListener("click", handleClickOutside); - }; + return () => document.removeEventListener("click", handleClickOutside); }, []); - // Toggle the dropdown - const toggleDropdown = () => { - setIsOpen((prev) => !prev); - }; + // Recalculate position when opening + useEffect(() => { + if (isOpen && dropdownRef.current) { + const rect = dropdownRef.current.getBoundingClientRect(); + setPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX, + width: rect.width, + }); + } + }, [isOpen]); + + const toggleDropdown = () => setIsOpen((prev) => !prev); - // Handle option selection const handleOptionClick = (option: string) => { setSelectedOption(option); onSelect(option); setIsOpen(false); }; - // Handle search input change const handleSearchChange = (event: React.ChangeEvent) => { const term = event.target.value; setSearchTerm(term); - - // Filter options based on the search term - const filtered = options.filter((option) => - option.toLowerCase().includes(term.toLowerCase()) + setFilteredOptions( + options.filter((option) => + option.toLowerCase().includes(term.toLowerCase()) + ) ); - setFilteredOptions(filtered); }; return (
- {/* Dropdown Header */} + {/* Header */}
{selectedOption || header}
- {/* Dropdown Options */} - {isOpen && ( -
- {/* Search Bar */} - {search && ( -
- -
- )} - - {/* Filtered Options */} - {filteredOptions.length > 0 ? ( - filteredOptions.map((option, index) => ( -
handleOptionClick(option)} - > - {option} + {/* Options rendered in portal */} + {isOpen && + createPortal( +
+ {search && ( +
+
- )) - ) : ( -
No options found
- )} -
- )} + )} + {filteredOptions.length > 0 ? ( + filteredOptions.map((option, index) => ( +
handleOptionClick(option)} + title={option} + > + {option} +
+ )) + ) : ( +
No options found
+ )} +
, + document.body + )}
); }; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index 3831e54..6792fa4 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -12,9 +12,7 @@ import { LockIcon, RemoveIcon, } from "../../icons/ExportCommonIcons"; -import { - useZoneAssetId, -} from "../../../store/builder/store"; +import { useZoneAssetId } from "../../../store/builder/store"; import { zoneCameraUpdate } from "../../../services/visulization/zone/zoneCameraUpdation"; import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; import { useParams } from "react-router-dom"; @@ -47,7 +45,9 @@ const List: React.FC = ({ items = [], remove }) => { const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); const { setSubModule } = useSubModuleStore(); - const [expandedZones, setExpandedZones] = useState>({}); + const [expandedZones, setExpandedZones] = useState>( + {} + ); const { projectId } = useParams(); const { assetStore } = useSceneContext(); const { setName } = assetStore(); @@ -55,7 +55,7 @@ const List: React.FC = ({ items = [], remove }) => { const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { zoneStore } = useSceneContext(); - const { zones, setZoneName } = zoneStore() + const { zones, setZoneName } = zoneStore(); useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ @@ -70,6 +70,17 @@ const List: React.FC = ({ items = [], remove }) => { }); }, [activeModule]); + useEffect(() => { + const expanded: Record = { "unassigned-zone": true }; + if (zones.length > 0) { + zones.forEach((zone: any) => { + expanded[zone.zoneUuid] = true; + }); + } + setExpandedZones(expanded); + // eslint-disable-next-line + }, [zones.length]); + const toggleZoneExpansion = (zoneUuid: string) => { setExpandedZones((prev) => ({ ...prev, @@ -79,13 +90,21 @@ const List: React.FC = ({ items = [], remove }) => { async function handleSelectZone(id: string) { try { - if (selectedZone?.zoneUuid === id) { + if (selectedZone?.zoneUuid === id || id === "unassigned-zone") { return; } setSubModule("zoneProperties"); - let response = await getZoneData(id, organization, projectId, selectedVersion?.versionId || ""); + let response = await getZoneData( + id, + organization, + projectId, + selectedVersion?.versionId || "" + ); + + if (!response) return; + setSelectedZone({ zoneName: response?.zoneName, activeSides: response?.activeSides ?? [], @@ -98,7 +117,6 @@ const List: React.FC = ({ items = [], remove }) => { }); } catch (error) { echo.error("Failed to select zone"); - console.log(error); } } @@ -121,17 +139,15 @@ const List: React.FC = ({ items = [], remove }) => { zoneUuid: selectedZone.zoneUuid, zoneName: newName, }; - const response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); + const response = await zoneCameraUpdate( + zonesdata, + organization, + projectId, + selectedVersion?.versionId || "" + ); if (response.message === "zone updated") { setSelectedZone((prev) => ({ ...prev, zoneName: newName })); - setZoneName(selectedZone.zoneUuid, newName) - // setZones((prevZones: any[]) => - // prevZones.map((zone) => - // zone.zoneUuid === selectedZone.zoneUuid - // ? { ...zone, zoneName: newName } - // : zone - // ) - // ); + setZoneName(selectedZone.zoneUuid, newName); } } @@ -140,7 +156,7 @@ const List: React.FC = ({ items = [], remove }) => { let response = await setAssetsApi({ modelUuid: zoneAssetId.id, modelName: newName, - projectId + projectId, }); // console.log("response: ", response); @@ -159,11 +175,15 @@ const List: React.FC = ({ items = [], remove }) => { let drag = false; let isLeftMouseDown = false; - const contextClassNames = ["list-wrapper", "zone-properties-container", "list-container"]; + const contextClassNames = [ + "list-wrapper", + "zone-properties-container", + "list-container", + ]; const isOutsideClick = (target: EventTarget | null) => { if (!(target instanceof HTMLElement)) return true; - return !contextClassNames.some(className => + return !contextClassNames.some((className) => target.closest(`.${className}`) ); }; @@ -189,38 +209,38 @@ const List: React.FC = ({ items = [], remove }) => { if (isOutsideClick(evt.target)) { // Clear selected zone setSelectedZone({ - zoneUuid: '', - zoneName: '', + zoneUuid: "", + zoneName: "", activeSides: [], panelOrder: [], lockedPanels: [], widgets: [], zoneViewPortTarget: [], - zoneViewPortPosition: [] + zoneViewPortPosition: [], }); setZoneAssetId({ - id: '', - name: '', + id: "", + name: "", }); - setSubModule("properties") + setSubModule("properties"); } } }; - if (selectedZone.zoneName! === '' && activeModule === 'Builder') { - document.addEventListener('mousedown', onMouseDown); - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); + if (selectedZone.zoneName! === "" && activeModule === "Builder") { + document.addEventListener("mousedown", onMouseDown); + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); } return () => { - document.removeEventListener('mousedown', onMouseDown); - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); + document.removeEventListener("mousedown", onMouseDown); + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); }; + // eslint-disable-next-line }, [selectedZone, activeModule]); - return ( <> {items?.length > 0 ? ( @@ -234,13 +254,18 @@ const List: React.FC = ({ items = [], remove }) => { toggleZoneExpansion(item.id); }} > -
+
@@ -276,13 +301,15 @@ const List: React.FC = ({ items = [], remove }) => { {item.assets.map((asset) => (
  • handleAssetClick(asset)} >
    - - {remove && ( - )} diff --git a/app/src/components/ui/log/LogList.tsx b/app/src/components/ui/log/LogList.tsx index fc905dc..408764c 100644 --- a/app/src/components/ui/log/LogList.tsx +++ b/app/src/components/ui/log/LogList.tsx @@ -1,9 +1,80 @@ -// LogList.tsx import React, { useEffect, useState } from "react"; -import { LogListIcon, CloseIcon } from "../../icons/ExportCommonIcons"; // Adjust path as needed -import { useLogger } from "./LoggerContext"; +import { + LogListIcon, + CloseIcon, + ExpandIcon2, +} from "../../icons/ExportCommonIcons"; // Adjust path as needed +import { LogEntry, useLogger } from "./LoggerContext"; import { GetLogIcon } from "../../footer/getLogIcons"; +import { RenderInNewWindow } from "../../templates/CreateNewWindow"; +// --- Logs Component --- +type LogsProps = { + selectedTab: "all" | "info" | "warning" | "error"; + setSelectedTab: (tab: "all" | "info" | "warning" | "error") => void; + clear: () => void; + filteredLogs: LogEntry[]; + formatTimestamp: (date: Date) => string; +}; + +const Logs: React.FC = ({ + selectedTab, + setSelectedTab, + clear, + filteredLogs, + formatTimestamp, +}) => { + return ( + <> +
    +
    + {["all", "info", "warning", "error"].map((type) => ( + + ))} +
    + +
    + + {/* Log Entries */} +
    + {filteredLogs.length > 0 ? ( + filteredLogs.map((log) => ( +
    +
    {GetLogIcon(log.type)}
    +
    +
    {log.message}
    +
    + {formatTimestamp(log.timestamp)} +
    +
    +
    + )) + ) : ( +
    + There are no logs to display at the moment. +
    + )} +
    + + ); +}; + +// --- LogList Component --- const LogList: React.FC = () => { const { logs, @@ -15,6 +86,7 @@ const LogList: React.FC = () => { } = useLogger(); const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString(); + const [open, setOpen] = useState(false); const filteredLogs = selectedTab === "all" @@ -25,92 +97,86 @@ const LogList: React.FC = () => { if (isLogListVisible && logs.length > 0) { const lastLog = logs[logs.length - 1]; const validTypes = ["all", "info", "warning", "error"]; - if (validTypes.includes(lastLog.type)) { - setSelectedTab(lastLog.type); + setSelectedTab(lastLog.type as any); } else { setSelectedTab("all"); } } + // eslint-disable-next-line }, [isLogListVisible]); return ( - // eslint-disable-next-line -
    setIsLogListVisible(false)} - > - {/* eslint-disable-next-line */} -
    { - e.stopPropagation(); - }} - > -
    -
    -
    - -
    -
    Log List
    -
    - -
    - - {/* Tabs */} -
    -
    - {["all", "info", "warning", "error"].map((type) => ( - - ))} -
    - -
    - - {/* Log Entries */} -
    - {filteredLogs.length > 0 ? ( - filteredLogs.map((log) => ( -
    -
    {GetLogIcon(log.type)}
    -
    -
    {log.message}
    -
    - {formatTimestamp(log.timestamp)} -
    +
    +
    +
    +
    +
    Log List
    +
    +
    + +
    - )) - ) : ( -
    - There are no logs to display at the moment.
    - )} + + {/* Logs Section */} + +
    -
    -
    + ) : ( + setOpen(false)} + > +
    + +
    +
    + )} + ); }; diff --git a/app/src/components/ui/menu/contextMenu.tsx b/app/src/components/ui/menu/contextMenu.tsx index 9069da8..db98c21 100644 --- a/app/src/components/ui/menu/contextMenu.tsx +++ b/app/src/components/ui/menu/contextMenu.tsx @@ -1,187 +1,216 @@ import React from "react"; -import { ArrayIcon, CopyIcon, DeleteIcon, DublicateIcon, FlipXAxisIcon, FlipZAxisIcon, FocusIcon, GroupIcon, ModifiersIcon, MoveIcon, PasteIcon, RenameIcon, RotateIcon, SubMenuIcon, TransformIcon } from "../../icons/ContextMenuIcons"; +import { + ArrayIcon, + CopyIcon, + DeleteIcon, + DublicateIcon, + FlipXAxisIcon, + FlipZAxisIcon, + FocusIcon, + GroupIcon, + ModifiersIcon, + MoveIcon, + PasteIcon, + RenameIcon, + RotateIcon, + SubMenuIcon, + TransformIcon, +} from "../../icons/ContextMenuIcons"; + +// Union of valid visibility keys +type VisibilityKey = + | "rename" + | "focus" + | "flipX" + | "flipZ" + | "move" + | "rotate" + | "duplicate" + | "copy" + | "paste" + | "modifier" + | "group" + | "array" + | "delete" + | "transform" // virtual key for submenu + | "groupArray"; // virtual key for submenu type ContextMenuProps = { - visibility: { - rename: boolean; - focus: boolean; - flipX: boolean; - flipZ: boolean; - move: boolean; - rotate: boolean; - duplicate: boolean; - copy: boolean; - paste: boolean; - modifier: boolean; - group: boolean; - array: boolean; - delete: boolean; - }; - onRename: () => void; - onFocus: () => void; - onFlipX: () => void; - onFlipZ: () => void; - onMove: () => void; - onRotate: () => void; - onDuplicate: () => void; - onCopy: () => void; - onPaste: () => void; - onGroup: () => void; - onArray: () => void; - onDelete: () => void; + visibility: Record< + Exclude, + boolean + >; + onRename: () => void; + onFocus: () => void; + onFlipX: () => void; + onFlipZ: () => void; + onMove: () => void; + onRotate: () => void; + onDuplicate: () => void; + onCopy: () => void; + onPaste: () => void; + onGroup: () => void; + onArray: () => void; + onDelete: () => void; +}; + +type MenuItem = { + key: VisibilityKey; + label: string; + icon?: React.ReactNode; + shortcut?: string; + onClick?: () => void; + submenuIcon?: React.ReactNode; + children?: MenuItem[]; }; const ContextMenu: React.FC = ({ - visibility, - onRename, - onFocus, - onFlipX, - onFlipZ, - onMove, - onRotate, - onDuplicate, - onCopy, - onPaste, - onGroup, - onArray, - onDelete, + visibility, + onRename, + onFocus, + onFlipX, + onFlipZ, + onMove, + onRotate, + onDuplicate, + onCopy, + onPaste, + onGroup, + onArray, + onDelete, }) => { + const items: MenuItem[] = [ + { + key: "rename", + label: "Rename", + icon: , + shortcut: "F2", + onClick: onRename, + }, + { + key: "focus", + label: "Focus", + icon: , + shortcut: "F", + onClick: onFocus, + }, + { + key: "flipX", + label: "Flip to X axis", + icon: , + onClick: onFlipX, + }, + { + key: "flipZ", + label: "Flip to Z axis", + icon: , + onClick: onFlipZ, + }, + { + key: "transform", + label: "Transform", + icon: , + submenuIcon: , + children: [ + { + key: "move", + label: "Move", + icon: , + shortcut: "G", + onClick: onMove, + }, + { + key: "rotate", + label: "Rotate", + icon: , + shortcut: "R", + onClick: onRotate, + }, + ], + }, + { + key: "duplicate", + label: "Duplicate", + icon: , + shortcut: "Ctrl + D", + onClick: onDuplicate, + }, + { + key: "copy", + label: "Copy Objects", + icon: , + shortcut: "Ctrl + C", + onClick: onCopy, + }, + { + key: "paste", + label: "Paste Objects", + icon: , + shortcut: "Ctrl + V", + onClick: onPaste, + }, + { key: "modifier", label: "Modifiers", icon: }, + { + key: "groupArray", + label: "Group / Array", + children: [ + { + key: "group", + label: "Group", + icon: , + shortcut: "Ctrl + G", + onClick: onGroup, + }, + { key: "array", label: "Array", icon: , onClick: onArray }, + ], + }, + { + key: "delete", + label: "Delete", + icon: , + shortcut: "X", + onClick: onDelete, + }, + ]; + const renderItem = (item: MenuItem): React.ReactNode => { + if (item.children) { + const children = item.children.filter( + (child) => visibility[child.key as keyof typeof visibility] + ); + if (!children.length) return null; + return ( +
    + +
    {children.map(renderItem)}
    +
    + ); + } + + if (!visibility[item.key as keyof typeof visibility]) return null; return ( -
    - {visibility.rename && ( -
    - - F2 -
    - )} - {visibility.focus && ( -
    - - F -
    - )} - {visibility.flipX && ( -
    - -
    - )} - {visibility.flipZ && ( -
    - -
    - )} - {(visibility.move || visibility.rotate) && ( -
    - -
    -
    - {visibility.move && ( -
    - - G -
    - )} - {visibility.rotate && ( -
    - - R -
    - )} -
    -
    - )} - {visibility.duplicate && ( -
    - - Ctrl + D -
    - )} - {visibility.copy && ( -
    - - Ctrl + C -
    - )} - {visibility.paste && ( -
    - - Ctrl + V -
    - )} - {visibility.modifier && ( -
    -
    - -
    - )} - {(visibility.group || visibility.array) && ( -
    - -
    - {visibility.group && ( -
    - - Ctrl + G -
    - )} - {visibility.array && ( -
    - -
    - )} -
    -
    - )} - {visibility.delete && ( -
    - - X -
    - )} -
    +
    + + {item.shortcut && {item.shortcut}} +
    ); + }; + + return
    {items.map(renderItem)}
    ; }; export default ContextMenu; diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 072c9a7..436ea54 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -232,18 +232,22 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "storageUnit", + storageCapacity: 10, + storageCount: 10, + materialType: "Default material", subType: item.eventData.subType || '', point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "store", - storageCapacity: 10, - triggers: [] - } + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "store", + triggers: [] + } + ] } }; addEvent(storageEvent); diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index c64064a..cd55e4e 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -345,18 +345,22 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "storageUnit", + storageCapacity: 10, + storageCount: 10, + materialType: "Default material", subType: selectedItem.subType || '', 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: "Action 1", - actionType: "store", - storageCapacity: 10, - triggers: [], - }, + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "store", + triggers: [], + } + ], }, }; addEvent(storageEvent); diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index 1fc26ae..5c02a2c 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -3,7 +3,7 @@ import { CameraControls } from '@react-three/drei'; import { ThreeEvent, useThree } from '@react-three/fiber'; import { useCallback, useEffect, useRef } from 'react'; -import { useActiveTool, useDeletableFloorItem, useSelectedFloorItem, useToggleView } from '../../../../../../store/builder/store'; +import { useActiveTool, useDeletableFloorItem, useSelectedFloorItem, useToggleView, useZoneAssetId } from '../../../../../../store/builder/store'; import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore'; import { useSocketStore } from '../../../../../../store/builder/store'; import { useSceneContext } from '../../../../../scene/sceneContext'; @@ -20,9 +20,11 @@ import { upsertProductOrEventApi } from '../../../../../../services/simulation/p export function useModelEventHandlers({ boundingBox, groupRef, + asset }: { boundingBox: THREE.Box3 | null, groupRef: React.RefObject, + asset: Asset }) { const { controls, gl, camera } = useThree(); const { activeTool } = useActiveTool(); @@ -32,12 +34,13 @@ export function useModelEventHandlers({ const { socket } = useSocketStore(); const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext(); const { push3D } = undoRedo3DStore(); - const { removeAsset } = assetStore(); + const { getAssetById, removeAsset } = assetStore(); + const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); const { removeEvent, getEventByModelUuid } = eventStore(); const { getIsEventInProduct, addPoint, deleteEvent } = productStore(); const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); - const { setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedVersionStore } = useVersionContext(); @@ -66,6 +69,19 @@ export function useModelEventHandlers({ }); }; + useEffect(() => { + if (!zoneAssetId) return + if (zoneAssetId.id === asset.modelUuid) { + handleDblClick(asset); + } + }, [zoneAssetId]) + + useEffect(() => { + if (!selectedFloorItem) { + setZoneAssetId(null); + } + }, [selectedFloorItem]) + const handleDblClick = (asset: Asset) => { if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 3f128bb..2f5f77c 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -158,7 +158,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere }); }, []); - const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef }); + const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef, asset }); return ( { - console.log(undoStack, redoStack); + // console.log(undoStack, redoStack); }, [undoStack, redoStack]); useEffect(() => { diff --git a/app/src/modules/scene/helpers/StatsHelper.tsx b/app/src/modules/scene/helpers/StatsHelper.tsx index 486488b..7414b70 100644 --- a/app/src/modules/scene/helpers/StatsHelper.tsx +++ b/app/src/modules/scene/helpers/StatsHelper.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from "react"; -import { Stats } from "@react-three/drei"; import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys"; +import { Perf } from 'r3f-perf'; + export default function StatsHelper() { const [visible, setVisible] = useState(false); @@ -18,5 +19,5 @@ export default function StatsHelper() { return () => window.removeEventListener("keydown", handleKeyDown); }, []); - return visible ? : null; + return visible ? : null; } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index feddc2c..b5e6739 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -14,7 +14,6 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { Color, SRGBColorSpace } from "three"; -import StatsHelper from "./helpers/StatsHelper"; export default function Scene({ layout }: { readonly layout: "Main Layout" | "Comparison Layout"; }) { const map = useMemo(() => [ @@ -72,7 +71,6 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co - ); diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx index 71dcce7..16011f1 100644 --- a/app/src/modules/scene/setup/setup.tsx +++ b/app/src/modules/scene/setup/setup.tsx @@ -1,11 +1,11 @@ import Sun from '../environment/sky' import Shadows from '../environment/shadow' import PostProcessing from '../postProcessing/postProcessing' +import StatsHelper from '../helpers/StatsHelper'; import Controls from '../controls/controls'; import { AdaptiveDpr, AdaptiveEvents, Environment } from '@react-three/drei' import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; -// import { Perf } from 'r3f-perf'; function Setup() { return ( @@ -18,12 +18,12 @@ function Setup() { - {/* */} - {/* */} + + diff --git a/app/src/modules/scene/tools/autoRotate.tsx b/app/src/modules/scene/tools/autoRotate.tsx new file mode 100644 index 0000000..f0ff7a0 --- /dev/null +++ b/app/src/modules/scene/tools/autoRotate.tsx @@ -0,0 +1,47 @@ +import { useEffect, useRef, useState } from 'react' +import { useThree, useFrame } from '@react-three/fiber' +import type CameraControlsImpl from 'camera-controls' + +export default function AutoRotate() { + const { gl, controls } = useThree() + const [isIdle, setIsIdle] = useState(false) + const idleTimeout = useRef(null) + const lastInteractionTime = useRef(Date.now()) + + const cameraControls = controls as CameraControlsImpl | null + + const resetIdleTimer = () => { + lastInteractionTime.current = Date.now() + if (isIdle) setIsIdle(false) + if (idleTimeout.current) clearTimeout(idleTimeout.current) + idleTimeout.current = setTimeout(() => { + setIsIdle(true) + }, 30_000) + } + + useEffect(() => { + const dom = gl.domElement + const listener = () => resetIdleTimer() + + dom.addEventListener('pointerdown', listener) + dom.addEventListener('wheel', listener) + window.addEventListener('keydown', listener) + + resetIdleTimer() + + return () => { + dom.removeEventListener('pointerdown', listener) + dom.removeEventListener('wheel', listener) + window.removeEventListener('keydown', listener) + } + }, [gl]) + + useFrame((_, delta) => { + if (isIdle && cameraControls) { + cameraControls.rotate(delta * 0.1, 0, true) + cameraControls.update(delta) + } + }) + + return null +} diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index 18e8bfd..37c991c 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -38,41 +38,35 @@ export function useRetrieveHandler() { echo.info(`${materialUuid}, ${status}`); } - const createNewMaterial = useCallback((materialId: string, materialType: string, action: StorageAction) => { + const createNewMaterial = useCallback((materialId: string, materialType: string, action: StorageAction, visible: boolean = false) => { const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); const pointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); if (!modelUuid || !pointUuid) return null; const currentTime = performance.now(); - if (action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && - action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid && - action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid - ) { - const newMaterial: MaterialSchema = { - materialId: materialId, - materialName: `${materialType}-${Date.now()}`, - materialType: materialType, - isActive: false, - isVisible: false, - isPaused: false, - isRendered: true, - startTime: currentTime, - previous: { - modelUuid: modelUuid, - pointUuid: pointUuid, - actionUuid: action.actionUuid - }, - current: { - modelUuid: modelUuid, - pointUuid: pointUuid, - actionUuid: action.actionUuid - }, - }; + const newMaterial: MaterialSchema = { + materialId: materialId, + materialName: `${materialType}-${Date.now()}`, + materialType: materialType, + isActive: false, + isVisible: false, + isPaused: false, + isRendered: true, + startTime: currentTime, + previous: { + modelUuid: modelUuid, + pointUuid: pointUuid, + actionUuid: action.actionUuid + }, + current: { + modelUuid: modelUuid, + pointUuid: pointUuid, + actionUuid: action.actionUuid + }, + }; - addMaterial(newMaterial); - return newMaterial; - } - return null; + addMaterial(newMaterial); + return newMaterial; }, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productUuid]); useEffect(() => { @@ -112,12 +106,16 @@ export function useRetrieveHandler() { getModelUuidByActionUuid(selectedProduct.productUuid, retrieval.action.actionUuid) ?? '' ); - if (!storageUnit || storageUnit.currentLoad <= 0) { + if (!storageUnit) { completedActions.push(actionUuid); hasChanges = true; return; } + if (storageUnit.currentLoad <= 0) { + return; + } + if (retrieval.action.triggers.length === 0 || !retrieval.action.triggers[0]?.triggeredAsset) { return; } @@ -170,9 +168,10 @@ export function useRetrieveHandler() { if (retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid) { const action = getActionByUuid(selectedProduct.productUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid); + const storageAction = getActionByUuid(selectedProduct.productUuid, actionUuid); if (action && action.triggers.length > 0 && action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid) { - const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid); - if (model) { + const model = getEventByModelUuid(selectedProduct.productUuid, action?.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (model && storageAction) { if (model.type === 'vehicle') { const vehicle = getVehicleById(model.modelUuid); if (vehicle && !vehicle.isActive && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { @@ -180,7 +179,7 @@ export function useRetrieveHandler() { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { @@ -193,11 +192,11 @@ export function useRetrieveHandler() { retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`); } } - } else { + } else if (storageAction) { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { @@ -271,10 +270,15 @@ export function useRetrieveHandler() { const lastMaterial = getLastMaterial(storageUnit.modelUuid); if (lastMaterial) { if (vehicle?.currentLoad < vehicle.point.action.loadCapacity) { + const triggeredAction = getActionByUuid( + selectedProduct.productUuid, + retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid || '' + ); + const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + triggeredAction as StorageAction ); if (material) { @@ -322,6 +326,9 @@ export function useRetrieveHandler() { const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid ? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid) : null; + + const storageAction = getActionByUuid(selectedProduct.productUuid, actionUuid); + if (triggeredModel?.type === 'vehicle') { const model = getVehicleById(triggeredModel.modelUuid); if (model && !model.isActive && model.state === 'idle' && model.isPicking && model.currentLoad < model.point.action.loadCapacity) { @@ -333,7 +340,7 @@ export function useRetrieveHandler() { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { removeLastMaterial(storageUnit.modelUuid); @@ -359,7 +366,7 @@ export function useRetrieveHandler() { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { removeLastMaterial(storageUnit.modelUuid); @@ -385,7 +392,7 @@ export function useRetrieveHandler() { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { removeLastMaterial(storageUnit.modelUuid); @@ -417,7 +424,7 @@ export function useRetrieveHandler() { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { removeLastMaterial(storageUnit.modelUuid); @@ -438,7 +445,7 @@ export function useRetrieveHandler() { const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + storageAction as StorageAction ); if (material) { removeLastMaterial(storageUnit.modelUuid); @@ -473,6 +480,7 @@ export function useRetrieveHandler() { addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); cranePickupLockRef.current.set(crane.modelUuid, true); + monitoredHumansRef.current.delete(human.modelUuid); }, action.triggers[0].triggeredAsset.triggeredAction?.actionUuid) } monitoredHumansRef.current.add(human.modelUuid); @@ -484,10 +492,15 @@ export function useRetrieveHandler() { const lastMaterial = getLastMaterial(storageUnit.modelUuid); if (lastMaterial) { + const triggeredAction = getActionByUuid( + selectedProduct.productUuid, + action?.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '' + ); + const material = createNewMaterial( lastMaterial.materialId, lastMaterial.materialType, - storageUnit.point.action + triggeredAction as StorageAction ); if (material) { removeLastMaterial(storageUnit.modelUuid); diff --git a/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx index 796a897..20788fa 100644 --- a/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx @@ -13,12 +13,12 @@ export default function MaterialAnimator({ crane }: Readonly(false); useEffect(() => { - if (crane.isCarrying) { + if (crane.isCarrying && (crane.currentPhase === 'pickup-drop' || crane.currentPhase === 'dropping')) { setIsRendered(true); } else { setIsRendered(false); } - }, [crane.isCarrying]); + }, [crane.isCarrying, crane.currentPhase]); useFrame(() => { const craneModel = scene.getObjectByProperty('uuid', crane.modelUuid); diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 96455b2..2af3223 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -4,7 +4,6 @@ import { useFrame, useThree } from '@react-three/fiber'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; -import { dragAction } from '@use-gesture/react'; function PillarJibAnimator({ crane, @@ -56,7 +55,6 @@ function PillarJibAnimator({ if (crane.currentPhase === 'init-pickup') { if (crane.currentMaterials.length > 0) { const materials = scene.getObjectsByProperty('uuid', crane.currentMaterials[0].materialId); - console.log('materials: ', materials); const material = materials.find((material) => material.visible === true); if (material) { const materialWorld = new THREE.Vector3(); diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index 11c06a4..4e18eb2 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -35,17 +35,15 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { setCurrentPhase(crane.modelUuid, 'init-pickup'); } else if (crane.currentPhase === 'picking' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && !crane.isCarrying) { - if (action.triggers.length > 0) { - if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted && humanId && humanAction && humanAction.actionType === 'operator') { - setCurrentAnimation(humanId, 'idle', true, true, true); - setIsCaryying(crane.modelUuid, true); - setCurrentPhase(crane.modelUuid, 'pickup-drop'); - } else { - setCurrentPhaseHuman(humanId, 'hooking'); - setHumanActive(humanId, true); - setHumanState(humanId, 'running'); - setCurrentAnimation(humanId, 'working_standing', true, false, false); - } + if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted && humanId && humanAction && humanAction.actionType === 'operator') { + setCurrentAnimation(humanId, 'idle', true, true, true); + setIsCaryying(crane.modelUuid, true); + setCurrentPhase(crane.modelUuid, 'pickup-drop'); + } else { + setCurrentPhaseHuman(humanId, 'hooking'); + setHumanActive(humanId, true); + setHumanState(humanId, 'running'); + setCurrentAnimation(humanId, 'working_standing', true, false, false); } } else if (crane.currentPhase === 'dropping' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && crane.isCarrying && human.currentPhase === 'hooking') { setCurrentPhaseHuman(humanId, 'loadPoint-unloadPoint'); diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 736667b..21a85c3 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -144,8 +144,8 @@ function TriggerConnector() { // Handle StorageUnit point else if (event.type === "storageUnit" && 'point' in event) { const point = event.point; - if (point.action?.triggers) { - point.action.triggers.forEach(trigger => { + point.actions?.forEach(action => { + action.triggers?.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, @@ -155,7 +155,7 @@ function TriggerConnector() { }); } }); - } + }); } // Handle Human point else if (event.type === "human" && 'point' in event) { diff --git a/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx index d938353..fce077f 100644 --- a/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx @@ -126,9 +126,11 @@ function OperatorInstance({ human }: { human: HumanStatus }) { humanStatus(human.modelUuid, 'Started from loadPoint, heading to unloadPoint'); } } else if (human.state === 'idle' && human.currentPhase === 'unhooking') { - setHumanState(human.modelUuid, 'running'); - setHumanActive(human.modelUuid, true); - setCurrentAnimation(human.modelUuid, 'working_standing', true, false, false); + setTimeout(() => { + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'working_standing', true, false, false); + }, 1) } } else { reset() diff --git a/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx index c5645af..078f203 100644 --- a/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx @@ -14,7 +14,7 @@ function WorkerInstance({ human }: { human: HumanStatus }) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); const { scene } = useThree(); - const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); + const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore, humanEventManagerRef } = useSceneContext(); const { removeMaterial, setEndTime, setIsVisible } = materialStore(); const { getStorageUnitById } = storageUnitStore(); const { getArmBotById } = armBotStore(); @@ -108,7 +108,7 @@ function WorkerInstance({ human }: { human: HumanStatus }) { if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); if (!humanMesh) return; - + const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); setPath(toPickupPath); @@ -129,20 +129,47 @@ function WorkerInstance({ human }: { human: HumanStatus }) { humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); } } else if (human.currentMaterials.length > 0 && human.currentLoad > 0 && humanAsset?.animationState?.current !== 'pickup') { - if (human.currentMaterials[0]?.materialId) { - setIsVisible(human.currentMaterials[0]?.materialId, false); - } - setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + setTimeout(() => { + if (human.currentMaterials[0]?.materialId) { + setIsVisible(human.currentMaterials[0]?.materialId, false); + } + humanStatus(human.modelUuid, 'Started to pickup in pickup point'); + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + }, 1) } } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'dropping' && human.currentLoad === 0) { if (action.pickUpPoint && action.dropPoint) { - const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); - setPath(dropToPickup); - setCurrentPhase(human.modelUuid, 'drop-pickup'); - setHumanState(human.modelUuid, 'running'); - setHumanActive(human.modelUuid, true); - setCurrentAnimation(human.modelUuid, 'walking', true, true, true); - humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); + // const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); + // setPath(dropToPickup); + // setCurrentPhase(human.modelUuid, 'drop-pickup'); + // setHumanState(human.modelUuid, 'running'); + // setHumanActive(human.modelUuid, true); + // setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + if (humanEventManagerRef.current) { + let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === human.modelUuid); + if (state) { + const existingAction = state.actionQueue.find(a => a.actionUuid === action.actionUuid); + if (existingAction) { + const currentCount = existingAction.count ?? 0; + if (existingAction.actionType === 'worker') { + if (currentCount < existingAction.maxLoadCount) { + const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); + setPath(dropToPickup); + setCurrentPhase(human.modelUuid, 'drop-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } else { + setCurrentPhase(human.modelUuid, 'picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); + } + } + } + } + } } } } else { @@ -233,15 +260,13 @@ function WorkerInstance({ human }: { human: HumanStatus }) { const checkAnimation = () => { if (humanAsset?.animationState?.isCompleted) { - if (model.point.action.actionType === 'store') { - loopMaterialDropToStorage( - human.modelUuid, - human.currentLoad, - model.modelUuid, - model.point.action.storageCapacity, - (action as HumanAction) - ); - } + loopMaterialDropToStorage( + human.modelUuid, + human.currentLoad, + model.modelUuid, + model.storageCapacity, + (action as HumanAction) + ); } else { requestAnimationFrame(checkAnimation); } diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index f65e33f..f90c26a 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -136,15 +136,14 @@ function Products() { if (event.type === 'storageUnit') { addStorageUnit(selectedProduct.productUuid, event); - if (event.point.action.actionType === 'retrieve') { - const storageAction = event.point.action; - const materials = Array.from({ length: storageAction.storageCapacity }, () => ({ - materialType: storageAction.materialType || 'Default material', + if (event.storageCount > 0) { + const materials = Array.from({ length: event.storageCount }, () => ({ + materialType: event.materialType || 'Default material', materialId: THREE.MathUtils.generateUUID() })); setCurrentMaterials(event.modelUuid, materials); - updateCurrentLoad(event.modelUuid, storageAction.storageCapacity); + updateCurrentLoad(event.modelUuid, event.storageCount); } else { setCurrentMaterials(event.modelUuid, []); updateCurrentLoad(event.modelUuid, 0); diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 8eb1af9..d7a5058 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -34,7 +34,7 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone const { armBotStore, productStore, materialStore } = useSceneContext(); const { getArmBotById } = armBotStore(); const { getMaterialById, getMaterialPosition } = materialStore(); - const { getEventByModelUuid, getActionByUuid, getPointByUuid } = productStore(); + const { getEventByModelUuid, getActionByUuid, getPointByUuid, getTriggeringModels } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { scene } = useThree(); @@ -176,7 +176,8 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone if (armbotStatus && currentMaterial && currentAction && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end' || currentPhase === 'end-to-rest')) { const materialData = getMaterialById(currentMaterial); if (materialData) { - const prevModel = getEventByModelUuid(selectedProduct.productUuid, materialData.current.modelUuid); + const triggeringModel = getTriggeringModels(selectedProduct.productUuid, currentAction.actionUuid); + const prevModel = triggeringModel[0] || null; const nextModel = getEventByModelUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || ''); const nextPoint = getPointByUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '', currentAction?.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || ''); diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index e1595b6..ee196f5 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -42,11 +42,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { const lastRemoved = useRef<{ type: string, materialId: string, modelId: string } | null>(null); - function firstFrame() { - startTime = performance.now(); - step(); - } - const action = getActionByUuid(selectedProduct.productUuid, armBot.currentAction?.actionUuid || ''); const handlePickUpTrigger = () => { @@ -109,68 +104,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { } } - function step() { - if (isPausedRef.current) { - if (!pauseTimeRef.current) { - pauseTimeRef.current = performance.now(); - } - requestAnimationFrame(() => step()); - return; - } - if (pauseTimeRef.current) { - const pauseDuration = performance.now() - pauseTimeRef.current; - startTime += pauseDuration; - pauseTimeRef.current = null; - } - const elapsedTime = performance.now() - startTime; - if (elapsedTime < 1000) { - // Wait until 1500ms has passed - requestAnimationFrame(step); - return; - } - if (currentPhase === "picking") { - setArmBotActive(armBot.modelUuid, true); - setArmBotState(armBot.modelUuid, "running"); - setCurrentPhase("start-to-end"); - startTime = 0 - if (!action) return; - const startPoint = (action as RoboticArmAction).process.startPoint; - const endPoint = (action as RoboticArmAction).process.endPoint; - if (startPoint && endPoint) { - let curve = createCurveBetweenTwoPoints( - new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), - new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])); - if (curve) { - logStatus(armBot.modelUuid, "picking the object"); - setPath(curve.points.map(point => [point.x, point.y, point.z])) - - handlePickUpTrigger(); - - } - } - logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") - } else if (currentPhase === "dropping") { - setArmBotActive(armBot.modelUuid, true); - setArmBotState(armBot.modelUuid, "running"); - setCurrentPhase("end-to-rest"); - startTime = 0; - if (!action) return; - const endPoint = (action as RoboticArmAction).process.endPoint; - if (endPoint) { - - let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); - if (curve) { - logStatus(armBot.modelUuid, "dropping the object"); - setPath(curve.points.map(point => [point.x, point.y, point.z])); - - handleDropTrigger(); - - } - } - logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") - } - } - useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]); @@ -268,7 +201,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { useEffect(() => { const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone); if (!isReset && isPlaying) { - //Moving armBot from initial point to rest position. if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") { if (targetBones) { setArmBotActive(armBot.modelUuid, true) @@ -280,13 +212,9 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { } } logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") - } - //Waiting for trigger. - else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { + } else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction") - } - //Moving to pickup point - else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) { + } else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) { if (armBot.currentAction) { setArmBotActive(armBot.modelUuid, true); @@ -302,14 +230,45 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { } } logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.") - } - // Moving to Pick to Drop position - else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "picking" && armBot.currentAction) { - requestAnimationFrame(firstFrame); - } - //Moving to drop point to restPosition - else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "dropping" && armBot.currentAction) { - requestAnimationFrame(firstFrame); + } else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "picking" && armBot.currentAction) { + setTimeout(() => { + setArmBotActive(armBot.modelUuid, true); + setArmBotState(armBot.modelUuid, "running"); + setCurrentPhase("start-to-end"); + startTime = 0 + if (!action) return; + const startPoint = (action as RoboticArmAction).process.startPoint; + const endPoint = (action as RoboticArmAction).process.endPoint; + if (startPoint && endPoint) { + let curve = createCurveBetweenTwoPoints(new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])); + if (curve) { + logStatus(armBot.modelUuid, "picking the object"); + setPath(curve.points.map(point => [point.x, point.y, point.z])) + handlePickUpTrigger(); + } + } + logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") + }, 100) + } else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "dropping" && armBot.currentAction) { + setTimeout(() => { + setArmBotActive(armBot.modelUuid, true); + setArmBotState(armBot.modelUuid, "running"); + setCurrentPhase("end-to-rest"); + startTime = 0; + if (!action) return; + const endPoint = (action as RoboticArmAction).process.endPoint; + if (endPoint) { + let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); + if (curve) { + logStatus(armBot.modelUuid, "dropping the object"); + setPath(curve.points.map(point => [point.x, point.y, point.z])); + + handleDropTrigger(); + + } + } + logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") + }, 100) } } else { logStatus(armBot.modelUuid, "Simulation Play Exited") @@ -348,7 +307,7 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { setCurrentPhase("rest"); setPath([]) } - else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") { + else if (armBot.state == "running" && currentPhase == "rest-to-start") { logStatus(armBot.modelUuid, "Callback triggered: pick."); setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "running") diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts index 09be2f2..f06992b 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -1,12 +1,13 @@ import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; -export function determineExecutionOrder(products: productsSchema): PointsScheme[] { +export function determineExecutionOrder(products: productsSchema): Action[] { // Create maps for all events and points const eventMap = new Map(); const pointMap = new Map(); const allPoints: PointsScheme[] = []; + const spawnActions: Action[] = []; - // First pass: collect all points + // First pass: collect all points and identify spawn actions products.forEach(product => { product.eventDatas.forEach(event => { eventMap.set(event.modelUuid, event); @@ -15,6 +16,11 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ event.points.forEach(point => { pointMap.set(point.uuid, point); allPoints.push(point); + + // Check for spawn actions in conveyors + if (point.action.actionType === 'spawn') { + spawnActions.push(point.action); + } }); } else if (event.type === 'vehicle' || event.type === 'machine' || @@ -25,6 +31,16 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); + + // Check for spawn actions in storage units and other types + if (event.type === 'storageUnit') { + const storagePoint = event.point as StoragePointSchema; + storagePoint.actions.forEach(action => { + if (action.actionType === 'retrieve') { + spawnActions.push(action); + } + }); + } } }); }); @@ -33,11 +49,19 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ const graph = new Map(); const reverseGraph = new Map(); const allTriggeredPoints = new Set(); + const actionMap = new Map(); // Map point UUID to its primary action allPoints.forEach(point => { const triggers = extractTriggersFromPoint(point); const dependencies: string[] = []; + // Store the primary action for this point + if ('action' in point) { + actionMap.set(point.uuid, point.action); + } else if ('actions' in point && point.actions.length > 0) { + actionMap.set(point.uuid, point.actions[0]); // Use first action as primary + } + triggers.forEach(trigger => { const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid; if (targetUuid && pointMap.has(targetUuid)) { @@ -58,15 +82,12 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ const rootPoints = allPoints .filter(point => !allTriggeredPoints.has(point.uuid)) .filter(point => { - // Only include roots that actually have triggers pointing FROM them const triggers = extractTriggersFromPoint(point); return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid); }); // If no root points found but we have triggered points, find the earliest triggers if (rootPoints.length === 0 && allTriggeredPoints.size > 0) { - // This handles cases where we have circular dependencies - // but still want to include the triggered points const minTriggerCount = Math.min( ...Array.from(allTriggeredPoints) .map(uuid => (graph.get(uuid) || []).length) @@ -105,11 +126,18 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ // Start processing from root points rootPoints.forEach(root => visit(root.uuid)); - // Convert UUIDs back to points and filter out untriggered points - const triggeredPoints = order - .map(uuid => pointMap.get(uuid)!) - .filter(point => allTriggeredPoints.has(point.uuid) || - rootPoints.some(root => root.uuid === point.uuid)); + // Convert UUIDs back to actions + const triggeredActions = order + .map(uuid => actionMap.get(uuid)) + .filter((action): action is Action => action !== undefined); - return triggeredPoints; + // Combine triggered actions with ALL spawn actions + const allExecutionActions = [...triggeredActions, ...spawnActions]; + + // Remove duplicate actions while preserving order + const uniqueActions = allExecutionActions.filter((action, index, array) => + array.findIndex(a => a.actionUuid === action.actionUuid) === index + ); + + return uniqueActions; } \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index 869ffa9..21bf97a 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -21,12 +21,13 @@ function Simulator() { if (!product) return; const executionOrder = determineExecutionOrder([product]); - executionOrder.forEach(point => { - const action = 'actions' in point ? point.actions[0] : point.action; + + executionOrder.forEach(action => { handleAction(action); }); }, [products, isPlaying, isReset, selectedProduct]); + return ( <> diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 9660a6d..d0b1619 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -106,6 +106,7 @@ export function useTriggerHandler() { // Handle current action using Event Manager addVehicleToMonitor(vehicle.modelUuid, () => { + setIsVisible(materialId, false); handleAction(action, materialId); }) } @@ -392,7 +393,7 @@ export function useTriggerHandler() { } } } else if (toEvent?.type === 'crane') { - // Transfer to Human + // Transfer to Crane if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { const material = getMaterialById(materialId); if (material) { @@ -527,24 +528,11 @@ export function useTriggerHandler() { if (action && armBot) { - if (armBot.isActive === false && armBot.state === 'idle') { - - // Handle current action from arm bot + addArmBotToMonitor(armBot.modelUuid, () => { setIsVisible(materialId, false); handleAction(action, materialId); - - } else { - - addArmBotToMonitor(armBot.modelUuid, - () => { - setIsVisible(materialId, false); - - handleAction(action, materialId); - } - ) - - } + }) } } } @@ -575,7 +563,7 @@ export function useTriggerHandler() { if (action && storageUnit) { - if (storageUnit.currentLoad < storageUnit.point.action.storageCapacity) { + if (storageUnit.currentLoad < storageUnit.storageCapacity) { // Handle current action from vehicle handleAction(action, materialId); @@ -622,7 +610,7 @@ export function useTriggerHandler() { } } } else if (toEvent?.type === 'crane') { - // Vehicle to Human + // Vehicle to Crane if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { const material = getMaterialById(materialId); if (material) { @@ -1135,7 +1123,7 @@ export function useTriggerHandler() { if (action && storageUnit) { - if (storageUnit.currentLoad < storageUnit.point.action.storageCapacity) { + if (storageUnit.currentLoad < storageUnit.storageCapacity) { // Handle current action from vehicle handleAction(action, materialId); @@ -1782,7 +1770,7 @@ export function useTriggerHandler() { if (action && storageUnit) { - if (storageUnit.currentLoad < storageUnit.point.action.storageCapacity) { + if (storageUnit.currentLoad < storageUnit.storageCapacity) { // Handle current action from vehicle handleAction(action, materialId); @@ -1801,25 +1789,25 @@ export function useTriggerHandler() { } } else if (fromEvent?.type === 'crane') { if (toEvent?.type === 'transfer') { - // Crane Unit to Transfer + // Crane to Transfer } else if (toEvent?.type === 'vehicle') { - // Crane Unit to Vehicle + // Crane to Vehicle } else if (toEvent?.type === 'machine') { - // Crane Unit to Machine + // Crane to Machine } else if (toEvent?.type === 'roboticArm') { - // Crane Unit to Robotic Arm + // Crane to Robotic Arm } else if (toEvent?.type === 'storageUnit') { - // Crane Unit to Storage Unit + // Crane to Storage Unit } else if (toEvent?.type === 'human') { - // Crane Unit to Human + // Crane to Human } else if (toEvent?.type === 'crane') { - // Crane Unit to Human + // Crane to Human } } diff --git a/app/src/modules/simulation/ui3d/StorageContentUi.tsx b/app/src/modules/simulation/ui3d/StorageContentUi.tsx index 38f62dc..7d02932 100644 --- a/app/src/modules/simulation/ui3d/StorageContentUi.tsx +++ b/app/src/modules/simulation/ui3d/StorageContentUi.tsx @@ -31,7 +31,7 @@ const StorageContentUi: React.FC = ({ storageUnit }) => { status={storageUnit.state} count={storageUnit.currentLoad} enableStatue={false} - totalCapacity={storageUnit.point.action.storageCapacity} + totalCapacity={storageUnit.storageCapacity} /> ); diff --git a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx index b4c9b90..9bc6afc 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx @@ -1,14 +1,14 @@ import { useEffect, useRef, useState } from 'react'; import { useThree, useFrame } from '@react-three/fiber'; -import * as THREE from 'three'; import { MaterialModel } from '../../../materials/instances/material/materialModel'; +import { Object3D, Vector3 } from 'three'; const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { const meshRef = useRef(null!); const [hasLoad, setHasLoad] = useState(false); const [isAttached, setIsAttached] = useState(false); const { scene } = useThree(); - const offset = new THREE.Vector3(0, 0.85, 0); + const [offset, setOffset] = useState(new Vector3(0, 0.85, 0)); useEffect(() => { const loadState = agvDetail.currentLoad > 0; @@ -16,19 +16,24 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { if (!loadState) { setIsAttached(false); - const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as Object3D; if (agvModel) { + // update offset + let offsetY = agvDetail?.point.position[1]; + if(offsetY) setOffset(new Vector3(0, offsetY, 0)); + const material = agvModel.getObjectByName('Sample-Material'); if (material) { agvModel.remove(material); } } } + // eslint-disable-next-line }, [agvDetail.currentLoad]); useFrame(() => { // if (agvDetail.currentMaterials.length === 0 || agvDetail.currentLoad === 0) { - // const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + // const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as Object3D; // if (agvModel) { // const material = agvModel.getObjectByName('Sample-Material'); // if (material) { @@ -38,7 +43,7 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { // } if (!hasLoad || !meshRef.current || isAttached) return; - const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as Object3D; if (agvModel && !isAttached) { const material = agvModel.getObjectByName('Sample-Material'); if (material) { diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 62ddb21..c8463cf 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -147,6 +147,16 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai if (angle < 0.01) { object.quaternion.copy(targetQuaternion); setRestingRotation(false); + setTimeout(() => { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(startUnloadingProcess); + } + }, 0) } else { const step = rotationSpeed * delta * speed * agvDetail.speed; const angle = object.quaternion.angleTo(targetQuaternion); @@ -169,7 +179,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai handleCallBack(); if (currentPhase === 'pickup-drop') { requestAnimationFrame(startUnloadingProcess); - } } }); @@ -226,13 +235,8 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai ); } - - export default VehicleAnimator; - - - function DraggableSphere({ index, position, diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 0466ba2..e9b13c2 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -378,8 +378,10 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) const human = getHumanById(humanId); const humanAction = human?.point.actions.find((action) => action.actionUuid === humanActionId); - if (human && human.currentAction?.actionUuid !== humanActionId && human.currentLoad < (humanAction?.loadCapacity || 0)) { - addCurrentAction(humanId, humanActionId); + if (human && human.currentLoad < (humanAction?.loadCapacity || 0)) { + if (human.currentAction?.actionUuid !== humanActionId) { + addCurrentAction(humanId, humanActionId); + } setTimeout(() => { unloadLoop(); }, 500) @@ -388,16 +390,14 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) function handleMaterialDropToStorageUnit(model: StorageEventSchema) { if (model) { - if (model.point.action.actionType === 'store') { - loopMaterialDropToStorage( - agvDetail.modelUuid, - agvDetail.currentLoad, - agvDetail.point.action.unLoadDuration, - model.modelUuid, - model.point.action.storageCapacity, - agvDetail.point.action - ); - } + loopMaterialDropToStorage( + agvDetail.modelUuid, + agvDetail.currentLoad, + agvDetail.point.action.unLoadDuration, + model.modelUuid, + model.storageCapacity, + agvDetail.point.action + ); } } diff --git a/app/src/modules/visualization/visualization.tsx b/app/src/modules/visualization/visualization.tsx index 77956f4..52e3947 100644 --- a/app/src/modules/visualization/visualization.tsx +++ b/app/src/modules/visualization/visualization.tsx @@ -1,7 +1,6 @@ import React from 'react' import Dropped3dWidgets from './widgets/3d/Dropped3dWidget' import ZoneCentreTarget from './zone/zoneCameraTarget' -import ZoneAssets from './zone/zoneAssets' import MqttEvents from '../../services/factoryBuilder/mqtt/mqttEvents' const Visualization:React.FC = () => { @@ -12,8 +11,6 @@ const Visualization:React.FC = () => { - - {/* */} diff --git a/app/src/modules/visualization/zone/zoneAssets.tsx b/app/src/modules/visualization/zone/zoneAssets.tsx deleted file mode 100644 index a5fed54..0000000 --- a/app/src/modules/visualization/zone/zoneAssets.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useEffect, useRef } from 'react' -import { useSelectedFloorItem, useZoneAssetId } from '../../../store/builder/store'; -import * as THREE from "three"; -import { useThree } from '@react-three/fiber'; -import * as Types from "../../../types/world/worldTypes"; -export default function ZoneAssets() { - const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); - const { setSelectedFloorItem } = useSelectedFloorItem(); - const { raycaster, controls, scene }: any = useThree(); - - useEffect(() => { - // console.log('zoneAssetId: ', zoneAssetId); - if (!zoneAssetId) return - let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id); - if (AssetMesh) { - const bbox = new THREE.Box3().setFromObject(AssetMesh); - const size = bbox.getSize(new THREE.Vector3()); - const center = bbox.getCenter(new THREE.Vector3()); - - const front = new THREE.Vector3(0, 0, 1); - AssetMesh.localToWorld(front); - front.sub(AssetMesh.position).normalize(); - - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center.clone().addScaledVector(front, distance); - - controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true); - controls.setTarget(center.x, center.y, center.z, true); - controls.fitToBox(AssetMesh, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, }); - - setSelectedFloorItem(AssetMesh); - } else { - if (Array.isArray(zoneAssetId.position) && zoneAssetId.position.length >= 3) { - let selectedAssetPosition = [ - zoneAssetId.position[0], - 10, - zoneAssetId.position[2] - ]; - let selectedAssetTarget = [ - zoneAssetId.position[0], - zoneAssetId.position[1], - zoneAssetId.position[2] - ]; - const setCam = async () => { - await controls?.setLookAt(...selectedAssetPosition, ...selectedAssetTarget, true); - setTimeout(() => { - let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id); - if (AssetMesh) { - const bbox = new THREE.Box3().setFromObject(AssetMesh); - const size = bbox.getSize(new THREE.Vector3()); - const center = bbox.getCenter(new THREE.Vector3()); - - const front = new THREE.Vector3(0, 0, 1); - AssetMesh.localToWorld(front); - front.sub(AssetMesh.position).normalize(); - - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center.clone().addScaledVector(front, distance); - - controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true); - controls.setTarget(center.x, center.y, center.z, true); - controls.fitToBox(AssetMesh, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, }); - - setSelectedFloorItem(AssetMesh); - } - }, 500) - - }; - setCam(); - } - } - }, [zoneAssetId, scene, controls]) - - return ( - <> - - ) -} \ No newline at end of file diff --git a/app/src/modules/visualization/zone/zoneCameraTarget.tsx b/app/src/modules/visualization/zone/zoneCameraTarget.tsx index 82f014a..f9e3a9c 100644 --- a/app/src/modules/visualization/zone/zoneCameraTarget.tsx +++ b/app/src/modules/visualization/zone/zoneCameraTarget.tsx @@ -3,101 +3,98 @@ import { useFrame, useThree } from "@react-three/fiber"; import * as THREE from "three"; import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore"; import { - useEditPosition, - usezonePosition, - usezoneTarget, + useEditPosition, + usezonePosition, + usezoneTarget, } from "../../../store/builder/store"; export default function ZoneCentreTarget() { - const { selectedZone } = useSelectedZoneStore(); - // - const [previousZoneCentre, setPreviousZoneCentre] = useState( - null - ); - const sphereRef = useRef(null); - const { controls }: any = useThree(); - const { setZonePosition } = usezonePosition(); - const { setZoneTarget } = usezoneTarget(); - const { Edit } = useEditPosition(); + const { selectedZone } = useSelectedZoneStore(); + const [previousZoneCentre, setPreviousZoneCentre] = useState(null); + const sphereRef = useRef(null); + const { controls }: any = useThree(); + const { setZonePosition } = usezonePosition(); + const { setZoneTarget } = usezoneTarget(); + const { Edit } = useEditPosition(); - const TRANSITION_SPEED = 2000; + const TRANSITION_SPEED = 2000; - useEffect(() => { - if ( - selectedZone.zoneViewPortTarget && - JSON.stringify(previousZoneCentre) !== - JSON.stringify(selectedZone.zoneViewPortTarget) - ) { - setPreviousZoneCentre(selectedZone.zoneViewPortTarget); - } - }, [selectedZone.zoneViewPortTarget, previousZoneCentre]); - - const centrePoint = useMemo(() => { - if (!previousZoneCentre || !selectedZone.zoneViewPortTarget) return null; - return previousZoneCentre.map( - (value, index) => (value + selectedZone.zoneViewPortTarget[index]) / 2 - ); - }, [previousZoneCentre, selectedZone.zoneViewPortTarget]); - - useEffect(() => { - if (selectedZone.zoneName !== "") { - if (sphereRef.current) { - sphereRef.current.position.set( - selectedZone.zoneViewPortTarget[0], - selectedZone.zoneViewPortTarget[1], - selectedZone.zoneViewPortTarget[2] - ); - } - if (centrePoint) { - if (centrePoint.length > 0) { - const setCam = async () => { - controls.setLookAt( - centrePoint[0], - 26, - centrePoint[2], - ...centrePoint, - true, - TRANSITION_SPEED - ); - setTimeout(() => { - controls?.setLookAt( - ...selectedZone.zoneViewPortPosition, - ...selectedZone.zoneViewPortTarget, - true, - TRANSITION_SPEED - ); - }, 100); - }; - setCam(); - } else { - const setCam = async () => { - controls?.setLookAt( - ...selectedZone.zoneViewPortPosition, - ...selectedZone.zoneViewPortTarget, - true, - TRANSITION_SPEED - ); - }; - setCam(); + useEffect(() => { + if ( + selectedZone.zoneViewPortTarget && + JSON.stringify(previousZoneCentre) !== + JSON.stringify(selectedZone.zoneViewPortTarget) + ) { + setPreviousZoneCentre(selectedZone.zoneViewPortTarget); } - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedZone.zoneViewPortTarget]); + }, [selectedZone.zoneViewPortTarget, previousZoneCentre]); - useFrame(() => { - if (Edit) { - setZonePosition([ - controls.getPosition().x, - controls.getPosition().y, - controls.getPosition().z, - ]); - setZoneTarget([ - controls.getTarget().x, - controls.getTarget().y, - controls.getTarget().z, - ]); - } - }); - return <>; + const centrePoint = useMemo(() => { + if (!previousZoneCentre || !selectedZone.zoneViewPortTarget) return null; + return previousZoneCentre.map( + (value, index) => (value + selectedZone.zoneViewPortTarget[index]) / 2 + ); + }, [previousZoneCentre, selectedZone.zoneViewPortTarget]); + + useEffect(() => { + if (selectedZone.zoneName !== "") { + if (sphereRef.current) { + sphereRef.current.position.set( + selectedZone.zoneViewPortTarget[0], + selectedZone.zoneViewPortTarget[1], + selectedZone.zoneViewPortTarget[2] + ); + } + if (centrePoint) { + if (centrePoint.length > 0) { + const setCam = async () => { + controls.setLookAt( + centrePoint[0], + 26, + centrePoint[2], + ...centrePoint, + true, + TRANSITION_SPEED + ); + setTimeout(() => { + controls?.setLookAt( + ...selectedZone.zoneViewPortPosition, + ...selectedZone.zoneViewPortTarget, + true, + TRANSITION_SPEED + ); + }, 100); + }; + setCam(); + } else { + const setCam = async () => { + controls?.setLookAt( + ...selectedZone.zoneViewPortPosition, + ...selectedZone.zoneViewPortTarget, + true, + TRANSITION_SPEED + ); + }; + setCam(); + } + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedZone.zoneViewPortTarget]); + + useFrame(() => { + if (Edit) { + setZonePosition([ + controls.getPosition().x, + controls.getPosition().y, + controls.getPosition().z, + ]); + setZoneTarget([ + controls.getTarget().x, + controls.getTarget().y, + controls.getTarget().z, + ]); + } + }); + return <>; } diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 62a5759..20aad02 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -4,7 +4,6 @@ import { useSocketStore, useOrganization, useUserName, - useSaveVersion, useProjectName, useActiveTool, } from "../store/builder/store"; @@ -33,7 +32,7 @@ const Project: React.FC = () => { const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); const { projectId } = useParams(); - const { projectName, setProjectName } = useProjectName(); + const { setProjectName } = useProjectName(); const { userId, email, organization, userName } = getUserData(); const { selectedUser } = useSelectedUserStore(); const { isLogListVisible } = useLogger(); diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts index 68839a7..3198be1 100644 --- a/app/src/store/simulation/useMaterialStore.ts +++ b/app/src/store/simulation/useMaterialStore.ts @@ -80,7 +80,13 @@ export const createMaterialStore = () => { addMaterial: (material) => { let updatedMaterial: MaterialSchema | undefined; set((state) => { + const existingIndex = state.materials.findIndex(m => m.materialId === material.materialId); + if (existingIndex !== -1) { + state.materials.splice(existingIndex, 1); + } + state.materials.push(material); + updatedMaterial = JSON.parse(JSON.stringify(material)); }); return updatedMaterial; }, diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index f9533a1..8ff8ac8 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -73,6 +73,7 @@ type ProductsStore = { getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getTriggerByUuid: (productUuid: string, triggerUuid: string) => TriggerSchema | undefined; getTriggersByTriggeredPointUuid: (productUuid: string, triggeredPointUuid: string) => TriggerSchema[]; + getTriggeringModels: (productUuid: string, actionUUid: string) => EventsSchema[]; getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean; }; @@ -898,6 +899,58 @@ export const createProductStore = () => { return undefined; }, + getTriggeringModels: (productUuid: string, actionUuid: string) => { + const product = get().products.find(p => p.productUuid === productUuid); + if (!product) return []; + + const triggeringModels: EventsSchema[] = []; + const targetModelUuid = get().getModelUuidByActionUuid(productUuid, actionUuid); + + if (!targetModelUuid) return []; + + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.triggers) { + for (const trigger of point.action.triggers) { + if (trigger.triggeredAsset?.triggeredModel?.modelUuid === targetModelUuid && + trigger.triggeredAsset?.triggeredAction?.actionUuid === actionUuid) { + triggeringModels.push(event); + break; + } + } + } + } + } else if ('point' in event) { + const point = (event as any).point; + + if ('action' in point && point.action?.triggers) { + for (const trigger of point.action.triggers) { + if (trigger.triggeredAsset?.triggeredModel?.modelUuid === targetModelUuid && + trigger.triggeredAsset?.triggeredAction?.actionUuid === actionUuid) { + triggeringModels.push(event); + break; + } + } + } else if ('actions' in point) { + for (const action of point.actions) { + if (action.triggers) { + for (const trigger of action.triggers) { + if (trigger.triggeredAsset?.triggeredModel?.modelUuid === targetModelUuid && + trigger.triggeredAsset?.triggeredAction?.actionUuid === actionUuid) { + triggeringModels.push(event); + break; + } + } + } + } + } + } + } + + return triggeringModels; + }, + getTriggersByTriggeredPointUuid: (productUuid, triggeredPointUuid) => { const product = get().products.find(p => p.productUuid === productUuid); if (!product) return []; diff --git a/app/src/store/simulation/useStorageUnitStore.ts b/app/src/store/simulation/useStorageUnitStore.ts index c89e643..deae86a 100644 --- a/app/src/store/simulation/useStorageUnitStore.ts +++ b/app/src/store/simulation/useStorageUnitStore.ts @@ -205,7 +205,7 @@ export const createStorageUnitStore = () => { getFullStorageUnits: () => { return get().storageUnits.filter( - s => s.currentLoad >= s.point.action.storageCapacity + s => s.currentLoad >= s.storageCapacity ); }, diff --git a/app/src/styles/components/contextMenu/_contextMenu.scss b/app/src/styles/components/contextMenu/_contextMenu.scss index 1b52e4b..3a812a8 100644 --- a/app/src/styles/components/contextMenu/_contextMenu.scss +++ b/app/src/styles/components/contextMenu/_contextMenu.scss @@ -2,61 +2,75 @@ @use "../../abstracts/mixins" as *; .context-menu { - position: absolute; - top: 0; - left: 0; - background: var(--background-color); - backdrop-filter: blur(50px); - color: var(--text-button-color); - box-shadow: var(--box-shadow-light); - border-radius: 6px; - z-index: 1000; - min-width: 200px; - padding: 4px; + position: absolute; + top: 0; + left: 0; + background: var(--background-color); + backdrop-filter: blur(50px); + color: var(--text-button-color); + box-shadow: var(--box-shadow-light); + border-radius: 10px; + z-index: 1000; + min-width: 200px; + padding: 4px; + display: flex; + flex-direction: column; + gap: 2px; + + .menuItem { + position: relative; display: flex; - flex-direction: column; - gap: 2px; + justify-content: space-between; + cursor: pointer; + .submenu { + display: none; + min-width: 178px; + position: absolute; + top: 0; + left: 100%; // place directly beside - .menuItem { - position: relative; - display: flex; - justify-content: space-between; - padding: 6px 8px; - border-radius: 10px; - cursor: pointer; - - .submenu { - display: none; - min-width: 178px; - position: absolute; - top: 0; - left: 100%; // place directly beside - - background: var(--background-color); - backdrop-filter: blur(50px); - color: var(--text-button-color); - box-shadow: var(--box-shadow-light); - - padding: 4px; - border-radius: 6px; - z-index: 1000; - } - - .button { - display: flex; - gap: 6px; - } - - // Keep submenu open while hovering parent OR submenu - &:hover .submenu, - .submenu:hover { - display: block; - } - - &:hover { - background-color: var(--background-color-button); - } + background: var(--background-color); + backdrop-filter: blur(50px); + color: var(--text-button-color); + box-shadow: var(--box-shadow-light); + padding: 4px; + border-radius: 6px; + z-index: 1000; + } + .value { + display: flex; + } + .button { + display: flex; + gap: 6px; + padding: 5px 8px; + width: 100%; + border-radius: 10px; + &:hover { + color: var(--text-button-color); + background-color: var(--background-color-button); + } + } + .button.delete { + &:hover { + background-color: #e02e16; + } + } + .button.more { + justify-content: space-between; } + .shortcut { + position: absolute; + right: 8px; + top: 6px; + pointer-events: none; + } -} \ No newline at end of file + // Keep submenu open while hovering parent OR submenu + &:hover .submenu, + .submenu:hover { + display: block; + } + } +} diff --git a/app/src/styles/components/footer/footer.scss b/app/src/styles/components/footer/footer.scss index 7bf951a..152a39f 100644 --- a/app/src/styles/components/footer/footer.scss +++ b/app/src/styles/components/footer/footer.scss @@ -86,6 +86,7 @@ height: 100%; color: var(--text-color); gap: 6px; + cursor: pointer; } .logs-detail { diff --git a/app/src/styles/components/input.scss b/app/src/styles/components/input.scss index a370d56..e628dcb 100644 --- a/app/src/styles/components/input.scss +++ b/app/src/styles/components/input.scss @@ -231,43 +231,49 @@ input[type="number"] { justify-content: space-between; cursor: pointer; border-radius: #{$border-radius-large}; - } - - .dropdown-options { - position: absolute; - width: 100%; - background: var(--background-color); - border: 1px solid var(--border-color); - border-radius: #{$border-radius-large}; - z-index: 10; - max-height: 200px; - overflow-y: auto; - left: 0; - top: 110%; - padding: 4px; - backdrop-filter: blur(8px); - - .dropdown-search { - margin-bottom: 4px; + .key{ + width: calc(100% - 18px); + overflow: hidden; + text-overflow: ellipsis; } - - .option { - padding: 2px 4px; - cursor: pointer; - flex-direction: row !important; - border-radius: #{$border-radius-medium}; - - &:hover { - color: var(--highlight-text-color); - background: var(--highlight-accent-color); - } - } - } - + } .icon { height: auto; } } +.dropdown-options { + position: absolute; + width: 100%; + background: var(--background-color); + border: 1px solid var(--border-color); + border-radius: #{$border-radius-large}; + z-index: 10; + max-height: 200px; + overflow-y: auto; + left: 0; + top: 110%; + padding: 4px; + backdrop-filter: blur(8px); + + .dropdown-search { + margin-bottom: 4px; + } + + .option { + padding: 2px 4px; + cursor: pointer; + flex-direction: row !important; + border-radius: #{$border-radius-medium}; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + + &:hover { + color: var(--highlight-text-color); + background: var(--highlight-accent-color); + } + } +} .input.default { width: 100%; diff --git a/app/src/styles/components/logs/logs.scss b/app/src/styles/components/logs/logs.scss index 69d6e44..41c6f70 100644 --- a/app/src/styles/components/logs/logs.scss +++ b/app/src/styles/components/logs/logs.scss @@ -1,6 +1,86 @@ @use "../../abstracts/variables.scss" as *; @use "../../abstracts/mixins.scss" as *; +.log-nav-container { + @include flex-space-between; + align-items: flex-end; + .log-nav-wrapper { + display: flex; + gap: 6px; + + .log-nav { + padding: 8px 16px; + border-radius: #{$border-radius-extra-large}; + } + + .log-nav.active { + background-color: var(--background-color-accent); + color: var(--text-button-color); + } + } + .clear-button { + margin: 0 6px; + padding: 4px 12px; + color: var(--text-disabled); + border-radius: #{$border-radius-large}; + &:hover { + font-weight: 300; + color: var(--text-color); + background: var(--background-color-solid-gradient); + } + } +} + +.log-entry-wrapper { + height: calc(100% - 80px); + display: flex; + flex-direction: column; + gap: 4px; + background: var(--background-color); + padding: 10px; + border-radius: #{$border-radius-xlarge}; + outline: 1px solid var(--border-color); + outline-offset: -1px; + overflow: auto; + + .log-entry { + padding: 4px; + border-radius: #{$border-radius-small}; + font-size: var(--font-size-small); + display: flex; + + .log-icon { + height: 24px; + width: 24px; + @include flex-center; + } + .log-entry-message-container { + @include flex-space-between; + gap: 12px; + width: 100%; + .message-time { + font-size: var(--font-size-tiny); + font-weight: 300; + opacity: 0.8; + text-wrap: nowrap; + // height: 100%; + } + .log-entry-message { + width: 100%; + } + } + + &:nth-child(odd) { + background: var(--background-color); + } + } + .no-log { + padding: 20px; + text-align: center; + color: var(--text-color); + } +} + .log-list-container { width: 100vw; height: 100vh; @@ -33,99 +113,38 @@ scale: 0.8; } } - - .close { - @include flex-center; - height: 28px; - width: 28px; - cursor: pointer; - border-radius: #{$border-radius-medium}; - svg { - scale: 1.6; - } - &:hover { - background: var(--background-color); - } - } - } - .log-nav-container { - @include flex-space-between; - align-items: flex-end; - .log-nav-wrapper { + .action-buttons-container { display: flex; - gap: 6px; - - .log-nav { - padding: 8px 16px; - border-radius: #{$border-radius-extra-large}; - } - - .log-nav.active { - background-color: var(--background-color-accent); - color: var(--text-button-color); - } - } - .clear-button{ - margin: 0 6px; - padding: 4px 12px; - color: var(--text-disabled); - border-radius: #{$border-radius-large}; - &:hover{ - font-weight: 300; - color: var(--text-color); - background: var(--background-color-solid-gradient); - } - } - } - - .log-entry-wrapper { - height: calc(100% - 80px); - display: flex; - flex-direction: column; - gap: 4px; - background: var(--background-color); - padding: 10px; - border-radius: #{$border-radius-xlarge}; - outline: 1px solid var(--border-color); - outline-offset: -1px; - overflow: auto; - - .log-entry { - padding: 4px; - border-radius: #{$border-radius-small}; - font-size: var(--font-size-small); - display: flex; - - .log-icon { - height: 24px; - width: 24px; + align-items: center; + gap: 4px; + .close, + .expand-btn { @include flex-center; - } - .log-entry-message-container { - @include flex-space-between; - gap: 12px; - width: 100%; - .message-time { - font-size: var(--font-size-tiny); - font-weight: 300; - opacity: 0.8; - text-wrap: nowrap; - // height: 100%; + height: 28px; + width: 28px; + cursor: pointer; + border-radius: #{$border-radius-medium}; + svg { + scale: 1.6; } - .log-entry-message { - width: 100%; + &:hover { + background: var(--background-color); + border: 1px solid #ffffff29; } } - - &:nth-child(odd) { - background: var(--background-color); - } } } - .no-log{ - padding: 20px; - text-align: center; - color: var(--text-color); - } } } + +.log-list-new-window-wrapper{ + padding: 10px; + background: var(--background-color-solid); + height: 100vh; + .log-nav-container{ + margin-bottom: 10px; + } + .log-entry-wrapper{ + height: calc(100% - 50px); + } +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index ef5aaee..255e7f7 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -2,2409 +2,2447 @@ @use "../abstracts/mixins" as *; .toggle-sidebar-ui-button { - @include flex-center; - cursor: pointer; - height: 32px; - width: 32px; - min-height: 32px; - min-width: 32px; - border-radius: #{$border-radius-large}; - position: relative; + @include flex-center; + cursor: pointer; + height: 32px; + width: 32px; + min-height: 32px; + min-width: 32px; + border-radius: #{$border-radius-large}; + position: relative; + + .tooltip { + top: 6px; + right: -168px; + + &::after { + left: 0px; + bottom: 50%; + } + } + + &:hover { + outline: 1px solid var(--border-color); + outline-offset: -1px; + background: var(--background-color-solid); .tooltip { + opacity: 1; + transform: translateX(2px); + } + } +} + +.toggle-sidebar-ui-button.active { + background: var(--background-color-accent); + + rect { + stroke: var(--icon-default-color-active); + } + + circle { + fill: var(--icon-default-color-active); + } + + &:hover { + filter: saturate(0.8); + background: var(--background-color-accent); + } +} + +.sidebar-left-wrapper { + width: 270px; + position: fixed; + top: 22px; + left: 8px; + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: #{$border-radius-extra-large}; + outline: 1px solid var(--border-color); + box-shadow: #{$box-shadow-medium}; + z-index: #{$z-index-tools}; + + .header-container { + @include flex-space-between; + padding: 10px; + width: 100%; + + .header-content { + @include flex-center; + width: calc(100% - 34px); + + .logo-container { + @include flex-center; + cursor: pointer; + } + + .header-title { + padding: 0 8px; + width: 100%; + max-width: calc(100% - 32px); + } + } + + .toggle-sidebar-ui-button { + .tooltip { top: 6px; right: -168px; &::after { - left: 0px; - bottom: 50%; + left: 0px; + bottom: 50%; } + } } + } - &:hover { - outline: 1px solid var(--border-color); - outline-offset: -1px; - background: var(--background-color-solid); + .sidebar-left-container { + min-height: 50vh; + max-width: 100%; + padding-bottom: 4px; + position: relative; + display: flex; + flex-direction: column; - .tooltip { - opacity: 1; - transform: translateX(2px); - } - } -} + .sidebar-left-content-container { + position: relative; -.toggle-sidebar-ui-button.active { - background: var(--background-color-accent); - - rect { - stroke: var(--icon-default-color-active); - } - - circle { - fill: var(--icon-default-color-active); - } - - &:hover { - filter: saturate(0.8); - background: var(--background-color-accent); - } -} - -.sidebar-left-wrapper { - width: 270px; - position: fixed; - top: 22px; - left: 8px; - background: var(--background-color); - backdrop-filter: blur(20px); - border-radius: #{$border-radius-extra-large}; - outline: 1px solid var(--border-color); - box-shadow: #{$box-shadow-medium}; - z-index: #{$z-index-tools}; - - .header-container { - @include flex-space-between; - padding: 10px; - width: 100%; - - .header-content { - @include flex-center; - width: calc(100% - 34px); - - .logo-container { - @include flex-center; - cursor: pointer; - } - - .header-title { - padding: 0 8px; - width: 100%; - max-width: calc(100% - 32px); - } - } - - .toggle-sidebar-ui-button { - .tooltip { - top: 6px; - right: -168px; - - &::after { - left: 0px; - bottom: 50%; - } - } - } - } - - .sidebar-left-container { - min-height: 50vh; - max-width: 100%; - padding-bottom: 4px; - position: relative; + .template-list { display: flex; flex-direction: column; + gap: 1rem; + padding: 1rem; + min-height: 50vh; + max-height: 60vh; + } - .sidebar-left-content-container { - position: relative; + .template-item { + border: 1px solid #e0e0e0; + border-radius: #{$border-radius-medium}; - .template-list { + padding: 1rem; + transition: box-shadow 0.3s ease; + } + + .template-image-container { + position: relative; + padding-bottom: 56.25%; // 16:9 aspect ratio + } + + .template-image { + position: absolute; + width: 100%; + height: 100%; + object-fit: contain; + border-radius: #{$border-radius-small}; + + cursor: pointer; + transition: transform 0.3s ease; + } + + .template-details { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.5rem; + } + + .template-name { + cursor: pointer; + font-weight: 500; + } + + .delete-button { + padding: 0.25rem 0.5rem; + background: #ff4444; + color: white; + border: none; + border-radius: #{$border-radius-small}; + + cursor: pointer; + transition: opacity 0.3s ease; + } + + .no-templates { + text-align: center; + color: #666; + grid-column: 1 / -1; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 6px; + + h2 { + font-weight: 600; + } + } + + .widgets-wrapper { + min-height: 50vh; + max-height: 60vh; + overflow: auto; + } + + .widget-left-sideBar { + .widget2D { + overflow: auto; + + .chart-container { + display: flex; + flex-direction: column; + gap: 8px; + padding: 6px; + flex-wrap: nowrap; + overflow: auto; + + .chart { + min-height: 170px; + background: var(--background-color); + border: 1.23px solid var(--border-color); + box-shadow: 0px 4.91px 4.91px 0px #0000001c; + border-radius: $border-radius-medium; + padding: 12px 6px; + } + + .progressBar { + height: auto !important; + padding: 12px 10px 41px 10px; + display: flex; + flex-direction: column; + gap: 16px; + + .header { display: flex; - flex-direction: column; - gap: 1rem; - padding: 1rem; - min-height: 50vh; - max-height: 60vh; - } + justify-content: start; + align-items: center; + border-bottom: none; + } - .template-item { - border: 1px solid #e0e0e0; + .stock { + padding: 13px 5px; + background: var(--background-color-secondary); border-radius: #{$border-radius-medium}; - - padding: 1rem; - transition: box-shadow 0.3s ease; - } - - .template-image-container { - position: relative; - padding-bottom: 56.25%; // 16:9 aspect ratio - } - - .template-image { - position: absolute; - width: 100%; - height: 100%; - object-fit: contain; - border-radius: #{$border-radius-small}; - - cursor: pointer; - transition: transform 0.3s ease; - } - - .template-details { + box-shadow: var(--box-shadow-light); display: flex; justify-content: space-between; - align-items: center; - margin-top: 0.5rem; - } - .template-name { - cursor: pointer; - font-weight: 500; - } - - .delete-button { - padding: 0.25rem 0.5rem; - background: #ff4444; - color: white; - border: none; - border-radius: #{$border-radius-small}; - - cursor: pointer; - transition: opacity 0.3s ease; - } - - .no-templates { - text-align: center; - color: #666; - grid-column: 1 / -1; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - - h2 { - font-weight: 600; - } - } - - .widgets-wrapper { - min-height: 50vh; - max-height: 60vh; - overflow: auto; - } - - .widget-left-sideBar { - .widget2D { - overflow: auto; - - .chart-container { - display: flex; - flex-direction: column; - gap: 8px; - padding: 6px; - flex-wrap: nowrap; - overflow: auto; - - .chart { - min-height: 170px; - background: var(--background-color); - border: 1.23px solid var(--border-color); - box-shadow: 0px 4.91px 4.91px 0px #0000001c; - border-radius: $border-radius-medium; - padding: 12px 6px; - } - - .progressBar { - height: auto !important; - padding: 12px 10px 41px 10px; - display: flex; - flex-direction: column; - gap: 16px; - - .header { - display: flex; - justify-content: start; - align-items: center; - border-bottom: none; - } - - .stock { - padding: 13px 5px; - background: var(--background-color-secondary); - border-radius: #{$border-radius-medium}; - box-shadow: var(--box-shadow-light); - display: flex; - justify-content: space-between; - - .stock-item { - .stockValues { - display: flex; - flex-direction: row-reverse; - align-items: flex-end; - gap: 3px; - - .value { - color: var(--accent-color); - font-size: var(--font-size-large); - } - } - - .stock-description { - font-size: var(--font-size-small); - } - } - } - } - } - } - - .widget3D { + .stock-item { + .stockValues { display: flex; - flex-direction: column; - gap: 6px; - padding: 6px; + flex-direction: row-reverse; + align-items: flex-end; + gap: 3px; - .widget-item { - height: 170px; - background: var(--background-color); - border: 1.23px solid var(--border-color); - box-shadow: 0px 4.91px 4.91px 0px #0000001c; - border-radius: $border-radius-medium; - padding: 12px 6px; - - .widget-image { - width: 100%; - height: 90%; - object-fit: contain; - } + .value { + color: var(--accent-color); + font-size: var(--font-size-large); } + } + + .stock-description { + font-size: var(--font-size-small); + } } + } } + } } - .outline-container { - height: 100%; + .widget3D { + display: flex; + flex-direction: column; + gap: 6px; + padding: 6px; - .outline-content-container { - position: relative; - height: 100%; - padding: 8px; + .widget-item { + height: 170px; + background: var(--background-color); + border: 1.23px solid var(--border-color); + box-shadow: 0px 4.91px 4.91px 0px #0000001c; + border-radius: $border-radius-medium; + padding: 12px 6px; - .overflow { - height: calc(100% - 16px); - max-height: 46vh; - overflow: auto; - margin: 0; - } + .widget-image { + width: 100%; + height: 90%; + object-fit: contain; } + } } + } } + + .outline-container { + height: 100%; + + .outline-content-container { + position: relative; + height: 100%; + padding: 8px; + + .overflow { + height: calc(100% - 16px); + max-height: 46vh; + overflow: auto; + margin: 0; + } + } + } + } } .sidebar-right-wrapper { - width: 320px; - position: fixed; - top: 22px; - right: 8px; - background: var(--background-color); - backdrop-filter: blur(20px); - border-radius: #{$border-radius-extra-large}; - outline: 1px solid var(--border-color); - box-shadow: #{$box-shadow-medium}; - z-index: #{$z-index-tools}; + width: 320px; + position: fixed; + top: 22px; + right: 8px; + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: #{$border-radius-extra-large}; + outline: 1px solid var(--border-color); + box-shadow: #{$box-shadow-medium}; + z-index: #{$z-index-tools}; - .header-container { - @include flex-space-between; - padding: 10px; - width: 100%; - gap: 8px; - height: 52px; + .header-container { + @include flex-space-between; + padding: 10px; + width: 100%; + gap: 8px; + height: 52px; - .options-container { - @include flex-center; - gap: 8px; + .options-container { + @include flex-center; + gap: 8px; - .share-button { - padding: 4px 12px; - color: var(--text-button-color); - background: var(--background-color-button); - font-weight: var(--font-weight-regular); - border-radius: #{$border-radius-large}; - cursor: pointer; - } + .share-button { + padding: 4px 12px; + color: var(--text-button-color); + background: var(--background-color-button); + font-weight: var(--font-weight-regular); + border-radius: #{$border-radius-large}; + cursor: pointer; + } - .app-docker-button { - @include flex-center; - } - } - - .split { - height: 20px; - min-width: 1px; - background: var(--text-disabled); - } - - .users-container { - width: 100%; - @include flex-space-between; - - .user-profile { - @include flex-center; - height: 26px; - width: 26px; - min-height: 26px; - min-width: 26px; - border-radius: #{$border-radius-circle}; - - font-weight: var(--font-weight-bold); - color: white; - text-transform: capitalize; - } - - .guest-users-container { - display: flex; - width: 100%; - justify-content: flex-end; - - .other-guest { - @include flex-center; - height: 26px; - width: 26px; - min-height: 26px; - min-width: 26px; - border-radius: #{$border-radius-circle}; - - background: var(--highlight-accent-color); - font-weight: var(--font-weight-bold); - color: var(--accent-color); - outline: 1px solid var(--accent-color); - outline-offset: -1px; - } - } - - .user-profile-container { - display: flex; - - .user-profile { - background: var(--background-color-accent); - color: var(--text-button-color); - } - - .user-organization { - height: 26px; - width: 52px; - max-width: 52px; - border-radius: #{$border-radius-extra-large}; - - overflow: hidden; - margin-left: 2px; - - img { - height: 100%; - width: 100%; - object-fit: cover; - vertical-align: top; - } - } - } - } + .app-docker-button { + @include flex-center; + } } - .sidebar-actions-container { - position: absolute; - left: -40px; - background: transparent; - overflow: visible; + .split { + height: 20px; + min-width: 1px; + background: var(--text-disabled); + } + + .users-container { + width: 100%; + @include flex-space-between; + + .user-profile { + @include flex-center; + height: 26px; + width: 26px; + min-height: 26px; + min-width: 26px; + border-radius: #{$border-radius-circle}; + + font-weight: var(--font-weight-bold); + color: white; + text-transform: capitalize; + } + + .guest-users-container { + display: flex; + width: 100%; + justify-content: flex-end; + + .other-guest { + @include flex-center; + height: 26px; + width: 26px; + min-height: 26px; + min-width: 26px; + border-radius: #{$border-radius-circle}; + + background: var(--highlight-accent-color); + font-weight: var(--font-weight-bold); + color: var(--accent-color); + outline: 1px solid var(--accent-color); + outline-offset: -1px; + } + } + + .user-profile-container { + display: flex; + + .user-profile { + background: var(--background-color-accent); + color: var(--text-button-color); + } + + .user-organization { + height: 26px; + width: 52px; + max-width: 52px; + border-radius: #{$border-radius-extra-large}; + + overflow: hidden; + margin-left: 2px; + + img { + height: 100%; + width: 100%; + object-fit: cover; + vertical-align: top; + } + } + } + } + } + + .sidebar-actions-container { + position: absolute; + left: -40px; + background: transparent; + overflow: visible; + + .tooltip { + top: 6px; + right: calc(100% + 6px); + + &::after { + left: 100%; + bottom: 50%; + } + } + + .sidebar-action-list { + margin-bottom: 12px; + @include flex-center; + height: 34px; + width: 34px; + border-radius: #{$border-radius-circle}; + background: var(--background-color-solid-gradient); + backdrop-filter: blur(12px); + outline: 1px solid var(--border-color); + outline-offset: -1px; + transition: all 0.2s; + + &:hover { + outline-color: var(--border-color-accent); + + svg { + transition: all 0.2s; + scale: 1.1; + } .tooltip { - top: 6px; - right: calc(100% + 6px); - - &::after { - left: 100%; - bottom: 50%; - } - } - - .sidebar-action-list { - margin-bottom: 12px; - @include flex-center; - height: 34px; - width: 34px; - border-radius: #{$border-radius-circle}; - background: var(--background-color-solid-gradient); - backdrop-filter: blur(12px); - outline: 1px solid var(--border-color); - outline-offset: -1px; - transition: all 0.2s; - - &:hover { - outline-color: var(--border-color-accent); - - svg { - transition: all 0.2s; - scale: 1.1; - } - - .tooltip { - opacity: 1; - transform: translateX(-2px); - } - } - } - - .active { - background: var(--background-color-accent); - outline: none; - - &:hover { - svg { - scale: 1; - } - - background: var(--background-color-accent); - } + opacity: 1; + transform: translateX(-2px); } + } } - .sidebar-right-container { - min-height: 50vh; - padding: 8px; - position: relative; - overflow: auto; + .active { + background: var(--background-color-accent); + outline: none; - .sidebar-right-content-container { - height: calc(100% - 36px); - position: relative; - width: 304px; - - .decal-transformation-container { - display: flex; - flex-direction: column; - gap: 4px; - - .transformation-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 12px; - - .header { - flex: 1; - text-transform: capitalize; - } - - .input-wrapppers { - display: flex; - align-items: center; - gap: 6px; - flex: 1.5; - - svg { - stroke: #CCACFF; - } - - .icon { - display: flex; - justify-content: center; - align-items: center; - } - - input { - min-width: 43px; - } - } - - .layers { - display: flex; - gap: 6px; - align-items: center; - - .icon { - display: flex; - justify-content: center; - align-items: center; - outline: 1px solid var(--border-color); - padding: 4px 16px; - width: 50px; - border-radius: 100px; - } - } - } - - .opacity { - input { - min-width: 190px !important; - } - } - - .preview { - width: 100%; - height: 150px; - border-radius: 20px; - outline: 1px solid var(--border-color); - position: relative; - - img { - width: 100%; - height: 100%; - object-fit: contain; - } - - .replace-btn { - background-color: #6F42C1; - border-radius: 100px; - color: #FFFFFF; - padding: 4px 16px; - width: fit-content; - cursor: pointer; - font-size: 12px; - text-transform: capitalize; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - } - } - - .version-history-container { - max-height: calc(62vh - 12px); - display: flex; - flex-direction: column; - padding: 0 8px; - gap: 10px; - - .version-history-header { - display: flex; - justify-content: space-between; - align-items: center; - - .version-history-icons { - display: flex; - align-items: center; - gap: 6px; - - .add-icon { - transform: scale(1.1); - } - - .kebab-icon { - display: flex; - transform: rotate(90deg) scale(0.8); - } - - .close-icon { - transform: scale(1.5); - } - } - } - - .version-history-shortcut-info { - display: flex; - gap: 6px; - border: 1px solid var(--border-color); - - background: var(--background-color); - padding: 10px 8px; - border-radius: 13px; - - .info-icon { - width: 12px; - height: 12px; - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - border: 1px solid var(--border-color); - padding: 4px; - font-size: 10px; - } - - .shortcut-text { - color: var(--text-disabled); - } - } - - .version-history-location { - display: flex; - align-items: center; - gap: 12px; - - .location-details { - display: flex; - flex-direction: column; - gap: 4px; - - .saved-history-count { - font-size: var(--font-size-tiny); - } - } - } - - .saved-versions-list { - padding-top: 16px; - display: flex; - flex-direction: column; - gap: 20px; - - .saved-version { - display: flex; - align-items: center; - gap: 12px; - - .version-name { - background: var(--background-color); - border: 1px solid var(--border-color); - color: var(--text-color); - border-radius: 13px; - padding: 4px 8px; - position: relative; - font-size: var(--font-size-small); - text-wrap: nowrap; - } - - &:not(:first-child) .version-name::after { - content: ""; - position: absolute; - top: -35px; - left: 50%; - transform: translateX(-50%); - width: 1px; - height: 32px; - background-color: var(--text-disabled); - } - - .version-details { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - gap: 6px; - - .details { - display: flex; - flex-direction: column; - gap: 6px; - } - - .saved-by { - display: flex; - align-items: center; - gap: 6px; - - .user-profile { - background: var(--background-color-accent); - color: var(--text-button-color); - width: 20px; - height: 20px; - border-radius: 50%; - - display: flex; - justify-content: center; - align-items: center; - text-transform: uppercase; - } - - .user-name { - text-transform: capitalize; - } - } - - .timestamp { - .input-value { - color: var(--text-color); - text-align: start; - } - } - } - } - } - } - - .no-event-selected { - color: #666; - padding: 16px; - grid-column: 1 / -1; - border-radius: #{$border-radius-large}; - background: var(--background-color); - - .products-list { - padding-top: 1rem; - - .product-item { - text-align: start; - margin-top: 8px; - padding: 2px 0; - text-decoration: none; - display: flex; - flex-wrap: wrap; - gap: 6px; - - button { - width: fit-content; - position: relative; - @include flex-center; - gap: 4px; - background: var(--background-color); - padding: 8px 12px; - border-radius: #{$border-radius-extra-large}; - outline: 1px solid var(--border-color); - - &:hover { - background: var(--background-color-accent); - color: var(--text-button-color); - outline: none; - - path { - stroke: var(--text-button-color); - strokewidth: 1.3; - } - } - } - } - - .products-list-title { - text-align: start; - color: var(--accent-color); - font-size: var(--font-size-regular); - } - } - } + &:hover { + svg { + scale: 1; } + + background: var(--background-color-accent); + } } + } - .visualization-right-sideBar { - min-height: 50vh; - max-height: 60vh; + .sidebar-right-container { + min-height: 50vh; + padding: 8px; + position: relative; + overflow: auto; - .sidebar-right-content-container { - .dataSideBar { - .inputs-wrapper { - display: flex; - flex-direction: column; - gap: 6px; + .sidebar-right-content-container { + height: calc(100% - 36px); + position: relative; + width: 304px; - .datas { - .input-value { - padding: 5px 10px; - } + .decal-transformation-container { + display: flex; + flex-direction: column; + gap: 4px; - .input-value, - .rename-input { - margin-right: 24px; - width: 170px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - } + .transformation-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 12px; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - gap: 12px; - padding: 10px 12px; + .header { + flex: 1; + text-transform: capitalize; + } - .datas { - display: flex; - align-items: center; - justify-content: space-between; - - .datas__class { - display: flex; - align-items: center; - gap: 12px; - - .multi-level-dropdown { - width: 170px; - - .dropdown-button { - display: flex; - justify-content: space-between; - gap: 6px; - } - - .disable { - cursor: not-allowed; - pointer-events: none; - /* Disables all mouse interactions */ - opacity: 0.5; - /* Optional: Makes the button look visually disabled */ - } - } - } - } - - .sideBarHeader { - color: var(--accent-color); - border-bottom: 1px solid var(--border-color); - padding-bottom: 6px; - } - - .selectedMain-container { - display: flex; - flex-direction: column; - gap: 6px; - border-bottom: 1px solid var(--border-color); - padding-bottom: 10px; - - .selectedMain { - display: flex; - align-items: center; - gap: 6px; - - main { - width: 35%; - white-space: nowrap; - /* Prevent wrapping */ - } - - .icon { - padding: 0; - cursor: pointer; - } - - button { - background: transparent; - box-shadow: none; - color: #5273eb; - padding: 6px; - font-size: var(--font-size-xlarge); - } - - .bulletPoint { - color: #5273eb; - font-size: var(--font-size-large); - } - - .regularDropdown-container { - width: 100%; - } - - &:first-child { - gap: 4px; - } - } - } - - .child { - width: 100%; - gap: 6px; - } - - .infoBox { - display: flex; - align-items: flex-start; - gap: 6px; - color: #444; - border-radius: #{$border-radius-medium}; - font-size: var(--font-weight-regular); - - .infoIcon { - padding: 0px 7px; - border-radius: #{$border-radius-circle}; - - border: 1px solid gray; - } - - p { - margin: 0; - } - } - } - - .design { - width: 100%; - display: flex; - flex-direction: column; - gap: 15px; - font-size: var(--font-weight-regular); - color: var(--text-color); - padding: 12px; - - .appearance-container, - .element-container { - background: var(--background-color); - backdrop-filter: blur(20px); - border-radius: 15px; - outline: 1px solid var(--border-color); - - padding: 10px; - - display: flex; - flex-direction: column; - gap: 12px; - - .header-container { - padding: 0; - height: auto; - } - - .appearance-style { - display: flex; - align-items: center; - justify-content: space-between; - flex-direction: column; - gap: 12px; - - .regularDropdown-container { - .dropdown-options { - width: 130%; - left: -15%; - } - - .dropdown-header { - gap: 12px; - } - } - - .color-wrapper, - .opacity-wrapper, - .blurEffect-wrapper, - .theme-wrapper { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: column; - - .input-range-container { - width: 100%; - padding: 0; - } - } - - .theme-wrapper { - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - } - - .color-wrapper { - flex-direction: row; - - .value { - display: flex; - align-items: center; - - input { - width: 34px; - height: 24px; - border-radius: 12px; - padding: 0; - } - } - } - } - } - - .element-container { - padding: 8px; - - .display-element { - width: 100%; - height: 150px; - @include flex-center; - background: var(--background-color); - backdrop-filter: blur(20px); - border-radius: 5px; - outline: 1px solid var(--border-color); - } - - .name-wrapper, - .element-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - - .value { - width: 60%; - } - } - } - } - - .reviewChart { - width: 100%; - height: 150px; - background: var(--background-color); - display: flex; - align-items: center; - } - - .optionsContainer { - display: flex; - flex-direction: column; - gap: 10px; - padding: 0 12px; - - .option { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - - .regularDropdown-container, - input { - width: 160px; - } - - &:last-child { - flex-direction: column; - - .header { - width: 100%; - display: flex; - justify-content: space-between; - } - - .colorDisplayer { - width: 100%; - display: flex; - justify-content: start; - align-items: center; - - input[type="color"] { - border: none; - outline: none; - background: none; - width: 24px; - height: 26px; - border-radius: #{$border-radius-small}; - padding: 0; - } - } - } - - span { - min-width: 100px; - } - } - } - } - } - - .machine-mechanics-container { - .machine-mechanics-header { - padding: 8px 12px; - border-top: 1px solid var(--border-color); - border-bottom: 1px solid var(--border-color); - color: var(--accent-color); - } - - .process-list-container { + .input-wrapppers { display: flex; align-items: center; - gap: 4px; - padding: 8px; - border-bottom: 1px solid var(--border-color); + gap: 6px; + flex: 1.5; - .label { - margin-right: 8px; + svg { + stroke: #ccacff; } - .add-new-process { - @include flex-center; - height: 24px; - min-width: 24px; - cursor: pointer; - background: var(--background-color-secondary); - border-radius: #{$border-radius-medium}; - - path { - stroke: var(--accent-color); - strokewidth: 1.5px; - } - - &:hover { - background: var(--accent-color); - - path { - stroke: var(--highlight-accent-color); - } - } + .icon { + display: flex; + justify-content: center; + align-items: center; } + + input { + min-width: 43px; + } + } + + .layers { + display: flex; + gap: 6px; + align-items: center; + + .icon { + display: flex; + justify-content: center; + align-items: center; + outline: 1px solid var(--border-color); + padding: 4px 16px; + width: 50px; + border-radius: 100px; + } + } } - } - .machine-mechanics-content-container, - .simulations-container, - .event-proprties-wrapper { - position: relative; - max-height: calc(62vh - (47px - 35px)); - width: 304px; - border-radius: #{$border-radius-large}; - overflow-x: hidden; + .opacity { + input { + min-width: 190px !important; + } + } - .header { - @include flex-space-between; - padding: 6px 12px; - padding-right: 6px; + .preview { + width: 100%; + height: 150px; + border-radius: 20px; + outline: 1px solid var(--border-color); + position: relative; + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + + .replace-btn { + background-color: #6f42c1; + border-radius: 100px; + color: #ffffff; + padding: 4px 16px; + width: fit-content; + cursor: pointer; + font-size: 12px; text-transform: capitalize; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } + } - .add-button { - @include flex-center; - padding: 4px 8px; - background: var(--background-color-button); - color: var(--text-button-color); - border-radius: #{$border-radius-large}; - cursor: pointer; - outline: none; - border: none; + .version-history-container { + max-height: calc(62vh - 12px); + display: flex; + flex-direction: column; + padding: 0 8px; + gap: 10px; - path { - stroke: var(--text-button-color); - } + .version-history-header { + display: flex; + justify-content: space-between; + align-items: center; - &:disabled { - background: var(--text-disabled); - } + .version-history-icons { + display: flex; + align-items: center; + gap: 0px; + + .add-icon, + .kebab-icon, + .close-icon { + height: 24px; + min-width: 24px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + &:hover { + background: var(--background-color); + outline: 1px solid #aaaaaa29; + } } + + .add-icon { + svg { + transform: scale(1.1); + } + } + + .kebab-icon { + display: flex; + svg { + transform: rotate(90deg) scale(0.8); + } + } + + .close-icon { + svg { + transform: scale(1.5); + } + } + } } - .global-props { - .property-list-container { - .property-item { - .value-field-container { - margin: 0; + .version-history-shortcut-info { + display: flex; + gap: 6px; + border: 1px solid var(--border-color); - input { - padding: 5px 10px; - } + background: var(--background-color); + padding: 10px 8px; + border-radius: 13px; - .dropdown { - top: 4px; - right: 4px; - } - } - } - } + .info-icon { + width: 12px; + height: 12px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + border: 1px solid var(--border-color); + padding: 4px; + font-size: 10px; + } + + .shortcut-text { + color: var(--text-disabled); + } } - .selected-actions-details { - .selected-actions-header .input-value { - padding: 8px 12px; - color: var(--accent-color); - } + .version-history-location { + display: flex; + align-items: center; + gap: 12px; - .selected-actions-list { - margin-bottom: 8px; - - .eye-dropper-input-container { - padding: 6px 12px; - - .regularDropdown-container { - padding: 5px 8px; - outline: 1px solid var(--border-color); - outline-offset: -1px; - border: none; - } - } - - .value-field-container { - margin: 0; - - input { - padding: 5px 4px; - text-align: center; - } - - .dropdown { - top: 4px; - right: 4px; - } - } - } - } - - .lists-main-container { - margin: 2px; - width: calc(100% - 4px); - background: var(--background-color-gray); - border-radius: #{$border-radius-medium}; - - min-height: 120px; - - .list-container { - padding: 4px; - height: calc(100% - 16px); - overflow-y: scroll; - - .list-item { - @include flex-space-between; - padding: 4px 12px; - width: 100%; - border-radius: #{$border-radius-medium}; - - .value { - display: flex; - justify-content: flex-start; - align-items: center; - min-width: 80%; - gap: 6px; - - .input-value { - text-align: start; - } - - input { - width: fit-content; - outline: none; - accent-color: var(--accent-color); - } - } - } - - .active { - background: var(--highlight-accent-color); - - .value .input-value { - color: var(--accent-color); - } - - path { - stroke: var(--accent-color); - } - } - - .remove-button { - @include flex-center; - height: 18px; - width: 18px; - cursor: pointer; - border-radius: #{$border-radius-small}; - transform: translateX(4px); - - &:hover { - background: var(--background-color-accent); - - path { - stroke: var(--text-button-color); - } - } - } - } - - .resize-icon { - @include flex-center; - padding: 4px; - cursor: grab; - width: 100%; - - &:active { - cursor: grabbing; - } - } - } - - .selected-properties-container { - padding: 12px; - - .properties-header { - color: var(--accent-color); - font-weight: var(--font-weight-regular); - padding: 8px 0; - } - - .input-toggle-container { - padding: 0; - margin-bottom: 6px; - } - - .value-field-container { - margin-bottom: 6px; - padding: 0; - @include flex-space-between; - - .label { - width: 40%; - } - - .regularDropdown-container { - width: 60%; - } - - .default { - width: 60%; - } - } - } - - .simulation-process { - .collapse-header-container { - @include flex-space-between; - padding-right: 12px; - margin: 8px 0; - width: 100%; - - .header { - color: var(--accent-color); - } - } - - .process-container { - padding: 0 12px; - margin: 6px 0; - padding-left: 16px; - position: relative; - - &::after { - content: "↶"; - rotate: -90deg; - transform: translate(-16px, 4px) scaleX(1); - height: 100%; - width: 1px; - position: absolute; - color: var(--accent-color); - font-size: var(--font-size-regular); - outline-offset: -1px; - top: 0; - left: 4px; - } - - &:last-child { - &::after { - display: none; - } - } - - .value { - @include flex-space-between; - - .arrow-container { - height: 16px; - width: 16px; - } - - .active { - rotate: 90deg; - } - } - - .children-drop { - .value { - padding: 6px; - border-left: 1px solid var(--border-color); - } - } - } - } - - .trigger-wrapper { - .trigger-item { - .trigger-name { - padding: 8px; - margin-top: 4px; - } - - .value-field-container { - margin: 0; - } - } - } - - .footer { - @include flex-center; - justify-content: flex-start; + .location-details { + display: flex; + flex-direction: column; gap: 4px; - padding: 12px; - font-size: var(--font-size-tiny); + + .saved-history-count { + font-size: var(--font-size-tiny); + } + } } - .compare-simulations-container { - background: var(--background-color); - padding: 12px; - border-radius: #{$border-radius-large}; + .saved-versions-list { + padding-top: 16px; + display: flex; + flex-direction: column; + gap: 20px; - .compare-simulations-header { - font-weight: var(--font-weight-medium); + .saved-version { + display: flex; + align-items: center; + gap: 12px; + + .version-name { + background: var(--background-color); + border: 1px solid var(--border-color); + color: var(--text-color); + border-radius: 13px; + padding: 4px 8px; + position: relative; + font-size: var(--font-size-small); + text-wrap: nowrap; } - .content { - padding: 12px 0; - font-size: var(--font-size-small); - - span { - font-size: inherit; - color: var(--accent-color); - } + &:not(:first-child) .version-name::after { + content: ""; + position: absolute; + top: -35px; + left: 50%; + transform: translateX(-50%); + width: 1px; + height: 32px; + background-color: var(--text-disabled); } - .input { - width: 100%; + .version-details { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + gap: 6px; + + .details { display: flex; - flex-direction: row-reverse; - justify-content: flex-start; + flex-direction: column; + gap: 6px; + } - input { - width: fit-content; - background: var(--background-color-button); - color: var(--text-button-color); - padding: 3px 10px; - cursor: pointer; - border: none; - outline: none; - } - } - } - } - - .aisle-properties-container { - max-height: 65vh; - overflow: auto; - - .aisle-texture-container { - max-height: 40vh; - overflow: auto; - - .aisle-list { - width: calc(100% - 8px); - text-align: start; - padding: 4px 6px; + .saved-by { display: flex; align-items: center; gap: 6px; - border-radius: #{$border-radius-large}; - margin: 2px 6px; - .texture-display { - height: 34px; - width: 34px; - background: #7e7e7e86; - border-radius: #{$border-radius-large}; - margin-right: 4px; - overflow: hidden; + .user-profile { + background: var(--background-color-accent); + color: var(--text-button-color); + width: 20px; + height: 20px; + border-radius: 50%; + + display: flex; + justify-content: center; + align-items: center; + text-transform: uppercase; } - .aisle-color { - text-transform: capitalize; + .user-name { + text-transform: capitalize; } + } - .aisle-brief { - font-size: var(--font-size-small); - color: var(--input-text-color); - } - - &.selected { - background: var(--highlight-accent-color); - - .aisle-color { - // color: var(--text-button-color); - } - - &:hover { - // background: var(--background-color-accent); - } - } - - &:hover { - background: var(--highlight-accent-color); - // background: var(--background-color-secondary); + .timestamp { + .input-value { + color: var(--text-color); + text-align: start; } + } } + } + } + } + + .no-event-selected { + color: #666; + padding: 16px; + grid-column: 1 / -1; + border-radius: #{$border-radius-large}; + background: var(--background-color); + + .products-list { + padding-top: 1rem; + + .product-item { + text-align: start; + margin-top: 8px; + padding: 2px 0; + text-decoration: none; + display: flex; + flex-wrap: wrap; + gap: 6px; + + button { + width: fit-content; + position: relative; + @include flex-center; + gap: 4px; + background: var(--background-color); + padding: 8px 12px; + border-radius: #{$border-radius-extra-large}; + outline: 1px solid var(--border-color); + + &:hover { + background: var(--background-color-accent); + color: var(--text-button-color); + outline: none; + + path { + stroke: var(--text-button-color); + stroke-width: 1.3; + } + } + } + } + + .products-list-title { + text-align: start; + color: var(--accent-color); + font-size: var(--font-size-regular); + } + } + } + } + } + + .visualization-right-sideBar { + min-height: 50vh; + max-height: 60vh; + + .sidebar-right-content-container { + .dataSideBar { + .inputs-wrapper { + display: flex; + flex-direction: column; + gap: 6px; + + .datas { + .input-value { + padding: 5px 10px; + } + + .input-value, + .rename-input { + margin-right: 24px; + width: 170px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; + padding: 10px 12px; + + .datas { + display: flex; + align-items: center; + justify-content: space-between; + + .datas__class { + display: flex; + align-items: center; + gap: 12px; + + .multi-level-dropdown { + width: 170px; + + .dropdown-button { + display: flex; + justify-content: space-between; + gap: 6px; + } + + .disable { + cursor: not-allowed; + pointer-events: none; + /* Disables all mouse interactions */ + opacity: 0.5; + /* Optional: Makes the button look visually disabled */ + } + } + } + } + + .sideBarHeader { + color: var(--accent-color); + border-bottom: 1px solid var(--border-color); + padding-bottom: 6px; + } + + .selectedMain-container { + display: flex; + flex-direction: column; + gap: 6px; + border-bottom: 1px solid var(--border-color); + padding-bottom: 10px; + + .selectedMain { + display: flex; + align-items: center; + gap: 6px; + + main { + width: 35%; + white-space: nowrap; + /* Prevent wrapping */ + } + + .icon { + padding: 0; + cursor: pointer; + } + + button { + background: transparent; + box-shadow: none; + color: #5273eb; + padding: 6px; + font-size: var(--font-size-xlarge); + } + + .bulletPoint { + color: #5273eb; + font-size: var(--font-size-large); + } + + .regularDropdown-container { + width: 100%; + } + + &:first-child { + gap: 4px; + } + } + } + + .child { + width: 100%; + gap: 6px; + } + + .infoBox { + display: flex; + align-items: flex-start; + gap: 6px; + color: #444; + border-radius: #{$border-radius-medium}; + font-size: var(--font-weight-regular); + + .infoIcon { + padding: 0px 7px; + border-radius: #{$border-radius-circle}; + + border: 1px solid gray; + } + + p { + margin: 0; + } + } + } + + .design { + width: 100%; + display: flex; + flex-direction: column; + gap: 15px; + font-size: var(--font-weight-regular); + color: var(--text-color); + padding: 12px; + + .appearance-container, + .element-container { + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 15px; + outline: 1px solid var(--border-color); + + padding: 10px; + + display: flex; + flex-direction: column; + gap: 12px; + + .header-container { + padding: 0; + height: auto; + } + + .appearance-style { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: column; + gap: 12px; + + .regularDropdown-container { + .dropdown-options { + width: 130%; + left: -15%; + } + + .dropdown-header { + gap: 12px; + } + } + + .color-wrapper, + .opacity-wrapper, + .blurEffect-wrapper, + .theme-wrapper { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + + .input-range-container { + width: 100%; + padding: 0; + } + } + + .theme-wrapper { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + } + + .color-wrapper { + flex-direction: row; + + .value { + display: flex; + align-items: center; + + input { + width: 34px; + height: 24px; + border-radius: 12px; + padding: 0; + } + } + } + } + } + + .element-container { + padding: 8px; + + .display-element { + width: 100%; + height: 150px; + @include flex-center; + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 5px; + outline: 1px solid var(--border-color); + } + + .name-wrapper, + .element-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + + .value { + width: 60%; + } + } + } + } + + .reviewChart { + width: 100%; + height: 150px; + background: var(--background-color); + display: flex; + align-items: center; + } + + .optionsContainer { + display: flex; + flex-direction: column; + gap: 10px; + padding: 0 12px; + + .option { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + + .regularDropdown-container, + input { + width: 160px; + } + + &:last-child { + flex-direction: column; + + .header { + width: 100%; + display: flex; + justify-content: space-between; + } + + .colorDisplayer { + width: 100%; + display: flex; + justify-content: start; + align-items: center; + + input[type="color"] { + border: none; + outline: none; + background: none; + width: 24px; + height: 26px; + border-radius: #{$border-radius-small}; + padding: 0; + } + } + } + + span { + min-width: 100px; + } + } + } + } + } + + .machine-mechanics-container { + .machine-mechanics-header { + padding: 8px 12px; + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); + color: var(--accent-color); + } + + .process-list-container { + display: flex; + align-items: center; + gap: 4px; + padding: 8px; + border-bottom: 1px solid var(--border-color); + + .label { + margin-right: 8px; + } + + .add-new-process { + @include flex-center; + height: 24px; + min-width: 24px; + cursor: pointer; + background: var(--background-color-secondary); + border-radius: #{$border-radius-medium}; + + path { + stroke: var(--accent-color); + stroke-width: 1.5px; + } + + &:hover { + background: var(--accent-color); + + path { + stroke: var(--highlight-accent-color); + } + } + } + } + } + + .machine-mechanics-content-container, + .simulations-container, + .event-proprties-wrapper { + position: relative; + max-height: calc(62vh - (47px - 35px)); + width: 304px; + border-radius: #{$border-radius-large}; + overflow-x: hidden; + + .header { + @include flex-space-between; + padding: 6px 12px; + padding-right: 6px; + text-transform: capitalize; + + .add-button { + @include flex-center; + padding: 4px 8px; + background: var(--background-color-button); + color: var(--text-button-color); + border-radius: #{$border-radius-large}; + cursor: pointer; + outline: none; + border: none; + + path { + stroke: var(--text-button-color); + } + + &:disabled { + background: var(--text-disabled); + cursor: not-allowed; + } + } + } + + .global-props { + .property-list-container { + .property-item { + .value-field-container { + margin: 0; + + input { + padding: 5px 10px; + } + + .dropdown { + top: 4px; + right: 4px; + } + } + } + } + } + + .selected-actions-details { + .selected-actions-header .input-value { + padding: 8px 12px; + color: var(--accent-color); + } + + .selected-actions-list { + margin-bottom: 8px; + + .eye-dropper-input-container { + padding: 6px 12px; + + .regularDropdown-container { + padding: 5px 8px; + outline: 1px solid var(--border-color); + outline-offset: -1px; + border: none; + } } .value-field-container { - margin: 0; - } - - .presets-list-container { - display: flex; - flex-wrap: wrap; - gap: 6px; - padding: 6px; - padding-left: 7px; - - .preset-list { - background: #444; - height: 90px; - width: 90px; - border-radius: #{$border-radius-large}; - overflow: hidden; - - .thumbnail { - height: 100%; - width: 100%; - border-radius: #{$border-radius-large}; - outline-offset: -1px; - - img { - height: 100%; - width: 100%; - object-fit: cover; - transition: all 0.2s; - } - - &.selected { - outline: 2px solid var(--border-color-accent); - outline-offset: -2px; - - &:hover { - outline: 2px solid var(--border-color-accent); - - img { - transform: scale(1); - } - } - } - - &:hover { - outline: 1px solid var(--border-color); - - img { - transform: scale(1.1); - } - } - } - } + margin: 0; + + input { + padding: 5px 4px; + text-align: center; + } + + .dropdown { + top: 4px; + right: 4px; + } } + } } - .global-properties-container, - .analysis-main-container, - .asset-properties-container, - .zone-properties-container, - .aisle-properties-container { - .header { - @include flex-space-between; - padding: 10px 12px; - color: var(--text-color); - width: 100%; + .lists-main-container { + margin: 2px; + width: calc(100% - 4px); + background: var(--background-color-gray); + border-radius: #{$border-radius-medium}; + + min-height: 120px; + + .list-container { + padding: 4px; + height: calc(100% - 16px); + overflow-y: scroll; + + .list-item { + @include flex-space-between; + padding: 4px 12px; + width: 100%; + border-radius: #{$border-radius-medium}; + + .value { + display: flex; + justify-content: flex-start; + align-items: center; + min-width: 80%; + gap: 6px; .input-value { - color: inherit; + text-align: start; } - } - - .input-container { - @include flex-center; - - .remove-button { - @include flex-center; - height: 18px; - width: 18px; - margin-bottom: 6px; - border-radius: 8px 0 0 8px; - - &:hover { - background: var(--background-color-accent); - - path { - stroke: var(--text-button-color); - } - } - } - } - - .optimize-button, - .generate-report-button, - .button-save { - @include flex-center; - background: var(--background-color-button); - color: var(--text-button-color); - border-radius: #{$border-radius-large}; - padding: 2px; - gap: 4px; - margin: 4px 12px; - cursor: pointer; - font-size: var(--font-size-small); - margin-bottom: 8px; - } - - .custom-input-container { - @include flex-space-between; - - .split { - height: 20px; - width: 2px; - border-radius: 2px; - background: var(--text-disabled); - } - - .header { - @include flex-space-between; - border: none; - - .eyedrop-button { - @include flex-center; - } - } - - .inputs-container { - @include flex-space-between; - - .input-container { - padding: 0 4px; - gap: 6px; - } - } - - .custom-input-label { - white-space: nowrap; - } - } - - .analysis-content-container { - min-height: 48vh; - max-height: 56vh; - overflow-y: auto; - - .dropdown-header-container, - .dropdown-content-container { - padding: 6px 12px; - } - - .value-field-container { - padding: 6px; - - .dropdown { - min-width: 44px; - text-align: center; - } - } - - .input-range-container { - .input-container { - width: 75%; - } - } - } - - .buttons-container { - @include flex-space-between; - padding: 12px; - gap: 12px; input { - border: none; - outline: none; - cursor: pointer; - - &:hover { - box-shadow: #{$box-shadow-medium}; - outline: 1px solid var(--input-border-color); - } - } - - .cancel { - background: transparent; - background: var(--background-color-secondary); - color: var(--text-color); - } - - .submit { - background: var(--background-color-button); - color: var(--text-button-color); + width: fit-content; + outline: none; + accent-color: var(--accent-color); } + } } - .create-custom-analysis-container { - margin: 6px; - background: var(--background-color); - padding: 12px; - border-radius: #{$border-radius-large}; + .active { + background: var(--highlight-accent-color); - .custom-analysis-header { - font-weight: var(--font-weight-medium); - } + .value .input-value { + color: var(--accent-color); + } - .content { - padding: 12px 0; - font-size: var(--font-size-small); - - span { - font-size: inherit; - color: var(--accent-color); - } - } - - .input { - display: flex; - flex-direction: row-reverse; - - input { - width: fit-content; - background: var(--background-color-button); - color: var(--text-button-color); - padding: 3px 10px; - cursor: pointer; - border: none; - outline: none; - } - } + path { + stroke: var(--accent-color); + } } + + .remove-button { + @include flex-center; + height: 18px; + width: 18px; + cursor: pointer; + border-radius: #{$border-radius-small}; + transform: translateX(4px); + + &:hover { + background: var(--background-color-accent); + + path { + stroke: var(--text-button-color); + } + } + } + } + + .resize-icon { + @include flex-center; + padding: 4px; + cursor: grab; + width: 100%; + + &:active { + cursor: grabbing; + } + } } - .toggle-sidebar-ui-button { - svg { - transform: scaleX(-1); + .selected-properties-container { + padding: 12px; + + .properties-header { + color: var(--accent-color); + font-weight: var(--font-weight-regular); + padding: 8px 0; + } + + .input-toggle-container { + padding: 0; + margin-bottom: 6px; + } + + .value-field-container { + margin-bottom: 6px; + padding: 0; + @include flex-space-between; + + .label { + width: 40%; } - .tooltip { - right: 56px; - - &::after { - left: 100%; - bottom: 50%; - } + .regularDropdown-container { + width: 60%; } + + .default { + width: 60%; + } + } } + + .simulation-process { + .collapse-header-container { + @include flex-space-between; + padding-right: 12px; + margin: 8px 0; + width: 100%; + + .header { + color: var(--accent-color); + } + } + + .process-container { + padding: 0 12px; + margin: 6px 0; + padding-left: 16px; + position: relative; + + &::after { + content: "↶"; + rotate: -90deg; + transform: translate(-16px, 4px) scaleX(1); + height: 100%; + width: 1px; + position: absolute; + color: var(--accent-color); + font-size: var(--font-size-regular); + outline-offset: -1px; + top: 0; + left: 4px; + } + + &:last-child { + &::after { + display: none; + } + } + + .value { + @include flex-space-between; + + .arrow-container { + height: 16px; + width: 16px; + } + + .active { + rotate: 90deg; + } + } + + .children-drop { + .value { + padding: 6px; + border-left: 1px solid var(--border-color); + } + } + } + } + + .trigger-wrapper { + .trigger-item { + .trigger-name { + padding: 8px; + margin-top: 4px; + } + + .value-field-container { + margin: 0; + } + } + } + + .footer { + @include flex-center; + justify-content: flex-start; + gap: 4px; + padding: 12px; + font-size: var(--font-size-tiny); + } + + .compare-simulations-container { + background: var(--background-color); + padding: 12px; + border-radius: #{$border-radius-large}; + + .compare-simulations-header { + font-weight: var(--font-weight-medium); + } + + .content { + padding: 12px 0; + font-size: var(--font-size-small); + + span { + font-size: inherit; + color: var(--accent-color); + } + } + + .input { + width: 100%; + display: flex; + flex-direction: row-reverse; + justify-content: flex-start; + + input { + width: fit-content; + background: var(--background-color-button); + color: var(--text-button-color); + padding: 3px 10px; + cursor: pointer; + border: none; + outline: none; + } + } + } + } + + .aisle-properties-container { + max-height: 65vh; + overflow: auto; + + .aisle-texture-container { + max-height: 40vh; + overflow: auto; + + .aisle-list { + width: calc(100% - 8px); + text-align: start; + padding: 4px 6px; + display: flex; + align-items: center; + gap: 6px; + border-radius: #{$border-radius-large}; + margin: 2px 6px; + + .texture-display { + height: 34px; + width: 34px; + background: #7e7e7e86; + border-radius: #{$border-radius-large}; + margin-right: 4px; + overflow: hidden; + } + + .aisle-color { + text-transform: capitalize; + } + + .aisle-brief { + font-size: var(--font-size-small); + color: var(--input-text-color); + } + + &.selected { + background: var(--highlight-accent-color); + + // .aisle-color { + // color: var(--text-button-color); + // } + + // &:hover { + // background: var(--background-color-accent); + // } + } + + &:hover { + background: var(--highlight-accent-color); + // background: var(--background-color-secondary); + } + } + } + + .value-field-container { + margin: 0; + } + + .presets-list-container { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 6px; + padding-left: 7px; + + .preset-list { + background: #444; + height: 90px; + width: 90px; + border-radius: #{$border-radius-large}; + overflow: hidden; + + .thumbnail { + height: 100%; + width: 100%; + border-radius: #{$border-radius-large}; + outline-offset: -1px; + + img { + height: 100%; + width: 100%; + object-fit: cover; + transition: all 0.2s; + } + + &.selected { + outline: 2px solid var(--border-color-accent); + outline-offset: -2px; + + &:hover { + outline: 2px solid var(--border-color-accent); + + img { + transform: scale(1); + } + } + } + + &:hover { + outline: 1px solid var(--border-color); + + img { + transform: scale(1.1); + } + } + } + } + } + } + + .global-properties-container, + .analysis-main-container, + .asset-properties-container, + .zone-properties-container, + .aisle-properties-container { + .header { + @include flex-space-between; + padding: 10px 12px; + color: var(--text-color); + width: 100%; + + .input-value { + color: inherit; + } + } + + .animations-lists { + max-height: 210px; + overflow: auto; + .animations-list-wrapper { + padding: 0 4px; + .animations-list { + margin: 2px 0; + padding: 4px 12px; + border-radius: 8px; + } + } + } + + .input-container { + @include flex-center; + + .remove-button { + @include flex-center; + height: 18px; + width: 18px; + margin-bottom: 6px; + border-radius: 8px 0 0 8px; + + &:hover { + background: var(--background-color-accent); + + path { + stroke: var(--text-button-color); + } + } + } + } + + .optimize-button, + .generate-report-button, + .button-save { + @include flex-center; + background: var(--background-color-button); + color: var(--text-button-color); + border-radius: #{$border-radius-large}; + padding: 2px; + gap: 4px; + margin: 4px 12px; + cursor: pointer; + font-size: var(--font-size-small); + margin-bottom: 8px; + } + + .custom-input-container { + @include flex-space-between; + + .split { + height: 20px; + width: 2px; + border-radius: 2px; + background: var(--text-disabled); + } + + .header { + @include flex-space-between; + border: none; + + .eyedrop-button { + @include flex-center; + } + } + + .inputs-container { + @include flex-space-between; + + .input-container { + padding: 0 4px; + gap: 6px; + } + } + + .custom-input-label { + white-space: nowrap; + } + } + + .analysis-content-container { + min-height: 48vh; + max-height: 56vh; + overflow-y: auto; + + .dropdown-header-container, + .dropdown-content-container { + padding: 6px 12px; + } + + .value-field-container { + padding: 6px; + + .dropdown { + min-width: 44px; + text-align: center; + } + } + + .input-range-container { + .input-container { + width: 75%; + } + } + } + + .buttons-container { + @include flex-space-between; + padding: 12px; + gap: 12px; + + input { + border: none; + outline: none; + cursor: pointer; + + &:hover { + box-shadow: #{$box-shadow-medium}; + outline: 1px solid var(--input-border-color); + } + } + + .cancel { + background: transparent; + background: var(--background-color-secondary); + color: var(--text-color); + } + + .submit { + background: var(--background-color-button); + color: var(--text-button-color); + } + } + + .create-custom-analysis-container { + margin: 6px; + background: var(--background-color); + padding: 12px; + border-radius: #{$border-radius-large}; + + .custom-analysis-header { + font-weight: var(--font-weight-medium); + } + + .content { + padding: 12px 0; + font-size: var(--font-size-small); + + span { + font-size: inherit; + color: var(--accent-color); + } + } + + .input { + display: flex; + flex-direction: row-reverse; + + input { + width: fit-content; + background: var(--background-color-button); + color: var(--text-button-color); + padding: 3px 10px; + cursor: pointer; + border: none; + outline: none; + } + } + } + } + + .toggle-sidebar-ui-button { + svg { + transform: scaleX(-1); + } + + .tooltip { + right: 56px; + + &::after { + left: 100%; + bottom: 50%; + } + } + } } .sidebar-right-wrapper { - .wall-properties-container { - .wall-properties-section { - padding: 14px; - padding-bottom: 0; - margin-bottom: 8px; - } - - .header { - color: var(--text-color); - } - - .wall-properties { - padding: 12px 0; - - .value-field-container { - padding: 0; - - .input { - input { - text-align: center; - } - } - } - } - - section { - padding: 0; - margin: 0; - max-height: 50vh; - overflow: hidden; - - .header-wrapper { - display: flex; - justify-content: space-between; - padding: 12px; - } - - .material-preview { - display: flex; - flex-direction: column; - align-items: center; - gap: 15px; - background: var(--background-color-secondary); - padding: 18px 25px; - - .sides-wrapper { - display: flex; - justify-content: space-between; - width: 100%; - background-color: #fcfdfd; - border-radius: 4px; - overflow: hidden; - - .side-wrapper { - flex: 1; - display: flex; - justify-content: center; - align-items: center; - gap: 8px; - padding: 8px 0; - cursor: pointer; - - .label { - color: var(--background-color-button); - } - - &.active { - background-color: #e0dfff; - } - - .texture-image { - width: 20px; - height: 20px; - border-radius: 50%; - overflow: hidden; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - } - } - } - - .preview { - width: 100%; - height: 111px; - border-radius: 20px; - overflow: hidden; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - } - } - - .materials { - max-height: 250px; - overflow: auto; - margin-top: 12px; - margin-bottom: 16px; - padding: 0 12px; - - .material-container { - display: flex; - flex-direction: column; - gap: 6px; - - .material-wrapper { - display: flex; - justify-content: space-between; - align-items: center; - padding: 4px 6px; - border-radius: 12px; - - &:hover { - background: var(--highlight-accent-color); - } - - &.selectedMaterial { - background: var(--highlight-accent-color); - } - - .material-property { - display: flex; - align-items: center; - gap: 6px; - - .material-image { - width: 34px; - height: 34px; - border-radius: 6px; - overflow: hidden; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - } - } - - .delete-material { - cursor: pointer; - } - } - } - } - } + .wall-properties-container { + .wall-properties-section { + padding: 14px; + padding-bottom: 0; + margin-bottom: 8px; } + + .header { + color: var(--text-color); + } + + .wall-properties { + padding: 12px 0; + + .value-field-container { + padding: 0; + + .input { + input { + text-align: center; + } + } + } + } + + section { + padding: 0; + margin: 0; + max-height: 50vh; + overflow: hidden; + + .header-wrapper { + display: flex; + justify-content: space-between; + padding: 12px; + } + + .material-preview { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + background: var(--background-color-secondary); + padding: 18px 25px; + + .sides-wrapper { + display: flex; + justify-content: space-between; + width: 100%; + background-color: #fcfdfd; + border-radius: 4px; + overflow: hidden; + + .side-wrapper { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + padding: 8px 0; + cursor: pointer; + + .label { + color: var(--background-color-button); + } + + &.active { + background-color: #e0dfff; + } + + .texture-image { + width: 20px; + height: 20px; + border-radius: 50%; + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + } + } + + .preview { + width: 100%; + height: 111px; + border-radius: 20px; + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + } + + .materials { + max-height: 250px; + overflow: auto; + margin-top: 12px; + margin-bottom: 16px; + padding: 0 12px; + + .material-container { + display: flex; + flex-direction: column; + gap: 6px; + + .material-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 6px; + border-radius: 12px; + + &:hover { + background: var(--highlight-accent-color); + } + + &.selectedMaterial { + background: var(--highlight-accent-color); + } + + .material-property { + display: flex; + align-items: center; + gap: 6px; + + .material-image { + width: 34px; + height: 34px; + border-radius: 6px; + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + } + + .delete-material { + cursor: pointer; + } + } + } + } + } + } } .assets-container-main { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + height: 100%; + gap: 3px; + + .assets-result { width: 100%; - display: flex; - flex-direction: row; - flex-wrap: wrap; - height: 100%; - gap: 3px; - - .assets-result { - width: 100%; - - .assets-wrapper { - margin: 0; - } - } - - .assets-list-section { - width: 100%; - padding: 4px; - } .assets-wrapper { - width: 100%; - position: relative; - max-height: 50vh; - overflow: auto; - - h2, - .searched-content { - color: var(--text-color); - font-family: $large; - font-weight: $bold-weight; - padding: 8px; - @include flex-space-between; - - .back-button { - cursor: pointer; - } - } - - .categories-container { - width: 100%; - display: flex; - flex-direction: row; - flex-wrap: wrap; - height: 100%; - gap: 4px; - padding: 2px; - - .category { - width: 123px; - height: 95px; - border-radius: #{$border-radius-large}; - background: var(--background-color); - outline: 1px solid var(--border-color); - outline-offset: -1px; - padding: 8px; - padding-top: 12px; - font-weight: $bold-weight; - position: relative; - overflow: hidden; - - &:hover { - outline: 1px solid var(--border-color-accent); - cursor: pointer; - - img { - transition: all 0.2s; - scale: 1.3; - } - - &::after { - top: 80px; - right: 0; - scale: 2; - } - } - - .category-name { - position: relative; - z-index: 3; - font-size: var(--font-size-regular); - } - - &::after { - content: ""; - width: 60px; - height: 60px; - border-radius: #{$border-radius-circle}; - background: var(--circle-color, #000); - position: absolute; - top: 60%; - right: -10px; - transform: translate(0, -50%); - transition: all 0.2s ease-in-out; - } - - &:nth-child(1), - &:nth-child(8) { - &::after { - @include gradient-by-child(1); // First child uses the first color - } - } - - &:nth-child(2) { - &::after { - - // @include gradient-by-child(4); // Second child uses the second color - background: linear-gradient(144.19deg, rgba(197, 137, 26, 0.7) 16.62%, rgba(69, 48, 10, 0.7) 85.81%); - - } - - } - - &:nth-child(3), - &:nth-child(9) { - &::after { - @include gradient-by-child(3); // Second child uses the second color - } - } - - &:nth-child(3), - &:nth-child(10) { - &::after { - @include gradient-by-child(3); // Third child uses the third color - } - } - - &:nth-child(4), - &:nth-child(11) { - &::after { - @include gradient-by-child(4); // Fourth child uses the fourth color - } - } - - &:nth-child(5), - &:nth-child(12) { - &::after { - @include gradient-by-child(5); // Fifth child uses the fifth color - } - } - - &:nth-child(6), - &:nth-child(13) { - &::after { - @include gradient-by-child(6); // Fifth child uses the fifth color - } - } - - &:nth-child(7), - &:nth-child(14) { - &::after { - @include gradient-by-child(7); // Fifth child uses the fifth color - } - } - - .category-image { - position: absolute; - bottom: 0; - right: -10px; - transform: translate(0, 0%) scale(0.8); - z-index: 2; - height: 80%; - } - } - } - - .catogory-asset-filter { - display: flex; - flex-wrap: wrap; - gap: 12px; - border: 1px solid #564B69; - padding: 12px 10px; - border-radius: 15px; - - .catogory-asset-filter-wrapper { - display: flex; - align-items: center; - gap: 2px; - border: 1px solid #564B69; - padding: 4px 8px; - border-radius: 100px; - cursor: pointer; - - .sub-catagory { - display: flex; - } - - &.active { - background-color: #6F42C1; - - .sub-catagory { - - color: #FFFFFF; - } - - // svg { - // stroke: white; - // fill: white; - // } - } - } - } - - .assets-container { - width: 100%; - display: flex; - flex-direction: row; - flex-wrap: wrap; - height: 100%; - gap: 6px; - padding: 2px; - - .no-asset { - text-align: center; - margin: 12px; - width: 100%; - } - - .assets { - width: 122px; - height: 95px; - border-radius: #{$border-radius-large}; - background: var(--background-color); - outline: 1px solid var(--border-color); - font-weight: $medium-weight; - position: relative; - overflow: hidden; - padding: 0; - - &:hover { - .asset-name { - opacity: 1; - } - - .asset-image { - scale: 1.2; - } - } - - .asset-name { - position: absolute; - top: 0; - z-index: 3; - padding: 8px; - width: 100%; - height: 100%; - font-size: var(--font-size-regular); - background: linear-gradient(0deg, - rgba(37, 24, 51, 0) 0%, - rgba(52, 41, 61, 0.5) 100%); - pointer-events: none; - backdrop-filter: blur(8px); - opacity: 0; - transition: opacity 0.3s ease; - display: -webkit-box; - -webkit-line-clamp: 3; - line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - } - - .asset-image { - height: 100%; - width: 100%; - z-index: 2; - object-fit: cover; - transition: all 0.2s; - } - } - } + margin: 0; } + } + + .assets-list-section { + width: 100%; + padding: 4px; + } + + .assets-wrapper { + width: 100%; + position: relative; + max-height: 50vh; + overflow: auto; + + h2, + .searched-content { + color: var(--text-color); + font-family: $large; + font-weight: $bold-weight; + padding: 8px; + @include flex-space-between; + + .back-button { + cursor: pointer; + } + } + + .categories-container { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + height: 100%; + gap: 4px; + padding: 2px; + + .category { + width: 123px; + height: 95px; + border-radius: #{$border-radius-large}; + background: var(--background-color); + outline: 1px solid var(--border-color); + outline-offset: -1px; + padding: 8px; + padding-top: 12px; + font-weight: $bold-weight; + position: relative; + overflow: hidden; + + &:hover { + outline: 1px solid var(--border-color-accent); + cursor: pointer; + + img { + transition: all 0.2s; + scale: 1.3; + } + + &::after { + top: 80px; + right: 0; + scale: 2; + } + } + + .category-name { + position: relative; + z-index: 3; + font-size: var(--font-size-regular); + } + + &::after { + content: ""; + width: 60px; + height: 60px; + border-radius: #{$border-radius-circle}; + background: var(--circle-color, #000); + position: absolute; + top: 60%; + right: -10px; + transform: translate(0, -50%); + transition: all 0.2s ease-in-out; + } + + &:nth-child(1), + &:nth-child(8) { + &::after { + @include gradient-by-child(1); // First child uses the first color + } + } + + &:nth-child(2) { + &::after { + // @include gradient-by-child(4); // Second child uses the second color + background: linear-gradient( + 144.19deg, + rgba(197, 137, 26, 0.5) 16.62%, + rgba(69, 48, 10, 0.5) 85.81% + ); + } + } + + &:nth-child(3), + &:nth-child(9) { + &::after { + @include gradient-by-child(3); // Second child uses the second color + } + } + + &:nth-child(3), + &:nth-child(10) { + &::after { + @include gradient-by-child(3); // Third child uses the third color + } + } + + &:nth-child(4), + &:nth-child(11) { + &::after { + @include gradient-by-child(4); // Fourth child uses the fourth color + } + } + + &:nth-child(5), + &:nth-child(12) { + &::after { + @include gradient-by-child(5); // Fifth child uses the fifth color + } + } + + &:nth-child(6), + &:nth-child(13) { + &::after { + @include gradient-by-child(6); // Fifth child uses the fifth color + } + } + + &:nth-child(7), + &:nth-child(14) { + &::after { + @include gradient-by-child(7); // Fifth child uses the fifth color + } + } + + .category-image { + position: absolute; + bottom: 0; + right: -10px; + transform: translate(0, 0%) scale(0.8); + z-index: 2; + height: 80%; + } + } + } + + .catogory-asset-filter { + display: flex; + flex-wrap: wrap; + gap: 12px; + border: 1px solid #564b69; + padding: 12px 10px; + border-radius: 15px; + + .catogory-asset-filter-wrapper { + display: flex; + align-items: center; + gap: 2px; + border: 1px solid #564b69; + padding: 4px 8px; + border-radius: 100px; + cursor: pointer; + + .sub-catagory { + display: flex; + } + + &.active { + background-color: #6f42c1; + + .sub-catagory { + color: #ffffff; + } + + // svg { + // stroke: white; + // fill: white; + // } + } + } + } + + .assets-container { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + height: 100%; + gap: 6px; + padding: 2px; + + .no-asset { + text-align: center; + margin: 12px; + width: 100%; + } + + .assets { + width: 122px; + height: 95px; + border-radius: #{$border-radius-large}; + background: var(--background-color); + outline: 1px solid var(--border-color); + font-weight: $medium-weight; + position: relative; + overflow: hidden; + padding: 0; + + &:hover { + .asset-name { + opacity: 1; + } + + .asset-image { + scale: 1.2; + } + } + + .asset-name { + position: absolute; + top: 0; + z-index: 3; + padding: 8px; + width: 100%; + height: 100%; + font-size: var(--font-size-regular); + background: linear-gradient( + 0deg, + rgba(37, 24, 51, 0) 0%, + rgba(52, 41, 61, 0.5) 100% + ); + pointer-events: none; + backdrop-filter: blur(8px); + opacity: 0; + transition: opacity 0.3s ease; + display: -webkit-box; + -webkit-line-clamp: 3; + line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .asset-image { + height: 100%; + width: 100%; + z-index: 2; + object-fit: cover; + transition: all 0.2s; + } + } + } + } } .sidebar-left-wrapper, .sidebar-right-wrapper { - transition: height 0.2s ease-in-out; + transition: height 0.2s ease-in-out; } .sidebar-left-wrapper.closed, .sidebar-right-wrapper.closed { - animation: closeSidebar 0.2s linear forwards; + animation: closeSidebar 0.2s linear forwards; } .sidebar-left-wrapper.open, .sidebar-right-wrapper.open { - height: fit-content; - animation: openSidebar 0.2s linear; + height: fit-content; + animation: openSidebar 0.2s linear; - .sidebar-right-container, - .sidebar-left-container { - opacity: 0; - animation: revealSmooth 0.3s 0.1s linear forwards; - } + .sidebar-right-container, + .sidebar-left-container { + opacity: 0; + animation: revealSmooth 0.3s 0.1s linear forwards; + } } @keyframes revealSmooth { - from { - opacity: 0; - } + from { + opacity: 0; + } - to { - opacity: 1; - } + to { + opacity: 1; + } } @keyframes closeSidebar { - from { - height: 60%; - } + from { + height: 60%; + } - to { - height: 52px; - } + to { + height: 52px; + } } @keyframes openSidebar { - from { - height: 52px; - } + from { + height: 52px; + } - to { - height: 60%; - } + to { + height: 60%; + } } .versionSaved { - min-width: 449px; - position: fixed; - bottom: 45px; - right: 10px; - z-index: 10; + min-width: 449px; + position: fixed; + bottom: 45px; + right: 10px; + z-index: 10; + display: flex; + flex-direction: column; + gap: 18px; + + .versionSaved-wrapper { + border-radius: 20px; + + padding: 8px 10px; + background: var(--background-color); + backdrop-filter: blur(20px); + } + + .version-header { display: flex; + justify-content: space-between; + align-items: center; + + .header-wrapper { + display: flex; + align-items: center; + gap: 6px; + } + } + + .version-details { + width: 100%; + + display: flex; + justify-content: center; + align-items: center; flex-direction: column; gap: 18px; - .versionSaved-wrapper { - border-radius: 20px; + .details { + width: 100%; - padding: 8px 10px; - background: var(--background-color); - backdrop-filter: blur(20px); - } + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 20px; + outline: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + padding: 7px 12px; - .version-header { - display: flex; - justify-content: space-between; - align-items: center; - - .header-wrapper { - display: flex; - align-items: center; - gap: 6px; - } - } - - .version-details { - width: 100%; + .details-wrapper { + font-size: var(--font-size-small); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + button { + font-size: var(--font-size-small); display: flex; justify-content: center; align-items: center; - flex-direction: column; - gap: 18px; - - .details { - width: 100%; - - background: var(--background-color); - backdrop-filter: blur(20px); - border-radius: 20px; - outline: 1px solid var(--border-color); - display: flex; - justify-content: space-between; - align-items: center; - padding: 7px 12px; - - .details-wrapper { - font-size: var(--font-size-small); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - button { - font-size: var(--font-size-small); - display: flex; - justify-content: center; - align-items: center; - background: var(--background-color-button); - color: var(--text-button-color); - border-radius: 12px; - padding: 5px 6px; - cursor: pointer; - } - } + background: var(--background-color-button); + color: var(--text-button-color); + border-radius: 12px; + padding: 5px 6px; + cursor: pointer; + } } + } } .dismissing { - display: none; + display: none; } .edit-version-popup-wrapper, .finishEdit-version-popup-wrapper { - height: 100vh; - width: 100vw; - background: var(--background-color-secondary); - @include flex-center; + height: 100vh; + width: 100vw; + background: var(--background-color-secondary); + @include flex-center; - .details-wrapper-popup-container, - .finishEdit-wrapper-popup-container { - min-width: 535px; - width: 520px; - background: var(--background-color); - border-radius: #{$border-radius-large}; - backdrop-filter: blur(15px); - outline: 1px solid var(--border-color); - display: flex; - flex-direction: column; - gap: 30px; - padding: 20px; + .details-wrapper-popup-container, + .finishEdit-wrapper-popup-container { + min-width: 535px; + width: 520px; + background: var(--background-color); + border-radius: #{$border-radius-large}; + backdrop-filter: blur(15px); + outline: 1px solid var(--border-color); + display: flex; + flex-direction: column; + gap: 30px; + padding: 20px; - .header-wrapper { - display: flex; - align-items: center; - gap: 6px; - } - - .details-wrapper { - display: flex; - flex-direction: column; - gap: 10px; - - .version-name, - .version-description { - background: var(--background-color); - backdrop-filter: blur(20px); - border-radius: 20px; - outline: 1px solid var(--border-color); - display: flex; - justify-content: space-between; - align-items: center; - padding: 4px 12px; - position: relative; - - .label { - position: absolute; - right: 8px; - font-size: var(--font-size-tiny); - color: var(--text-disabled); - } - - input { - border: none; - background: none; - outline: none; - color: var(--text-color); - } - } - - .version-description { - textarea { - padding: 4px 8px; - width: 100%; - min-height: 101px; - background: transparent; - border: none; - outline: none; - } - } - } - - .btn-wrapper { - display: flex; - justify-content: flex-end; - gap: 20px; - - .save { - display: flex; - justify-content: center; - align-items: center; - background: var(--background-color-button); - color: var(--text-button-color); - border-radius: 12px; - padding: 5px 12px; - cursor: pointer; - } - } + .header-wrapper { + display: flex; + align-items: center; + gap: 6px; } - .finishEdit-wrapper-popup-container { - min-height: 250px; + .details-wrapper { + display: flex; + flex-direction: column; + gap: 10px; + + .version-name, + .version-description { + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 20px; + outline: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 12px; + position: relative; + + .label { + position: absolute; + right: 8px; + font-size: var(--font-size-tiny); + color: var(--text-disabled); + } + + input { + border: none; + background: none; + outline: none; + color: var(--text-color); + } + } + + .version-description { + textarea { + padding: 4px 8px; + width: 100%; + min-height: 101px; + background: transparent; + border: none; + outline: none; + } + } + } + + .btn-wrapper { + display: flex; + justify-content: flex-end; + gap: 20px; + + .save { display: flex; justify-content: center; align-items: center; - gap: 12px; - - .versionname { - font-size: var(--font-size-large); - color: var(--background-color-accent); - color: #ccacff; - text-transform: capitalize; - } + background: var(--background-color-button); + color: var(--text-button-color); + border-radius: 12px; + padding: 5px 12px; + cursor: pointer; + } } -} \ No newline at end of file + } + + .finishEdit-wrapper-popup-container { + min-height: 250px; + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + + .versionname { + font-size: var(--font-size-large); + color: var(--background-color-accent); + color: #ccacff; + text-transform: capitalize; + } + } +} diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index ae6c55a..1578b5a 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -90,8 +90,6 @@ interface StorageAction { actionUuid: string; actionName: string; actionType: "store" | "retrieve"; - materialType?: string; - storageCapacity: number; triggers: TriggerSchema[]; } @@ -155,7 +153,7 @@ interface StoragePointSchema { uuid: string; position: [number, number, number]; rotation: [number, number, number]; - action: StorageAction; + actions: StorageAction[]; } interface HumanPointSchema { @@ -207,6 +205,9 @@ interface MachineEventSchema extends AssetEventSchema { interface StorageEventSchema extends AssetEventSchema { type: "storageUnit"; subType: string; + storageCapacity: number; + storageCount: number; + materialType: string; point: StoragePointSchema; } @@ -455,7 +456,6 @@ type ConveyorPoints = NormalConveyor | YJunctionConveyor | CurvedConveyor; // Crane Constraints - type PillarJibCrane = { trolleySpeed: number; hookSpeed: number;