From a517905dadb2e97fb734bd19ae74dcc9d9ba3965 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 16 Sep 2025 16:44:01 +0530 Subject: [PATCH] added collabcam socket response --- app/src/app.tsx | 12 +- .../components/layout/scenes/MainScene.tsx | 4 +- .../components/layout/sidebarRight/Header.tsx | 199 ++++++------ .../templates/CollaborationPopup.tsx | 225 ++++++-------- app/src/components/ui/FileMenu.tsx | 11 - .../ui/collaboration/threads/Messages.tsx | 4 +- .../ui/collaboration/threads/ThreadChat.tsx | 12 +- app/src/components/ui/log/LoggerContext.tsx | 161 +++++----- app/src/global.d.ts | 16 +- .../collaboration/camera/collabCams.tsx | 289 ++++-------------- .../socket/threadSocketResponses.dev.tsx | 6 +- .../collaboration/socket/userResponses.tsx | 24 +- .../threadInstances/threadInstances.tsx | 4 +- app/src/modules/scene/controls/controls.tsx | 2 +- app/src/modules/scene/sceneContext.tsx | 14 +- app/src/pages/Dashboard.tsx | 2 +- app/src/pages/Project.tsx | 74 ++++- .../collab/comments/addCommentApi.ts | 2 +- .../collab/comments/editCommentApi.ts | 2 +- app/src/store/builder/store.ts | 8 - .../collaboration/useCollabUsersStore.ts | 50 +++ .../{useCommentStore.ts => useThreadStore.ts} | 24 +- app/src/store/socket/useSocketStore.ts | 98 +++--- app/src/types/collaborationTypes.d.ts | 16 + 24 files changed, 582 insertions(+), 677 deletions(-) create mode 100644 app/src/store/collaboration/useCollabUsersStore.ts rename app/src/store/collaboration/{useCommentStore.ts => useThreadStore.ts} (82%) diff --git a/app/src/app.tsx b/app/src/app.tsx index 2b2a25d..a2d7df7 100644 --- a/app/src/app.tsx +++ b/app/src/app.tsx @@ -1,14 +1,16 @@ -import React, { useEffect } from "react"; import { Cache } from "three"; +import React, { useEffect } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { LoggerProvider } from "./components/ui/log/LoggerContext"; + +import UserAuth from "./pages/UserAuth"; +import ForgotPassword from "./pages/ForgotPassword"; import Dashboard from "./pages/Dashboard"; import Project from "./pages/Project"; -import UserAuth from "./pages/UserAuth"; -import "./styles/main.scss"; -import { LoggerProvider } from "./components/ui/log/LoggerContext"; -import ForgotPassword from "./pages/ForgotPassword"; import PageNotFound from "./pages/PageNotFound"; +import "./styles/main.scss"; + const App: React.FC = () => { useEffect(() => { Cache.clear(); diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index b2d0c99..701517d 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -65,7 +65,9 @@ function MainScene() { useEffect(() => { if (builderSocket && projectId) { - builderSocket.emit("joinRoom", { projectId: projectId }); + setTimeout(() => { + builderSocket.emit("joinRoom", { projectId: projectId }); + }, 1000); } }, [builderSocket, projectId]); diff --git a/app/src/components/layout/sidebarRight/Header.tsx b/app/src/components/layout/sidebarRight/Header.tsx index 0377e3f..630cc72 100644 --- a/app/src/components/layout/sidebarRight/Header.tsx +++ b/app/src/components/layout/sidebarRight/Header.tsx @@ -1,7 +1,6 @@ import React, { useState } from "react"; import orgImg from "../../../assets/image/orgTemp.png"; -import { useActiveUsers, useCamMode } from "../../../store/builder/store"; -import { ActiveUser } from "../../../types/users"; +import { useCamMode } from "../../../store/builder/store"; import CollaborationPopup from "../../templates/CollaborationPopup"; import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor"; import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore"; @@ -9,120 +8,110 @@ import { useToggleStore } from "../../../store/ui/useUIToggleStore"; import { ToggleSidebarIcon } from "../../icons/HeaderIcons"; import useModuleStore from "../../../store/ui/useModuleStore"; import { getUserData } from "../../../functions/getUserData"; +import { useSceneContext } from "../../../modules/scene/sceneContext"; const Header: React.FC = () => { - const { activeUsers } = useActiveUsers(); - const { userName } = getUserData(); - const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore(); - const { activeModule } = useModuleStore(); + const { collabUsersStore } = useSceneContext(); + const { collabUsers } = collabUsersStore(); + const { userId, userName } = getUserData(); + const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore(); + const { activeModule } = useModuleStore(); - const guestUsers: ActiveUser[] = activeUsers.filter( - (user: ActiveUser) => user.userName !== userName - ); + const guestUsers = collabUsers.filter((user) => user.userId !== userId); - const [userManagement, setUserManagement] = useState(false); - const { setSelectedUser } = useSelectedUserStore(); - const { setCamMode } = useCamMode(); + const [userManagement, setUserManagement] = useState(false); + const { setSelectedUser } = useSelectedUserStore(); + const { setCamMode } = useCamMode(); - function handleUserFollow(user: any, index: number) { - const position = { - x: user.position?.x!, - y: user.position?.y!, - z: user.position?.z!, - }; - const target = { - x: user.target?.x!, - y: user.target?.y!, - z: user.target?.z!, - }; - const rotation = { - x: user.rotation?.x!, - y: user.rotation?.y!, - z: user.rotation?.z!, - }; + function handleUserFollow(user: any, index: number) { + const position = { + x: user.position?.x!, + y: user.position?.y!, + z: user.position?.z!, + }; + const target = { + x: user.target?.x!, + y: user.target?.y!, + z: user.target?.z!, + }; + const rotation = { + x: user.rotation?.x!, + y: user.rotation?.y!, + z: user.rotation?.z!, + }; - // retun on no data - if (!position || !target || !rotation) return; + // retun on no data + if (!position || !target || !rotation) return; - // Set the selected user in the store - setSelectedUser({ - color: getAvatarColor(index, user.userName), - name: user.userName, - id: user.id, - location: { position, rotation, target }, - }); - setCamMode("FollowPerson"); - } + // Set the selected user in the store + setSelectedUser({ + color: getAvatarColor(index, user.userName), + name: user.userName, + id: user.id, + location: { position, rotation, target }, + }); + setCamMode("FollowPerson"); + } - return ( - <> - {userManagement && ( - - )} -
-
- - - {/*
+ return ( + <> + {userManagement && } +
+
+ + + {/*
*/} -
-
-
-
- {guestUsers.length > 3 && ( -
+{guestUsers.length - 3}
- )} - {guestUsers.slice(0, 3).map((user, index) => ( - - ))} -
-
-
{userName?.charAt(0).toUpperCase()}
-
- +
+
+
+
+ {guestUsers.length > 3 &&
+{guestUsers.length - 3}
} + {guestUsers.slice(0, 3).map((user, index) => ( + + ))} +
+
+
{userName?.charAt(0).toUpperCase()}
+
+ +
+
+
-
-
-
- - ); + + ); }; export default Header; diff --git a/app/src/components/templates/CollaborationPopup.tsx b/app/src/components/templates/CollaborationPopup.tsx index cd246fc..b336829 100644 --- a/app/src/components/templates/CollaborationPopup.tsx +++ b/app/src/components/templates/CollaborationPopup.tsx @@ -1,11 +1,9 @@ import React, { useEffect, useState } from "react"; import RenderOverlay from "./Overlay"; -import { ArrowIcon, CloseIcon } from "../icons/ExportCommonIcons"; +import { CloseIcon } from "../icons/ExportCommonIcons"; import { AccessOption, User } from "../../types/users"; import RegularDropDown from "../ui/inputs/RegularDropDown"; -import { access } from "fs"; import MultiEmailInvite from "../ui/inputs/MultiEmailInvite"; -import { useActiveUsers } from "../../store/builder/store"; import { getUserData } from "../../functions/getUserData"; import { getProjectSharedList } from "../../services/factoryBuilder/collab/project/getProjectSharedList"; import { useParams } from "react-router-dom"; @@ -13,147 +11,124 @@ import { shareAccess } from "../../services/factoryBuilder/collab/project/shareA import { getAvatarColor } from "../../modules/collaboration/functions/getAvatarColor"; interface UserListTemplateProps { - user: User; + user: User; } const UserListTemplate: React.FC = ({ user }) => { - const [accessSelection, setAccessSelection] = useState(user?.Access); - const { projectId } = useParams(); + const [accessSelection, setAccessSelection] = useState(user?.Access); + const { projectId } = useParams(); - const accessUpdate = async ({ option }: AccessOption) => { - if (!projectId) return - const accessSelection = await shareAccess(projectId, user.userId, option) - setAccessSelection(option); - } + const accessUpdate = async ({ option }: AccessOption) => { + if (!projectId) return; + const accessSelection = await shareAccess(projectId, user.userId, option); + setAccessSelection(option); + }; - return ( -
-
-
- {user.profileImage ? ( - {`${user. - ) : ( -
- {user. - userName.charAt(0).toUpperCase()} + return ( +
+
+
+ {user.profileImage ? ( + {`${user.userName}'s + ) : ( +
+ {user.userName.charAt(0).toUpperCase()} +
+ )} +
+
{user.userName.charAt(0).toUpperCase() + user.userName.slice(1).toLowerCase()}
+
+
+ accessUpdate({ option })} search={false} /> + {/* */}
- )}
-
{user. - userName.charAt(0).toUpperCase() + user. - userName.slice(1).toLowerCase()}
-
-
- accessUpdate({ option })} - search={false} - /> - {/* */} -
-
- ); + ); }; interface CollaborateProps { - setUserManagement: (value: boolean) => void; + setUserManagement: (value: boolean) => void; } -const CollaborationPopup: React.FC = ({ - setUserManagement, -}) => { - const { activeUsers } = useActiveUsers(); - const { userName } = getUserData(); - const [users, setUsers] = useState([]) - const { projectId } = useParams(); +const CollaborationPopup: React.FC = ({ setUserManagement }) => { + const { userName } = getUserData(); + const [users, setUsers] = useState([]); + const { projectId } = useParams(); - function getData() { - if (!projectId) return; - getProjectSharedList(projectId).then((allUser) => { - const accesMail = allUser?.datas || [] - setUsers(accesMail) - }).catch((err) => { + function getData() { + if (!projectId) return; + getProjectSharedList(projectId) + .then((allUser) => { + const accesMail = allUser?.datas || []; + setUsers(accesMail); + }) + .catch((err) => {}); + } - }) - } + useEffect(() => { + getData(); + }, []); - useEffect(() => { - getData(); - }, []) - - useEffect(() => { - // - }, [activeUsers]); - return ( - -
{ - setUserManagement(false); - }} - > -
{ - e.preventDefault(); - e.stopPropagation(); - }} - > -
-
Share this file
-
- {/*
copy link
*/} -
+
{ - setUserManagement(false); + setUserManagement(false); }} - > - -
-
-
-
- -
-
-
-
-
Who has access
-
-
-
Untitled
-
- {users.length} persons -
+ > +
{ + e.preventDefault(); + e.stopPropagation(); + }} + > +
+
Share this file
+
+ {/*
copy link
*/} +
{ + setUserManagement(false); + }} + > + +
+
+
+
+ +
+
+
+
+
Who has access
+
+
+
Untitled
+
{users.length} persons
+
+
+
+
+
+
{userName && userName[0].toUpperCase()}
+ {userName && userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()} +
+
you
+
+ {users.map((user, index) => ( + + ))} +
+
+
-
-
-
-
-
{userName && userName[0].toUpperCase()}
- {userName && userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()} -
-
you
-
- {users.map((user, index) => ( - - ))} -
-
-
-
- - ); + + ); }; export default CollaborationPopup; diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 33a85fc..9e41929 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -14,7 +14,6 @@ const FileMenu: React.FC = () => { 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(); @@ -58,16 +57,6 @@ const FileMenu: React.FC = () => { 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 updateProjectApi(projectId, undefined, projectName); diff --git a/app/src/components/ui/collaboration/threads/Messages.tsx b/app/src/components/ui/collaboration/threads/Messages.tsx index 7f48114..11af2a8 100644 --- a/app/src/components/ui/collaboration/threads/Messages.tsx +++ b/app/src/components/ui/collaboration/threads/Messages.tsx @@ -34,7 +34,7 @@ const Messages: React.FC = ({ val, i, setMessages, mode, setIsEdit const [isEditComment, setIsEditComment] = useState(false); const { selectedComment, setCommentPositionState } = useSelectedComment(); const { versionStore, threadStore } = useSceneContext(); - const { updateComment, removeReply, updateReply } = threadStore(); + const { updateThread, removeReply, updateReply } = threadStore(); const { selectedVersion } = versionStore(); const [value, setValue] = useState("comment" in val ? val.comment : val.threadTitle); @@ -71,7 +71,7 @@ const Messages: React.FC = ({ val, i, setMessages, mode, setIsEdit rotation: [0, 0, 0], comments: [], }; - updateComment((val as ThreadSchema).threadId, editedThread); + updateThread((val as ThreadSchema).threadId, editedThread); } }); } else { diff --git a/app/src/components/ui/collaboration/threads/ThreadChat.tsx b/app/src/components/ui/collaboration/threads/ThreadChat.tsx index 1cfc08f..a7e8c50 100644 --- a/app/src/components/ui/collaboration/threads/ThreadChat.tsx +++ b/app/src/components/ui/collaboration/threads/ThreadChat.tsx @@ -36,7 +36,7 @@ const ThreadChat: React.FC = () => { const messagesRef = useRef(null); const { versionStore, threadStore } = useSceneContext(); const { selectedVersion } = versionStore(); - const { addComment, removeComment, addReply, threads } = threadStore(); + const { addThread, removeThread, addReply, threads } = threadStore(); useEffect(() => { modeRef.current = mode; @@ -157,7 +157,7 @@ const ThreadChat: React.FC = () => { } else if (threadSocket?.connected && mode === "create") { // SOCKET - const addComment = { + const addThread = { versionId: selectedVersion?.versionId || "", projectId, userId, @@ -166,7 +166,7 @@ const ThreadChat: React.FC = () => { threadId: selectedComment?.threadId, }; - threadSocket.emit("v1-Comment:add", addComment); + threadSocket.emit("v1-Comment:add", addThread); } setInputActive(false); }; @@ -179,7 +179,7 @@ const ThreadChat: React.FC = () => { deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "").then((deleteThread) => { if (deleteThread.message === "Thread deleted Successfully") { - removeComment(selectedComment?.threadId); + removeThread(selectedComment?.threadId); setSelectedComment(null); } }); @@ -194,7 +194,7 @@ const ThreadChat: React.FC = () => { versionId: selectedVersion?.versionId || "", }; setSelectedComment(null); - removeComment(selectedComment?.threadId); + removeThread(selectedComment?.threadId); threadSocket.emit("v1:thread:delete", deleteThread); } }; @@ -219,7 +219,7 @@ const ThreadChat: React.FC = () => { rotation: [0, 0, 0], comments: [], }; - addComment(comment); + addThread(comment); setCommentPositionState(null); setInputActive(false); setSelectedComment(null); diff --git a/app/src/components/ui/log/LoggerContext.tsx b/app/src/components/ui/log/LoggerContext.tsx index 37eaf6c..8f7a949 100644 --- a/app/src/components/ui/log/LoggerContext.tsx +++ b/app/src/components/ui/log/LoggerContext.tsx @@ -1,112 +1,89 @@ // LoggerProvider.tsx -import React, { - createContext, - useContext, - useState, - useCallback, - useMemo, - useEffect, -} from "react"; +import React, { createContext, useContext, useState, useCallback, useMemo, useEffect } from "react"; import { MathUtils } from "three"; export type LogType = "log" | "info" | "warning" | "error" | "success"; export interface LogEntry { - id: string; - type: LogType; - message: string; - timestamp: Date; + id: string; + type: LogType; + message: string; + timestamp: Date; } interface LoggerContextValue { - logs: LogEntry[]; - setLogs: React.Dispatch>; - isLogListVisible: boolean; - setIsLogListVisible: React.Dispatch>; - selectedTab: LogType | "all"; - setSelectedTab: React.Dispatch>; - log: (message: string) => void; - info: (message: string) => void; - warn: (message: string) => void; - error: (message: string) => void; - success: (message: string) => void; - clear: () => void; + logs: LogEntry[]; + setLogs: React.Dispatch>; + isLogListVisible: boolean; + setIsLogListVisible: React.Dispatch>; + selectedTab: LogType | "all"; + setSelectedTab: React.Dispatch>; + log: (message: string) => void; + info: (message: string) => void; + warn: (message: string) => void; + error: (message: string) => void; + success: (message: string) => void; + clear: () => void; } const LoggerContext = createContext(undefined); -export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({ - children, -}) => { - const [logs, setLogs] = useState([]); - const [isLogListVisible, setIsLogListVisible] = useState(false); - const [selectedTab, setSelectedTab] = useState("all"); +export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [logs, setLogs] = useState([]); + const [isLogListVisible, setIsLogListVisible] = useState(false); + const [selectedTab, setSelectedTab] = useState("all"); - const addLog = useCallback((type: LogType, message: string) => { - const newLog: LogEntry = { - id: MathUtils.generateUUID(), - type, - message, - timestamp: new Date(), - }; - setLogs((prevLogs) => [...prevLogs, newLog]); - }, []); + const addLog = useCallback((type: LogType, message: string) => { + const newLog: LogEntry = { + id: MathUtils.generateUUID(), + type, + message, + timestamp: new Date(), + }; + setLogs((prevLogs) => [...prevLogs, newLog]); + }, []); - const loggerMethods: LoggerContextValue = useMemo( - () => ({ - logs, - setLogs, - isLogListVisible, - setIsLogListVisible, - selectedTab, - setSelectedTab, - log: (message: string) => addLog("log", message), - info: (message: string) => addLog("info", message), - warn: (message: string) => addLog("warning", message), - error: (message: string) => addLog("error", message), - success: (message: string) => addLog("success", message), - clear: () => { - if (selectedTab !== "all") { - setLogs((prevLogs) => - prevLogs.filter((log) => log.type !== selectedTab) - ); - } else { - setLogs([]); - } - }, - }), - [ - logs, - setLogs, - isLogListVisible, - setIsLogListVisible, - selectedTab, - setSelectedTab, - addLog, - ] - ); - useEffect(() => { - (window as any).echo = { - log: loggerMethods.log, - info: loggerMethods.info, - warn: loggerMethods.warn, - error: loggerMethods.error, - success: loggerMethods.success, - clear: loggerMethods.clear, - }; - }, [loggerMethods]); + const loggerMethods: LoggerContextValue = useMemo( + () => ({ + logs, + setLogs, + isLogListVisible, + setIsLogListVisible, + selectedTab, + setSelectedTab, + log: (message: string) => addLog("log", message), + info: (message: string) => addLog("info", message), + warn: (message: string) => addLog("warning", message), + error: (message: string) => addLog("error", message), + success: (message: string) => addLog("success", message), + clear: () => { + if (selectedTab !== "all") { + setLogs((prevLogs) => prevLogs.filter((log) => log.type !== selectedTab)); + } else { + setLogs([]); + } + }, + }), + [logs, setLogs, isLogListVisible, setIsLogListVisible, selectedTab, setSelectedTab, addLog] + ); + useEffect(() => { + (window as any).echo = { + log: loggerMethods.log, + info: loggerMethods.info, + warn: loggerMethods.warn, + error: loggerMethods.error, + success: loggerMethods.success, + clear: loggerMethods.clear, + }; + }, [loggerMethods]); - return ( - - {children} - - ); + return {children}; }; export const useLogger = () => { - const context = useContext(LoggerContext); - if (!context) { - throw new Error("useLogger must be used within a LoggerProvider"); - } - return context; + const context = useContext(LoggerContext); + if (!context) { + throw new Error("useLogger must be used within a LoggerProvider"); + } + return context; }; diff --git a/app/src/global.d.ts b/app/src/global.d.ts index 804fae0..c6fe18c 100644 --- a/app/src/global.d.ts +++ b/app/src/global.d.ts @@ -2,12 +2,12 @@ import { LogType } from "../components/ui/log/LoggerContext"; declare global { - const echo: { - log: (message: string) => void; - info: (message: string) => void; - warn: (message: string) => void; - error: (message: string) => void; - success: (message: string) => void; - clear: () => void; - }; + const echo: { + log: (message: string) => void; + info: (message: string) => void; + warn: (message: string) => void; + error: (message: string) => void; + success: (message: string) => void; + clear: () => void; + }; } diff --git a/app/src/modules/collaboration/camera/collabCams.tsx b/app/src/modules/collaboration/camera/collabCams.tsx index 4356820..429d3a5 100644 --- a/app/src/modules/collaboration/camera/collabCams.tsx +++ b/app/src/modules/collaboration/camera/collabCams.tsx @@ -1,18 +1,8 @@ import * as THREE from "three"; -import { Html } from "@react-three/drei"; -import { useNavigate } from "react-router-dom"; -import { useEffect, useRef, useState } from "react"; -import { useFrame, useThree } from "@react-three/fiber"; -import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; -import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; -import { useActiveUsers, useCamMode } from "../../../store/builder/store"; -import { useSocketStore } from "../../../store/socket/useSocketStore"; -import useModuleStore from "../../../store/ui/useModuleStore"; -import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore"; -import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; - -import setCameraView from "../functions/setCameraView"; -import getActiveUsersData from "../../../services/factoryBuilder/collab/users/getActiveUsers"; +import { Html, useGLTF } from "@react-three/drei"; +import { useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { useSceneContext } from "../../scene/sceneContext"; import { getUserData } from "../../../functions/getUserData"; import { getAvatarColor } from "../functions/getAvatarColor"; @@ -20,222 +10,65 @@ import CollabUserIcon from "./collabUserIcon"; import camModel from "../../../assets/gltf-glb/camera face 2.gltf"; const CamModelsGroup = () => { - const navigate = useNavigate(); - const groupRef = useRef(null); - const { organization, email } = getUserData(); - const { setActiveUsers } = useActiveUsers(); - const { builderSocket } = useSocketStore(); - const { activeModule } = useModuleStore(); - const { selectedUser, setSelectedUser } = useSelectedUserStore(); - const { isPlaying } = usePlayButtonStore(); - // eslint-disable-next-line react-hooks/exhaustive-deps - const loader = new GLTFLoader(); - const dracoLoader = new DRACOLoader(); - dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); - loader.setDRACOLoader(dracoLoader); - - const { camMode } = useCamMode(); - const { camera, controls } = useThree(); - - useEffect(() => { - if (camMode !== "FollowPerson") return; - if (selectedUser?.location) { - const { position, rotation, target } = selectedUser.location; - if (rotation && target) - setCameraView({ - controls, - camera, - position, - rotation, - target, - username: selectedUser.name, - }); - } - }, [selectedUser, camera, controls, camMode]); - - const [cams, setCams] = useState([]); - const [models, setModels] = useState< - Record< - string, - { - targetPosition: THREE.Vector3; - targetRotation: THREE.Euler; - target: THREE.Vector3; - } - > - >({}); - - const dedupeCams = (cams: any[]) => { - const seen = new Set(); - return cams.filter((cam) => { - if (seen.has(cam.uuid)) return false; - seen.add(cam.uuid); - return true; - }); - }; - - const dedupeUsers = (users: any[]) => { - const seen = new Set(); - return users.filter((user) => { - if (seen.has(user._id)) return false; - seen.add(user._id); - return true; - }); - }; - - useEffect(() => { - if (!email) navigate("/"); - - if (!builderSocket) return; - - builderSocket.on("userConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (data.data.userData.email === email) return; - if (builderSocket.id === data.socketId || organization !== data.organization) return; - - const model = groupRef.current.getObjectByProperty("uuid", data.data.userData._id); - if (model) { - groupRef.current.remove(model); - } - - loader.load(camModel, (gltf) => { - const newModel = gltf.scene.clone(); - newModel.uuid = data.data.userData._id; - newModel.position.set(data.data.position.x, data.data.position.y, data.data.position.z); - newModel.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); - newModel.userData = data.data.userData; - newModel.userData.target = new THREE.Vector3(data.data.target.x, data.data.target.y, data.data.target.z); - - setCams((prev) => dedupeCams([...prev, newModel])); - setActiveUsers((prev: any) => dedupeUsers([...prev, data.data.userData])); - }); - }); - - builderSocket.on("userDisConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (builderSocket.id === data.socketId || organization !== data.organization) return; - - setCams((prev) => prev.filter((cam) => cam.uuid !== data.data.userData._id)); - setActiveUsers((prev: any) => prev.filter((user: any) => user._id !== data.data.userData._id)); - }); - - builderSocket.on("v1:camera:Response:update", (data: any) => { - if (!groupRef.current || builderSocket.id === data.socketId || organization !== data.organization) return; - - if (selectedUser && selectedUser?.id === data.data.userId) { - setSelectedUser({ - color: selectedUser.color, - name: selectedUser.name, - id: selectedUser.id, - location: { - position: data.data.position, - rotation: data.data.rotation, - target: data.data.target, - }, - }); - } - setModels((prev) => ({ - ...prev, - [data.data.userId]: { - targetPosition: new THREE.Vector3(data.data.position.x, data.data.position.y, data.data.position.z), - targetRotation: new THREE.Euler(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z), - target: new THREE.Vector3(data.data.target.x, data.data.target.y, data.data.target.z), - }, - })); - }); - - return () => { - builderSocket.off("userConnectResponse"); - builderSocket.off("userDisConnectResponse"); - builderSocket.off("v1:camera:Response:update"); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [email, loader, navigate, setActiveUsers, builderSocket]); - - useFrame(() => { - if (!groupRef.current) return; - Object.keys(models).forEach((uuid) => { - const model = groupRef.current!.getObjectByProperty("uuid", uuid); - if (!model) return; - - const { targetPosition, targetRotation } = models[uuid]; - model.position.lerp(targetPosition, 0.1); - model.rotation.x = THREE.MathUtils.lerp(model.rotation.x, targetRotation.x, 0.1); - model.rotation.y = THREE.MathUtils.lerp(model.rotation.y, targetRotation.y, 0.1); - model.rotation.z = THREE.MathUtils.lerp(model.rotation.z, targetRotation.z, 0.1); - }); - }); - - useEffect(() => { - if (!groupRef.current) return; - - getActiveUsersData(organization) - .then((data) => { - const filteredData = data.cameraDatas.filter((camera: any) => camera.userData.email !== email); - - if (filteredData.length > 0) { - loader.load(camModel, (gltf) => { - const newCams = filteredData.map((cam: any) => { - const newModel = gltf.scene.clone(); - newModel.uuid = cam.userData._id; - newModel.position.set(cam.position.x, cam.position.y, cam.position.z); - newModel.rotation.set(cam.rotation.x, cam.rotation.y, cam.rotation.z); - newModel.userData = cam.userData; - cam.userData.position = newModel.position; - cam.userData.rotation = newModel.rotation; - newModel.userData.target = cam.target; - - return newModel; - }); - - const users = filteredData.map((cam: any) => cam.userData); - setActiveUsers((prev: any) => dedupeUsers([...prev, ...users])); - setCams((prev) => dedupeCams([...prev, ...newCams])); - }); - } - }) - .catch(() => { - console.log("Error fetching active users data"); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { collabUsersStore } = useSceneContext(); + const { collabUsers } = collabUsersStore(); + const model: any = useGLTF(camModel); + const { userId: currentUserId } = getUserData(); return ( - - {cams.map((cam, index) => ( - - - - - - ))} + + {collabUsers + .filter((user) => user.userId !== currentUserId) + .map((user, index) => ( + + ))} + + ); +}; + +const CamModel = ({ user, model, index }: { user: CollabUsersScheme; model: THREE.Object3D; index: number }) => { + const ref = useRef(null); + + const targetPos = new THREE.Vector3(); + const targetQuat = new THREE.Quaternion(); + + useFrame((_, delta) => { + if (ref.current) { + targetPos.set(user.camData.position.x, user.camData.position.y, user.camData.position.z); + + targetQuat.setFromEuler(new THREE.Euler(user.camData.rotation.x, user.camData.rotation.y, user.camData.rotation.z)); + + ref.current.position.lerp(targetPos, 5 * delta); + ref.current.quaternion.slerp(targetQuat, 5 * delta); + } + }); + + return ( + + + + + ); }; diff --git a/app/src/modules/collaboration/socket/threadSocketResponses.dev.tsx b/app/src/modules/collaboration/socket/threadSocketResponses.dev.tsx index d4fb12f..abdb38f 100644 --- a/app/src/modules/collaboration/socket/threadSocketResponses.dev.tsx +++ b/app/src/modules/collaboration/socket/threadSocketResponses.dev.tsx @@ -16,7 +16,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock const { threadSocket } = useSocketStore(); const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState } = useSelectedComment(); const { threadStore } = useSceneContext(); - const { threads, removeReply, addComment, addReply, updateComment, updateReply } = threadStore(); + const { threads, removeReply, addThread, addReply, updateThread, updateReply } = threadStore(); const { userId } = getUserData(); useEffect(() => { @@ -98,7 +98,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock comments: [], }; setSelectedComment(comment); - addComment(comment); + addThread(comment); setCommentPositionState(null); // setSelectedComment(null); }; @@ -128,7 +128,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock }; // - updateComment(data.data?._id, editedThread); + updateThread(data.data?._id, editedThread); setSelectedComment(editedThread); // setSelectedComment(null); diff --git a/app/src/modules/collaboration/socket/userResponses.tsx b/app/src/modules/collaboration/socket/userResponses.tsx index 1135903..cd0b24b 100644 --- a/app/src/modules/collaboration/socket/userResponses.tsx +++ b/app/src/modules/collaboration/socket/userResponses.tsx @@ -1,8 +1,29 @@ import { useEffect } from "react"; import { useSocketStore } from "../../../store/socket/useSocketStore"; +import { useSceneContext } from "../../scene/sceneContext"; function UserResponses() { const { builderSocket } = useSocketStore(); + const { collabUsersStore } = useSceneContext(); + const { setCollabUsers } = collabUsersStore(); + + //#region Users + useEffect(() => { + if (!builderSocket) return; + + builderSocket.on("v1:room:userDatas", (data: any) => { + if (!data?.data?.UsersDetails || data.data.UsersDetails.length === 0) return; + console.log("data: ", data.data.UsersDetails); + setCollabUsers(data.data.UsersDetails); + }); + + return () => { + if (builderSocket) { + builderSocket.off("v1:room:userDatas"); + } + }; + }, [builderSocket]); + //#endregion //#region Camera useEffect(() => { @@ -10,9 +31,6 @@ function UserResponses() { builderSocket.on("v1:camera:Response:update", (data: any) => { if (!data.message) return; - if (data.message === "Model created successfully") { - } else if (data.message === "Updated successfully") { - } }); return () => { diff --git a/app/src/modules/collaboration/threads/threadInstances/threadInstances.tsx b/app/src/modules/collaboration/threads/threadInstances/threadInstances.tsx index 92771dc..131c098 100644 --- a/app/src/modules/collaboration/threads/threadInstances/threadInstances.tsx +++ b/app/src/modules/collaboration/threads/threadInstances/threadInstances.tsx @@ -10,7 +10,7 @@ function ThreadInstances() { const { projectId } = useParams(); const { userId } = getUserData(); const { versionStore, threadStore } = useSceneContext(); - const { threads, setComments } = threadStore(); + const { threads, setThreads } = threadStore(); const { selectedVersion } = versionStore(); useEffect(() => { @@ -36,7 +36,7 @@ function ThreadInstances() { : [], })) : []; - setComments(formattedThreads); + setThreads(formattedThreads); }) .catch((err) => { console.error("Failed to fetch threads:", err); diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index c1c52cb..7389bd6 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -82,7 +82,7 @@ export default function Controls() { setResetCamera(false); } - }, [resetCamera]); + }, [resetCamera, builderSocket, projectId]); useEffect(() => { controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop))); diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index dd2b6fe..34d1ca2 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -24,7 +24,9 @@ import { createStorageUnitStore, StorageUnitStoreType } from "../../store/simula import { createHumanStore, HumanStoreType } from "../../store/simulation/useHumanStore"; import { createCraneStore, CraneStoreType } from "../../store/simulation/useCraneStore"; -import { createThreadsStore, ThreadStoreType } from "../../store/collaboration/useCommentStore"; +import { createThreadsStore, ThreadStoreType } from "../../store/collaboration/useThreadStore"; + +import { createCollabusersStore, CollabUsersStoreType } from "../../store/collaboration/useCollabUsersStore"; type SceneContextValue = { versionStore: VersionStoreType; @@ -53,6 +55,8 @@ type SceneContextValue = { threadStore: ThreadStoreType; + collabUsersStore: CollabUsersStoreType; + humanEventManagerRef: React.RefObject; craneEventManagerRef: React.RefObject; @@ -90,6 +94,8 @@ export function SceneProvider({ children, layout }: { readonly children: React.R const threadStore = useMemo(() => createThreadsStore(), []); + const collabUsersStore = useMemo(() => createCollabusersStore(), []); + const humanEventManagerRef = useRef({ humanStates: [] }); const craneEventManagerRef = useRef({ craneStates: [] }); @@ -114,7 +120,8 @@ export function SceneProvider({ children, layout }: { readonly children: React.R storageUnitStore.getState().clearStorageUnits(); humanStore.getState().clearHumans(); craneStore.getState().clearCranes(); - threadStore.getState().clearComments(); + threadStore.getState().clearThreads(); + collabUsersStore.getState().clearCollabUsers(); humanEventManagerRef.current.humanStates = []; craneEventManagerRef.current.craneStates = []; }, @@ -139,6 +146,7 @@ export function SceneProvider({ children, layout }: { readonly children: React.R humanStore, craneStore, threadStore, + collabUsersStore, ] ); @@ -164,6 +172,7 @@ export function SceneProvider({ children, layout }: { readonly children: React.R humanStore, craneStore, threadStore, + collabUsersStore, humanEventManagerRef, craneEventManagerRef, clearStores, @@ -190,6 +199,7 @@ export function SceneProvider({ children, layout }: { readonly children: React.R humanStore, craneStore, threadStore, + collabUsersStore, clearStores, layout, ] diff --git a/app/src/pages/Dashboard.tsx b/app/src/pages/Dashboard.tsx index f5c6a5f..bf587f5 100644 --- a/app/src/pages/Dashboard.tsx +++ b/app/src/pages/Dashboard.tsx @@ -13,7 +13,7 @@ const Dashboard: React.FC = () => { const token = localStorage.getItem("token"); const refreshToken = localStorage.getItem("refreshToken"); if (token && refreshToken) { - useSocketStore.getState().initializeDashBoardSocket(token, refreshToken); + useSocketStore.getState().initializeProjectSocket(token, refreshToken); } }, [email, organization]); diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 0126105..a1550ee 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -24,7 +24,17 @@ const Project: React.FC = () => { let navigate = useNavigate(); const echo = useLogger(); const { setActiveModule } = useModuleStore(); - const { initializeBuilderSocket, initializeVisualizationSocket, initializeThreadSocket, disconnectBuilderSocket, disconnectVisualizationSocket, disconnectThreadSocket } = useSocketStore(); + const { + builderSocket, + visualizationSocket, + threadSocket, + initializeBuilderSocket, + initializeVisualizationSocket, + initializeThreadSocket, + disconnectBuilderSocket, + disconnectVisualizationSocket, + disconnectThreadSocket, + } = useSocketStore(); const { projectId } = useParams(); const { setProjectName } = useProjectName(); const { userId, email } = getUserData(); @@ -33,6 +43,7 @@ const Project: React.FC = () => { const { activeTool } = useActiveTool(); useEffect(() => { + setActiveModule("builder"); if (!email || !userId) { console.error("User data not found in localStorage"); navigate("/page-not-found"); @@ -64,30 +75,65 @@ const Project: React.FC = () => { }, [projectId]); useEffect(() => { - setActiveModule("builder"); - if (email) { - const token = localStorage.getItem("token"); - const refreshToken = localStorage.getItem("refreshToken"); + const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + + if (token && refreshToken && projectId) { echo.warn("Validating token"); - if (token && refreshToken && projectId) { - initializeBuilderSocket(token, refreshToken, projectId); - initializeVisualizationSocket(token, refreshToken, projectId); - initializeThreadSocket(token, refreshToken, projectId); - } - echo.success("Project initialized and loaded successfully"); + initializeBuilderSocket(token, refreshToken, projectId); + echo.success("Builder socket initialized"); } else { navigate("/"); + return; } - // eslint-disable-next-line react-hooks/exhaustive-deps return () => { - if (projectId) { + if (projectId && builderSocket) { disconnectBuilderSocket(projectId); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projectId, builderSocket]); + + useEffect(() => { + const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + + if (token && refreshToken && projectId) { + initializeVisualizationSocket(token, refreshToken, projectId); + echo.success("Visualization socket initialized"); + } else { + navigate("/"); + return; + } + + return () => { + if (projectId && visualizationSocket) { disconnectVisualizationSocket(projectId); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projectId, visualizationSocket]); + + useEffect(() => { + const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + + if (token && refreshToken && projectId) { + initializeThreadSocket(token, refreshToken, projectId); + echo.success("Thread socket initialized"); + } else { + navigate("/"); + return; + } + + return () => { + if (projectId && threadSocket) { disconnectThreadSocket(projectId); } }; - }, [projectId]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projectId, threadSocket]); useEffect(() => { handleCanvasCursors(activeTool); diff --git a/app/src/services/factoryBuilder/collab/comments/addCommentApi.ts b/app/src/services/factoryBuilder/collab/comments/addCommentApi.ts index b44506c..db3e2ef 100644 --- a/app/src/services/factoryBuilder/collab/comments/addCommentApi.ts +++ b/app/src/services/factoryBuilder/collab/comments/addCommentApi.ts @@ -2,7 +2,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR export const addCommentsApi = async (projectId: any, comment: string, threadId: string, versionId: string) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addComment`, { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addThread`, { method: "POST", headers: { Authorization: "Bearer ", diff --git a/app/src/services/factoryBuilder/collab/comments/editCommentApi.ts b/app/src/services/factoryBuilder/collab/comments/editCommentApi.ts index 3166356..f3c2527 100644 --- a/app/src/services/factoryBuilder/collab/comments/editCommentApi.ts +++ b/app/src/services/factoryBuilder/collab/comments/editCommentApi.ts @@ -2,7 +2,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR export const editCommentsApi = async (projectId: any, comment: string, commentId: string, threadId: string, versionId: string) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addComment`, { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addThread`, { method: "POST", headers: { Authorization: "Bearer ", diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 7030d51..e051768 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -145,14 +145,6 @@ export const useDrieTemp = create((set: any) => ({ setDrieTemp: (x: any) => set({ drieTemp: x }), })); -export const useActiveUsers = create((set: any) => ({ - activeUsers: [], - setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => - set((state: { activeUsers: any[] }) => ({ - activeUsers: typeof callback === "function" ? callback(state.activeUsers) : callback, - })), -})); - export const useDrieUIValue = create((set: any) => ({ drieUIValue: { touch: null, temperature: null, humidity: null }, diff --git a/app/src/store/collaboration/useCollabUsersStore.ts b/app/src/store/collaboration/useCollabUsersStore.ts new file mode 100644 index 0000000..987dd83 --- /dev/null +++ b/app/src/store/collaboration/useCollabUsersStore.ts @@ -0,0 +1,50 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +interface CollabUsersStore { + collabUsers: CollabUsersScheme[]; + + addCollabUser: (user: CollabUsersScheme) => void; + updateCollabUser: (userId: string, updates: Partial) => void; + setCollabUsers: (users: CollabUsersScheme[]) => void; + removeCollabUser: (userId: string) => void; + clearCollabUsers: () => void; +} + +export const createCollabusersStore = () => { + return create()( + immer((set) => ({ + collabUsers: [], + + addCollabUser: (user) => + set((state) => { + state.collabUsers.push(user); + }), + + updateCollabUser: (userId, updates) => + set((state) => { + const idx = state.collabUsers.findIndex((u) => u.userId === userId); + if (idx !== -1) { + Object.assign(state.collabUsers[idx], updates); + } + }), + + setCollabUsers: (users) => + set((state) => { + state.collabUsers = users; + }), + + removeCollabUser: (userId) => + set((state) => { + state.collabUsers = state.collabUsers.filter((u) => u.userId !== userId); + }), + + clearCollabUsers: () => + set((state) => { + state.collabUsers = []; + }), + })) + ); +}; + +export type CollabUsersStoreType = ReturnType; diff --git a/app/src/store/collaboration/useCommentStore.ts b/app/src/store/collaboration/useThreadStore.ts similarity index 82% rename from app/src/store/collaboration/useCommentStore.ts rename to app/src/store/collaboration/useThreadStore.ts index a8c068a..ed29e2f 100644 --- a/app/src/store/collaboration/useCommentStore.ts +++ b/app/src/store/collaboration/useThreadStore.ts @@ -4,17 +4,17 @@ import { immer } from "zustand/middleware/immer"; interface ThreadStore { threads: ThreadsSchema; - addComment: (thread: ThreadSchema) => void; - setComments: (threads: ThreadsSchema) => void; - updateComment: (threadId: string, updates: Partial) => void; - removeComment: (threadId: string) => void; - clearComments: () => void; + addThread: (thread: ThreadSchema) => void; + setThreads: (threads: ThreadsSchema) => void; + updateThread: (threadId: string, updates: Partial) => void; + removeThread: (threadId: string) => void; + clearThreads: () => void; addReply: (threadId: string, reply: Reply) => void; updateReply: (threadId: string, replyId: string, updates: Partial) => void; removeReply: (threadId: string, _id: string) => void; - getCommentById: (threadId: string) => ThreadSchema | undefined; + getThreadById: (threadId: string) => ThreadSchema | undefined; } export const createThreadsStore = () => { @@ -22,7 +22,7 @@ export const createThreadsStore = () => { immer((set, get) => ({ threads: [], - addComment: (thread) => { + addThread: (thread) => { set((state) => { if (!state.threads.find((c) => c.threadId === thread.threadId)) { state.threads.push(JSON.parse(JSON.stringify(thread))); @@ -30,13 +30,13 @@ export const createThreadsStore = () => { }); }, - setComments: (threads) => { + setThreads: (threads) => { set((state) => { state.threads = threads; }); }, - updateComment: (threadId, updates) => { + updateThread: (threadId, updates) => { set((state) => { const thread = state.threads.find((c) => c.threadId === threadId); if (thread) { @@ -45,13 +45,13 @@ export const createThreadsStore = () => { }); }, - removeComment: (threadId) => { + removeThread: (threadId) => { set((state) => { state.threads = state.threads.filter((c) => c.threadId !== threadId); }); }, - clearComments: () => { + clearThreads: () => { set((state) => { state.threads = []; }); @@ -87,7 +87,7 @@ export const createThreadsStore = () => { }); }, - getCommentById: (threadId) => { + getThreadById: (threadId) => { return get().threads.find((c) => c.threadId === threadId); }, })) diff --git a/app/src/store/socket/useSocketStore.ts b/app/src/store/socket/useSocketStore.ts index 53dd34b..333f252 100644 --- a/app/src/store/socket/useSocketStore.ts +++ b/app/src/store/socket/useSocketStore.ts @@ -4,98 +4,104 @@ import { io, Socket } from "socket.io-client"; interface SocketStore { builderSocket: Socket | null; visualizationSocket: Socket | null; - dashBoardSocket: Socket | null; projectSocket: Socket | null; threadSocket: Socket | null; initializeBuilderSocket: (token: string, refreshToken: string, projectId: string) => void; initializeVisualizationSocket: (token: string, refreshToken: string, projectId: string) => void; initializeThreadSocket: (token: string, refreshToken: string, projectId: string) => void; - initializeDashBoardSocket: (token: string, refreshToken: string) => void; initializeProjectSocket: (token: string, refreshToken: string) => void; disconnectBuilderSocket: (projectId: string) => void; disconnectVisualizationSocket: (projectId: string) => void; disconnectThreadSocket: (projectId: string) => void; - disconnectDashBoardSocket: () => void; disconnectProjectSocket: () => void; disconnectAll: (projectId?: string) => void; } +const socketOptions = (auth: any) => ({ + reconnection: true, + reconnectionAttempts: Infinity, + reconnectionDelay: 2000, + timeout: 10000, + auth, +}); + +const attachLogs = (name: string, socket: Socket) => { + socket.on("connect", () => { + // console.log(`${name} socket connected:`, socket.id); + }); + + socket.on("disconnect", (reason) => { + // console.warn(`${name} socket disconnected:`, reason); + }); + + socket.on("error", (err) => { + // console.error(`${name} socket error:`, err); + }); + + socket.on("connect_error", (err) => { + console.warn(`${name} socket connection failed:`, err.message); + }); +}; + export const useSocketStore = create((set, get) => ({ builderSocket: null, visualizationSocket: null, - dashBoardSocket: null, projectSocket: null, threadSocket: null, initializeBuilderSocket: (token, refreshToken, projectId) => { if (get().builderSocket) return; - const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, { - reconnection: true, - auth: { token, refreshToken, projectId }, - }); + const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, socketOptions({ token, refreshToken, projectId })); + attachLogs("Builder", builderSocket); set({ builderSocket }); }, initializeVisualizationSocket: (token, refreshToken, projectId) => { if (get().visualizationSocket) return; - const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, { - reconnection: true, - auth: { token, refreshToken, projectId }, - }); + const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, socketOptions({ token, refreshToken, projectId })); + attachLogs("Visualization", visualizationSocket); set({ visualizationSocket }); }, initializeThreadSocket: (token, refreshToken, projectId) => { if (get().threadSocket) return; - const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, { - reconnection: true, - auth: { token, refreshToken, projectId }, - }); + const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId })); + attachLogs("Thread", threadSocket); set({ threadSocket }); }, - initializeDashBoardSocket: (token, refreshToken) => { - if (get().dashBoardSocket) return; - const dashBoardSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/dashboard`, { - reconnection: true, - auth: { token, refreshToken }, - }); - set({ dashBoardSocket }); - }, - initializeProjectSocket: (token, refreshToken) => { if (get().projectSocket) return; - const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, { - reconnection: true, - auth: { token, refreshToken }, - }); + const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, socketOptions({ token, refreshToken })); + attachLogs("Project", projectSocket); set({ projectSocket }); }, disconnectBuilderSocket: (projectId) => { get().builderSocket?.emit("leaveRoom", { projectId }); - get().builderSocket?.disconnect(); - set({ builderSocket: null }); + setTimeout(() => { + get().builderSocket?.disconnect(); + set({ builderSocket: null }); + }, 1000); }, disconnectVisualizationSocket: (projectId) => { get().visualizationSocket?.emit("leaveRoom", { projectId }); - get().visualizationSocket?.disconnect(); - set({ visualizationSocket: null }); + setTimeout(() => { + get().visualizationSocket?.disconnect(); + set({ visualizationSocket: null }); + }, 1000); }, disconnectThreadSocket: (projectId) => { get().threadSocket?.emit("leaveRoom", { projectId }); - get().threadSocket?.disconnect(); - set({ threadSocket: null }); - }, - - disconnectDashBoardSocket: () => { - get().dashBoardSocket?.disconnect(); - set({ dashBoardSocket: null }); + setTimeout(() => { + get().threadSocket?.disconnect(); + set({ threadSocket: null }); + }, 1000); }, disconnectProjectSocket: () => { @@ -108,16 +114,16 @@ export const useSocketStore = create((set, get) => ({ get().visualizationSocket?.emit("leaveRoom", { projectId }); get().threadSocket?.emit("leaveRoom", { projectId }); - get().builderSocket?.disconnect(); - get().visualizationSocket?.disconnect(); - get().dashBoardSocket?.disconnect(); - get().projectSocket?.disconnect(); - get().threadSocket?.disconnect(); + setTimeout(() => { + get().builderSocket?.disconnect(); + get().visualizationSocket?.disconnect(); + get().projectSocket?.disconnect(); + get().threadSocket?.disconnect(); + }, 1000); set({ builderSocket: null, visualizationSocket: null, - dashBoardSocket: null, projectSocket: null, threadSocket: null, }); diff --git a/app/src/types/collaborationTypes.d.ts b/app/src/types/collaborationTypes.d.ts index 8de989d..10315bc 100644 --- a/app/src/types/collaborationTypes.d.ts +++ b/app/src/types/collaborationTypes.d.ts @@ -1,3 +1,18 @@ +//#region CollllabUsersData +interface CollabUsersScheme { + userId: string; + userName: string; + projectId: string; + versionId: string; + camData: { + position: { x: number; y: number; z: number }; + rotation: { x: number; y: number; z: number }; + target: { x: number; y: number; z: number }; + }; +} +//#endregion + +//#region Thread interface ThreadSchema { state: "active" | "inactive"; threadId: string; @@ -19,3 +34,4 @@ interface Reply { } type ThreadsSchema = ThreadSchema[]; +//#endregion