added collabcam socket response

This commit is contained in:
2025-09-16 16:44:01 +05:30
parent efc46eaa6d
commit a517905dad
24 changed files with 582 additions and 677 deletions

View File

@@ -1,14 +1,16 @@
import React, { useEffect } from "react";
import { Cache } from "three"; import { Cache } from "three";
import React, { useEffect } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 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 Dashboard from "./pages/Dashboard";
import Project from "./pages/Project"; 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 PageNotFound from "./pages/PageNotFound";
import "./styles/main.scss";
const App: React.FC = () => { const App: React.FC = () => {
useEffect(() => { useEffect(() => {
Cache.clear(); Cache.clear();

View File

@@ -65,7 +65,9 @@ function MainScene() {
useEffect(() => { useEffect(() => {
if (builderSocket && projectId) { if (builderSocket && projectId) {
builderSocket.emit("joinRoom", { projectId: projectId }); setTimeout(() => {
builderSocket.emit("joinRoom", { projectId: projectId });
}, 1000);
} }
}, [builderSocket, projectId]); }, [builderSocket, projectId]);

View File

@@ -1,7 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import orgImg from "../../../assets/image/orgTemp.png"; import orgImg from "../../../assets/image/orgTemp.png";
import { useActiveUsers, useCamMode } from "../../../store/builder/store"; import { useCamMode } from "../../../store/builder/store";
import { ActiveUser } from "../../../types/users";
import CollaborationPopup from "../../templates/CollaborationPopup"; import CollaborationPopup from "../../templates/CollaborationPopup";
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor"; import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore"; import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore";
@@ -9,120 +8,110 @@ import { useToggleStore } from "../../../store/ui/useUIToggleStore";
import { ToggleSidebarIcon } from "../../icons/HeaderIcons"; import { ToggleSidebarIcon } from "../../icons/HeaderIcons";
import useModuleStore from "../../../store/ui/useModuleStore"; import useModuleStore from "../../../store/ui/useModuleStore";
import { getUserData } from "../../../functions/getUserData"; import { getUserData } from "../../../functions/getUserData";
import { useSceneContext } from "../../../modules/scene/sceneContext";
const Header: React.FC = () => { const Header: React.FC = () => {
const { activeUsers } = useActiveUsers(); const { collabUsersStore } = useSceneContext();
const { userName } = getUserData(); const { collabUsers } = collabUsersStore();
const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore(); const { userId, userName } = getUserData();
const { activeModule } = useModuleStore(); const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore();
const { activeModule } = useModuleStore();
const guestUsers: ActiveUser[] = activeUsers.filter( const guestUsers = collabUsers.filter((user) => user.userId !== userId);
(user: ActiveUser) => user.userName !== userName
);
const [userManagement, setUserManagement] = useState(false); const [userManagement, setUserManagement] = useState(false);
const { setSelectedUser } = useSelectedUserStore(); const { setSelectedUser } = useSelectedUserStore();
const { setCamMode } = useCamMode(); const { setCamMode } = useCamMode();
function handleUserFollow(user: any, index: number) { function handleUserFollow(user: any, index: number) {
const position = { const position = {
x: user.position?.x!, x: user.position?.x!,
y: user.position?.y!, y: user.position?.y!,
z: user.position?.z!, z: user.position?.z!,
}; };
const target = { const target = {
x: user.target?.x!, x: user.target?.x!,
y: user.target?.y!, y: user.target?.y!,
z: user.target?.z!, z: user.target?.z!,
}; };
const rotation = { const rotation = {
x: user.rotation?.x!, x: user.rotation?.x!,
y: user.rotation?.y!, y: user.rotation?.y!,
z: user.rotation?.z!, z: user.rotation?.z!,
}; };
// retun on no data // retun on no data
if (!position || !target || !rotation) return; if (!position || !target || !rotation) return;
// Set the selected user in the store // Set the selected user in the store
setSelectedUser({ setSelectedUser({
color: getAvatarColor(index, user.userName), color: getAvatarColor(index, user.userName),
name: user.userName, name: user.userName,
id: user.id, id: user.id,
location: { position, rotation, target }, location: { position, rotation, target },
}); });
setCamMode("FollowPerson"); setCamMode("FollowPerson");
} }
return ( return (
<> <>
{userManagement && ( {userManagement && <CollaborationPopup setUserManagement={setUserManagement} />}
<CollaborationPopup setUserManagement={setUserManagement} /> <div className="header-container">
)} <div className="options-container">
<div className="header-container"> <button
<div className="options-container"> id="toggle-rightSidebar-ui-button"
<button className={`toggle-sidebar-ui-button ${!toggleUIRight ? "active" : ""}`}
id="toggle-rightSidebar-ui-button" onClick={() => {
className={`toggle-sidebar-ui-button ${!toggleUIRight ? "active" : "" if (activeModule !== "market") {
}`} setToggleUI(toggleUILeft, !toggleUIRight);
onClick={() => { localStorage.setItem("navBarUiRight", JSON.stringify(!toggleUIRight));
if (activeModule !== "market") { }
setToggleUI(toggleUILeft, !toggleUIRight); }}
localStorage.setItem( >
"navBarUiRight", <div className="tooltip">{toggleUIRight ? "Hide" : "Show"} sidebar (ctrl + ])</div>
JSON.stringify(!toggleUIRight) <ToggleSidebarIcon />
); </button>
} <button
}} id="share-button"
> className="share-button"
<div className="tooltip"> onClick={() => {
{toggleUIRight ? "Hide" : "Show"} sidebar (ctrl + ]) setUserManagement(true);
</div> }}
<ToggleSidebarIcon /> >
</button> Share
<button </button>
id="share-button" {/* <div className="app-docker-button">
className="share-button"
onClick={() => {
setUserManagement(true);
}}
>
Share
</button>
{/* <div className="app-docker-button">
<AppDockIcon /> <AppDockIcon />
</div> */} </div> */}
</div> </div>
<div className="split"></div> <div className="split"></div>
<div className="users-container"> <div className="users-container">
<div className="guest-users-container"> <div className="guest-users-container">
{guestUsers.length > 3 && ( {guestUsers.length > 3 && <div className="other-guest">+{guestUsers.length - 3}</div>}
<div className="other-guest">+{guestUsers.length - 3}</div> {guestUsers.slice(0, 3).map((user, index) => (
)} <button
{guestUsers.slice(0, 3).map((user, index) => ( id="user-profile-button"
<button key={`${index}-${user.userName}`}
id="user-profile-button" className="user-profile"
key={`${index}-${user.userName}`} style={{ background: getAvatarColor(index, user.userName) }}
className="user-profile" onClick={() => {
style={{ background: getAvatarColor(index, user.userName) }} handleUserFollow(user, index);
onClick={() => { }}
handleUserFollow(user, index); >
}} {user.userName[0]}
> </button>
{user.userName[0]} ))}
</button> </div>
))} <div className="user-profile-container">
</div> <div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
<div className="user-profile-container"> <div className="user-organization">
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div> <img src={orgImg} alt="" />
<div className="user-organization"> </div>
<img src={orgImg} alt="" /> </div>
</div>
</div> </div>
</div> </>
</div> );
</div>
</>
);
}; };
export default Header; export default Header;

View File

@@ -1,11 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import RenderOverlay from "./Overlay"; import RenderOverlay from "./Overlay";
import { ArrowIcon, CloseIcon } from "../icons/ExportCommonIcons"; import { CloseIcon } from "../icons/ExportCommonIcons";
import { AccessOption, User } from "../../types/users"; import { AccessOption, User } from "../../types/users";
import RegularDropDown from "../ui/inputs/RegularDropDown"; import RegularDropDown from "../ui/inputs/RegularDropDown";
import { access } from "fs";
import MultiEmailInvite from "../ui/inputs/MultiEmailInvite"; import MultiEmailInvite from "../ui/inputs/MultiEmailInvite";
import { useActiveUsers } from "../../store/builder/store";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import { getProjectSharedList } from "../../services/factoryBuilder/collab/project/getProjectSharedList"; import { getProjectSharedList } from "../../services/factoryBuilder/collab/project/getProjectSharedList";
import { useParams } from "react-router-dom"; 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"; import { getAvatarColor } from "../../modules/collaboration/functions/getAvatarColor";
interface UserListTemplateProps { interface UserListTemplateProps {
user: User; user: User;
} }
const UserListTemplate: React.FC<UserListTemplateProps> = ({ user }) => { const UserListTemplate: React.FC<UserListTemplateProps> = ({ user }) => {
const [accessSelection, setAccessSelection] = useState<string>(user?.Access); const [accessSelection, setAccessSelection] = useState<string>(user?.Access);
const { projectId } = useParams(); const { projectId } = useParams();
const accessUpdate = async ({ option }: AccessOption) => { const accessUpdate = async ({ option }: AccessOption) => {
if (!projectId) return if (!projectId) return;
const accessSelection = await shareAccess(projectId, user.userId, option) const accessSelection = await shareAccess(projectId, user.userId, option);
setAccessSelection(option); setAccessSelection(option);
} };
return ( return (
<div className="user-list-container"> <div className="user-list-container">
<div className="user-details"> <div className="user-details">
<div className="profile-image"> <div className="profile-image">
{user.profileImage ? ( {user.profileImage ? (
<img <img src={user.profileImage || "https://via.placeholder.com/150"} alt={`${user.userName}'s profile`} />
src={user.profileImage || "https://via.placeholder.com/150"} ) : (
alt={`${user. <div className="no-profile-container" style={{ background: getAvatarColor(1, user.userId) }}>
userName}'s profile`} {user.userName.charAt(0).toUpperCase()}
/> </div>
) : ( )}
<div </div>
className="no-profile-container" <div className="user-name"> {user.userName.charAt(0).toUpperCase() + user.userName.slice(1).toLowerCase()}</div>
style={{ background: getAvatarColor(1, user.userId) }} </div>
> <div className="user-access">
{user. <RegularDropDown header={accessSelection} options={["Admin", "User", "Viewer"]} onSelect={(option) => accessUpdate({ option })} search={false} />
userName.charAt(0).toUpperCase()} {/* <ArrowIcon /> */}
</div> </div>
)}
</div> </div>
<div className="user-name"> {user. );
userName.charAt(0).toUpperCase() + user.
userName.slice(1).toLowerCase()}</div>
</div>
<div className="user-access">
<RegularDropDown
header={accessSelection}
options={["Admin", "User", "Viewer"]}
onSelect={(option) => accessUpdate({ option })}
search={false}
/>
{/* <ArrowIcon /> */}
</div>
</div>
);
}; };
interface CollaborateProps { interface CollaborateProps {
setUserManagement: (value: boolean) => void; setUserManagement: (value: boolean) => void;
} }
const CollaborationPopup: React.FC<CollaborateProps> = ({ const CollaborationPopup: React.FC<CollaborateProps> = ({ setUserManagement }) => {
setUserManagement, const { userName } = getUserData();
}) => { const [users, setUsers] = useState([]);
const { activeUsers } = useActiveUsers(); const { projectId } = useParams();
const { userName } = getUserData();
const [users, setUsers] = useState([])
const { projectId } = useParams();
function getData() { function getData() {
if (!projectId) return; if (!projectId) return;
getProjectSharedList(projectId).then((allUser) => { getProjectSharedList(projectId)
const accesMail = allUser?.datas || [] .then((allUser) => {
setUsers(accesMail) const accesMail = allUser?.datas || [];
}).catch((err) => { setUsers(accesMail);
})
.catch((err) => {});
}
}) useEffect(() => {
} getData();
}, []);
useEffect(() => { return (
getData(); <RenderOverlay>
}, []) <div
className="collaboration-popup-wrapper"
useEffect(() => {
//
}, [activeUsers]);
return (
<RenderOverlay>
<div
className="collaboration-popup-wrapper"
onClick={() => {
setUserManagement(false);
}}
>
<div
className="collaboration-popup-container"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div className="header">
<div className="content">Share this file</div>
<div className="content">
{/* <div className="copy-link-button">copy link</div> */}
<div
className="close-button"
onClick={() => { onClick={() => {
setUserManagement(false); setUserManagement(false);
}} }}
> >
<CloseIcon /> <div
</div> className="collaboration-popup-container"
</div> onClick={(e) => {
</div> e.preventDefault();
<div className="invite-input-container"> e.stopPropagation();
<MultiEmailInvite users={users} getData={getData} /> }}
</div> >
<div className="split"></div> <div className="header">
<section> <div className="content">Share this file</div>
<div className="access-and-user-control-container"> <div className="content">
<div className="user-header">Who has access</div> {/* <div className="copy-link-button">copy link</div> */}
<div className="general-access-container"> <div
<div className="team-container"> className="close-button"
<div className="project-name">Untitled</div> onClick={() => {
<div className="number-of-peoples-have-access"> setUserManagement(false);
{users.length} persons }}
</div> >
<CloseIcon />
</div>
</div>
</div>
<div className="invite-input-container">
<MultiEmailInvite users={users} getData={getData} />
</div>
<div className="split"></div>
<section>
<div className="access-and-user-control-container">
<div className="user-header">Who has access</div>
<div className="general-access-container">
<div className="team-container">
<div className="project-name">Untitled</div>
<div className="number-of-peoples-have-access">{users.length} persons</div>
</div>
</div>
<div className="users-list-container">
<div className="you-container">
<div className="your-name">
<div className="user-profile">{userName && userName[0].toUpperCase()}</div>
{userName && userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()}
</div>
<div className="indicater">you</div>
</div>
{users.map((user, index) => (
<UserListTemplate key={index} user={user} />
))}
</div>
</div>
</section>
</div> </div>
</div>
<div className="users-list-container">
<div className="you-container">
<div className="your-name">
<div className="user-profile">{userName && userName[0].toUpperCase()}</div>
{userName && userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()}
</div>
<div className="indicater">you</div>
</div>
{users.map((user, index) => (
<UserListTemplate key={index} user={user} />
))}
</div>
</div> </div>
</section> </RenderOverlay>
</div> );
</div>
</RenderOverlay>
);
}; };
export default CollaborationPopup; export default CollaborationPopup;

View File

@@ -14,7 +14,6 @@ const FileMenu: React.FC = () => {
const containerRef = useRef<HTMLButtonElement>(null); const containerRef = useRef<HTMLButtonElement>(null);
let clickTimeout: NodeJS.Timeout | null = null; let clickTimeout: NodeJS.Timeout | null = null;
const { projectName, setProjectName } = useProjectName(); const { projectName, setProjectName } = useProjectName();
// const { dashBoardSocket } = useSocketStore();
const { projectId } = useParams(); const { projectId } = useParams();
const { userId, organization, email } = getUserData(); const { userId, organization, email } = getUserData();
@@ -58,16 +57,6 @@ const FileMenu: React.FC = () => {
projectName, projectName,
thumbnail: undefined, 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 //API for projects rename
const updatedProjectName = await updateProjectApi(projectId, undefined, projectName); const updatedProjectName = await updateProjectApi(projectId, undefined, projectName);

View File

@@ -34,7 +34,7 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
const [isEditComment, setIsEditComment] = useState(false); const [isEditComment, setIsEditComment] = useState(false);
const { selectedComment, setCommentPositionState } = useSelectedComment(); const { selectedComment, setCommentPositionState } = useSelectedComment();
const { versionStore, threadStore } = useSceneContext(); const { versionStore, threadStore } = useSceneContext();
const { updateComment, removeReply, updateReply } = threadStore(); const { updateThread, removeReply, updateReply } = threadStore();
const { selectedVersion } = versionStore(); const { selectedVersion } = versionStore();
const [value, setValue] = useState<string>("comment" in val ? val.comment : val.threadTitle); const [value, setValue] = useState<string>("comment" in val ? val.comment : val.threadTitle);
@@ -71,7 +71,7 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
rotation: [0, 0, 0], rotation: [0, 0, 0],
comments: [], comments: [],
}; };
updateComment((val as ThreadSchema).threadId, editedThread); updateThread((val as ThreadSchema).threadId, editedThread);
} }
}); });
} else { } else {

View File

@@ -36,7 +36,7 @@ const ThreadChat: React.FC = () => {
const messagesRef = useRef<HTMLDivElement>(null); const messagesRef = useRef<HTMLDivElement>(null);
const { versionStore, threadStore } = useSceneContext(); const { versionStore, threadStore } = useSceneContext();
const { selectedVersion } = versionStore(); const { selectedVersion } = versionStore();
const { addComment, removeComment, addReply, threads } = threadStore(); const { addThread, removeThread, addReply, threads } = threadStore();
useEffect(() => { useEffect(() => {
modeRef.current = mode; modeRef.current = mode;
@@ -157,7 +157,7 @@ const ThreadChat: React.FC = () => {
} else if (threadSocket?.connected && mode === "create") { } else if (threadSocket?.connected && mode === "create") {
// SOCKET // SOCKET
const addComment = { const addThread = {
versionId: selectedVersion?.versionId || "", versionId: selectedVersion?.versionId || "",
projectId, projectId,
userId, userId,
@@ -166,7 +166,7 @@ const ThreadChat: React.FC = () => {
threadId: selectedComment?.threadId, threadId: selectedComment?.threadId,
}; };
threadSocket.emit("v1-Comment:add", addComment); threadSocket.emit("v1-Comment:add", addThread);
} }
setInputActive(false); setInputActive(false);
}; };
@@ -179,7 +179,7 @@ const ThreadChat: React.FC = () => {
deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "").then((deleteThread) => { deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "").then((deleteThread) => {
if (deleteThread.message === "Thread deleted Successfully") { if (deleteThread.message === "Thread deleted Successfully") {
removeComment(selectedComment?.threadId); removeThread(selectedComment?.threadId);
setSelectedComment(null); setSelectedComment(null);
} }
}); });
@@ -194,7 +194,7 @@ const ThreadChat: React.FC = () => {
versionId: selectedVersion?.versionId || "", versionId: selectedVersion?.versionId || "",
}; };
setSelectedComment(null); setSelectedComment(null);
removeComment(selectedComment?.threadId); removeThread(selectedComment?.threadId);
threadSocket.emit("v1:thread:delete", deleteThread); threadSocket.emit("v1:thread:delete", deleteThread);
} }
}; };
@@ -219,7 +219,7 @@ const ThreadChat: React.FC = () => {
rotation: [0, 0, 0], rotation: [0, 0, 0],
comments: [], comments: [],
}; };
addComment(comment); addThread(comment);
setCommentPositionState(null); setCommentPositionState(null);
setInputActive(false); setInputActive(false);
setSelectedComment(null); setSelectedComment(null);

