diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx
index ea2e800..927f34e 100644
--- a/app/src/components/layout/scenes/MainScene.tsx
+++ b/app/src/components/layout/scenes/MainScene.tsx
@@ -1,6 +1,13 @@
import { useEffect } from "react";
import { useParams } from "react-router-dom";
-import { useLoadingProgress, useRenameModeStore, useIsComparing, useSelectedComment, useWidgetSubOption, useToggleView, useCreateNewWindow } from "../../../store/builder/store";
+import {
+ useLoadingProgress,
+ useRenameModeStore,
+ useIsComparing,
+ useWidgetSubOption,
+ useToggleView,
+ useCreateNewWindow,
+} from "../../../store/builder/store";
import useModuleStore from "../../../store/ui/useModuleStore";
import { useSocketStore } from "../../../store/socket/useSocketStore";
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
@@ -44,16 +51,17 @@ function MainScene() {
const { toggleView } = useToggleView();
const { isPlaying } = usePlayButtonStore();
const { widgetSubOption } = useWidgetSubOption();
- const { builderSocket, visualizationSocket } = useSocketStore();
+ const { builderSocket, visualizationSocket, threadSocket } = useSocketStore();
const { selectedZone } = useSelectedZoneStore();
const { setFloatingWidget } = useFloatingWidget();
- const { assetStore, productStore, versionStore } = useSceneContext();
+ const { assetStore, productStore, versionStore, threadStore } = useSceneContext();
const { products, selectedProduct } = productStore();
const { versionHistory, setVersions, selectedVersion, setSelectedVersion } = versionStore();
const { setName, selectedAssets, setSelectedAssets } = assetStore();
const { projectId } = useParams();
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
- const { selectedComment, commentPositionState } = useSelectedComment();
+ const { commentPositionState, selectedThread } = threadStore();
+
const { resetStates } = useRestStates();
const { organization, userId } = getUserData();
const { createNewWindow } = useCreateNewWindow();
@@ -71,7 +79,12 @@ function MainScene() {
builderSocket.emit("joinRoom", { projectId: projectId });
}, 1000);
}
- }, [builderSocket, projectId]);
+ if (threadSocket && projectId) {
+ setTimeout(() => {
+ threadSocket.emit("joinRoom", { projectId: projectId });
+ }, 1000);
+ }
+ }, [builderSocket, threadSocket, projectId]);
useEffect(() => {
if (activeModule !== "simulation") {
@@ -108,9 +121,13 @@ function MainScene() {
useEffect(() => {
if (versionHistory.length > 0) {
recentlyViewedApi().then((projects) => {
- const recent_opened_verisionID = (Object.values(projects?.RecentlyViewed || {})[0] as any)?.Present_version._id;
+ const recent_opened_verisionID = (
+ Object.values(projects?.RecentlyViewed || {})[0] as any
+ )?.Present_version._id;
if (recent_opened_verisionID && projects.RecentlyViewed[0]._id === projectId) {
- const version = versionHistory.find((ver) => ver.versionId === recent_opened_verisionID);
+ const version = versionHistory.find(
+ (ver) => ver.versionId === recent_opened_verisionID
+ );
if (version) {
setSelectedVersion(version);
}
@@ -188,7 +205,9 @@ function MainScene() {
{!selectedUser && (
<>
- {!createNewWindow && loadingProgress > 0 && }
+ {!createNewWindow && loadingProgress > 0 && (
+
+ )}
{!isPlaying && (
<>
{!toggleView && !isComparing && }
@@ -203,27 +222,48 @@ function MainScene() {
{/* */}
{activeModule === "market" && }
{activeModule !== "market" && !isPlaying && !isComparing && }
- {isPlaying && activeModule === "simulation" && loadingProgress === 0 && }
+ {isPlaying && activeModule === "simulation" && loadingProgress === 0 && (
+
+ )}
{isPlaying && activeModule !== "simulation" && }
{activeModule === "visualization" && }
- {isRenameMode && selectedAssets.length === 1 && }
+ {isRenameMode && selectedAssets.length === 1 && (
+
+ )}
{activeModule === "builder" && toggleView && }
- {selectedProduct && selectedVersion && isComparing && !isPlaying && activeModule === "simulation" && (
-
- v.versionName)} onSelect={handleSelectVersion} search={false} />
-
- l.productName)} onSelect={handleSelectProduct} search={false} />
-
- )}
+ {selectedProduct &&
+ selectedVersion &&
+ isComparing &&
+ !isPlaying &&
+ activeModule === "simulation" && (
+
+ v.versionName)}
+ onSelect={handleSelectVersion}
+ search={false}
+ />
+
+ l.productName)}
+ onSelect={handleSelectProduct}
+ search={false}
+ />
+
+ )}
>
)}
- {(commentPositionState !== null || selectedComment !== null) && }
+ {(commentPositionState !== null || selectedThread !== null) && }
{activeModule !== "market" && !selectedUser && }
diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx
index b7607f8..8964b04 100644
--- a/app/src/components/layout/sidebarRight/SideBarRight.tsx
+++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx
@@ -31,10 +31,10 @@ import SelectedFloorProperties from "./properties/SelectedFloorProperties";
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
import SelectedZoneProperties from "./properties/SelectedZoneProperties";
+import ThreadProperties from "./properties/ThreadProperties";
import ResourceManagement from "./resourceManagement/ResourceManagement";
import { useSceneContext } from "../../../modules/scene/sceneContext";
-import ThreadDetails from "./properties/eventProperties/components/ThreadDetails";
type DisplayComponent =
| "versionHistory"
@@ -54,13 +54,12 @@ type DisplayComponent =
| "analysis"
| "visualization"
| "resourceManagement"
- | "threadDetails"
+ | "threadProperties"
| "none";
const SideBarRight: React.FC = () => {
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
- console.log("activeTool: ", activeTool);
const { toggleUIRight } = useToggleStore();
const { toolMode } = useToolMode();
const { subModule, setSubModule } = useSubModuleStore();
@@ -68,12 +67,10 @@ const SideBarRight: React.FC = () => {
useBuilderStore();
const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere();
- const { versionStore, assetStore, threadStore } = useSceneContext();
+ const { versionStore, assetStore } = useSceneContext();
const { selectedAssets } = assetStore();
const { viewVersionHistory, setVersionHistoryVisible } = versionStore();
const { isComparing } = useIsComparing();
- const { threads } = threadStore();
- console.log("threads: ", threads);
const [displayComponent, setDisplayComponent] = useState("none");
@@ -119,7 +116,12 @@ const SideBarRight: React.FC = () => {
return;
}
}
-
+ if (
+ (activeModule === "builder" || activeModule === "simulation") &&
+ activeTool === "comment"
+ ) {
+ setDisplayComponent("threadProperties");
+ }
if (
activeModule === "simulation" ||
(activeModule === "builder" && activeTool !== "comment")
@@ -130,7 +132,11 @@ const SideBarRight: React.FC = () => {
}
}
- if (subModule === "properties" && activeModule !== "visualization") {
+ if (
+ subModule === "properties" &&
+ activeModule !== "visualization" &&
+ activeTool !== "comment"
+ ) {
if (selectedAssets.length === 1) {
setDisplayComponent("assetProperties");
return;
@@ -230,12 +236,8 @@ const SideBarRight: React.FC = () => {
setDisplayComponent("globalProperties");
return;
}
- if (activeModule === "builder" && activeTool === "comment") {
- setDisplayComponent("threadDetails");
- }
}
-
- setDisplayComponent("none");
+ // setDisplayComponent("none");
}, [
viewVersionHistory,
activeModule,
@@ -287,8 +289,8 @@ const SideBarRight: React.FC = () => {
return ;
case "resourceManagement":
return ;
- case "threadDetails":
- return ;
+ case "threadProperties":
+ return ;
default:
return null;
}
diff --git a/app/src/components/layout/sidebarRight/properties/ThreadProperties.tsx b/app/src/components/layout/sidebarRight/properties/ThreadProperties.tsx
new file mode 100644
index 0000000..31bc9ed
--- /dev/null
+++ b/app/src/components/layout/sidebarRight/properties/ThreadProperties.tsx
@@ -0,0 +1,124 @@
+import { useState } from "react";
+import { useSceneContext } from "../../../../modules/scene/sceneContext";
+import { getRelativeTime } from "../../../ui/collaboration/function/getRelativeTime";
+import { KebabIcon } from "../../../icons/ExportCommonIcons";
+import { getAvatarColorUsingUserID } from "../../../../modules/collaboration/functions/getAvatarColor";
+import { deleteThreadApi } from "../../../../services/builder/collab/comments/deleteThreadApi";
+import { useParams } from "react-router-dom";
+import { useSocketStore } from "../../../../store/socket/useSocketStore";
+import { getUserData } from "../../../../functions/getUserData";
+import useThreadResponseHandler from "../../../../modules/collaboration/responseHandler/useThreadResponseHandler";
+
+const ThreadProperties = () => {
+ const { threadStore, versionStore } = useSceneContext();
+ const { threads, setSelectedThread } = threadStore();
+ const { userId, organization } = getUserData();
+ const { selectedVersion } = versionStore();
+ const { projectId } = useParams();
+ const { threadSocket } = useSocketStore();
+ const [selectedChart, setSelectedChart] = useState(null);
+ const [isKebabActive, setIsKebabActive] = useState(false);
+ const { removeThreadFromScene } = useThreadResponseHandler();
+ const handleDeleteThread = (val: ThreadSchema) => {
+ if (!projectId) return;
+
+ const threadIdToDelete = val?.threadId;
+
+ if (!threadSocket?.connected) {
+ // API fallback
+ deleteThreadApi(projectId, threadIdToDelete, selectedVersion?.versionId || "").then(
+ (res) => {
+ if (res.message === "Thread deleted Successfully") {
+ removeThreadFromScene(res.data._id);
+ }
+ }
+ );
+ } else {
+ // SOCKET path
+ const deleteThreadPayload = {
+ projectId,
+ userId,
+ organization,
+ threadId: threadIdToDelete,
+ versionId: selectedVersion?.versionId || "",
+ };
+
+ threadSocket.emit("v1:thread:delete", deleteThreadPayload);
+ }
+ };
+
+ return (
+
+ {threads &&
+ threads.map((val) => {
+ const isMenuVisible =
+ selectedChart?.createdAt === val.createdAt && isKebabActive;
+
+ return (
+
+
+ {/* Avatar */}
+
+ {val?.creatorName?.charAt(0).toUpperCase()}
+
+
+ {/* Content */}
+
+ {/* Title + username */}
+
+
{val.threadTitle}
+
{val.creatorName}
+
+
+ {/* Last updated + kebab */}
+
+
+ {getRelativeTime(val.createdAt)}
+
+
+
+
+
+
+ {/* Kebab menu shown only for selected thread */}
+ {isMenuVisible && (
+
+
+
+ {val.creatorId === userId && (
+
+ )}
+
+ )}
+
+ );
+ })}
+
+ );
+};
+
+export default ThreadProperties;
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/ThreadDetails.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ThreadDetails.tsx
deleted file mode 100644
index 4189ab0..0000000
--- a/app/src/components/layout/sidebarRight/properties/eventProperties/components/ThreadDetails.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react";
-
-const ThreadDetails = () => {
- return ;
-};
-
-export default ThreadDetails;
diff --git a/app/src/components/ui/collaboration/threads/CommentThreads.tsx b/app/src/components/ui/collaboration/threads/CommentThreads.tsx
index 6426d53..2c0a754 100644
--- a/app/src/components/ui/collaboration/threads/CommentThreads.tsx
+++ b/app/src/components/ui/collaboration/threads/CommentThreads.tsx
@@ -1,22 +1,39 @@
-import React, { useState } from "react";
-import { getAvatarColor } from "../../../../modules/collaboration/functions/getAvatarColor";
-import { getUserData } from "../../../../functions/getUserData";
+import React, { useMemo, useState } from "react";
+import {
+ // getAvatarColor,
+ getAvatarColorUsingUserID,
+} from "../../../../modules/collaboration/functions/getAvatarColor";
import { getRelativeTime } from "../function/getRelativeTime";
interface CommentThreadsProps {
commentClicked: () => void;
thread?: ThreadSchema;
+ selectedThread: ThreadSchema | null;
}
-const CommentThreads: React.FC = ({ commentClicked, thread }) => {
+const CommentThreads = ({ commentClicked, thread, selectedThread }: CommentThreadsProps) => {
const [expand, setExpand] = useState(false);
- const commentsedUsers = [{ creatorId: "1" }];
- const { userName } = getUserData();
+ const commentedUsers = useMemo(() => {
+ if (!thread) return [];
- function getUsername(userId: string) {
- const UserName = userName?.charAt(0).toUpperCase() || "user";
- return UserName;
- }
+ const usersMap = new Map();
+
+ usersMap.set(thread.creatorId, {
+ creatorId: thread.creatorId,
+ creatorName: thread.creatorName,
+ });
+
+ for (const reply of thread.comments) {
+ if (!usersMap.has(reply.creatorId)) {
+ usersMap.set(reply.creatorId, {
+ creatorId: reply.creatorId,
+ creatorName: reply.creatorName ?? "Unknown User",
+ });
+ }
+ }
+
+ return Array.from(usersMap.values());
+ }, [thread]);
function getDetails(type?: "clicked") {
if (type === "clicked") {
@@ -27,36 +44,49 @@ const CommentThreads: React.FC = ({ commentClicked, thread
}
}
+ if (!thread) return null;
+
return (