import React, { useEffect, useRef, useState } from "react"; import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons"; import Messages from "./Messages"; import { ExpandIcon } from "../../icons/SimulationIcons"; import { adjustHeight } from "./function/textAreaHeightAdjust"; import { useParams } from "react-router-dom"; import { useSelectedComment, useSocketStore } from "../../../store/builder/store"; import { useCommentStore } from "../../../store/collaboration/useCommentStore"; import { getUserData } from "../../../functions/getUserData"; import ThreadSocketResponsesDev from "../../../modules/collaboration/socket/threadSocketResponses.dev"; import { useSceneContext } from "../../../modules/scene/sceneContext"; // import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi"; // import { deleteThreadApi } from "../../../services/factoryBuilder/comments/deleteThreadApi"; // import { createThreadApi } from "../../../services/factoryBuilder/comments/createThreadApi"; // import { getRelativeTime } from "./function/getRelativeTime"; const ThreadChat: React.FC = () => { const { userId, organization } = getUserData(); const [openThreadOptions, setOpenThreadOptions] = useState(false); const [inputActive, setInputActive] = useState(false); const [value, setValue] = useState(""); const { addComment, removeReply, removeComment, addReply, comments } = useCommentStore(); const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState, position2Dstate } = useSelectedComment(); const [mode, setMode] = useState<"create" | "edit" | null>("create"); const [isEditable, setIsEditable] = useState(false); const [editedThread, setEditedThread] = useState(false); const textareaRef = useRef(null); const wrapperRef = useRef(null); const [messages, setMessages] = useState([]); const { projectId } = useParams(); const [dragging, setDragging] = useState(false); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y }); const { threadSocket } = useSocketStore(); const modeRef = useRef<"create" | "edit" | null>(null); const messagesRef = useRef(null); const { versionStore } = useSceneContext(); const { selectedVersion } = versionStore(); useEffect(() => { modeRef.current = mode; }, [mode]); useEffect(() => { if (comments.length > 0 && selectedComment) { const allMessages = comments .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); } }, []); useEffect(() => { if (textareaRef.current) adjustHeight(textareaRef.current); }, [value]); const clamp = (val: number, min: number, max: number) => { return Math.min(Math.max(val, min), max); }; const handlePointerDown = (event: React.PointerEvent) => { if (event.button !== 0) return; // Avoid dragging if a button, icon, textarea etc. was clicked const target = event.target as HTMLElement; if (target.closest("button") || target.closest(".sent-button") || target.closest("textarea") || target.closest(".options-button") || target.closest(".options-list") || target.closest(".send-message-wrapper") || target.closest(".options delete")) { return; } const wrapper = wrapperRef.current; if (!wrapper) return; const rect = wrapper.getBoundingClientRect(); const offsetX = event.clientX - rect.left; const offsetY = event.clientY - rect.top; setDragging(true); setDragOffset({ x: offsetX, y: offsetY }); wrapper.setPointerCapture(event.pointerId); }; const updatePosition = ({ clientX, clientY }: { clientX: number; clientY: number }, allowMove: boolean = true) => { if (!allowMove || !wrapperRef.current) return; const container = document.getElementById("work-space-three-d-canvas"); const wrapper = wrapperRef.current; if (!container || !wrapper) return; const containerRect = container.getBoundingClientRect(); let newX = clientX - containerRect.left - dragOffset.x; let newY = clientY - containerRect.top - dragOffset.y; const maxX = containerRect.width - wrapper.offsetWidth; const maxY = containerRect.height - wrapper.offsetHeight; newX = clamp(newX, 0, maxX); newY = clamp(newY, 0, maxY); setPosition({ x: newX, y: newY }); }; const handlePointerMove = (e: { clientX: number; clientY: number }) => { if (dragging) updatePosition(e, true); }; // Commented this useEffect to prevent offset after user saved the comment // useEffect(() => { // updatePosition({ clientX: position.x, clientY: position.y }, true); // }, [selectedComment]); const handlePointerUp = (event: React.PointerEvent) => { if (!dragging) return; setDragging(false); const wrapper = wrapperRef.current; if (wrapper) wrapper.releasePointerCapture(event.pointerId); }; const handleCreateComments = async (e: any) => { // Continue send or create message only there is only value avalibale // To prevent empty value if (!value) return; e.preventDefault(); try { // const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/ // if (createComments.message === 'Thread comments add Successfully' && createComments.data) { // const commentData = { // replyId: `${createComments.data?._id}`, // creatorId: `${selectedComment?.threadId}`, // createdAt: "2 hrs ago", // lastUpdatedAt: "2 hrs ago", // comment: value, // } // setMessages((prevMessages) => [ // ...prevMessages, // commentData, // ]); // addReply(selectedComment?.threadId, commentData) // } if (threadSocket && mode === "create") { const addComment = { versionId: selectedVersion?.versionId || "", projectId, userId, comment: value, organization, threadId: selectedComment?.threadId, }; threadSocket.emit("v1-Comment:add", addComment); } } catch {} setInputActive(false); }; const handleDeleteThread = async () => { if (!projectId) return; try { // const deleteThread = await deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "") // if (deleteThread.message === "Thread deleted Successfully") { // removeComment(selectedComment?.threadId) // setSelectedComment([]) // } if (threadSocket) { // projectId, userId, organization, threadId const deleteThread = { projectId, userId, organization, threadId: selectedComment?.threadId, versionId: selectedVersion?.versionId || "", }; setSelectedComment(null); removeComment(selectedComment?.threadId); threadSocket.emit("v1:thread:delete", deleteThread); } } catch {} }; const handleCreateThread = async (e: any) => { e.preventDefault(); if (!projectId) return; try { // const thread = await createThreadApi( // projectId, // "active", // commentPositionState[0].position, // [0, 0, 0], // value, // selectedVersion?.versionId || "" // ); // // if (thread.message === "Thread created Successfully" && thread?.threadData) { // // const comment: CommentSchema = { // state: 'active', // threadId: thread?.threadData?._id, // creatorId: userId, // createdAt: getRelativeTime(thread.threadData?.createdAt), // threadTitle: value, // lastUpdatedAt: new Date().toISOString(), // position: commentPositionState[0].position, // rotation: [0, 0, 0], // comments: [] // } // addComment(comment); // setCommentPositionState(null); // setInputActive(false); // setSelectedComment([]) // } const createThread = { projectId, versionId: selectedVersion?.versionId || "", userId, organization, state: "active", position: commentPositionState.position, rotation: [0, 0, 0], threadTitle: value, }; if (threadSocket) { setInputActive(false); threadSocket.emit("v1:thread:create", createThread); } } catch {} }; const scrollToBottom = () => { const messagesWrapper = document.querySelector(".messages-wrapper"); if (messagesWrapper) { messagesWrapper.scrollTop = messagesWrapper.scrollHeight; } }; useEffect(() => { if (messages.length > 0) scrollToBottom(); }, [messages]); return (
handlePointerMove({ clientX: e.clientX, clientY: e.clientY })} onPointerUp={handlePointerUp} style={{ position: "absolute", left: position.x, top: position.y, cursor: dragging ? "grabbing" : "grab", userSelect: "none", zIndex: 9999, }} >
Comment
{ e.preventDefault(); setOpenThreadOptions((prev) => !prev); }} >
{openThreadOptions && (
Mark as Unread
Mark as Resolved
Delete Thread
)}
{selectedComment && } {messages && messages.map((val, i) => )}