View File

@@ -1,112 +1,89 @@
// LoggerProvider.tsx // LoggerProvider.tsx
import React, { import React, { createContext, useContext, useState, useCallback, useMemo, useEffect } from "react";
createContext,
useContext,
useState,
useCallback,
useMemo,
useEffect,
} from "react";
import { MathUtils } from "three"; import { MathUtils } from "three";
export type LogType = "log" | "info" | "warning" | "error" | "success"; export type LogType = "log" | "info" | "warning" | "error" | "success";
export interface LogEntry { export interface LogEntry {
id: string; id: string;
type: LogType; type: LogType;
message: string; message: string;
timestamp: Date; timestamp: Date;
} }
interface LoggerContextValue { interface LoggerContextValue {
logs: LogEntry[]; logs: LogEntry[];
setLogs: React.Dispatch<React.SetStateAction<LogEntry[]>>; setLogs: React.Dispatch<React.SetStateAction<LogEntry[]>>;
isLogListVisible: boolean; isLogListVisible: boolean;
setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>; setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>;
selectedTab: LogType | "all"; selectedTab: LogType | "all";
setSelectedTab: React.Dispatch<React.SetStateAction<LogType | "all">>; setSelectedTab: React.Dispatch<React.SetStateAction<LogType | "all">>;
log: (message: string) => void; log: (message: string) => void;
info: (message: string) => void; info: (message: string) => void;
warn: (message: string) => void; warn: (message: string) => void;
error: (message: string) => void; error: (message: string) => void;
success: (message: string) => void; success: (message: string) => void;
clear: () => void; clear: () => void;
} }
const LoggerContext = createContext<LoggerContextValue | undefined>(undefined); const LoggerContext = createContext<LoggerContextValue | undefined>(undefined);
export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({ export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
children, const [logs, setLogs] = useState<LogEntry[]>([]);
}) => { const [isLogListVisible, setIsLogListVisible] = useState<boolean>(false);
const [logs, setLogs] = useState<LogEntry[]>([]); const [selectedTab, setSelectedTab] = useState<LogType | "all">("all");
const [isLogListVisible, setIsLogListVisible] = useState<boolean>(false);
const [selectedTab, setSelectedTab] = useState<LogType | "all">("all");
const addLog = useCallback((type: LogType, message: string) => { const addLog = useCallback((type: LogType, message: string) => {
const newLog: LogEntry = { const newLog: LogEntry = {
id: MathUtils.generateUUID(), id: MathUtils.generateUUID(),
type, type,
message, message,
timestamp: new Date(), timestamp: new Date(),
}; };
setLogs((prevLogs) => [...prevLogs, newLog]); setLogs((prevLogs) => [...prevLogs, newLog]);
}, []); }, []);
const loggerMethods: LoggerContextValue = useMemo( const loggerMethods: LoggerContextValue = useMemo(
() => ({ () => ({
logs, logs,
setLogs, setLogs,
isLogListVisible, isLogListVisible,
setIsLogListVisible, setIsLogListVisible,
selectedTab, selectedTab,
setSelectedTab, setSelectedTab,
log: (message: string) => addLog("log", message), log: (message: string) => addLog("log", message),
info: (message: string) => addLog("info", message), info: (message: string) => addLog("info", message),
warn: (message: string) => addLog("warning", message), warn: (message: string) => addLog("warning", message),
error: (message: string) => addLog("error", message), error: (message: string) => addLog("error", message),
success: (message: string) => addLog("success", message), success: (message: string) => addLog("success", message),
clear: () => { clear: () => {
if (selectedTab !== "all") { if (selectedTab !== "all") {
setLogs((prevLogs) => setLogs((prevLogs) => prevLogs.filter((log) => log.type !== selectedTab));
prevLogs.filter((log) => log.type !== selectedTab) } else {
); setLogs([]);
} else { }
setLogs([]); },
} }),
}, [logs, setLogs, isLogListVisible, setIsLogListVisible, selectedTab, setSelectedTab, addLog]
}), );
[ useEffect(() => {
logs, (window as any).echo = {
setLogs, log: loggerMethods.log,
isLogListVisible, info: loggerMethods.info,
setIsLogListVisible, warn: loggerMethods.warn,
selectedTab, error: loggerMethods.error,
setSelectedTab, success: loggerMethods.success,
addLog, clear: loggerMethods.clear,
] };
); }, [loggerMethods]);
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 ( return <LoggerContext.Provider value={loggerMethods}>{children}</LoggerContext.Provider>;
<LoggerContext.Provider value={loggerMethods}>
{children}
</LoggerContext.Provider>
);
}; };
export const useLogger = () => { export const useLogger = () => {
const context = useContext(LoggerContext); const context = useContext(LoggerContext);
if (!context) { if (!context) {
throw new Error("useLogger must be used within a LoggerProvider"); throw new Error("useLogger must be used within a LoggerProvider");
} }
return context; return context;
}; };

