diff --git a/app/src/components/footer/Footer.tsx b/app/src/components/footer/Footer.tsx index c890087..0ee70c5 100644 --- a/app/src/components/footer/Footer.tsx +++ b/app/src/components/footer/Footer.tsx @@ -10,7 +10,6 @@ import { import ShortcutHelper from "./shortcutHelper"; import { useShortcutStore } from "../../store/builder/store"; import { usePlayButtonStore } from "../../store/usePlayButtonStore"; -import OuterClick from "../../utils/outerClick"; const Footer: React.FC = () => { const { logs, setIsLogListVisible } = useLogger(); @@ -19,11 +18,6 @@ const Footer: React.FC = () => { const { isPlaying } = usePlayButtonStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore(); - OuterClick({ - contextClassName: ["shortcut-helper-overlay"], - setMenuVisible: () => setShowShortcuts(false), - }); - return (
@@ -53,6 +47,7 @@ const Footer: React.FC = () => {
)}
diff --git a/app/src/components/footer/shortcutHelper.tsx b/app/src/components/footer/shortcutHelper.tsx index 8a15b2e..aa927c5 100644 --- a/app/src/components/footer/shortcutHelper.tsx +++ b/app/src/components/footer/shortcutHelper.tsx @@ -29,8 +29,8 @@ import { DublicateIcon, DuplicateInstanceIcon, PlayIcon, - BrowserIcon, } from "../icons/ShortcutIcons"; +import { CloseIcon } from "../icons/ExportCommonIcons"; interface ShortcutItem { keys: string[]; @@ -44,7 +44,13 @@ interface ShortcutGroup { items: ShortcutItem[]; } -const ShortcutHelper = () => { +interface ShortcutHelperProps { + setShowShortcuts: (value: boolean) => void; +} + +const ShortcutHelper: React.FC = ({ + setShowShortcuts, +}) => { const shortcuts: ShortcutGroup[] = [ { category: "Essential", @@ -256,31 +262,31 @@ const ShortcutHelper = () => { }, ], }, - { - category: "Miscellaneous", - items: [ - { - keys: ["F5", "F11", "F12", "CTRL", "+", "R"], - name: "Browser Defaults", - description: "Reserved for browser defaults", - icon: , - }, - ], - }, ]; const [activeCategory, setActiveCategory] = React.useState("Essential"); const activeShortcuts = - shortcuts.find((group) => group.category === activeCategory)?.items || []; + shortcuts.find((group) => group.category === activeCategory)?.items ?? []; return (
+
{shortcuts.map((group) => ( -
diff --git a/app/src/components/layout/controls/ControlsPlayer.tsx b/app/src/components/layout/controls/ControlsPlayer.tsx new file mode 100644 index 0000000..724c3bb --- /dev/null +++ b/app/src/components/layout/controls/ControlsPlayer.tsx @@ -0,0 +1,88 @@ +import React, { useEffect, useState } from "react"; +import useCameraModeStore, { + usePlayButtonStore, +} from "../../../store/usePlayButtonStore"; +import useModuleStore from "../../../store/useModuleStore"; +import { PlayIcon } from "../../icons/ShortcutIcons"; +import InputToggle from "../../ui/inputs/InputToggle"; +import { EyeCloseIcon, WalkIcon } from "../../icons/ExportCommonIcons"; +import { ExitIcon } from "../../icons/SimulationIcons"; +import { useCamMode } from "../../../store/builder/store"; + +const ControlsPlayer: React.FC = () => { + const { setIsPlaying } = usePlayButtonStore(); + const { activeModule } = useModuleStore(); + const { walkMode, toggleWalkMode } = useCameraModeStore(); + const [hidePlayer, setHidePlayer] = useState(false); + const { camMode } = useCamMode(); + + const changeCamMode = () => { + toggleWalkMode(); + echo.log("switch camera mode to first person"); + // Simulate "/" keypress + const slashKeyEvent = new KeyboardEvent("keydown", { + key: "/", + code: "Slash", + keyCode: 191, // for compatibility + which: 191, + bubbles: true, + cancelable: true, + }); + document.dispatchEvent(slashKeyEvent); + }; + useEffect(() => { + if (camMode === "ThirdPerson") { + toggleWalkMode(); + } else return; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [camMode]); + + return ( +
+ {!hidePlayer && ( +
+ +
Running {activeModule}...
+
+ )} + +
+ {!hidePlayer && activeModule === "builder" && ( +
+ + +
+ )} + + +
+
+ ); +}; + +export default ControlsPlayer; diff --git a/app/src/components/layout/sidebarLeft/Header.tsx b/app/src/components/layout/sidebarLeft/Header.tsx index f26f4fa..45df499 100644 --- a/app/src/components/layout/sidebarLeft/Header.tsx +++ b/app/src/components/layout/sidebarLeft/Header.tsx @@ -20,6 +20,7 @@ const Header: React.FC = () => {
)} {/* simulation */} - {toggleUIRight && !viewVersionHistory && activeModule === "simulation" && ( - <> - {subModule === "simulations" && ( -
-
- + {toggleUIRight && + !viewVersionHistory && + activeModule === "simulation" && ( + <> + {subModule === "simulations" && ( +
+
+ +
-
- )} - {subModule === "mechanics" && ( -
-
- + )} + {subModule === "mechanics" && ( +
+
+ +
-
- )} - {subModule === "analysis" && ( -
-
- + )} + {subModule === "analysis" && ( +
+
+ +
-
- )} - - )} + )} + + )} {/* realtime visualization */} {toggleUIRight && activeModule === "visualization" && }
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index 053fc14..b9550ae 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -94,6 +94,7 @@ const EventProperties: React.FC = () => {
{products.map((product) => ( -
-
-
- {multipleAction && selectedPointData && - selectedPointData.actions.map((action: any) => ( -
- - {selectedPointData.actions.length > 1 && ( - - )} -
- ))} - {!multipleAction && selectedPointData && ( -
- -
- )} -
- {multipleAction && ( - - )} -
-
-
+ const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + const event = renameAction( + selectedProduct.productId, + selectedAction.actionId, + newName ); + setSelectedAction(selectedAction.actionId, newName); + if (event) { + upsertProductOrEventApi({ + productName: selectedProduct.productName, + productId: selectedProduct.productId, + organization: organization, + eventDatas: event, + }); + } + }; + + const handleActionSelect = (actionUuid: string, actionName: string) => { + setSelectedAction(actionUuid, actionName); + }; + + return ( +
+
+
+
Actions
+ + +
+
+
+ {multipleAction && + selectedPointData?.actions?.map((action: any) => ( +
+ + {selectedPointData?.actions?.length > 1 && ( + + )} +
+ ))} + {!multipleAction && selectedPointData?.action && ( +
+ +
+ )} +
+ {multipleAction && ( + + )} +
+
+
+ ); }; export default ActionsList; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index da36384..b81dbf6 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -1,395 +1,512 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { - AddIcon, - RemoveIcon, - ResizeHeightIcon, + AddIcon, + RemoveIcon, + ResizeHeightIcon, } from "../../../../../icons/ExportCommonIcons"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../../../functions/handleResizePannel"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; -import { useSelectedAction, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { + useSelectedAction, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; type TriggerProps = { - selectedPointData?: PointsScheme | undefined; - type?: 'Conveyor' | 'Vehicle' | 'RoboticArm' | 'Machine' | 'StorageUnit'; + selectedPointData?: PointsScheme | undefined; + type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit"; }; const Trigger = ({ selectedPointData, type }: TriggerProps) => { - const [currentAction, setCurrentAction] = useState(); - const { selectedProduct } = useSelectedProduct(); - const { getActionByUuid, getEventByModelUuid, getPointByUuid, getTriggerByUuid, addTrigger, removeTrigger, updateTrigger, renameTrigger, getProductById } = useProductStore(); - const [triggers, setTriggers] = useState([]); - const [selectedTrigger, setSelectedTrigger] = useState(); - const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete"); - const triggersContainerRef = useRef(null); - const { selectedAction } = useSelectedAction(); + const [currentAction, setCurrentAction] = useState(); + const { selectedProduct } = useSelectedProduct(); + const { + getActionByUuid, + getEventByModelUuid, + getPointByUuid, + getTriggerByUuid, + addTrigger, + removeTrigger, + updateTrigger, + renameTrigger, + getProductById, + } = useProductStore(); + const [triggers, setTriggers] = useState([]); + const [selectedTrigger, setSelectedTrigger] = useState< + TriggerSchema | undefined + >(); + const [activeOption, setActiveOption] = useState< + "onComplete" | "onStart" | "onStop" | "delay" | "onError" + >("onComplete"); + const triggersContainerRef = useRef(null); + const { selectedAction } = useSelectedAction(); - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; - useEffect(() => { - if (!selectedPointData || !selectedProduct) return; + useEffect(() => { + if (!selectedPointData || !selectedProduct) return; - let actionUuid: string | undefined; + let actionUuid: string | undefined; - if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') { - actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid; - } else if (type === 'RoboticArm' && selectedAction && selectedAction.actionId) { - actionUuid = selectedAction.actionId; - } - - setCurrentAction(actionUuid); - }, [selectedPointData, selectedProduct, type, selectedAction]); - - const updateBackend = ( - productName: string, - productId: string, - organization: string, - eventData: EventsSchema - ) => { - upsertProductOrEventApi({ - productName: productName, - productId: productId, - organization: organization, - eventDatas: eventData - }) + if ( + type === "Conveyor" || + type === "Vehicle" || + type === "Machine" || + type === "StorageUnit" + ) { + actionUuid = ( + selectedPointData as + | ConveyorPointSchema + | VehiclePointSchema + | MachinePointSchema + | StoragePointSchema + ).action?.actionUuid; + } else if ( + type === "RoboticArm" && + selectedAction && + selectedAction.actionId + ) { + actionUuid = selectedAction.actionId; } - useEffect(() => { - if (!currentAction || !selectedProduct) return; - const action = getActionByUuid(selectedProduct.productId, currentAction); - const actionTriggers = action?.triggers || []; - setTriggers(actionTriggers); - setSelectedTrigger(actionTriggers[0]); - }, [currentAction, selectedProduct]); + setCurrentAction(actionUuid); + }, [selectedPointData, selectedProduct, type, selectedAction]); - const triggeredModel = useMemo(() => { - if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredModel?.modelUuid) return undefined; - return getEventByModelUuid(selectedProduct.productId, selectedTrigger.triggeredAsset.triggeredModel.modelUuid); - }, [selectedProduct, selectedTrigger]); + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData, + }); + }; - const triggeredPoint = useMemo(() => { - if (!selectedProduct || !triggeredModel || !selectedTrigger?.triggeredAsset?.triggeredPoint?.pointUuid) return undefined; - return getPointByUuid( - selectedProduct.productId, - triggeredModel.modelUuid, - selectedTrigger.triggeredAsset.triggeredPoint.pointUuid - ); - }, [selectedProduct, triggeredModel, selectedTrigger]); + useEffect(() => { + if (!currentAction || !selectedProduct) return; + const action = getActionByUuid(selectedProduct.productId, currentAction); + const actionTriggers = action?.triggers || []; + setTriggers(actionTriggers); + setSelectedTrigger(actionTriggers[0]); + }, [currentAction, selectedProduct]); - const triggeredAction = useMemo(() => { - if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredAction?.actionUuid) return undefined; - return getActionByUuid( - selectedProduct.productId, - selectedTrigger.triggeredAsset.triggeredAction.actionUuid - ); - }, [selectedProduct, selectedTrigger]); - - const modelOptions = getProductById(selectedProduct.productId)?.eventDatas || []; - - const pointOptions: PointsScheme[] = useMemo(() => { - if (!triggeredModel) return []; - - const model = modelOptions.find(m => m.modelUuid === triggeredModel.modelUuid); - if (!model) return []; - - if ('points' in model) { - return (model as ConveyorEventSchema).points; - } else if ('point' in model) { - return [(model as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point]; - } - return []; - }, [triggeredModel, modelOptions]); - - const actionOptions: any = useMemo(() => { - if (!triggeredPoint) return []; - const point = pointOptions.find((p) => p.uuid === triggeredPoint.uuid); - if (!point) return []; - - if ('action' in point) { - const typedPoint = point as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema; - return typedPoint.action ? [typedPoint.action] : []; - } else if ('actions' in point) { - const typedPoint = point as RoboticArmPointSchema; - return typedPoint.actions; - } - return []; - }, [triggeredPoint, pointOptions]); - - const handleModelSelect = (option: string, triggerUuid: string) => { - if (!selectedProduct) return; - - const selectedModel = modelOptions.find(m => m.modelName === option); - if (!selectedModel) return; - - const event = updateTrigger(selectedProduct.productId, triggerUuid, { - triggeredAsset: { - triggeredModel: { - modelName: selectedModel.modelName, - modelUuid: selectedModel.modelUuid - }, - triggeredPoint: null, - triggeredAction: null - } - }); - - if (event) { - const updatedTrigger = getTriggerByUuid(selectedProduct.productId, triggerUuid); - setSelectedTrigger(updatedTrigger); - - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; - - const handlePointSelect = (option: string, triggerUuid: string) => { - if (!selectedProduct || !selectedTrigger) return; - - const pointUuid = pointOptions.find(p => `Point ${p.uuid.slice(0, 4)}` === option)?.uuid; - if (!pointUuid) return; - - if (selectedTrigger.triggeredAsset?.triggeredModel) { - const event = updateTrigger(selectedProduct.productId, triggerUuid, { - triggeredAsset: { - ...selectedTrigger.triggeredAsset, - triggeredPoint: { - pointName: option, - pointUuid: pointUuid - }, - triggeredAction: null - } - }); - - if (event) { - const updatedTrigger = getTriggerByUuid(selectedProduct.productId, triggerUuid); - setSelectedTrigger(updatedTrigger); - - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - } - }; - - - const handleActionSelect = (option: string, triggerUuid: string) => { - if (!selectedProduct || !selectedTrigger) return; - - const selectedAction = actionOptions.find((a: any) => a.actionName === option); - - if (!selectedAction) return; - - if (selectedTrigger.triggeredAsset?.triggeredPoint) { - const event = updateTrigger(selectedProduct.productId, triggerUuid, { - triggeredAsset: { - ...selectedTrigger.triggeredAsset, - triggeredAction: { - actionName: option, - actionUuid: selectedAction.actionUuid - } - } - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - } - }; - - const handleAddTrigger = () => { - if (!selectedProduct || !currentAction) return; - - const newTrigger: TriggerSchema = { - triggerUuid: THREE.MathUtils.generateUUID(), - triggerName: `New Trigger ${triggers.length + 1}`, - triggerType: activeOption, - delay: 0, - triggeredAsset: null - }; - - const event = addTrigger(selectedProduct.productId, currentAction, newTrigger); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - - const updatedAction = getActionByUuid(selectedProduct.productId, currentAction); - const updatedTriggers = updatedAction?.triggers || []; - - setTriggers(updatedTriggers); - setSelectedTrigger(newTrigger); - }; - - const handleRemoveTrigger = (triggerUuid: string) => { - if (!selectedProduct || !currentAction) return; - - const event = removeTrigger(selectedProduct.productId, triggerUuid); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - - const index = triggers.findIndex(t => t.triggerUuid === triggerUuid); - const newTriggers = triggers.filter(t => t.triggerUuid !== triggerUuid); - setTriggers(newTriggers); - - if (selectedTrigger?.triggerUuid === triggerUuid) { - const nextTrigger = newTriggers[index] || newTriggers[index - 1]; - setSelectedTrigger(nextTrigger); - } - }; - - const handleTriggerRename = (triggerUuid: string, newName: string) => { - if (!selectedProduct) return; - const event = renameTrigger(selectedProduct.productId, triggerUuid, newName); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; - - const handleTriggerTypeChange = (option: string) => { - if (!selectedTrigger || !selectedProduct) return; - - const validTypes: Array = ["onComplete", "onStart", "onStop", "delay", "onError"]; - if (!validTypes.includes(option as TriggerSchema['triggerType'])) return; - - setActiveOption(option as TriggerSchema['triggerType']); - const event = updateTrigger(selectedProduct.productId, selectedTrigger.triggerUuid, { - triggerType: option as TriggerSchema['triggerType'] - }); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; - - return ( -
-
-
Trigger
- -
-
-
-
- {triggers.map((trigger) => ( -
setSelectedTrigger(trigger)} - > - - {triggers.length > 1 && ( - - )} -
- ))} -
- -
- - {selectedTrigger && ( -
-
{selectedTrigger.triggerName}
- - -
- (option.modelName))]} - onSelect={(option) => { handleModelSelect(option, selectedTrigger.triggerUuid) }} - /> - (`Point ${option.uuid.slice(0, 4)}`))]} - onSelect={(option) => { handlePointSelect(option, selectedTrigger.triggerUuid) }} - /> - (option.actionName))]} - onSelect={(option) => { handleActionSelect(option, selectedTrigger.triggerUuid) }} - /> -
-
- )} -
-
+ const triggeredModel = useMemo(() => { + if ( + !selectedProduct || + !selectedTrigger?.triggeredAsset?.triggeredModel?.modelUuid + ) + return undefined; + return getEventByModelUuid( + selectedProduct.productId, + selectedTrigger.triggeredAsset.triggeredModel.modelUuid ); + }, [selectedProduct, selectedTrigger]); + + const triggeredPoint = useMemo(() => { + if ( + !selectedProduct || + !triggeredModel || + !selectedTrigger?.triggeredAsset?.triggeredPoint?.pointUuid + ) + return undefined; + return getPointByUuid( + selectedProduct.productId, + triggeredModel.modelUuid, + selectedTrigger.triggeredAsset.triggeredPoint.pointUuid + ); + }, [selectedProduct, triggeredModel, selectedTrigger]); + + const triggeredAction = useMemo(() => { + if ( + !selectedProduct || + !selectedTrigger?.triggeredAsset?.triggeredAction?.actionUuid + ) + return undefined; + return getActionByUuid( + selectedProduct.productId, + selectedTrigger.triggeredAsset.triggeredAction.actionUuid + ); + }, [selectedProduct, selectedTrigger]); + + const modelOptions = + getProductById(selectedProduct.productId)?.eventDatas || []; + + const pointOptions: PointsScheme[] = useMemo(() => { + if (!triggeredModel) return []; + + const model = modelOptions.find( + (m) => m.modelUuid === triggeredModel.modelUuid + ); + if (!model) return []; + + if ("points" in model) { + return (model as ConveyorEventSchema).points; + } else if ("point" in model) { + return [ + ( + model as + | VehicleEventSchema + | RoboticArmEventSchema + | MachineEventSchema + | StorageEventSchema + ).point, + ]; + } + return []; + }, [triggeredModel, modelOptions]); + + const actionOptions: any = useMemo(() => { + if (!triggeredPoint) return []; + const point = pointOptions.find((p) => p.uuid === triggeredPoint.uuid); + if (!point) return []; + + if ("action" in point) { + const typedPoint = point as + | ConveyorPointSchema + | VehiclePointSchema + | MachinePointSchema + | StoragePointSchema; + return typedPoint.action ? [typedPoint.action] : []; + } else if ("actions" in point) { + const typedPoint = point as RoboticArmPointSchema; + return typedPoint.actions; + } + return []; + }, [triggeredPoint, pointOptions]); + + const handleModelSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct) return; + + const selectedModel = modelOptions.find((m) => m.modelName === option); + if (!selectedModel) return; + + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + triggeredModel: { + modelName: selectedModel.modelName, + modelUuid: selectedModel.modelUuid, + }, + triggeredPoint: null, + triggeredAction: null, + }, + }); + + if (event) { + const updatedTrigger = getTriggerByUuid( + selectedProduct.productId, + triggerUuid + ); + setSelectedTrigger(updatedTrigger); + + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + const handlePointSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct || !selectedTrigger) return; + + const pointUuid = pointOptions.find( + (p) => `Point ${p.uuid.slice(0, 4)}` === option + )?.uuid; + if (!pointUuid) return; + + if (selectedTrigger.triggeredAsset?.triggeredModel) { + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + ...selectedTrigger.triggeredAsset, + triggeredPoint: { + pointName: option, + pointUuid: pointUuid, + }, + triggeredAction: null, + }, + }); + + if (event) { + const updatedTrigger = getTriggerByUuid( + selectedProduct.productId, + triggerUuid + ); + setSelectedTrigger(updatedTrigger); + + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; + + const handleActionSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct || !selectedTrigger) return; + + const selectedAction = actionOptions.find( + (a: any) => a.actionName === option + ); + + if (!selectedAction) return; + + if (selectedTrigger.triggeredAsset?.triggeredPoint) { + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + ...selectedTrigger.triggeredAsset, + triggeredAction: { + actionName: option, + actionUuid: selectedAction.actionUuid, + }, + }, + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; + + const handleAddTrigger = () => { + if (!selectedProduct || !currentAction) return; + + const newTrigger: TriggerSchema = { + triggerUuid: THREE.MathUtils.generateUUID(), + triggerName: `New Trigger ${triggers.length + 1}`, + triggerType: activeOption, + delay: 0, + triggeredAsset: null, + }; + + const event = addTrigger( + selectedProduct.productId, + currentAction, + newTrigger + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + + const updatedAction = getActionByUuid( + selectedProduct.productId, + currentAction + ); + const updatedTriggers = updatedAction?.triggers || []; + + setTriggers(updatedTriggers); + setSelectedTrigger(newTrigger); + }; + + const handleRemoveTrigger = (triggerUuid: string) => { + if (!selectedProduct || !currentAction) return; + + const event = removeTrigger(selectedProduct.productId, triggerUuid); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + + const index = triggers.findIndex((t) => t.triggerUuid === triggerUuid); + const newTriggers = triggers.filter((t) => t.triggerUuid !== triggerUuid); + setTriggers(newTriggers); + + if (selectedTrigger?.triggerUuid === triggerUuid) { + const nextTrigger = newTriggers[index] || newTriggers[index - 1]; + setSelectedTrigger(nextTrigger); + } + }; + + const handleTriggerRename = (triggerUuid: string, newName: string) => { + if (!selectedProduct) return; + const event = renameTrigger( + selectedProduct.productId, + triggerUuid, + newName + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + const handleTriggerTypeChange = (option: string) => { + if (!selectedTrigger || !selectedProduct) return; + + const validTypes: Array = [ + "onComplete", + "onStart", + "onStop", + "delay", + "onError", + ]; + if (!validTypes.includes(option as TriggerSchema["triggerType"])) return; + + setActiveOption(option as TriggerSchema["triggerType"]); + const event = updateTrigger( + selectedProduct.productId, + selectedTrigger.triggerUuid, + { + triggerType: option as TriggerSchema["triggerType"], + } + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + return ( +
+
+
Trigger
+ +
+
+
+
+ {triggers.map((trigger) => ( +
setSelectedTrigger(trigger)} + > + + {triggers.length > 1 && ( + + )} +
+ ))} +
+ +
+ + {selectedTrigger && ( +
+
{selectedTrigger.triggerName}
+ + +
+ option.modelName)]} + onSelect={(option) => { + handleModelSelect(option, selectedTrigger.triggerUuid); + }} + /> + `Point ${option.uuid.slice(0, 4)}` + ), + ]} + onSelect={(option) => { + handlePointSelect(option, selectedTrigger.triggerUuid); + }} + /> + option.actionName), + ]} + onSelect={(option) => { + handleActionSelect(option, selectedTrigger.triggerUuid); + }} + /> +
+
+ )} +
+
+ ); }; export default Trigger; diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 6609e92..66e4948 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -22,6 +22,8 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP import { deleteProductApi } from "../../../../services/simulation/deleteProductApi"; import { renameProductApi } from "../../../../services/simulation/renameProductApi"; import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences"; +import ComparePopUp from "../../../../modules/simulation/compare/compare"; +import { useCompareStore } from "../../../../store/builder/store"; interface Event { modelName: string; @@ -49,7 +51,7 @@ const Simulations: React.FC = () => { renameProduct, addEvent, removeEvent, - getProductById + getProductById, } = useProductStore(); const { selectedProduct, setSelectedProduct } = useSelectedProduct(); const { getEventByModelUuid } = useEventsStore(); @@ -58,7 +60,7 @@ const Simulations: React.FC = () => { const organization = email!.split("@")[1].split(".")[0]; const [openObjects, setOpenObjects] = useState(true); const [processes, setProcesses] = useState(); - + const { comparePopUp, setComparePopUp } = useCompareStore(); const handleAddProduct = () => { const id = generateUUID(); const name = `Product ${products.length + 1}`; @@ -122,21 +124,21 @@ const Simulations: React.FC = () => { const selectedProductData = getProductById(selectedProduct.productId); if (selectedProductData) { - determineExecutionMachineSequences([selectedProductData]) - .then((sequences) => { + determineExecutionMachineSequences([selectedProductData]).then( + (sequences) => { sequences.forEach((sequence) => { const events: Event[] = sequence.map((event) => ({ modelName: event.modelName, - modelId: event.modelUuid + modelId: event.modelUuid, })) || []; processes.push(events); - }) - setProcesses(processes) - }) - + }); + setProcesses(processes); + } + ); } - }, [selectedProduct.productId, products]) + }, [selectedProduct.productId, products]); return (
@@ -145,7 +147,11 @@ const Simulations: React.FC = () => {
Products
-
@@ -158,10 +164,11 @@ const Simulations: React.FC = () => { {products.map((product, index) => (
{/* eslint-disable-next-line */}
{
{products.length > 1 && (
{openObjects && - processes?.map((process, index) => + processes?.map((process, index) => (
{process.map((event, index) => ( ))}
- ) - } + ))}
@@ -233,7 +241,7 @@ const Simulations: React.FC = () => { Click 'Compare' to review and analyze the layout differences between them.
-
+
setComparePopUp(true)}>
@@ -258,6 +266,12 @@ const Simulations: React.FC = () => { /> )} + + {comparePopUp && ( + + + + )}
); }; diff --git a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx index 73a630c..282c371 100644 --- a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx +++ b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx @@ -57,13 +57,17 @@ const VersionHistory = () => {
Version History
- -
+
-
+
@@ -96,8 +100,9 @@ const VersionHistory = () => {
{versions.map((version, index) => ( ); @@ -125,7 +139,7 @@ const Tools: React.FC = () => { switch (tool) { case "cursor": if (toggleView) { - setToolMode('move'); + setToolMode("move"); } break; case "draw-wall": @@ -170,37 +184,33 @@ const Tools: React.FC = () => { ); }; - if (isPlaying && activeModule !== "simulation") { - return ( - - ); - } - const renderBuilderTools = () => ( <> {!toggleThreeD && (
setActiveTool("draw-wall")} /> setActiveTool("draw-zone")} /> setActiveTool("draw-aisle")} /> { )}
{ const renderSimulationTools = () => (
{ const renderVisualizationTools = () => (
{ const renderModeSwitcher = () => (
diff --git a/app/src/components/ui/inputs/InputToggle.tsx b/app/src/components/ui/inputs/InputToggle.tsx index 5e69291..d2968bf 100644 --- a/app/src/components/ui/inputs/InputToggle.tsx +++ b/app/src/components/ui/inputs/InputToggle.tsx @@ -24,7 +24,10 @@ const InputToggle: React.FC = ({ -
= ({ checked={value} readOnly /> -
+
); }; diff --git a/app/src/components/ui/inputs/LabledButton.tsx b/app/src/components/ui/inputs/LabledButton.tsx index 26f4d75..b9e2e04 100644 --- a/app/src/components/ui/inputs/LabledButton.tsx +++ b/app/src/components/ui/inputs/LabledButton.tsx @@ -16,7 +16,12 @@ const LabeledButton: React.FC = ({ return (
{label}
-
diff --git a/app/src/components/ui/inputs/MultiLevelDropDown.tsx b/app/src/components/ui/inputs/MultiLevelDropDown.tsx index fa30a55..3be1e17 100644 --- a/app/src/components/ui/inputs/MultiLevelDropDown.tsx +++ b/app/src/components/ui/inputs/MultiLevelDropDown.tsx @@ -16,7 +16,10 @@ const DropdownItem = ({ onClick={() => { if (!disabled) onClick(); }} - style={{ cursor: disabled ? "not-allowed": "default", opacity: disabled ? 0.5 : 1 }} + style={{ + cursor: disabled ? "not-allowed" : "default", + opacity: disabled ? 0.5 : 1, + }} > {label}
@@ -127,13 +130,17 @@ const MultiLevelDropdown = ({ const disabledFieldsList = Object.values(allSelections) .filter( (sel) => - !(sel.name === selectedValue?.name && sel.fields === selectedValue?.fields) + !( + sel.name === selectedValue?.name && + sel.fields === selectedValue?.fields + ) ) .map((sel) => `${sel.name}-${sel.fields}`); return (
)} diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index 5de5654..3ec828e 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -123,6 +123,7 @@ const DropDownList: React.FC = ({
)}
- - {remove && ( - )} diff --git a/app/src/components/ui/log/LogList.tsx b/app/src/components/ui/log/LogList.tsx index 2ec50f8..caaf957 100644 --- a/app/src/components/ui/log/LogList.tsx +++ b/app/src/components/ui/log/LogList.tsx @@ -38,6 +38,7 @@ const LogList: React.FC = () => {
Log List
))}
-
@@ -80,7 +87,9 @@ const LogList: React.FC = () => {
)) ) : ( -
There are no logs to display at the moment.
+
+ There are no logs to display at the moment. +
)}
diff --git a/app/src/components/ui/menu/EditWidgetOption.tsx b/app/src/components/ui/menu/EditWidgetOption.tsx index 558957b..cbc1e68 100644 --- a/app/src/components/ui/menu/EditWidgetOption.tsx +++ b/app/src/components/ui/menu/EditWidgetOption.tsx @@ -11,7 +11,7 @@ interface EditWidgetOptionProps { const EditWidgetOption: React.FC = ({ options, - onClick + onClick, }) => { const { top } = useTopData(); const { left } = useLeftData(); @@ -29,6 +29,7 @@ const EditWidgetOption: React.FC = ({
{options.map((option, index) => ( {/* Log out */} -
diff --git a/app/src/components/ui/simulation/AssetDetailsCard.tsx b/app/src/components/ui/simulation/AssetDetailsCard.tsx index 5a4b4da..15c5574 100644 --- a/app/src/components/ui/simulation/AssetDetailsCard.tsx +++ b/app/src/components/ui/simulation/AssetDetailsCard.tsx @@ -82,6 +82,7 @@ const AssetDetailsCard: React.FC = ({ )} + )} + {!hidePlayer && ( + + )} - - + {subModule === "analysis" && ( - + {!hidePlayer && ( +
+
+
+
+ +
+
+
23 April ,25
+
04:41 PM
+
+
+
+
+ {intervals.map((label, index) => { + const segmentProgress = (index / totalSegments) * 100; + const isFilled = progress >= segmentProgress; + return ( + +
+
{label} mins
+
+
+ {index < intervals.length - 1 && ( +
= ((index + 1) / totalSegments) * 100 + ? "filled" + : "" + }`} + >
+ )} +
+ ); + })} +
+
+ +
+
+
00:10:20
+
+
+ +
+
+
+
+
+
+ +
+ Speed +
+
+
0.5X
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
{MAX_SPEED}x
-
{MAX_SPEED}x
- - {subModule === "analysis" && ( + )} + {!hidePlayer && subModule === "analysis" && (
00:00
24:00
diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts index 0d96c8d..096665a 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts @@ -3,7 +3,6 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import gsap from 'gsap'; import * as THREE from 'three'; import * as CONSTANTS from '../../../types/world/worldConstants'; -import { toast } from 'react-toastify'; import * as Types from "../../../types/world/worldTypes"; import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils'; import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi'; @@ -91,7 +90,7 @@ async function loadInitialFloorItems( }, undefined, (error) => { - toast.error(`[IndexedDB] Error loading ${item.modelName}:`); + echo.error(`[IndexedDB] Error loading ${item.modelName}:`); URL.revokeObjectURL(blobUrl); resolve(); } @@ -111,7 +110,7 @@ async function loadInitialFloorItems( }, undefined, (error) => { - toast.error(`[Backend] Error loading ${item.modelName}:`); + echo.error(`[Backend] Error loading ${item.modelName}:`); resolve(); } ); diff --git a/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts b/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts index 2258f3b..96395ba 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts @@ -33,7 +33,7 @@ async function loadInitialWallItems( const loadedWallItems = await Promise.all(items.map(async (item: Types.WallItem) => { // Check THREE.js cache first - const cachedModel = THREE.Cache.get(item.modelName!); + const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { return processModel(cachedModel, item); } @@ -45,7 +45,7 @@ async function loadInitialWallItems( return new Promise((resolve) => { loader.load(blobUrl, (gltf) => { URL.revokeObjectURL(blobUrl); - THREE.Cache.add(item.modelName!, gltf); + THREE.Cache.add(item.modelfileID!, gltf); resolve(processModel(gltf, item)); }); }); @@ -58,8 +58,8 @@ async function loadInitialWallItems( try { // Cache the model const modelBlob = await fetch(modelUrl).then((res) => res.blob()); - await storeGLTF(item.modelName!, modelBlob); - THREE.Cache.add(item.modelName!, gltf); + await storeGLTF(item.modelfileID!, modelBlob); + THREE.Cache.add(item.modelfileID!, gltf); resolve(processModel(gltf, item)); } catch (error) { console.error('Failed to cache model:', error); diff --git a/app/src/modules/builder/assetGroup/assetsGroup.tsx b/app/src/modules/builder/assetGroup/assetsGroup.tsx new file mode 100644 index 0000000..9e57fb7 --- /dev/null +++ b/app/src/modules/builder/assetGroup/assetsGroup.tsx @@ -0,0 +1,125 @@ +import * as THREE from "three" +import { useEffect } from 'react' +import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi'; +import { useLoadingProgress } from '../../../store/builder/store'; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; +import { FloorItems } from "../../../types/world/worldTypes"; +import { useAssetsStore } from "../../../store/builder/useAssetStore"; +import Models from "./models/models"; +import { useGLTF } from "@react-three/drei"; + +const gltfLoaderWorker = new Worker( + new URL( + "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", + import.meta.url + ) +); + +function AssetsGroup() { + const { setLoadingProgress } = useLoadingProgress(); + const { setAssets } = useAssetsStore(); + + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); + + dracoLoader.setDecoderPath( + "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/" + ); + loader.setDRACOLoader(dracoLoader); + + useEffect(() => { + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; + + let totalAssets = 0; + let loadedAssets = 0; + + const updateLoadingProgress = (progress: number) => { + if (progress < 100) { + setLoadingProgress(progress); + } else if (progress === 100) { + setTimeout(() => { + setLoadingProgress(100); + setTimeout(() => { + setLoadingProgress(0); + }, 1500); + }, 1000); + } + }; + + getFloorAssets(organization).then((data) => { + if (data.length > 0) { + const uniqueItems = (data as FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID)); + totalAssets = uniqueItems.length; + if (totalAssets === 0) { + updateLoadingProgress(100); + return; + } + gltfLoaderWorker.postMessage({ floorItems: uniqueItems }); + } else { + gltfLoaderWorker.postMessage({ floorItems: [] }); + updateLoadingProgress(100); + } + }); + + gltfLoaderWorker.onmessage = async (event) => { + if (event.data.message === "gltfLoaded" && event.data.modelBlob) { + const blobUrl = URL.createObjectURL(event.data.modelBlob); + loader.load(blobUrl, (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(event.data.modelID, gltf); + + loadedAssets++; + const progress = Math.round((loadedAssets / totalAssets) * 100); + updateLoadingProgress(progress); + + if (loadedAssets === totalAssets) { + const assets: Asset[] = []; + getFloorAssets(organization).then((data: FloorItems) => { + data.forEach((item) => { + if (item.eventData) { + assets.push({ + modelUuid: item.modelUuid, + modelName: item.modelName, + assetId: item.modelfileID, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + isLocked: item.isLocked, + isCollidable: false, + isVisible: item.isVisible, + opacity: 1, + eventData: item.eventData + }) + } else { + assets.push({ + modelUuid: item.modelUuid, + modelName: item.modelName, + assetId: item.modelfileID, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + isLocked: item.isLocked, + isCollidable: false, + isVisible: item.isVisible, + opacity: 1, + }) + } + }) + setAssets(assets); + }) + updateLoadingProgress(100); + } + }); + } + }; + }, []); + + return ( + <> + + + ) +} + +export default AssetsGroup; \ No newline at end of file diff --git a/app/src/modules/builder/assetGroup/models/model/model.tsx b/app/src/modules/builder/assetGroup/models/model/model.tsx new file mode 100644 index 0000000..55eb1a6 --- /dev/null +++ b/app/src/modules/builder/assetGroup/models/model/model.tsx @@ -0,0 +1,82 @@ +import { Outlines } from '@react-three/drei'; +import { useEffect, useState } from 'react'; +import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; +import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; +import * as THREE from 'three'; + +function Model({ asset }: { asset: Asset }) { + const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; + const [gltfScene, setGltfScene] = useState(null); + + useEffect(() => { + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); + + dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/'); + loader.setDRACOLoader(dracoLoader); + const loadModel = async () => { + try { + const cachedModel = THREE.Cache.get(asset.assetId!); + if (cachedModel) { + setGltfScene(cachedModel); + return; + } + + // Check IndexedDB + const indexedDBModel = await retrieveGLTF(asset.assetId!); + if (indexedDBModel) { + const blobUrl = URL.createObjectURL(indexedDBModel); + loader.load(blobUrl, (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(asset.assetId!, gltf); + setGltfScene(gltf); + }, + undefined, + (error) => { + echo.error(`[IndexedDB] Error loading ${asset.modelName}:`); + URL.revokeObjectURL(blobUrl); + } + ); + return; + } + + // Fetch from Backend + const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${asset.assetId!}`; + loader.load(modelUrl, async (gltf) => { + const modelBlob = await fetch(modelUrl).then((res) => res.blob()); + await storeGLTF(asset.assetId!, modelBlob); + THREE.Cache.add(asset.assetId!, gltf); + setGltfScene(gltf); + }, + undefined, + (error) => { + echo.error(`[Backend] Error loading ${asset.modelName}:`); + } + ); + } catch (err) { + console.error("Failed to load model:", asset.assetId, err); + } + }; + + loadModel(); + + }, [asset.assetId]); + + return ( + <> + {gltfScene && + + + + } + + ); +} + +export default Model; \ No newline at end of file diff --git a/app/src/modules/builder/assetGroup/models/models.tsx b/app/src/modules/builder/assetGroup/models/models.tsx new file mode 100644 index 0000000..7378812 --- /dev/null +++ b/app/src/modules/builder/assetGroup/models/models.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { useAssetsStore } from '../../../../store/builder/useAssetStore'; +import Model from './model/model'; + +function Models() { + const { assets } = useAssetsStore(); + + return ( + <> + {assets.map((asset) => + + )} + + ) +} + +export default Models \ No newline at end of file diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index ed82590..57ead56 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -47,6 +47,7 @@ import MeasurementTool from "../scene/tools/measurementTool"; import NavMesh from "../simulation/vehicle/navMesh/navMesh"; import CalculateAreaGroup from "./groups/calculateAreaGroup"; import LayoutImage from "./layout/layoutImage"; +import AssetsGroup from "./assetGroup/assetsGroup"; export default function Builder() { const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -299,6 +300,8 @@ export default function Builder() { anglesnappedPoint={anglesnappedPoint} /> + {/* */} + diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts index 889d905..5d2f90f 100644 --- a/app/src/modules/builder/geomentries/assets/assetManager.ts +++ b/app/src/modules/builder/geomentries/assets/assetManager.ts @@ -78,7 +78,7 @@ export default async function assetManager( }, undefined, (error) => { - toast.error(`[IndexedDB] Error loading ${item.modelName}:`); + echo.error(`[IndexedDB] Error loading ${item.modelName}:`); resolve(); } ); @@ -97,7 +97,7 @@ export default async function assetManager( }, undefined, (error) => { - toast.error(`[Backend] Error loading ${item.modelName}:`); + echo.error(`[Backend] Error loading ${item.modelName}:`); resolve(); } ); diff --git a/app/src/modules/builder/geomentries/lines/updateDistanceText.ts b/app/src/modules/builder/geomentries/lines/updateDistanceText.ts index 2f743a9..8a5a1cc 100644 --- a/app/src/modules/builder/geomentries/lines/updateDistanceText.ts +++ b/app/src/modules/builder/geomentries/lines/updateDistanceText.ts @@ -28,7 +28,7 @@ function updateDistanceText( const textMesh = text as THREE.Mesh; if (textMesh.userData[0][1] === linePoints[0][1] && textMesh.userData[1][1] === linePoints[1][1]) { textMesh.position.set(position.x, 1, position.z); - const className = `Distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`; + const className = `distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`; const element = document.getElementsByClassName(className)[0] as HTMLElement; if (element) { element.innerHTML = `${distance} m`; diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index ad0c86e..aacd04c 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -104,7 +104,7 @@ const FloorItemsGroup = ({ updateLoadingProgress(100); return; } - gltfLoaderWorker.postMessage({ floorItems: data }); + gltfLoaderWorker.postMessage({ floorItems: uniqueItems }); } else { gltfLoaderWorker.postMessage({ floorItems: [] }); loadInitialFloorItems( diff --git a/app/src/modules/collaboration/camera/collabUserIcon.tsx b/app/src/modules/collaboration/camera/collabUserIcon.tsx index d3c8f21..7a9873b 100644 --- a/app/src/modules/collaboration/camera/collabUserIcon.tsx +++ b/app/src/modules/collaboration/camera/collabUserIcon.tsx @@ -7,7 +7,7 @@ interface CollabUserIconProps { userName: string; userImage?: string; color: string; - id: string, + id: string; position?: { x: number; y: number; @@ -39,6 +39,7 @@ const CollabUserIcon: React.FC = ({ return (
{/* close button */} -
diff --git a/app/src/modules/market/Card.tsx b/app/src/modules/market/Card.tsx index b1b5faa..2b878bb 100644 --- a/app/src/modules/market/Card.tsx +++ b/app/src/modules/market/Card.tsx @@ -7,7 +7,6 @@ import { VerifiedIcon, } from "../../components/icons/marketPlaceIcons"; - interface CardProps { assetName: string; uploadedOn: number; @@ -16,8 +15,8 @@ interface CardProps { views: number; image: string; description: string; - AssetID: string - modelUrl: string + AssetID: string; + modelUrl: string; onSelectCard: (cardData: { assetName: string; uploadedOn: number; @@ -40,14 +39,19 @@ const Card: React.FC = ({ AssetID, modelUrl, onSelectCard, - }) => { const handleCardSelect = () => { - console.log('assetName: ', assetName); - console.log('AssetID: ', AssetID); + console.log("assetName: ", assetName); + console.log("AssetID: ", AssetID); onSelectCard({ - assetName, uploadedOn, price, rating, views, description, AssetID + assetName, + uploadedOn, + price, + rating, + views, + description, + AssetID, }); }; @@ -89,12 +93,8 @@ const Card: React.FC = ({
{[...Array(5)].map((_, index) => ( - - {index < 3 ? ( - - ) : ( - - )} + + {index < 3 ? : } ))}
@@ -102,7 +102,11 @@ const Card: React.FC = ({ ₹ {price}/unit
- diff --git a/app/src/modules/market/FilterSearch.tsx b/app/src/modules/market/FilterSearch.tsx index e860b44..ebc6d3a 100644 --- a/app/src/modules/market/FilterSearch.tsx +++ b/app/src/modules/market/FilterSearch.tsx @@ -39,10 +39,14 @@ const FilterSearch: React.FC = ({ useEffect(() => { if (activeOption === "Alphabet ascending") { - const ascending = [...models].sort((a, b) => a.filename.localeCompare(b.filename)); + const ascending = [...models].sort((a, b) => + a.filename.localeCompare(b.filename) + ); setModels(ascending); } else if (activeOption === "Alphabet descending") { - const descending = [...models].sort((a, b) => b.filename.localeCompare(a.filename)); + const descending = [...models].sort((a, b) => + b.filename.localeCompare(a.filename) + ); setModels(descending); } }, [activeOption, models, setModels]); @@ -75,6 +79,7 @@ const FilterSearch: React.FC = ({
{[0, 1, 2, 3, 4].map((i) => ( +
+ + +
+ Save this version and proceed. +
+ + + ); +}; + +export default ComparePopUp; diff --git a/app/src/modules/visualization/template/Templates.tsx b/app/src/modules/visualization/template/Templates.tsx index d7f6b3f..6a94d19 100644 --- a/app/src/modules/visualization/template/Templates.tsx +++ b/app/src/modules/visualization/template/Templates.tsx @@ -14,7 +14,7 @@ const Templates = () => { useEffect(() => { async function templateData() { try { - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; let response = await getTemplateData(organization); setTemplates(response); @@ -33,8 +33,7 @@ const Templates = () => { ) => { try { e.stopPropagation(); - const email = localStorage.getItem("email") || ""; - + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; let deleteTemplate = { organization: organization, @@ -56,7 +55,7 @@ const Templates = () => { const handleLoadTemplate = async (template: any) => { try { if (selectedZone.zoneName === "") return; - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; let loadingTemplate = { @@ -110,10 +109,10 @@ const Templates = () => { )}
- {/* {`Template ${index + 1}`} */}
+ - {/* Extra Buttons */} - {selectedZone.activeSides.includes(side) && ( -
= ({ : "" } `} + > + {/* Hide Panel */} + - {/* Clean Panel */} -
cleanPanel(side)} - style={{ - cursor: - hiddenPanels[selectedZone.zoneId]?.includes(side) || - selectedZone.lockedPanels.includes(side) - ? "not-allowed" - : "pointer", - }} - > - -
- - {/* Lock/Unlock Panel */} -
cleanPanel(side)} + style={{ + cursor: + hiddenPanels[selectedZone.zoneId]?.includes(side) || selectedZone.lockedPanels.includes(side) - ? "Unlock Panel" - : "Lock Panel" + ? "not-allowed" + : "pointer", + }} + > + + + + {/* Lock/Unlock Panel */} +
-
- )} -
- ))} - - + /> + + + )} + + ))} + ); }; diff --git a/app/src/modules/visualization/widgets/panel/Panel.tsx b/app/src/modules/visualization/widgets/panel/Panel.tsx index f0fb86b..4b510b4 100644 --- a/app/src/modules/visualization/widgets/panel/Panel.tsx +++ b/app/src/modules/visualization/widgets/panel/Panel.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { arrayMove } from "@dnd-kit/sortable"; -import { useAsset3dWidget, useSocketStore } from "../../../../store/builder/store"; +import { useSocketStore } from "../../../../store/builder/store"; import { usePlayButtonStore } from "../../../../store/usePlayButtonStore"; import { useWidgetStore } from "../../../../store/useWidgetStore"; import { DraggableWidget } from "../2d/DraggableWidget"; @@ -47,7 +47,7 @@ interface PanelProps { } const generateUniqueId = () => - `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; const Panel: React.FC = ({ selectedZone, @@ -56,7 +56,6 @@ const Panel: React.FC = ({ setZonesData, waitingPanels, }) => { - const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({}); const [panelDimensions, setPanelDimensions] = useState<{ [side in Side]?: { width: number; height: number }; @@ -183,7 +182,7 @@ const Panel: React.FC = ({ // Add widget to panel const addWidgetToPanel = async (asset: any, panel: Side) => { - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; const newWidget = { @@ -285,7 +284,7 @@ const Panel: React.FC = ({ {selectedZone.activeSides.map((side) => (
= ({ }} >
= ({ const [showLeftArrow, setShowLeftArrow] = useState(false); const [showRightArrow, setShowRightArrow] = useState(false); const { floatingWidget, setFloatingWidget } = useFloatingWidget(); + const { isPlaying } = usePlayButtonStore(); const { setSelectedChartId } = useWidgetStore(); @@ -206,16 +208,21 @@ const DisplayZone: React.FC = ({ return (
{/* Left Arrow */} {showLeftArrow && ( - )} @@ -252,7 +259,11 @@ const DisplayZone: React.FC = ({ {/* Right Arrow */} {showRightArrow && ( - )} diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 6857a5c..d30fdcc 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -32,6 +32,7 @@ import RenderOverlay from "../components/templates/Overlay"; import LogList from "../components/ui/log/LogList"; import Footer from "../components/footer/Footer"; import SelectFloorPlan from "../components/temporary/SelectFloorPlan"; +import ControlsPlayer from "../components/layout/controls/ControlsPlayer"; const Project: React.FC = () => { let navigate = useNavigate(); @@ -97,8 +98,10 @@ const Project: React.FC = () => { )} {activeModule === "market" && } - {activeModule !== "market" && } + {activeModule !== "market" && !isPlaying && } {isPlaying && activeModule === "simulation" && } + {isPlaying && activeModule !== "simulation" && } + {/* remove this later */} {activeModule === "builder" && !toggleThreeD && } diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index 8b162c6..cd837db 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -102,7 +102,7 @@ const UserAuth: React.FC = () => { )}

- @@ -139,6 +139,7 @@ const UserAuth: React.FC = () => { required />
)} - diff --git a/app/src/services/visulization/zone/panel.ts b/app/src/services/visulization/zone/panel.ts index 97934ce..1f1911b 100644 --- a/app/src/services/visulization/zone/panel.ts +++ b/app/src/services/visulization/zone/panel.ts @@ -1,5 +1,4 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; -// let url_Backend_dwinzo = `http://192.168.0.102:5000`; type Side = "top" | "bottom" | "left" | "right"; export const panelData = async ( diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 06c7bfc..e5f0c9e 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -1,454 +1,479 @@ import * as THREE from "three"; import { create } from "zustand"; import { io } from "socket.io-client"; -import * as CONSTANTS from '../../types/world/worldConstants'; +import * as CONSTANTS from "../../types/world/worldConstants"; export const useSocketStore = create((set: any, get: any) => ({ - socket: null, - initializeSocket: (email: string, organization: string) => { - const existingSocket = get().socket; - if (existingSocket) { - return; - } + socket: null, + initializeSocket: (email: string, organization: string) => { + const existingSocket = get().socket; + if (existingSocket) { + return; + } - const socket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder`, - { - reconnection: true, - auth: { email, organization }, - } - ); + const socket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder`, + { + reconnection: true, + auth: { email, organization }, + } + ); - const visualizationSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization`, - { - reconnection: true, - auth: { email, organization }, - } - ); + const visualizationSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization`, + { + reconnection: true, + auth: { email, organization }, + } + ); - set({ socket, visualizationSocket }); - }, - disconnectSocket: () => { - set((state: any) => { - state.socket?.disconnect(); - state.visualizationSocket?.disconnect(); - return { socket: null }; - }); - }, + set({ socket, visualizationSocket }); + }, + disconnectSocket: () => { + set((state: any) => { + state.socket?.disconnect(); + state.visualizationSocket?.disconnect(); + return { socket: null }; + }); + }, })); export const useLoadingProgress = create<{ - loadingProgress: number; - setLoadingProgress: (x: number) => void; + loadingProgress: number; + setLoadingProgress: (x: number) => void; }>((set) => ({ - loadingProgress: 1, - setLoadingProgress: (x: number) => set({ loadingProgress: x }), + loadingProgress: 1, + setLoadingProgress: (x: number) => set({ loadingProgress: x }), })); export const useOrganization = create((set: any) => ({ - organization: "", - setOrganization: (x: any) => set(() => ({ organization: x })), + organization: "", + setOrganization: (x: any) => set(() => ({ organization: x })), })); export const useToggleView = create((set: any) => ({ - toggleView: false, - setToggleView: (x: any) => set(() => ({ toggleView: x })), + toggleView: false, + setToggleView: (x: any) => set(() => ({ toggleView: x })), })); export const useUpdateScene = create((set: any) => ({ - updateScene: false, - setUpdateScene: (x: any) => set(() => ({ updateScene: x })), + updateScene: false, + setUpdateScene: (x: any) => set(() => ({ updateScene: x })), })); export const useWalls = create((set: any) => ({ - walls: [], - setWalls: (x: any) => set(() => ({ walls: x })), + walls: [], + setWalls: (x: any) => set(() => ({ walls: x })), })); export const useRoomsState = create((set: any) => ({ - roomsState: [], - setRoomsState: (x: any) => set(() => ({ roomsState: x })), + roomsState: [], + setRoomsState: (x: any) => set(() => ({ roomsState: x })), })); export const useZones = create((set: any) => ({ - zones: [], - setZones: (callback: any) => - set((state: any) => ({ - zones: typeof callback === "function" ? callback(state.zones) : callback, - })), + zones: [], + setZones: (callback: any) => + set((state: any) => ({ + zones: typeof callback === "function" ? callback(state.zones) : callback, + })), })); interface ZonePointsState { - zonePoints: THREE.Vector3[]; - setZonePoints: (points: THREE.Vector3[]) => void; + zonePoints: THREE.Vector3[]; + setZonePoints: (points: THREE.Vector3[]) => void; } export const useZonePoints = create((set) => ({ - zonePoints: [], - setZonePoints: (points) => set({ zonePoints: points }), + zonePoints: [], + setZonePoints: (points) => set({ zonePoints: points }), })); export const useSelectedItem = create((set: any) => ({ - selectedItem: { name: "", id: "", type: undefined, category: '', subCatergory: '' }, - setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), + selectedItem: { + name: "", + id: "", + type: undefined, + category: "", + subCatergory: "", + }, + setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), })); export const useNavMesh = create((set: any) => ({ - navMesh: null, - setNavMesh: (x: any) => set({ navMesh: x }), + navMesh: null, + setNavMesh: (x: any) => set({ navMesh: x }), })); export const useSelectedAssets = create((set: any) => ({ - selectedAssets: [], - setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), + selectedAssets: [], + setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), })); export const useLayers = create((set: any) => ({ - Layers: 1, - setLayers: (x: any) => set(() => ({ Layers: x })), + Layers: 1, + setLayers: (x: any) => set(() => ({ Layers: x })), })); export const useCamPosition = create((set: any) => ({ - camPosition: { x: undefined, y: undefined, z: undefined }, - setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), + camPosition: { x: undefined, y: undefined, z: undefined }, + setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), })); export const useMenuVisible = create((set: any) => ({ - menuVisible: false, - setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), + menuVisible: false, + setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), })); export const useDeleteTool = create((set: any) => ({ - deleteTool: false, - setDeleteTool: (x: any) => set(() => ({ deleteTool: x })), + deleteTool: false, + setDeleteTool: (x: any) => set(() => ({ deleteTool: x })), })); export const useToolMode = create((set: any) => ({ - toolMode: null, - setToolMode: (x: any) => set(() => ({ toolMode: x })), + toolMode: null, + setToolMode: (x: any) => set(() => ({ toolMode: x })), })); export const useNewLines = create((set: any) => ({ - newLines: [], - setNewLines: (x: any) => set(() => ({ newLines: x })), + newLines: [], + setNewLines: (x: any) => set(() => ({ newLines: x })), })); export const useDeletedLines = create((set: any) => ({ - deletedLines: [], - setDeletedLines: (x: any) => set(() => ({ deletedLines: x })), + deletedLines: [], + setDeletedLines: (x: any) => set(() => ({ deletedLines: x })), })); export const useMovePoint = create((set: any) => ({ - movePoint: false, - setMovePoint: (x: any) => set(() => ({ movePoint: x })), + movePoint: false, + setMovePoint: (x: any) => set(() => ({ movePoint: x })), })); export const useDeletePointOrLine = create((set: any) => ({ - deletePointOrLine: false, - setDeletePointOrLine: (x: any) => set(() => ({ deletePointOrLine: x })), + deletePointOrLine: false, + setDeletePointOrLine: (x: any) => set(() => ({ deletePointOrLine: x })), })); export const useFloorItems = create((set: any) => ({ - floorItems: null, - setFloorItems: (callback: any) => - set((state: any) => ({ - floorItems: - typeof callback === "function" ? callback(state.floorItems) : callback, - })), + floorItems: null, + setFloorItems: (callback: any) => + set((state: any) => ({ + floorItems: + typeof callback === "function" ? callback(state.floorItems) : callback, + })), })); export const useWallItems = create((set: any) => ({ - wallItems: [], - setWallItems: (callback: any) => - set((state: any) => ({ - wallItems: - typeof callback === "function" ? callback(state.wallItems) : callback, - })), + wallItems: [], + setWallItems: (callback: any) => + set((state: any) => ({ + wallItems: + typeof callback === "function" ? callback(state.wallItems) : callback, + })), })); export const useSelectedWallItem = create((set: any) => ({ - selectedWallItem: null, - setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), + selectedWallItem: null, + setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), })); export const useSelectedFloorItem = create((set: any) => ({ - selectedFloorItem: null, - setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), + selectedFloorItem: null, + setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), })); export const useDeletableFloorItem = create((set: any) => ({ - deletableFloorItem: null, - setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })), + deletableFloorItem: null, + setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })), })); export const useSetScale = create((set: any) => ({ - scale: null, - setScale: (x: any) => set(() => ({ scale: x })), + scale: null, + setScale: (x: any) => set(() => ({ scale: x })), })); export const useRoofVisibility = create((set: any) => ({ - roofVisibility: false, - setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })), + roofVisibility: false, + setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })), })); export const useWallVisibility = create((set: any) => ({ - wallVisibility: false, - setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })), + wallVisibility: false, + setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })), })); export const useShadows = create((set: any) => ({ - shadows: false, - setShadows: (x: any) => set(() => ({ shadows: x })), + shadows: false, + setShadows: (x: any) => set(() => ({ shadows: x })), })); export const useSunPosition = create((set: any) => ({ - sunPosition: { x: undefined, y: undefined, z: undefined }, - setSunPosition: (newSuntPosition: any) => - set({ sunPosition: newSuntPosition }), + sunPosition: { x: undefined, y: undefined, z: undefined }, + setSunPosition: (newSuntPosition: any) => + set({ sunPosition: newSuntPosition }), })); export const useRemoveLayer = create((set: any) => ({ - removeLayer: false, - setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })), + removeLayer: false, + setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })), })); export const useRemovedLayer = create((set: any) => ({ - removedLayer: null, - setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })), + removedLayer: null, + setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })), })); export const useActiveLayer = create((set: any) => ({ - activeLayer: 1, - setActiveLayer: (x: any) => set({ activeLayer: x }), + activeLayer: 1, + setActiveLayer: (x: any) => set({ activeLayer: x }), })); interface RefTextUpdateState { - refTextupdate: number; - setRefTextUpdate: ( - callback: (currentValue: number) => number | number - ) => void; + refTextupdate: number; + setRefTextUpdate: ( + callback: (currentValue: number) => number | number + ) => void; } export const useRefTextUpdate = create((set) => ({ - refTextupdate: -1000, - setRefTextUpdate: (callback) => - set((state) => ({ - refTextupdate: - typeof callback === "function" - ? callback(state.refTextupdate) - : callback, - })), + refTextupdate: -1000, + setRefTextUpdate: (callback) => + set((state) => ({ + refTextupdate: + typeof callback === "function" + ? callback(state.refTextupdate) + : callback, + })), })); export const useResetCamera = create((set: any) => ({ - resetCamera: false, - setResetCamera: (x: any) => set({ resetCamera: x }), + resetCamera: false, + setResetCamera: (x: any) => set({ resetCamera: x }), })); export const useAddAction = create((set: any) => ({ - addAction: null, - setAddAction: (x: any) => set({ addAction: x }), + addAction: null, + setAddAction: (x: any) => set({ addAction: x }), })); export const useActiveTool = create((set: any) => ({ - activeTool: "cursor", - setActiveTool: (x: any) => set({ activeTool: x }), + activeTool: "cursor", + setActiveTool: (x: any) => set({ activeTool: x }), })); export const useActiveSubTool = create((set: any) => ({ - activeSubTool: "cursor", - setActiveSubTool: (x: any) => set({ activeSubTool: x }), + activeSubTool: "cursor", + setActiveSubTool: (x: any) => set({ activeSubTool: x }), })); export const use2DUndoRedo = create((set: any) => ({ - is2DUndoRedo: null, - set2DUndoRedo: (x: any) => set({ is2DUndoRedo: x }), + is2DUndoRedo: null, + set2DUndoRedo: (x: any) => set({ is2DUndoRedo: x }), })); export const useElevation = create((set: any) => ({ - elevation: 45, - setElevation: (x: any) => set({ elevation: x }), + elevation: 45, + setElevation: (x: any) => set({ elevation: x }), })); export const useAzimuth = create((set: any) => ({ - azimuth: -160, - setAzimuth: (x: any) => set({ azimuth: x }), + azimuth: -160, + setAzimuth: (x: any) => set({ azimuth: x }), })); export const useRenderDistance = create((set: any) => ({ - renderDistance: 40, - setRenderDistance: (x: any) => set({ renderDistance: x }), + renderDistance: 40, + setRenderDistance: (x: any) => set({ renderDistance: x }), })); export const useCamMode = create((set: any) => ({ - camMode: "ThirdPerson", - setCamMode: (x: any) => set({ camMode: x }), + camMode: "ThirdPerson", + setCamMode: (x: any) => set({ camMode: x }), })); export const useUserName = create((set: any) => ({ - userName: "", - setUserName: (x: any) => set({ userName: x }), + userName: "", + setUserName: (x: any) => set({ userName: x }), })); export const useObjectPosition = create((set: any) => ({ - objectPosition: { x: undefined, y: undefined, z: undefined }, - setObjectPosition: (newObjectPosition: any) => - set({ objectPosition: newObjectPosition }), + objectPosition: { x: undefined, y: undefined, z: undefined }, + setObjectPosition: (newObjectPosition: any) => + set({ objectPosition: newObjectPosition }), })); export const useObjectRotation = create((set: any) => ({ - objectRotation: { x: undefined, y: undefined, z: undefined }, - setObjectRotation: (newObjectRotation: any) => - set({ objectRotation: newObjectRotation }), + objectRotation: { x: undefined, y: undefined, z: undefined }, + setObjectRotation: (newObjectRotation: any) => + set({ objectRotation: newObjectRotation }), })); export const useDrieTemp = create((set: any) => ({ - drieTemp: undefined, - setDrieTemp: (x: any) => set({ drieTemp: x }), + drieTemp: undefined, + setDrieTemp: (x: any) => set({ drieTemp: x }), })); export const useActiveUsers = create((set: any) => ({ - activeUsers: [], - setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => - set((state: { activeUsers: any[] }) => ({ - activeUsers: - typeof callback === "function" ? callback(state.activeUsers) : callback, - })), + activeUsers: [], + setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => + set((state: { activeUsers: any[] }) => ({ + activeUsers: + typeof callback === "function" ? callback(state.activeUsers) : callback, + })), })); export const useDrieUIValue = create((set: any) => ({ - drieUIValue: { touch: null, temperature: null, humidity: null }, + drieUIValue: { touch: null, temperature: null, humidity: null }, - setDrieUIValue: (x: any) => - set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), + setDrieUIValue: (x: any) => + set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), - setTouch: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, touch: value }, - })), - setTemperature: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, temperature: value }, - })), - setHumidity: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, humidity: value }, - })), + setTouch: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, touch: value }, + })), + setTemperature: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, temperature: value }, + })), + setHumidity: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, humidity: value }, + })), })); export const useStartSimulation = create((set: any) => ({ - startSimulation: false, - setStartSimulation: (x: any) => set({ startSimulation: x }), + startSimulation: false, + setStartSimulation: (x: any) => set({ startSimulation: x }), })); export const useEyeDropMode = create((set: any) => ({ - eyeDropMode: false, - setEyeDropMode: (x: any) => set({ eyeDropMode: x }), + eyeDropMode: false, + setEyeDropMode: (x: any) => set({ eyeDropMode: x }), })); export const useEditingPoint = create((set: any) => ({ - editingPoint: false, - setEditingPoint: (x: any) => set({ editingPoint: x }), + editingPoint: false, + setEditingPoint: (x: any) => set({ editingPoint: x }), })); export const usezoneTarget = create((set: any) => ({ - zoneTarget: [], - setZoneTarget: (x: any) => set({ zoneTarget: x }), + zoneTarget: [], + setZoneTarget: (x: any) => set({ zoneTarget: x }), })); export const usezonePosition = create((set: any) => ({ - zonePosition: [], - setZonePosition: (x: any) => set({ zonePosition: x }), + zonePosition: [], + setZonePosition: (x: any) => set({ zonePosition: x }), })); interface EditPositionState { - Edit: boolean; - setEdit: (value: boolean) => void; + Edit: boolean; + setEdit: (value: boolean) => void; } export const useEditPosition = create((set) => ({ - Edit: false, - setEdit: (value) => set({ Edit: value }), + Edit: false, + setEdit: (value) => set({ Edit: value }), })); export const useAsset3dWidget = create((set: any) => ({ - widgetSelect: "", - setWidgetSelect: (x: any) => set({ widgetSelect: x }), + widgetSelect: "", + setWidgetSelect: (x: any) => set({ widgetSelect: x }), })); export const useWidgetSubOption = create((set: any) => ({ - widgetSubOption: "2D", - setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), + widgetSubOption: "2D", + setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), })); export const useLimitDistance = create((set: any) => ({ - limitDistance: true, - setLimitDistance: (x: any) => set({ limitDistance: x }), + limitDistance: true, + setLimitDistance: (x: any) => set({ limitDistance: x }), })); export const useTileDistance = create((set: any) => ({ - gridValue: { size: CONSTANTS.gridConfig.size, divisions: CONSTANTS.gridConfig.divisions }, - planeValue: { height: CONSTANTS.planeConfig.height, width: CONSTANTS.planeConfig.width }, + gridValue: { + size: CONSTANTS.gridConfig.size, + divisions: CONSTANTS.gridConfig.divisions, + }, + planeValue: { + height: CONSTANTS.planeConfig.height, + width: CONSTANTS.planeConfig.width, + }, - setGridValue: (value: any) => - set((state: any) => ({ - gridValue: { ...state.gridValue, ...value }, - })), + setGridValue: (value: any) => + set((state: any) => ({ + gridValue: { ...state.gridValue, ...value }, + })), - setPlaneValue: (value: any) => - set((state: any) => ({ - planeValue: { ...state.planeValue, ...value }, - })), + setPlaneValue: (value: any) => + set((state: any) => ({ + planeValue: { ...state.planeValue, ...value }, + })), })); export const usePlayAgv = create((set, get) => ({ - PlayAgv: [], - setPlayAgv: (updateFn: (prev: any[]) => any[]) => - set({ PlayAgv: updateFn(get().PlayAgv) }), + PlayAgv: [], + setPlayAgv: (updateFn: (prev: any[]) => any[]) => + set({ PlayAgv: updateFn(get().PlayAgv) }), })); // Define the Asset type type Asset = { - id: string; - name: string; - position?: [number, number, number]; // Optional: 3D position - rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation + id: string; + name: string; + position?: [number, number, number]; // Optional: 3D position + rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation }; // Zustand store type type ZoneAssetState = { - zoneAssetId: Asset | null; - setZoneAssetId: (asset: Asset | null) => void; + zoneAssetId: Asset | null; + setZoneAssetId: (asset: Asset | null) => void; }; // Zustand store export const useZoneAssetId = create((set) => ({ - zoneAssetId: null, - setZoneAssetId: (asset) => set({ zoneAssetId: asset }), + zoneAssetId: null, + setZoneAssetId: (asset) => set({ zoneAssetId: asset }), })); // version visible hidden interface VersionHistoryState { - viewVersionHistory: boolean; - setVersionHistory: (value: boolean) => void; + viewVersionHistory: boolean; + setVersionHistory: (value: boolean) => void; } const useVersionHistoryStore = create((set) => ({ - viewVersionHistory: false, - setVersionHistory: (value) => set({ viewVersionHistory: value }), + viewVersionHistory: false, + setVersionHistory: (value) => set({ viewVersionHistory: value }), })); export default useVersionHistoryStore; interface ShortcutStore { - showShortcuts: boolean; - setShowShortcuts: (value: boolean) => void; - toggleShortcuts: () => void; + showShortcuts: boolean; + setShowShortcuts: (value: boolean) => void; + toggleShortcuts: () => void; } export const useShortcutStore = create((set) => ({ - showShortcuts: false, - setShowShortcuts: (value) => set({ showShortcuts: value }), - toggleShortcuts: () => - set((state) => ({ showShortcuts: !state.showShortcuts })), -})); \ No newline at end of file + showShortcuts: false, + setShowShortcuts: (value) => set({ showShortcuts: value }), + toggleShortcuts: () => + set((state) => ({ showShortcuts: !state.showShortcuts })), +})); + +interface CompareStore { + comparePopUp: boolean; + setComparePopUp: (value: boolean) => void; + toggleComparePopUp: () => void; +} + +export const useCompareStore = create((set) => ({ + comparePopUp: false, + setComparePopUp: (value) => set({ comparePopUp: value }), + toggleComparePopUp: () => + set((state) => ({ comparePopUp: !state.comparePopUp })), +})); diff --git a/app/src/store/builder/useAssetStore.ts b/app/src/store/builder/useAssetStore.ts new file mode 100644 index 0000000..326a877 --- /dev/null +++ b/app/src/store/builder/useAssetStore.ts @@ -0,0 +1,221 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +interface AssetsStore { + assets: Assets; + + // Asset CRUD operations + addAsset: (asset: Asset) => void; + removeAsset: (modelUuid: string) => void; + updateAsset: (modelUuid: string, updates: Partial) => void; + setAssets: (assets: Assets) => void; + + // Asset properties + setPosition: (modelUuid: string, position: [number, number, number]) => void; + setRotation: (modelUuid: string, rotation: [number, number, number]) => void; + setLock: (modelUuid: string, isLocked: boolean) => void; + setCollision: (modelUuid: string, isCollidable: boolean) => void; + setVisibility: (modelUuid: string, isVisible: boolean) => void; + setOpacity: (modelUuid: string, opacity: number) => void; + + // Animation controls + setAnimation: (modelUuid: string, animation: string) => void; + setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void; + addAnimation: (modelUuid: string, animation: string) => void; + removeAnimation: (modelUuid: string, animation: string) => void; + + // Event data operations + addEventData: (modelUuid: string, eventData: Asset['eventData']) => void; + updateEventData: (modelUuid: string, updates: Partial) => void; + removeEventData: (modelUuid: string) => void; + + // Helper functions + getAssetById: (modelUuid: string) => Asset | undefined; + getAssetByPointUuid: (pointUuid: string) => Asset | undefined; + hasAsset: (modelUuid: string) => boolean; +} + +export const useAssetsStore = create()( + immer((set, get) => ({ + assets: [], + + // Asset CRUD operations + addAsset: (asset) => { + set((state) => { + if (!state.assets.some(a => a.modelUuid === asset.modelUuid)) { + state.assets.push(asset); + } + }); + }, + + removeAsset: (modelUuid) => { + set((state) => { + state.assets = state.assets.filter(a => a.modelUuid !== modelUuid); + }); + }, + + updateAsset: (modelUuid, updates) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + Object.assign(asset, updates); + } + }); + }, + + setAssets: (assets) => { + set((state) => { + state.assets = assets; + }); + }, + + // Asset properties + setPosition: (modelUuid, position) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.position = position; + } + }); + }, + + setRotation: (modelUuid, rotation) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.rotation = rotation; + } + }); + }, + + setLock: (modelUuid, isLocked) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.isLocked = isLocked; + } + }); + }, + + setCollision: (modelUuid, isCollidable) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.isCollidable = isCollidable; + } + }); + }, + + setVisibility: (modelUuid, isVisible) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.isVisible = isVisible; + } + }); + }, + + setOpacity: (modelUuid, opacity) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.opacity = opacity; + } + }); + }, + + // Animation controls + setAnimation: (modelUuid, animation) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + if (!asset.animationState) { + asset.animationState = { current: animation, playing: false }; + } else { + asset.animationState.current = animation; + } + } + }); + }, + + setCurrentAnimation: (modelUuid, current, isPlaying) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animationState) { + asset.animationState.current = current; + asset.animationState.playing = isPlaying; + } + }); + }, + + addAnimation: (modelUuid, animation) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + if (!asset.animations) { + asset.animations = [animation]; + } else if (!asset.animations.includes(animation)) { + asset.animations.push(animation); + } + } + }); + }, + + removeAnimation: (modelUuid, animation) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animations) { + asset.animations = asset.animations.filter(a => a !== animation); + if (asset.animationState?.current === animation) { + asset.animationState.playing = false; + asset.animationState.current = ''; + } + } + }); + }, + + // Event data operations + addEventData: (modelUuid, eventData) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + asset.eventData = eventData; + } + }); + }, + + updateEventData: (modelUuid, updates) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.eventData) { + asset.eventData = { ...asset.eventData, ...updates }; + } + }); + }, + + removeEventData: (modelUuid) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset) { + delete asset.eventData; + } + }); + }, + + // Helper functions + getAssetById: (modelUuid) => { + return get().assets.find(a => a.modelUuid === modelUuid); + }, + + getAssetByPointUuid: (pointUuid) => { + return get().assets.find(asset => + asset.eventData?.point?.uuid === pointUuid || + asset.eventData?.points?.some(p => p.uuid === pointUuid) + ); + }, + + hasAsset: (modelUuid) => { + return get().assets.some(a => a.modelUuid === modelUuid); + } + })) +); \ No newline at end of file diff --git a/app/src/store/usePlayButtonStore.ts b/app/src/store/usePlayButtonStore.ts index bed2bd7..fe85d6a 100644 --- a/app/src/store/usePlayButtonStore.ts +++ b/app/src/store/usePlayButtonStore.ts @@ -33,3 +33,17 @@ export const useAnimationPlaySpeed = create((set) => ({ speed: 1, setSpeed: (value) => set({ speed: value }), })); + +interface CameraModeState { + walkMode: boolean; + setWalkMode: (enabled: boolean) => void; + toggleWalkMode: () => void; +} + +const useCameraModeStore = create((set) => ({ + walkMode: false, + setWalkMode: (enabled) => set({ walkMode: enabled }), + toggleWalkMode: () => set((state) => ({ walkMode: !state.walkMode })), +})); + +export default useCameraModeStore; diff --git a/app/src/styles/components/footer/footer.scss b/app/src/styles/components/footer/footer.scss index 728e222..7bf951a 100644 --- a/app/src/styles/components/footer/footer.scss +++ b/app/src/styles/components/footer/footer.scss @@ -1,10 +1,9 @@ @use "../../abstracts/variables" as *; @use "../../abstracts/mixins" as *; - .footer-container { width: 100%; - position: absolute; + position: fixed; bottom: 0; left: 0; z-index: 3; @@ -12,6 +11,7 @@ display: flex; flex-direction: column; gap: 6px; + pointer-events: none; .footer-wrapper { display: flex; @@ -33,7 +33,7 @@ .selector { color: var(--text-color); - font-size: var(--font-size-small) + font-size: var(--font-size-small); } .icon { @@ -46,8 +46,8 @@ display: flex; gap: 6px; position: relative; - - // dummy + pointer-events: all; + // dummy .bg-dummy { background: var(--background-color-solid); position: absolute; @@ -165,11 +165,23 @@ min-height: 320px; height: 320px; border-radius: 18px; - - + pointer-events: all; background: var(--background-color); backdrop-filter: blur(20px); - + .close-button { + position: absolute; + @include flex-center; + height: 26px; + width: 26px; + right: 12px; + top: 10px; + background: var(--background-color); + border-radius: #{$border-radius-medium}; + outline: 1px solid var(--border-color); + &:hover{ + background: var(--background-color-solid); + } + } .header { display: flex; justify-content: center; @@ -191,7 +203,7 @@ padding-left: 10px; &::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -243,7 +255,6 @@ .shortcut-intro { display: flex; - // align-items: center; gap: 6px; .value-wrapper { @@ -253,7 +264,6 @@ .description { font-size: var(--font-size-tiny); - } } } @@ -264,14 +274,22 @@ gap: 6px; flex-wrap: wrap; - .key { - background: linear-gradient(135.11deg, #656DC2 3.48%, #9526E5 91.33%); + background: linear-gradient( + 135.11deg, + #656dc2 3.48%, + #9526e5 91.33% + ); + @include flex-center; padding: 4px 10px; + height: 25px; border-radius: 4px; font-family: monospace; font-size: var(--font-size-tiny); color: var(--icon-default-color-active); + &:last-child{ + background: var(--background-color-button); + } } .key.add { @@ -294,19 +312,12 @@ align-items: flex-start; /* or center if vertical centering is desired */ } - - - } - - - } .shortcut-helper-overlay { max-height: 0; overflow: hidden; - // opacity: 0; transform: translateY(20px); transition: all 0.3s ease-in-out; @@ -315,4 +326,4 @@ opacity: 1; transform: translateY(0); } -} \ No newline at end of file +} diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss index c57bfb0..b7ac080 100644 --- a/app/src/styles/components/simulation/simulation.scss +++ b/app/src/styles/components/simulation/simulation.scss @@ -8,7 +8,16 @@ z-index: 2; transform: translate(-50%, 0); width: 70vw; - + transition: all 0.3s; + &.hide { + width: fit-content; + .simulation-player-container + .controls-container + .simulation-button-container { + width: 32px; + height: 24px; + } + } .simulation-player-container { background: var(--background-color); padding: 7px; @@ -90,11 +99,12 @@ @include flex-center; gap: 2px; padding: 4px 8px; - min-width: 64px; + width: 64px; background: var(--background-color); border-radius: #{$border-radius-extra-large}; height: fit-content; cursor: pointer; + transition: all 0.2s; &:hover { outline: 1px solid var(--border-color); @@ -347,6 +357,86 @@ } } +.controls-player-container { + min-width: 26vw; + max-width: 80vw; + border-radius: 15px; + gap: 40px; + background: var(--background-color); + backdrop-filter: blur(20px); + cursor: pointer; + @include flex-center; + justify-content: space-between; + position: fixed; + bottom: 32px; + left: 50%; + transform: translate(-50%, 0); + color: var(--accent-color); + z-index: 100; + isolation: isolate; + font-weight: 700; + padding: 8px; + transition: all 0.2s; + + &.hide { + min-width: auto; + width: 92px; + } + + .controls-left, + .controls-right { + display: flex; + align-items: center; + gap: 12px; + font-size: var(--font-size-small); + + .label { + text-transform: capitalize; + font-size: var(--font-size-small); + } + + .walkMode-wrapper { + display: flex; + align-items: center; + gap: 4px; + + .input-toggle-container { + padding: 0; + gap: 4px; + + .label { + font-size: var(--font-size-small); + } + } + } + + .btn-wrapper { + @include flex-center; + gap: 2px; + padding: 4px 8px; + width: 64px; + background: var(--background-color); + border-radius: 20px; + height: fit-content; + cursor: pointer; + transition: all 0.2s; + outline: 1px solid transparent; + &:hover { + outline: 1px solid var(--border-color); + color: var(--accent-color); + } + &.hide { + width: 32px; + } + .icon { + width: 16px; + height: 16px; + @include flex-center; + } + } + } +} + .processDisplayer { border-radius: #{$border-radius-large}; outline: 1px solid var(--border-color); @@ -454,7 +544,7 @@ } } } - .storage-container{ + .storage-container { font-size: var(--font-size-tiny); color: var(--highlight-text-color); } diff --git a/app/src/styles/components/tools.scss b/app/src/styles/components/tools.scss index 05e991a..c07f11a 100644 --- a/app/src/styles/components/tools.scss +++ b/app/src/styles/components/tools.scss @@ -12,12 +12,16 @@ box-shadow: #{$box-shadow-medium}; border-radius: #{$border-radius-large}; width: fit-content; - transition: width 0.2s; background: var(--background-color); backdrop-filter: blur(20px); z-index: 2; outline: 1px solid var(--border-color); outline-offset: -1px; + transition: transform 0.4s ease-in-out 0.01s; + + &.visible { + transform: translate(-50%, -310px); + } .split { height: 20px; @@ -47,9 +51,11 @@ position: relative; &:hover { - background: color-mix(in srgb, - var(--highlight-accent-color) 60%, - transparent); + background: color-mix( + in srgb, + var(--highlight-accent-color) 60%, + transparent + ); .tooltip { opacity: 1; @@ -78,9 +84,11 @@ position: relative; &:hover { - background: color-mix(in srgb, - var(--highlight-accent-color) 60%, - transparent); + background: color-mix( + in srgb, + var(--highlight-accent-color) 60%, + transparent + ); } .drop-down-container { @@ -178,52 +186,6 @@ } -.tools-container { - transition: transform 0.4s ease-in-out 0.01s; - - &.visible { - transform: translate(-50%, -310px); - } -} - - -.exitPlay { - width: 30px; - height: 30px; - border-radius: #{$border-radius-circle}; - background: var(--highlight-accent-color); - cursor: pointer; - @include flex-center; - position: fixed; - bottom: 60px; - left: 50%; - transform: translate(-50%, 0); - color: var(--accent-color); - z-index: 100; - isolation: isolate; - font-weight: 700; - - &:hover { - font-weight: 500; - background: var(--accent-color); - color: var(--highlight-accent-color); - - &::after { - animation: pulse 1s ease-out infinite; - } - } - - &::after { - content: ""; - position: absolute; - height: 100%; - width: 100%; - background: var(--background-color-secondary); - border-radius: #{$border-radius-circle}; - z-index: -1; - } -} - @keyframes pulse { 0% { opacity: 0; @@ -250,4 +212,4 @@ width: fit-content; opacity: 1; } -} \ No newline at end of file +} diff --git a/app/src/styles/layout/compareLayoutPopUp.scss b/app/src/styles/layout/compareLayoutPopUp.scss new file mode 100644 index 0000000..5b59390 --- /dev/null +++ b/app/src/styles/layout/compareLayoutPopUp.scss @@ -0,0 +1,267 @@ +.compare-container { + width: 100vw; + height: 100vh; + + background: var(--background-color-secondary); + backdrop-filter: blur(2px); + display: flex; + justify-content: center; + align-items: center; + + .compare-wrapper { + min-width: 312px; + min-height: 363px; + background: var(--background-color); + padding: 20px; + border-radius: 24px; + display: flex; + flex-direction: column; + gap: 12px; + backdrop-filter: blur(50px); + outline: 1px solid var(--border-color); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); + position: relative; + + .grid-wrapper { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + border-radius: 20px; + position: relative; + + background-size: 52px 52px; + background-repeat: repeat; + + + + >* { + position: relative; + z-index: 2; + } + + .header { + text-align: center; + font-size: var(--font-size-small); + font-weight: 600; + margin-bottom: 8px; + } + + .cards-container { + margin-top: 30px; + display: flex; + justify-content: center; + align-items: center; + + .card { + background: var(--background-color); + backdrop-filter: blur(20px); + width: 100px; + height: 100px; + margin: 10px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + position: absolute; + + + } + + + } + + .card-layout-wrapper { + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 20px; + outline: 1px solid var(--border-color); + } + + .card-layout-container { + width: 130px; + height: 130px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + justify-content: space-around; + + align-items: center; + padding: 10px; + + outline: 1px solid var(--border-color); + outline-offset: -1px; + border-radius: 12px; + background: var(--background-color); + + .tab-header { + display: flex; + justify-content: space-between; + width: 100%; + + .label-tab { + font-size: var(--font-size-small); + font-weight: 500; + } + + .status { + width: 10px; + height: 10px; + border-radius: 50%; + background-color: #5a33a3; + } + + } + + + + .skeleton-wrapper { + + width: 100%; + display: flex; + flex-direction: column; + gap: 6px; + justify-content: center; + align-items: center; + + .skeleton { + height: 2.662480115890503px; + + &:nth-child(1) { + width: 40%; + } + + &:nth-child(2) { + width: 70%; + } + + &:nth-child(3) { + width: 40%; + } + } + + } + } + + .button-wrapper { + margin-top: 20px; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + + .button-group { + display: flex; + gap: 8px; + + .btn { + padding: 10px 16px; + font-size: var(--font-size-small); + font-weight: 600; + border-radius: 30px; + cursor: pointer; + transition: all 0.3s ease; + user-select: none; + } + + .save { + background-color: #6f42c1; + color: white; + box-shadow: 0px 2px 8px rgba(111, 66, 193, 0.4); + + &:hover { + background-color: #5a33a3; + } + } + + .replace { + border: 1px solid #6f42c1; + color: #6f42c1; + background: transparent; + + &:hover { + background-color: rgba(111, 66, 193, 0.08); + } + } + } + + .cancel { + color: red; + font-size: var(--font-size-small); + font-weight: 500; + cursor: pointer; + } + } + } + + .footer { + font-size: var(--font-size-small); + opacity: 0.7; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + } + } +} + + + + +.cards-container { + margin-top: 30px; + position: relative; + width: 100%; + height: 150px; + + .card { + position: absolute; + width: 100px; + height: 100px; + background: white; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 1; + + &:nth-child(1) { + left: -10px; + transform: scale(0.8); + } + + &:nth-child(2) { + right: -10px; + transform: scale(0.8); + } + + &:nth-child(4) { + left: 20px; + transform: scale(1); + } + + &:nth-child(5) { + right: 20px; + transform: scale(1); + } + } + + .card-layout-wrapper { + position: absolute; + left: 50%; + top: 0; + transform: translateX(-50%); + z-index: 2; + + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 20px; + outline: 1px solid var(--border-color); + } +} \ No newline at end of file diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 6c25a3e..1e3c460 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -33,7 +33,8 @@ @use 'layout/sidebar'; @use 'layout/popup'; @use 'layout/toast'; -@use 'layout/skeleton.scss'; +@use 'layout/skeleton'; +@use 'layout/compareLayoutPopUp'; // pages @use 'pages/dashboard'; diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index e454a34..3140802 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -22,7 +22,6 @@ .floating { // width: calc(var(--realTimeViz-container-width) * 0.2px); - // transform: scale(min(1, calc(var(--realTimeViz-container-width) / 1000))); min-width: 230px; @@ -54,7 +53,7 @@ } } - .zone-wrapper { + .zone-container { display: flex; background: var(--background-color); backdrop-filter: blur(10px); @@ -63,7 +62,6 @@ left: 50%; gap: 6px; border-radius: #{$border-radius-medium}; - overflow: auto; max-width: calc(100% - 500px); z-index: 3; @@ -71,6 +69,10 @@ pointer-events: all; transition: all 0.3s linear; + &.bottom{ + bottom: var(--bottomWidth); + } + &::-webkit-scrollbar { display: none; } @@ -117,6 +119,13 @@ } } + .zone-container.visualization-playing { + bottom: 74px; + &.bottom{ + bottom: var(--bottomWidth); + } + } + .zone-wrapper.bottom { bottom: var(--bottomWidth); } @@ -363,6 +372,9 @@ } } + + + // Side Buttons .side-button-container { position: absolute; @@ -968,4 +980,4 @@ display: flex; justify-content: center; align-items: center; -} +} \ No newline at end of file diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts new file mode 100644 index 0000000..adfdc78 --- /dev/null +++ b/app/src/types/builderTypes.d.ts @@ -0,0 +1,31 @@ +interface Asset { + modelUuid: string; + modelName: string; + assetId: string; + position: [number, number, number]; + rotation: [number, number, number]; + isLocked: boolean; + isCollidable: boolean; + isVisible: boolean; + opacity: number; + animations?: string[]; + animationState?: { + current: string; + playing: boolean; + }; + eventData?: { + type: string; + point?: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + } + points?: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + }[]; + } +}; + +type Assets = Asset[]; \ No newline at end of file diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 813d2cb..db35d84 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -11,7 +11,7 @@ import { useToggleView, useToolMode, } from "../../store/builder/store"; -import { usePlayButtonStore } from "../../store/usePlayButtonStore"; +import useCameraModeStore, { usePlayButtonStore } from "../../store/usePlayButtonStore"; import { detectModifierKeys } from "./detectModifierKeys"; import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; @@ -21,7 +21,7 @@ const KeyPressListener: React.FC = () => { const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore(); const { setToggleThreeD } = useThreeDStore(); const { setToolMode } = useToolMode(); - const { setIsPlaying } = usePlayButtonStore(); + const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { toggleView, setToggleView } = useToggleView(); const { setDeleteTool } = useDeleteTool(); const { setAddAction } = useAddAction(); @@ -29,6 +29,7 @@ const KeyPressListener: React.FC = () => { const { setActiveTool } = useActiveTool(); const { clearSelectedZone } = useSelectedZoneStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore(); + const { setWalkMode } = useCameraModeStore(); const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || @@ -66,7 +67,7 @@ const KeyPressListener: React.FC = () => { }; const handleBuilderShortcuts = (key: string) => { - if (activeModule !== "builder") return; + if (activeModule !== "builder" || isPlaying) return; if (key === "TAB") { const toggleTo2D = toggleView; @@ -168,6 +169,7 @@ const KeyPressListener: React.FC = () => { } if (keyCombination === "ESCAPE") { + setWalkMode(false); setActiveTool("cursor"); setActiveSubTool("cursor"); setIsPlaying(false); @@ -189,7 +191,7 @@ const KeyPressListener: React.FC = () => { window.addEventListener("keydown", handleKeyPress); return () => window.removeEventListener("keydown", handleKeyPress); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeModule, toggleUIRight, toggleUILeft, toggleView, showShortcuts]); + }, [activeModule, toggleUIRight, toggleUILeft, toggleView, showShortcuts, isPlaying]); return null; };