From a7e2e08d912ffdd45568ac16aa53dd5a777ea15e Mon Sep 17 00:00:00 2001 From: Vishnu Date: Fri, 1 Aug 2025 12:14:04 +0530 Subject: [PATCH 01/14] dof commented --- app/src/modules/scene/postProcessing/postProcessing.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 3b96455..fc9ce0b 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -79,11 +79,11 @@ export default function PostProcessing() { denoiseRadius={6} denoiseSamples={16} /> - + /> */} Date: Fri, 1 Aug 2025 12:16:17 +0530 Subject: [PATCH 02/14] bloom comented --- app/src/modules/scene/postProcessing/postProcessing.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index fc9ce0b..8aacd96 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -84,12 +84,12 @@ export default function PostProcessing() { focalLength={0.15} bokehScale={2} /> */} - + /> */} {selectedWallAsset && ( Date: Fri, 1 Aug 2025 13:28:42 +0530 Subject: [PATCH 03/14] refactor: stats moved to sepetate component --- app/src/modules/scene/helpers/StatsHelper.tsx | 22 +++++++++++++++++++ app/src/modules/scene/scene.tsx | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 app/src/modules/scene/helpers/StatsHelper.tsx diff --git a/app/src/modules/scene/helpers/StatsHelper.tsx b/app/src/modules/scene/helpers/StatsHelper.tsx new file mode 100644 index 0000000..486488b --- /dev/null +++ b/app/src/modules/scene/helpers/StatsHelper.tsx @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; +import { Stats } from "@react-three/drei"; +import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys"; + +export default function StatsHelper() { + const [visible, setVisible] = useState(false); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + if (keyCombination === "F1") { + event.preventDefault(); + setVisible(prev => !prev); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, []); + + return visible ? : null; +} diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 0f08a2d..0e7a277 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo } from "react"; import { Canvas } from "@react-three/fiber"; -import { KeyboardControls, Stats } from "@react-three/drei"; +import { KeyboardControls } from "@react-three/drei"; import { useSceneContext } from "./sceneContext"; import Builder from "../builder/builder"; @@ -14,6 +14,7 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { Color, SRGBColorSpace } from "three"; +import StatsHelper from "./helpers/StatsHelper"; export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) { const map = useMemo(() => [ @@ -76,7 +77,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co - + ); From 35153c8c79ae42caf0326de2346969202bb3931a Mon Sep 17 00:00:00 2001 From: Poovizhi Date: Sat, 2 Aug 2025 18:23:42 +0530 Subject: [PATCH 04/14] updated dashboard duplication --- .../components/Dashboard/DashboardCard.tsx | 541 ++-- .../components/Dashboard/DashboardHome.tsx | 18 +- .../Dashboard/DashboardProjects.tsx | 73 +- app/src/components/ui/FileMenu.tsx | 169 +- app/src/modules/scene/scene.tsx | 146 +- app/src/pages/Project.tsx | 5 +- .../services/dashboard/duplicateProject.ts | 10 +- app/src/services/dashboard/updateProject.ts | 2 + app/src/store/simulation/useProductStore.ts | 2193 ++++++++++------- 9 files changed, 1782 insertions(+), 1375 deletions(-) diff --git a/app/src/components/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx index 68d91e7..0f75d19 100644 --- a/app/src/components/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -2,289 +2,314 @@ import React, { useState, useRef, useEffect, act } from "react"; import img from "../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; import { getUserData } from "../../functions/getUserData"; -import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store"; +import { + useLoadingProgress, + useProjectName, + useSocketStore, +} from "../../store/builder/store"; import { viewProject } from "../../services/dashboard/viewProject"; import OuterClick from "../../utils/outerClick"; import { KebabIcon } from "../icons/ExportCommonIcons"; import { getAllProjects } from "../../services/dashboard/getAllProjects"; +import { updateProject } from "../../services/dashboard/updateProject"; interface DashBoardCardProps { - projectName: string; - thumbnail: any; - projectId: string; - createdAt?: string; - isViewed?: string; - createdBy?: { _id: string, userName: string }; - handleDeleteProject?: (projectId: string) => Promise; - handleTrashDeleteProject?: (projectId: string) => Promise; - handleRestoreProject?: (projectId: string) => Promise; - handleDuplicateWorkspaceProject?: ( - projectId: string, - projectName: string, - thumbnail: string, - userId?: string - ) => Promise; - handleDuplicateRecentProject?: ( - projectId: string, - projectName: string, - thumbnail: string - ) => Promise; - active?: string; - setIsSearchActive?: React.Dispatch>; - setRecentDuplicateData?: React.Dispatch>; - setProjectDuplicateData?: React.Dispatch>; - setActiveFolder?: React.Dispatch>; + projectName: string; + thumbnail: any; + projectId: string; + createdAt?: string; + isViewed?: string; + createdBy?: { _id: string; userName: string }; + handleDeleteProject?: (projectId: string) => Promise; + handleTrashDeleteProject?: (projectId: string) => Promise; + handleRestoreProject?: (projectId: string) => Promise; + handleDuplicateWorkspaceProject?: ( + projectId: string, + projectName: string, + thumbnail: string, + userId?: string + ) => Promise; + handleDuplicateRecentProject?: ( + projectId: string, + projectName: string, + thumbnail: string + ) => Promise; + active?: string; + setIsSearchActive?: React.Dispatch>; + setRecentDuplicateData?: React.Dispatch>; + setProjectDuplicateData?: React.Dispatch>; + setActiveFolder?: React.Dispatch>; } type RelativeTimeFormatUnit = any; const DashboardCard: React.FC = ({ - projectName, - thumbnail, - projectId, - active, - handleDeleteProject, - handleRestoreProject, - handleTrashDeleteProject, - handleDuplicateWorkspaceProject, - handleDuplicateRecentProject, - createdAt, - createdBy, - setRecentDuplicateData, - setProjectDuplicateData, - setActiveFolder + projectName, + thumbnail, + projectId, + active, + handleDeleteProject, + handleRestoreProject, + handleTrashDeleteProject, + handleDuplicateWorkspaceProject, + handleDuplicateRecentProject, + createdAt, + createdBy, + setRecentDuplicateData, + setProjectDuplicateData, + setActiveFolder, }) => { - const navigate = useNavigate(); - const { setProjectName } = useProjectName(); - const { userId, organization, userName } = getUserData(); - const [isKebabOpen, setIsKebabOpen] = useState(false); - const [renameValue, setRenameValue] = useState(projectName); - const [isRenaming, setIsRenaming] = useState(false); - const { projectSocket } = useSocketStore(); - const { setLoadingProgress } = useLoadingProgress(); - const kebabRef = useRef(null); + const navigate = useNavigate(); + const { setProjectName } = useProjectName(); + const { userId, organization, userName } = getUserData(); + const [isKebabOpen, setIsKebabOpen] = useState(false); + const [renameValue, setRenameValue] = useState(projectName); + const [isRenaming, setIsRenaming] = useState(false); + const { projectSocket } = useSocketStore(); + const { setLoadingProgress } = useLoadingProgress(); + const kebabRef = useRef(null); - const navigateToProject = async (e: any) => { - console.log('active: ', active); - if (active && active == "trash") return; - try { - const viewProjects = await viewProject(organization, projectId, userId) - console.log('viewProjects: ', viewProjects); - console.log('projectName: ', projectName); - setLoadingProgress(1) - setProjectName(projectName); - navigate(`/${projectId}`); - } catch { + const navigateToProject = async (e: any) => { + if (active && active == "trash") return; + try { + const viewProjects = await viewProject(organization, projectId, userId); + setLoadingProgress(1); + setProjectName(projectName); + navigate(`/${projectId}`); + } catch {} + }; + + const handleOptionClick = async (option: string) => { + switch (option) { + case "delete": + if (handleDeleteProject) { + handleDeleteProject(projectId); + } else if (handleTrashDeleteProject) { + handleTrashDeleteProject(projectId); } - }; - - const handleOptionClick = async (option: string) => { - switch (option) { - case "delete": - if (handleDeleteProject) { - handleDeleteProject(projectId); - } else if (handleTrashDeleteProject) { - handleTrashDeleteProject(projectId); - } - break; - case "restore": - if (handleRestoreProject) { - await handleRestoreProject(projectId); - } - break; - case "open in new tab": - try { - if (active === "shared" && createdBy) { - console.log("ihreq"); - const newTab = await viewProject(organization, projectId, createdBy?._id); - console.log('newTab: ', newTab); - } else { - const newTab = await viewProject(organization, projectId, userId); - console.log('newTab: ', newTab); - setProjectName(projectName); - setIsKebabOpen(false); - } - } catch (error) { - - } - window.open(`/${projectId}`, "_blank"); - break; - case "rename": - setIsRenaming(true); - break; - case "duplicate": - if (handleDuplicateWorkspaceProject) { - setProjectDuplicateData && - setProjectDuplicateData({ - projectId, - projectName, - thumbnail, - }); - await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId); - if (active === "shared" && setActiveFolder) { - setActiveFolder("myProjects") - } - } else if (handleDuplicateRecentProject) { - setRecentDuplicateData && - setRecentDuplicateData({ - projectId, - projectName, - thumbnail, - userId - }); - await handleDuplicateRecentProject(projectId, projectName, thumbnail); - } - break; - default: - break; + break; + case "restore": + if (handleRestoreProject) { + await handleRestoreProject(projectId); } - setIsKebabOpen(false); - }; - - OuterClick({ - contextClassName: ["kebab-wrapper", "kebab-options-wrapper"], - setMenuVisible: () => setIsKebabOpen(false), - }); - - const handleProjectName = async (projectName: string) => { - setRenameValue(projectName); - if (!projectId) return; + break; + case "open in new tab": try { - const projects = await getAllProjects(userId, organization); - if (!projects || !projects.Projects) return; - let projectUuid = projects.Projects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId + if (active === "shared" && createdBy) { + const newTab = await viewProject( + organization, + projectId, + createdBy?._id ); - const updateProjects = { - projectId: projectUuid, - organization, - userId, - projectName, - thumbnail: undefined, - }; + } else { + const newTab = await viewProject(organization, projectId, userId); - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } - } catch (error) { } - }; - - function getRelativeTime(dateString: string): string { - const date = new Date(dateString); - const now = new Date(); - const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); - - const intervals: Record = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1, - }; - - const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); - - for (const key in intervals) { - const unit = key as RelativeTimeFormatUnit; - const diff = Math.floor(diffInSeconds / intervals[unit]); - if (diff >= 1) { - return rtf.format(-diff, unit); - } + setProjectName(projectName); + setIsKebabOpen(false); + } + } catch (error) {} + window.open(`/${projectId}`, "_blank"); + break; + case "rename": + setIsRenaming(true); + break; + case "duplicate": + if (handleDuplicateWorkspaceProject) { + setProjectDuplicateData && + setProjectDuplicateData({ + projectId, + projectName, + thumbnail, + }); + await handleDuplicateWorkspaceProject( + projectId, + projectName, + thumbnail, + userId + ); + if (active === "shared" && setActiveFolder) { + setActiveFolder("myProjects"); + } + } else if (handleDuplicateRecentProject) { + setRecentDuplicateData && + setRecentDuplicateData({ + projectId, + projectName, + thumbnail, + userId, + }); + await handleDuplicateRecentProject(projectId, projectName, thumbnail); } - return "just now"; + break; + default: + break; } + setIsKebabOpen(false); + }; - const kebabOptionsMap: Record = { - default: ["rename", "delete", "duplicate", "open in new tab"], - trash: ["restore", "delete"], - shared: ["duplicate", "open in new tab"], + OuterClick({ + contextClassName: ["kebab-wrapper", "kebab-options-wrapper"], + setMenuVisible: () => setIsKebabOpen(false), + }); + + const handleProjectName = async (projectName: string) => { + setRenameValue(projectName); + if (!projectId) return; + try { + const projects = await getAllProjects(userId, organization); + if (!projects || !projects.Projects) return; + let projectUuid = projects.Projects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); + const updateProjects = { + projectId: projectUuid?._id, + organization, + userId, + projectName, + thumbnail: undefined, + }; + // const updatedProjectName = await updateProject( + // projectUuid._id, + // userId, + // organization, + // undefined, + // projectName + // ); + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + } catch (error) {} + }; + + function getRelativeTime(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + const intervals: Record = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1, }; - const getOptions = () => { - if (active === "trash") return kebabOptionsMap.trash; - if (active === "shared") return kebabOptionsMap.shared; - if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared; - return kebabOptionsMap.default; - }; + const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); - return ( - - - - - {isKebabOpen && ( -
- {getOptions().map((option) => ( - - ))} -
+
+ {isRenaming ? ( + { + e.stopPropagation(); + handleProjectName(e.target.value); + }} + onBlur={() => { + setIsRenaming(false); + setProjectName(renameValue); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + setProjectName(renameValue); + setIsRenaming(false); + } + }} + autoFocus + /> + ) : ( + {renameValue} )} - - ); + {createdAt && ( +
+ {active && active == "trash" ? `Trashed by you` : `Edited `}{" "} + {getRelativeTime(createdAt)} +
+ )} +
+
+
+ {!createdBy + ? userName + ? userName?.charAt(0).toUpperCase() + : "A" + : createdBy?.userName?.charAt(0).toUpperCase()} +
+ +
+ + + {isKebabOpen && ( +
+ {getOptions().map((option) => ( + + ))} +
+ )} + + ); }; -export default DashboardCard; \ No newline at end of file +export default DashboardCard; diff --git a/app/src/components/Dashboard/DashboardHome.tsx b/app/src/components/Dashboard/DashboardHome.tsx index 6e9dce7..9e05994 100644 --- a/app/src/components/Dashboard/DashboardHome.tsx +++ b/app/src/components/Dashboard/DashboardHome.tsx @@ -13,10 +13,10 @@ interface Project { _id: string; projectName: string; thumbnail: string; - createdBy: { _id: string, userName: string }; + createdBy: { _id: string; userName: string }; projectUuid?: string; createdAt: string; - isViewed?: string + isViewed?: string; } interface RecentProjectsData { @@ -25,12 +25,12 @@ interface RecentProjectsData { const DashboardHome: React.FC = () => { const [recentProjects, setRecentProjects] = useState({}); + const [isSearchActive, setIsSearchActive] = useState(false); const { userId, organization } = getUserData(); const { projectSocket } = useSocketStore(); const [recentDuplicateData, setRecentDuplicateData] = useState({}); - const fetchRecentProjects = async () => { try { const projects = await recentlyViewed(organization, userId); @@ -38,9 +38,7 @@ const DashboardHome: React.FC = () => { if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) { setRecentProjects(projects); } - } catch (error) { - console.error("Error fetching recent projects:", error); - } + } catch (error) {} }; const handleRecentProjectSearch = async (inputValue: string) => { @@ -65,7 +63,7 @@ const DashboardHome: React.FC = () => { // userId, // organization // ); - // console.log('deletedProject: ', deletedProject); + // //socket for delete Project const deleteProject = { @@ -91,9 +89,7 @@ const DashboardHome: React.FC = () => { }; }); setIsSearchActive(false); - } catch (error) { - console.error("Error deleting project:", error); - } + } catch (error) {} }; const handleDuplicateRecentProject = async ( @@ -163,4 +159,4 @@ const DashboardHome: React.FC = () => { ); }; -export default DashboardHome; \ No newline at end of file +export default DashboardHome; diff --git a/app/src/components/Dashboard/DashboardProjects.tsx b/app/src/components/Dashboard/DashboardProjects.tsx index ccb56b2..51888d0 100644 --- a/app/src/components/Dashboard/DashboardProjects.tsx +++ b/app/src/components/Dashboard/DashboardProjects.tsx @@ -9,6 +9,7 @@ import { deleteProject } from "../../services/dashboard/deleteProject"; import ProjectSocketRes from "./socket/projectSocketRes.dev"; import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; import { duplicateProject } from "../../services/dashboard/duplicateProject"; +import { generateUniqueId } from "../../functions/generateUniqueId"; interface Project { _id: string; @@ -27,7 +28,7 @@ const DashboardProjects: React.FC = () => { const [workspaceProjects, setWorkspaceProjects] = useState( {} ); - const [sharedwithMeProject, setSharedWithMeProjects] = useState([]) + const [sharedwithMeProject, setSharedWithMeProjects] = useState([]); const [projectDuplicateData, setProjectDuplicateData] = useState({}); const [isSearchActive, setIsSearchActive] = useState(false); const [activeFolder, setActiveFolder] = useState("myProjects"); @@ -41,7 +42,11 @@ const DashboardProjects: React.FC = () => { } if (!setWorkspaceProjects || !setIsSearchActive) return; - const searchedProject = await searchProject(organization, userId, inputValue); + const searchedProject = await searchProject( + organization, + userId, + inputValue + ); setIsSearchActive(true); setWorkspaceProjects(searchedProject.message ? {} : searchedProject); }; @@ -49,14 +54,13 @@ const DashboardProjects: React.FC = () => { const fetchAllProjects = async () => { try { const projects = await getAllProjects(userId, organization); + if (!projects || !projects.Projects) return; if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) { setWorkspaceProjects(projects); } - } catch (error) { - console.error("Error fetching projects:", error); - } + } catch (error) {} }; const handleDeleteProject = async (projectId: any) => { @@ -66,7 +70,7 @@ const DashboardProjects: React.FC = () => { // userId, // organization // ); - // console.log('deletedProject: ', deletedProject); + // const deleteProjects = { projectId, organization, @@ -77,7 +81,6 @@ const DashboardProjects: React.FC = () => { if (projectSocket) { projectSocket.emit("v1:project:delete", deleteProjects); } else { - console.error("Socket is not connected."); } setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => { if (!Array.isArray(prevDiscardedProjects?.Projects)) { @@ -92,16 +95,21 @@ const DashboardProjects: React.FC = () => { }; }); setIsSearchActive(false); - } catch (error) { - console.error("Error deleting project:", error); - } + } catch (error) {} }; const handleDuplicateWorkspaceProject = async ( projectId: string, projectName: string, - thumbnail: string, + thumbnail: string ) => { + const duplicatedProject = await duplicateProject( + projectId, + generateUniqueId(), + thumbnail, + projectName + ); + console.log("duplicatedProject: ", duplicatedProject); const duplicateProjectData = { userId, @@ -110,7 +118,7 @@ const DashboardProjects: React.FC = () => { projectUuid: projectId, projectName, }; - projectSocket.emit("v1:project:Duplicate", duplicateProjectData); + // projectSocket.emit("v1:project:Duplicate", duplicateProjectData); }; const renderProjects = () => { @@ -153,16 +161,12 @@ const DashboardProjects: React.FC = () => { )); }; - const sharedProject = async () => { try { const sharedWithMe = await sharedWithMeProjects(); - setSharedWithMeProjects(sharedWithMe) - } catch { - - } - } - + setSharedWithMeProjects(sharedWithMe); + } catch {} + }; useEffect(() => { if (!isSearchActive) { @@ -172,10 +176,9 @@ const DashboardProjects: React.FC = () => { useEffect(() => { if (activeFolder === "shared") { - sharedProject() + sharedProject(); } - }, [activeFolder]) - + }, [activeFolder]); return (
@@ -184,7 +187,10 @@ const DashboardProjects: React.FC = () => { handleProjectsSearch={handleProjectsSearch} /> -
+
-
{activeFolder == "myProjects" ? renderProjects() : renderSharedProjects()}
+
+ {activeFolder == "myProjects" + ? renderProjects() + : renderSharedProjects()} +
- {projectDuplicateData && Object.keys(projectDuplicateData).length > 0 && ( - - )} + {projectDuplicateData && + Object.keys(projectDuplicateData).length > 0 && ( + + )}
); }; export default DashboardProjects; - - diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 86146ba..0f0aabe 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -10,100 +10,101 @@ import { updateProject } from "../../services/dashboard/updateProject"; import { getUserData } from "../../functions/getUserData"; const FileMenu: React.FC = () => { - const [openMenu, setOpenMenu] = useState(false); - const containerRef = useRef(null); - let clickTimeout: NodeJS.Timeout | null = null; - const { projectName, setProjectName } = useProjectName(); - const { dashBoardSocket } = useSocketStore(); - const { projectId } = useParams(); - const { userId, organization, email } = getUserData(); + const [openMenu, setOpenMenu] = useState(false); + const containerRef = useRef(null); + let clickTimeout: NodeJS.Timeout | null = null; + const { projectName, setProjectName } = useProjectName(); + const { dashBoardSocket } = useSocketStore(); + const { projectId } = useParams(); + const { userId, organization, email } = getUserData(); - const handleClick = () => { - if (clickTimeout) return; - setOpenMenu((prev) => !prev); - clickTimeout = setTimeout(() => { - clickTimeout = null; - }, 800); + const handleClick = () => { + if (clickTimeout) return; + setOpenMenu((prev) => !prev); + clickTimeout = setTimeout(() => { + clickTimeout = null; + }, 800); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(event.target as Node) + ) { + setOpenMenu(false); + } }; - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - containerRef.current && - !containerRef.current.contains(event.target as Node) - ) { - setOpenMenu(false); - } - }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); + const handleProjectRename = async (projectName: string) => { + setProjectName(projectName); + if (!projectId) return; - const handleProjectRename = async (projectName: string) => { - setProjectName(projectName); - if (!projectId) return + // localStorage.setItem("projectName", newName); - // localStorage.setItem("projectName", newName); + try { + if (!email || !userId) return; - try { + const projects = await getAllProjects(userId, organization); + if (!projects || !projects.Projects) return; + // console.log('projects: ', projects); + let projectUuid = projects.Projects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); - if (!email || !userId) return; + const updateProjects = { + projectId: projectUuid?._id, + organization, + userId, + projectName, + thumbnail: undefined, + }; - const projects = await getAllProjects(userId, organization); - if (!projects || !projects.Projects) return; - // console.log('projects: ', projects); - let projectUuid = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId) + // if (dashBoardSocket) { + // const handleResponse = (data: any) => { + // console.log('Project update response:', data); + // dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up + // }; + // dashBoardSocket.on("v1-project:response:update", handleResponse); + // dashBoardSocket.emit("v1:project:update", updateProjects); + // } - const updateProjects = { - projectId: projectUuid, - organization, - userId, - projectName, - thumbnail: undefined - } - - // if (dashBoardSocket) { - // const handleResponse = (data: any) => { - // console.log('Project update response:', data); - // dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up - // }; - // dashBoardSocket.on("v1-project:response:update", handleResponse); - // dashBoardSocket.emit("v1:project:update", updateProjects); - // } - - //API for projects rename - const updatedProjectName = await updateProject( - projectId, - userId, - organization, - undefined, - projectName - ); - // - } catch (error) { - console.error("Error updating project name:", error); - } - }; - return ( - - ); + //API for projects rename + const updatedProjectName = await updateProject( + projectId, + userId, + organization, + undefined, + projectName + ); + console.log("updatedProjectName: ", updatedProjectName, projectId); + } catch (error) { + console.error("Error updating project name:", error); + } + }; + return ( + + ); }; export default FileMenu; diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 22e18ac..67f2ef2 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -16,69 +16,89 @@ import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { Color, SRGBColorSpace } from "three"; import StatsHelper from "./helpers/StatsHelper"; -export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) { - const map = useMemo(() => [ - { name: "forward", keys: ["ArrowUp", "w", "W"] }, - { name: "backward", keys: ["ArrowDown", "s", "S"] }, - { name: "left", keys: ["ArrowLeft", "a", "A"] }, - { name: "right", keys: ["ArrowRight", "d", "D"] }, - ], []); - const { assetStore } = useSceneContext(); - const { assets } = assetStore(); - const { userId, organization } = getUserData(); - const { projectId } = useParams(); - const { projectSocket } = useSocketStore(); - const { activeModule } = useModuleStore(); - const { loadingProgress } = useLoadingProgress(); +export default function Scene({ + layout, +}: { + readonly layout: "Main Layout" | "Comparison Layout"; +}) { + const map = useMemo( + () => [ + { name: "forward", keys: ["ArrowUp", "w", "W"] }, + { name: "backward", keys: ["ArrowDown", "s", "S"] }, + { name: "left", keys: ["ArrowLeft", "a", "A"] }, + { name: "right", keys: ["ArrowRight", "d", "D"] }, + ], + [] + ); + const { assetStore } = useSceneContext(); + const { assets } = assetStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { projectSocket } = useSocketStore(); + const { activeModule } = useModuleStore(); + const { loadingProgress } = useLoadingProgress(); - useEffect(() => { - if (!projectId && loadingProgress > 1) return; - getAllProjects(userId, organization) - .then((projects) => { - if (!projects || !projects.Projects) return; - let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId); - const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName('canvas')[0]; - if (!canvas) return; - const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png"); - const updateProjects = { - projectId: project?.projectUuid, - organization, - userId, - projectName: project?.projectName, - thumbnail: screenshotDataUrl, - }; - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } - }).catch((err) => { - console.error(err); - }); - // eslint-disable-next-line - }, [activeModule, assets, loadingProgress]) + useEffect(() => { + if (!projectId && loadingProgress > 1) return; + getAllProjects(userId, organization) + .then((projects) => { + if (!projects || !projects.Projects) return; + let project = projects.Projects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); + const canvas = document + .getElementById("sceneCanvas") + ?.getElementsByTagName("canvas")[0]; + if (!canvas) return; + const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL( + "image/png" + ); + const updateProjects = { + projectId: project?._id, + organization, + userId, + projectName: project?.projectName, + thumbnail: screenshotDataUrl, + }; + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + }) + .catch((err) => { + console.error(err); + }); + // eslint-disable-next-line + }, [activeModule, assets, loadingProgress]); - return ( - - { - e.preventDefault(); - }} - performance={{ min: 0.9, max: 1.0 }} - onCreated={(e) => { - e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d); - }} - gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} - > - - - - - - - - - ); + return ( + + { + e.preventDefault(); + }} + performance={{ min: 0.9, max: 1.0 }} + onCreated={(e) => { + e.scene.background = + layout === "Main Layout" ? null : new Color(0x19191d); + }} + gl={{ + outputColorSpace: SRGBColorSpace, + powerPreference: "high-performance", + antialias: true, + preserveDrawingBuffer: true, + }} + > + + + + + + + + + ); } diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 96c529f..2ee6b29 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -33,7 +33,8 @@ const Project: React.FC = () => { const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); const { projectId } = useParams(); - const { setProjectName } = useProjectName(); + const { projectName, setProjectName } = useProjectName(); + console.log("projectName: ", projectName); const { userId, email, organization, userName } = getUserData(); const { selectedUser } = useSelectedUserStore(); const { isLogListVisible } = useLogger(); @@ -49,6 +50,7 @@ const Project: React.FC = () => { const fetchProjects = async () => { try { const projects = await getAllProjects(userId, organization); + console.log("projects: ", projects); const shared = await sharedWithMeProjects(); const allProjects = [...(projects?.Projects || []), ...(shared || [])]; @@ -56,7 +58,6 @@ const Project: React.FC = () => { const matchedProject = allProjects.find( (val: any) => val.projectUuid === projectId || val._id === projectId ); - if (matchedProject) { setProjectName(matchedProject.projectName); await viewProject(organization, matchedProject._id, userId); diff --git a/app/src/services/dashboard/duplicateProject.ts b/app/src/services/dashboard/duplicateProject.ts index 837bb0e..68fe5c6 100644 --- a/app/src/services/dashboard/duplicateProject.ts +++ b/app/src/services/dashboard/duplicateProject.ts @@ -1,6 +1,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; export const duplicateProject = async ( + refProjectID: string, projectUuid: string, thumbnail: string, projectName: string @@ -16,7 +17,12 @@ export const duplicateProject = async ( token: localStorage.getItem("token") || "", // Coerce null to empty string refresh_token: localStorage.getItem("refreshToken") || "", }, - body: JSON.stringify({ projectUuid, thumbnail, projectName }), + body: JSON.stringify({ + refProjectID, + projectUuid, + thumbnail, + projectName, + }), } ); const newAccessToken = response.headers.get("x-access-token"); @@ -24,7 +30,7 @@ export const duplicateProject = async ( //console.log("New token received:", newAccessToken); localStorage.setItem("token", newAccessToken); } - // console.log("response: ", response); + console.log("response: ", response); if (!response.ok) { console.error("Failed to add project"); } diff --git a/app/src/services/dashboard/updateProject.ts b/app/src/services/dashboard/updateProject.ts index 42e0d44..ab9ac0f 100644 --- a/app/src/services/dashboard/updateProject.ts +++ b/app/src/services/dashboard/updateProject.ts @@ -30,6 +30,7 @@ export const updateProject = async ( body: JSON.stringify(body), } ); + console.log('body: ', body); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { @@ -41,6 +42,7 @@ export const updateProject = async ( } const result = await response.json(); + console.log('result: ', result); return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 258a8cb..3c338b1 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -1,943 +1,1290 @@ -import { create } from 'zustand'; -import { immer } from 'zustand/middleware/immer'; +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; type ProductsStore = { - products: productsSchema; + products: productsSchema; - // Product-level actions - addProduct: (productName: string, productUuid: string) => void; - setProducts: (products: productsSchema) => void; - clearProducts: () => void; - removeProduct: (productUuid: string) => void; - updateProduct: (productUuid: string, updates: Partial<{ productName: string; eventDatas: EventsSchema[] }>) => void; + // Product-level actions + addProduct: (productName: string, productUuid: string) => void; + setProducts: (products: productsSchema) => void; + clearProducts: () => void; + removeProduct: (productUuid: string) => void; + updateProduct: ( + productUuid: string, + updates: Partial<{ productName: string; eventDatas: EventsSchema[] }> + ) => void; - // Event-level actions - addEvent: (productUuid: string, event: EventsSchema) => void; - removeEvent: (productUuid: string, modelUuid: string) => void; - deleteEvent: (modelUuid: string) => EventsSchema[]; - updateEvent: (productUuid: string, modelUuid: string, updates: Partial) => EventsSchema | undefined; + // Event-level actions + addEvent: (productUuid: string, event: EventsSchema) => void; + removeEvent: (productUuid: string, modelUuid: string) => void; + deleteEvent: (modelUuid: string) => EventsSchema[]; + updateEvent: ( + productUuid: string, + modelUuid: string, + updates: Partial + ) => EventsSchema | undefined; - // Point-level actions - addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => EventsSchema | undefined; - removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => EventsSchema | undefined; - updatePoint: ( - productUuid: string, - modelUuid: string, - pointUuid: string, - updates: Partial - ) => EventsSchema | undefined; + // Point-level actions + addPoint: ( + productUuid: string, + modelUuid: string, + point: + | ConveyorPointSchema + | VehiclePointSchema + | RoboticArmPointSchema + | MachinePointSchema + | StoragePointSchema + | HumanPointSchema + ) => EventsSchema | undefined; + removePoint: ( + productUuid: string, + modelUuid: string, + pointUuid: string + ) => EventsSchema | undefined; + updatePoint: ( + productUuid: string, + modelUuid: string, + pointUuid: string, + updates: Partial< + | ConveyorPointSchema + | VehiclePointSchema + | RoboticArmPointSchema + | MachinePointSchema + | StoragePointSchema + | HumanPointSchema + > + ) => EventsSchema | undefined; - // Action-level actions - addAction: ( - productUuid: string, - modelUuid: string, - pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] - ) => EventsSchema | undefined; - removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; - updateAction: ( - productUuid: string, - actionUuid: string, - updates: Partial - ) => EventsSchema | undefined; + // Action-level actions + addAction: ( + productUuid: string, + modelUuid: string, + pointUuid: string, + action: + | ConveyorPointSchema["action"] + | VehiclePointSchema["action"] + | RoboticArmPointSchema["actions"][0] + | MachinePointSchema["action"] + | StoragePointSchema["action"] + | HumanPointSchema["actions"][0] + ) => EventsSchema | undefined; + removeAction: ( + productUuid: string, + actionUuid: string + ) => EventsSchema | undefined; + updateAction: ( + productUuid: string, + actionUuid: string, + updates: Partial< + | ConveyorPointSchema["action"] + | VehiclePointSchema["action"] + | RoboticArmPointSchema["actions"][0] + | MachinePointSchema["action"] + | StoragePointSchema["action"] + | HumanPointSchema["actions"][0] + > + ) => EventsSchema | undefined; - // Trigger-level actionss - addTrigger: ( - productUuid: string, - actionUuid: string, - trigger: TriggerSchema - ) => EventsSchema | undefined; - removeTrigger: (productUuid: string, triggerUuid: string) => EventsSchema | undefined; - updateTrigger: ( - productUuid: string, - triggerUuid: string, - updates: Partial - ) => EventsSchema | undefined; + // Trigger-level actionss + addTrigger: ( + productUuid: string, + actionUuid: string, + trigger: TriggerSchema + ) => EventsSchema | undefined; + removeTrigger: ( + productUuid: string, + triggerUuid: string + ) => EventsSchema | undefined; + updateTrigger: ( + productUuid: string, + triggerUuid: string, + updates: Partial + ) => EventsSchema | undefined; - // Renaming functions - renameProduct: (productUuid: string, newName: string) => void; - renameAction: (productUuid: string, actionUuid: string, newName: string) => EventsSchema | undefined; - renameTrigger: (productUuid: string, triggerUuid: string, newName: string) => EventsSchema | undefined; + // Renaming functions + renameProduct: (productUuid: string, newName: string) => void; + renameAction: ( + productUuid: string, + actionUuid: string, + newName: string + ) => EventsSchema | undefined; + renameTrigger: ( + productUuid: string, + triggerUuid: string, + newName: string + ) => EventsSchema | undefined; - // Helper functions - getProductById: (productUuid: string) => { productName: string; productUuid: string; eventDatas: EventsSchema[] } | undefined; - getEventByModelUuid: (productUuid: string, modelUuid: string) => EventsSchema | undefined; - getEventByActionUuid: (productUuid: string, actionUuid: string) => EventsSchema | undefined; - getEventByTriggerUuid: (productUuid: string, triggerUuid: string) => EventsSchema | undefined; - getEventByPointUuid: (productUuid: string, pointUuid: string) => EventsSchema | undefined; - getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema | undefined; - getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; - getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; - getModelUuidByPointUuid: (productUuid: string, actionUuid: string) => (string) | undefined; - getModelUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; - getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; - getTriggerByUuid: (productUuid: string, triggerUuid: string) => TriggerSchema | undefined; - getTriggersByTriggeredPointUuid: (productUuid: string, triggeredPointUuid: string) => TriggerSchema[]; - getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean; + // Helper functions + getProductById: ( + productUuid: string + ) => + | { productName: string; productUuid: string; eventDatas: EventsSchema[] } + | undefined; + getEventByModelUuid: ( + productUuid: string, + modelUuid: string + ) => EventsSchema | undefined; + getEventByActionUuid: ( + productUuid: string, + actionUuid: string + ) => EventsSchema | undefined; + getEventByTriggerUuid: ( + productUuid: string, + triggerUuid: string + ) => EventsSchema | undefined; + getEventByPointUuid: ( + productUuid: string, + pointUuid: string + ) => EventsSchema | undefined; + getPointByUuid: ( + productUuid: string, + modelUuid: string, + pointUuid: string + ) => + | ConveyorPointSchema + | VehiclePointSchema + | RoboticArmPointSchema + | MachinePointSchema + | StoragePointSchema + | HumanPointSchema + | undefined; + getActionByUuid: ( + productUuid: string, + actionUuid: string + ) => + | ( + | ConveyorPointSchema["action"] + | VehiclePointSchema["action"] + | RoboticArmPointSchema["actions"][0] + | MachinePointSchema["action"] + | StoragePointSchema["action"] + | HumanPointSchema["actions"][0] + ) + | undefined; + getActionByPointUuid: ( + productUuid: string, + pointUuid: string + ) => + | ( + | ConveyorPointSchema["action"] + | VehiclePointSchema["action"] + | RoboticArmPointSchema["actions"][0] + | MachinePointSchema["action"] + | StoragePointSchema["action"] + | HumanPointSchema["actions"][0] + ) + | undefined; + getModelUuidByPointUuid: ( + productUuid: string, + actionUuid: string + ) => string | undefined; + getModelUuidByActionUuid: ( + productUuid: string, + actionUuid: string + ) => string | undefined; + getPointUuidByActionUuid: ( + productUuid: string, + actionUuid: string + ) => string | undefined; + getTriggerByUuid: ( + productUuid: string, + triggerUuid: string + ) => TriggerSchema | undefined; + getTriggersByTriggeredPointUuid: ( + productUuid: string, + triggeredPointUuid: string + ) => TriggerSchema[]; + getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean; }; export const createProductStore = () => { - return create()( - immer((set, get) => ({ - products: [], + return create()( + immer((set, get) => ({ + products: [], - // Product-level actions - addProduct: (productName, productUuid) => { - set((state) => { - const existingProduct = state.products.find(p => p.productUuid === productUuid); - if (!existingProduct) { - const newProduct = { - productName, - productUuid: productUuid, - eventDatas: [] - }; - state.products.push(newProduct); - } - }); - }, + // Product-level actions + addProduct: (productName, productUuid) => { + set((state) => { + const existingProduct = state.products.find( + (p) => p.productUuid === productUuid + ); + if (!existingProduct) { + const newProduct = { + productName, + productUuid: productUuid, + eventDatas: [], + }; + state.products.push(newProduct); + } + }); + }, - setProducts: (products) => { - set((state) => { - state.products = products; - }); - }, + setProducts: (products) => { + set((state) => { + state.products = products; + }); + }, - clearProducts: () => { - set((state) => { - state.products = []; - }); - }, + clearProducts: () => { + set((state) => { + state.products = []; + }); + }, - removeProduct: (productUuid) => { - set((state) => { - state.products = state.products.filter(p => p.productUuid !== productUuid); - }); - }, + removeProduct: (productUuid) => { + set((state) => { + state.products = state.products.filter( + (p) => p.productUuid !== productUuid + ); + }); + }, - updateProduct: (productUuid, updates) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - Object.assign(product, updates); - } - }); - }, + updateProduct: (productUuid, updates) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + Object.assign(product, updates); + } + }); + }, - // Event-level actions - addEvent: (productUuid, event) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const existingEvent = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === event.modelUuid); - if (!existingEvent) { - product.eventDatas.push(event); - } - } - }); - }, - - removeEvent: (productUuid, modelUuid) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); - } - }); - }, - - deleteEvent: (modelUuid) => { - let updatedEvents: EventsSchema[] = []; - set((state) => { - const actionsToDelete = new Set(); - - for (const product of state.products) { - const eventIndex = product.eventDatas.findIndex(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (eventIndex !== -1) { - const event = product.eventDatas[eventIndex]; - - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action) { - actionsToDelete.add(point.action.actionUuid); - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action) { - actionsToDelete.add(point.action.actionUuid); - } else if ('actions' in point) { - for (const action of point.actions) { - actionsToDelete.add(action.actionUuid); - } - } - } - - product.eventDatas.splice(eventIndex, 1); - } - } - - for (const product of state.products) { - for (const event of product.eventDatas) { - let eventModified = false; - - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.triggers) { - const originalLength = point.action.triggers.length; - point.action.triggers = point.action.triggers.filter(trigger => { - return !( - (trigger.triggeredAsset?.triggeredModel?.modelUuid === modelUuid) || - (actionsToDelete.has(trigger.triggeredAsset?.triggeredAction?.actionUuid || '')) - ); - }); - if (point.action.triggers.length !== originalLength) { - eventModified = true; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.triggers) { - const originalLength = point.action.triggers.length; - point.action.triggers = point.action.triggers.filter((trigger: TriggerSchema) => { - return !( - (trigger.triggeredAsset?.triggeredModel?.modelUuid === modelUuid) || - (actionsToDelete.has(trigger.triggeredAsset?.triggeredAction?.actionUuid || '')) - ); - }); - if (point.action.triggers.length !== originalLength) { - eventModified = true; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if (action.triggers) { - const originalLength = action.triggers.length; - action.triggers = action.triggers.filter((trigger: TriggerSchema) => { - return !( - (trigger.triggeredAsset?.triggeredModel?.modelUuid === modelUuid) || - (actionsToDelete.has(trigger.triggeredAsset?.triggeredAction?.actionUuid || '')) - ); - }); - if (action.triggers.length !== originalLength) { - eventModified = true; - } - } - } - } - } - - if (eventModified) { - updatedEvents.push(JSON.parse(JSON.stringify(event))); - } - } - } - }); - return updatedEvents; - }, - - updateEvent: (productUuid, modelUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event) { - Object.assign(event, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - }); - return updatedEvent; - }, - - // Point-level actions - addPoint: (productUuid, modelUuid, point) => { - let updatedEvent: EventsSchema | undefined = undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid); - if (!existingPoint) { - (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if (event && 'point' in event) { - const existingPoint = (event as any).point?.uuid === point.uuid; - if (!existingPoint) { - (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - }); - return updatedEvent; - }, - - removePoint: (productUuid, modelUuid, pointUuid) => { - let updatedEvent: EventsSchema | undefined = undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - (event as ConveyorEventSchema).points = (event as ConveyorEventSchema).points.filter(p => p.uuid !== pointUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - // For events with single point, we can't remove it, only reset to empty - } - } - }); - return updatedEvent; - }, - - updatePoint: (productUuid, modelUuid, pointUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); - if (point) { - Object.assign(point, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - Object.assign((event as any).point, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - }); - return updatedEvent; - }, - - // Action-level actions - addAction: (productUuid, modelUuid, pointUuid, action) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); - if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) { - point.action = action as any; - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - if ('action' in (event as any).point) { - if (!(event as any).point.action || (event as any).point.action.actionUuid !== action.actionUuid) { - (event as any).point.action = action; - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if ('actions' in (event as any).point) { - const existingAction = (event as any).point.actions.find((a: any) => a.actionUuid === action.actionUuid); - if (!existingAction) { - (event as any).point.actions.push(action); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - } - }); - return updatedEvent; - }, - - removeAction: (productUuid, actionUuid) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - // Handle ConveyorEventSchema - for (const point of (event as ConveyorEventSchema).points) { - } - } else if ('point' in event) { - const point = (event as any).point; - if (event.type === "roboticArm") { - if ('actions' in point) { - const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); - if (index !== -1) { - point.actions.splice(index, 1); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if (event.type === "human") { - if ('actions' in point) { - const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); - if (index !== -1) { - point.actions.splice(index, 1); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if ('action' in point && point.action?.actionUuid === actionUuid) { - point.action = undefined; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - }); - return updatedEvent; - }, - - updateAction: (productUuid, actionUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && point.action.actionUuid === actionUuid) { - Object.assign(point.action, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action.actionUuid === actionUuid) { - Object.assign(point.action, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - Object.assign(action, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - }); - return updatedEvent; - }, - - // Trigger-level actions - addTrigger: (productUuid, actionUuid, trigger) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && point.action.actionUuid === actionUuid) { - const existingTrigger = point.action.triggers.find(t => t.triggerUuid === trigger.triggerUuid); - if (!existingTrigger) { - point.action.triggers.push(trigger); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - return; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action.actionUuid === actionUuid) { - const existingTrigger = point.action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid); - if (!existingTrigger) { - point.action.triggers.push(trigger); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - return; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - const existingTrigger = action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid); - if (!existingTrigger) { - action.triggers.push(trigger); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - return; - } - } - } - } - } - }); - return updatedEvent; - }, - - removeTrigger: (productUuid, triggerUuid) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && 'triggers' in point.action) { - const Trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); - if (Trigger) { - point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && 'triggers' in point.action) { - const Trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (Trigger) { - point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if ('actions' in point) { - for (const action of point.actions) { - if ('triggers' in action) { - const Trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (Trigger) { - action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - } - } - } - } - }); - return updatedEvent; - }, - - updateTrigger: (productUuid, triggerUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && 'triggers' in point.action) { - const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); - if (trigger) { - Object.assign(trigger, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && 'triggers' in point.action) { - const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - Object.assign(trigger, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if ('triggers' in action) { - const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - Object.assign(trigger, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - } - } - }); - return updatedEvent; - }, - - // Renaming functions - renameProduct: (productUuid, newName) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - product.productName = newName; - } - }); - }, - - renameAction: (productUuid, actionUuid, newName) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && point.action.actionUuid === actionUuid) { - point.action.actionName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action.actionUuid === actionUuid) { - point.action.actionName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - action.actionName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - }); - return updatedEvent; - }, - - renameTrigger: (productUuid, triggerUuid, newName) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && 'triggers' in point.action) { - const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); - if (trigger) { - trigger.triggerName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && 'triggers' in point.action) { - const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - trigger.triggerName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if ('triggers' in action) { - const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - trigger.triggerName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - } - } - }); - return updatedEvent; - }, - - // Helper functions - getProductById: (productUuid) => { - return get().products.find(p => p.productUuid === productUuid); - }, - - getEventByModelUuid: (productUuid, modelUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - }, - - getEventByActionUuid: (productUuid, actionUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return event; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return event; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - return event; - } - } - } - } - return undefined; - }, - - getEventByTriggerUuid: (productUuid, triggerUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.triggers?.some(t => t.triggerUuid === triggerUuid)) { - return event; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point) { - if (point.action?.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) { - return event; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if (action.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) { - return event; - } - } - } - } - } - return undefined; - }, - - getEventByPointUuid: (productUuid, pointUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - if ((event as ConveyorEventSchema).points.some(p => p.uuid === pointUuid)) { - return event; - } - } else if ('point' in event) { - if ((event as any).point?.uuid === pointUuid) { - return event; - } - } - } - return undefined; - }, - - getPointByUuid: (productUuid, modelUuid, pointUuid) => { - const event = get().getEventByModelUuid(productUuid, modelUuid); - if (!event) return undefined; - - if ('points' in event) { - return (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); - } else if ('point' in event && (event as any).point.uuid === pointUuid) { - return (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point; - } - return undefined; - }, - - getActionByUuid: (productUuid, actionUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return point.action; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return point.action; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) return action; - } - } - } - return undefined; - }, - - getActionByPointUuid: (productUuid, pointUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.uuid === pointUuid) { - return point.action; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if (point.uuid === pointUuid) { - return point.action; - } - } - } - return undefined; - }, - - getModelUuidByPointUuid: (productUuid, pointUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.uuid === pointUuid) { - return event.modelUuid; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if (point.uuid === pointUuid) { - return event.modelUuid; - } - } - } - return undefined; - }, - - getModelUuidByActionUuid: (productUuid, actionUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return event.modelUuid; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return event.modelUuid; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) return event.modelUuid; - } - } - } - return undefined; - }, - - getPointUuidByActionUuid: (productUuid, actionUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return point.uuid; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return point.uuid; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) return point.uuid; - } - } - } - return undefined; - }, - - getTriggerByUuid: (productUuid, triggerUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - for (const trigger of point.action?.triggers || []) { - if (trigger.triggerUuid === triggerUuid) { - return trigger; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point) { - for (const trigger of point.action?.triggers || []) { - if (trigger.triggerUuid === triggerUuid) { - return trigger; - } - } - } else if ('actions' in point) { - for (const action of point.actions) { - for (const trigger of action.triggers || []) { - if (trigger.triggerUuid === triggerUuid) { - return trigger; - } - } - } - } - } - } - return undefined; - }, - - getTriggersByTriggeredPointUuid: (productUuid, triggeredPointUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return []; - - const triggers: TriggerSchema[] = []; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.triggers) { - for (const trigger of point.action.triggers) { - if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { - triggers.push(trigger); - } - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.triggers) { - for (const trigger of point.action.triggers) { - if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { - triggers.push(trigger); - } - } - } else if ('actions' in point) { - for (const action of point.actions) { - if (action.triggers) { - for (const trigger of action.triggers) { - if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { - triggers.push(trigger); - } - } - } - } - } - } - } - - return triggers; - }, - - getIsEventInProduct: (productUuid, modelUuid) => { - const product = get().getProductById(productUuid); - if (!product) return false; - return product.eventDatas.some(e => 'modelUuid' in e && e.modelUuid === modelUuid); + // Event-level actions + addEvent: (productUuid, event) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const existingEvent = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === event.modelUuid + ); + if (!existingEvent) { + product.eventDatas.push(event); } - })) - ) -} + } + }); + }, -export type ProductStoreType = ReturnType; \ No newline at end of file + removeEvent: (productUuid, modelUuid) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + product.eventDatas = product.eventDatas.filter( + (e) => "modelUuid" in e && e.modelUuid !== modelUuid + ); + } + }); + }, + + deleteEvent: (modelUuid) => { + let updatedEvents: EventsSchema[] = []; + set((state) => { + const actionsToDelete = new Set(); + + for (const product of state.products) { + const eventIndex = product.eventDatas.findIndex( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (eventIndex !== -1) { + const event = product.eventDatas[eventIndex]; + + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action) { + actionsToDelete.add(point.action.actionUuid); + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action) { + actionsToDelete.add(point.action.actionUuid); + } else if ("actions" in point) { + for (const action of point.actions) { + actionsToDelete.add(action.actionUuid); + } + } + } + + product.eventDatas.splice(eventIndex, 1); + } + } + + for (const product of state.products) { + for (const event of product.eventDatas) { + let eventModified = false; + + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.triggers) { + const originalLength = point.action.triggers.length; + point.action.triggers = point.action.triggers.filter( + (trigger) => { + return !( + trigger.triggeredAsset?.triggeredModel?.modelUuid === + modelUuid || + actionsToDelete.has( + trigger.triggeredAsset?.triggeredAction + ?.actionUuid || "" + ) + ); + } + ); + if (point.action.triggers.length !== originalLength) { + eventModified = true; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.triggers) { + const originalLength = point.action.triggers.length; + point.action.triggers = point.action.triggers.filter( + (trigger: TriggerSchema) => { + return !( + trigger.triggeredAsset?.triggeredModel?.modelUuid === + modelUuid || + actionsToDelete.has( + trigger.triggeredAsset?.triggeredAction?.actionUuid || + "" + ) + ); + } + ); + if (point.action.triggers.length !== originalLength) { + eventModified = true; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if (action.triggers) { + const originalLength = action.triggers.length; + action.triggers = action.triggers.filter( + (trigger: TriggerSchema) => { + return !( + trigger.triggeredAsset?.triggeredModel + ?.modelUuid === modelUuid || + actionsToDelete.has( + trigger.triggeredAsset?.triggeredAction + ?.actionUuid || "" + ) + ); + } + ); + if (action.triggers.length !== originalLength) { + eventModified = true; + } + } + } + } + } + + if (eventModified) { + updatedEvents.push(JSON.parse(JSON.stringify(event))); + } + } + } + }); + return updatedEvents; + }, + + updateEvent: (productUuid, modelUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event) { + Object.assign(event, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + }); + return updatedEvent; + }, + + // Point-level actions + addPoint: (productUuid, modelUuid, point) => { + let updatedEvent: EventsSchema | undefined = undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + const existingPoint = (event as ConveyorEventSchema).points.find( + (p) => p.uuid === point.uuid + ); + if (!existingPoint) { + (event as ConveyorEventSchema).points.push( + point as ConveyorPointSchema + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if (event && "point" in event) { + const existingPoint = (event as any).point?.uuid === point.uuid; + if (!existingPoint) { + ( + event as + | VehicleEventSchema + | RoboticArmEventSchema + | MachineEventSchema + | StorageEventSchema + ).point = point as any; + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + }); + return updatedEvent; + }, + + removePoint: (productUuid, modelUuid, pointUuid) => { + let updatedEvent: EventsSchema | undefined = undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + (event as ConveyorEventSchema).points = ( + event as ConveyorEventSchema + ).points.filter((p) => p.uuid !== pointUuid); + updatedEvent = JSON.parse(JSON.stringify(event)); + } else if ( + event && + "point" in event && + (event as any).point.uuid === pointUuid + ) { + // For events with single point, we can't remove it, only reset to empty + } + } + }); + return updatedEvent; + }, + + updatePoint: (productUuid, modelUuid, pointUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + const point = (event as ConveyorEventSchema).points.find( + (p) => p.uuid === pointUuid + ); + if (point) { + Object.assign(point, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ( + event && + "point" in event && + (event as any).point.uuid === pointUuid + ) { + Object.assign((event as any).point, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + }); + return updatedEvent; + }, + + // Action-level actions + addAction: (productUuid, modelUuid, pointUuid, action) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + const point = (event as ConveyorEventSchema).points.find( + (p) => p.uuid === pointUuid + ); + if ( + point && + (!point.action || point.action.actionUuid !== action.actionUuid) + ) { + point.action = action as any; + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ( + event && + "point" in event && + (event as any).point.uuid === pointUuid + ) { + if ("action" in (event as any).point) { + if ( + !(event as any).point.action || + (event as any).point.action.actionUuid !== action.actionUuid + ) { + (event as any).point.action = action; + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ("actions" in (event as any).point) { + const existingAction = (event as any).point.actions.find( + (a: any) => a.actionUuid === action.actionUuid + ); + if (!existingAction) { + (event as any).point.actions.push(action); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + } + }); + return updatedEvent; + }, + + removeAction: (productUuid, actionUuid) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + // Handle ConveyorEventSchema + for (const point of (event as ConveyorEventSchema).points) { + } + } else if ("point" in event) { + const point = (event as any).point; + if (event.type === "roboticArm") { + if ("actions" in point) { + const index = point.actions.findIndex( + (a: any) => a.actionUuid === actionUuid + ); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if (event.type === "human") { + if ("actions" in point) { + const index = point.actions.findIndex( + (a: any) => a.actionUuid === actionUuid + ); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if ( + "action" in point && + point.action?.actionUuid === actionUuid + ) { + point.action = undefined; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + }); + return updatedEvent; + }, + + updateAction: (productUuid, actionUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + Object.assign(point.action, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ( + "action" in point && + point.action.actionUuid === actionUuid + ) { + Object.assign(point.action, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + Object.assign(action, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + }); + return updatedEvent; + }, + + // Trigger-level actions + addTrigger: (productUuid, actionUuid, trigger) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + const existingTrigger = point.action.triggers.find( + (t) => t.triggerUuid === trigger.triggerUuid + ); + if (!existingTrigger) { + point.action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + return; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ( + "action" in point && + point.action.actionUuid === actionUuid + ) { + const existingTrigger = point.action.triggers.find( + (t: any) => t.triggerUuid === trigger.triggerUuid + ); + if (!existingTrigger) { + point.action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + return; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + const existingTrigger = action.triggers.find( + (t: any) => t.triggerUuid === trigger.triggerUuid + ); + if (!existingTrigger) { + action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + return; + } + } + } + } + } + }); + return updatedEvent; + }, + + removeTrigger: (productUuid, triggerUuid) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && "triggers" in point.action) { + const Trigger = point.action.triggers.find( + (t) => t.triggerUuid === triggerUuid + ); + if (Trigger) { + point.action.triggers = point.action.triggers.filter( + (t) => t.triggerUuid !== triggerUuid + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && "triggers" in point.action) { + const Trigger = point.action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (Trigger) { + point.action.triggers = point.action.triggers.filter( + (t: any) => t.triggerUuid !== triggerUuid + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ("triggers" in action) { + const Trigger = action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (Trigger) { + action.triggers = action.triggers.filter( + (t: any) => t.triggerUuid !== triggerUuid + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + } + } + } + } + }); + return updatedEvent; + }, + + updateTrigger: (productUuid, triggerUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t) => t.triggerUuid === triggerUuid + ); + if (trigger) { + Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ("triggers" in action) { + const trigger = action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + } + } + }); + return updatedEvent; + }, + + // Renaming functions + renameProduct: (productUuid, newName) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + product.productName = newName; + } + }); + }, + + renameAction: (productUuid, actionUuid, newName) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + point.action.actionName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ( + "action" in point && + point.action.actionUuid === actionUuid + ) { + point.action.actionName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + action.actionName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + }); + return updatedEvent; + }, + + renameTrigger: (productUuid, triggerUuid, newName) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t) => t.triggerUuid === triggerUuid + ); + if (trigger) { + trigger.triggerName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + trigger.triggerName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ("triggers" in action) { + const trigger = action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + trigger.triggerName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + } + } + }); + return updatedEvent; + }, + + // Helper functions + getProductById: (productUuid) => { + // console.log( + // "j", + // get().products.map((p) => p.productUuid) + // ); + + // console.log("productUuid: ", productUuid); + return get().products.find((p) => p.productUuid === productUuid); + }, + + getEventByModelUuid: (productUuid, modelUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + return product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + }, + + getEventByActionUuid: (productUuid, actionUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return event; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return event; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + return event; + } + } + } + } + return undefined; + }, + + getEventByTriggerUuid: (productUuid, triggerUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if ( + point.action?.triggers?.some( + (t) => t.triggerUuid === triggerUuid + ) + ) { + return event; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point) { + if ( + point.action?.triggers?.some( + (t: any) => t.triggerUuid === triggerUuid + ) + ) { + return event; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ( + action.triggers?.some( + (t: any) => t.triggerUuid === triggerUuid + ) + ) { + return event; + } + } + } + } + } + return undefined; + }, + + getEventByPointUuid: (productUuid, pointUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + if ( + (event as ConveyorEventSchema).points.some( + (p) => p.uuid === pointUuid + ) + ) { + return event; + } + } else if ("point" in event) { + if ((event as any).point?.uuid === pointUuid) { + return event; + } + } + } + return undefined; + }, + + getPointByUuid: (productUuid, modelUuid, pointUuid) => { + const event = get().getEventByModelUuid(productUuid, modelUuid); + if (!event) return undefined; + + if ("points" in event) { + return (event as ConveyorEventSchema).points.find( + (p) => p.uuid === pointUuid + ); + } else if ( + "point" in event && + (event as any).point.uuid === pointUuid + ) { + return ( + event as + | VehicleEventSchema + | RoboticArmEventSchema + | MachineEventSchema + | StorageEventSchema + ).point; + } + return undefined; + }, + + getActionByUuid: (productUuid, actionUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return point.action; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return point.action; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) return action; + } + } + } + return undefined; + }, + + getActionByPointUuid: (productUuid, pointUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.uuid === pointUuid) { + return point.action; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if (point.uuid === pointUuid) { + return point.action; + } + } + } + return undefined; + }, + + getModelUuidByPointUuid: (productUuid, pointUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.uuid === pointUuid) { + return event.modelUuid; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if (point.uuid === pointUuid) { + return event.modelUuid; + } + } + } + return undefined; + }, + + getModelUuidByActionUuid: (productUuid, actionUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return event.modelUuid; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return event.modelUuid; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) return event.modelUuid; + } + } + } + return undefined; + }, + + getPointUuidByActionUuid: (productUuid, actionUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return point.uuid; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return point.uuid; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) return point.uuid; + } + } + } + return undefined; + }, + + getTriggerByUuid: (productUuid, triggerUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } else if ("actions" in point) { + for (const action of point.actions) { + for (const trigger of action.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } + } + } + return undefined; + }, + + getTriggersByTriggeredPointUuid: (productUuid, triggeredPointUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return []; + + const triggers: TriggerSchema[] = []; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.triggers) { + for (const trigger of point.action.triggers) { + if ( + trigger.triggeredAsset?.triggeredPoint?.pointUuid === + triggeredPointUuid + ) { + triggers.push(trigger); + } + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.triggers) { + for (const trigger of point.action.triggers) { + if ( + trigger.triggeredAsset?.triggeredPoint?.pointUuid === + triggeredPointUuid + ) { + triggers.push(trigger); + } + } + } else if ("actions" in point) { + for (const action of point.actions) { + if (action.triggers) { + for (const trigger of action.triggers) { + if ( + trigger.triggeredAsset?.triggeredPoint?.pointUuid === + triggeredPointUuid + ) { + triggers.push(trigger); + } + } + } + } + } + } + } + + return triggers; + }, + + getIsEventInProduct: (productUuid, modelUuid) => { + const product = get().getProductById(productUuid); + if (!product) return false; + return product.eventDatas.some( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + }, + })) + ); +}; + +export type ProductStoreType = ReturnType; From a4bb4a4bf62a021cea2312a8611eb387634b29c1 Mon Sep 17 00:00:00 2001 From: Poovizhi Date: Tue, 5 Aug 2025 11:31:26 +0530 Subject: [PATCH 05/14] created new id for duplicate projects --- .../components/Dashboard/DashboardHome.tsx | 4 +++- .../Dashboard/DashboardProjects.tsx | 19 ++++++++++--------- app/src/components/ui/FileMenu.tsx | 1 + app/src/pages/Project.tsx | 2 -- .../services/dashboard/duplicateProject.ts | 12 ++++++------ 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/components/Dashboard/DashboardHome.tsx b/app/src/components/Dashboard/DashboardHome.tsx index 9e05994..0569c08 100644 --- a/app/src/components/Dashboard/DashboardHome.tsx +++ b/app/src/components/Dashboard/DashboardHome.tsx @@ -8,6 +8,7 @@ import { recentlyViewed } from "../../services/dashboard/recentlyViewed"; import { searchProject } from "../../services/dashboard/searchProjects"; import { deleteProject } from "../../services/dashboard/deleteProject"; import ProjectSocketRes from "./socket/projectSocketRes.dev"; +import { generateUniqueId } from "../../functions/generateUniqueId"; interface Project { _id: string; @@ -101,7 +102,8 @@ const DashboardHome: React.FC = () => { userId, thumbnail, organization, - projectUuid: projectId, + projectUuid: generateUniqueId(), + refProjectID: projectId, projectName, }; projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData); diff --git a/app/src/components/Dashboard/DashboardProjects.tsx b/app/src/components/Dashboard/DashboardProjects.tsx index 51888d0..ba2db7b 100644 --- a/app/src/components/Dashboard/DashboardProjects.tsx +++ b/app/src/components/Dashboard/DashboardProjects.tsx @@ -103,22 +103,23 @@ const DashboardProjects: React.FC = () => { projectName: string, thumbnail: string ) => { - const duplicatedProject = await duplicateProject( - projectId, - generateUniqueId(), - thumbnail, - projectName - ); - console.log("duplicatedProject: ", duplicatedProject); + // const duplicatedProject = await duplicateProject( + // projectId, + // generateUniqueId(), + // thumbnail, + // projectName + // ); + // console.log("duplicatedProject: ", duplicatedProject); const duplicateProjectData = { userId, thumbnail, organization, - projectUuid: projectId, + projectUuid: generateUniqueId(), + refProjectID: projectId, projectName, }; - // projectSocket.emit("v1:project:Duplicate", duplicateProjectData); + projectSocket.emit("v1:project:Duplicate", duplicateProjectData); }; const renderProjects = () => { diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 0f0aabe..843af5d 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -74,6 +74,7 @@ const FileMenu: React.FC = () => { // } //API for projects rename + const updatedProjectName = await updateProject( projectId, userId, diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 2ee6b29..62a5759 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -34,7 +34,6 @@ const Project: React.FC = () => { const { setOrganization } = useOrganization(); const { projectId } = useParams(); const { projectName, setProjectName } = useProjectName(); - console.log("projectName: ", projectName); const { userId, email, organization, userName } = getUserData(); const { selectedUser } = useSelectedUserStore(); const { isLogListVisible } = useLogger(); @@ -50,7 +49,6 @@ const Project: React.FC = () => { const fetchProjects = async () => { try { const projects = await getAllProjects(userId, organization); - console.log("projects: ", projects); const shared = await sharedWithMeProjects(); const allProjects = [...(projects?.Projects || []), ...(shared || [])]; diff --git a/app/src/services/dashboard/duplicateProject.ts b/app/src/services/dashboard/duplicateProject.ts index 68fe5c6..ca341de 100644 --- a/app/src/services/dashboard/duplicateProject.ts +++ b/app/src/services/dashboard/duplicateProject.ts @@ -27,22 +27,22 @@ export const duplicateProject = async ( ); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { - //console.log("New token received:", newAccessToken); + // localStorage.setItem("token", newAccessToken); } - console.log("response: ", response); + if (!response.ok) { - console.error("Failed to add project"); + } const result = await response.json(); - // console.log("result: ", result); + // return result; } catch (error) { if (error instanceof Error) { - console.log(error.message); + } else { - console.log("An unknown error occurred"); + } } }; From dead5ea571d462e2f9bd0ca749d88c6b08b0ba63 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Tue, 5 Aug 2025 11:35:18 +0530 Subject: [PATCH 06/14] refactor: updated transformControls with Y-axis --- .../scene/controls/transformControls/transformControls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index e0ce1cf..964588b 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -187,7 +187,7 @@ export default function TransformControl() { {(selectedFloorItem && transformMode) && Date: Thu, 7 Aug 2025 14:53:20 +0530 Subject: [PATCH 07/14] pillar jib animator added --- .../instances/animator/pillarJibAnimator.tsx | 299 ++++++++++++------ .../instances/helper/pillarJibHelper.tsx | 77 +++++ .../instances/instance/pillarJibInstance.tsx | 17 +- 3 files changed, 290 insertions(+), 103 deletions(-) create mode 100644 app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 95a531c..04bfbba 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,82 +1,40 @@ +import { useEffect, useState } from 'react'; import * as THREE from 'three'; -import { useEffect, useMemo } from 'react'; -import { useThree } from '@react-three/fiber'; +import { useFrame, useThree } from '@react-three/fiber'; +import { Sphere, Box } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; -function PillarJibAnimator({ crane }: { crane: CraneStatus }) { +function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3]; }) { const { scene } = useThree(); + const { assetStore } = useSceneContext(); + const { resetAsset } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + + const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); + const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); + const [animationPhase, setAnimationPhase] = useState('idle'); // 0: idle, 1: init-hook-adjust, 2: 'rotate-base', 3: 'move-trolley', 4: 'final-hook-adjust' + + useEffect(() => { + if (!isPlaying) { + resetAsset(crane.modelUuid); + setAnimationPhase('idle'); + } else if (animationPhase === 'idle') { + setAnimationPhase('init-hook-adjust'); + } + }, [isPlaying, scene, crane.modelUuid]); useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - - if (model) { - const base = model.getObjectByName('base'); - const trolley = model.getObjectByName('trolley'); - const hook = model.getObjectByName('hook'); - - if (base && trolley && hook) { - let trolleyDir = 1; - let hookDir = 1; - - const trolleySpeed = 0.01; - const hookSpeed = 0.01; - const rotationSpeed = 0.005; - - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; - - const originalTrolleyX = trolley.position.x; - const originalHookY = hook.position.y; - - const animate = () => { - if (base) { - base.rotation.y += rotationSpeed; - } - - if (trolley) { - trolley.position.x += trolleyDir * trolleySpeed; - if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset || - trolley.position.x <= originalTrolleyX + trolleyMinOffset) { - trolleyDir *= -1; - } - } - - if (hook) { - hook.position.y += hookDir * hookSpeed; - if (hook.position.y >= originalHookY + hookMinOffset || - hook.position.y <= originalHookY + hookMaxOffset) { - hookDir *= -1; - } - } - - requestAnimationFrame(animate); - }; - - animate(); - } - } - }, [crane, scene]); - - return ( - - ); -} - -export default PillarJibAnimator; - -function PillarJibHelper({ crane }: { crane: CraneStatus }) { - const { scene } = useThree(); - - const { geometry, position } = useMemo(() => { - const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return { geometry: null, position: null }; + if (!model) return; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return { geometry: null, position: null }; + if (!base || !trolley || !hook) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -87,47 +45,186 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) { const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); - const outerRadius = distFromBase + 1.75; - const innerRadius = Math.max(distFromBase - 1, 0.05); - const height = (0.25 - (-1.5)); - const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2; + const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); + const outerRadius = Math.max( + new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length() + trolleyMaxOffset, + innerRadius + ); - const shape = new THREE.Shape(); - shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + const yMin = hookWorld.y + hookMaxOffset; + const yMax = hookWorld.y + hookMinOffset; - const hole = new THREE.Path(); - hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); - shape.holes.push(hole); + function clampToCylinder(pos: THREE.Vector3) { + const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z); + const distance = xzDist.length(); - const extrudeSettings = { - depth: height, - bevelEnabled: false, - steps: 1 - }; + let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius); + if (distance > 0) { + clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius)); + } - const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); - const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance); + const y = THREE.MathUtils.clamp(pos.y, yMin, yMax); - return { geometry, position }; - }, [scene, crane.modelUuid]); + return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y); + } - if (!geometry || !position) return null; + const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()]; + const newIsInside: [boolean, boolean] = [false, false]; + + points.forEach((point, i) => { + const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length(); + const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius; + const insideY = point.y >= yMin && point.y <= yMax; + newIsInside[i] = insideXZ && insideY; + + newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point); + }); + + setClampedPoints(newClampedPoints); + setIsInside(newIsInside); + }, [scene, points, crane.modelUuid]); + + useFrame(() => { + if (!isPlaying || isPaused || !points || animationPhase === 'idle') return; + + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook || !clampedPoints || !trolley.parent) return; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + if (!model.userData.animationData) { + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: points[0].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: points[0].clone(), + finalHookTargetY: 0, + }; + } + + const { animationData } = model.userData; + const hookSpeed = 0.01 * speed; + const rotationSpeed = 0.005 * speed; + const trolleySpeed = 0.01 * speed; + + switch (animationPhase) { + case 'init-hook-adjust': { + const direction = Math.sign(animationData.targetHookY - hook.position.y); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { + hook.position.y = animationData.targetHookY; + setAnimationPhase('rotate-base'); + } + break; + } + + case 'rotate-base': { + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const baseForward = new THREE.Vector3(1, 0, 0); + base.localToWorld(baseForward); + baseForward.sub(baseWorld); + + const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); + + const targetWorld = clampedPoints[0]; + const targetDir = new THREE.Vector2( + targetWorld.x - baseWorld.x, + targetWorld.z - baseWorld.z + ).normalize(); + + const currentAngle = Math.atan2(currentDir.y, currentDir.x); + const targetAngle = Math.atan2(targetDir.y, targetDir.x); + let angleDiff = currentAngle - targetAngle; + + angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); + + if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { + base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + } else { + base.rotation.y += angleDiff; + const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); + animationData.targetTrolleyX = localTarget?.x; + setAnimationPhase('move-trolley'); + } + + break; + } + + case 'move-trolley': { + const dx = animationData.targetTrolleyX - trolley.position.x; + const direction = Math.sign(dx); + trolley.position.x += direction * trolleySpeed; + + if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = animationData.targetTrolleyX; + + animationData.finalHookTargetY = hook.position.y - 0.5; + setAnimationPhase('final-hook-adjust'); + } + break; + } + + case 'final-hook-adjust': { + const dy = animationData.finalHookTargetY - hook.position.y; + const direction = Math.sign(dy); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { + hook.position.y = animationData.finalHookTargetY; + setAnimationPhase('idle'); + console.log('hii'); + } + break; + } + } + }); + + if (!clampedPoints) return null; return ( - - - + <> + {points.map((point, i) => ( + + + + ))} + + {clampedPoints.map((point, i) => ( + + + + ))} + ); } + +export default PillarJibAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx new file mode 100644 index 0000000..945cf2f --- /dev/null +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -0,0 +1,77 @@ +import { useMemo } from "react"; +import * as THREE from "three"; +import { useThree } from "@react-three/fiber"; + +function PillarJibHelper({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + const { geometry, position } = useMemo(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return { geometry: null, position: null }; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook) return { geometry: null, position: null }; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const trolleyWorld = new THREE.Vector3(); + trolley.getWorldPosition(trolleyWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); + const outerRadius = distFromBase + trolleyMaxOffset; + const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); + const height = (hookMinOffset - (hookMaxOffset)); + const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + + const extrudeSettings = { + depth: height, + bevelEnabled: false, + steps: 1 + }; + + const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + + return { geometry, position }; + }, [scene, crane.modelUuid]); + + if (!geometry || !position) return null; + + return ( + + + + ); +} + +export default PillarJibHelper; \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index bef6a17..a736a24 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,14 +1,27 @@ +import * as THREE from 'three' import PillarJibAnimator from '../animator/pillarJibAnimator' +import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const position1: [number, number, number] = [5, 1, -4]; + const position2: [number, number, number] = [-2, 2, -2]; + return ( <> - + + + ) } -export default PillarJibInstance \ No newline at end of file +export default PillarJibInstance; \ No newline at end of file From 54740ffbfcc2b316d22b726eaf2f5c69b960f3bd Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 7 Aug 2025 14:53:38 +0530 Subject: [PATCH 08/14] fix --- .../simulation/crane/instances/animator/pillarJibAnimator.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 04bfbba..8c28254 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -193,7 +193,6 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { hook.position.y = animationData.finalHookTargetY; setAnimationPhase('idle'); - console.log('hii'); } break; } From 2760e73ab8393a9627b1ffff2ec38961019dd99b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 7 Aug 2025 16:49:14 +0530 Subject: [PATCH 09/14] crane animator --- .../instances/animator/pillarJibAnimator.tsx | 68 +++++++++++++------ .../crane/instances/craneInstances.tsx | 2 +- .../instances/instance/pillarJibInstance.tsx | 18 +++++ 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 8c28254..d287d04 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -5,7 +5,19 @@ import { Sphere, Box } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; -function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3]; }) { +function PillarJibAnimator({ + crane, + points, + animationPhase, + setAnimationPhase, + onAnimationComplete +}: { + crane: CraneStatus; + points: [THREE.Vector3, THREE.Vector3]; + animationPhase: string; + setAnimationPhase: (phase: string) => void; + onAnimationComplete: (action: string) => void; +}) { const { scene } = useThree(); const { assetStore } = useSceneContext(); const { resetAsset } = assetStore(); @@ -15,12 +27,13 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); - const [animationPhase, setAnimationPhase] = useState('idle'); // 0: idle, 1: init-hook-adjust, 2: 'rotate-base', 3: 'move-trolley', 4: 'final-hook-adjust' + const [currentTargetIndex, setCurrentTargetIndex] = useState(0); useEffect(() => { if (!isPlaying) { resetAsset(crane.modelUuid); setAnimationPhase('idle'); + setCurrentTargetIndex(0); } else if (animationPhase === 'idle') { setAnimationPhase('init-hook-adjust'); } @@ -89,10 +102,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR setClampedPoints(newClampedPoints); setIsInside(newIsInside); - }, [scene, points, crane.modelUuid]); + }, [crane.modelUuid]); useFrame(() => { - if (!isPlaying || isPaused || !points || animationPhase === 'idle') return; + if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return; const model = scene.getObjectByProperty('uuid', crane.modelUuid); if (!model) return; @@ -101,7 +114,7 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook || !clampedPoints || !trolley.parent) return; + if (!base || !trolley || !hook || !trolley.parent) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -112,10 +125,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR if (!model.userData.animationData) { model.userData.animationData = { originalHookY: hook.position.y, - targetHookY: points[0].y - baseWorld.y + 0.5, + targetHookY: clampedPoints[currentTargetIndex].y - baseWorld.y + 0.5, targetDirection: new THREE.Vector2(), targetTrolleyX: 0, - targetWorldPosition: points[0].clone(), + targetWorldPosition: clampedPoints[currentTargetIndex].clone(), finalHookTargetY: 0, }; } @@ -132,22 +145,19 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { hook.position.y = animationData.targetHookY; - setAnimationPhase('rotate-base'); + setAnimationPhase('init-rotate-base'); } break; } - case 'rotate-base': { - const baseWorld = new THREE.Vector3(); - base.getWorldPosition(baseWorld); - + case 'init-rotate-base': { const baseForward = new THREE.Vector3(1, 0, 0); base.localToWorld(baseForward); baseForward.sub(baseWorld); const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); - const targetWorld = clampedPoints[0]; + const targetWorld = clampedPoints[currentTargetIndex]; const targetDir = new THREE.Vector2( targetWorld.x - baseWorld.x, targetWorld.z - baseWorld.z @@ -163,15 +173,14 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR base.rotation.y += Math.sign(angleDiff) * rotationSpeed; } else { base.rotation.y += angleDiff; - const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); + const localTarget = trolley.parent.worldToLocal(clampedPoints[currentTargetIndex].clone()); animationData.targetTrolleyX = localTarget?.x; - setAnimationPhase('move-trolley'); + setAnimationPhase('init-move-trolley'); } - break; } - case 'move-trolley': { + case 'init-move-trolley': { const dx = animationData.targetTrolleyX - trolley.position.x; const direction = Math.sign(dx); trolley.position.x += direction * trolleySpeed; @@ -180,19 +189,38 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR trolley.position.x = animationData.targetTrolleyX; animationData.finalHookTargetY = hook.position.y - 0.5; - setAnimationPhase('final-hook-adjust'); + setAnimationPhase('init-final-hook-adjust'); } break; } - case 'final-hook-adjust': { + case 'init-final-hook-adjust': { const dy = animationData.finalHookTargetY - hook.position.y; const direction = Math.sign(dy); hook.position.y += direction * hookSpeed; if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { hook.position.y = animationData.finalHookTargetY; - setAnimationPhase('idle'); + + if (currentTargetIndex < points.length - 1) { + setCurrentTargetIndex(currentTargetIndex + 1); + + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: clampedPoints[currentTargetIndex + 1].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: clampedPoints[currentTargetIndex + 1].clone(), + finalHookTargetY: 0, + }; + + setAnimationPhase('picking'); + onAnimationComplete('picking'); + + } else { + setAnimationPhase('dropping'); + onAnimationComplete('dropping'); + } } break; } diff --git a/app/src/modules/simulation/crane/instances/craneInstances.tsx b/app/src/modules/simulation/crane/instances/craneInstances.tsx index bd6a96b..240062c 100644 --- a/app/src/modules/simulation/crane/instances/craneInstances.tsx +++ b/app/src/modules/simulation/crane/instances/craneInstances.tsx @@ -12,7 +12,7 @@ function CraneInstances() { {crane.subType === "pillarJib" && - + } diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index a736a24..d6b8fd8 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,21 +1,39 @@ +import { useState } from 'react'; import * as THREE from 'three' import PillarJibAnimator from '../animator/pillarJibAnimator' import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const [animationPhase, setAnimationPhase] = useState('idle'); const position1: [number, number, number] = [5, 1, -4]; const position2: [number, number, number] = [-2, 2, -2]; + const handleAnimationComplete = (action: string) => { + if (action === 'picking') { + setTimeout(() => { + setAnimationPhase('init-hook-adjust'); + }, 3000) + } else if (action === 'dropping') { + setTimeout(() => { + setAnimationPhase('idle'); + }, 3000) + } + } + return ( <> From a7a55bf1379d2d9be134c243ecf20032ae84709c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 7 Aug 2025 18:11:11 +0530 Subject: [PATCH 10/14] added crane mechanics ui and dblclick zoom effect chnage --- .../eventProperties/EventProperties.tsx | 4 + .../mechanics/craneMechanics.tsx | 210 ++++++++++++++++++ .../model/eventHandlers/useEventHandlers.ts | 56 +++-- .../instances/animator/pillarJibAnimator.tsx | 24 -- .../instances/helper/pillarJibHelper.tsx | 96 ++++++-- .../instances/instance/pillarJibInstance.tsx | 8 +- 6 files changed, 340 insertions(+), 58 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index 252e44d..a6332db 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -15,6 +15,7 @@ import { useProductContext } from "../../../../../modules/simulation/products/pr import { useParams } from "react-router-dom"; import { useVersionContext } from "../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../modules/scene/sceneContext"; +import CraneMechanics from "./mechanics/craneMechanics"; const EventProperties: React.FC = () => { const { selectedEventData } = useSelectedEventData(); @@ -63,6 +64,8 @@ const EventProperties: React.FC = () => { return "storageUnit"; case "human": return "human"; + case "crane": + return "crane"; default: return null; } @@ -83,6 +86,7 @@ const EventProperties: React.FC = () => { {assetType === "machine" && } {assetType === "storageUnit" && } {assetType === "human" && } + {assetType === "crane" && } )} {!currentEventData && selectedEventSphere && ( diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx new file mode 100644 index 0000000..f5d1229 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx @@ -0,0 +1,210 @@ +import { useEffect, useState } from "react"; +import { MathUtils } from "three"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; +import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; +import { useParams } from "react-router-dom"; +import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; + +function CraneMechanics() { + const [activeOption, setActiveOption] = useState<"pickAndDrop">("pickAndDrop"); + const [selectedPointData, setSelectedPointData] = useState(); + + const { selectedEventData } = useSelectedEventData(); + const { productStore } = useSceneContext(); + const { getPointByUuid, updateAction, addAction, removeAction } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as CranePointSchema | undefined; + + if (point?.actions) { + setSelectedPointData(point); + + if (point.actions.length > 0) { + const firstAction = point.actions[0]; + setActiveOption(firstAction.actionType); + setSelectedAction(firstAction.actionUuid, firstAction.actionName); + } + } + } else { + clearSelectedAction(); + } + }, [selectedEventData, selectedProduct]); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName, + productUuid, + projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || "", + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { actionName: newName } + ); + + const updatedActions = selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "pickAndDrop" as const, + maxPickUpCount: 1, + triggers: [] as TriggerSchema[], + }; + + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + + setSelectedPointData({ + ...selectedPointData, + actions: [...selectedPointData.actions, newAction], + }); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + const event = removeAction( + selectedProduct.productUuid, + actionUuid + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + + const index = selectedPointData.actions.findIndex(a => a.actionUuid === actionUuid); + const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid); + + setSelectedPointData({ + ...selectedPointData, + actions: newActions, + }); + + if (selectedAction.actionId === actionUuid) { + const nextAction = newActions[index] || newActions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + } else { + clearSelectedAction(); + } + } + }; + + const availableActions = { + defaultOption: "pickAndDrop", + options: ["pickAndDrop"], + }; + + const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId); + + return ( + <> +
+ + + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ { }} + disabled={true} + /> +
+
+ +
+
+ )} +
+ + ); +} + +export default CraneMechanics \ No newline at end of file diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index c08144c..a0ea33a 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -24,7 +24,7 @@ export function useModelEventHandlers({ boundingBox: THREE.Box3 | null, groupRef: React.RefObject, }) { - const { controls, gl } = useThree(); + const { controls, gl, camera } = useThree(); const { activeTool } = useActiveTool(); const { activeModule } = useModuleStore(); const { toggleView } = useToggleView(); @@ -68,25 +68,47 @@ export function useModelEventHandlers({ const handleDblClick = (asset: Asset) => { if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { - const size = boundingBox.getSize(new THREE.Vector3()); - const center = boundingBox.getCenter(new THREE.Vector3()); - const front = new THREE.Vector3(0, 0, 1); - groupRef.current.localToWorld(front); - front.sub(groupRef.current.position).normalize(); + const frontView = false; - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center.clone().addScaledVector(front, distance); + if (frontView) { + const size = boundingBox.getSize(new THREE.Vector3()); + const center = boundingBox.getCenter(new THREE.Vector3()); - (controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true); - (controls as CameraControls).setTarget(center.x, center.y, center.z, true); - (controls as CameraControls).fitToBox(groupRef.current, true, { - cover: true, - paddingTop: 5, - paddingLeft: 5, - paddingBottom: 5, - paddingRight: 5, - }); + const front = new THREE.Vector3(0, 0, 1); + groupRef.current.localToWorld(front); + front.sub(groupRef.current.position).normalize(); + + const distance = Math.max(size.x, size.y, size.z) * 2; + const newPosition = center.clone().addScaledVector(front, distance); + + (controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true); + (controls as CameraControls).setTarget(center.x, center.y, center.z, true); + (controls as CameraControls).fitToBox(groupRef.current, true, { + cover: true, + paddingTop: 5, + paddingLeft: 5, + paddingBottom: 5, + paddingRight: 5, + }); + } else { + + const collisionPos = new THREE.Vector3(); + groupRef.current.getWorldPosition(collisionPos); + + const currentPos = new THREE.Vector3().copy(camera.position); + + const target = new THREE.Vector3(); + if (!controls) return; + (controls as CameraControls).getTarget(target); + const direction = new THREE.Vector3().subVectors(target, currentPos).normalize(); + + const offsetDistance = 5; + const newCameraPos = new THREE.Vector3().copy(collisionPos).sub(direction.multiplyScalar(offsetDistance)); + + camera.position.copy(newCameraPos); + (controls as CameraControls).setLookAt(newCameraPos.x, newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true); + } setSelectedFloorItem(groupRef.current); } diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index d287d04..9c7f3ab 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; -import { Sphere, Box } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; @@ -26,7 +25,6 @@ function PillarJibAnimator({ const { speed } = useAnimationPlaySpeed(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); - const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); const [currentTargetIndex, setCurrentTargetIndex] = useState(0); useEffect(() => { @@ -101,7 +99,6 @@ function PillarJibAnimator({ }); setClampedPoints(newClampedPoints); - setIsInside(newIsInside); }, [crane.modelUuid]); useFrame(() => { @@ -227,29 +224,8 @@ function PillarJibAnimator({ } }); - if (!clampedPoints) return null; - return ( <> - {points.map((point, i) => ( - - - - ))} - - {clampedPoints.map((point, i) => ( - - - - ))} ); } diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx index 945cf2f..84883fa 100644 --- a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -1,9 +1,18 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; +import { Box, Sphere } from "@react-three/drei"; -function PillarJibHelper({ crane }: { crane: CraneStatus }) { +function PillarJibHelper({ + crane, + points +}: { + crane: CraneStatus, + points: [THREE.Vector3, THREE.Vector3]; +}) { const { scene } = useThree(); + const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); + const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); const { geometry, position } = useMemo(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); @@ -51,26 +60,81 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) { const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + const yMin = hookWorld.y + hookMaxOffset; + const yMax = hookWorld.y + hookMinOffset; + + function clampToCylinder(pos: THREE.Vector3) { + const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z); + const distance = xzDist.length(); + + let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius); + if (distance > 0) { + clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius)); + } + + const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance); + const y = THREE.MathUtils.clamp(pos.y, yMin, yMax); + + return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y); + } + + const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()]; + const newIsInside: [boolean, boolean] = [false, false]; + + points.forEach((point, i) => { + const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length(); + const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius; + const insideY = point.y >= yMin && point.y <= yMax; + newIsInside[i] = insideXZ && insideY; + + newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point); + }); + + setClampedPoints(newClampedPoints); + setIsInside(newIsInside); + return { geometry, position }; }, [scene, crane.modelUuid]); if (!geometry || !position) return null; return ( - - - + <> + + + + + {points.map((point, i) => ( + + + + ))} + + {clampedPoints && clampedPoints.map((point, i) => ( + + + + ))} + ); } diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index d6b8fd8..bf0fa43 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -36,7 +36,13 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { onAnimationComplete={handleAnimationComplete} /> - + {/* */} ) From fcc5fa64e929eb151568966d109cd4324692374b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 8 Aug 2025 18:05:25 +0530 Subject: [PATCH 11/14] added event handler --- .../actions/pillarJibAction.tsx | 39 ++++++++ .../mechanics/humanMechanics.tsx | 4 +- .../transformControls/transformControls.tsx | 4 +- app/src/modules/scene/scene.tsx | 2 +- app/src/modules/scene/sceneContext.tsx | 4 + .../actionHandler/usePickAndDropHandler.ts | 37 +++++++ .../actions/crane/useCraneActions.ts | 36 +++++++ .../simulation/actions/useActionHandler.ts | 10 +- .../eventManager/useCraneEventManager.ts | 99 +++++++++++++++++++ .../instances/animator/pillarJibAnimator.tsx | 4 +- .../instances/instance/pillarJibInstance.tsx | 32 ++++-- .../triggerHandler/useTriggerHandler.ts | 87 +++++++++++++++- app/src/types/simulationTypes.d.ts | 17 ++++ 13 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx create mode 100644 app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts create mode 100644 app/src/modules/simulation/actions/crane/useCraneActions.ts create mode 100644 app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx new file mode 100644 index 0000000..e7ff2c5 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface WorkerActionProps { + loadCount: { + value: number; + min: number; + max: number; + step: number; + defaultValue: string, + disabled: false, + onChange: (value: number) => void; + }; +} + +const WorkerAction: React.FC = ({ + loadCount +}) => { + return ( +
+ {loadCount && ( + { }} + onChange={(value) => loadCount.onChange(parseInt(value))} + /> + )} +
+ ); +}; + +export default WorkerAction; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index fc73e58..9d8e393 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import ActionsList from "../components/ActionsList"; -import WorkerAction from "../actions/workerAction"; -import AssemblyAction from "../actions/assemblyAction"; +import WorkerAction from "../actions/WorkerAction"; +import AssemblyAction from "../actions/AssemblyAction"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index e0ce1cf..6d5e7d5 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -3,7 +3,7 @@ import * as THREE from "three"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store"; import { useThree } from "@react-three/fiber"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; // import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; @@ -15,6 +15,7 @@ import { useVersionContext } from "../../../builder/version/versionContext"; export default function TransformControl() { const state = useThree(); + const ref = useRef(null); const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { setObjectPosition } = useObjectPosition(); @@ -186,6 +187,7 @@ export default function TransformControl() { <> {(selectedFloorItem && transformMode) && { e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d); }} - gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} + gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true }} > diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index ac17198..6dbfce8 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -45,6 +45,7 @@ type SceneContextValue = { craneStore: CraneStoreType; humanEventManagerRef: React.RefObject; + craneEventManagerRef: React.RefObject; clearStores: () => void; @@ -83,6 +84,7 @@ export function SceneProvider({ const craneStore = useMemo(() => createCraneStore(), []); const humanEventManagerRef = useRef({ humanStates: [] }); + const craneEventManagerRef = useRef({ craneStates: [] }); const clearStores = useMemo(() => () => { assetStore.getState().clearAssets(); @@ -103,6 +105,7 @@ export function SceneProvider({ humanStore.getState().clearHumans(); craneStore.getState().clearCranes(); humanEventManagerRef.current.humanStates = []; + craneEventManagerRef.current.craneStates = []; }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); const contextValue = useMemo(() => ( @@ -125,6 +128,7 @@ export function SceneProvider({ humanStore, craneStore, humanEventManagerRef, + craneEventManagerRef, clearStores, layout } diff --git a/app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts b/app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts new file mode 100644 index 0000000..48896f4 --- /dev/null +++ b/app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts @@ -0,0 +1,37 @@ +import { useCallback } from "react"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { useProductContext } from "../../../products/productContext"; + +export function usePickAndDropHandler() { + const { materialStore, craneStore, productStore } = useSceneContext(); + const { getMaterialById } = materialStore(); + const { getModelUuidByActionUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { incrementCraneLoad, addCurrentMaterial, addCurrentAction } = craneStore(); + + const pickAndDropLogStatus = (materialUuid: string, status: string) => { + echo.info(`${materialUuid}, ${status}`); + } + + const handlePickAndDrop = useCallback((action: CraneAction, materialId?: string) => { + if (!action || action.actionType !== 'pickAndDrop' || !materialId) return; + + const material = getMaterialById(materialId); + if (!material) return; + + const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); + if (!modelUuid) return; + + incrementCraneLoad(modelUuid, 1); + addCurrentAction(modelUuid, action.actionUuid); + addCurrentMaterial(modelUuid, material.materialType, material.materialId); + + pickAndDropLogStatus(material.materialName, `performing pickAndDrop action`); + + }, [getMaterialById]); + + return { + handlePickAndDrop, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/crane/useCraneActions.ts b/app/src/modules/simulation/actions/crane/useCraneActions.ts new file mode 100644 index 0000000..aa7de6f --- /dev/null +++ b/app/src/modules/simulation/actions/crane/useCraneActions.ts @@ -0,0 +1,36 @@ +import { useEffect, useCallback } from 'react'; +import { usePickAndDropHandler } from './actionHandler/usePickAndDropHandler'; + +export function useCraneActions() { + const { handlePickAndDrop } = usePickAndDropHandler(); + + const handleWorkerAction = useCallback((action: CraneAction, materialId: string) => { + handlePickAndDrop(action, materialId); + }, [handlePickAndDrop]); + + const handleCraneAction = useCallback((action: CraneAction, materialId: string) => { + if (!action) return; + + switch (action.actionType) { + case 'pickAndDrop': + handleWorkerAction(action, materialId); + break; + default: + console.warn(`Unknown Human action type: ${action.actionType}`); + } + }, [handleWorkerAction]); + + const cleanup = useCallback(() => { + }, []); + + useEffect(() => { + return () => { + cleanup(); + }; + }, [cleanup]); + + return { + handleCraneAction, + cleanup + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index 7352ae1..99d8225 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -8,6 +8,7 @@ import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions"; import { useStorageActions } from "./storageUnit/useStorageUnitActions"; import { useVehicleActions } from "./vehicle/useVehicleActions"; import { useHumanActions } from "./human/useHumanActions"; +import { useCraneActions } from "./crane/useCraneActions"; import { useCallback, useEffect } from "react"; export function useActionHandler() { @@ -19,6 +20,7 @@ export function useActionHandler() { const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions(); const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions(); const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions(); + const { handleCraneAction, cleanup: cleanupCrane } = useCraneActions(); const handleAction = useCallback((action: Action, materialId?: string) => { if (!action) return; @@ -42,6 +44,9 @@ export function useActionHandler() { case 'worker': case 'assembly': handleHumanAction(action as HumanAction, materialId as string); break; + case 'pickAndDrop': + handleCraneAction(action as CraneAction, materialId as string); + break; default: console.warn(`Unknown action type: ${(action as Action).actionType}`); } @@ -49,7 +54,7 @@ export function useActionHandler() { echo.error("Failed to handle action"); console.error("Error handling action:", error); } - }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction]); + }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction, handleCraneAction]); const cleanup = useCallback(() => { cleanupConveyor(); @@ -58,7 +63,8 @@ export function useActionHandler() { cleanupMachine(); cleanupStorage(); cleanupHuman(); - }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman]); + cleanupCrane(); + }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman, cleanupCrane]); useEffect(() => { return () => { diff --git a/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts new file mode 100644 index 0000000..f8d40d9 --- /dev/null +++ b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts @@ -0,0 +1,99 @@ +import { useEffect } from 'react'; +import { useFrame } from '@react-three/fiber'; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../scene/sceneContext'; +import { useProductContext } from '../../products/productContext'; + +export function useCraneEventManager() { + const { craneStore, productStore, assetStore, craneEventManagerRef } = useSceneContext(); + const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore(); + const { getAssetById } = assetStore(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { isReset } = useResetButtonStore(); + + useEffect(() => { + if ((isReset || !isPlaying) && craneEventManagerRef.current) { + craneEventManagerRef.current.craneStates = []; + } + }, [isReset, isPlaying]); + + const addCraneToMonitor = (craneId: string, callback: () => void, actionUuid: string) => { + const crane = getCraneById(craneId); + const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as CraneAction | undefined; + + if (!crane || !action || action.actionType !== 'pickAndDrop' || !craneEventManagerRef.current) return; + + let state = craneEventManagerRef.current.craneStates.find(c => c.craneId === craneId); + if (!state) { + state = { + craneId, + pendingActions: [], + currentAction: null, + isProcessing: false + }; + craneEventManagerRef.current.craneStates.push(state); + } + + state.pendingActions.push({ + actionUuid, + callback + }); + + if (!state.isProcessing) { + processNextAction(state); + } + }; + + const processNextAction = (state: CraneEventState) => { + if (state.pendingActions.length === 0) { + state.currentAction = null; + return; + } + + state.isProcessing = true; + state.currentAction = state.pendingActions.shift() || null; + }; + + const completeCurrentAction = (state: CraneEventState) => { + processNextAction(state); + }; + + useFrame(() => { + if (!craneEventManagerRef.current || craneEventManagerRef.current.craneStates.length === 0 || !isPlaying || isPaused) return; + + for (const craneState of craneEventManagerRef.current.craneStates) { + if (!craneState.currentAction || !craneState.isProcessing) continue; + + const { craneId, currentAction } = craneState; + const crane = getCraneById(craneId); + const craneAsset = getAssetById(craneId); + const currentCraneAction = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '') as CraneAction | undefined; + + if (!crane || !craneAsset || !currentCraneAction) continue; + if (crane.isActive || crane.state !== "idle") continue; + + if (currentCraneAction.actionType === 'pickAndDrop' && crane.currentLoad < currentCraneAction.maxPickUpCount) { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } + }, 0); + + return { + addCraneToMonitor + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 9c7f3ab..fefad50 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -12,7 +12,7 @@ function PillarJibAnimator({ onAnimationComplete }: { crane: CraneStatus; - points: [THREE.Vector3, THREE.Vector3]; + points: [THREE.Vector3, THREE.Vector3] | null; animationPhase: string; setAnimationPhase: (phase: string) => void; onAnimationComplete: (action: string) => void; @@ -45,7 +45,7 @@ function PillarJibAnimator({ const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return; + if (!base || !trolley || !hook || !points) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index bf0fa43..9c16e78 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,13 +1,34 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import * as THREE from 'three' +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; + import PillarJibAnimator from '../animator/pillarJibAnimator' import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const { isPlaying } = usePlayButtonStore(); + const { craneStore, productStore } = useSceneContext(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { getCraneById } = craneStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const [currentPhase, setCurrentPhase] = useState('idle'); const [animationPhase, setAnimationPhase] = useState('idle'); + const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null); - const position1: [number, number, number] = [5, 1, -4]; - const position2: [number, number, number] = [-2, 2, -2]; + useEffect(() => { + if (isPlaying) { + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + if (!action || action.actionType !== 'pickAndDrop') return; + + if (!crane.isActive && currentPhase === 'idle' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { + console.log('crane: ', crane); + + } + } + }, [crane, currentPhase]) const handleAnimationComplete = (action: string) => { if (action === 'picking') { @@ -27,10 +48,7 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { 0 && + action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && + action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid && + action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + if (model?.type === 'transfer') { + const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (crane) { + setIsPaused(materialId, true); + setCraneScheduled(crane.modelUuid, true); + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + addConveyorToMonitor(conveyor.modelUuid, () => { + addCraneToMonitor(crane.modelUuid, () => { + setIsPaused(materialId, true); + + handleAction(action, materialId); + }, action.actionUuid) + }) + } + } + } + } + } + } + } + } + } } } else if (fromEvent?.type === 'vehicle') { if (toEvent?.type === 'transfer') { @@ -1547,6 +1609,29 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'human') { // Human to Human + } + } else if (fromEvent?.type === 'crane') { + if (toEvent?.type === 'transfer') { + // Crane Unit to Transfer + + } else if (toEvent?.type === 'vehicle') { + // Crane Unit to Vehicle + + } else if (toEvent?.type === 'machine') { + // Crane Unit to Machine + + } else if (toEvent?.type === 'roboticArm') { + // Crane Unit to Robotic Arm + + } else if (toEvent?.type === 'storageUnit') { + // Crane Unit to Storage Unit + + } else if (toEvent?.type === 'human') { + // Crane Unit to Human + + } else if (toEvent?.type === 'crane') { + // Crane Unit to Human + } } } diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index e257ed8..1e90fe8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -336,6 +336,23 @@ type HumanEventManagerState = { humanStates: HumanEventState[]; }; +type CraneEventState = { + craneId: string; + pendingActions: { + actionUuid: string; + callback: () => void; + }[]; + currentAction: { + actionUuid: string; + callback: () => void; + } | null; + isProcessing: boolean; +}; + +type CraneEventManagerState = { + craneStates: CraneEventState[]; +}; + // Materials From a7dc3665ca52fb10726f54678202d05439b6d449 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 11 Aug 2025 12:23:05 +0530 Subject: [PATCH 12/14] added pillarJib trigger --- .../instances/animator/pillarJibAnimator.tsx | 150 ++++++++++++++---- .../instances/helper/pillarJibHelper.tsx | 8 +- .../instances/instance/pillarJibInstance.tsx | 32 ++-- 3 files changed, 138 insertions(+), 52 deletions(-) diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index fefad50..1b1332d 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,18 +1,20 @@ import { useEffect, useState } from 'react'; import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; -import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; function PillarJibAnimator({ crane, points, + setPoints, animationPhase, setAnimationPhase, onAnimationComplete }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3] | null; + setPoints: (points: [THREE.Vector3, THREE.Vector3] | null) => void; animationPhase: string; setAnimationPhase: (phase: string) => void; onAnimationComplete: (action: string) => void; @@ -22,20 +24,46 @@ function PillarJibAnimator({ const { resetAsset } = assetStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); + const { isReset } = useResetButtonStore(); const { speed } = useAnimationPlaySpeed(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); - const [currentTargetIndex, setCurrentTargetIndex] = useState(0); useEffect(() => { - if (!isPlaying) { + if (!isPlaying || isReset) { resetAsset(crane.modelUuid); setAnimationPhase('idle'); - setCurrentTargetIndex(0); - } else if (animationPhase === 'idle') { - setAnimationPhase('init-hook-adjust'); + setPoints(null); } - }, [isPlaying, scene, crane.modelUuid]); + }, [isPlaying, scene, crane.modelUuid, isReset]); + + useEffect(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + + if (!model) return; + const hook = model.getObjectByName('hook'); + + if (!hook) return; + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + if (crane.currentPhase === 'init-pickup') { + if (crane.currentMaterials.length > 0) { + const material = scene.getObjectByProperty('uuid', crane.currentMaterials[0].materialId); + if (material) { + const materialWorld = new THREE.Vector3(); + material.getWorldPosition(materialWorld); + setAnimationPhase('init-hook-adjust'); + setPoints( + [ + new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z), + new THREE.Vector3(materialWorld.x, materialWorld.y + 0.5, materialWorld.z) + ] + ); + } + } + } + }, [crane.currentPhase]) useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); @@ -99,7 +127,7 @@ function PillarJibAnimator({ }); setClampedPoints(newClampedPoints); - }, [crane.modelUuid]); + }, [crane.modelUuid, points]); useFrame(() => { if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return; @@ -122,10 +150,10 @@ function PillarJibAnimator({ if (!model.userData.animationData) { model.userData.animationData = { originalHookY: hook.position.y, - targetHookY: clampedPoints[currentTargetIndex].y - baseWorld.y + 0.5, + targetHookY: clampedPoints[0].y - baseWorld.y, targetDirection: new THREE.Vector2(), targetTrolleyX: 0, - targetWorldPosition: clampedPoints[currentTargetIndex].clone(), + targetWorldPosition: clampedPoints[0].clone(), finalHookTargetY: 0, }; } @@ -137,11 +165,12 @@ function PillarJibAnimator({ switch (animationPhase) { case 'init-hook-adjust': { - const direction = Math.sign(animationData.targetHookY - hook.position.y); + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + const direction = Math.sign((clampedPoints[0].y - baseWorld.y) - hookWorld.y); hook.position.y += direction * hookSpeed; - if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { - hook.position.y = animationData.targetHookY; + if (parseFloat(Math.abs(hookWorld.y - clampedPoints[0].y).toFixed(2)) < 0.05) { setAnimationPhase('init-rotate-base'); } break; @@ -154,7 +183,7 @@ function PillarJibAnimator({ const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); - const targetWorld = clampedPoints[currentTargetIndex]; + const targetWorld = clampedPoints[0]; const targetDir = new THREE.Vector2( targetWorld.x - baseWorld.x, targetWorld.z - baseWorld.z @@ -170,7 +199,7 @@ function PillarJibAnimator({ base.rotation.y += Math.sign(angleDiff) * rotationSpeed; } else { base.rotation.y += angleDiff; - const localTarget = trolley.parent.worldToLocal(clampedPoints[currentTargetIndex].clone()); + const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); animationData.targetTrolleyX = localTarget?.x; setAnimationPhase('init-move-trolley'); } @@ -185,7 +214,7 @@ function PillarJibAnimator({ if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { trolley.position.x = animationData.targetTrolleyX; - animationData.finalHookTargetY = hook.position.y - 0.5; + animationData.finalHookTargetY = hook.position.y; setAnimationPhase('init-final-hook-adjust'); } break; @@ -199,22 +228,87 @@ function PillarJibAnimator({ if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { hook.position.y = animationData.finalHookTargetY; - if (currentTargetIndex < points.length - 1) { - setCurrentTargetIndex(currentTargetIndex + 1); + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: clampedPoints[1].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: clampedPoints[1].clone(), + finalHookTargetY: 0, + }; - model.userData.animationData = { - originalHookY: hook.position.y, - targetHookY: clampedPoints[currentTargetIndex + 1].y - baseWorld.y + 0.5, - targetDirection: new THREE.Vector2(), - targetTrolleyX: 0, - targetWorldPosition: clampedPoints[currentTargetIndex + 1].clone(), - finalHookTargetY: 0, - }; + setAnimationPhase('starting'); + onAnimationComplete('starting'); + } + break; + } + case 'first-hook-adjust': { + const direction = Math.sign(animationData.targetHookY - hook.position.y); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { + hook.position.y = animationData.targetHookY; + setAnimationPhase('first-rotate-base'); + } + break; + } + + case 'first-rotate-base': { + const baseForward = new THREE.Vector3(1, 0, 0); + base.localToWorld(baseForward); + baseForward.sub(baseWorld); + + const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); + + const targetWorld = clampedPoints[1]; + const targetDir = new THREE.Vector2( + targetWorld.x - baseWorld.x, + targetWorld.z - baseWorld.z + ).normalize(); + + const currentAngle = Math.atan2(currentDir.y, currentDir.x); + const targetAngle = Math.atan2(targetDir.y, targetDir.x); + let angleDiff = currentAngle - targetAngle; + + angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); + + if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { + base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + } else { + base.rotation.y += angleDiff; + const localTarget = trolley.parent.worldToLocal(clampedPoints[1].clone()); + animationData.targetTrolleyX = localTarget?.x; + setAnimationPhase('first-move-trolley'); + } + break; + } + + case 'first-move-trolley': { + const dx = animationData.targetTrolleyX - trolley.position.x; + const direction = Math.sign(dx); + trolley.position.x += direction * trolleySpeed; + + if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = animationData.targetTrolleyX; + + animationData.finalHookTargetY = hook.position.y - 0.5; + setAnimationPhase('first-final-hook-adjust'); + } + break; + } + + case 'first-final-hook-adjust': { + const dy = animationData.finalHookTargetY - hook.position.y; + const direction = Math.sign(dy); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { + hook.position.y = animationData.finalHookTargetY; + if (crane.currentPhase === 'init-pickup') { setAnimationPhase('picking'); onAnimationComplete('picking'); - - } else { + } else if (crane.currentPhase === 'pickup-drop') { setAnimationPhase('dropping'); onAnimationComplete('dropping'); } diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx index 84883fa..8998865 100644 --- a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -8,7 +8,7 @@ function PillarJibHelper({ points }: { crane: CraneStatus, - points: [THREE.Vector3, THREE.Vector3]; + points: [THREE.Vector3, THREE.Vector3] | null; }) { const { scene } = useThree(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); @@ -22,7 +22,7 @@ function PillarJibHelper({ const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return { geometry: null, position: null }; + if (!base || !trolley || !hook || !points) return { geometry: null, position: null }; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -94,7 +94,7 @@ function PillarJibHelper({ setIsInside(newIsInside); return { geometry, position }; - }, [scene, crane.modelUuid]); + }, [scene, crane.modelUuid, points]); if (!geometry || !position) return null; @@ -115,7 +115,7 @@ function PillarJibHelper({ /> - {points.map((point, i) => ( + {points && points.map((point, i) => ( ('idle'); const [animationPhase, setAnimationPhase] = useState('idle'); const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null); @@ -23,22 +22,17 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); if (!action || action.actionType !== 'pickAndDrop') return; - if (!crane.isActive && currentPhase === 'idle' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { - console.log('crane: ', crane); - + if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { + setCurrentPhase(crane.modelUuid, 'init-pickup'); } } - }, [crane, currentPhase]) + }, [crane]) const handleAnimationComplete = (action: string) => { - if (action === 'picking') { - setTimeout(() => { - setAnimationPhase('init-hook-adjust'); - }, 3000) - } else if (action === 'dropping') { - setTimeout(() => { - setAnimationPhase('idle'); - }, 3000) + if (action === 'starting') { + setAnimationPhase('first-hook-adjust'); + } else if (action === 'picking') { + setCurrentPhase(crane.modelUuid, 'picking'); } } @@ -49,18 +43,16 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { key={crane.modelUuid} crane={crane} points={points} + setPoints={setPoints} animationPhase={animationPhase} setAnimationPhase={setAnimationPhase} onAnimationComplete={handleAnimationComplete} /> - {/* */} + points={points} + /> ) From 78e1ccf39f72914d1aa73e25e7ceeeed67208c30 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 11 Aug 2025 16:59:36 +0530 Subject: [PATCH 13/14] added undo redo for builder (not for simulation data) --- .../model/eventHandlers/useEventHandlers.ts | 21 +- .../builder/asset/models/model/model.tsx | 30 +- app/src/modules/scene/controls/controls.tsx | 3 + .../selection3D/copyPasteControls3D.tsx | 42 ++- .../selection3D/duplicationControls3D.tsx | 42 ++- .../selection3D/moveControls3D.tsx | 53 ++- .../selection3D/rotateControls3D.tsx | 55 ++- .../selection3D/selectionControls3D.tsx | 38 ++- .../transformControls/transformControls.tsx | 29 +- ...{useRedoHandler.ts => use2DRedoHandler.ts} | 4 +- ...{useUndoHandler.ts => use2DUndoHandler.ts} | 53 ++- .../handlers/use3DRedoHandler.ts | 318 +++++++++++++++++ .../handlers/use3DUndoHandler.ts | 323 ++++++++++++++++++ .../undoRedo2D/undoRedo2DControls.tsx | 10 +- .../undoRedo3D/undoRedo3DControls.tsx | 51 +++ app/src/modules/scene/sceneContext.tsx | 9 +- .../instances/ikInstance/ikInstance.tsx | 10 +- .../simulation/spatialUI/arm/armBotUI.tsx | 4 +- .../asset/floorAsset/getAssetField.ts} | 8 +- app/src/store/builder/useUndoRedo3DStore.ts | 78 +++++ app/src/types/builderTypes.d.ts | 65 ++++ .../utils/shortcutkeys/handleShortcutKeys.ts | 5 +- 22 files changed, 1179 insertions(+), 72 deletions(-) rename app/src/modules/scene/controls/undoRedoControls/handlers/{useRedoHandler.ts => use2DRedoHandler.ts} (99%) rename app/src/modules/scene/controls/undoRedoControls/handlers/{useUndoHandler.ts => use2DUndoHandler.ts} (83%) create mode 100644 app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts create mode 100644 app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts create mode 100644 app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx rename app/src/services/{simulation/ik/getAssetIKs.ts => factoryBuilder/asset/floorAsset/getAssetField.ts} (80%) create mode 100644 app/src/store/builder/useUndoRedo3DStore.ts diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index a0ea33a..1fc26ae 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -30,11 +30,11 @@ export function useModelEventHandlers({ const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { socket } = useSocketStore(); - const { eventStore, productStore, assetStore } = useSceneContext(); + const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { removeAsset } = assetStore(); - const { removeEvent } = eventStore(); + const { removeEvent, getEventByModelUuid } = eventStore(); const { getIsEventInProduct, addPoint, deleteEvent } = productStore(); - const { getEventByModelUuid } = eventStore(); const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); const { setSelectedFloorItem } = useSelectedFloorItem(); @@ -152,6 +152,21 @@ export function useModelEventHandlers({ removeAsset(asset.modelUuid); + push3D({ + type: 'Scene', + actions: [ + { + module: "builder", + actionType: "Asset-Delete", + asset: { + type: "Asset", + assetData: asset, + timeStap: new Date().toISOString() + } + } + ] + }); + echo.success("Model Removed!"); } diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 9767138..3f128bb 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -8,7 +8,7 @@ import useModuleStore from '../../../../../store/useModuleStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { SkeletonUtils } from 'three-stdlib'; -import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; +import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField'; import { ModelAnimator } from './animator/modelAnimator'; import { useModelEventHandlers } from './eventHandlers/useEventHandlers'; @@ -26,19 +26,31 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere const [boundingBox, setBoundingBox] = useState(null); const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); - const [ikData, setIkData] = useState(); + const [fieldData, setFieldData] = useState(); const { selectedAssets } = useSelectedAssets(); useEffect(() => { - if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') { - getAssetIksApi(asset.assetId).then((data) => { - if (data.iks) { - const iks: IK[] = data.iks; - setIkData(iks); + if (!fieldData && asset.eventData) { + getAssetFieldApi(asset.assetId).then((data) => { + if (data.type === 'ArmBot') { + if (data.data) { + const fieldData: IK[] = data.data; + setFieldData(fieldData); + } + } else if (data.type === 'Conveyor') { + if (data.data) { + const fieldData = data.data; + setFieldData(fieldData); + } + } else if (data.type === 'Crane') { + if (data.data) { + const fieldData = data.data; + setFieldData(fieldData); + } } }) } - }, [asset.modelUuid, ikData]) + }, [asset.modelUuid, fieldData]) useEffect(() => { setDeletableFloorItem(null); @@ -157,7 +169,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere position={asset.position} rotation={asset.rotation} visible={asset.isVisible} - userData={{ ...asset, iks: ikData }} + userData={{ ...asset, fieldData: fieldData }} castShadow receiveShadow onDoubleClick={(e) => { diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index 79b9c9f..d58c362 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -16,6 +16,7 @@ import { getUserData } from "../../../functions/getUserData"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; +import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls"; export default function Controls() { const controlsRef = useRef(null); @@ -144,6 +145,8 @@ export default function Controls() { + + diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index e28da3c..e9d7bc3 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -28,7 +28,8 @@ const CopyPasteControls3D = ({ const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { socket } = useSocketStore(); - const { assetStore, eventStore } = useSceneContext(); + const { assetStore, eventStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { addEvent } = eventStore(); const { projectId } = useParams(); const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore(); @@ -198,6 +199,9 @@ const CopyPasteControls3D = ({ const addPastedObjects = () => { if (pastedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToCopy: AssetData[] = []; + pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => { if (pastedAsset) { const assetUuid = pastedAsset.userData.modelUuid; @@ -529,9 +533,45 @@ const CopyPasteControls3D = ({ updateAsset(asset.modelUuid, asset); } + + assetsToCopy.push({ + type: "Asset", + assetData: { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + assetId: newFloorItem.assetId, + position: [position.x, position.y, position.z], + rotation: [pastedAsset.rotation.x, pastedAsset.rotation.y, pastedAsset.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 1, + eventData: newFloorItem.eventData || undefined + }, + timeStap: new Date().toISOString() + }); } }); + if (assetsToCopy.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Copied", + asset: assetsToCopy[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Copied", + assets: assetsToCopy + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + echo.success("Object added!"); clearSelection(); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index f494025..344ae4e 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -26,7 +26,8 @@ const DuplicationControls3D = ({ const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { socket } = useSocketStore(); - const { assetStore, eventStore } = useSceneContext(); + const { assetStore, eventStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { addEvent } = eventStore(); const { projectId } = useParams(); const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore(); @@ -199,6 +200,9 @@ const DuplicationControls3D = ({ const addDuplicatedAssets = () => { if (duplicatedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToDuplicate: AssetData[] = []; + duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => { if (duplicatedAsset) { const assetUuid = duplicatedAsset.userData.modelUuid; @@ -530,9 +534,45 @@ const DuplicationControls3D = ({ updateAsset(asset.modelUuid, asset); } + + assetsToDuplicate.push({ + type: "Asset", + assetData: { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + assetId: newFloorItem.assetId, + position: [position.x, position.y, position.z], + rotation: [duplicatedAsset.rotation.x, duplicatedAsset.rotation.y, duplicatedAsset.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 1, + eventData: newFloorItem.eventData || undefined + }, + timeStap: new Date().toISOString() + }); } }); + if (assetsToDuplicate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Copied", + asset: assetsToDuplicate[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Copied", + assets: assetsToDuplicate + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + echo.success("Object duplicated!"); clearSelection(); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 6f65300..4916886 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -37,7 +37,8 @@ function MoveControls3D({ const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const { userId, organization } = getUserData(); const { projectId } = useParams(); - const { assetStore, eventStore, productStore } = useSceneContext(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { updateAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -284,6 +285,9 @@ function MoveControls3D({ const placeMovedAssets = () => { if (movedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToUpdate: AssetData[] = []; + movedObjects.forEach(async (movedAsset: THREE.Object3D) => { if (movedAsset) { const assetUuid = movedAsset.userData.modelUuid; @@ -291,6 +295,32 @@ function MoveControls3D({ const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid); if (!asset || !model) return; const position = new THREE.Vector3().copy(model.position); + const initialState = initialStates[movedAsset.uuid]; + + if (initialState) { + assetsToUpdate.push({ + type: "Asset", + assetData: { + ...asset, + position: [ + initialState.position.x, + initialState.position.y, + initialState.position.z + ], + rotation: [ + initialState.rotation?.x || 0, + initialState.rotation?.y || 0, + initialState.rotation?.z || 0 + ] + }, + newData: { + ...asset, + position: [position.x, position.y, position.z], + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z] + }, + timeStap: new Date().toISOString() + }); + } const newFloorItem: Types.FloorItemType = { modelUuid: movedAsset.userData.modelUuid, @@ -368,6 +398,27 @@ function MoveControls3D({ } }); + if (assetsToUpdate.length > 0) { + if (assetsToUpdate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Update", + asset: assetsToUpdate[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Update", + assets: assetsToUpdate + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + } + echo.success("Object moved!"); setIsMoving(false); clearSelection(); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 0ca114d..bd5e254 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -32,7 +32,8 @@ function RotateControls3D({ const { socket } = useSocketStore(); const { userId, organization } = getUserData(); const { projectId } = useParams(); - const { assetStore, eventStore, productStore } = useSceneContext(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { updateAsset } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -222,12 +223,43 @@ function RotateControls3D({ const placeRotatedAssets = useCallback(() => { if (rotatedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToUpdate: AssetData[] = []; + rotatedObjects.forEach((obj: THREE.Object3D) => { if (obj && obj.userData.modelUuid) { + const asset = assetStore.getState().getAssetById(obj.userData.modelUuid); + if (!asset) return; + const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z]; + if (initialRotations[obj.uuid] && initialPositions[obj.uuid]) { + assetsToUpdate.push({ + type: "Asset", + assetData: { + ...asset, + position: [ + initialPositions[obj.uuid].x, + initialPositions[obj.uuid].y, + initialPositions[obj.uuid].z + ], + rotation: [ + initialRotations[obj.uuid].x, + initialRotations[obj.uuid].y, + initialRotations[obj.uuid].z + ] + }, + newData: { + ...asset, + position: positionArray, + rotation: rotationArray + }, + timeStap: new Date().toISOString() + }); + } + const newFloorItem: Types.FloorItemType = { modelUuid: obj.userData.modelUuid, modelName: obj.userData.modelName, @@ -305,6 +337,27 @@ function RotateControls3D({ } }); + if (assetsToUpdate.length > 0) { + if (assetsToUpdate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Update", + asset: assetsToUpdate[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Update", + assets: assetsToUpdate + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + } + setIsRotating(false); clearSelection(); }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index c72da8b..91acd3c 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -31,8 +31,9 @@ const SelectionControls3D: React.FC = () => { const boundingBoxRef = useRef(); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); - const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset } = assetStore(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); + const { removeAsset, getAssetById } = assetStore(); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); @@ -266,12 +267,18 @@ const SelectionControls3D: React.FC = () => { const deleteSelection = () => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { + const undoActions: UndoRedo3DAction[] = []; + const assetsToDelete: AssetData[] = []; + const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid); selectedAssets.forEach((selectedMesh: THREE.Object3D) => { + const asset = getAssetById(selectedMesh.userData.modelUuid); + if (!asset) return; + //REST - // const response = await deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName); + // const response = deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName); //SOCKET @@ -321,6 +328,31 @@ const SelectionControls3D: React.FC = () => { } }); + + assetsToDelete.push({ + type: "Asset", + assetData: asset, + timeStap: new Date().toISOString() + }); + }); + + if (assetsToDelete.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Delete", + asset: assetsToDelete[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Delete", + assets: assetsToDelete + }); + } + + push3D({ + type: 'Scene', + actions: undoActions }); selectedUUIDs.forEach((uuid: string) => { diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index 6d5e7d5..af51542 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -24,7 +24,8 @@ export default function TransformControl() { const { socket } = useSocketStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { assetStore, eventStore, productStore } = useSceneContext(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { updateAsset, getAssetById } = assetStore(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); @@ -137,13 +138,37 @@ export default function TransformControl() { projectId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); + + push3D({ + type: 'Scene', + actions: [ + { + module: "builder", + actionType: "Asset-Update", + asset: { + type: "Asset", + assetData: asset, + newData: { + ...asset, + position: [selectedFloorItem.position.x, 0, selectedFloorItem.position.z], + rotation: [selectedFloorItem.rotation.x, selectedFloorItem.rotation.y, selectedFloorItem.rotation.z], + }, + timeStap: new Date().toISOString() + } + } + ] + }); } } useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + const isTextInput = (element: Element | null): boolean => + element instanceof HTMLInputElement || + element instanceof HTMLTextAreaElement || + element?.getAttribute("contenteditable") === "true"; + if (isTextInput(document.activeElement)) return; const keyCombination = detectModifierKeys(e); if (!selectedFloorItem) return; if (keyCombination === "G") { diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DRedoHandler.ts similarity index 99% rename from app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts rename to app/src/modules/scene/controls/undoRedoControls/handlers/use2DRedoHandler.ts index 99902c1..12a0929 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DRedoHandler.ts @@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store"; // import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; // import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; -function useRedoHandler() { +function use2DRedoHandler() { const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext(); const { redo2D, peekRedo2D } = undoRedo2DStore(); const { addWall, removeWall, updateWall } = wallStore(); @@ -352,4 +352,4 @@ function useRedoHandler() { return { handleRedo }; } -export default useRedoHandler; +export default use2DRedoHandler; diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DUndoHandler.ts similarity index 83% rename from app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts rename to app/src/modules/scene/controls/undoRedoControls/handlers/use2DUndoHandler.ts index c822ef9..9f7ff60 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DUndoHandler.ts @@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store"; // import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; // import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; -function useUndoHandler() { +function use2DUndoHandler() { const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext(); const { undo2D, peekUndo2D } = undoRedo2DStore(); const { addWall, removeWall, updateWall } = wallStore(); @@ -67,38 +67,37 @@ function useUndoHandler() { } undo2D(); - }; const handleCreate = (point: UndoRedo2DDataTypeSchema) => { switch (point.type) { - case 'Wall': createWallFromBackend(point.lineData); break; - case 'Floor': createFloorFromBackend(point.lineData); break; - case 'Zone': createZoneFromBackend(point.lineData); break; - case 'Aisle': createAisleFromBackend(point.lineData); break; + case 'Wall': createWallToBackend(point.lineData); break; + case 'Floor': createFloorToBackend(point.lineData); break; + case 'Zone': createZoneToBackend(point.lineData); break; + case 'Aisle': createAisleToBackend(point.lineData); break; } }; const handleRemove = (point: UndoRedo2DDataTypeSchema) => { switch (point.type) { - case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break; - case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break; - case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break; - case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break; + case 'Wall': removeWallToBackend(point.lineData.wallUuid); break; + case 'Floor': removeFloorToBackend(point.lineData.floorUuid); break; + case 'Zone': removeZoneToBackend(point.lineData.zoneUuid); break; + case 'Aisle': removeAisleToBackend(point.lineData.aisleUuid); break; } }; const handleUpdate = (point: UndoRedo2DDataTypeSchema) => { switch (point.type) { - case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break; - case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break; - case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break; - case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break; + case 'Wall': updateWallToBackend(point.lineData.wallUuid, point.lineData); break; + case 'Floor': updateFloorToBackend(point.lineData.floorUuid, point.lineData); break; + case 'Zone': updateZoneToBackend(point.lineData.zoneUuid, point.lineData); break; + case 'Aisle': updateAisleToBackend(point.lineData.aisleUuid, point.lineData); break; } }; - const createWallFromBackend = (wallData: Wall) => { + const createWallToBackend = (wallData: Wall) => { addWall(wallData); if (projectId) { // API @@ -119,7 +118,7 @@ function useUndoHandler() { } }; - const removeWallFromBackend = (wallUuid: string) => { + const removeWallToBackend = (wallUuid: string) => { removeWall(wallUuid); if (projectId) { // API @@ -140,7 +139,7 @@ function useUndoHandler() { } }; - const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => { + const updateWallToBackend = (wallUuid: string, updatedData: Wall) => { updateWall(wallUuid, updatedData); if (projectId) { // API @@ -161,7 +160,7 @@ function useUndoHandler() { } }; - const createFloorFromBackend = (floorData: Floor) => { + const createFloorToBackend = (floorData: Floor) => { addFloor(floorData); if (projectId) { // API @@ -182,7 +181,7 @@ function useUndoHandler() { } }; - const removeFloorFromBackend = (floorUuid: string) => { + const removeFloorToBackend = (floorUuid: string) => { removeFloor(floorUuid); if (projectId) { // API @@ -203,7 +202,7 @@ function useUndoHandler() { } }; - const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => { + const updateFloorToBackend = (floorUuid: string, updatedData: Floor) => { updateFloor(floorUuid, updatedData); if (projectId) { // API @@ -224,7 +223,7 @@ function useUndoHandler() { } }; - const createZoneFromBackend = (zoneData: Zone) => { + const createZoneToBackend = (zoneData: Zone) => { addZone(zoneData); if (projectId) { // API @@ -245,7 +244,7 @@ function useUndoHandler() { } }; - const removeZoneFromBackend = (zoneUuid: string) => { + const removeZoneToBackend = (zoneUuid: string) => { removeZone(zoneUuid); if (projectId) { // API @@ -266,7 +265,7 @@ function useUndoHandler() { } }; - const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => { + const updateZoneToBackend = (zoneUuid: string, updatedData: Zone) => { updateZone(zoneUuid, updatedData); if (projectId) { // API @@ -287,7 +286,7 @@ function useUndoHandler() { } }; - const createAisleFromBackend = (aisleData: Aisle) => { + const createAisleToBackend = (aisleData: Aisle) => { addAisle(aisleData); if (projectId) { // API @@ -308,7 +307,7 @@ function useUndoHandler() { } }; - const removeAisleFromBackend = (aisleUuid: string) => { + const removeAisleToBackend = (aisleUuid: string) => { removeAisle(aisleUuid); if (projectId) { // API @@ -329,7 +328,7 @@ function useUndoHandler() { } }; - const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => { + const updateAisleToBackend = (aisleUuid: string, updatedData: Aisle) => { updateAisle(aisleUuid, updatedData); if (projectId) { // API @@ -353,4 +352,4 @@ function useUndoHandler() { return { handleUndo }; } -export default useUndoHandler; \ No newline at end of file +export default use2DUndoHandler; \ No newline at end of file diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts new file mode 100644 index 0000000..b44f6a7 --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts @@ -0,0 +1,318 @@ +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSceneContext } from "../../../sceneContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { useSocketStore } from "../../../../../store/builder/store"; + +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; + +// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; +// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi"; + +function use3DRedoHandler() { + const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext(); + const { deleteEvent } = productStore(); + const { addEvent, removeEvent } = eventStore(); + const { updateAsset, removeAsset, addAsset } = assetStore(); + const { redo3D, peekRedo3D } = undoRedo3DStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + const handleRedo = () => { + const redoData = peekRedo3D(); + if (!redoData) return; + + if (redoData.type === 'Scene') { + const { actions } = redoData; + + actions.forEach(action => { + const { actionType } = action; + + if ('asset' in action) { + const asset = action.asset; + + if (actionType === 'Asset-Add') { + handleAdd(asset); + } else if (actionType === 'Asset-Delete') { + handleDelete(asset); + } else if (actionType === 'Asset-Update') { + handleUpdate(asset); + } else if (actionType === 'Asset-Copied') { + handleCopy(asset); + } else if (actionType === 'Asset-Duplicated') { + handleDuplicate(asset); + } + + } else if ('assets' in action) { + const assets = action.assets; + + if (actionType === 'Assets-Add') { + assets.forEach(handleAdd); + } else if (actionType === 'Assets-Delete') { + assets.forEach(handleDelete); + } else if (actionType === 'Assets-Update') { + assets.forEach(handleUpdate); + } else if (actionType === 'Assets-Copied') { + assets.forEach(handleCopy); + } else if (actionType === 'Assets-Duplicated') { + assets.forEach(handleDuplicate); + } + } + }); + } else if (redoData.type === 'UI') { + // Handle UI actions if needed + } + + redo3D(); + }; + + + const handleAdd = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': addAssetToBackend(asset.assetData); break; + case 'WallAsset': addWallAssetToBackend(asset.assetData); break; + } + }; + + const handleDelete = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': deleteAssetToBackend(asset.assetData); break; + case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break; + } + } + + const handleUpdate = (asset: AssetData) => { + if (!asset.newData) return; + switch (asset.type) { + case 'Asset': updateAssetToBackend(asset.newData.modelUuid, asset.newData); break; + case 'WallAsset': updateWallAssetToBackend(asset.newData.modelUuid, asset.newData); break; + } + } + + const handleCopy = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': copyAssetToBackend(asset.assetData); break; + case 'WallAsset': copyWallAssetToBackend(asset.assetData); break; + } + } + + const handleDuplicate = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': duplicateAssetToBackend(asset.assetData); break; + case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break; + } + } + + + const addAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const deleteAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => { + updateAsset(modelUuid, updatedData); + if (projectId) { + const data = { + organization, + modelUuid: updatedData.modelUuid, + modelName: updatedData.modelName, + assetId: updatedData.assetId, + position: updatedData.position, + rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] }, + isLocked: false, + isVisible: true, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const copyAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const duplicateAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const addWallAssetToBackend = (assetData: WallAsset) => { + + } + + const deleteWallAssetToBackend = (assetData: WallAsset) => { + + } + + const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => { + + } + + const copyWallAssetToBackend = (assetData: WallAsset) => { + + } + + const duplicateWallAssetToBackend = (assetData: WallAsset) => { + + } + + return { handleRedo }; +} + +export default use3DRedoHandler; diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts new file mode 100644 index 0000000..50fd6cf --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts @@ -0,0 +1,323 @@ +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSceneContext } from "../../../sceneContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { useSocketStore } from "../../../../../store/builder/store"; + +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; + +// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; +// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi"; + +function use3DUndoHandler() { + const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext(); + const { deleteEvent } = productStore(); + const { addEvent, removeEvent } = eventStore(); + const { updateAsset, removeAsset, addAsset } = assetStore(); + const { undo3D, peekUndo3D } = undoRedo3DStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + const handleUndo = () => { + const unDoData = peekUndo3D(); + if (!unDoData) return; + + if (unDoData.type === 'Scene') { + const { actions } = unDoData; + + actions.forEach(action => { + const { actionType } = action; + + if ('asset' in action) { + const asset = action.asset; + + if (actionType === 'Asset-Add') { + handleDelete(asset); + } else if (actionType === 'Asset-Delete') { + handleAdd(asset); + } else if (actionType === 'Asset-Update') { + handleUpdate(asset); + } else if (actionType === 'Asset-Copied') { + handleCopy(asset); + } else if (actionType === 'Asset-Duplicated') { + handleDuplicate(asset); + } + + } else if ('assets' in action) { + const assets = action.assets; + + if (actionType === 'Assets-Add') { + assets.forEach(handleDelete); + } else if (actionType === 'Assets-Delete') { + assets.forEach(handleAdd); + } else if (actionType === 'Assets-Update') { + assets.forEach(handleUpdate); + } else if (actionType === 'Assets-Copied') { + assets.forEach(handleCopy); + } else if (actionType === 'Assets-Duplicated') { + assets.forEach(handleDuplicate); + } + } + }); + } else if (unDoData.type === 'UI') { + // Handle UI actions if needed + } + + undo3D(); + }; + + + const handleAdd = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': addAssetToBackend(asset.assetData); break; + case 'WallAsset': addWallAssetToBackend(asset.assetData); break; + } + }; + + const handleDelete = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': deleteAssetToBackend(asset.assetData); break; + case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break; + } + } + + const handleUpdate = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': updateAssetToBackend(asset.assetData.modelUuid, asset.assetData); break; + case 'WallAsset': updateWallAssetToBackend(asset.assetData.modelUuid, asset.assetData); break; + } + } + + const handleCopy = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': copyAssetToBackend(asset.assetData); break; + case 'WallAsset': copyWallAssetToBackend(asset.assetData); break; + } + } + + const handleDuplicate = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': duplicateAssetToBackend(asset.assetData); break; + case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break; + } + } + + + const addAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const deleteAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => { + updateAsset(modelUuid, updatedData); + if (projectId) { + const data = { + organization, + modelUuid: updatedData.modelUuid, + modelName: updatedData.modelName, + assetId: updatedData.assetId, + position: updatedData.position, + rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] }, + isLocked: false, + isVisible: true, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const copyAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const duplicateAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const addWallAssetToBackend = (assetData: WallAsset) => { + + } + + const deleteWallAssetToBackend = (assetData: WallAsset) => { + + } + + const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => { + + } + + const copyWallAssetToBackend = (assetData: WallAsset) => { + + } + + const duplicateWallAssetToBackend = (assetData: WallAsset) => { + + } + + return { handleUndo }; +} + +export default use3DUndoHandler; \ No newline at end of file diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx index d45518c..1965087 100644 --- a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx @@ -4,21 +4,21 @@ import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModi import { useSocketStore, useToggleView } from '../../../../../store/builder/store'; import { useVersionContext } from '../../../../builder/version/versionContext'; -import useUndoHandler from '../handlers/useUndoHandler'; -import useRedoHandler from '../handlers/useRedoHandler'; +import use2DUndoHandler from '../handlers/use2DUndoHandler'; +import use2DRedoHandler from '../handlers/use2DRedoHandler'; function UndoRedo2DControls() { const { undoRedo2DStore } = useSceneContext(); const { undoStack, redoStack } = undoRedo2DStore(); const { toggleView } = useToggleView(); - const { handleUndo } = useUndoHandler(); - const { handleRedo } = useRedoHandler(); + const { handleUndo } = use2DUndoHandler(); + const { handleRedo } = use2DRedoHandler(); const { socket } = useSocketStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); useEffect(() => { - console.log(undoStack, redoStack); + // console.log(undoStack, redoStack); }, [undoStack, redoStack]); useEffect(() => { diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx new file mode 100644 index 0000000..9331320 --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx @@ -0,0 +1,51 @@ +import { useEffect } from 'react' +import { useSceneContext } from '../../../sceneContext' +import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys'; +import { useSocketStore, useToggleView } from '../../../../../store/builder/store'; +import { useVersionContext } from '../../../../builder/version/versionContext'; +import useModuleStore from '../../../../../store/useModuleStore'; + +import use3DUndoHandler from '../handlers/use3DUndoHandler'; +import use3DRedoHandler from '../handlers/use3DRedoHandler'; + +function UndoRedo3DControls() { + const { undoRedo3DStore } = useSceneContext(); + const { undoStack, redoStack } = undoRedo3DStore(); + const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { handleUndo } = use3DUndoHandler(); + const { handleRedo } = use3DRedoHandler(); + const { socket } = useSocketStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + + useEffect(() => { + console.log(undoStack, redoStack); + }, [undoStack, redoStack]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === 'Ctrl+Z') { + handleUndo(); + } + + if (keyCombination === 'Ctrl+Y') { + handleRedo(); + } + }; + + if (!toggleView) { + window.addEventListener('keydown', handleKeyDown); + } + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [toggleView, undoStack, redoStack, socket, selectedVersion, activeModule]); + + return null; +} + +export default UndoRedo3DControls; \ No newline at end of file diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 6dbfce8..73e5f9c 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -8,6 +8,7 @@ import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore'; import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore'; +import { createUndoRedo3DStore, UndoRedo3DStoreType } from '../../store/builder/useUndoRedo3DStore'; import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore'; import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore'; @@ -31,6 +32,7 @@ type SceneContextValue = { floorStore: FloorStoreType, undoRedo2DStore: UndoRedo2DStoreType, + undoRedo3DStore: UndoRedo3DStoreType, eventStore: EventStoreType, productStore: ProductStoreType, @@ -70,6 +72,7 @@ export function SceneProvider({ const floorStore = useMemo(() => createFloorStore(), []); const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []); + const undoRedo3DStore = useMemo(() => createUndoRedo3DStore(), []); const eventStore = useMemo(() => createEventStore(), []); const productStore = useMemo(() => createProductStore(), []); @@ -94,6 +97,7 @@ export function SceneProvider({ zoneStore.getState().clearZones(); floorStore.getState().clearFloors(); undoRedo2DStore.getState().clearUndoRedo2D(); + undoRedo3DStore.getState().clearUndoRedo3D(); eventStore.getState().clearEvents(); productStore.getState().clearProducts(); materialStore.getState().clearMaterials(); @@ -106,7 +110,7 @@ export function SceneProvider({ craneStore.getState().clearCranes(); humanEventManagerRef.current.humanStates = []; craneEventManagerRef.current.craneStates = []; - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); const contextValue = useMemo(() => ( { @@ -117,6 +121,7 @@ export function SceneProvider({ zoneStore, floorStore, undoRedo2DStore, + undoRedo3DStore, eventStore, productStore, materialStore, @@ -132,7 +137,7 @@ export function SceneProvider({ clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]); return ( diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index ab6041c..b43c7fa 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -22,7 +22,7 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { const trySetup = () => { const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid); - if (!targetMesh || !targetMesh.userData.iks || targetMesh.userData.iks.length < 1) { + if (!targetMesh || !targetMesh.userData.fieldData || targetMesh.userData.fieldData.length < 1) { retryId = setTimeout(trySetup, 100); return; } @@ -34,8 +34,8 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { }); if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return; - const rawIks: IK[] = targetMesh.userData.iks; - const iks = rawIks.map((ik) => ({ + const rawIks: IK[] = targetMesh.userData.fieldData; + const fieldData = rawIks.map((ik) => ({ target: ik.target, effector: ik.effector, links: ik.links.map((link) => ({ @@ -51,10 +51,10 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { minheight: ik.minheight, })); - const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks); + const solver = new CCDIKSolver(OOI.Skinned_Mesh, fieldData); setIkSolver(solver); - // const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05) + // const helper = new CCDIKHelper(OOI.Skinned_Mesh, fieldData, 0.05) // scene.add(helper); }; diff --git a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx index dfd500a..b39076e 100644 --- a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx +++ b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx @@ -173,8 +173,8 @@ const ArmBotUI = () => { const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || ''); - const iks = targetMesh?.userData?.iks; - const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {}; + const fieldData = targetMesh?.userData?.fieldData; + const firstIK = Array.isArray(fieldData) && fieldData.length > 0 ? fieldData[0] : {}; const { handlePointerDown } = useDraggableGLTF( updatePointToState, diff --git a/app/src/services/simulation/ik/getAssetIKs.ts b/app/src/services/factoryBuilder/asset/floorAsset/getAssetField.ts similarity index 80% rename from app/src/services/simulation/ik/getAssetIKs.ts rename to app/src/services/factoryBuilder/asset/floorAsset/getAssetField.ts index cfc6ad5..83567a9 100644 --- a/app/src/services/simulation/ik/getAssetIKs.ts +++ b/app/src/services/factoryBuilder/asset/floorAsset/getAssetField.ts @@ -1,9 +1,9 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; -export const getAssetIksApi = async (assetId: string) => { +export const getAssetFieldApi = async (assetId: string) => { try { const response = await fetch( - `${url_Backend_dwinzo}/api/v2/getAssetIks/${assetId}`, + `${url_Backend_dwinzo}/api/v2/getAssetField/${assetId}`, { method: "GET", headers: { @@ -20,13 +20,13 @@ export const getAssetIksApi = async (assetId: string) => { } if (!response.ok) { - console.error("Failed to fetch assetIks"); + console.error("Failed to fetch asset field"); } const result = await response.json(); return result; } catch (error) { - echo.error("Failed to get assetIks"); + echo.error("Failed to get asset field"); if (error instanceof Error) { console.log(error.message); } else { diff --git a/app/src/store/builder/useUndoRedo3DStore.ts b/app/src/store/builder/useUndoRedo3DStore.ts new file mode 100644 index 0000000..e8c3ce3 --- /dev/null +++ b/app/src/store/builder/useUndoRedo3DStore.ts @@ -0,0 +1,78 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import { undoRedoConfig } from '../../types/world/worldConstants'; + +type UndoRedo3DStore = { + undoStack: UndoRedo3DTypes[]; + redoStack: UndoRedo3DTypes[]; + + push3D: (entry: UndoRedo3DTypes) => void; + undo3D: () => UndoRedo3DTypes | undefined; + redo3D: () => UndoRedo3DTypes | undefined; + clearUndoRedo3D: () => void; + + peekUndo3D: () => UndoRedo3DTypes | undefined; + peekRedo3D: () => UndoRedo3DTypes | undefined; +}; + +export const createUndoRedo3DStore = () => { + return create()( + immer((set, get) => ({ + undoStack: [], + redoStack: [], + + push3D: (entry) => { + set((state) => { + state.undoStack.push(entry); + + if (state.undoStack.length > undoRedoConfig.undoRedoCount) { + state.undoStack.shift(); + } + + state.redoStack = []; + }); + }, + + undo3D: () => { + let lastAction: UndoRedo3DTypes | undefined; + set((state) => { + lastAction = state.undoStack.pop(); + if (lastAction) { + state.redoStack.unshift(lastAction); + } + }); + return lastAction; + }, + + redo3D: () => { + let redoAction: UndoRedo3DTypes | undefined; + set((state) => { + redoAction = state.redoStack.shift(); + if (redoAction) { + state.undoStack.push(redoAction); + } + }); + return redoAction; + }, + + clearUndoRedo3D: () => { + set((state) => { + state.undoStack = []; + state.redoStack = []; + }); + }, + + peekUndo3D: () => { + const stack = get().undoStack; + return stack.length > 0 ? stack[stack.length - 1] : undefined; + }, + + peekRedo3D: () => { + const stack = get().redoStack; + return stack.length > 0 ? stack[0] : undefined; + }, + })) + ); +} + +export type UndoRedo3DStoreType = ReturnType; \ No newline at end of file diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index 63ed97b..3487fc6 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -253,4 +253,69 @@ type UndoRedo2DTypes = UndoRedo2DDraw | UndoRedo2DUi type UndoRedo2D = { undoStack: UndoRedo2DTypes[]; redoStack: UndoRedo2DTypes[]; +}; + + +// Undo/Redo 3D + +type AssetType = { + type: "Asset"; + assetData: Asset; + newData?: Asset; + eventMetaData?: EventsSchema; + timeStap: string; +} + +type WallAssetType = { + type: "WallAsset"; + assetData: WallAsset; + newData?: WallAsset; + timeStap: string; +} + +type AssetData = AssetType | WallAssetType; + +type UndoRedo3DActionBuilderSchema = { + module: "builder"; + actionType: "Asset-Add" | "Asset-Delete" | "Asset-Update" | "Asset-Duplicated" | "Asset-Copied" | "Wall-Asset-Add" | "Wall-Asset-Delete" | "Wall-Asset-Update"; + asset: AssetData; +} + +type UndoRedo3DActionSimulationSchema = { + module: "simulation"; + actionType: ''; +} + +type UndoRedo3DActionSchema = UndoRedo3DActionBuilderSchema | UndoRedo3DActionSimulationSchema; + +type UndoRedo3DActionsBuilderSchema = { + module: "builder"; + actionType: "Assets-Add" | "Assets-Delete" | "Assets-Update" | "Assets-Duplicated" | "Assets-Copied" | "Wall-Assets-Add" | "Wall-Assets-Delete" | "Wall-Assets-Update"; + assets: AssetData[]; +} + +type UndoRedo3DActionsSimulationSchema = { + module: "simulation"; + actionType: ''; +} + +type UndoRedo3DActionsSchema = UndoRedo3DActionsBuilderSchema | UndoRedo3DActionsSimulationSchema; + +type UndoRedo3DAction = UndoRedo3DActionSchema | UndoRedo3DActionsSchema; + +type UndoRedo3DDraw = { + type: 'Scene'; + actions: UndoRedo3DAction[]; +}; + +type UndoRedo3DUi = { + type: 'UI'; + action: any; // Define UI actions as needed +} + +type UndoRedo3DTypes = UndoRedo3DDraw | UndoRedo3DUi; + +type UndoRedo3D = { + undoStack: UndoRedo3DTypes[]; + redoStack: UndoRedo3DTypes[]; }; \ No newline at end of file diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index ffd9ddf..7abbe2f 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -189,12 +189,9 @@ const KeyPressListener: React.FC = () => { }; const handleKeyPress = (event: KeyboardEvent) => { - if (isTextInput(document.activeElement)) return; - const keyCombination = detectModifierKeys(event); - if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") - return; + if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") return; if (keyCombination === "ESCAPE") { setWalkMode(false); From 01f0fa7cd735710315aa15a1ceb5055f7db5026c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 11 Aug 2025 17:09:39 +0530 Subject: [PATCH 14/14] merge --- app/src/components/ui/FileMenu.tsx | 3 +- app/src/modules/scene/scene.tsx | 46 +++++++++------------ app/src/services/dashboard/updateProject.ts | 2 - 3 files changed, 20 insertions(+), 31 deletions(-) diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 843af5d..4575b3b 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -74,7 +74,7 @@ const FileMenu: React.FC = () => { // } //API for projects rename - + const updatedProjectName = await updateProject( projectId, userId, @@ -82,7 +82,6 @@ const FileMenu: React.FC = () => { undefined, projectName ); - console.log("updatedProjectName: ", updatedProjectName, projectId); } catch (error) { console.error("Error updating project name:", error); } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index f463e52..feddc2c 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -33,33 +33,25 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co useEffect(() => { if (!projectId && loadingProgress > 1) return; - getAllProjects(userId, organization) - .then((projects) => { - if (!projects || !projects.Projects) return; - let project = projects.Projects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId - ); - const canvas = document - .getElementById("sceneCanvas") - ?.getElementsByTagName("canvas")[0]; - if (!canvas) return; - const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL( - "image/png" - ); - const updateProjects = { - projectId: project?._id, - organization, - userId, - projectName: project?.projectName, - thumbnail: screenshotDataUrl, - }; - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } - }) - .catch((err) => { - console.error(err); - }); + getAllProjects(userId, organization).then((projects) => { + if (!projects || !projects.Projects) return; + let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId); + const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0]; + if (!canvas) return; + const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png"); + const updateProjects = { + projectId: project?._id, + organization, + userId, + projectName: project?.projectName, + thumbnail: screenshotDataUrl, + }; + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + }).catch((err) => { + console.error(err); + }); // eslint-disable-next-line }, [activeModule, assets, loadingProgress]); diff --git a/app/src/services/dashboard/updateProject.ts b/app/src/services/dashboard/updateProject.ts index ab9ac0f..42e0d44 100644 --- a/app/src/services/dashboard/updateProject.ts +++ b/app/src/services/dashboard/updateProject.ts @@ -30,7 +30,6 @@ export const updateProject = async ( body: JSON.stringify(body), } ); - console.log('body: ', body); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { @@ -42,7 +41,6 @@ export const updateProject = async ( } const result = await response.json(); - console.log('result: ', result); return result; } catch (error) { if (error instanceof Error) {