16
app/src/global.d.ts vendored
View File

@@ -2,12 +2,12 @@
import { LogType } from "../components/ui/log/LoggerContext"; import { LogType } from "../components/ui/log/LoggerContext";
declare global { declare global {
const echo: { const echo: {
log: (message: string) => void; log: (message: string) => void;
info: (message: string) => void; info: (message: string) => void;
warn: (message: string) => void; warn: (message: string) => void;
error: (message: string) => void; error: (message: string) => void;
success: (message: string) => void; success: (message: string) => void;
clear: () => void; clear: () => void;
}; };
} }

View File

@@ -1,18 +1,8 @@
import * as THREE from "three"; import * as THREE from "three";
import { Html } from "@react-three/drei"; import { Html, useGLTF } from "@react-three/drei";
import { useNavigate } from "react-router-dom"; import { useRef } from "react";
import { useEffect, useRef, useState } from "react"; import { useFrame } from "@react-three/fiber";
import { useFrame, useThree } from "@react-three/fiber"; import { useSceneContext } from "../../scene/sceneContext";
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 { getUserData } from "../../../functions/getUserData"; import { getUserData } from "../../../functions/getUserData";
import { getAvatarColor } from "../functions/getAvatarColor"; import { getAvatarColor } from "../functions/getAvatarColor";
@@ -20,222 +10,65 @@ import CollabUserIcon from "./collabUserIcon";
import camModel from "../../../assets/gltf-glb/camera face 2.gltf"; import camModel from "../../../assets/gltf-glb/camera face 2.gltf";
const CamModelsGroup = () => { const CamModelsGroup = () => {
const navigate = useNavigate(); const { collabUsersStore } = useSceneContext();
const groupRef = useRef<THREE.Group>(null); const { collabUsers } = collabUsersStore();
const { organization, email } = getUserData(); const model: any = useGLTF(camModel);
const { setActiveUsers } = useActiveUsers(); const { userId: currentUserId } = getUserData();
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<any[]>([]);
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
}, []);
return ( return (
<group ref={groupRef} name="Cam-Model-Group"> <group name="Cam-Model-Group">
{cams.map((cam, index) => ( {collabUsers
<primitive .filter((user) => user.userId !== currentUserId)
key={cam.uuid} .map((user, index) => (
//eslint-disable-next-line <CamModel key={user.userId} index={index} user={user} model={model.scene as THREE.Object3D} />
object={cam} ))}
visible={selectedUser?.name !== cam.userData.userName && activeModule !== "visualization" && !isPlaying} </group>
> );
<Html };
as="div"
center const CamModel = ({ user, model, index }: { user: CollabUsersScheme; model: THREE.Object3D; index: number }) => {
zIndexRange={[1, 0]} const ref = useRef<THREE.Group>(null);
sprite
style={{ const targetPos = new THREE.Vector3();
color: "white", const targetQuat = new THREE.Quaternion();
textAlign: "center",
fontFamily: "Arial, sans-serif", useFrame((_, delta) => {
display: `${activeModule !== "visualization" ? "" : "none"}`, if (ref.current) {
opacity: `${selectedUser?.name !== cam.userData.userName && !isPlaying ? 1 : 0}`, targetPos.set(user.camData.position.x, user.camData.position.y, user.camData.position.z);
transition: "opacity .2s ease",
}} targetQuat.setFromEuler(new THREE.Euler(user.camData.rotation.x, user.camData.rotation.y, user.camData.rotation.z));
position={[-0.015, 0, 0.7]}
> ref.current.position.lerp(targetPos, 5 * delta);
<CollabUserIcon ref.current.quaternion.slerp(targetQuat, 5 * delta);
userImage={cam.userData.userImage ?? ""} }
userName={cam.userData.userName} });
id={cam.userData._id}
color={getAvatarColor(index, cam.userData.userName)} return (
position={cam.position} <group ref={ref}>
rotation={cam.rotation} <primitive object={model.clone()} />
target={cam.userData.target} <Html
/> as="div"
</Html> center
</primitive> zIndexRange={[1, 0]}
))} sprite
style={{
color: "white",
textAlign: "center",
fontFamily: "Arial, sans-serif",
transition: "opacity .2s ease",
}}
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={""}
userName={user.userName}
id={user.userId}
color={getAvatarColor(index, user.userName)}
position={user.camData.position}
rotation={user.camData.rotation}
target={user.camData.target}
/>
</Html>
</group> </group>
); );
}; };

