From 128c1148f8b600b50aa9baaa23bd77befdc2cbbc Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Fri, 20 Jun 2025 14:16:18 +0530 Subject: [PATCH 1/2] feat: Implement collaboration comments feature with socket integration --- .../ui/collaboration/CommentThreads.tsx | 53 +++- .../components/ui/collaboration/Messages.tsx | 173 +++++++++-- .../ui/collaboration/ThreadChat.tsx | 275 +++++++++++++++--- .../collaboration/function/getRelativeTime.ts | 27 ++ .../collaboration/comments/commentsGroup.tsx | 31 +- .../commentInstance/commentInstance.tsx | 98 ++++--- .../comments/instances/commentInstances.tsx | 48 ++- .../socket/threadSocketResponses.dev.tsx | 167 +++++++++++ app/src/pages/Project.tsx | 4 + .../factoryBuilder/comments/addCommentsApi.ts | 46 +++ .../comments/createThreadApi.ts | 34 +++ .../comments/deleteCommentApi.ts | 43 +++ .../comments/deleteThreadApi.ts | 35 +++ .../comments/editThreadTitleApi.ts | 43 +++ .../factoryBuilder/comments/getAllThreads.ts | 35 +++ app/src/store/builder/store.ts | 40 ++- .../store/collaboration/useCommentStore.ts | 155 +++++----- app/src/styles/scene/comments.scss | 37 ++- app/src/types/collaborationTypes.d.ts | 8 +- .../utils/shortcutkeys/handleShortcutKeys.ts | 4 +- 20 files changed, 1124 insertions(+), 232 deletions(-) create mode 100644 app/src/components/ui/collaboration/function/getRelativeTime.ts create mode 100644 app/src/modules/collaboration/socket/threadSocketResponses.dev.tsx create mode 100644 app/src/services/factoryBuilder/comments/addCommentsApi.ts create mode 100644 app/src/services/factoryBuilder/comments/createThreadApi.ts create mode 100644 app/src/services/factoryBuilder/comments/deleteCommentApi.ts create mode 100644 app/src/services/factoryBuilder/comments/deleteThreadApi.ts create mode 100644 app/src/services/factoryBuilder/comments/editThreadTitleApi.ts create mode 100644 app/src/services/factoryBuilder/comments/getAllThreads.ts diff --git a/app/src/components/ui/collaboration/CommentThreads.tsx b/app/src/components/ui/collaboration/CommentThreads.tsx index 0ce9fad..c06248c 100644 --- a/app/src/components/ui/collaboration/CommentThreads.tsx +++ b/app/src/components/ui/collaboration/CommentThreads.tsx @@ -1,13 +1,21 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor"; +import { getUserData } from "../../../functions/getUserData"; +import { getAllThreads } from "../../../services/factoryBuilder/comments/getAllThreads"; +import { useParams } from "react-router-dom"; +import { useCommentStore } from "../../../store/collaboration/useCommentStore"; +import { getRelativeTime } from "./function/getRelativeTime"; +import { useSelectedComment } from "../../../store/builder/store"; interface CommentThreadsProps { commentClicked: () => void; + comment?: CommentSchema } -const CommentThreads: React.FC = ({ commentClicked }) => { +const CommentThreads: React.FC = ({ commentClicked, comment }) => { const [expand, setExpand] = useState(false); const commentsedUsers = [{ creatorId: "1" }]; + const { userName } = getUserData(); const CommentDetails = { state: "active", @@ -16,26 +24,26 @@ const CommentThreads: React.FC = ({ commentClicked }) => { createdAt: "2 hours ago", comment: "Thread check", lastUpdatedAt: "string", - replies: [ + comments: [ { replyId: "string", creatorId: "string", createdAt: "string", lastUpdatedAt: "string", - reply: "string", + comment: "string", }, { replyId: "string", creatorId: "string", createdAt: "string", lastUpdatedAt: "string", - reply: "string", + comment: "string", }, ], }; function getUsername(userId: string) { - const UserName = "username"; + const UserName = userName?.charAt(0).toUpperCase() || "user"; return UserName; } @@ -48,15 +56,15 @@ const CommentThreads: React.FC = ({ commentClicked }) => { } } + return (
- {getUsername(CommentDetails.creatorId)} + {userName} + {/* {getUsername(CommentDetails.creatorId)} */}
-
{CommentDetails.createdAt}
+
{comment?.createdAt && getRelativeTime(comment.createdAt)}
-
{CommentDetails.comment}
- {CommentDetails.replies.length > 0 && ( -
- {CommentDetails.replies.length}{" "} - {CommentDetails.replies.length === 1 ? "reply" : "replies"} +
{comment?.threadTitle}
+ {comment && comment?.comments.length > 0 && ( +
+ {comment && comment?.comments.length}{" "} + {comment && comment?.comments.length === 1 ? "comment" : "replies"}
)}
+ ); }; diff --git a/app/src/components/ui/collaboration/Messages.tsx b/app/src/components/ui/collaboration/Messages.tsx index 0544b1c..6577bdd 100644 --- a/app/src/components/ui/collaboration/Messages.tsx +++ b/app/src/components/ui/collaboration/Messages.tsx @@ -2,44 +2,172 @@ import React, { useEffect, useRef, useState } from "react"; import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor"; import { KebabIcon } from "../../icons/ExportCommonIcons"; import { adjustHeight } from "./function/textAreaHeightAdjust"; +import { getUserData } from "../../../functions/getUserData"; +import { useParams } from "react-router-dom"; +import { deleteCommentApi } from "../../../services/factoryBuilder/comments/deleteCommentApi"; +import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi"; +import { useCommentStore } from "../../../store/collaboration/useCommentStore"; +import { useSelectedComment, useSocketStore } from "../../../store/builder/store"; +import { getRelativeTime } from "./function/getRelativeTime"; +import { editThreadTitleApi } from "../../../services/factoryBuilder/comments/editThreadTitleApi"; + interface MessageProps { val: Reply | CommentSchema; + // val: Reply | CommentSchema; i: number; + setMessages?: React.Dispatch> + setIsEditable?: React.Dispatch> + setEditedThread?: React.Dispatch> + setMode?: React.Dispatch> + isEditable?: boolean; + isEditableThread?: boolean + editedThread?: boolean; + mode?: 'create' | 'edit' | null } -const Messages: React.FC = ({ val, i }) => { - const [isEditing, setIsEditing] = useState(false); +const Messages: React.FC = ({ val, i, setMessages, mode, setIsEditable, setEditedThread, editedThread, isEditableThread, setMode }) => { + + const { comments, updateComment, updateReply, removeReply } = useCommentStore(); const [openOptions, setOpenOptions] = useState(false); + const { projectId } = useParams(); + const { threadSocket } = useSocketStore(); + const { userName, userId, organization } = getUserData(); + const [isEditComment, setIsEditComment] = useState(false) + + const { selectedComment, setCommentPositionState } = useSelectedComment(); // input const [value, setValue] = useState( - "reply" in val ? val.reply : val.comment + "comment" in val ? val.comment : val.threadTitle ); + const textareaRef = useRef(null); const currentUser = "1"; - const UserName = "username"; + // const UserName = "username"; useEffect(() => { if (textareaRef.current) adjustHeight(textareaRef.current); }, [value]); function handleCancelAction() { - setIsEditing(false); + setCommentPositionState(null) + setIsEditable && setIsEditable(true); + setIsEditComment(false) } - function handleSaveAction() { - setIsEditing(false); + const handleSaveAction = async () => { + + if (!projectId) return + + if (isEditableThread && editedThread) { + try { + // const editThreadTitle = await editThreadTitleApi(projectId, (val as CommentSchema).threadId, value) + // if (editThreadTitle.message == "ThreadTitle updated Successfully") { + // const editedThread: CommentSchema = { + // state: 'active', + // threadId: editThreadTitle.data.replyId, + // creatorId: userId, + // createdAt: getRelativeTime(editThreadTitle.data.createdAt), + // threadTitle: value, + // lastUpdatedAt: new Date().toISOString(), + // position: editThreadTitle.data.position, + // rotation: [0, 0, 0], + // comments: [] + // } + // updateComment((val as CommentSchema).threadId, editedThread) + // } + // projectId, userId, threadTitle, organization, threadId + const threadEdit = { + projectId, + userId, + threadTitle: value, + organization, + threadId: (val as CommentSchema).threadId + } + + threadSocket.emit('v1:thread:updateTitle', threadEdit) + } catch { + } + } else { + + if (mode === "edit") { + try { + // const editComments = await addCommentsApi(projectId, value, selectedComment?.threadId, (val as Reply).replyId) + // + // const commentData = { + // replyId: `${editComments.data?.replyId}`, + // creatorId: `${userId}`, + // createdAt: getRelativeTime(editComments.data?.createdAt), + // lastUpdatedAt: "2 hrs ago", + // comment: value, + // } + + // updateReply((val as CommentSchema).threadId, (val as Reply)?.replyId, commentData); + + if (threadSocket) { + // projectId, userId, comment, organization, threadId + const editComment = { + projectId, + userId, + comment: value, + organization, + threadId: selectedComment?.threadId, + commentId: (val as Reply).replyId ?? "" + } + + + threadSocket.emit("v1-Comment:add", editComment); + setIsEditable && setIsEditable(true); + setEditedThread && setEditedThread(false) + } + + } catch { + } + } + + } + // setValue(""); + setIsEditComment(false); } - function handleDeleteAction() { + const handleDeleteAction = async (replyID: any) => { + if (!projectId) return setOpenOptions(false); + try { + // const deletedComment = await deleteCommentApi(projectId, selectedComment?.threadId, (val as Reply).replyId) + // + // if (deletedComment === "'Thread comment deleted Successfully'") { + // setMessages && setMessages(prev => prev.filter(message => message.replyId !== replyID)); + // removeReply(val.creatorId, replyID) + // } + if (threadSocket && setMessages) { + + + // projectId, userId, commentId, organization, threadId + const deleteComment = { + projectId, + userId, + commentId: (val as Reply).replyId, + organization, + threadId: selectedComment?.threadId + } + + + setMessages(prev => prev.filter(message => message.replyId !== (val as Reply).replyId)) + + removeReply(selectedComment?.threadId, (val as Reply).replyId); // Remove listener after response + threadSocket.emit("v1-Comment:delete", deleteComment); + } + } catch { + + } } return ( <> - {isEditing ? ( + {isEditComment ? (