updated
This commit is contained in:
@@ -3,36 +3,67 @@ import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons";
|
||||
import Messages from "./Messages";
|
||||
import { ExpandIcon } from "../../icons/SimulationIcons";
|
||||
import { adjustHeight } from "./function/textAreaHeightAdjust";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSelectedComment, useSocketStore } from "../../../store/builder/store";
|
||||
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import ThreadSocketResponsesDev from "../../../modules/collaboration/socket/threadSocketResponses.dev";
|
||||
import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi";
|
||||
import { deleteThreadApi } from "../../../services/factoryBuilder/comments/deleteThreadApi";
|
||||
import { createThreadApi } from "../../../services/factoryBuilder/comments/createThreadApi";
|
||||
import { getRelativeTime } from "./function/getRelativeTime";
|
||||
|
||||
|
||||
const ThreadChat: React.FC = () => {
|
||||
const { userId, organization } = getUserData();
|
||||
const [openThreadOptions, setOpenThreadOptions] = useState(false);
|
||||
const [inputActive, setInputActive] = useState(false);
|
||||
const [value, setValue] = useState<string>("");
|
||||
|
||||
const { addComment, removeReply, removeComment, addReply, comments } = useCommentStore();
|
||||
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState, position2Dstate } = useSelectedComment()
|
||||
const [mode, setMode] = useState<'create' | 'edit' | null>('create');
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const [editedThread, setEditedThread] = useState(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [messages, setMessages] = useState<Reply[]>([])
|
||||
const { projectId } = useParams();
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [selectedDiv, setSelectedDiv] = useState(true);
|
||||
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||||
const [position, setPosition] = useState({ x: 100, y: 100 });
|
||||
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
||||
const { threadSocket } = useSocketStore();
|
||||
const modeRef = useRef<'create' | 'edit' | null>(null);
|
||||
const messagesRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const messages = [
|
||||
{
|
||||
replyId: "user 1",
|
||||
creatorId: "1",
|
||||
createdAt: "2 hrs ago",
|
||||
lastUpdatedAt: "2 hrs ago",
|
||||
reply:
|
||||
"reply testing reply content 1, reply testing reply content 1reply testing reply content 1",
|
||||
},
|
||||
{
|
||||
replyId: "user 2",
|
||||
creatorId: "2",
|
||||
createdAt: "2 hrs ago",
|
||||
lastUpdatedAt: "2 hrs ago",
|
||||
reply: "reply 2",
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
modeRef.current = mode;
|
||||
}, [mode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (comments.length > 0 && selectedComment) {
|
||||
|
||||
|
||||
const allMessages = comments
|
||||
.flatMap((val: any) =>
|
||||
val?.threadId === selectedComment?.threadId ? val.comments : []
|
||||
)
|
||||
.map((c) => {
|
||||
return {
|
||||
replyId: c._id ?? "",
|
||||
creatorId: c.creatorId || c.userId,
|
||||
createdAt: c.createdAt,
|
||||
lastUpdatedAt: "1 hr ago",
|
||||
comment: c.comment,
|
||||
_id: c._id ?? "",
|
||||
};
|
||||
});
|
||||
|
||||
setMessages(allMessages);
|
||||
|
||||
}
|
||||
|
||||
}, [selectedComment])
|
||||
|
||||
useEffect(() => {
|
||||
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||||
@@ -44,6 +75,19 @@ const ThreadChat: React.FC = () => {
|
||||
|
||||
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button !== 0) return;
|
||||
// Avoid dragging if a button, icon, textarea etc. was clicked
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest("button") ||
|
||||
target.closest(".sent-button") ||
|
||||
target.closest("textarea") ||
|
||||
target.closest(".options-button") ||
|
||||
target.closest(".options-list") ||
|
||||
target.closest(".send-message-wrapper") ||
|
||||
target.closest(".options delete")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = wrapperRef.current;
|
||||
if (!wrapper) return;
|
||||
@@ -58,18 +102,20 @@ const ThreadChat: React.FC = () => {
|
||||
wrapper.setPointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragging) 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;
|
||||
if (!container || !wrapper) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const wrapperRect = wrapper.getBoundingClientRect();
|
||||
|
||||
let newX = event.clientX - containerRect.left - dragOffset.x;
|
||||
let newY = event.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;
|
||||
@@ -80,6 +126,15 @@ const ThreadChat: React.FC = () => {
|
||||
setPosition({ x: newX, y: newY });
|
||||
};
|
||||
|
||||
const handlePointerMove = (e: { clientX: number; clientY: number }) => {
|
||||
if (dragging) updatePosition(e, true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updatePosition({ clientX: position.x, clientY: position.y }, true);
|
||||
}, [selectedComment]);
|
||||
|
||||
|
||||
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragging) return;
|
||||
setDragging(false);
|
||||
@@ -87,12 +142,146 @@ const ThreadChat: React.FC = () => {
|
||||
if (wrapper) wrapper.releasePointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const handleCreateComments = async (e: any) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
// const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId)/
|
||||
// if (createComments.message === 'Thread comments add Successfully' && createComments.data) {
|
||||
// const commentData = {
|
||||
// replyId: `${createComments.data?._id}`,
|
||||
// creatorId: `${selectedComment?.threadId}`,
|
||||
// createdAt: "2 hrs ago",
|
||||
// lastUpdatedAt: "2 hrs ago",
|
||||
// comment: value,
|
||||
// }
|
||||
// setMessages((prevMessages) => [
|
||||
// ...prevMessages,
|
||||
// commentData,
|
||||
// ]);
|
||||
// addReply(selectedComment?.threadId, commentData)
|
||||
|
||||
// }
|
||||
|
||||
if (threadSocket && mode === "create") {
|
||||
const addComment = {
|
||||
projectId,
|
||||
userId,
|
||||
comment: value,
|
||||
organization,
|
||||
threadId: selectedComment?.threadId
|
||||
}
|
||||
|
||||
threadSocket.emit("v1-Comment:add", addComment);
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
setInputActive(false)
|
||||
}
|
||||
const handleDeleteThread = async () => {
|
||||
if (!projectId) return;
|
||||
try {
|
||||
// const deleteThread = await deleteThreadApi(projectId, selectedComment?.threadId)
|
||||
//
|
||||
// if (deleteThread.message === "Thread deleted Successfully") {
|
||||
// removeComment(selectedComment?.threadId)
|
||||
// setSelectedComment([])
|
||||
// }
|
||||
if (threadSocket) {
|
||||
// projectId, userId, organization, threadId
|
||||
const deleteThread = {
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
threadId: selectedComment?.threadId
|
||||
}
|
||||
|
||||
setSelectedComment(null)
|
||||
removeComment(selectedComment?.threadId)
|
||||
threadSocket.emit("v1:thread:delete", deleteThread);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateThread = async (e: any) => {
|
||||
e.preventDefault();
|
||||
if (!projectId) return;
|
||||
|
||||
try {
|
||||
// try {
|
||||
// const thread = await createThreadApi(
|
||||
// projectId,
|
||||
// "active",
|
||||
// commentPositionState[0].position,
|
||||
// [0, 0, 0],
|
||||
// value
|
||||
// );
|
||||
//
|
||||
//
|
||||
|
||||
// if (thread.message === "Thread created Successfully" && thread?.threadData) {
|
||||
//
|
||||
// const comment: CommentSchema = {
|
||||
// state: 'active',
|
||||
// threadId: thread?.threadData?._id,
|
||||
// creatorId: userId,
|
||||
// createdAt: getRelativeTime(thread.threadData?.createdAt),
|
||||
// threadTitle: value,
|
||||
// lastUpdatedAt: new Date().toISOString(),
|
||||
// position: commentPositionState[0].position,
|
||||
// rotation: [0, 0, 0],
|
||||
// comments: []
|
||||
// }
|
||||
// addComment(comment);
|
||||
// setCommentPositionState(null);
|
||||
// setInputActive(false);
|
||||
// setSelectedComment([])
|
||||
// }
|
||||
|
||||
const createThread = {
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
state: "active",
|
||||
position: commentPositionState.position,
|
||||
rotation: [0, 0, 0],
|
||||
threadTitle: value
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
if (threadSocket) {
|
||||
|
||||
setInputActive(false);
|
||||
threadSocket.emit("v1:thread:create", createThread);
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const messagesWrapper = document.querySelector(".messages-wrapper");
|
||||
if (messagesWrapper) {
|
||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (messages.length > 0)
|
||||
scrollToBottom();
|
||||
}, [messages])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="thread-chat-wrapper"
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerMove={(e) => handlePointerMove({ clientX: e.clientX, clientY: e.clientY })}
|
||||
onPointerUp={handlePointerUp}
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -107,49 +296,94 @@ const ThreadChat: React.FC = () => {
|
||||
<div className="header-wrapper">
|
||||
<div className="header">Comment</div>
|
||||
<div className="header-options">
|
||||
<button
|
||||
<div
|
||||
className="options-button"
|
||||
onClick={() => setOpenThreadOptions(!openThreadOptions)}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setOpenThreadOptions((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<KebabIcon />
|
||||
</button>
|
||||
</div>
|
||||
{openThreadOptions && (
|
||||
<div className="options-list">
|
||||
<div className="options">Mark as Unread</div>
|
||||
<div className="options">Mark as Resolved</div>
|
||||
<div className="options delete">Delete Thread</div>
|
||||
<div className="options delete" onClick={handleDeleteThread}>Delete Thread</div>
|
||||
</div>
|
||||
)}
|
||||
<button className="close-button">
|
||||
<button className="close-button" onClick={() => {
|
||||
setSelectedComment(null)
|
||||
setCommentPositionState(null)
|
||||
}}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="messages-wrapper">
|
||||
{messages.map((val, i) => (
|
||||
<Messages val={val as any} i={i} key={val.replyId} />
|
||||
<div className="messages-wrapper" ref={messagesRef}>
|
||||
{selectedComment &&
|
||||
<Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />
|
||||
}
|
||||
{messages && messages.map((val, i) => (
|
||||
<Messages val={val as any} i={i} key={val.replyId} setMessages={setMessages} setIsEditable={setIsEditable} isEditable={isEditable} isEditableThread={false} setMode={setMode} mode={mode} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="send-message-wrapper">
|
||||
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
||||
<textarea
|
||||
placeholder="type something"
|
||||
placeholder={commentPositionState && selectedComment === null ? "Type Thread Title" : "type something"}
|
||||
ref={textareaRef}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onFocus={() => setInputActive(true)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (commentPositionState && selectedComment === null) {
|
||||
handleCreateThread(e);
|
||||
} else {
|
||||
setMode("create");
|
||||
handleCreateComments(e);
|
||||
textareaRef.current?.blur();
|
||||
}
|
||||
setValue('')
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
textareaRef.current?.blur();
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!commentPositionState && selectedComment !== null) {
|
||||
setMode("create");
|
||||
}
|
||||
}}
|
||||
autoFocus={selectedComment === null}
|
||||
onBlur={() => setInputActive(false)}
|
||||
onFocus={() => setInputActive(true)}
|
||||
style={{ resize: "none" }}
|
||||
/>
|
||||
<div className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}>
|
||||
<div
|
||||
className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}
|
||||
onClick={(e) => {
|
||||
if (commentPositionState && selectedComment === null) {
|
||||
handleCreateThread(e);
|
||||
} else {
|
||||
setMode("create");
|
||||
handleCreateComments(e);
|
||||
}
|
||||
setValue('')
|
||||
}}
|
||||
>
|
||||
<ExpandIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ThreadSocketResponsesDev setMessages={setMessages} modeRef={modeRef} messages={messages} />
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user