View File

@@ -16,7 +16,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
const { threadSocket } = useSocketStore(); const { threadSocket } = useSocketStore();
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState } = useSelectedComment(); const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState } = useSelectedComment();
const { threadStore } = useSceneContext(); const { threadStore } = useSceneContext();
const { threads, removeReply, addComment, addReply, updateComment, updateReply } = threadStore(); const { threads, removeReply, addThread, addReply, updateThread, updateReply } = threadStore();
const { userId } = getUserData(); const { userId } = getUserData();
useEffect(() => { useEffect(() => {
@@ -98,7 +98,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
comments: [], comments: [],
}; };
setSelectedComment(comment); setSelectedComment(comment);
addComment(comment); addThread(comment);
setCommentPositionState(null); setCommentPositionState(null);
// setSelectedComment(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(editedThread);
// setSelectedComment(null); // setSelectedComment(null);

View File

@@ -1,8 +1,29 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useSocketStore } from "../../../store/socket/useSocketStore"; import { useSocketStore } from "../../../store/socket/useSocketStore";
import { useSceneContext } from "../../scene/sceneContext";
function UserResponses() { function UserResponses() {
const { builderSocket } = useSocketStore(); 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 //#region Camera
useEffect(() => { useEffect(() => {
@@ -10,9 +31,6 @@ function UserResponses() {
builderSocket.on("v1:camera:Response:update", (data: any) => { builderSocket.on("v1:camera:Response:update", (data: any) => {
if (!data.message) return; if (!data.message) return;
if (data.message === "Model created successfully") {
} else if (data.message === "Updated successfully") {
}
}); });
return () => { return () => {

View File

@@ -10,7 +10,7 @@ function ThreadInstances() {
const { projectId } = useParams(); const { projectId } = useParams();
const { userId } = getUserData(); const { userId } = getUserData();
const { versionStore, threadStore } = useSceneContext(); const { versionStore, threadStore } = useSceneContext();
const { threads, setComments } = threadStore(); const { threads, setThreads } = threadStore();
const { selectedVersion } = versionStore(); const { selectedVersion } = versionStore();
useEffect(() => { useEffect(() => {
@@ -36,7 +36,7 @@ function ThreadInstances() {
: [], : [],
})) }))
: []; : [];
setComments(formattedThreads); setThreads(formattedThreads);
}) })
.catch((err) => { .catch((err) => {
console.error("Failed to fetch threads:", err); console.error("Failed to fetch threads:", err);

View File

@@ -82,7 +82,7 @@ export default function Controls() {
setResetCamera(false); setResetCamera(false);
} }
}, [resetCamera]); }, [resetCamera, builderSocket, projectId]);
useEffect(() => { useEffect(() => {
controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop))); controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)));

View File

@@ -24,7 +24,9 @@ import { createStorageUnitStore, StorageUnitStoreType } from "../../store/simula
import { createHumanStore, HumanStoreType } from "../../store/simulation/useHumanStore"; import { createHumanStore, HumanStoreType } from "../../store/simulation/useHumanStore";
import { createCraneStore, CraneStoreType } from "../../store/simulation/useCraneStore"; 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 = { type SceneContextValue = {
versionStore: VersionStoreType; versionStore: VersionStoreType;
@@ -53,6 +55,8 @@ type SceneContextValue = {
threadStore: ThreadStoreType; threadStore: ThreadStoreType;
collabUsersStore: CollabUsersStoreType;
humanEventManagerRef: React.RefObject<HumanEventManagerState>; humanEventManagerRef: React.RefObject<HumanEventManagerState>;
craneEventManagerRef: React.RefObject<CraneEventManagerState>; craneEventManagerRef: React.RefObject<CraneEventManagerState>;
@@ -90,6 +94,8 @@ export function SceneProvider({ children, layout }: { readonly children: React.R
const threadStore = useMemo(() => createThreadsStore(), []); const threadStore = useMemo(() => createThreadsStore(), []);
const collabUsersStore = useMemo(() => createCollabusersStore(), []);
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] }); const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] }); const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
@@ -114,7 +120,8 @@ export function SceneProvider({ children, layout }: { readonly children: React.R
storageUnitStore.getState().clearStorageUnits(); storageUnitStore.getState().clearStorageUnits();
humanStore.getState().clearHumans(); humanStore.getState().clearHumans();
craneStore.getState().clearCranes(); craneStore.getState().clearCranes();
threadStore.getState().clearComments(); threadStore.getState().clearThreads();
collabUsersStore.getState().clearCollabUsers();
humanEventManagerRef.current.humanStates = []; humanEventManagerRef.current.humanStates = [];
craneEventManagerRef.current.craneStates = []; craneEventManagerRef.current.craneStates = [];
}, },
@@ -139,6 +146,7 @@ export function SceneProvider({ children, layout }: { readonly children: React.R
humanStore, humanStore,
craneStore, craneStore,
threadStore, threadStore,
collabUsersStore,
] ]
); );
@@ -164,6 +172,7 @@ export function SceneProvider({ children, layout }: { readonly children: React.R
humanStore, humanStore,
craneStore, craneStore,
threadStore, threadStore,
collabUsersStore,
humanEventManagerRef, humanEventManagerRef,
craneEventManagerRef, craneEventManagerRef,
clearStores, clearStores,
@@ -190,6 +199,7 @@ export function SceneProvider({ children, layout }: { readonly children: React.R
humanStore, humanStore,
craneStore, craneStore,
threadStore, threadStore,
collabUsersStore,
clearStores, clearStores,
layout, layout,
] ]

View File

@@ -13,7 +13,7 @@ const Dashboard: React.FC = () => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken"); const refreshToken = localStorage.getItem("refreshToken");
if (token && refreshToken) { if (token && refreshToken) {
useSocketStore.getState().initializeDashBoardSocket(token, refreshToken); useSocketStore.getState().initializeProjectSocket(token, refreshToken);
} }
}, [email, organization]); }, [email, organization]);

