added collabcam socket response
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
16
app/src/global.d.ts
vendored
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|||||||
@@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>",
|
||||||
|
|||||||
@@ -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>",
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|
||||||
|
|||||||
50
app/src/store/collaboration/useCollabUsersStore.ts
Normal file
50
app/src/store/collaboration/useCollabUsersStore.ts
Normal 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>;
|
||||||
@@ -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);
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
16
app/src/types/collaborationTypes.d.ts
vendored
16
app/src/types/collaborationTypes.d.ts
vendored
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user