refactoring thread functionalities including socketresponses
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
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 useModuleStore from "../../../store/ui/useModuleStore";
|
||||||
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
||||||
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||||
@@ -44,16 +51,17 @@ function MainScene() {
|
|||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { widgetSubOption } = useWidgetSubOption();
|
const { widgetSubOption } = useWidgetSubOption();
|
||||||
const { builderSocket, visualizationSocket } = useSocketStore();
|
const { builderSocket, visualizationSocket, threadSocket } = useSocketStore();
|
||||||
const { selectedZone } = useSelectedZoneStore();
|
const { selectedZone } = useSelectedZoneStore();
|
||||||
const { setFloatingWidget } = useFloatingWidget();
|
const { setFloatingWidget } = useFloatingWidget();
|
||||||
const { assetStore, productStore, versionStore } = useSceneContext();
|
const { assetStore, productStore, versionStore, threadStore } = useSceneContext();
|
||||||
const { products, selectedProduct } = productStore();
|
const { products, selectedProduct } = productStore();
|
||||||
const { versionHistory, setVersions, selectedVersion, setSelectedVersion } = versionStore();
|
const { versionHistory, setVersions, selectedVersion, setSelectedVersion } = versionStore();
|
||||||
const { setName, selectedAssets, setSelectedAssets } = assetStore();
|
const { setName, selectedAssets, setSelectedAssets } = assetStore();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
||||||
const { selectedComment, commentPositionState } = useSelectedComment();
|
const { commentPositionState, selectedThread } = threadStore();
|
||||||
|
|
||||||
const { resetStates } = useRestStates();
|
const { resetStates } = useRestStates();
|
||||||
const { organization, userId } = getUserData();
|
const { organization, userId } = getUserData();
|
||||||
const { createNewWindow } = useCreateNewWindow();
|
const { createNewWindow } = useCreateNewWindow();
|
||||||
@@ -71,7 +79,12 @@ function MainScene() {
|
|||||||
builderSocket.emit("joinRoom", { projectId: projectId });
|
builderSocket.emit("joinRoom", { projectId: projectId });
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}, [builderSocket, projectId]);
|
if (threadSocket && projectId) {
|
||||||
|
setTimeout(() => {
|
||||||
|
threadSocket.emit("joinRoom", { projectId: projectId });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, [builderSocket, threadSocket, projectId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeModule !== "simulation") {
|
if (activeModule !== "simulation") {
|
||||||
@@ -108,9 +121,13 @@ function MainScene() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (versionHistory.length > 0) {
|
if (versionHistory.length > 0) {
|
||||||
recentlyViewedApi().then((projects) => {
|
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) {
|
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) {
|
if (version) {
|
||||||
setSelectedVersion(version);
|
setSelectedVersion(version);
|
||||||
}
|
}
|
||||||
@@ -188,7 +205,9 @@ function MainScene() {
|
|||||||
{!selectedUser && (
|
{!selectedUser && (
|
||||||
<>
|
<>
|
||||||
<KeyPressListener />
|
<KeyPressListener />
|
||||||
{!createNewWindow && loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
{!createNewWindow && loadingProgress > 0 && (
|
||||||
|
<LoadingPage progress={loadingProgress} />
|
||||||
|
)}
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
<>
|
<>
|
||||||
{!toggleView && !isComparing && <ModuleToggle />}
|
{!toggleView && !isComparing && <ModuleToggle />}
|
||||||
@@ -203,27 +222,48 @@ function MainScene() {
|
|||||||
{/* <RealTimeVisulization /> */}
|
{/* <RealTimeVisulization /> */}
|
||||||
{activeModule === "market" && <MarketPlace />}
|
{activeModule === "market" && <MarketPlace />}
|
||||||
{activeModule !== "market" && !isPlaying && !isComparing && <Tools />}
|
{activeModule !== "market" && !isPlaying && !isComparing && <Tools />}
|
||||||
{isPlaying && activeModule === "simulation" && loadingProgress === 0 && <SimulationPlayer />}
|
{isPlaying && activeModule === "simulation" && loadingProgress === 0 && (
|
||||||
|
<SimulationPlayer />
|
||||||
|
)}
|
||||||
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />}
|
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />}
|
||||||
{activeModule === "visualization" && <DashboardEditor />}
|
{activeModule === "visualization" && <DashboardEditor />}
|
||||||
|
|
||||||
{isRenameMode && selectedAssets.length === 1 && <RenameTooltip name={selectedAssets[0].userData.modelName} onSubmit={handleObjectRename} />}
|
{isRenameMode && selectedAssets.length === 1 && (
|
||||||
|
<RenameTooltip
|
||||||
|
name={selectedAssets[0].userData.modelName}
|
||||||
|
onSubmit={handleObjectRename}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{activeModule === "builder" && toggleView && <SelectFloorPlan />}
|
{activeModule === "builder" && toggleView && <SelectFloorPlan />}
|
||||||
|
|
||||||
{selectedProduct && selectedVersion && isComparing && !isPlaying && activeModule === "simulation" && (
|
{selectedProduct &&
|
||||||
<div className="selectLayout-wrapper">
|
selectedVersion &&
|
||||||
<RegularDropDown header={selectedVersion.versionName} options={versionHistory.map((v) => v.versionName)} onSelect={handleSelectVersion} search={false} />
|
isComparing &&
|
||||||
<br />
|
!isPlaying &&
|
||||||
<RegularDropDown header={selectedProduct.productName} options={products.map((l) => l.productName)} onSelect={handleSelectProduct} search={false} />
|
activeModule === "simulation" && (
|
||||||
</div>
|
<div className="selectLayout-wrapper">
|
||||||
)}
|
<RegularDropDown
|
||||||
|
header={selectedVersion.versionName}
|
||||||
|
options={versionHistory.map((v) => v.versionName)}
|
||||||
|
onSelect={handleSelectVersion}
|
||||||
|
search={false}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<RegularDropDown
|
||||||
|
header={selectedProduct.productName}
|
||||||
|
options={products.map((l) => l.productName)}
|
||||||
|
onSelect={handleSelectProduct}
|
||||||
|
search={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<VersionSaved />
|
<VersionSaved />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(commentPositionState !== null || selectedComment !== null) && <ThreadChat />}
|
{(commentPositionState !== null || selectedThread !== null) && <ThreadChat />}
|
||||||
|
|
||||||
{activeModule !== "market" && !selectedUser && <Footer />}
|
{activeModule !== "market" && !selectedUser && <Footer />}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ import SelectedFloorProperties from "./properties/SelectedFloorProperties";
|
|||||||
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
|
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
|
||||||
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
|
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
|
||||||
import SelectedZoneProperties from "./properties/SelectedZoneProperties";
|
import SelectedZoneProperties from "./properties/SelectedZoneProperties";
|
||||||
|
import ThreadProperties from "./properties/ThreadProperties";
|
||||||
|
|
||||||
import ResourceManagement from "./resourceManagement/ResourceManagement";
|
import ResourceManagement from "./resourceManagement/ResourceManagement";
|
||||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||||
import ThreadDetails from "./properties/eventProperties/components/ThreadDetails";
|
|
||||||
|
|
||||||
type DisplayComponent =
|
type DisplayComponent =
|
||||||
| "versionHistory"
|
| "versionHistory"
|
||||||
@@ -54,13 +54,12 @@ type DisplayComponent =
|
|||||||
| "analysis"
|
| "analysis"
|
||||||
| "visualization"
|
| "visualization"
|
||||||
| "resourceManagement"
|
| "resourceManagement"
|
||||||
| "threadDetails"
|
| "threadProperties"
|
||||||
| "none";
|
| "none";
|
||||||
|
|
||||||
const SideBarRight: React.FC = () => {
|
const SideBarRight: React.FC = () => {
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { activeTool } = useActiveTool();
|
const { activeTool } = useActiveTool();
|
||||||
console.log("activeTool: ", activeTool);
|
|
||||||
const { toggleUIRight } = useToggleStore();
|
const { toggleUIRight } = useToggleStore();
|
||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
const { subModule, setSubModule } = useSubModuleStore();
|
const { subModule, setSubModule } = useSubModuleStore();
|
||||||
@@ -68,12 +67,10 @@ const SideBarRight: React.FC = () => {
|
|||||||
useBuilderStore();
|
useBuilderStore();
|
||||||
const { selectedEventData } = useSelectedEventData();
|
const { selectedEventData } = useSelectedEventData();
|
||||||
const { selectedEventSphere } = useSelectedEventSphere();
|
const { selectedEventSphere } = useSelectedEventSphere();
|
||||||
const { versionStore, assetStore, threadStore } = useSceneContext();
|
const { versionStore, assetStore } = useSceneContext();
|
||||||
const { selectedAssets } = assetStore();
|
const { selectedAssets } = assetStore();
|
||||||
const { viewVersionHistory, setVersionHistoryVisible } = versionStore();
|
const { viewVersionHistory, setVersionHistoryVisible } = versionStore();
|
||||||
const { isComparing } = useIsComparing();
|
const { isComparing } = useIsComparing();
|
||||||
const { threads } = threadStore();
|
|
||||||
console.log("threads: ", threads);
|
|
||||||
|
|
||||||
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
|
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
|
||||||
|
|
||||||
@@ -119,7 +116,12 @@ const SideBarRight: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
(activeModule === "builder" || activeModule === "simulation") &&
|
||||||
|
activeTool === "comment"
|
||||||
|
) {
|
||||||
|
setDisplayComponent("threadProperties");
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
activeModule === "simulation" ||
|
activeModule === "simulation" ||
|
||||||
(activeModule === "builder" && activeTool !== "comment")
|
(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) {
|
if (selectedAssets.length === 1) {
|
||||||
setDisplayComponent("assetProperties");
|
setDisplayComponent("assetProperties");
|
||||||
return;
|
return;
|
||||||
@@ -230,12 +236,8 @@ const SideBarRight: React.FC = () => {
|
|||||||
setDisplayComponent("globalProperties");
|
setDisplayComponent("globalProperties");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activeModule === "builder" && activeTool === "comment") {
|
|
||||||
setDisplayComponent("threadDetails");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// setDisplayComponent("none");
|
||||||
setDisplayComponent("none");
|
|
||||||
}, [
|
}, [
|
||||||
viewVersionHistory,
|
viewVersionHistory,
|
||||||
activeModule,
|
activeModule,
|
||||||
@@ -287,8 +289,8 @@ const SideBarRight: React.FC = () => {
|
|||||||
return <Visualization />;
|
return <Visualization />;
|
||||||
case "resourceManagement":
|
case "resourceManagement":
|
||||||
return <ResourceManagement />;
|
return <ResourceManagement />;
|
||||||
case "threadDetails":
|
case "threadProperties":
|
||||||
return <ThreadDetails />;
|
return <ThreadProperties />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<ThreadSchema | null>(null);
|
||||||
|
const [isKebabActive, setIsKebabActive] = useState<boolean>(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 (
|
||||||
|
<div className="global-properties-container">
|
||||||
|
{threads &&
|
||||||
|
threads.map((val) => {
|
||||||
|
const isMenuVisible =
|
||||||
|
selectedChart?.createdAt === val.createdAt && isKebabActive;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section key={val?.threadId} className="thread-section">
|
||||||
|
<div className="thread-card">
|
||||||
|
{/* Avatar */}
|
||||||
|
<div
|
||||||
|
className="thread-avatar"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getAvatarColorUsingUserID(val.creatorId),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{val?.creatorName?.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="thread-content">
|
||||||
|
{/* Title + username */}
|
||||||
|
<div>
|
||||||
|
<div className="thread-title">{val.threadTitle}</div>
|
||||||
|
<div className="thread-username">{val.creatorName}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Last updated + kebab */}
|
||||||
|
<div className="thread-footer">
|
||||||
|
<div className="thread-time">
|
||||||
|
{getRelativeTime(val.createdAt)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="thread-kebab-button"
|
||||||
|
onClick={() => {
|
||||||
|
// Toggle menu visibility per-thread
|
||||||
|
if (
|
||||||
|
selectedChart?.createdAt === val.createdAt &&
|
||||||
|
isKebabActive
|
||||||
|
) {
|
||||||
|
setIsKebabActive(false);
|
||||||
|
setSelectedChart(null);
|
||||||
|
} else {
|
||||||
|
setSelectedChart(val);
|
||||||
|
setIsKebabActive(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KebabIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Kebab menu shown only for selected thread */}
|
||||||
|
{isMenuVisible && (
|
||||||
|
<div className="thread-menu">
|
||||||
|
<button onClick={() => setSelectedThread(val)}>Visible</button>
|
||||||
|
<button>Show in Scene</button>
|
||||||
|
{val.creatorId === userId && (
|
||||||
|
<button onClick={() => handleDeleteThread(val)}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThreadProperties;
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const ThreadDetails = () => {
|
|
||||||
return <div></div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThreadDetails;
|
|
||||||
@@ -1,22 +1,39 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { getAvatarColor } from "../../../../modules/collaboration/functions/getAvatarColor";
|
import {
|
||||||
import { getUserData } from "../../../../functions/getUserData";
|
// getAvatarColor,
|
||||||
|
getAvatarColorUsingUserID,
|
||||||
|
} from "../../../../modules/collaboration/functions/getAvatarColor";
|
||||||
import { getRelativeTime } from "../function/getRelativeTime";
|
import { getRelativeTime } from "../function/getRelativeTime";
|
||||||
|
|
||||||
interface CommentThreadsProps {
|
interface CommentThreadsProps {
|
||||||
commentClicked: () => void;
|
commentClicked: () => void;
|
||||||
thread?: ThreadSchema;
|
thread?: ThreadSchema;
|
||||||
|
selectedThread: ThreadSchema | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked, thread }) => {
|
const CommentThreads = ({ commentClicked, thread, selectedThread }: CommentThreadsProps) => {
|
||||||
const [expand, setExpand] = useState(false);
|
const [expand, setExpand] = useState(false);
|
||||||
const commentsedUsers = [{ creatorId: "1" }];
|
const commentedUsers = useMemo(() => {
|
||||||
const { userName } = getUserData();
|
if (!thread) return [];
|
||||||
|
|
||||||
function getUsername(userId: string) {
|
const usersMap = new Map<string, { creatorId: string; creatorName: string }>();
|
||||||
const UserName = userName?.charAt(0).toUpperCase() || "user";
|
|
||||||
return UserName;
|
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") {
|
function getDetails(type?: "clicked") {
|
||||||
if (type === "clicked") {
|
if (type === "clicked") {
|
||||||
@@ -27,36 +44,49 @@ const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked, thread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!thread) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="comments-threads-wrapper">
|
<div className="comments-threads-wrapper">
|
||||||
<button
|
<button
|
||||||
onPointerEnter={() => getDetails()}
|
onPointerEnter={() => {
|
||||||
|
getDetails();
|
||||||
|
}}
|
||||||
onPointerLeave={() => getDetails()}
|
onPointerLeave={() => getDetails()}
|
||||||
onClick={() => getDetails("clicked")}
|
onClick={() => getDetails("clicked")}
|
||||||
className={`comments-threads-container ${expand ? "open" : "closed"} unread`}
|
className={`comments-threads-container ${
|
||||||
|
expand || (selectedThread && selectedThread.threadId === thread.threadId)
|
||||||
|
? "open"
|
||||||
|
: "closed"
|
||||||
|
} unread`}
|
||||||
>
|
>
|
||||||
<div className="users-commented">
|
<div className="users-commented">
|
||||||
{commentsedUsers.map((val, i) => (
|
{thread?.creatorId &&
|
||||||
<div
|
commentedUsers.map((val, i) => (
|
||||||
className="users"
|
<div
|
||||||
key={val.creatorId}
|
className="users"
|
||||||
style={{
|
key={val.creatorId}
|
||||||
background: getAvatarColor(i, getUsername(val.creatorId)),
|
style={{
|
||||||
}}
|
// background: getAvatarColor(i, getUsername(val.creatorId)),
|
||||||
>
|
background: getAvatarColorUsingUserID(val.creatorId),
|
||||||
{getUsername(val.creatorId)[0]}
|
}}
|
||||||
</div>
|
>
|
||||||
))}
|
{val.creatorName.charAt(0)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={`last-comment-details ${expand ? "expand" : ""}`}>
|
<div className={`last-comment-details ${expand ? "expand" : ""}`}>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<div className="user-name">{userName}</div>
|
<div className="user-name">{thread.creatorName}</div>
|
||||||
<div className="time">{thread?.createdAt && getRelativeTime(thread.createdAt)}</div>
|
<div className="time">
|
||||||
|
{thread?.createdAt && getRelativeTime(thread.createdAt)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="message">{thread?.threadTitle}</div>
|
<div className="message">{thread?.threadTitle}</div>
|
||||||
{thread && thread?.comments.length > 0 && (
|
{thread && thread?.comments.length > 0 && (
|
||||||
<div className="comments">
|
<div className="comments">
|
||||||
{thread?.comments.length} {thread?.comments.length === 1 ? "comment" : "replies"}
|
{thread?.comments.length}{" "}
|
||||||
|
{thread?.comments.length === 1 ? "comment" : "replies"}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ import { useParams } from "react-router-dom";
|
|||||||
import { KebabIcon } from "../../../icons/ExportCommonIcons";
|
import { KebabIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import { adjustHeight } from "../function/textAreaHeightAdjust";
|
import { adjustHeight } from "../function/textAreaHeightAdjust";
|
||||||
import { useSocketStore } from "../../../../store/socket/useSocketStore";
|
import { useSocketStore } from "../../../../store/socket/useSocketStore";
|
||||||
import { useSelectedComment } from "../../../../store/builder/store";
|
|
||||||
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||||
import { getAvatarColor } from "../../../../modules/collaboration/functions/getAvatarColor";
|
import {
|
||||||
|
getAvatarColor,
|
||||||
|
getAvatarColorUsingUserID,
|
||||||
|
} from "../../../../modules/collaboration/functions/getAvatarColor";
|
||||||
import { getUserData } from "../../../../functions/getUserData";
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
import { getRelativeTime } from "../function/getRelativeTime";
|
import { getRelativeTime } from "../function/getRelativeTime";
|
||||||
|
|
||||||
import { editThreadTitleApi } from "../../../../services/builder/collab/comments/editThreadTitleApi";
|
import { editThreadTitleApi } from "../../../../services/builder/collab/comments/editThreadTitleApi";
|
||||||
import { deleteCommentApi } from "../../../../services/builder/collab/comments/deleteCommentApi";
|
import { deleteCommentApi } from "../../../../services/builder/collab/comments/deleteCommentApi";
|
||||||
import { editCommentsApi } from "../../../../services/builder/collab/comments/editCommentApi";
|
import useThreadResponseHandler from "../../../../modules/collaboration/responseHandler/useThreadResponseHandler";
|
||||||
|
import { addCommentsApi } from "../../../../services/builder/collab/comments/addCommentApi";
|
||||||
|
|
||||||
interface MessageProps {
|
interface MessageProps {
|
||||||
val: Reply | ThreadSchema;
|
val: Reply | ThreadSchema;
|
||||||
i: number;
|
|
||||||
setMessages?: React.Dispatch<React.SetStateAction<Reply[]>>;
|
|
||||||
setIsEditable?: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsEditable?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setEditedThread?: React.Dispatch<React.SetStateAction<boolean>>;
|
setEditedThread?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setMode?: React.Dispatch<React.SetStateAction<"create" | "edit" | null>>;
|
setMode?: React.Dispatch<React.SetStateAction<"create" | "edit" | null>>;
|
||||||
@@ -26,17 +27,25 @@ interface MessageProps {
|
|||||||
mode?: "create" | "edit" | null;
|
mode?: "create" | "edit" | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEditable, setEditedThread, editedThread, isEditableThread, setMode }) => {
|
const Messages: React.FC<MessageProps> = ({
|
||||||
|
val,
|
||||||
|
mode,
|
||||||
|
setIsEditable,
|
||||||
|
setEditedThread,
|
||||||
|
editedThread,
|
||||||
|
isEditableThread,
|
||||||
|
setMode,
|
||||||
|
}) => {
|
||||||
const [openOptions, setOpenOptions] = useState(false);
|
const [openOptions, setOpenOptions] = useState(false);
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { threadSocket } = useSocketStore();
|
const { threadSocket } = useSocketStore();
|
||||||
const { userName, userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const [isEditComment, setIsEditComment] = useState(false);
|
const [isEditComment, setIsEditComment] = useState(false);
|
||||||
const { selectedComment, setCommentPositionState } = useSelectedComment();
|
|
||||||
const { versionStore, threadStore } = useSceneContext();
|
const { versionStore, threadStore } = useSceneContext();
|
||||||
const { updateThread, removeReply, updateReply } = threadStore();
|
const { setCommentPositionState, selectedThread } = threadStore();
|
||||||
const { selectedVersion } = versionStore();
|
const { selectedVersion } = versionStore();
|
||||||
|
const { updateThreadInScene, removeReplyFromThread, updateReplyInThread } =
|
||||||
|
useThreadResponseHandler();
|
||||||
const [value, setValue] = useState<string>("comment" in val ? val.comment : val.threadTitle);
|
const [value, setValue] = useState<string>("comment" in val ? val.comment : val.threadTitle);
|
||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@@ -52,26 +61,31 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveAction = async () => {
|
const handleSaveAction = async () => {
|
||||||
if (!projectId || !selectedVersion) return;
|
if (!projectId || !selectedVersion || !selectedThread) return;
|
||||||
|
console.log("mode: ", mode);
|
||||||
|
|
||||||
if (isEditableThread && editedThread) {
|
if (isEditableThread && editedThread) {
|
||||||
if (!threadSocket?.active) {
|
if (!threadSocket?.active) {
|
||||||
// API
|
// API
|
||||||
|
editThreadTitleApi(
|
||||||
editThreadTitleApi(projectId, (val as ThreadSchema).threadId, value, selectedVersion?.versionId || "").then((editThreadTitle) => {
|
projectId,
|
||||||
|
(val as ThreadSchema).threadId,
|
||||||
|
value,
|
||||||
|
selectedVersion?.versionId || ""
|
||||||
|
).then((editThreadTitle) => {
|
||||||
if (editThreadTitle.message == "ThreadTitle updated Successfully") {
|
if (editThreadTitle.message == "ThreadTitle updated Successfully") {
|
||||||
const editedThread: ThreadSchema = {
|
const updatedThread: ThreadSchema = {
|
||||||
state: "active",
|
state: editThreadTitle.data.state,
|
||||||
threadId: editThreadTitle.data.replyId,
|
threadId: editThreadTitle.data._id,
|
||||||
creatorId: userId,
|
creatorId: editThreadTitle.data.createdBy._id,
|
||||||
|
creatorName: editThreadTitle.data.createdBy.userName,
|
||||||
|
threadTitle: editThreadTitle.data.threadTitle,
|
||||||
createdAt: getRelativeTime(editThreadTitle.data.createdAt),
|
createdAt: getRelativeTime(editThreadTitle.data.createdAt),
|
||||||
threadTitle: value,
|
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
|
||||||
position: editThreadTitle.data.position,
|
position: editThreadTitle.data.position,
|
||||||
rotation: [0, 0, 0],
|
rotation: editThreadTitle.data.rotation,
|
||||||
comments: [],
|
comments: editThreadTitle.data.comments,
|
||||||
};
|
};
|
||||||
updateThread((val as ThreadSchema).threadId, editedThread);
|
updateThreadInScene(editThreadTitle.data._id, updatedThread);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -82,7 +96,7 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
userId,
|
userId,
|
||||||
threadTitle: value,
|
threadTitle: value,
|
||||||
organization,
|
organization,
|
||||||
threadId: (val as ThreadSchema).threadId || selectedComment.threadId,
|
threadId: (val as ThreadSchema).threadId || selectedThread.threadId,
|
||||||
versionId: selectedVersion?.versionId || "",
|
versionId: selectedVersion?.versionId || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,17 +105,23 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
} else if (mode === "edit") {
|
} else if (mode === "edit") {
|
||||||
if (!threadSocket?.active) {
|
if (!threadSocket?.active) {
|
||||||
// API
|
// API
|
||||||
|
addCommentsApi(
|
||||||
editCommentsApi(projectId, value, selectedComment?.threadId, (val as Reply).replyId, selectedVersion?.versionId || "").then((editComments) => {
|
projectId,
|
||||||
const commentData = {
|
value,
|
||||||
replyId: `${editComments.data?.replyId}`,
|
selectedThread?.threadId,
|
||||||
creatorId: `${userId}`,
|
selectedVersion?.versionId || "",
|
||||||
createdAt: getRelativeTime(editComments.data?.createdAt),
|
(val as Reply).replyId
|
||||||
|
).then((editedComment) => {
|
||||||
|
console.log("editedComment: ", editedComment);
|
||||||
|
const commentData: Partial<Reply> = {
|
||||||
lastUpdatedAt: "2 hrs ago",
|
lastUpdatedAt: "2 hrs ago",
|
||||||
comment: value,
|
comment: editedComment.data.comment,
|
||||||
};
|
};
|
||||||
|
updateReplyInThread(
|
||||||
updateReply((val as ThreadSchema).threadId, (val as Reply)?.replyId, commentData);
|
selectedThread.threadId,
|
||||||
|
editedComment.data._id,
|
||||||
|
commentData
|
||||||
|
);
|
||||||
|
|
||||||
if (setIsEditable) setIsEditable(true);
|
if (setIsEditable) setIsEditable(true);
|
||||||
if (setEditedThread) setEditedThread(false);
|
if (setEditedThread) setEditedThread(false);
|
||||||
@@ -114,7 +134,7 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
userId,
|
userId,
|
||||||
comment: value,
|
comment: value,
|
||||||
organization,
|
organization,
|
||||||
threadId: selectedComment?.threadId,
|
threadId: selectedThread?.threadId,
|
||||||
commentId: (val as Reply).replyId ?? "",
|
commentId: (val as Reply).replyId ?? "",
|
||||||
versionId: selectedVersion?.versionId || "",
|
versionId: selectedVersion?.versionId || "",
|
||||||
};
|
};
|
||||||
@@ -128,16 +148,24 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAction = async (replyID: any) => {
|
const handleDeleteAction = async (replyID: any) => {
|
||||||
if (!projectId || !selectedVersion || !setMessages) return;
|
if (!projectId || !selectedVersion || !selectedThread) return;
|
||||||
setOpenOptions(false);
|
setOpenOptions(false);
|
||||||
|
|
||||||
if (!threadSocket?.active) {
|
if (!threadSocket?.active) {
|
||||||
// API
|
// API
|
||||||
|
|
||||||
deleteCommentApi(projectId, selectedComment?.threadId, (val as Reply).replyId, selectedVersion?.versionId || "").then((deletedComment) => {
|
deleteCommentApi(
|
||||||
|
projectId,
|
||||||
|
selectedThread?.threadId,
|
||||||
|
(val as Reply).replyId,
|
||||||
|
selectedVersion?.versionId || ""
|
||||||
|
).then((deletedComment) => {
|
||||||
if (deletedComment === "'Thread comment deleted Successfully'") {
|
if (deletedComment === "'Thread comment deleted Successfully'") {
|
||||||
setMessages((prev) => prev.filter((message) => message.replyId !== replyID));
|
removeReplyFromThread(
|
||||||
removeReply(val.creatorId, replyID);
|
deletedComment.data._id,
|
||||||
|
deletedComment.data.comments[0]._id
|
||||||
|
);
|
||||||
|
console.log(deletedComment.data._id, deletedComment.data.comments[0]._id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -148,13 +176,9 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
userId,
|
userId,
|
||||||
commentId: (val as Reply).replyId,
|
commentId: (val as Reply).replyId,
|
||||||
organization,
|
organization,
|
||||||
threadId: selectedComment?.threadId,
|
threadId: selectedThread?.threadId,
|
||||||
versionId: selectedVersion?.versionId || "",
|
versionId: selectedVersion?.versionId || "",
|
||||||
};
|
};
|
||||||
setMessages((prev) => {
|
|
||||||
return prev.filter((message) => message.replyId !== (val as Reply).replyId);
|
|
||||||
});
|
|
||||||
removeReply(selectedComment?.threadId, (val as Reply).replyId);
|
|
||||||
|
|
||||||
threadSocket.emit("v1-Comment:delete", deleteComment);
|
threadSocket.emit("v1-Comment:delete", deleteComment);
|
||||||
}
|
}
|
||||||
@@ -174,7 +198,15 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
{isEditComment ? (
|
{isEditComment ? (
|
||||||
<div className="edit-container">
|
<div className="edit-container">
|
||||||
<div className="input-container">
|
<div className="input-container">
|
||||||
<textarea placeholder="type here" ref={textareaRef} autoFocus value={value} onChange={(e) => setValue(e.target.value)} style={{ resize: "none" }} onFocus={handleFocus} />
|
<textarea
|
||||||
|
placeholder="type here"
|
||||||
|
ref={textareaRef}
|
||||||
|
autoFocus
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
style={{ resize: "none" }}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="actions-container">
|
<div className="actions-container">
|
||||||
<div className="options"></div>
|
<div className="options"></div>
|
||||||
@@ -200,16 +232,24 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="message-container">
|
<div className="message-container">
|
||||||
<div className="profile" style={{ background: getAvatarColor(i, userId) }}>
|
<div
|
||||||
{userName?.charAt(0).toUpperCase() || "user"}
|
className="profile"
|
||||||
|
style={{ background: getAvatarColorUsingUserID(val.creatorId) }}
|
||||||
|
>
|
||||||
|
{val.creatorName?.charAt(0).toUpperCase() || "U"}
|
||||||
</div>
|
</div>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="user-details">
|
<div className="user-details">
|
||||||
<div className="user-name">{userName}</div>
|
<div className="user-name">{val.creatorName}</div>
|
||||||
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
|
<div className="time">
|
||||||
|
{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(val as Reply).creatorId === userId && (
|
{(val as Reply).creatorId === userId && (
|
||||||
<div className="more-options" onMouseLeave={() => setOpenOptions(false)}>
|
<div
|
||||||
|
className="more-options"
|
||||||
|
onMouseLeave={() => setOpenOptions(false)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="more-options-button"
|
className="more-options-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -249,7 +289,9 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="message">{"comment" in val ? val.comment : val.threadTitle}</div>
|
<div className="message">
|
||||||
|
{"comment" in val ? val.comment : val.threadTitle}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,64 +3,65 @@ import { useParams } from "react-router-dom";
|
|||||||
import { CloseIcon, KebabIcon } from "../../../icons/ExportCommonIcons";
|
import { CloseIcon, KebabIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import { ExpandIcon } from "../../../icons/SimulationIcons";
|
import { ExpandIcon } from "../../../icons/SimulationIcons";
|
||||||
import { adjustHeight } from "../function/textAreaHeightAdjust";
|
import { adjustHeight } from "../function/textAreaHeightAdjust";
|
||||||
import { useSelectedComment } from "../../../../store/builder/store";
|
|
||||||
import { useSocketStore } from "../../../../store/socket/useSocketStore";
|
import { useSocketStore } from "../../../../store/socket/useSocketStore";
|
||||||
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||||
import Messages from "./Messages";
|
import Messages from "./Messages";
|
||||||
import ThreadSocketResponsesDev from "../../../../modules/collaboration/socket/threadSocketResponses.dev";
|
|
||||||
|
|
||||||
import { getUserData } from "../../../../functions/getUserData";
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
import { addCommentsApi } from "../../../../services/builder/collab/comments/addCommentApi";
|
import { addCommentsApi } from "../../../../services/builder/collab/comments/addCommentApi";
|
||||||
import { deleteThreadApi } from "../../../../services/builder/collab/comments/deleteThreadApi";
|
import { deleteThreadApi } from "../../../../services/builder/collab/comments/deleteThreadApi";
|
||||||
import { createThreadApi } from "../../../../services/builder/collab/comments/createThreadApi";
|
import { createThreadApi } from "../../../../services/builder/collab/comments/createThreadApi";
|
||||||
import { getRelativeTime } from "../function/getRelativeTime";
|
import { getRelativeTime } from "../function/getRelativeTime";
|
||||||
|
import useThreadResponseHandler from "../../../../modules/collaboration/responseHandler/useThreadResponseHandler";
|
||||||
|
|
||||||
const ThreadChat: React.FC = () => {
|
const ThreadChat: React.FC = () => {
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const [openThreadOptions, setOpenThreadOptions] = useState(false);
|
const [openThreadOptions, setOpenThreadOptions] = useState(false);
|
||||||
const [inputActive, setInputActive] = useState(false);
|
const [inputActive, setInputActive] = useState(false);
|
||||||
const [value, setValue] = useState<string>("");
|
const [value, setValue] = useState<string>("");
|
||||||
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState, position2Dstate } = useSelectedComment();
|
|
||||||
const [mode, setMode] = useState<"create" | "edit" | null>("create");
|
const [mode, setMode] = useState<"create" | "edit" | null>("create");
|
||||||
const [isEditable, setIsEditable] = useState(false);
|
const [isEditable, setIsEditable] = useState(false);
|
||||||
const [editedThread, setEditedThread] = useState(false);
|
const [editedThread, setEditedThread] = useState(false);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const [messages, setMessages] = useState<Reply[]>([]);
|
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||||||
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
|
||||||
const { threadSocket } = useSocketStore();
|
const { threadSocket } = useSocketStore();
|
||||||
const modeRef = useRef<"create" | "edit" | null>(null);
|
const modeRef = useRef<"create" | "edit" | null>(null);
|
||||||
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 { addThread, removeThread, addReply, threads } = threadStore();
|
const {
|
||||||
|
threads,
|
||||||
|
getThreadById,
|
||||||
|
selectedThread,
|
||||||
|
setSelectedThread,
|
||||||
|
setCommentPositionState,
|
||||||
|
commentPositionState,
|
||||||
|
position2Dstate,
|
||||||
|
} = threadStore();
|
||||||
|
const [position, setPosition] = useState<{ x?: number; y?: number }>({
|
||||||
|
x: (position2Dstate as any)?.x,
|
||||||
|
y: (position2Dstate as any)?.y,
|
||||||
|
});
|
||||||
|
const { addThreadToScene, addReplyToThread, removeThreadFromScene } =
|
||||||
|
useThreadResponseHandler();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (threads.length > 0 && selectedThread) {
|
||||||
|
const thread = getThreadById(selectedThread.threadId);
|
||||||
|
if (thread) {
|
||||||
|
setSelectedThread(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("threads: ", threads);
|
||||||
|
}, [threads, selectedThread]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modeRef.current = mode;
|
modeRef.current = mode;
|
||||||
}, [mode]);
|
}, [mode]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (threads.length > 0 && selectedComment) {
|
|
||||||
const allMessages = threads
|
|
||||||
.flatMap((val: any) => (val?.threadId === selectedComment?.threadId ? val.comments : []))
|
|
||||||
.map((c) => {
|
|
||||||
return {
|
|
||||||
replyId: c._id ?? c.replyId,
|
|
||||||
creatorId: c.creatorId || c.userId,
|
|
||||||
createdAt: c.createdAt,
|
|
||||||
lastUpdatedAt: "1 hr ago",
|
|
||||||
comment: c.comment,
|
|
||||||
_id: c._id ?? c.replyId,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
setMessages(allMessages);
|
|
||||||
}
|
|
||||||
}, [selectedComment]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textareaRef.current) adjustHeight(textareaRef.current);
|
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
@@ -97,7 +98,10 @@ const ThreadChat: React.FC = () => {
|
|||||||
wrapper.setPointerCapture(event.pointerId);
|
wrapper.setPointerCapture(event.pointerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePosition = ({ clientX, clientY }: { clientX: number; clientY: number }, allowMove: boolean = true) => {
|
const updatePosition = (
|
||||||
|
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||||
|
allowMove: boolean = true
|
||||||
|
) => {
|
||||||
if (!allowMove || !wrapperRef.current) return;
|
if (!allowMove || !wrapperRef.current) return;
|
||||||
|
|
||||||
const container = document.getElementById("work-space-three-d-canvas");
|
const container = document.getElementById("work-space-three-d-canvas");
|
||||||
@@ -125,7 +129,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
// Commented this useEffect to prevent offset after user saved the comment
|
// Commented this useEffect to prevent offset after user saved the comment
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// updatePosition({ clientX: position.x, clientY: position.y }, true);
|
// updatePosition({ clientX: position.x, clientY: position.y }, true);
|
||||||
// }, [selectedComment]);
|
// }, [selectedThread]);
|
||||||
|
|
||||||
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
if (!dragging) return;
|
if (!dragging) return;
|
||||||
@@ -136,51 +140,62 @@ const ThreadChat: React.FC = () => {
|
|||||||
|
|
||||||
const handleCreateComments = async (e: any) => {
|
const handleCreateComments = async (e: any) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!value) return;
|
if (!value || !selectedThread) return;
|
||||||
|
|
||||||
if (!threadSocket?.connected && mode === "create") {
|
if (!threadSocket?.connected) {
|
||||||
// API
|
// API
|
||||||
|
addCommentsApi(
|
||||||
addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "").then((createComments) => {
|
projectId,
|
||||||
if (createComments.message === "Thread comments add Successfully" && createComments.data) {
|
value,
|
||||||
|
selectedThread?.threadId,
|
||||||
|
selectedVersion?.versionId || ""
|
||||||
|
).then((createComments) => {
|
||||||
|
console.log("createComments: ", createComments);
|
||||||
|
if (
|
||||||
|
createComments?.message === "Thread comments add Successfully" &&
|
||||||
|
createComments.data
|
||||||
|
) {
|
||||||
const commentData = {
|
const commentData = {
|
||||||
replyId: `${createComments.data?._id}`,
|
replyId: `${createComments.data?._id}`,
|
||||||
creatorId: `${selectedComment?.threadId}`,
|
creatorId: `${createComments.data?.userId}`,
|
||||||
createdAt: "2 hrs ago",
|
createdAt: getRelativeTime(createComments.data?.createdAt),
|
||||||
lastUpdatedAt: "2 hrs ago",
|
lastUpdatedAt: "2 hrs ago",
|
||||||
comment: value,
|
creatorName: createComments.data?.creatorName,
|
||||||
|
comment: createComments.data?.comment,
|
||||||
};
|
};
|
||||||
setMessages((prevMessages) => [...prevMessages, commentData]);
|
addReplyToThread(createComments?.data.threadId[0], commentData);
|
||||||
addReply(selectedComment?.threadId, commentData);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (threadSocket?.connected && mode === "create") {
|
} else if (threadSocket?.connected) {
|
||||||
// SOCKET
|
// SOCKET
|
||||||
|
console.log("else works");
|
||||||
const addThread = {
|
const addThread = {
|
||||||
versionId: selectedVersion?.versionId || "",
|
versionId: selectedVersion?.versionId || "",
|
||||||
projectId,
|
projectId,
|
||||||
userId,
|
userId,
|
||||||
comment: value,
|
comment: value,
|
||||||
organization,
|
organization,
|
||||||
threadId: selectedComment?.threadId,
|
threadId: selectedThread?.threadId,
|
||||||
};
|
};
|
||||||
|
console.log("addThread: ", addThread);
|
||||||
threadSocket.emit("v1-Comment:add", addThread);
|
threadSocket.emit("v1-Comment:add", addThread);
|
||||||
}
|
}
|
||||||
setInputActive(false);
|
setInputActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteThread = async () => {
|
const handleDeleteThread = async () => {
|
||||||
if (!projectId) return;
|
if (!projectId || !selectedThread) return;
|
||||||
|
|
||||||
if (!threadSocket?.connected) {
|
if (!threadSocket?.connected) {
|
||||||
// API
|
// API
|
||||||
|
deleteThreadApi(
|
||||||
deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "").then((deleteThread) => {
|
projectId,
|
||||||
|
selectedThread?.threadId,
|
||||||
|
selectedVersion?.versionId || ""
|
||||||
|
).then((deleteThread) => {
|
||||||
if (deleteThread.message === "Thread deleted Successfully") {
|
if (deleteThread.message === "Thread deleted Successfully") {
|
||||||
removeThread(selectedComment?.threadId);
|
removeThreadFromScene(deleteThread.data._id);
|
||||||
setSelectedComment(null);
|
setSelectedThread(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -190,11 +205,10 @@ const ThreadChat: React.FC = () => {
|
|||||||
projectId,
|
projectId,
|
||||||
userId,
|
userId,
|
||||||
organization,
|
organization,
|
||||||
threadId: selectedComment?.threadId,
|
threadId: selectedThread?.threadId,
|
||||||
versionId: selectedVersion?.versionId || "",
|
versionId: selectedVersion?.versionId || "",
|
||||||
};
|
};
|
||||||
setSelectedComment(null);
|
setSelectedThread(null);
|
||||||
removeThread(selectedComment?.threadId);
|
|
||||||
threadSocket.emit("v1:thread:delete", deleteThread);
|
threadSocket.emit("v1:thread:delete", deleteThread);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -205,24 +219,31 @@ const ThreadChat: React.FC = () => {
|
|||||||
|
|
||||||
if (!threadSocket?.connected) {
|
if (!threadSocket?.connected) {
|
||||||
// API
|
// API
|
||||||
|
createThreadApi(
|
||||||
createThreadApi(projectId, "active", commentPositionState.position, [0, 0, 0], value, selectedVersion?.versionId || "").then((thread) => {
|
projectId,
|
||||||
|
"active",
|
||||||
|
(commentPositionState as any)?.position,
|
||||||
|
[0, 0, 0],
|
||||||
|
value,
|
||||||
|
selectedVersion?.versionId || ""
|
||||||
|
).then((thread) => {
|
||||||
if (thread.message === "Thread created Successfully" && thread?.threadData) {
|
if (thread.message === "Thread created Successfully" && thread?.threadData) {
|
||||||
const comment: ThreadSchema = {
|
const comment: ThreadSchema = {
|
||||||
state: "active",
|
state: thread.threadData.state,
|
||||||
threadId: thread?.threadData?._id,
|
threadId: thread?.threadData?._id,
|
||||||
creatorId: userId,
|
creatorId: thread.threadData.createdBy,
|
||||||
|
creatorName: thread?.threadData.creatorName,
|
||||||
createdAt: getRelativeTime(thread.threadData?.createdAt),
|
createdAt: getRelativeTime(thread.threadData?.createdAt),
|
||||||
threadTitle: value,
|
threadTitle: thread.threadData.threadTitle,
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
lastUpdatedAt: new Date().toISOString(),
|
||||||
position: commentPositionState.position,
|
position: thread.threadData.position,
|
||||||
rotation: [0, 0, 0],
|
rotation: thread.threadData.rotation,
|
||||||
comments: [],
|
comments: [],
|
||||||
};
|
};
|
||||||
addThread(comment);
|
addThreadToScene(comment);
|
||||||
setCommentPositionState(null);
|
setCommentPositionState(null);
|
||||||
setInputActive(false);
|
setInputActive(false);
|
||||||
setSelectedComment(null);
|
setSelectedThread(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -234,15 +255,17 @@ const ThreadChat: React.FC = () => {
|
|||||||
userId,
|
userId,
|
||||||
organization,
|
organization,
|
||||||
state: "active",
|
state: "active",
|
||||||
position: commentPositionState.position,
|
position: (commentPositionState as any)?.position,
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
threadTitle: value,
|
threadTitle: value,
|
||||||
};
|
};
|
||||||
|
|
||||||
setCommentPositionState(null);
|
setCommentPositionState(null);
|
||||||
setInputActive(false);
|
setInputActive(false);
|
||||||
setSelectedComment(null);
|
setSelectedThread(null);
|
||||||
|
|
||||||
threadSocket.emit("v1:thread:create", createThread);
|
threadSocket.emit("v1:thread:create", createThread);
|
||||||
|
|
||||||
|
// Listen for response
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,8 +277,8 @@ const ThreadChat: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length > 0) scrollToBottom();
|
if (selectedThread && selectedThread.comments.length > 0) scrollToBottom();
|
||||||
}, [messages]);
|
}, [selectedThread]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -291,15 +314,17 @@ const ThreadChat: React.FC = () => {
|
|||||||
<div className="options-list">
|
<div className="options-list">
|
||||||
<div className="options">Mark as Unread</div>
|
<div className="options">Mark as Unread</div>
|
||||||
<div className="options">Mark as Resolved</div>
|
<div className="options">Mark as Resolved</div>
|
||||||
<div className="options delete" onClick={handleDeleteThread}>
|
{selectedThread?.creatorId === userId && (
|
||||||
Delete Thread
|
<div className="options delete" onClick={handleDeleteThread}>
|
||||||
</div>
|
Delete Thread
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="close-button"
|
className="close-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedComment(null);
|
setSelectedThread(null);
|
||||||
setCommentPositionState(null);
|
setCommentPositionState(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -309,13 +334,19 @@ const ThreadChat: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="messages-wrapper" ref={messagesRef}>
|
<div className="messages-wrapper" ref={messagesRef}>
|
||||||
{selectedComment && <Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />}
|
{selectedThread && (
|
||||||
{messages.map((val, i) => (
|
<Messages
|
||||||
|
val={selectedThread}
|
||||||
|
key={selectedThread?.creatorId}
|
||||||
|
isEditableThread={true}
|
||||||
|
setEditedThread={setEditedThread}
|
||||||
|
editedThread={editedThread}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(selectedThread as ThreadSchema)?.comments.map((val, i) => (
|
||||||
<Messages
|
<Messages
|
||||||
val={val as any}
|
val={val as any}
|
||||||
i={i}
|
|
||||||
key={val.replyId}
|
key={val.replyId}
|
||||||
setMessages={setMessages}
|
|
||||||
setIsEditable={setIsEditable}
|
setIsEditable={setIsEditable}
|
||||||
isEditable={isEditable}
|
isEditable={isEditable}
|
||||||
isEditableThread={false}
|
isEditableThread={false}
|
||||||
@@ -328,14 +359,18 @@ const ThreadChat: React.FC = () => {
|
|||||||
<div className="send-message-wrapper">
|
<div className="send-message-wrapper">
|
||||||
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder={commentPositionState && selectedComment === null ? "Type Thread Title" : "type something"}
|
placeholder={
|
||||||
|
commentPositionState && selectedThread === null
|
||||||
|
? "Type Thread Title"
|
||||||
|
: "type something"
|
||||||
|
}
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (commentPositionState && selectedComment === null) {
|
if (commentPositionState && selectedThread === null) {
|
||||||
handleCreateThread(e);
|
handleCreateThread(e);
|
||||||
} else {
|
} else {
|
||||||
setMode("create");
|
setMode("create");
|
||||||
@@ -349,11 +384,11 @@ const ThreadChat: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!commentPositionState && selectedComment !== null) {
|
if (!commentPositionState && selectedThread !== null) {
|
||||||
setMode("create");
|
setMode("create");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
autoFocus={selectedComment === null}
|
autoFocus={selectedThread === null}
|
||||||
onBlur={() => setInputActive(false)}
|
onBlur={() => setInputActive(false)}
|
||||||
onFocus={() => setInputActive(true)}
|
onFocus={() => setInputActive(true)}
|
||||||
style={{ resize: "none" }}
|
style={{ resize: "none" }}
|
||||||
@@ -361,7 +396,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
<div
|
<div
|
||||||
className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}
|
className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (commentPositionState && selectedComment === null) {
|
if (commentPositionState && selectedThread === null) {
|
||||||
handleCreateThread(e);
|
handleCreateThread(e);
|
||||||
} else {
|
} else {
|
||||||
setMode("create");
|
setMode("create");
|
||||||
@@ -375,7 +410,6 @@ const ThreadChat: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ThreadSocketResponsesDev setMessages={setMessages} modeRef={modeRef} messages={messages} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,12 +36,13 @@ export function getAvatarColor(index: number, userId?: string): string {
|
|||||||
|
|
||||||
// Find a new color not already assigned
|
// Find a new color not already assigned
|
||||||
const usedColors = Object.values(userColors);
|
const usedColors = Object.values(userColors);
|
||||||
const availableColors = avatarColors.filter(color => !usedColors.includes(color));
|
const availableColors = avatarColors.filter((color) => !usedColors.includes(color));
|
||||||
|
|
||||||
// Assign a new color
|
// Assign a new color
|
||||||
const assignedColor = availableColors.length > 0
|
const assignedColor =
|
||||||
? availableColors[0]
|
availableColors.length > 0
|
||||||
: avatarColors[index % avatarColors.length];
|
? availableColors[0]
|
||||||
|
: avatarColors[index % avatarColors.length];
|
||||||
|
|
||||||
userColors[userId] = assignedColor;
|
userColors[userId] = assignedColor;
|
||||||
|
|
||||||
@@ -54,3 +55,12 @@ export function getAvatarColor(index: number, userId?: string): string {
|
|||||||
// Fallback: Assign a color using the index if no userId or local storage is unavailable
|
// Fallback: Assign a color using the index if no userId or local storage is unavailable
|
||||||
return avatarColors[index % avatarColors.length];
|
return avatarColors[index % avatarColors.length];
|
||||||
}
|
}
|
||||||
|
export function getAvatarColorUsingUserID(userId: string): string {
|
||||||
|
// Simple deterministic color based on userId hash
|
||||||
|
let hash = 1;
|
||||||
|
for (let i = 1; i < userId.length; i++) {
|
||||||
|
hash = userId.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
hash = Math.abs(hash);
|
||||||
|
return avatarColors[hash % avatarColors.length];
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { useSceneContext } from "../../scene/sceneContext";
|
||||||
|
|
||||||
|
function useThreadResponseHandler() {
|
||||||
|
const { threadStore } = useSceneContext();
|
||||||
|
const { addThread, updateThread, removeThread, addReply, updateReply, removeReply } =
|
||||||
|
threadStore();
|
||||||
|
|
||||||
|
const addThreadToScene = useCallback(
|
||||||
|
(thread: ThreadSchema, callback?: () => void) => {
|
||||||
|
addThread(thread);
|
||||||
|
callback?.();
|
||||||
|
},
|
||||||
|
[addThread]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateThreadInScene = useCallback(
|
||||||
|
(threadId: string, updates: Partial<ThreadSchema>, callback?: () => void) => {
|
||||||
|
updateThread(threadId, updates);
|
||||||
|
callback?.();
|
||||||
|
},
|
||||||
|
[updateThread]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeThreadFromScene = useCallback(
|
||||||
|
(threadId: string, callback?: () => void) => {
|
||||||
|
removeThread(threadId);
|
||||||
|
callback?.();
|
||||||
|
},
|
||||||
|
[removeThread]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addReplyToThread = useCallback(
|
||||||
|
(threadId: string, reply: Reply, callback?: () => void) => {
|
||||||
|
addReply(threadId, reply);
|
||||||
|
callback?.();
|
||||||
|
},
|
||||||
|
[addReply]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateReplyInThread = useCallback(
|
||||||
|
(threadId: string, replyId: string, updates: Partial<Reply>, callback?: () => void) => {
|
||||||
|
updateReply(threadId, replyId, updates);
|
||||||
|
callback?.();
|
||||||
|
},
|
||||||
|
[updateReply]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeReplyFromThread = useCallback(
|
||||||
|
(threadId: string, replyId: string, callback?: () => void) => {
|
||||||
|
removeReply(threadId, replyId);
|
||||||
|
callback?.();
|
||||||
|
},
|
||||||
|
[removeReply]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
addThreadToScene,
|
||||||
|
updateThreadInScene,
|
||||||
|
removeThreadFromScene,
|
||||||
|
addReplyToThread,
|
||||||
|
updateReplyInThread,
|
||||||
|
removeReplyFromThread,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useThreadResponseHandler;
|
||||||
122
app/src/modules/collaboration/socket/collaborationResponses.tsx
Normal file
122
app/src/modules/collaboration/socket/collaborationResponses.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
||||||
|
import { getRelativeTime } from "../../../components/ui/collaboration/function/getRelativeTime";
|
||||||
|
import useThreadResponseHandler from "../responseHandler/useThreadResponseHandler";
|
||||||
|
|
||||||
|
const CollaborationResponses = () => {
|
||||||
|
const { threadSocket } = useSocketStore();
|
||||||
|
const {
|
||||||
|
addThreadToScene,
|
||||||
|
updateThreadInScene,
|
||||||
|
removeThreadFromScene,
|
||||||
|
addReplyToThread,
|
||||||
|
removeReplyFromThread,
|
||||||
|
} = useThreadResponseHandler();
|
||||||
|
|
||||||
|
//#region Thread
|
||||||
|
useEffect(() => {
|
||||||
|
if (!threadSocket) return;
|
||||||
|
|
||||||
|
threadSocket.on("v1-thread:response:create", (data: any) => {
|
||||||
|
if (!data.message || !data.data) {
|
||||||
|
echo.error(`Error adding or updating thread`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.message === "Thread created Successfully") {
|
||||||
|
const comment: ThreadSchema = {
|
||||||
|
state: data.data.state,
|
||||||
|
threadId: data.data._id,
|
||||||
|
creatorId: data.data.createdBy,
|
||||||
|
creatorName: data.data.creatorName,
|
||||||
|
threadTitle: data.data.threadTitle,
|
||||||
|
createdAt: getRelativeTime(data.data.createdAt),
|
||||||
|
position: data.data.position,
|
||||||
|
rotation: data.data.rotation,
|
||||||
|
comments: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
addThreadToScene(comment, () => {
|
||||||
|
echo.log("Thread created successfully");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
threadSocket.on("v1-thread:response:delete", (data: any) => {
|
||||||
|
if (!data.message || !data.data) {
|
||||||
|
echo.error(`Error in deleting thread`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.message === "Thread deleted Successfully") {
|
||||||
|
removeThreadFromScene(data.data._id, () => {
|
||||||
|
echo.log("Thread Deleted successfully");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
threadSocket.on("v1-thread:response:updateTitle", (data: any) => {
|
||||||
|
if (!data.message || !data.data) {
|
||||||
|
echo.error(`Error updating thread`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.message === "ThreadTitle updated Successfully") {
|
||||||
|
const updatedThread: ThreadSchema = {
|
||||||
|
state: data.data.state,
|
||||||
|
threadId: data.data._id,
|
||||||
|
creatorId: data.data.createdBy._id,
|
||||||
|
creatorName: data.data.createdBy.userName,
|
||||||
|
threadTitle: data.data.threadTitle,
|
||||||
|
createdAt: getRelativeTime(data.data.createdAt),
|
||||||
|
position: data.data.position,
|
||||||
|
rotation: data.data.rotation,
|
||||||
|
comments: data.data.comments,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateThreadInScene(data.data._id, updatedThread, () => {
|
||||||
|
echo.log("Thread title is updated");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
threadSocket.on("v1-Comment:response:add", (data: any) => {
|
||||||
|
if (data.message === "Thread comments add Successfully") {
|
||||||
|
const reply: Reply = {
|
||||||
|
creatorName: data.data.creatorName,
|
||||||
|
creatorId: data.data.userId,
|
||||||
|
createdAt: getRelativeTime(data.data.createdAt),
|
||||||
|
comment: data.data.comment,
|
||||||
|
lastUpdatedAt: "2hrs",
|
||||||
|
replyId: data.data._id,
|
||||||
|
};
|
||||||
|
|
||||||
|
addReplyToThread(data.data.threadId[0], reply);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
threadSocket.on("v1-Comment:response:delete", (data: any) => {
|
||||||
|
if (!data.message || !data.data) {
|
||||||
|
echo.error(`Error deleting reply`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.message === "Thread comment deleted Successfully") {
|
||||||
|
removeReplyFromThread(data.data._id, data.data.comments[0]._id, () => {
|
||||||
|
echo.log("Reply added Successfully");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (threadSocket) {
|
||||||
|
threadSocket.off("v1-thread:response:create");
|
||||||
|
threadSocket.off("v1-thread:response:delete");
|
||||||
|
threadSocket.off("v1-thread:response:updateTitle");
|
||||||
|
threadSocket.off("v1-Comment:response:add");
|
||||||
|
threadSocket.off("v1-Comment:response:delete");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [threadSocket]);
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollaborationResponses;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import UserResponses from "./userResponses";
|
import UserResponses from "./userResponses";
|
||||||
import BuilderResponses from "./builderResponses";
|
import BuilderResponses from "./builderResponses";
|
||||||
import SimulationResponses from "./simulationResponses";
|
import SimulationResponses from "./simulationResponses";
|
||||||
|
import CollaborationResponses from "./collaborationResponses";
|
||||||
|
|
||||||
export default function SocketResponses() {
|
export default function SocketResponses() {
|
||||||
return (
|
return (
|
||||||
@@ -10,6 +11,8 @@ export default function SocketResponses() {
|
|||||||
<BuilderResponses />
|
<BuilderResponses />
|
||||||
|
|
||||||
<SimulationResponses />
|
<SimulationResponses />
|
||||||
|
|
||||||
|
<CollaborationResponses />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { useSelectedComment } from "../../../store/builder/store";
|
|
||||||
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
|
||||||
import { getUserData } from "../../../functions/getUserData";
|
|
||||||
import { getRelativeTime } from "../../../components/ui/collaboration/function/getRelativeTime";
|
|
||||||
import { useSceneContext } from "../../scene/sceneContext";
|
|
||||||
|
|
||||||
interface ThreadSocketProps {
|
|
||||||
setMessages: React.Dispatch<React.SetStateAction<Reply[]>>;
|
|
||||||
// mode: 'create' | 'edit' | null
|
|
||||||
modeRef: React.RefObject<"create" | "edit" | null>;
|
|
||||||
messages: Reply[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSocketProps) => {
|
|
||||||
const { threadSocket } = useSocketStore();
|
|
||||||
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState } =
|
|
||||||
useSelectedComment();
|
|
||||||
const { threadStore } = useSceneContext();
|
|
||||||
const { threads, removeReply, addThread, addReply, updateThread, updateReply } = threadStore();
|
|
||||||
const { userId } = getUserData();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!threadSocket) return;
|
|
||||||
|
|
||||||
// --- Add Comment Handler ---
|
|
||||||
// const handleAddComment = (data: any) => {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// const commentData = {
|
|
||||||
// replyId: data.data?._id,
|
|
||||||
// creatorId: data.data?.userId,
|
|
||||||
// createdAt: getRelativeTime(data.data?.createdAt),
|
|
||||||
// lastUpdatedAt: '2 hrs ago',
|
|
||||||
// comment: data.data.comment,
|
|
||||||
// };
|
|
||||||
// //
|
|
||||||
//
|
|
||||||
// if (mode == "create") {
|
|
||||||
// addReply(selectedComment?.threadId, commentData);
|
|
||||||
//
|
|
||||||
// setMessages(prevMessages => [...prevMessages, commentData]);
|
|
||||||
// } else if (mode == "edit") {
|
|
||||||
// updateReply(selectedComment?.threadId, data.data?._id, commentData);
|
|
||||||
// setMessages((prev) =>
|
|
||||||
// prev.map((message) => {
|
|
||||||
// // 👈 log each message
|
|
||||||
// return (message.replyId || message._id) === data.data?._id
|
|
||||||
// ? { ...message, comment: data.data?.comment }
|
|
||||||
// : message;
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// } else {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// threadSocket.off('v1-Comment:response:add', handleAddComment);
|
|
||||||
// };
|
|
||||||
// threadSocket.on('v1-Comment:response:add', handleAddComment);
|
|
||||||
const handleAddComment = (data: any) => {
|
|
||||||
const commentData = {
|
|
||||||
replyId: data.data?._id,
|
|
||||||
creatorId: data.data?.userId,
|
|
||||||
createdAt: getRelativeTime(data.data?.createdAt),
|
|
||||||
lastUpdatedAt: "2 hrs ago",
|
|
||||||
comment: data.data?.comment,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (modeRef.current === "create") {
|
|
||||||
addReply(selectedComment?.threadId, commentData);
|
|
||||||
setMessages((prevMessages) => [...prevMessages, commentData]);
|
|
||||||
} else if (modeRef.current === "edit") {
|
|
||||||
updateReply(selectedComment?.threadId, data.data?._id, commentData);
|
|
||||||
setMessages((prev) =>
|
|
||||||
prev.map((message) => {
|
|
||||||
// 👈 log each message
|
|
||||||
return message.replyId === data.data?._id
|
|
||||||
? { ...message, comment: data.data?.comment }
|
|
||||||
: message;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
threadSocket.on("v1-Comment:response:add", handleAddComment);
|
|
||||||
// --- Delete Comment Handler ---
|
|
||||||
|
|
||||||
const handleDeleteComment = (data: any) => {};
|
|
||||||
threadSocket.on("v1-Comment:response:delete", handleDeleteComment);
|
|
||||||
|
|
||||||
// --- Create Thread Handler ---
|
|
||||||
const handleCreateThread = (data: any) => {
|
|
||||||
const comment: ThreadSchema = {
|
|
||||||
state: "active",
|
|
||||||
threadId: data.data?._id,
|
|
||||||
creatorId: userId || data.data?.userId,
|
|
||||||
createdAt: data.data?.createdAt,
|
|
||||||
threadTitle: data.data?.threadTitle,
|
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
|
||||||
position: commentPositionState.position,
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
comments: [],
|
|
||||||
};
|
|
||||||
setSelectedComment(comment);
|
|
||||||
addThread(comment);
|
|
||||||
setCommentPositionState(null);
|
|
||||||
// setSelectedComment(null);
|
|
||||||
};
|
|
||||||
threadSocket.on("v1-thread:response:create", handleCreateThread);
|
|
||||||
|
|
||||||
// --- Delete Thread Handler ---
|
|
||||||
// const handleDeleteThread = (data: any) => {
|
|
||||||
//
|
|
||||||
|
|
||||||
// };
|
|
||||||
// threadSocket.on("v1-thread:response:delete", handleDeleteThread);
|
|
||||||
|
|
||||||
const handleDeleteThread = (data: any) => {};
|
|
||||||
threadSocket.on("v1-thread:response:delete", handleDeleteThread);
|
|
||||||
|
|
||||||
const handleEditThread = (data: any) => {
|
|
||||||
const editedThread: ThreadSchema = {
|
|
||||||
state: "active",
|
|
||||||
threadId: data.data?._id,
|
|
||||||
creatorId: userId,
|
|
||||||
createdAt: data.data?.createdAt,
|
|
||||||
threadTitle: data.data?.threadTitle,
|
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
|
||||||
position: data.data?.position,
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
comments: data.data?.comments,
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
updateThread(data.data?._id, editedThread);
|
|
||||||
setSelectedComment(editedThread);
|
|
||||||
|
|
||||||
// setSelectedComment(null);
|
|
||||||
};
|
|
||||||
threadSocket.on("v1-thread:response:updateTitle", handleEditThread);
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
return () => {
|
|
||||||
threadSocket.off("v1-Comment:response:add", handleAddComment);
|
|
||||||
threadSocket.off("v1-Comment:response:delete", handleDeleteComment);
|
|
||||||
threadSocket.off("v1-thread:response:create", handleCreateThread);
|
|
||||||
threadSocket.off("v1-thread:response:delete", handleDeleteThread);
|
|
||||||
threadSocket.off("v1-thread:response:updateTitle", handleEditThread);
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
threadSocket,
|
|
||||||
selectedComment,
|
|
||||||
commentPositionState,
|
|
||||||
userId,
|
|
||||||
setSelectedComment,
|
|
||||||
setCommentPositionState,
|
|
||||||
threads,
|
|
||||||
modeRef.current,
|
|
||||||
messages,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThreadSocketResponsesDev;
|
|
||||||
@@ -3,38 +3,40 @@ import { useThree } from "@react-three/fiber";
|
|||||||
import { Html, TransformControls } from "@react-three/drei";
|
import { Html, TransformControls } from "@react-three/drei";
|
||||||
import { Group, Object3D, Vector3 } from "three";
|
import { Group, Object3D, Vector3 } from "three";
|
||||||
import { usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore";
|
||||||
import { useSelectedComment } from "../../../../../store/builder/store";
|
|
||||||
import CommentThreads from "../../../../../components/ui/collaboration/threads/CommentThreads";
|
import CommentThreads from "../../../../../components/ui/collaboration/threads/CommentThreads";
|
||||||
|
|
||||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||||
|
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||||
|
|
||||||
function ThreadInstance({ thread }: { readonly thread: ThreadSchema }) {
|
function ThreadInstance({ thread }: { readonly thread: ThreadSchema }) {
|
||||||
|
const { threadStore } = useSceneContext();
|
||||||
|
const { selectedThread, setSelectedThread, setPosition2Dstate } = threadStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const CommentRef = useRef(null);
|
const CommentRef = useRef(null);
|
||||||
const [selectedObject, setSelectedObject] = useState<Object3D | null>(null);
|
const [selectedObject, setSelectedObject] = useState<Object3D | null>(null);
|
||||||
const { selectedComment, setSelectedComment, setPosition2Dstate } = useSelectedComment();
|
|
||||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||||
const groupRef = useRef<Group>(null);
|
const groupRef = useRef<Group>(null);
|
||||||
const { size, camera } = useThree();
|
const { size, camera } = useThree();
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
// const keyCombination = detectModifierKeys(e);
|
const keyCombination = detectModifierKeys(e);
|
||||||
// if (!selectedComment) return;
|
if (!selectedThread) return;
|
||||||
// if (keyCombination === "G") {
|
if (keyCombination === "G") {
|
||||||
// setTransformMode((prev) => (prev === "translate" ? null : "translate"));
|
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
|
||||||
// }
|
}
|
||||||
// if (keyCombination === "R") {
|
if (keyCombination === "R") {
|
||||||
// setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
|
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// window.addEventListener("keydown", handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
// return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
// }, [selectedComment]);
|
}, [selectedThread]);
|
||||||
|
|
||||||
const commentClicked = () => {
|
const commentClicked = () => {
|
||||||
setSelectedComment(thread);
|
setSelectedThread(thread);
|
||||||
|
|
||||||
const position = new Vector3(thread.position[0], thread.position[1], thread.position[2]);
|
const position = new Vector3(thread.position[0], thread.position[1], thread.position[2]);
|
||||||
|
|
||||||
position.project(camera);
|
position.project(camera);
|
||||||
@@ -48,10 +50,10 @@ function ThreadInstance({ thread }: { readonly thread: ThreadSchema }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedComment || selectedComment.threadId !== thread.threadId) {
|
if (!selectedThread || selectedThread.threadId !== thread.threadId) {
|
||||||
setSelectedObject(null);
|
setSelectedObject(null);
|
||||||
}
|
}
|
||||||
}, [selectedComment]);
|
}, [selectedThread]);
|
||||||
|
|
||||||
if (thread.state === "inactive" || isPlaying) return null;
|
if (thread.state === "inactive" || isPlaying) return null;
|
||||||
|
|
||||||
@@ -68,10 +70,14 @@ function ThreadInstance({ thread }: { readonly thread: ThreadSchema }) {
|
|||||||
// rotation={comment.rotation}
|
// rotation={comment.rotation}
|
||||||
className="comments-main-wrapper"
|
className="comments-main-wrapper"
|
||||||
>
|
>
|
||||||
<CommentThreads commentClicked={commentClicked} thread={thread} />
|
<CommentThreads
|
||||||
|
commentClicked={commentClicked}
|
||||||
|
thread={thread}
|
||||||
|
selectedThread={selectedThread}
|
||||||
|
/>
|
||||||
</Html>
|
</Html>
|
||||||
</group>
|
</group>
|
||||||
{/* {selectedObject && transformMode && (
|
{selectedObject && transformMode && (
|
||||||
<TransformControls
|
<TransformControls
|
||||||
object={selectedObject}
|
object={selectedObject}
|
||||||
mode={transformMode}
|
mode={transformMode}
|
||||||
@@ -79,7 +85,7 @@ function ThreadInstance({ thread }: { readonly thread: ThreadSchema }) {
|
|||||||
console.log("sad");
|
console.log("sad");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)} */}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,26 +8,25 @@ import { useSceneContext } from "../../../scene/sceneContext";
|
|||||||
|
|
||||||
function ThreadInstances() {
|
function ThreadInstances() {
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { userId } = getUserData();
|
|
||||||
const { versionStore, threadStore } = useSceneContext();
|
const { versionStore, threadStore } = useSceneContext();
|
||||||
const { threads, setThreads } = threadStore();
|
const { threads, setThreads } = threadStore();
|
||||||
const { selectedVersion } = versionStore();
|
const { selectedVersion } = versionStore();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// console.log("threads", threads);
|
|
||||||
}, [threads]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!projectId || !selectedVersion) return;
|
if (!projectId || !selectedVersion) return;
|
||||||
getAllThreads(projectId, selectedVersion?.versionId)
|
getAllThreads(projectId, selectedVersion?.versionId)
|
||||||
.then((fetchedComments) => {
|
.then((fetchedComments) => {
|
||||||
const formattedThreads = Array.isArray(fetchedComments.data)
|
const formattedThreads = Array.isArray(fetchedComments?.data)
|
||||||
? fetchedComments.data.map((thread: any) => ({
|
? fetchedComments.data.map((thread: any) => ({
|
||||||
...thread,
|
...thread,
|
||||||
|
creatorId: thread.creatorId._id,
|
||||||
|
creatorName: thread.creatorId.userName,
|
||||||
comments: Array.isArray(thread.comments)
|
comments: Array.isArray(thread.comments)
|
||||||
? thread.comments.map((val: any) => ({
|
? thread.comments.map((val: any) => ({
|
||||||
replyId: val._id ?? "",
|
replyId: val._id ?? "",
|
||||||
creatorId: userId,
|
creatorId: val.userId._id,
|
||||||
|
creatorName: val.userId.userName,
|
||||||
createdAt: getRelativeTime(val.createdAt),
|
createdAt: getRelativeTime(val.createdAt),
|
||||||
lastUpdatedAt: "1 hr ago",
|
lastUpdatedAt: "1 hr ago",
|
||||||
comment: val.comment,
|
comment: val.comment,
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { useThree } from "@react-three/fiber";
|
|||||||
import { Vector3 } from "three";
|
import { Vector3 } from "three";
|
||||||
import ThreadInstances from "./threadInstances/threadInstances";
|
import ThreadInstances from "./threadInstances/threadInstances";
|
||||||
import { Sphere } from "@react-three/drei";
|
import { Sphere } from "@react-three/drei";
|
||||||
import { useActiveTool, useSelectedComment } from "../../../store/builder/store";
|
import { useActiveTool } from "../../../store/builder/store";
|
||||||
|
import { useSceneContext } from "../../scene/sceneContext";
|
||||||
|
|
||||||
function ThreadsGroup() {
|
function ThreadsGroup() {
|
||||||
const { gl, raycaster, camera, scene, pointer, size } = useThree();
|
const { gl, raycaster, camera, scene, pointer, size } = useThree();
|
||||||
const { activeTool } = useActiveTool();
|
const { activeTool } = useActiveTool();
|
||||||
const [hoverPos, setHoverPos] = useState<Vector3 | null>(null);
|
const [hoverPos, setHoverPos] = useState<Vector3 | null>(null);
|
||||||
const { setSelectedComment, setCommentPositionState, setPosition2Dstate } = useSelectedComment();
|
const { threadStore } = useSceneContext();
|
||||||
|
const { setSelectedThread, setCommentPositionState, setPosition2Dstate } = threadStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
@@ -83,8 +85,12 @@ function ThreadsGroup() {
|
|||||||
intersect.object.type !== "GridHelper"
|
intersect.object.type !== "GridHelper"
|
||||||
);
|
);
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const position = new Vector3(intersects[0].point.x, Math.max(intersects[0].point.y, 0), intersects[0].point.z);
|
const position = new Vector3(
|
||||||
setSelectedComment(null);
|
intersects[0].point.x,
|
||||||
|
Math.max(intersects[0].point.y, 0),
|
||||||
|
intersects[0].point.z
|
||||||
|
);
|
||||||
|
setSelectedThread(null);
|
||||||
setCommentPositionState({ position: position.toArray() });
|
setCommentPositionState({ position: position.toArray() });
|
||||||
|
|
||||||
position.project(camera);
|
position.project(camera);
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||||
|
|
||||||
export const addCommentsApi = async (projectId: any, comment: string, threadId: string, versionId: string) => {
|
export const addCommentsApi = async (
|
||||||
|
projectId: any,
|
||||||
|
comment: string,
|
||||||
|
threadId: string,
|
||||||
|
versionId: string,
|
||||||
|
commentId?: string
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addThread`, {
|
const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addComment`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer <access_token>",
|
Authorization: "Bearer <access_token>",
|
||||||
@@ -10,8 +16,16 @@ export const addCommentsApi = async (projectId: any, comment: string, threadId:
|
|||||||
token: localStorage.getItem("token") || "",
|
token: localStorage.getItem("token") || "",
|
||||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ projectId, comment, threadId, versionId }),
|
body: JSON.stringify({ projectId, comment, threadId, versionId, commentId }),
|
||||||
});
|
});
|
||||||
|
console.log(
|
||||||
|
"projectId, comment, threadId, versionId: ",
|
||||||
|
projectId,
|
||||||
|
comment,
|
||||||
|
threadId,
|
||||||
|
versionId,
|
||||||
|
commentId
|
||||||
|
);
|
||||||
|
|
||||||
const newAccessToken = response.headers.get("x-access-token");
|
const newAccessToken = response.headers.get("x-access-token");
|
||||||
if (newAccessToken) {
|
if (newAccessToken) {
|
||||||
@@ -23,6 +37,7 @@ export const addCommentsApi = async (projectId: any, comment: string, threadId:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
console.log("result: ", result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -17,7 +17,14 @@ export const createThreadApi = async (
|
|||||||
token: localStorage.getItem("token") || "",
|
token: localStorage.getItem("token") || "",
|
||||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ projectId, state, position, rotation, threadTitle, versionId }),
|
body: JSON.stringify({
|
||||||
|
projectId,
|
||||||
|
state,
|
||||||
|
position,
|
||||||
|
rotation,
|
||||||
|
threadTitle,
|
||||||
|
versionId,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const newAccessToken = response.headers.get("x-access-token");
|
const newAccessToken = response.headers.get("x-access-token");
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const deleteThreadApi = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
console.log('result: ', result);
|
||||||
return result;
|
return result;
|
||||||
} catch {
|
} catch {
|
||||||
echo.error("Failed to delete thread");
|
echo.error("Failed to delete thread");
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||||
|
|
||||||
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/addThread`, {
|
const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addComment`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer <access_token>",
|
Authorization: "Bearer <access_token>",
|
||||||
@@ -23,6 +29,7 @@ export const editCommentsApi = async (projectId: any, comment: string, commentId
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
console.log("result: ", result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -435,15 +435,6 @@ export const useCompareProductDataStore = create<{
|
|||||||
setCompareProductsData: (x) => set({ compareProductsData: x }),
|
setCompareProductsData: (x) => set({ compareProductsData: x }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useSelectedComment = create<any>((set: any) => ({
|
|
||||||
selectedComment: null,
|
|
||||||
setSelectedComment: (x: any) => set({ selectedComment: x }),
|
|
||||||
position2Dstate: {},
|
|
||||||
setPosition2Dstate: (x: any) => set({ position2Dstate: x }),
|
|
||||||
commentPositionState: null,
|
|
||||||
setCommentPositionState: (x: any) => set({ commentPositionState: x }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const useSelectedPath = create<any>((set: any) => ({
|
export const useSelectedPath = create<any>((set: any) => ({
|
||||||
selectedPath: "auto",
|
selectedPath: "auto",
|
||||||
setSelectedPath: (x: any) => set({ selectedPath: x }),
|
setSelectedPath: (x: any) => set({ selectedPath: x }),
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { immer } from "zustand/middleware/immer";
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
|
interface CommentPositionState {
|
||||||
|
position: [number, number, number];
|
||||||
|
}
|
||||||
|
|
||||||
interface ThreadStore {
|
interface ThreadStore {
|
||||||
threads: ThreadsSchema;
|
threads: ThreadsSchema;
|
||||||
|
selectedThread: ThreadSchema | null;
|
||||||
|
position2Dstate: {};
|
||||||
|
setSelectedThread: (thread: ThreadSchema | null) => void;
|
||||||
|
setPosition2Dstate: (thread: {}) => void;
|
||||||
|
commentPositionState: null;
|
||||||
|
setCommentPositionState: (thread: CommentPositionState | null) => void;
|
||||||
|
|
||||||
addThread: (thread: ThreadSchema) => void;
|
addThread: (thread: ThreadSchema) => void;
|
||||||
setThreads: (threads: ThreadsSchema) => void;
|
setThreads: (threads: ThreadsSchema) => void;
|
||||||
@@ -21,6 +31,9 @@ export const createThreadsStore = () => {
|
|||||||
return create<ThreadStore>()(
|
return create<ThreadStore>()(
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
threads: [],
|
threads: [],
|
||||||
|
selectedThread: null,
|
||||||
|
|
||||||
|
setSelectedThread: (thread) => set({ selectedThread: thread }),
|
||||||
|
|
||||||
addThread: (thread) => {
|
addThread: (thread) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
@@ -35,6 +48,10 @@ export const createThreadsStore = () => {
|
|||||||
state.threads = threads;
|
state.threads = threads;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
position2Dstate: {},
|
||||||
|
commentPositionState: null,
|
||||||
|
setPosition2Dstate: (x: any) => set({ position2Dstate: x }),
|
||||||
|
setCommentPositionState: (x: any) => set({ commentPositionState: x }),
|
||||||
|
|
||||||
updateThread: (threadId, updates) => {
|
updateThread: (threadId, updates) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const useSocketStore = create<SocketStore>((set, get) => ({
|
|||||||
if (get().threadSocket) return;
|
if (get().threadSocket) return;
|
||||||
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId }));
|
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId }));
|
||||||
attachLogs("Thread", threadSocket);
|
attachLogs("Thread", threadSocket);
|
||||||
set({ threadSocket });
|
set({ threadSocket })
|
||||||
},
|
},
|
||||||
|
|
||||||
initializeProjectSocket: (token, refreshToken) => {
|
initializeProjectSocket: (token, refreshToken) => {
|
||||||
|
|||||||
@@ -1503,12 +1503,24 @@
|
|||||||
&.yellow-black {
|
&.yellow-black {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
background-size: 10px 10px;
|
background-size: 10px 10px;
|
||||||
background-image: repeating-linear-gradient(45deg, #fbe50e 0, #fbe50e 2px, black 0, black 50%);
|
background-image: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
#fbe50e 0,
|
||||||
|
#fbe50e 2px,
|
||||||
|
black 0,
|
||||||
|
black 50%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
&.white-black {
|
&.white-black {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
background-size: 10px 10px;
|
background-size: 10px 10px;
|
||||||
background-image: repeating-linear-gradient(45deg, white 0, white 2px, black 0, black 50%);
|
background-image: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
white 0,
|
||||||
|
white 2px,
|
||||||
|
black 0,
|
||||||
|
black 50%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1781,6 +1793,99 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.thread-section {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-avatar {
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-username {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-kebab-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-menu {
|
||||||
|
position: absolute;
|
||||||
|
height: 100px;
|
||||||
|
width: 150px;
|
||||||
|
background-color: #222;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 8px;
|
||||||
|
right: 0;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #00b4d8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-sidebar-ui-button {
|
.toggle-sidebar-ui-button {
|
||||||
svg {
|
svg {
|
||||||
@@ -2099,7 +2204,11 @@
|
|||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
&::after {
|
&::after {
|
||||||
// @include gradient-by-child(4); // Second child uses the second color
|
// @include gradient-by-child(4); // Second child uses the second color
|
||||||
background: linear-gradient(144.19deg, rgba(197, 137, 26, 0.5) 16.62%, rgba(69, 48, 10, 0.5) 85.81%);
|
background: linear-gradient(
|
||||||
|
144.19deg,
|
||||||
|
rgba(197, 137, 26, 0.5) 16.62%,
|
||||||
|
rgba(69, 48, 10, 0.5) 85.81%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2232,7 +2341,11 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: var(--font-size-regular);
|
font-size: var(--font-size-regular);
|
||||||
background: linear-gradient(0deg, rgba(37, 24, 51, 0) 0%, rgba(52, 41, 61, 0.5) 100%);
|
background: linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(37, 24, 51, 0) 0%,
|
||||||
|
rgba(52, 41, 61, 0.5) 100%
|
||||||
|
);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
4
app/src/types/collaborationTypes.d.ts
vendored
4
app/src/types/collaborationTypes.d.ts
vendored
@@ -21,8 +21,9 @@ interface ThreadSchema {
|
|||||||
threadId: string;
|
threadId: string;
|
||||||
creatorId: string;
|
creatorId: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
creatorName: string;
|
||||||
threadTitle: string;
|
threadTitle: string;
|
||||||
lastUpdatedAt: string;
|
lastUpdatedAt?: string;
|
||||||
position: [number, number, number];
|
position: [number, number, number];
|
||||||
rotation: [number, number, number];
|
rotation: [number, number, number];
|
||||||
comments: Reply[];
|
comments: Reply[];
|
||||||
@@ -32,6 +33,7 @@ interface Reply {
|
|||||||
replyId: string;
|
replyId: string;
|
||||||
creatorId: string;
|
creatorId: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
creatorName: string;
|
||||||
lastUpdatedAt: string;
|
lastUpdatedAt: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
useDfxUpload,
|
useDfxUpload,
|
||||||
useRenameModeStore,
|
useRenameModeStore,
|
||||||
useIsComparing,
|
useIsComparing,
|
||||||
useSelectedComment,
|
|
||||||
useShortcutStore,
|
useShortcutStore,
|
||||||
useToggleView,
|
useToggleView,
|
||||||
useToolMode,
|
useToolMode,
|
||||||
@@ -24,7 +23,7 @@ import { useSceneContext } from "../../modules/scene/sceneContext";
|
|||||||
const KeyPressListener: React.FC = () => {
|
const KeyPressListener: React.FC = () => {
|
||||||
const { comparisonScene, clearComparisonState } = useSimulationState();
|
const { comparisonScene, clearComparisonState } = useSimulationState();
|
||||||
const { activeModule, setActiveModule } = useModuleStore();
|
const { activeModule, setActiveModule } = useModuleStore();
|
||||||
const { assetStore, versionStore } = useSceneContext();
|
const { assetStore, versionStore, threadStore } = useSceneContext();
|
||||||
const { selectedAssets } = assetStore();
|
const { selectedAssets } = assetStore();
|
||||||
const { setSubModule } = useSubModuleStore();
|
const { setSubModule } = useSubModuleStore();
|
||||||
const { setActiveSubTool } = useActiveSubTool();
|
const { setActiveSubTool } = useActiveSubTool();
|
||||||
@@ -43,9 +42,12 @@ const KeyPressListener: React.FC = () => {
|
|||||||
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
||||||
const { setSelectedWallAsset } = useBuilderStore();
|
const { setSelectedWallAsset } = useBuilderStore();
|
||||||
const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
|
const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
|
||||||
const { setSelectedComment } = useSelectedComment();
|
const { setSelectedThread } = threadStore();
|
||||||
const { setDfxUploaded } = useDfxUpload();
|
const { setDfxUploaded } = useDfxUpload();
|
||||||
const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element?.getAttribute("contenteditable") === "true";
|
const isTextInput = (element: Element | null): boolean =>
|
||||||
|
element instanceof HTMLInputElement ||
|
||||||
|
element instanceof HTMLTextAreaElement ||
|
||||||
|
element?.getAttribute("contenteditable") === "true";
|
||||||
|
|
||||||
const handleModuleSwitch = (keyCombination: string) => {
|
const handleModuleSwitch = (keyCombination: string) => {
|
||||||
const modules: Record<string, string> = {
|
const modules: Record<string, string> = {
|
||||||
@@ -84,7 +86,10 @@ const KeyPressListener: React.FC = () => {
|
|||||||
setToggleView(!toggleTo2D);
|
setToggleView(!toggleTo2D);
|
||||||
if (toggleTo2D) {
|
if (toggleTo2D) {
|
||||||
setSelectedWallAsset(null);
|
setSelectedWallAsset(null);
|
||||||
setToggleUI(localStorage.getItem("navBarUiLeft") !== "false", localStorage.getItem("navBarUiRight") !== "false");
|
setToggleUI(
|
||||||
|
localStorage.getItem("navBarUiLeft") !== "false",
|
||||||
|
localStorage.getItem("navBarUiRight") !== "false"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setToggleUI(false, false);
|
setToggleUI(false, false);
|
||||||
}
|
}
|
||||||
@@ -188,10 +193,15 @@ const KeyPressListener: React.FC = () => {
|
|||||||
setIsLogListVisible(false);
|
setIsLogListVisible(false);
|
||||||
setIsRenameMode(false);
|
setIsRenameMode(false);
|
||||||
setDfxUploaded([]);
|
setDfxUploaded([]);
|
||||||
setSelectedComment(null);
|
setSelectedThread(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keyCombination || ["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") return;
|
if (
|
||||||
|
!keyCombination ||
|
||||||
|
["F5", "F11", "F12"].includes(event.key) ||
|
||||||
|
keyCombination === "Ctrl+R"
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -243,7 +253,18 @@ const KeyPressListener: React.FC = () => {
|
|||||||
window.addEventListener("keydown", handleKeyPress);
|
window.addEventListener("keydown", handleKeyPress);
|
||||||
return () => window.removeEventListener("keydown", handleKeyPress);
|
return () => window.removeEventListener("keydown", handleKeyPress);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [activeModule, toggleUIRight, toggleUILeft, toggleView, showShortcuts, isPlaying, isLogListVisible, hidePlayer, isRenameMode, selectedAssets]);
|
}, [
|
||||||
|
activeModule,
|
||||||
|
toggleUIRight,
|
||||||
|
toggleUILeft,
|
||||||
|
toggleView,
|
||||||
|
showShortcuts,
|
||||||
|
isPlaying,
|
||||||
|
isLogListVisible,
|
||||||
|
hidePlayer,
|
||||||
|
isRenameMode,
|
||||||
|
selectedAssets,
|
||||||
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user