View File

@@ -24,7 +24,17 @@ const Project: React.FC = () => {
let navigate = useNavigate(); let navigate = useNavigate();
const echo = useLogger(); const echo = useLogger();
const { setActiveModule } = useModuleStore(); 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 { projectId } = useParams();
const { setProjectName } = useProjectName(); const { setProjectName } = useProjectName();
const { userId, email } = getUserData(); const { userId, email } = getUserData();
@@ -33,6 +43,7 @@ const Project: React.FC = () => {
const { activeTool } = useActiveTool(); const { activeTool } = useActiveTool();
useEffect(() => { useEffect(() => {
setActiveModule("builder");
if (!email || !userId) { if (!email || !userId) {
console.error("User data not found in localStorage"); console.error("User data not found in localStorage");
navigate("/page-not-found"); navigate("/page-not-found");
@@ -64,30 +75,65 @@ const Project: React.FC = () => {
}, [projectId]); }, [projectId]);
useEffect(() => { useEffect(() => {
setActiveModule("builder"); const token = localStorage.getItem("token");
if (email) { const refreshToken = localStorage.getItem("refreshToken");
const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken"); if (token && refreshToken && projectId) {
echo.warn("Validating token"); echo.warn("Validating token");
if (token && refreshToken && projectId) { initializeBuilderSocket(token, refreshToken, projectId);
initializeBuilderSocket(token, refreshToken, projectId); echo.success("Builder socket initialized");
initializeVisualizationSocket(token, refreshToken, projectId);
initializeThreadSocket(token, refreshToken, projectId);
}
echo.success("Project initialized and loaded successfully");
} else { } else {
navigate("/"); navigate("/");
return;
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
return () => { return () => {
if (projectId) { if (projectId && builderSocket) {
disconnectBuilderSocket(projectId); 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); 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); disconnectThreadSocket(projectId);
} }
}; };
}, [projectId]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId, threadSocket]);
useEffect(() => { useEffect(() => {
handleCanvasCursors(activeTool); handleCanvasCursors(activeTool);

View File

@@ -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) => { export const addCommentsApi = async (projectId: any, comment: string, threadId: string, versionId: string) => {
try { 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", method: "POST",
headers: { headers: {
Authorization: "Bearer <access_token>", Authorization: "Bearer <access_token>",

View File

@@ -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) => { export const editCommentsApi = async (projectId: any, comment: string, commentId: string, threadId: string, versionId: string) => {
try { 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", method: "POST",
headers: { headers: {
Authorization: "Bearer <access_token>", Authorization: "Bearer <access_token>",

View File

@@ -145,14 +145,6 @@ export const useDrieTemp = create<any>((set: any) => ({
setDrieTemp: (x: any) => set({ drieTemp: x }), setDrieTemp: (x: any) => set({ drieTemp: x }),
})); }));
export const useActiveUsers = create<any>((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<any>((set: any) => ({ export const useDrieUIValue = create<any>((set: any) => ({
drieUIValue: { touch: null, temperature: null, humidity: null }, drieUIValue: { touch: null, temperature: null, humidity: null },

View File

@@ -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<CollabUsersScheme>) => void;
setCollabUsers: (users: CollabUsersScheme[]) => void;
removeCollabUser: (userId: string) => void;
clearCollabUsers: () => void;
}
export const createCollabusersStore = () => {
return create<CollabUsersStore>()(
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<typeof createCollabusersStore>;

View File

@@ -4,17 +4,17 @@ import { immer } from "zustand/middleware/immer";
interface ThreadStore { interface ThreadStore {
threads: ThreadsSchema; threads: ThreadsSchema;
addComment: (thread: ThreadSchema) => void; addThread: (thread: ThreadSchema) => void;
setComments: (threads: ThreadsSchema) => void; setThreads: (threads: ThreadsSchema) => void;
updateComment: (threadId: string, updates: Partial<ThreadSchema>) => void; updateThread: (threadId: string, updates: Partial<ThreadSchema>) => void;
removeComment: (threadId: string) => void; removeThread: (threadId: string) => void;
clearComments: () => void; clearThreads: () => void;
addReply: (threadId: string, reply: Reply) => void; addReply: (threadId: string, reply: Reply) => void;
updateReply: (threadId: string, replyId: string, updates: Partial<Reply>) => void; updateReply: (threadId: string, replyId: string, updates: Partial<Reply>) => void;
removeReply: (threadId: string, _id: string) => void; removeReply: (threadId: string, _id: string) => void;
getCommentById: (threadId: string) => ThreadSchema | undefined; getThreadById: (threadId: string) => ThreadSchema | undefined;
} }
export const createThreadsStore = () => { export const createThreadsStore = () => {
@@ -22,7 +22,7 @@ export const createThreadsStore = () => {
immer((set, get) => ({ immer((set, get) => ({
threads: [], threads: [],
addComment: (thread) => { addThread: (thread) => {
set((state) => { set((state) => {
if (!state.threads.find((c) => c.threadId === thread.threadId)) { if (!state.threads.find((c) => c.threadId === thread.threadId)) {
state.threads.push(JSON.parse(JSON.stringify(thread))); state.threads.push(JSON.parse(JSON.stringify(thread)));
@@ -30,13 +30,13 @@ export const createThreadsStore = () => {
}); });
}, },
setComments: (threads) => { setThreads: (threads) => {
set((state) => { set((state) => {
state.threads = threads; state.threads = threads;
}); });
}, },
updateComment: (threadId, updates) => { updateThread: (threadId, updates) => {
set((state) => { set((state) => {
const thread = state.threads.find((c) => c.threadId === threadId); const thread = state.threads.find((c) => c.threadId === threadId);
if (thread) { if (thread) {
@@ -45,13 +45,13 @@ export const createThreadsStore = () => {
}); });
}, },
removeComment: (threadId) => { removeThread: (threadId) => {
set((state) => { set((state) => {
state.threads = state.threads.filter((c) => c.threadId !== threadId); state.threads = state.threads.filter((c) => c.threadId !== threadId);
}); });
}, },
clearComments: () => { clearThreads: () => {
set((state) => { set((state) => {
state.threads = []; state.threads = [];
}); });
@@ -87,7 +87,7 @@ export const createThreadsStore = () => {
}); });
}, },
getCommentById: (threadId) => { getThreadById: (threadId) => {
return get().threads.find((c) => c.threadId === threadId); return get().threads.find((c) => c.threadId === threadId);
}, },
})) }))

View File

@@ -4,98 +4,104 @@ import { io, Socket } from "socket.io-client";
interface SocketStore { interface SocketStore {
builderSocket: Socket | null; builderSocket: Socket | null;
visualizationSocket: Socket | null; visualizationSocket: Socket | null;
dashBoardSocket: Socket | null;
projectSocket: Socket | null; projectSocket: Socket | null;
threadSocket: Socket | null; threadSocket: Socket | null;
initializeBuilderSocket: (token: string, refreshToken: string, projectId: string) => void; initializeBuilderSocket: (token: string, refreshToken: string, projectId: string) => void;
initializeVisualizationSocket: (token: string, refreshToken: string, projectId: string) => void; initializeVisualizationSocket: (token: string, refreshToken: string, projectId: string) => void;
initializeThreadSocket: (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; initializeProjectSocket: (token: string, refreshToken: string) => void;
disconnectBuilderSocket: (projectId: string) => void; disconnectBuilderSocket: (projectId: string) => void;
disconnectVisualizationSocket: (projectId: string) => void; disconnectVisualizationSocket: (projectId: string) => void;
disconnectThreadSocket: (projectId: string) => void; disconnectThreadSocket: (projectId: string) => void;
disconnectDashBoardSocket: () => void;
disconnectProjectSocket: () => void; disconnectProjectSocket: () => void;
disconnectAll: (projectId?: string) => 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<SocketStore>((set, get) => ({ export const useSocketStore = create<SocketStore>((set, get) => ({
builderSocket: null, builderSocket: null,
visualizationSocket: null, visualizationSocket: null,
dashBoardSocket: null,
projectSocket: null, projectSocket: null,
threadSocket: null, threadSocket: null,
initializeBuilderSocket: (token, refreshToken, projectId) => { initializeBuilderSocket: (token, refreshToken, projectId) => {
if (get().builderSocket) return; if (get().builderSocket) return;
const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, { const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, socketOptions({ token, refreshToken, projectId }));
reconnection: true, attachLogs("Builder", builderSocket);
auth: { token, refreshToken, projectId },
});
set({ builderSocket }); set({ builderSocket });
}, },
initializeVisualizationSocket: (token, refreshToken, projectId) => { initializeVisualizationSocket: (token, refreshToken, projectId) => {
if (get().visualizationSocket) return; if (get().visualizationSocket) return;
const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, { const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, socketOptions({ token, refreshToken, projectId }));
reconnection: true, attachLogs("Visualization", visualizationSocket);
auth: { token, refreshToken, projectId },
});
set({ visualizationSocket }); set({ visualizationSocket });
}, },
initializeThreadSocket: (token, refreshToken, projectId) => { initializeThreadSocket: (token, refreshToken, projectId) => {
if (get().threadSocket) return; if (get().threadSocket) return;
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, { const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId }));
reconnection: true, attachLogs("Thread", threadSocket);
auth: { token, refreshToken, projectId },
});
set({ 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) => { initializeProjectSocket: (token, refreshToken) => {
if (get().projectSocket) return; if (get().projectSocket) return;
const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, { const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, socketOptions({ token, refreshToken }));
reconnection: true, attachLogs("Project", projectSocket);
auth: { token, refreshToken },
});
set({ projectSocket }); set({ projectSocket });
}, },
disconnectBuilderSocket: (projectId) => { disconnectBuilderSocket: (projectId) => {
get().builderSocket?.emit("leaveRoom", { projectId }); get().builderSocket?.emit("leaveRoom", { projectId });
get().builderSocket?.disconnect(); setTimeout(() => {
set({ builderSocket: null }); get().builderSocket?.disconnect();
set({ builderSocket: null });
}, 1000);
}, },
disconnectVisualizationSocket: (projectId) => { disconnectVisualizationSocket: (projectId) => {
get().visualizationSocket?.emit("leaveRoom", { projectId }); get().visualizationSocket?.emit("leaveRoom", { projectId });
get().visualizationSocket?.disconnect(); setTimeout(() => {
set({ visualizationSocket: null }); get().visualizationSocket?.disconnect();
set({ visualizationSocket: null });
}, 1000);
}, },
disconnectThreadSocket: (projectId) => { disconnectThreadSocket: (projectId) => {
get().threadSocket?.emit("leaveRoom", { projectId }); get().threadSocket?.emit("leaveRoom", { projectId });
get().threadSocket?.disconnect(); setTimeout(() => {
set({ threadSocket: null }); get().threadSocket?.disconnect();
}, set({ threadSocket: null });
}, 1000);
disconnectDashBoardSocket: () => {
get().dashBoardSocket?.disconnect();
set({ dashBoardSocket: null });
}, },
disconnectProjectSocket: () => { disconnectProjectSocket: () => {
@@ -108,16 +114,16 @@ export const useSocketStore = create<SocketStore>((set, get) => ({
get().visualizationSocket?.emit("leaveRoom", { projectId }); get().visualizationSocket?.emit("leaveRoom", { projectId });
get().threadSocket?.emit("leaveRoom", { projectId }); get().threadSocket?.emit("leaveRoom", { projectId });
get().builderSocket?.disconnect(); setTimeout(() => {
get().visualizationSocket?.disconnect(); get().builderSocket?.disconnect();
get().dashBoardSocket?.disconnect(); get().visualizationSocket?.disconnect();
get().projectSocket?.disconnect(); get().projectSocket?.disconnect();
get().threadSocket?.disconnect(); get().threadSocket?.disconnect();
}, 1000);
set({ set({
builderSocket: null, builderSocket: null,
visualizationSocket: null, visualizationSocket: null,
dashBoardSocket: null,
projectSocket: null, projectSocket: null,
threadSocket: null, threadSocket: null,
}); });

View File

@@ -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 { interface ThreadSchema {
state: "active" | "inactive"; state: "active" | "inactive";
threadId: string; threadId: string;
@@ -19,3 +34,4 @@ interface Reply {
} }
type ThreadsSchema = ThreadSchema[]; type ThreadsSchema = ThreadSchema[];
//#endregion