threads api refactor
This commit is contained in:
@@ -26,7 +26,7 @@ import RegularDropDown from "../../ui/inputs/RegularDropDown";
|
|||||||
import RenameTooltip from "../../ui/features/RenameTooltip";
|
import RenameTooltip from "../../ui/features/RenameTooltip";
|
||||||
import VersionSaved from "../sidebarRight/versionHisory/VersionSaved";
|
import VersionSaved from "../sidebarRight/versionHisory/VersionSaved";
|
||||||
import Footer from "../../footer/Footer";
|
import Footer from "../../footer/Footer";
|
||||||
import ThreadChat from "../../ui/collaboration/ThreadChat";
|
import ThreadChat from "../../ui/collaboration/threads/ThreadChat";
|
||||||
import Scene from "../../../modules/scene/scene";
|
import Scene from "../../../modules/scene/scene";
|
||||||
|
|
||||||
import { getUserData } from "../../../functions/getUserData";
|
import { getUserData } from "../../../functions/getUserData";
|
||||||
@@ -179,7 +179,9 @@ function MainScene() {
|
|||||||
{isPlaying && activeModule === "simulation" && loadingProgress === 0 && <SimulationPlayer />}
|
{isPlaying && activeModule === "simulation" && loadingProgress === 0 && <SimulationPlayer />}
|
||||||
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />}
|
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />}
|
||||||
|
|
||||||
{isRenameMode && (selectedFloorAsset?.userData.modelName || selectedAssets.length === 1) && <RenameTooltip name={selectedFloorAsset?.userData.modelName || selectedAssets[0].userData.modelName} onSubmit={handleObjectRename} />}
|
{isRenameMode && (selectedFloorAsset?.userData.modelName || selectedAssets.length === 1) && (
|
||||||
|
<RenameTooltip name={selectedFloorAsset?.userData.modelName || selectedAssets[0].userData.modelName} onSubmit={handleObjectRename} />
|
||||||
|
)}
|
||||||
{/* remove this later */}
|
{/* remove this later */}
|
||||||
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
|
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
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<CommentThreadsProps> = ({ commentClicked, comment }) => {
|
|
||||||
const [expand, setExpand] = useState(false);
|
|
||||||
const commentsedUsers = [{ creatorId: "1" }];
|
|
||||||
const { userName } = getUserData();
|
|
||||||
|
|
||||||
const CommentDetails = {
|
|
||||||
state: "active",
|
|
||||||
commentId: "c-1",
|
|
||||||
creatorId: "12",
|
|
||||||
createdAt: "2 hours ago",
|
|
||||||
comment: "Thread check",
|
|
||||||
lastUpdatedAt: "string",
|
|
||||||
comments: [
|
|
||||||
{
|
|
||||||
replyId: "string",
|
|
||||||
creatorId: "string",
|
|
||||||
createdAt: "string",
|
|
||||||
lastUpdatedAt: "string",
|
|
||||||
comment: "string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
replyId: "string",
|
|
||||||
creatorId: "string",
|
|
||||||
createdAt: "string",
|
|
||||||
lastUpdatedAt: "string",
|
|
||||||
comment: "string",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
function getUsername(userId: string) {
|
|
||||||
const UserName = userName?.charAt(0).toUpperCase() || "user";
|
|
||||||
return UserName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDetails(type?: "clicked") {
|
|
||||||
if (type === "clicked") {
|
|
||||||
setExpand(true);
|
|
||||||
commentClicked();
|
|
||||||
} else {
|
|
||||||
setExpand((prev) => !prev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="comments-threads-wrapper">
|
|
||||||
<button
|
|
||||||
onPointerEnter={() => getDetails()}
|
|
||||||
onPointerLeave={() => getDetails()}
|
|
||||||
onClick={() => getDetails("clicked")}
|
|
||||||
className={`comments-threads-container ${expand ? "open" : "closed"
|
|
||||||
} unread`}
|
|
||||||
>
|
|
||||||
<div className="users-commented">
|
|
||||||
{commentsedUsers.map((val, i) => (
|
|
||||||
<div
|
|
||||||
className="users"
|
|
||||||
key={val.creatorId}
|
|
||||||
style={{
|
|
||||||
background: getAvatarColor(i, getUsername(val.creatorId)),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getUsername(val.creatorId)[0]}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{/* {commentsedUsers.map((val, i) => (
|
|
||||||
<div
|
|
||||||
className="users"
|
|
||||||
key={val.creatorId}
|
|
||||||
style={{
|
|
||||||
background: getAvatarColor(i, getUsername(val.creatorId)),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getUsername(val.creatorId)[0]}
|
|
||||||
</div>
|
|
||||||
))} */}
|
|
||||||
</div>
|
|
||||||
<div className={`last-comment-details ${expand ? "expand" : ""}`}>
|
|
||||||
<div className="header">
|
|
||||||
<div className="user-name">
|
|
||||||
{userName}
|
|
||||||
{/* {getUsername(CommentDetails.creatorId)} */}
|
|
||||||
</div>
|
|
||||||
<div className="time">{comment?.createdAt && getRelativeTime(comment.createdAt)}</div>
|
|
||||||
</div>
|
|
||||||
<div className="message">{comment?.threadTitle}</div>
|
|
||||||
{comment && comment?.comments.length > 0 && (
|
|
||||||
<div className="comments">
|
|
||||||
{comment && comment?.comments.length}{" "}
|
|
||||||
{comment && comment?.comments.length === 1 ? "comment" : "replies"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CommentThreads;
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { getAvatarColor } from "../../../../modules/collaboration/functions/getAvatarColor";
|
||||||
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
|
import { getRelativeTime } from "../function/getRelativeTime";
|
||||||
|
|
||||||
|
interface CommentThreadsProps {
|
||||||
|
commentClicked: () => void;
|
||||||
|
comment?: CommentSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked, comment }) => {
|
||||||
|
const [expand, setExpand] = useState(false);
|
||||||
|
const commentsedUsers = [{ creatorId: "1" }];
|
||||||
|
const { userName } = getUserData();
|
||||||
|
|
||||||
|
function getUsername(userId: string) {
|
||||||
|
const UserName = userName?.charAt(0).toUpperCase() || "user";
|
||||||
|
return UserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetails(type?: "clicked") {
|
||||||
|
if (type === "clicked") {
|
||||||
|
setExpand(true);
|
||||||
|
commentClicked();
|
||||||
|
} else {
|
||||||
|
setExpand((prev) => !prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="comments-threads-wrapper">
|
||||||
|
<button
|
||||||
|
onPointerEnter={() => getDetails()}
|
||||||
|
onPointerLeave={() => getDetails()}
|
||||||
|
onClick={() => getDetails("clicked")}
|
||||||
|
className={`comments-threads-container ${expand ? "open" : "closed"} unread`}
|
||||||
|
>
|
||||||
|
<div className="users-commented">
|
||||||
|
{commentsedUsers.map((val, i) => (
|
||||||
|
<div
|
||||||
|
className="users"
|
||||||
|
key={val.creatorId}
|
||||||
|
style={{
|
||||||
|
background: getAvatarColor(i, getUsername(val.creatorId)),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getUsername(val.creatorId)[0]}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className={`last-comment-details ${expand ? "expand" : ""}`}>
|
||||||
|
<div className="header">
|
||||||
|
<div className="user-name">{userName}</div>
|
||||||
|
<div className="time">{comment?.createdAt && getRelativeTime(comment.createdAt)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="message">{comment?.threadTitle}</div>
|
||||||
|
{comment && comment?.comments.length > 0 && (
|
||||||
|
<div className="comments">
|
||||||
|
{comment?.comments.length} {comment?.comments.length === 1 ? "comment" : "replies"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommentThreads;
|
||||||
@@ -1,254 +1,259 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
import { getAvatarColor } from "../../../../modules/collaboration/functions/getAvatarColor";
|
||||||
import { KebabIcon } from "../../icons/ExportCommonIcons";
|
import { KebabIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import { adjustHeight } from "./function/textAreaHeightAdjust";
|
import { adjustHeight } from "../function/textAreaHeightAdjust";
|
||||||
import { getUserData } from "../../../functions/getUserData";
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
|
import { useSelectedComment, useSocketStore } from "../../../../store/builder/store";
|
||||||
import { useSelectedComment, useSocketStore } from "../../../store/builder/store";
|
import { getRelativeTime } from "../function/getRelativeTime";
|
||||||
import { getRelativeTime } from "./function/getRelativeTime";
|
import { editThreadTitleApi } from "../../../../services/factoryBuilder/comments/editThreadTitleApi";
|
||||||
import { editThreadTitleApi } from "../../../services/factoryBuilder/comments/editThreadTitleApi";
|
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
|
||||||
|
import { deleteCommentApi } from "../../../../services/factoryBuilder/comments/deleteCommentApi";
|
||||||
// import { deleteCommentApi } from "../../../services/factoryBuilder/comments/deleteCommentApi";
|
import { addCommentsApi } from "../../../../services/factoryBuilder/comments/addCommentApi";
|
||||||
// import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi";
|
import { editCommentsApi } from "../../../../services/factoryBuilder/comments/editCommentApi";
|
||||||
|
|
||||||
interface MessageProps {
|
interface MessageProps {
|
||||||
val: Reply | CommentSchema;
|
val: Reply | CommentSchema;
|
||||||
i: number;
|
i: number;
|
||||||
setMessages?: React.Dispatch<React.SetStateAction<Reply[]>>;
|
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>>;
|
||||||
isEditable?: boolean;
|
isEditable?: boolean;
|
||||||
isEditableThread?: boolean;
|
isEditableThread?: boolean;
|
||||||
editedThread?: boolean;
|
editedThread?: boolean;
|
||||||
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, i, setMessages, mode, setIsEditable, setEditedThread, editedThread, isEditableThread, setMode }) => {
|
||||||
const { updateComment, removeReply, updateReply } = useCommentStore();
|
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 { userName, userId, organization } = getUserData();
|
const [isEditComment, setIsEditComment] = useState(false);
|
||||||
const [isEditComment, setIsEditComment] = useState(false);
|
const { selectedComment, setCommentPositionState } = useSelectedComment();
|
||||||
const { selectedComment, setCommentPositionState } = useSelectedComment();
|
const { versionStore, commentStore } = useSceneContext();
|
||||||
const { versionStore } = useSceneContext();
|
const { updateComment, removeReply, updateReply } = commentStore();
|
||||||
const { selectedVersion } = versionStore();
|
const { selectedVersion } = versionStore();
|
||||||
|
|
||||||
const [value, setValue] = useState<string>("comment" in val ? val.comment : val.threadTitle);
|
const [value, setValue] = useState<string>("comment" in val ? val.comment : val.threadTitle);
|
||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textareaRef.current) adjustHeight(textareaRef.current);
|
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
function handleCancelAction() {
|
function handleCancelAction() {
|
||||||
setCommentPositionState(null);
|
setCommentPositionState(null);
|
||||||
if (setIsEditable) setIsEditable(true);
|
if (setIsEditable) setIsEditable(true);
|
||||||
setIsEditComment(false);
|
setIsEditComment(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveAction = async () => {
|
const handleSaveAction = async () => {
|
||||||
if (!projectId || !selectedVersion) return;
|
if (!projectId || !selectedVersion || !setIsEditable || !setEditedThread) return;
|
||||||
|
|
||||||
if (isEditableThread && editedThread) {
|
if (isEditableThread && editedThread) {
|
||||||
try {
|
if (!threadSocket?.active) {
|
||||||
if (!threadSocket?.active) {
|
// API
|
||||||
const editThreadTitle = await editThreadTitleApi(projectId, (val as CommentSchema).threadId, value, selectedVersion?.versionId || "");
|
|
||||||
if (editThreadTitle.message == "ThreadTitle updated Successfully") {
|
editThreadTitleApi(projectId, (val as CommentSchema).threadId, value, selectedVersion?.versionId || "").then((editThreadTitle) => {
|
||||||
const editedThread: CommentSchema = {
|
if (editThreadTitle.message == "ThreadTitle updated Successfully") {
|
||||||
state: "active",
|
const editedThread: CommentSchema = {
|
||||||
threadId: editThreadTitle.data.replyId,
|
state: "active",
|
||||||
creatorId: userId,
|
threadId: editThreadTitle.data.replyId,
|
||||||
createdAt: getRelativeTime(editThreadTitle.data.createdAt),
|
creatorId: userId,
|
||||||
threadTitle: value,
|
createdAt: getRelativeTime(editThreadTitle.data.createdAt),
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
threadTitle: value,
|
||||||
position: editThreadTitle.data.position,
|
lastUpdatedAt: new Date().toISOString(),
|
||||||
rotation: [0, 0, 0],
|
position: editThreadTitle.data.position,
|
||||||
comments: [],
|
rotation: [0, 0, 0],
|
||||||
};
|
comments: [],
|
||||||
updateComment((val as CommentSchema).threadId, editedThread);
|
};
|
||||||
}
|
updateComment((val as CommentSchema).threadId, editedThread);
|
||||||
} else {
|
}
|
||||||
const threadEdit = {
|
});
|
||||||
projectId,
|
} else {
|
||||||
userId,
|
// SOCKET
|
||||||
threadTitle: value,
|
|
||||||
organization,
|
const threadEdit = {
|
||||||
threadId: (val as CommentSchema).threadId || selectedComment.threadId,
|
projectId,
|
||||||
versionId: selectedVersion?.versionId || "",
|
userId,
|
||||||
};
|
threadTitle: value,
|
||||||
|
organization,
|
||||||
threadSocket.emit("v1:thread:updateTitle", threadEdit);
|
threadId: (val as CommentSchema).threadId || selectedComment.threadId,
|
||||||
}
|
versionId: selectedVersion?.versionId || "",
|
||||||
} catch {}
|
};
|
||||||
} else {
|
|
||||||
if (mode === "edit") {
|
threadSocket.emit("v1:thread:updateTitle", threadEdit);
|
||||||
try {
|
}
|
||||||
// const editComments = await addCommentsApi(projectId, value, selectedComment?.threadId, (val as Reply).replyId, selectedVersion?.versionId || "")
|
} else if (mode === "edit") {
|
||||||
//
|
if (!threadSocket?.active) {
|
||||||
// const commentData = {
|
// API
|
||||||
// replyId: `${editComments.data?.replyId}`,
|
|
||||||
// creatorId: `${userId}`,
|
editCommentsApi(projectId, value, selectedComment?.threadId, (val as Reply).replyId, selectedVersion?.versionId || "").then((editComments) => {
|
||||||
// createdAt: getRelativeTime(editComments.data?.createdAt),
|
const commentData = {
|
||||||
// lastUpdatedAt: "2 hrs ago",
|
replyId: `${editComments.data?.replyId}`,
|
||||||
// comment: value,
|
creatorId: `${userId}`,
|
||||||
// }
|
createdAt: getRelativeTime(editComments.data?.createdAt),
|
||||||
|
lastUpdatedAt: "2 hrs ago",
|
||||||
// updateReply((val as CommentSchema).threadId, (val as Reply)?.replyId, commentData);
|
comment: value,
|
||||||
|
};
|
||||||
if (threadSocket) {
|
|
||||||
// projectId, userId, comment, organization, threadId
|
updateReply((val as CommentSchema).threadId, (val as Reply)?.replyId, commentData);
|
||||||
const editComment = {
|
setIsEditable(true);
|
||||||
projectId,
|
setEditedThread(false);
|
||||||
userId,
|
});
|
||||||
comment: value,
|
} else {
|
||||||
organization,
|
// SOCKET
|
||||||
threadId: selectedComment?.threadId,
|
|
||||||
commentId: (val as Reply).replyId ?? "",
|
const editComment = {
|
||||||
versionId: selectedVersion?.versionId || "",
|
projectId,
|
||||||
};
|
userId,
|
||||||
|
comment: value,
|
||||||
threadSocket.emit("v1-Comment:add", editComment);
|
organization,
|
||||||
setIsEditable && setIsEditable(true);
|
threadId: selectedComment?.threadId,
|
||||||
setEditedThread && setEditedThread(false);
|
commentId: (val as Reply).replyId ?? "",
|
||||||
}
|
versionId: selectedVersion?.versionId || "",
|
||||||
} catch {}
|
};
|
||||||
}
|
|
||||||
}
|
threadSocket.emit("v1-Comment:add", editComment);
|
||||||
// setValue("");
|
setIsEditable(true);
|
||||||
setIsEditComment(false);
|
setEditedThread(false);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
const handleDeleteAction = async (replyID: any) => {
|
setIsEditComment(false);
|
||||||
if (!projectId || !selectedVersion) return;
|
};
|
||||||
setOpenOptions(false);
|
|
||||||
try {
|
const handleDeleteAction = async (replyID: any) => {
|
||||||
// const deletedComment = await deleteCommentApi(projectId, selectedComment?.threadId, (val as Reply).replyId , selectedVersion?.versionId || "")
|
if (!projectId || !selectedVersion || !setMessages) return;
|
||||||
//
|
setOpenOptions(false);
|
||||||
// if (deletedComment === "'Thread comment deleted Successfully'") {
|
|
||||||
// setMessages && setMessages(prev => prev.filter(message => message.replyId !== replyID));
|
if (!threadSocket?.active) {
|
||||||
// removeReply(val.creatorId, replyID)
|
// API
|
||||||
// }
|
|
||||||
if (threadSocket && setMessages) {
|
deleteCommentApi(projectId, selectedComment?.threadId, (val as Reply).replyId, selectedVersion?.versionId || "").then((deletedComment) => {
|
||||||
// projectId, userId, commentId, organization, threadId
|
if (deletedComment === "'Thread comment deleted Successfully'") {
|
||||||
const deleteComment = {
|
setMessages((prev) => prev.filter((message) => message.replyId !== replyID));
|
||||||
projectId,
|
removeReply(val.creatorId, replyID);
|
||||||
userId,
|
}
|
||||||
commentId: (val as Reply).replyId,
|
});
|
||||||
organization,
|
} else {
|
||||||
threadId: selectedComment?.threadId,
|
// SOCKET
|
||||||
versionId: selectedVersion?.versionId || "",
|
|
||||||
};
|
const deleteComment = {
|
||||||
setMessages((prev) => {
|
projectId,
|
||||||
// 👈 logs the current state
|
userId,
|
||||||
return prev.filter((message) => message.replyId !== (val as Reply).replyId);
|
commentId: (val as Reply).replyId,
|
||||||
});
|
organization,
|
||||||
removeReply(selectedComment?.threadId, (val as Reply).replyId); // Remove listener after response
|
threadId: selectedComment?.threadId,
|
||||||
|
versionId: selectedVersion?.versionId || "",
|
||||||
threadSocket.emit("v1-Comment:delete", deleteComment);
|
};
|
||||||
}
|
setMessages((prev) => {
|
||||||
} catch {}
|
return prev.filter((message) => message.replyId !== (val as Reply).replyId);
|
||||||
};
|
});
|
||||||
|
removeReply(selectedComment?.threadId, (val as Reply).replyId);
|
||||||
const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
|
||||||
requestAnimationFrame(() => {
|
threadSocket.emit("v1-Comment:delete", deleteComment);
|
||||||
if (textareaRef.current) {
|
}
|
||||||
const length = textareaRef.current.value.length;
|
};
|
||||||
textareaRef.current.setSelectionRange(length, length);
|
|
||||||
}
|
const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
});
|
requestAnimationFrame(() => {
|
||||||
};
|
if (textareaRef.current) {
|
||||||
|
const length = textareaRef.current.value.length;
|
||||||
return (
|
textareaRef.current.setSelectionRange(length, length);
|
||||||
<>
|
}
|
||||||
{isEditComment ? (
|
});
|
||||||
<div className="edit-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} />
|
return (
|
||||||
</div>
|
<>
|
||||||
<div className="actions-container">
|
{isEditComment ? (
|
||||||
<div className="options"></div>
|
<div className="edit-container">
|
||||||
<div className="actions">
|
<div className="input-container">
|
||||||
<button
|
<textarea placeholder="type here" ref={textareaRef} autoFocus value={value} onChange={(e) => setValue(e.target.value)} style={{ resize: "none" }} onFocus={handleFocus} />
|
||||||
className="cancel-button"
|
</div>
|
||||||
onClick={() => {
|
<div className="actions-container">
|
||||||
handleCancelAction();
|
<div className="options"></div>
|
||||||
}}
|
<div className="actions">
|
||||||
>
|
<button
|
||||||
Cancel
|
className="cancel-button"
|
||||||
</button>
|
onClick={() => {
|
||||||
<button
|
handleCancelAction();
|
||||||
className="save-button"
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
handleSaveAction();
|
Cancel
|
||||||
}}
|
</button>
|
||||||
>
|
<button
|
||||||
Save
|
className="save-button"
|
||||||
</button>
|
onClick={() => {
|
||||||
</div>
|
handleSaveAction();
|
||||||
</div>
|
}}
|
||||||
</div>
|
>
|
||||||
) : (
|
Save
|
||||||
<div className="message-container">
|
</button>
|
||||||
<div className="profile" style={{ background: getAvatarColor(i, userName) }}>
|
</div>
|
||||||
{userName?.charAt(0).toUpperCase() || "user"}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="content">
|
) : (
|
||||||
<div className="user-details">
|
<div className="message-container">
|
||||||
<div className="user-name">{userName}</div>
|
<div className="profile" style={{ background: getAvatarColor(i, userName) }}>
|
||||||
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
|
{userName?.charAt(0).toUpperCase() || "user"}
|
||||||
</div>
|
</div>
|
||||||
{(val as Reply).creatorId === userId && (
|
<div className="content">
|
||||||
<div className="more-options" onMouseLeave={() => setOpenOptions(false)}>
|
<div className="user-details">
|
||||||
<button
|
<div className="user-name">{userName}</div>
|
||||||
className="more-options-button"
|
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
|
||||||
onClick={() => {
|
</div>
|
||||||
setOpenOptions(!openOptions);
|
{(val as Reply).creatorId === userId && (
|
||||||
}}
|
<div className="more-options" onMouseLeave={() => setOpenOptions(false)}>
|
||||||
>
|
<button
|
||||||
<KebabIcon />
|
className="more-options-button"
|
||||||
</button>
|
onClick={() => {
|
||||||
|
setOpenOptions(!openOptions);
|
||||||
{openOptions && (
|
}}
|
||||||
<div className="options-list">
|
>
|
||||||
<button
|
<KebabIcon />
|
||||||
className="option"
|
</button>
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
{openOptions && (
|
||||||
setMode && setMode("edit");
|
<div className="options-list">
|
||||||
setOpenOptions(false);
|
<button
|
||||||
setEditedThread && setEditedThread(true);
|
className="option"
|
||||||
setIsEditComment(true);
|
onClick={(e) => {
|
||||||
}}
|
e.preventDefault();
|
||||||
>
|
setMode && setMode("edit");
|
||||||
Edit
|
setOpenOptions(false);
|
||||||
</button>
|
setEditedThread && setEditedThread(true);
|
||||||
|
setIsEditComment(true);
|
||||||
{!isEditableThread && (
|
}}
|
||||||
<button
|
>
|
||||||
className="option"
|
Edit
|
||||||
onClick={() => {
|
</button>
|
||||||
handleDeleteAction((val as Reply).replyId);
|
|
||||||
}}
|
{!isEditableThread && (
|
||||||
>
|
<button
|
||||||
Delete
|
className="option"
|
||||||
</button>
|
onClick={() => {
|
||||||
)}
|
handleDeleteAction((val as Reply).replyId);
|
||||||
</div>
|
}}
|
||||||
)}
|
>
|
||||||
</div>
|
Delete
|
||||||
)}
|
</button>
|
||||||
|
)}
|
||||||
<div className="message">{"comment" in val ? val.comment : val.threadTitle}</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
);
|
<div className="message">{"comment" in val ? val.comment : val.threadTitle}</div>
|
||||||
};
|
</div>
|
||||||
|
</div>
|
||||||
export default Messages;
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Messages;
|
||||||
@@ -1,387 +1,382 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons";
|
import { CloseIcon, KebabIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import Messages from "./Messages";
|
import Messages from "./Messages";
|
||||||
import { ExpandIcon } from "../../icons/SimulationIcons";
|
import { ExpandIcon } from "../../../icons/SimulationIcons";
|
||||||
import { adjustHeight } from "./function/textAreaHeightAdjust";
|
import { adjustHeight } from "../function/textAreaHeightAdjust";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useSelectedComment, useSocketStore } from "../../../store/builder/store";
|
import { useSelectedComment, useSocketStore } from "../../../../store/builder/store";
|
||||||
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
import { getUserData } from "../../../functions/getUserData";
|
import ThreadSocketResponsesDev from "../../../../modules/collaboration/socket/threadSocketResponses.dev";
|
||||||
import ThreadSocketResponsesDev from "../../../modules/collaboration/socket/threadSocketResponses.dev";
|
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
|
||||||
|
import { addCommentsApi } from "../../../../services/factoryBuilder/comments/addCommentApi";
|
||||||
// import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi";
|
import { deleteThreadApi } from "../../../../services/factoryBuilder/comments/deleteThreadApi";
|
||||||
// import { deleteThreadApi } from "../../../services/factoryBuilder/comments/deleteThreadApi";
|
import { createThreadApi } from "../../../../services/factoryBuilder/comments/createThreadApi";
|
||||||
// import { createThreadApi } from "../../../services/factoryBuilder/comments/createThreadApi";
|
import { getRelativeTime } from "../function/getRelativeTime";
|
||||||
// import { getRelativeTime } from "./function/getRelativeTime";
|
|
||||||
|
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 { addComment, removeReply, removeComment, addReply, comments } = useCommentStore();
|
const [mode, setMode] = useState<"create" | "edit" | null>("create");
|
||||||
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState, position2Dstate } = useSelectedComment();
|
const [isEditable, setIsEditable] = useState(false);
|
||||||
const [mode, setMode] = useState<"create" | "edit" | null>("create");
|
const [editedThread, setEditedThread] = useState(false);
|
||||||
const [isEditable, setIsEditable] = useState(false);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const [editedThread, setEditedThread] = useState(false);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const [messages, setMessages] = useState<Reply[]>([]);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const { projectId } = useParams();
|
||||||
const [messages, setMessages] = useState<Reply[]>([]);
|
const [dragging, setDragging] = useState(false);
|
||||||
const { projectId } = useParams();
|
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||||||
const [dragging, setDragging] = useState(false);
|
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
||||||
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
const { threadSocket } = useSocketStore();
|
||||||
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
const modeRef = useRef<"create" | "edit" | null>(null);
|
||||||
const { threadSocket } = useSocketStore();
|
const messagesRef = useRef<HTMLDivElement>(null);
|
||||||
const modeRef = useRef<"create" | "edit" | null>(null);
|
const { versionStore, commentStore } = useSceneContext();
|
||||||
const messagesRef = useRef<HTMLDivElement>(null);
|
const { selectedVersion } = versionStore();
|
||||||
const { versionStore } = useSceneContext();
|
const { addComment, removeComment, addReply, comments } = commentStore();
|
||||||
const { selectedVersion } = versionStore();
|
|
||||||
|
useEffect(() => {
|
||||||
useEffect(() => {
|
modeRef.current = mode;
|
||||||
modeRef.current = mode;
|
}, [mode]);
|
||||||
}, [mode]);
|
|
||||||
|
useEffect(() => {
|
||||||
useEffect(() => {
|
if (comments.length > 0 && selectedComment) {
|
||||||
if (comments.length > 0 && selectedComment) {
|
const allMessages = comments
|
||||||
const allMessages = comments
|
.flatMap((val: any) => (val?.threadId === selectedComment?.threadId ? val.comments : []))
|
||||||
.flatMap((val: any) => (val?.threadId === selectedComment?.threadId ? val.comments : []))
|
.map((c) => {
|
||||||
.map((c) => {
|
return {
|
||||||
return {
|
replyId: c._id ?? c.replyId,
|
||||||
replyId: c._id ?? c.replyId,
|
creatorId: c.creatorId || c.userId,
|
||||||
creatorId: c.creatorId || c.userId,
|
createdAt: c.createdAt,
|
||||||
createdAt: c.createdAt,
|
lastUpdatedAt: "1 hr ago",
|
||||||
lastUpdatedAt: "1 hr ago",
|
comment: c.comment,
|
||||||
comment: c.comment,
|
_id: c._id ?? c.replyId,
|
||||||
_id: c._id ?? c.replyId,
|
};
|
||||||
};
|
});
|
||||||
});
|
|
||||||
|
setMessages(allMessages);
|
||||||
setMessages(allMessages);
|
}
|
||||||
}
|
}, []);
|
||||||
}, []);
|
|
||||||
|
useEffect(() => {
|
||||||
useEffect(() => {
|
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||||||
if (textareaRef.current) adjustHeight(textareaRef.current);
|
}, [value]);
|
||||||
}, [value]);
|
|
||||||
|
const clamp = (val: number, min: number, max: number) => {
|
||||||
const clamp = (val: number, min: number, max: number) => {
|
return Math.min(Math.max(val, min), max);
|
||||||
return Math.min(Math.max(val, min), max);
|
};
|
||||||
};
|
|
||||||
|
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
if (event.button !== 0) return;
|
||||||
if (event.button !== 0) return;
|
const target = event.target as HTMLElement;
|
||||||
// Avoid dragging if a button, icon, textarea etc. was clicked
|
if (
|
||||||
const target = event.target as HTMLElement;
|
target.closest("button") ||
|
||||||
if (
|
target.closest(".sent-button") ||
|
||||||
target.closest("button") ||
|
target.closest("textarea") ||
|
||||||
target.closest(".sent-button") ||
|
target.closest(".options-button") ||
|
||||||
target.closest("textarea") ||
|
target.closest(".options-list") ||
|
||||||
target.closest(".options-button") ||
|
target.closest(".send-message-wrapper") ||
|
||||||
target.closest(".options-list") ||
|
target.closest(".options delete")
|
||||||
target.closest(".send-message-wrapper") ||
|
) {
|
||||||
target.closest(".options delete")
|
return;
|
||||||
) {
|
}
|
||||||
return;
|
|
||||||
}
|
const wrapper = wrapperRef.current;
|
||||||
|
if (!wrapper) return;
|
||||||
const wrapper = wrapperRef.current;
|
|
||||||
if (!wrapper) return;
|
const rect = wrapper.getBoundingClientRect();
|
||||||
|
const offsetX = event.clientX - rect.left;
|
||||||
const rect = wrapper.getBoundingClientRect();
|
const offsetY = event.clientY - rect.top;
|
||||||
const offsetX = event.clientX - rect.left;
|
|
||||||
const offsetY = event.clientY - rect.top;
|
setDragging(true);
|
||||||
|
setDragOffset({ x: offsetX, y: offsetY });
|
||||||
setDragging(true);
|
|
||||||
setDragOffset({ x: offsetX, y: offsetY });
|
wrapper.setPointerCapture(event.pointerId);
|
||||||
|
};
|
||||||
wrapper.setPointerCapture(event.pointerId);
|
|
||||||
};
|
const updatePosition = ({ clientX, clientY }: { clientX: number; clientY: number }, allowMove: boolean = true) => {
|
||||||
|
if (!allowMove || !wrapperRef.current) return;
|
||||||
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;
|
||||||
const container = document.getElementById("work-space-three-d-canvas");
|
if (!container || !wrapper) return;
|
||||||
const wrapper = wrapperRef.current;
|
|
||||||
if (!container || !wrapper) return;
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
|
||||||
const containerRect = container.getBoundingClientRect();
|
let newX = clientX - containerRect.left - dragOffset.x;
|
||||||
|
let newY = clientY - containerRect.top - dragOffset.y;
|
||||||
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;
|
||||||
const maxX = containerRect.width - wrapper.offsetWidth;
|
|
||||||
const maxY = containerRect.height - wrapper.offsetHeight;
|
newX = clamp(newX, 0, maxX);
|
||||||
|
newY = clamp(newY, 0, maxY);
|
||||||
newX = clamp(newX, 0, maxX);
|
|
||||||
newY = clamp(newY, 0, maxY);
|
setPosition({ x: newX, y: newY });
|
||||||
|
};
|
||||||
setPosition({ x: newX, y: newY });
|
|
||||||
};
|
const handlePointerMove = (e: { clientX: number; clientY: number }) => {
|
||||||
|
if (dragging) updatePosition(e, true);
|
||||||
const handlePointerMove = (e: { clientX: number; clientY: number }) => {
|
};
|
||||||
if (dragging) updatePosition(e, true);
|
|
||||||
};
|
// Commented this useEffect to prevent offset after user saved the comment
|
||||||
|
// useEffect(() => {
|
||||||
// Commented this useEffect to prevent offset after user saved the comment
|
// updatePosition({ clientX: position.x, clientY: position.y }, true);
|
||||||
// useEffect(() => {
|
// }, [selectedComment]);
|
||||||
// updatePosition({ clientX: position.x, clientY: position.y }, true);
|
|
||||||
// }, [selectedComment]);
|
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
|
if (!dragging) return;
|
||||||
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
setDragging(false);
|
||||||
if (!dragging) return;
|
const wrapper = wrapperRef.current;
|
||||||
setDragging(false);
|
if (wrapper) wrapper.releasePointerCapture(event.pointerId);
|
||||||
const wrapper = wrapperRef.current;
|
};
|
||||||
if (wrapper) wrapper.releasePointerCapture(event.pointerId);
|
|
||||||
};
|
const handleCreateComments = async (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
const handleCreateComments = async (e: any) => {
|
if (!value) return;
|
||||||
// Continue send or create message only there is only value avalibale
|
|
||||||
// To prevent empty value
|
if (!threadSocket?.connected && mode === "create") {
|
||||||
|
// API
|
||||||
if (!value) return;
|
|
||||||
e.preventDefault();
|
addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "").then((createComments) => {
|
||||||
try {
|
if (createComments.message === "Thread comments add Successfully" && createComments.data) {
|
||||||
// const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/
|
const commentData = {
|
||||||
// if (createComments.message === 'Thread comments add Successfully' && createComments.data) {
|
replyId: `${createComments.data?._id}`,
|
||||||
// const commentData = {
|
creatorId: `${selectedComment?.threadId}`,
|
||||||
// replyId: `${createComments.data?._id}`,
|
createdAt: "2 hrs ago",
|
||||||
// creatorId: `${selectedComment?.threadId}`,
|
lastUpdatedAt: "2 hrs ago",
|
||||||
// createdAt: "2 hrs ago",
|
comment: value,
|
||||||
// lastUpdatedAt: "2 hrs ago",
|
};
|
||||||
// comment: value,
|
setMessages((prevMessages) => [...prevMessages, commentData]);
|
||||||
// }
|
addReply(selectedComment?.threadId, commentData);
|
||||||
// setMessages((prevMessages) => [
|
}
|
||||||
// ...prevMessages,
|
});
|
||||||
// commentData,
|
} else if (mode === "create") {
|
||||||
// ]);
|
// SOCKET
|
||||||
// addReply(selectedComment?.threadId, commentData)
|
|
||||||
// }
|
const addComment = {
|
||||||
|
versionId: selectedVersion?.versionId || "",
|
||||||
if (threadSocket && mode === "create") {
|
projectId,
|
||||||
const addComment = {
|
userId,
|
||||||
versionId: selectedVersion?.versionId || "",
|
comment: value,
|
||||||
projectId,
|
organization,
|
||||||
userId,
|
threadId: selectedComment?.threadId,
|
||||||
comment: value,
|
};
|
||||||
organization,
|
|
||||||
threadId: selectedComment?.threadId,
|
threadSocket.emit("v1-Comment:add", addComment);
|
||||||
};
|
}
|
||||||
|
setInputActive(false);
|
||||||
threadSocket.emit("v1-Comment:add", addComment);
|
};
|
||||||
}
|
|
||||||
} catch {}
|
const handleDeleteThread = async () => {
|
||||||
setInputActive(false);
|
if (!projectId) return;
|
||||||
};
|
|
||||||
const handleDeleteThread = async () => {
|
if (!threadSocket?.connected) {
|
||||||
if (!projectId) return;
|
// API
|
||||||
try {
|
|
||||||
// const deleteThread = await deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "")
|
deleteThreadApi(projectId, selectedComment?.threadId, selectedVersion?.versionId || "").then((deleteThread) => {
|
||||||
// if (deleteThread.message === "Thread deleted Successfully") {
|
if (deleteThread.message === "Thread deleted Successfully") {
|
||||||
// removeComment(selectedComment?.threadId)
|
removeComment(selectedComment?.threadId);
|
||||||
// setSelectedComment([])
|
setSelectedComment(null);
|
||||||
// }
|
}
|
||||||
|
});
|
||||||
if (threadSocket) {
|
} else {
|
||||||
// projectId, userId, organization, threadId
|
// SOCKET
|
||||||
const deleteThread = {
|
|
||||||
projectId,
|
const deleteThread = {
|
||||||
userId,
|
projectId,
|
||||||
organization,
|
userId,
|
||||||
threadId: selectedComment?.threadId,
|
organization,
|
||||||
versionId: selectedVersion?.versionId || "",
|
threadId: selectedComment?.threadId,
|
||||||
};
|
versionId: selectedVersion?.versionId || "",
|
||||||
setSelectedComment(null);
|
};
|
||||||
removeComment(selectedComment?.threadId);
|
setSelectedComment(null);
|
||||||
threadSocket.emit("v1:thread:delete", deleteThread);
|
removeComment(selectedComment?.threadId);
|
||||||
}
|
threadSocket.emit("v1:thread:delete", deleteThread);
|
||||||
} catch {}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateThread = async (e: any) => {
|
const handleCreateThread = async (e: any) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
try {
|
if (!threadSocket?.connected) {
|
||||||
// const thread = await createThreadApi(
|
// API
|
||||||
// projectId,
|
|
||||||
// "active",
|
createThreadApi(projectId, "active", commentPositionState.position, [0, 0, 0], value, selectedVersion?.versionId || "").then((thread) => {
|
||||||
// commentPositionState[0].position,
|
if (thread.message === "Thread created Successfully" && thread?.threadData) {
|
||||||
// [0, 0, 0],
|
const comment: CommentSchema = {
|
||||||
// value,
|
state: "active",
|
||||||
// selectedVersion?.versionId || ""
|
threadId: thread?.threadData?._id,
|
||||||
// );
|
creatorId: userId,
|
||||||
//
|
createdAt: getRelativeTime(thread.threadData?.createdAt),
|
||||||
// if (thread.message === "Thread created Successfully" && thread?.threadData) {
|
threadTitle: value,
|
||||||
//
|
lastUpdatedAt: new Date().toISOString(),
|
||||||
// const comment: CommentSchema = {
|
position: commentPositionState.position,
|
||||||
// state: 'active',
|
rotation: [0, 0, 0],
|
||||||
// threadId: thread?.threadData?._id,
|
comments: [],
|
||||||
// creatorId: userId,
|
};
|
||||||
// createdAt: getRelativeTime(thread.threadData?.createdAt),
|
addComment(comment);
|
||||||
// threadTitle: value,
|
setCommentPositionState(null);
|
||||||
// lastUpdatedAt: new Date().toISOString(),
|
setInputActive(false);
|
||||||
// position: commentPositionState[0].position,
|
setSelectedComment(null);
|
||||||
// rotation: [0, 0, 0],
|
}
|
||||||
// comments: []
|
});
|
||||||
// }
|
} else {
|
||||||
// addComment(comment);
|
// SOCKET
|
||||||
// setCommentPositionState(null);
|
|
||||||
// setInputActive(false);
|
const createThread = {
|
||||||
// setSelectedComment([])
|
projectId,
|
||||||
// }
|
versionId: selectedVersion?.versionId || "",
|
||||||
|
userId,
|
||||||
const createThread = {
|
organization,
|
||||||
projectId,
|
state: "active",
|
||||||
versionId: selectedVersion?.versionId || "",
|
position: commentPositionState.position,
|
||||||
userId,
|
rotation: [0, 0, 0],
|
||||||
organization,
|
threadTitle: value,
|
||||||
state: "active",
|
};
|
||||||
position: commentPositionState.position,
|
|
||||||
rotation: [0, 0, 0],
|
setCommentPositionState(null);
|
||||||
threadTitle: value,
|
setInputActive(false);
|
||||||
};
|
setSelectedComment(null);
|
||||||
|
threadSocket.emit("v1:thread:create", createThread);
|
||||||
if (threadSocket) {
|
}
|
||||||
setInputActive(false);
|
};
|
||||||
threadSocket.emit("v1:thread:create", createThread);
|
|
||||||
}
|
const scrollToBottom = () => {
|
||||||
} catch {}
|
const messagesWrapper = document.querySelector(".messages-wrapper");
|
||||||
};
|
if (messagesWrapper) {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
||||||
const scrollToBottom = () => {
|
}
|
||||||
const messagesWrapper = document.querySelector(".messages-wrapper");
|
};
|
||||||
if (messagesWrapper) {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
useEffect(() => {
|
||||||
}
|
if (messages.length > 0) scrollToBottom();
|
||||||
};
|
}, [messages]);
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
if (messages.length > 0) scrollToBottom();
|
<div
|
||||||
}, [messages]);
|
ref={wrapperRef}
|
||||||
|
className="thread-chat-wrapper"
|
||||||
return (
|
onPointerDown={handlePointerDown}
|
||||||
<div
|
onPointerMove={(e) => handlePointerMove({ clientX: e.clientX, clientY: e.clientY })}
|
||||||
ref={wrapperRef}
|
onPointerUp={handlePointerUp}
|
||||||
className="thread-chat-wrapper"
|
style={{
|
||||||
onPointerDown={handlePointerDown}
|
position: "absolute",
|
||||||
onPointerMove={(e) => handlePointerMove({ clientX: e.clientX, clientY: e.clientY })}
|
left: position.x,
|
||||||
onPointerUp={handlePointerUp}
|
top: position.y,
|
||||||
style={{
|
cursor: dragging ? "grabbing" : "grab",
|
||||||
position: "absolute",
|
userSelect: "none",
|
||||||
left: position.x,
|
zIndex: 9999,
|
||||||
top: position.y,
|
}}
|
||||||
cursor: dragging ? "grabbing" : "grab",
|
>
|
||||||
userSelect: "none",
|
<div className="thread-chat-container">
|
||||||
zIndex: 9999,
|
<div className="header-wrapper">
|
||||||
}}
|
<div className="header">Comment</div>
|
||||||
>
|
<div className="header-options">
|
||||||
<div className="thread-chat-container">
|
<div
|
||||||
<div className="header-wrapper">
|
className="options-button"
|
||||||
<div className="header">Comment</div>
|
style={{ cursor: "pointer" }}
|
||||||
<div className="header-options">
|
onClick={(e) => {
|
||||||
<div
|
e.preventDefault();
|
||||||
className="options-button"
|
setOpenThreadOptions((prev) => !prev);
|
||||||
style={{ cursor: "pointer" }}
|
}}
|
||||||
onClick={(e) => {
|
>
|
||||||
e.preventDefault();
|
<KebabIcon />
|
||||||
setOpenThreadOptions((prev) => !prev);
|
</div>
|
||||||
}}
|
{openThreadOptions && (
|
||||||
>
|
<div className="options-list">
|
||||||
<KebabIcon />
|
<div className="options">Mark as Unread</div>
|
||||||
</div>
|
<div className="options">Mark as Resolved</div>
|
||||||
{openThreadOptions && (
|
<div className="options delete" onClick={handleDeleteThread}>
|
||||||
<div className="options-list">
|
Delete Thread
|
||||||
<div className="options">Mark as Unread</div>
|
</div>
|
||||||
<div className="options">Mark as Resolved</div>
|
</div>
|
||||||
<div className="options delete" onClick={handleDeleteThread}>
|
)}
|
||||||
Delete Thread
|
<button
|
||||||
</div>
|
className="close-button"
|
||||||
</div>
|
onClick={() => {
|
||||||
)}
|
setSelectedComment(null);
|
||||||
<button
|
setCommentPositionState(null);
|
||||||
className="close-button"
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
setSelectedComment(null);
|
<CloseIcon />
|
||||||
setCommentPositionState(null);
|
</button>
|
||||||
}}
|
</div>
|
||||||
>
|
</div>
|
||||||
<CloseIcon />
|
|
||||||
</button>
|
<div className="messages-wrapper" ref={messagesRef}>
|
||||||
</div>
|
{selectedComment && <Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />}
|
||||||
</div>
|
{messages.map((val, i) => (
|
||||||
|
<Messages
|
||||||
<div className="messages-wrapper" ref={messagesRef}>
|
val={val as any}
|
||||||
{selectedComment && <Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />}
|
i={i}
|
||||||
{messages &&
|
key={val.replyId}
|
||||||
messages.map((val, i) => (
|
setMessages={setMessages}
|
||||||
<Messages
|
setIsEditable={setIsEditable}
|
||||||
val={val as any}
|
isEditable={isEditable}
|
||||||
i={i}
|
isEditableThread={false}
|
||||||
key={val.replyId}
|
setMode={setMode}
|
||||||
setMessages={setMessages}
|
mode={mode}
|
||||||
setIsEditable={setIsEditable}
|
/>
|
||||||
isEditable={isEditable}
|
))}
|
||||||
isEditableThread={false}
|
</div>
|
||||||
setMode={setMode}
|
|
||||||
mode={mode}
|
<div className="send-message-wrapper">
|
||||||
/>
|
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
||||||
))}
|
<textarea
|
||||||
</div>
|
placeholder={commentPositionState && selectedComment === null ? "Type Thread Title" : "type something"}
|
||||||
|
ref={textareaRef}
|
||||||
<div className="send-message-wrapper">
|
value={value}
|
||||||
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
onChange={(e) => setValue(e.target.value)}
|
||||||
<textarea
|
onKeyDown={(e) => {
|
||||||
placeholder={commentPositionState && selectedComment === null ? "Type Thread Title" : "type something"}
|
if (e.key === "Enter") {
|
||||||
ref={textareaRef}
|
e.preventDefault();
|
||||||
value={value}
|
if (commentPositionState && selectedComment === null) {
|
||||||
onChange={(e) => setValue(e.target.value)}
|
handleCreateThread(e);
|
||||||
onKeyDown={(e) => {
|
} else {
|
||||||
if (e.key === "Enter") {
|
setMode("create");
|
||||||
e.preventDefault();
|
handleCreateComments(e);
|
||||||
if (commentPositionState && selectedComment === null) {
|
textareaRef.current?.blur();
|
||||||
handleCreateThread(e);
|
}
|
||||||
} else {
|
setValue("");
|
||||||
setMode("create");
|
}
|
||||||
handleCreateComments(e);
|
if (e.key === "Escape") {
|
||||||
textareaRef.current?.blur();
|
textareaRef.current?.blur();
|
||||||
}
|
}
|
||||||
setValue("");
|
}}
|
||||||
}
|
onClick={() => {
|
||||||
if (e.key === "Escape") {
|
if (!commentPositionState && selectedComment !== null) {
|
||||||
textareaRef.current?.blur();
|
setMode("create");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
autoFocus={selectedComment === null}
|
||||||
if (!commentPositionState && selectedComment !== null) {
|
onBlur={() => setInputActive(false)}
|
||||||
setMode("create");
|
onFocus={() => setInputActive(true)}
|
||||||
}
|
style={{ resize: "none" }}
|
||||||
}}
|
/>
|
||||||
autoFocus={selectedComment === null}
|
<div
|
||||||
onBlur={() => setInputActive(false)}
|
className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}
|
||||||
onFocus={() => setInputActive(true)}
|
onClick={(e) => {
|
||||||
style={{ resize: "none" }}
|
if (commentPositionState && selectedComment === null) {
|
||||||
/>
|
handleCreateThread(e);
|
||||||
<div
|
} else {
|
||||||
className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}
|
setMode("create");
|
||||||
onClick={(e) => {
|
handleCreateComments(e);
|
||||||
if (commentPositionState && selectedComment === null) {
|
}
|
||||||
handleCreateThread(e);
|
setValue("");
|
||||||
} else {
|
}}
|
||||||
setMode("create");
|
>
|
||||||
handleCreateComments(e);
|
<ExpandIcon />
|
||||||
}
|
</div>
|
||||||
setValue("");
|
</div>
|
||||||
}}
|
</div>
|
||||||
>
|
</div>
|
||||||
<ExpandIcon />
|
<ThreadSocketResponsesDev setMessages={setMessages} modeRef={modeRef} messages={messages} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
};
|
||||||
</div>
|
|
||||||
<ThreadSocketResponsesDev setMessages={setMessages} modeRef={modeRef} messages={messages} />
|
export default ThreadChat;
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThreadChat;
|
|
||||||
@@ -31,6 +31,7 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fieldData && asset.eventData) {
|
if (!fieldData && asset.eventData) {
|
||||||
getAssetFieldApi(asset.assetId).then((data) => {
|
getAssetFieldApi(asset.assetId).then((data) => {
|
||||||
|
if (!data) return;
|
||||||
if (data.type === "ArmBot") {
|
if (data.type === "ArmBot") {
|
||||||
if (data.data) {
|
if (data.data) {
|
||||||
const fieldData: IK[] = data.data;
|
const fieldData: IK[] = data.data;
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Html, TransformControls } from '@react-three/drei';
|
import { Html, TransformControls } from "@react-three/drei";
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { usePlayButtonStore } from '../../../../../store/ui/usePlayButtonStore';
|
import { usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore";
|
||||||
import CommentThreads from '../../../../../components/ui/collaboration/CommentThreads';
|
import CommentThreads from "../../../../../components/ui/collaboration/threads/CommentThreads";
|
||||||
import { useSelectedComment } from '../../../../../store/builder/store';
|
import { useSelectedComment } from "../../../../../store/builder/store";
|
||||||
import { Group, Object3D, Vector2, Vector3 } from 'three';
|
import { Group, Object3D, Vector3 } from "three";
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||||
|
|
||||||
function CommentInstance({ comment }: { readonly comment: CommentSchema }) {
|
function ThreadInstance({ comment }: { readonly comment: CommentSchema }) {
|
||||||
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 { 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();
|
||||||
@@ -33,28 +34,26 @@ function CommentInstance({ comment }: { readonly comment: CommentSchema }) {
|
|||||||
|
|
||||||
const commentClicked = () => {
|
const commentClicked = () => {
|
||||||
setSelectedComment(comment);
|
setSelectedComment(comment);
|
||||||
const position = new Vector3(comment.position[0], comment.position[1], comment.position[2])
|
const position = new Vector3(comment.position[0], comment.position[1], comment.position[2]);
|
||||||
|
|
||||||
position.project(camera);
|
position.project(camera);
|
||||||
const x = (position.x * 0.5 + 0.5) * size.width;
|
const x = (position.x * 0.5 + 0.5) * size.width;
|
||||||
const y = (-(position.y * 0.5) + 0.2) * size.height;
|
const y = (-(position.y * 0.5) + 0.2) * size.height;
|
||||||
setPosition2Dstate({ x, y })
|
setPosition2Dstate({ x, y });
|
||||||
|
|
||||||
if (groupRef.current) {
|
if (groupRef.current) {
|
||||||
setSelectedObject(groupRef.current);
|
setSelectedObject(groupRef.current);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedComment || selectedComment.threadId !== comment.threadId) {
|
if (!selectedComment || selectedComment.threadId !== comment.threadId) {
|
||||||
setSelectedObject(null)
|
setSelectedObject(null);
|
||||||
}
|
}
|
||||||
}, [selectedComment])
|
}, [selectedComment]);
|
||||||
|
|
||||||
|
if (comment.state === "inactive" || isPlaying) return null;
|
||||||
if (comment.state === 'inactive' || isPlaying) return null;
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<group ref={groupRef} position={comment.position} rotation={comment.rotation}>
|
<group ref={groupRef} position={comment.position} rotation={comment.rotation}>
|
||||||
<Html
|
<Html
|
||||||
@@ -65,7 +64,7 @@ function CommentInstance({ comment }: { readonly comment: CommentSchema }) {
|
|||||||
center
|
center
|
||||||
// position={comment.position}
|
// position={comment.position}
|
||||||
// rotation={comment.rotation}
|
// rotation={comment.rotation}
|
||||||
className='comments-main-wrapper'
|
className="comments-main-wrapper"
|
||||||
>
|
>
|
||||||
<CommentThreads commentClicked={commentClicked} comment={comment} />
|
<CommentThreads commentClicked={commentClicked} comment={comment} />
|
||||||
</Html>
|
</Html>
|
||||||
@@ -80,11 +79,7 @@ function CommentInstance({ comment }: { readonly comment: CommentSchema }) {
|
|||||||
/>
|
/>
|
||||||
)} */}
|
)} */}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CommentInstance;
|
export default ThreadInstance;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import CommentInstance from "./commentInstance/commentInstance";
|
import CommentInstance from "./commentInstance/commentInstance";
|
||||||
import { useCommentStore } from "../../../../store/collaboration/useCommentStore";
|
|
||||||
import { getAllThreads } from "../../../../services/factoryBuilder/comments/getAllThreads";
|
import { getAllThreads } from "../../../../services/factoryBuilder/comments/getAllThreads";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { getUserData } from "../../../../functions/getUserData";
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
@@ -8,10 +7,10 @@ import { getRelativeTime } from "../../../../components/ui/collaboration/functio
|
|||||||
import { useSceneContext } from "../../../scene/sceneContext";
|
import { useSceneContext } from "../../../scene/sceneContext";
|
||||||
|
|
||||||
function CommentInstances() {
|
function CommentInstances() {
|
||||||
const { comments, setComments } = useCommentStore();
|
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { userId } = getUserData();
|
const { userId } = getUserData();
|
||||||
const { versionStore } = useSceneContext();
|
const { versionStore, commentStore } = useSceneContext();
|
||||||
|
const { comments, setComments } = commentStore();
|
||||||
const { selectedVersion } = versionStore();
|
const { selectedVersion } = versionStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -42,7 +41,7 @@ function CommentInstances() {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Failed to fetch threads:", err);
|
console.error("Failed to fetch threads:", err);
|
||||||
});
|
});
|
||||||
}, []);
|
}, [projectId, selectedVersion]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { useSelectedComment, useSocketStore } from '../../../store/builder/store';
|
import { useSelectedComment, useSocketStore } from "../../../store/builder/store";
|
||||||
import { useCommentStore } from '../../../store/collaboration/useCommentStore';
|
import { getUserData } from "../../../functions/getUserData";
|
||||||
import { getUserData } from '../../../functions/getUserData';
|
import { getRelativeTime } from "../../../components/ui/collaboration/function/getRelativeTime";
|
||||||
import { getRelativeTime } from '../../../components/ui/collaboration/function/getRelativeTime';
|
import { useSceneContext } from "../../scene/sceneContext";
|
||||||
|
|
||||||
interface ThreadSocketProps {
|
interface ThreadSocketProps {
|
||||||
setMessages: React.Dispatch<React.SetStateAction<Reply[]>>;
|
setMessages: React.Dispatch<React.SetStateAction<Reply[]>>;
|
||||||
// mode: 'create' | 'edit' | null
|
// mode: 'create' | 'edit' | null
|
||||||
modeRef: React.RefObject<'create' | 'edit' | null>;
|
modeRef: React.RefObject<"create" | "edit" | null>;
|
||||||
messages: Reply[]
|
messages: Reply[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSocketProps) => {
|
const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSocketProps) => {
|
||||||
const { threadSocket } = useSocketStore();
|
const { threadSocket } = useSocketStore();
|
||||||
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState } = useSelectedComment();
|
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState } = useSelectedComment();
|
||||||
const { comments, removeReply, addComment, addReply, updateComment, updateReply } = useCommentStore();
|
const { commentStore } = useSceneContext();
|
||||||
|
const { comments, removeReply, addComment, addReply, updateComment, updateReply } = commentStore();
|
||||||
const { userId } = getUserData();
|
const { userId } = getUserData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!threadSocket) return;
|
if (!threadSocket) return;
|
||||||
|
|
||||||
|
|
||||||
// --- Add Comment Handler ---
|
// --- Add Comment Handler ---
|
||||||
// const handleAddComment = (data: any) => {
|
// const handleAddComment = (data: any) => {
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// const commentData = {
|
// const commentData = {
|
||||||
// replyId: data.data?._id,
|
// replyId: data.data?._id,
|
||||||
// creatorId: data.data?.userId,
|
// creatorId: data.data?.userId,
|
||||||
@@ -32,11 +32,11 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
// lastUpdatedAt: '2 hrs ago',
|
// lastUpdatedAt: '2 hrs ago',
|
||||||
// comment: data.data.comment,
|
// comment: data.data.comment,
|
||||||
// };
|
// };
|
||||||
// //
|
// //
|
||||||
//
|
//
|
||||||
// if (mode == "create") {
|
// if (mode == "create") {
|
||||||
// addReply(selectedComment?.threadId, commentData);
|
// addReply(selectedComment?.threadId, commentData);
|
||||||
//
|
//
|
||||||
// setMessages(prevMessages => [...prevMessages, commentData]);
|
// setMessages(prevMessages => [...prevMessages, commentData]);
|
||||||
// } else if (mode == "edit") {
|
// } else if (mode == "edit") {
|
||||||
// updateReply(selectedComment?.threadId, data.data?._id, commentData);
|
// updateReply(selectedComment?.threadId, data.data?._id, commentData);
|
||||||
@@ -48,17 +48,14 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
// : message;
|
// : message;
|
||||||
// })
|
// })
|
||||||
// );
|
// );
|
||||||
//
|
//
|
||||||
// } else {
|
// } else {
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
// threadSocket.off('v1-Comment:response:add', handleAddComment);
|
// threadSocket.off('v1-Comment:response:add', handleAddComment);
|
||||||
// };
|
// };
|
||||||
// threadSocket.on('v1-Comment:response:add', handleAddComment);
|
// threadSocket.on('v1-Comment:response:add', handleAddComment);
|
||||||
const handleAddComment = (data: any) => {
|
const handleAddComment = (data: any) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const commentData = {
|
const commentData = {
|
||||||
replyId: data.data?._id,
|
replyId: data.data?._id,
|
||||||
creatorId: data.data?.userId,
|
creatorId: data.data?.userId,
|
||||||
@@ -75,27 +72,21 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
prev.map((message) => {
|
prev.map((message) => {
|
||||||
// 👈 log each message
|
// 👈 log each message
|
||||||
return message.replyId === data.data?._id
|
return message.replyId === data.data?._id ? { ...message, comment: data.data?.comment } : message;
|
||||||
? { ...message, comment: data.data?.comment }
|
|
||||||
: message;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
threadSocket.on('v1-Comment:response:add', handleAddComment);
|
threadSocket.on("v1-Comment:response:add", handleAddComment);
|
||||||
// --- Delete Comment Handler ---
|
// --- Delete Comment Handler ---
|
||||||
|
|
||||||
const handleDeleteComment = (data: any) => {
|
const handleDeleteComment = (data: any) => {};
|
||||||
};
|
threadSocket.on("v1-Comment:response:delete", handleDeleteComment);
|
||||||
threadSocket.on('v1-Comment:response:delete', handleDeleteComment);
|
|
||||||
|
|
||||||
// --- Create Thread Handler ---
|
// --- Create Thread Handler ---
|
||||||
const handleCreateThread = (data: any) => {
|
const handleCreateThread = (data: any) => {
|
||||||
|
|
||||||
|
|
||||||
const comment: CommentSchema = {
|
const comment: CommentSchema = {
|
||||||
state: 'active',
|
state: "active",
|
||||||
threadId: data.data?._id,
|
threadId: data.data?._id,
|
||||||
creatorId: userId || data.data?.userId,
|
creatorId: userId || data.data?.userId,
|
||||||
createdAt: data.data?.createdAt,
|
createdAt: data.data?.createdAt,
|
||||||
@@ -105,32 +96,26 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
comments: [],
|
comments: [],
|
||||||
};
|
};
|
||||||
setSelectedComment(comment)
|
setSelectedComment(comment);
|
||||||
addComment(comment);
|
addComment(comment);
|
||||||
setCommentPositionState(null);
|
setCommentPositionState(null);
|
||||||
// setSelectedComment(null);
|
// setSelectedComment(null);
|
||||||
};
|
};
|
||||||
threadSocket.on('v1-thread:response:create', handleCreateThread);
|
threadSocket.on("v1-thread:response:create", handleCreateThread);
|
||||||
|
|
||||||
// --- Delete Thread Handler ---
|
// --- Delete Thread Handler ---
|
||||||
// const handleDeleteThread = (data: any) => {
|
// const handleDeleteThread = (data: any) => {
|
||||||
//
|
//
|
||||||
|
|
||||||
// };
|
// };
|
||||||
// threadSocket.on("v1-thread:response:delete", handleDeleteThread);
|
// threadSocket.on("v1-thread:response:delete", handleDeleteThread);
|
||||||
|
|
||||||
const handleDeleteThread = (data: any) => {
|
|
||||||
|
|
||||||
};
|
const handleDeleteThread = (data: any) => {};
|
||||||
threadSocket.on("v1-thread:response:delete", handleDeleteThread);
|
threadSocket.on("v1-thread:response:delete", handleDeleteThread);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleEditThread = (data: any) => {
|
const handleEditThread = (data: any) => {
|
||||||
|
|
||||||
|
|
||||||
const editedThread: CommentSchema = {
|
const editedThread: CommentSchema = {
|
||||||
state: 'active',
|
state: "active",
|
||||||
threadId: data.data?._id,
|
threadId: data.data?._id,
|
||||||
creatorId: userId,
|
creatorId: userId,
|
||||||
createdAt: data.data?.createdAt,
|
createdAt: data.data?.createdAt,
|
||||||
@@ -141,23 +126,37 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
comments: data.data?.comments,
|
comments: data.data?.comments,
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
updateComment(data.data?._id, editedThread);
|
updateComment(data.data?._id, editedThread);
|
||||||
setSelectedComment(editedThread)
|
setSelectedComment(editedThread);
|
||||||
|
|
||||||
// setSelectedComment(null);
|
// setSelectedComment(null);
|
||||||
};
|
};
|
||||||
threadSocket.on('v1-thread:response:updateTitle', handleEditThread);
|
threadSocket.on("v1-thread:response:updateTitle", handleEditThread);
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
return () => {
|
return () => {
|
||||||
threadSocket.off('v1-Comment:response:add', handleAddComment);
|
threadSocket.off("v1-Comment:response:add", handleAddComment);
|
||||||
threadSocket.off('v1-Comment:response:delete', handleDeleteComment);
|
threadSocket.off("v1-Comment:response:delete", handleDeleteComment);
|
||||||
threadSocket.off('v1-thread:response:create', handleCreateThread);
|
threadSocket.off("v1-thread:response:create", handleCreateThread);
|
||||||
threadSocket.off('v1-thread:response:delete', handleDeleteThread);
|
threadSocket.off("v1-thread:response:delete", handleDeleteThread);
|
||||||
threadSocket.off('v1-thread:response:updateTitle', handleEditThread);
|
threadSocket.off("v1-thread:response:updateTitle", handleEditThread);
|
||||||
};
|
};
|
||||||
}, [threadSocket, selectedComment, commentPositionState, userId, setMessages, addReply, addComment, setSelectedComment, setCommentPositionState, updateComment, comments, modeRef.current, messages]);
|
}, [
|
||||||
|
threadSocket,
|
||||||
|
selectedComment,
|
||||||
|
commentPositionState,
|
||||||
|
userId,
|
||||||
|
setMessages,
|
||||||
|
addReply,
|
||||||
|
addComment,
|
||||||
|
setSelectedComment,
|
||||||
|
setCommentPositionState,
|
||||||
|
updateComment,
|
||||||
|
comments,
|
||||||
|
modeRef.current,
|
||||||
|
messages,
|
||||||
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
import { createContext, useContext, useMemo, useRef } from 'react';
|
import { createContext, useContext, useMemo, useRef } from "react";
|
||||||
|
|
||||||
import { createVersionStore, VersionStoreType } from '../../store/builder/useVersionStore';
|
import { createVersionStore, VersionStoreType } from "../../store/builder/useVersionStore";
|
||||||
|
|
||||||
import { createAssetStore, AssetStoreType } from '../../store/builder/useAssetStore';
|
import { createAssetStore, AssetStoreType } from "../../store/builder/useAssetStore";
|
||||||
import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore';
|
import { createWallAssetStore, WallAssetStoreType } from "../../store/builder/useWallAssetStore";
|
||||||
import { createWallStore, WallStoreType } from '../../store/builder/useWallStore';
|
import { createWallStore, WallStoreType } from "../../store/builder/useWallStore";
|
||||||
import { createAisleStore, AisleStoreType } from '../../store/builder/useAisleStore';
|
import { createAisleStore, AisleStoreType } from "../../store/builder/useAisleStore";
|
||||||
import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore';
|
import { createZoneStore, ZoneStoreType } from "../../store/builder/useZoneStore";
|
||||||
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
|
import { createFloorStore, FloorStoreType } from "../../store/builder/useFloorStore";
|
||||||
|
|
||||||
import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
|
import { createUndoRedo2DStore, UndoRedo2DStoreType } from "../../store/builder/useUndoRedo2DStore";
|
||||||
import { createUndoRedo3DStore, UndoRedo3DStoreType } from '../../store/builder/useUndoRedo3DStore';
|
import { createUndoRedo3DStore, UndoRedo3DStoreType } from "../../store/builder/useUndoRedo3DStore";
|
||||||
|
|
||||||
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
|
import { createEventStore, EventStoreType } from "../../store/simulation/useEventsStore";
|
||||||
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
|
import { createProductStore, ProductStoreType } from "../../store/simulation/useProductStore";
|
||||||
|
|
||||||
import { createMaterialStore, MaterialStoreType } from '../../store/simulation/useMaterialStore';
|
import { createMaterialStore, MaterialStoreType } from "../../store/simulation/useMaterialStore";
|
||||||
import { createArmBotStore, ArmBotStoreType } from '../../store/simulation/useArmBotStore';
|
import { createArmBotStore, ArmBotStoreType } from "../../store/simulation/useArmBotStore";
|
||||||
import { createMachineStore, MachineStoreType } from '../../store/simulation/useMachineStore';
|
import { createMachineStore, MachineStoreType } from "../../store/simulation/useMachineStore";
|
||||||
import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/useConveyorStore';
|
import { createConveyorStore, ConveyorStoreType } from "../../store/simulation/useConveyorStore";
|
||||||
import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore';
|
import { createVehicleStore, VehicleStoreType } from "../../store/simulation/useVehicleStore";
|
||||||
import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore';
|
import { createStorageUnitStore, StorageUnitStoreType } from "../../store/simulation/useStorageUnitStore";
|
||||||
import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore';
|
import { createHumanStore, HumanStoreType } from "../../store/simulation/useHumanStore";
|
||||||
import { createCraneStore, CraneStoreType } from '../../store/simulation/useCraneStore';
|
import { createCraneStore, CraneStoreType } from "../../store/simulation/useCraneStore";
|
||||||
|
|
||||||
|
import { createCommentsStore, CommentStoreType } from "../../store/collaboration/useCommentStore";
|
||||||
|
|
||||||
type SceneContextValue = {
|
type SceneContextValue = {
|
||||||
|
versionStore: VersionStoreType;
|
||||||
|
|
||||||
versionStore: VersionStoreType
|
assetStore: AssetStoreType;
|
||||||
|
wallAssetStore: WallAssetStoreType;
|
||||||
|
wallStore: WallStoreType;
|
||||||
|
aisleStore: AisleStoreType;
|
||||||
|
zoneStore: ZoneStoreType;
|
||||||
|
floorStore: FloorStoreType;
|
||||||
|
|
||||||
assetStore: AssetStoreType,
|
undoRedo2DStore: UndoRedo2DStoreType;
|
||||||
wallAssetStore: WallAssetStoreType,
|
undoRedo3DStore: UndoRedo3DStoreType;
|
||||||
wallStore: WallStoreType,
|
|
||||||
aisleStore: AisleStoreType,
|
|
||||||
zoneStore: ZoneStoreType,
|
|
||||||
floorStore: FloorStoreType,
|
|
||||||
|
|
||||||
undoRedo2DStore: UndoRedo2DStoreType,
|
eventStore: EventStoreType;
|
||||||
undoRedo3DStore: UndoRedo3DStoreType,
|
productStore: ProductStoreType;
|
||||||
|
|
||||||
eventStore: EventStoreType,
|
|
||||||
productStore: ProductStoreType,
|
|
||||||
|
|
||||||
materialStore: MaterialStoreType;
|
materialStore: MaterialStoreType;
|
||||||
armBotStore: ArmBotStoreType;
|
armBotStore: ArmBotStoreType;
|
||||||
@@ -50,24 +51,19 @@ type SceneContextValue = {
|
|||||||
humanStore: HumanStoreType;
|
humanStore: HumanStoreType;
|
||||||
craneStore: CraneStoreType;
|
craneStore: CraneStoreType;
|
||||||
|
|
||||||
|
commentStore: CommentStoreType;
|
||||||
|
|
||||||
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
||||||
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
|
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
|
||||||
|
|
||||||
clearStores: () => void;
|
clearStores: () => void;
|
||||||
|
|
||||||
layout: 'Main Layout' | 'Comparison Layout';
|
layout: "Main Layout" | "Comparison Layout";
|
||||||
};
|
};
|
||||||
|
|
||||||
const SceneContext = createContext<SceneContextValue | null>(null);
|
const SceneContext = createContext<SceneContextValue | null>(null);
|
||||||
|
|
||||||
export function SceneProvider({
|
export function SceneProvider({ children, layout }: { readonly children: React.ReactNode; readonly layout: "Main Layout" | "Comparison Layout" }) {
|
||||||
children,
|
|
||||||
layout
|
|
||||||
}: {
|
|
||||||
readonly children: React.ReactNode;
|
|
||||||
readonly layout: 'Main Layout' | 'Comparison Layout';
|
|
||||||
}) {
|
|
||||||
|
|
||||||
const versionStore = useMemo(() => createVersionStore(), []);
|
const versionStore = useMemo(() => createVersionStore(), []);
|
||||||
|
|
||||||
const assetStore = useMemo(() => createAssetStore(), []);
|
const assetStore = useMemo(() => createAssetStore(), []);
|
||||||
@@ -92,35 +88,62 @@ export function SceneProvider({
|
|||||||
const humanStore = useMemo(() => createHumanStore(), []);
|
const humanStore = useMemo(() => createHumanStore(), []);
|
||||||
const craneStore = useMemo(() => createCraneStore(), []);
|
const craneStore = useMemo(() => createCraneStore(), []);
|
||||||
|
|
||||||
|
const commentStore = useMemo(() => createCommentsStore(), []);
|
||||||
|
|
||||||
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
||||||
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
|
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
|
||||||
|
|
||||||
const clearStores = useMemo(() => () => {
|
const clearStores = useMemo(
|
||||||
versionStore.getState().clearVersions();
|
() => () => {
|
||||||
assetStore.getState().clearAssets();
|
versionStore.getState().clearVersions();
|
||||||
wallAssetStore.getState().clearWallAssets();
|
assetStore.getState().clearAssets();
|
||||||
wallStore.getState().clearWalls();
|
wallAssetStore.getState().clearWallAssets();
|
||||||
aisleStore.getState().clearAisles();
|
wallStore.getState().clearWalls();
|
||||||
zoneStore.getState().clearZones();
|
aisleStore.getState().clearAisles();
|
||||||
floorStore.getState().clearFloors();
|
zoneStore.getState().clearZones();
|
||||||
undoRedo2DStore.getState().clearUndoRedo2D();
|
floorStore.getState().clearFloors();
|
||||||
undoRedo3DStore.getState().clearUndoRedo3D();
|
undoRedo2DStore.getState().clearUndoRedo2D();
|
||||||
eventStore.getState().clearEvents();
|
undoRedo3DStore.getState().clearUndoRedo3D();
|
||||||
productStore.getState().clearProducts();
|
eventStore.getState().clearEvents();
|
||||||
materialStore.getState().clearMaterials();
|
productStore.getState().clearProducts();
|
||||||
armBotStore.getState().clearArmBots();
|
materialStore.getState().clearMaterials();
|
||||||
machineStore.getState().clearMachines();
|
armBotStore.getState().clearArmBots();
|
||||||
conveyorStore.getState().clearConveyors();
|
machineStore.getState().clearMachines();
|
||||||
vehicleStore.getState().clearVehicles();
|
conveyorStore.getState().clearConveyors();
|
||||||
storageUnitStore.getState().clearStorageUnits();
|
vehicleStore.getState().clearVehicles();
|
||||||
humanStore.getState().clearHumans();
|
storageUnitStore.getState().clearStorageUnits();
|
||||||
craneStore.getState().clearCranes();
|
humanStore.getState().clearHumans();
|
||||||
humanEventManagerRef.current.humanStates = [];
|
craneStore.getState().clearCranes();
|
||||||
craneEventManagerRef.current.craneStates = [];
|
commentStore.getState().clearComments();
|
||||||
}, [versionStore, assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
|
humanEventManagerRef.current.humanStates = [];
|
||||||
|
craneEventManagerRef.current.craneStates = [];
|
||||||
|
},
|
||||||
|
[
|
||||||
|
versionStore,
|
||||||
|
assetStore,
|
||||||
|
wallAssetStore,
|
||||||
|
wallStore,
|
||||||
|
aisleStore,
|
||||||
|
zoneStore,
|
||||||
|
undoRedo2DStore,
|
||||||
|
undoRedo3DStore,
|
||||||
|
floorStore,
|
||||||
|
eventStore,
|
||||||
|
productStore,
|
||||||
|
materialStore,
|
||||||
|
armBotStore,
|
||||||
|
machineStore,
|
||||||
|
conveyorStore,
|
||||||
|
vehicleStore,
|
||||||
|
storageUnitStore,
|
||||||
|
humanStore,
|
||||||
|
craneStore,
|
||||||
|
commentStore,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const contextValue = useMemo(() => (
|
const contextValue = useMemo(
|
||||||
{
|
() => ({
|
||||||
versionStore,
|
versionStore,
|
||||||
assetStore,
|
assetStore,
|
||||||
wallAssetStore,
|
wallAssetStore,
|
||||||
@@ -140,25 +163,46 @@ export function SceneProvider({
|
|||||||
storageUnitStore,
|
storageUnitStore,
|
||||||
humanStore,
|
humanStore,
|
||||||
craneStore,
|
craneStore,
|
||||||
|
commentStore,
|
||||||
humanEventManagerRef,
|
humanEventManagerRef,
|
||||||
craneEventManagerRef,
|
craneEventManagerRef,
|
||||||
clearStores,
|
clearStores,
|
||||||
layout
|
layout,
|
||||||
}
|
}),
|
||||||
), [versionStore, assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
|
[
|
||||||
|
versionStore,
|
||||||
return (
|
assetStore,
|
||||||
<SceneContext.Provider value={contextValue}>
|
wallAssetStore,
|
||||||
{children}
|
wallStore,
|
||||||
</SceneContext.Provider>
|
aisleStore,
|
||||||
|
zoneStore,
|
||||||
|
floorStore,
|
||||||
|
undoRedo2DStore,
|
||||||
|
undoRedo3DStore,
|
||||||
|
eventStore,
|
||||||
|
productStore,
|
||||||
|
materialStore,
|
||||||
|
armBotStore,
|
||||||
|
machineStore,
|
||||||
|
conveyorStore,
|
||||||
|
vehicleStore,
|
||||||
|
storageUnitStore,
|
||||||
|
humanStore,
|
||||||
|
craneStore,
|
||||||
|
commentStore,
|
||||||
|
clearStores,
|
||||||
|
layout,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return <SceneContext.Provider value={contextValue}>{children}</SceneContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base hook to get the context
|
// Base hook to get the context
|
||||||
export function useSceneContext() {
|
export function useSceneContext() {
|
||||||
const context = useContext(SceneContext);
|
const context = useContext(SceneContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useSceneContext must be used within a SceneProvider');
|
throw new Error("useSceneContext must be used within a SceneProvider");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|||||||
31
app/src/services/factoryBuilder/comments/addCommentApi.ts
Normal file
31
app/src/services/factoryBuilder/comments/addCommentApi.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addComment`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer <access_token>",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
token: localStorage.getItem("token") || "",
|
||||||
|
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ projectId, comment, threadId, versionId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const newAccessToken = response.headers.get("x-access-token");
|
||||||
|
if (newAccessToken) {
|
||||||
|
localStorage.setItem("token", newAccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
echo.error("Failed to add comment");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
echo.error("Failed to add comment");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
|
||||||
|
|
||||||
export const addCommentsApi = async (
|
|
||||||
projectId: any,
|
|
||||||
comment: string,
|
|
||||||
threadId: string,
|
|
||||||
commentId: string,
|
|
||||||
versionId: string
|
|
||||||
) => {
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${url_Backend_dwinzo}/api/V1/Thread/addComment`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer <access_token>",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
token: localStorage.getItem("token") || "",
|
|
||||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ projectId, comment, threadId, commentId, versionId }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const newAccessToken = response.headers.get("x-access-token");
|
|
||||||
if (newAccessToken) {
|
|
||||||
localStorage.setItem("token", newAccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
echo.error("Failed to add comment");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch {
|
|
||||||
echo.error("Failed to add comment");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
31
app/src/services/factoryBuilder/comments/editCommentApi.ts
Normal file
31
app/src/services/factoryBuilder/comments/editCommentApi.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${url_Backend_dwinzo}/api/V1/Thread/addComment`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer <access_token>",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
token: localStorage.getItem("token") || "",
|
||||||
|
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ projectId, comment, commentId, threadId, versionId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const newAccessToken = response.headers.get("x-access-token");
|
||||||
|
if (newAccessToken) {
|
||||||
|
localStorage.setItem("token", newAccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
echo.error("Failed to edit comment");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
echo.error("Failed to edit comment");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -24,10 +24,12 @@ export const useSocketStore = create<any>((set: any, get: any) => ({
|
|||||||
reconnection: true,
|
reconnection: true,
|
||||||
auth: { token, refreshToken },
|
auth: { token, refreshToken },
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, {
|
const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, {
|
||||||
reconnection: true,
|
reconnection: true,
|
||||||
auth: { token, refreshToken },
|
auth: { token, refreshToken },
|
||||||
});
|
});
|
||||||
|
|
||||||
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, {
|
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, {
|
||||||
reconnection: true,
|
reconnection: true,
|
||||||
auth: { token, refreshToken },
|
auth: { token, refreshToken },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Object3D } from 'three';
|
import { Object3D } from "three";
|
||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
interface AssetsStore {
|
interface AssetsStore {
|
||||||
assets: Assets;
|
assets: Assets;
|
||||||
@@ -44,8 +44,8 @@ interface AssetsStore {
|
|||||||
removeAnimation: (modelUuid: string, animation: string) => void;
|
removeAnimation: (modelUuid: string, animation: string) => void;
|
||||||
|
|
||||||
// Event data operations
|
// Event data operations
|
||||||
addEventData: (modelUuid: string, eventData: Asset['eventData']) => void;
|
addEventData: (modelUuid: string, eventData: Asset["eventData"]) => void;
|
||||||
updateEventData: (modelUuid: string, updates: Partial<Asset['eventData']>) => void;
|
updateEventData: (modelUuid: string, updates: Partial<Asset["eventData"]>) => void;
|
||||||
removeEventData: (modelUuid: string) => void;
|
removeEventData: (modelUuid: string) => void;
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
@@ -68,7 +68,7 @@ export const createAssetStore = () => {
|
|||||||
// Asset CRUD operations
|
// Asset CRUD operations
|
||||||
addAsset: (asset) => {
|
addAsset: (asset) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.assets.some(a => a.modelUuid === asset.modelUuid)) {
|
if (!state.assets.some((a) => a.modelUuid === asset.modelUuid)) {
|
||||||
state.assets.push(asset);
|
state.assets.push(asset);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -76,13 +76,13 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
removeAsset: (modelUuid) => {
|
removeAsset: (modelUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.assets = state.assets.filter(a => a.modelUuid !== modelUuid);
|
state.assets = state.assets.filter((a) => a.modelUuid !== modelUuid);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAsset: (modelUuid, updates) => {
|
updateAsset: (modelUuid, updates) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
Object.assign(asset, updates);
|
Object.assign(asset, updates);
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ export const createAssetStore = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetAsset: (modelUuid) => {
|
resetAsset: (modelUuid) => {
|
||||||
const asset = get().assets.find(a => a.modelUuid === modelUuid);
|
const asset = get().assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
const clonedAsset = JSON.parse(JSON.stringify(asset));
|
const clonedAsset = JSON.parse(JSON.stringify(asset));
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -158,7 +158,7 @@ export const createAssetStore = () => {
|
|||||||
// Asset properties
|
// Asset properties
|
||||||
setName: (modelUuid, newName) => {
|
setName: (modelUuid, newName) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.modelName = newName;
|
asset.modelName = newName;
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setPosition: (modelUuid, position) => {
|
setPosition: (modelUuid, position) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.position = position;
|
asset.position = position;
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setRotation: (modelUuid, rotation) => {
|
setRotation: (modelUuid, rotation) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.rotation = rotation;
|
asset.rotation = rotation;
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setLock: (modelUuid, isLocked) => {
|
setLock: (modelUuid, isLocked) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.isLocked = isLocked;
|
asset.isLocked = isLocked;
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setCollision: (modelUuid, isCollidable) => {
|
setCollision: (modelUuid, isCollidable) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.isCollidable = isCollidable;
|
asset.isCollidable = isCollidable;
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setVisibility: (modelUuid, isVisible) => {
|
setVisibility: (modelUuid, isVisible) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.isVisible = isVisible;
|
asset.isVisible = isVisible;
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setOpacity: (modelUuid, opacity) => {
|
setOpacity: (modelUuid, opacity) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.opacity = opacity;
|
asset.opacity = opacity;
|
||||||
}
|
}
|
||||||
@@ -222,11 +222,11 @@ export const createAssetStore = () => {
|
|||||||
// Animation controls
|
// Animation controls
|
||||||
setAnimations: (modelUuid, animations) => {
|
setAnimations: (modelUuid, animations) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.animations = animations;
|
asset.animations = animations;
|
||||||
if (!asset.animationState) {
|
if (!asset.animationState) {
|
||||||
asset.animationState = { current: '', isPlaying: false, loopAnimation: true, isCompleted: true };
|
asset.animationState = { current: "", isPlaying: false, loopAnimation: true, isCompleted: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -234,7 +234,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setCurrentAnimation: (modelUuid, current, isPlaying, loopAnimation, isCompleted) => {
|
setCurrentAnimation: (modelUuid, current, isPlaying, loopAnimation, isCompleted) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset?.animationState) {
|
if (asset?.animationState) {
|
||||||
asset.animationState.current = current;
|
asset.animationState.current = current;
|
||||||
asset.animationState.isPlaying = isPlaying;
|
asset.animationState.isPlaying = isPlaying;
|
||||||
@@ -246,7 +246,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
setAnimationComplete: (modelUuid, isCompleted) => {
|
setAnimationComplete: (modelUuid, isCompleted) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset?.animationState) {
|
if (asset?.animationState) {
|
||||||
asset.animationState.isCompleted = isCompleted;
|
asset.animationState.isCompleted = isCompleted;
|
||||||
}
|
}
|
||||||
@@ -255,9 +255,9 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
resetAnimation: (modelUuid) => {
|
resetAnimation: (modelUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset?.animationState) {
|
if (asset?.animationState) {
|
||||||
asset.animationState.current = '';
|
asset.animationState.current = "";
|
||||||
asset.animationState.isPlaying = true;
|
asset.animationState.isPlaying = true;
|
||||||
asset.animationState.loopAnimation = true;
|
asset.animationState.loopAnimation = true;
|
||||||
asset.animationState.isCompleted = true;
|
asset.animationState.isCompleted = true;
|
||||||
@@ -267,7 +267,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
addAnimation: (modelUuid, animation) => {
|
addAnimation: (modelUuid, animation) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
if (!asset.animations) {
|
if (!asset.animations) {
|
||||||
asset.animations = [animation];
|
asset.animations = [animation];
|
||||||
@@ -280,12 +280,12 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
removeAnimation: (modelUuid, animation) => {
|
removeAnimation: (modelUuid, animation) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset?.animations) {
|
if (asset?.animations) {
|
||||||
asset.animations = asset.animations.filter(a => a !== animation);
|
asset.animations = asset.animations.filter((a) => a !== animation);
|
||||||
if (asset.animationState?.current === animation) {
|
if (asset.animationState?.current === animation) {
|
||||||
asset.animationState.isPlaying = false;
|
asset.animationState.isPlaying = false;
|
||||||
asset.animationState.current = '';
|
asset.animationState.current = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -294,7 +294,7 @@ export const createAssetStore = () => {
|
|||||||
// Event data operations
|
// Event data operations
|
||||||
addEventData: (modelUuid, eventData) => {
|
addEventData: (modelUuid, eventData) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.eventData = eventData;
|
asset.eventData = eventData;
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
updateEventData: (modelUuid, updates) => {
|
updateEventData: (modelUuid, updates) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset?.eventData) {
|
if (asset?.eventData) {
|
||||||
asset.eventData = { ...asset.eventData, ...updates };
|
asset.eventData = { ...asset.eventData, ...updates };
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
removeEventData: (modelUuid) => {
|
removeEventData: (modelUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
const asset = state.assets.find((a) => a.modelUuid === modelUuid);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
delete asset.eventData;
|
delete asset.eventData;
|
||||||
}
|
}
|
||||||
@@ -321,21 +321,18 @@ export const createAssetStore = () => {
|
|||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
getAssetById: (modelUuid) => {
|
getAssetById: (modelUuid) => {
|
||||||
return get().assets.find(a => a.modelUuid === modelUuid);
|
return get().assets.find((a) => a.modelUuid === modelUuid);
|
||||||
},
|
},
|
||||||
|
|
||||||
getAssetByPointUuid: (pointUuid) => {
|
getAssetByPointUuid: (pointUuid) => {
|
||||||
return get().assets.find(asset =>
|
return get().assets.find((asset) => asset.eventData?.point?.uuid === pointUuid || asset.eventData?.points?.some((p) => p.uuid === pointUuid));
|
||||||
asset.eventData?.point?.uuid === pointUuid ||
|
|
||||||
asset.eventData?.points?.some(p => p.uuid === pointUuid)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hasAsset: (modelUuid) => {
|
hasAsset: (modelUuid) => {
|
||||||
return get().assets.some(a => a.modelUuid === modelUuid);
|
return get().assets.some((a) => a.modelUuid === modelUuid);
|
||||||
}
|
},
|
||||||
}))
|
}))
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export type AssetStoreType = ReturnType<typeof createAssetStore>;
|
export type AssetStoreType = ReturnType<typeof createAssetStore>;
|
||||||
|
|||||||
@@ -4,90 +4,94 @@ import { immer } from "zustand/middleware/immer";
|
|||||||
interface CommentStore {
|
interface CommentStore {
|
||||||
comments: CommentsSchema;
|
comments: CommentsSchema;
|
||||||
|
|
||||||
// Comment operations
|
|
||||||
addComment: (comment: CommentSchema) => void;
|
addComment: (comment: CommentSchema) => void;
|
||||||
setComments: (comments: CommentsSchema) => void;
|
setComments: (comments: CommentsSchema) => void;
|
||||||
updateComment: (threadId: string, updates: Partial<CommentSchema>) => void;
|
updateComment: (threadId: string, updates: Partial<CommentSchema>) => void;
|
||||||
removeComment: (threadId: string) => void;
|
removeComment: (threadId: string) => void;
|
||||||
|
clearComments: () => void;
|
||||||
|
|
||||||
// Reply operations
|
|
||||||
addReply: (threadId: string, reply: Reply) => void;
|
addReply: (threadId: string, reply: Reply) => void;
|
||||||
updateReply: (threadId: string, replyId: string, updates: Partial<Reply>) => void;
|
updateReply: (threadId: string, replyId: string, updates: Partial<Reply>) => void;
|
||||||
removeReply: (threadId: string, _id: string) => void;
|
removeReply: (threadId: string, _id: string) => void;
|
||||||
|
|
||||||
// Getters
|
|
||||||
getCommentById: (threadId: string) => CommentSchema | undefined;
|
getCommentById: (threadId: string) => CommentSchema | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCommentStore = create<CommentStore>()(
|
export const createCommentsStore = () => {
|
||||||
immer((set, get) => ({
|
return create<CommentStore>()(
|
||||||
comments: [],
|
immer((set, get) => ({
|
||||||
|
comments: [],
|
||||||
|
|
||||||
// Comment operations
|
addComment: (comment) => {
|
||||||
addComment: (comment) => {
|
set((state) => {
|
||||||
set((state) => {
|
if (!state.comments.find((c) => c.threadId === comment.threadId)) {
|
||||||
if (!state.comments.find((c) => c.threadId === comment.threadId)) {
|
state.comments.push(JSON.parse(JSON.stringify(comment)));
|
||||||
state.comments.push(JSON.parse(JSON.stringify(comment)));
|
}
|
||||||
}
|
});
|
||||||
});
|
},
|
||||||
},
|
|
||||||
|
|
||||||
setComments: (comments) => {
|
setComments: (comments) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.comments = comments;
|
state.comments = comments;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateComment: (threadId, updates) => {
|
updateComment: (threadId, updates) => {
|
||||||
console.log("threadId:updater ", threadId);
|
set((state) => {
|
||||||
set((state) => {
|
const comment = state.comments.find((c) => c.threadId === threadId);
|
||||||
const comment = state.comments.find((c) => c.threadId === threadId);
|
|
||||||
if (comment) {
|
|
||||||
Object.assign(comment, updates);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeComment: (threadId) => {
|
|
||||||
set((state) => {
|
|
||||||
state.comments = state.comments.filter((c) => c.threadId !== threadId);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Reply operations
|
|
||||||
addReply: (threadId, comment) => {
|
|
||||||
set((state) => {
|
|
||||||
const reply = state.comments.find((c) => c.threadId === threadId);
|
|
||||||
if (reply) {
|
|
||||||
reply.comments.push(comment);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateReply: (threadId, replyId, updates) => {
|
|
||||||
set((state) => {
|
|
||||||
const reply = state.comments.find((c) => c.threadId === threadId);
|
|
||||||
if (reply) {
|
|
||||||
const comment = reply.comments.find((r) => r.replyId === replyId);
|
|
||||||
if (comment) {
|
if (comment) {
|
||||||
Object.assign(comment, updates);
|
Object.assign(comment, updates);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
},
|
||||||
},
|
|
||||||
|
|
||||||
removeReply: (threadId, _id) => {
|
removeComment: (threadId) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const comment = state.comments.find((c) => c.threadId === threadId);
|
state.comments = state.comments.filter((c) => c.threadId !== threadId);
|
||||||
if (comment) {
|
});
|
||||||
comment.comments = comment.comments.filter((r) => r.replyId !== _id);
|
},
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Getter
|
clearComments: () => {
|
||||||
getCommentById: (threadId) => {
|
set((state) => {
|
||||||
return get().comments.find((c) => c.threadId === threadId);
|
state.comments = [];
|
||||||
},
|
});
|
||||||
}))
|
},
|
||||||
);
|
|
||||||
|
addReply: (threadId, comment) => {
|
||||||
|
set((state) => {
|
||||||
|
const reply = state.comments.find((c) => c.threadId === threadId);
|
||||||
|
if (reply) {
|
||||||
|
reply.comments.push(comment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateReply: (threadId, replyId, updates) => {
|
||||||
|
set((state) => {
|
||||||
|
const reply = state.comments.find((c) => c.threadId === threadId);
|
||||||
|
if (reply) {
|
||||||
|
const comment = reply.comments.find((r) => r.replyId === replyId);
|
||||||
|
if (comment) {
|
||||||
|
Object.assign(comment, updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeReply: (threadId, _id) => {
|
||||||
|
set((state) => {
|
||||||
|
const comment = state.comments.find((c) => c.threadId === threadId);
|
||||||
|
if (comment) {
|
||||||
|
comment.comments = comment.comments.filter((r) => r.replyId !== _id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getCommentById: (threadId) => {
|
||||||
|
return get().comments.find((c) => c.threadId === threadId);
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CommentStoreType = ReturnType<typeof createCommentsStore>;
|
||||||
|
|||||||
Reference in New Issue
Block a user