feat: Add focus handling to textarea in Messages component and implement auto-scrolling in ThreadChat
This commit is contained in:
@@ -165,6 +165,15 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (textareaRef.current) {
|
||||||
|
const length = textareaRef.current.value.length;
|
||||||
|
textareaRef.current.setSelectionRange(length, length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isEditComment ? (
|
{isEditComment ? (
|
||||||
@@ -177,6 +186,7 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
style={{ resize: "none" }}
|
style={{ resize: "none" }}
|
||||||
|
onFocus={handleFocus}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="actions-container">
|
<div className="actions-container">
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ const ThreadChat: React.FC = () => {
|
|||||||
const [messages, setMessages] = useState<Reply[]>([])
|
const [messages, setMessages] = useState<Reply[]>([])
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const [selectedDiv, setSelectedDiv] = useState(true);
|
||||||
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||||||
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
||||||
console.log('position2Dstate: ', position2Dstate);
|
|
||||||
console.log('position:Comment ', position);
|
|
||||||
const { threadSocket } = useSocketStore();
|
const { threadSocket } = useSocketStore();
|
||||||
const modeRef = useRef<'create' | 'edit' | null>(null);
|
const modeRef = useRef<'create' | 'edit' | null>(null);
|
||||||
|
const messagesRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modeRef.current = mode;
|
modeRef.current = mode;
|
||||||
@@ -42,7 +42,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (comments.length > 0 && selectedComment) {
|
if (comments.length > 0 && selectedComment) {
|
||||||
console.log('comments: ', comments);
|
|
||||||
|
|
||||||
const allMessages = comments
|
const allMessages = comments
|
||||||
.flatMap((val: any) =>
|
.flatMap((val: any) =>
|
||||||
@@ -60,7 +60,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setMessages(allMessages);
|
setMessages(allMessages);
|
||||||
console.log('allMessages: ', allMessages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [selectedComment])
|
}, [selectedComment])
|
||||||
@@ -102,16 +102,17 @@ const ThreadChat: React.FC = () => {
|
|||||||
wrapper.setPointerCapture(event.pointerId);
|
wrapper.setPointerCapture(event.pointerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePointerMove = ({ clientX, clientY }: { clientX: number; clientY: number }) => {
|
const updatePosition = (
|
||||||
|
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||||
if (!dragging) return;
|
allowMove: boolean = true
|
||||||
|
) => {
|
||||||
|
if (!allowMove || !wrapperRef.current) return;
|
||||||
|
|
||||||
const container = document.getElementById("work-space-three-d-canvas");
|
const container = document.getElementById("work-space-three-d-canvas");
|
||||||
const wrapper = wrapperRef.current;
|
const wrapper = wrapperRef.current;
|
||||||
if (!container || !wrapper) return;
|
if (!container || !wrapper) return;
|
||||||
|
|
||||||
const containerRect = container.getBoundingClientRect();
|
const containerRect = container.getBoundingClientRect();
|
||||||
const wrapperRect = wrapper.getBoundingClientRect();
|
|
||||||
|
|
||||||
let newX = clientX - containerRect.left - dragOffset.x;
|
let newX = clientX - containerRect.left - dragOffset.x;
|
||||||
let newY = clientY - containerRect.top - dragOffset.y;
|
let newY = clientY - containerRect.top - dragOffset.y;
|
||||||
@@ -125,10 +126,14 @@ const ThreadChat: React.FC = () => {
|
|||||||
setPosition({ x: newX, y: newY });
|
setPosition({ x: newX, y: newY });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (e: { clientX: number; clientY: number }) => {
|
||||||
|
if (dragging) updatePosition(e, true);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// handlePointerMove({ clientX: position.x, clientY: position.y })
|
updatePosition({ clientX: position.x, clientY: position.y }, true);
|
||||||
// console.log('wrapperRef: ', wrapperRef.current);
|
}, [selectedComment]);
|
||||||
}, [selectedComment])
|
|
||||||
|
|
||||||
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
if (!dragging) return;
|
if (!dragging) return;
|
||||||
@@ -171,7 +176,6 @@ const ThreadChat: React.FC = () => {
|
|||||||
} catch {
|
} catch {
|
||||||
|
|
||||||
}
|
}
|
||||||
setValue("")
|
|
||||||
setInputActive(false)
|
setInputActive(false)
|
||||||
}
|
}
|
||||||
const handleDeleteThread = async () => {
|
const handleDeleteThread = async () => {
|
||||||
@@ -260,6 +264,18 @@ const ThreadChat: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
const messagesWrapper = document.querySelector(".messages-wrapper");
|
||||||
|
if (messagesWrapper) {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (messages.length > 0)
|
||||||
|
scrollToBottom();
|
||||||
|
}, [messages])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
@@ -306,7 +322,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="messages-wrapper">
|
<div className="messages-wrapper" ref={messagesRef}>
|
||||||
{selectedComment &&
|
{selectedComment &&
|
||||||
<Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />
|
<Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />
|
||||||
}
|
}
|
||||||
@@ -332,10 +348,20 @@ const ThreadChat: React.FC = () => {
|
|||||||
handleCreateComments(e);
|
handleCreateComments(e);
|
||||||
textareaRef.current?.blur();
|
textareaRef.current?.blur();
|
||||||
}
|
}
|
||||||
|
setValue('')
|
||||||
|
}
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
textareaRef.current?.blur();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onFocus={() => setInputActive(true)}
|
onClick={() => {
|
||||||
|
if (!commentPositionState && selectedComment !== null) {
|
||||||
|
setMode("create");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
autoFocus={selectedComment === null}
|
||||||
onBlur={() => setInputActive(false)}
|
onBlur={() => setInputActive(false)}
|
||||||
|
onFocus={() => setInputActive(true)}
|
||||||
style={{ resize: "none" }}
|
style={{ resize: "none" }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -347,6 +373,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
setMode("create");
|
setMode("create");
|
||||||
handleCreateComments(e);
|
handleCreateComments(e);
|
||||||
}
|
}
|
||||||
|
setValue('')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ExpandIcon />
|
<ExpandIcon />
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function CommentInstance({ comment }: { comment: CommentSchema }) {
|
|||||||
|
|
||||||
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.3) * size.height;
|
const y = (-(position.y * 0.5) + 0.2) * size.height;
|
||||||
setPosition2Dstate({ x, y })
|
setPosition2Dstate({ x, y })
|
||||||
|
|
||||||
if (groupRef.current) {
|
if (groupRef.current) {
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ function CommentInstances() {
|
|||||||
: [],
|
: [],
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
console.log('formattedThreads: ', formattedThreads);
|
// console.log('formattedThreads: ', formattedThreads);
|
||||||
setComments(formattedThreads);
|
setComments(formattedThreads);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch threads:", err);
|
// console.error("Failed to fetch threads:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
// };
|
// };
|
||||||
// threadSocket.on('v1-Comment:response:add', handleAddComment);
|
// threadSocket.on('v1-Comment:response:add', handleAddComment);
|
||||||
const handleAddComment = (data: any) => {
|
const handleAddComment = (data: any) => {
|
||||||
console.log('Add: ', data);
|
// console.log('Add: ', data);
|
||||||
|
|
||||||
const commentData = {
|
const commentData = {
|
||||||
replyId: data.data?._id,
|
replyId: data.data?._id,
|
||||||
@@ -81,7 +81,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
: message;
|
: message;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
console.log('data.data?.comment: ', data.data?.comment);
|
// console.log('data.data?.comment: ', data.data?.comment);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
// --- Delete Comment Handler ---
|
// --- Delete Comment Handler ---
|
||||||
|
|
||||||
const handleDeleteComment = (data: any) => {
|
const handleDeleteComment = (data: any) => {
|
||||||
console.log('delete: ', data);
|
// console.log('delete: ', data);
|
||||||
|
|
||||||
threadSocket.off('v1-Comment:response:delete', handleDeleteComment);
|
threadSocket.off('v1-Comment:response:delete', handleDeleteComment);
|
||||||
};
|
};
|
||||||
@@ -100,7 +100,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
|
|
||||||
// --- Create Thread Handler ---
|
// --- Create Thread Handler ---
|
||||||
const handleCreateThread = (data: any) => {
|
const handleCreateThread = (data: any) => {
|
||||||
console.log('createThread: ', data);
|
// console.log('createThread: ', data);
|
||||||
|
|
||||||
const comment: CommentSchema = {
|
const comment: CommentSchema = {
|
||||||
state: 'active',
|
state: 'active',
|
||||||
@@ -113,9 +113,10 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
comments: [],
|
comments: [],
|
||||||
};
|
};
|
||||||
|
setSelectedComment(comment)
|
||||||
addComment(comment);
|
addComment(comment);
|
||||||
setCommentPositionState(null);
|
setCommentPositionState(null);
|
||||||
setSelectedComment(null);
|
// setSelectedComment(null);
|
||||||
threadSocket.off('v1-thread:response:create', handleCreateThread);
|
threadSocket.off('v1-thread:response:create', handleCreateThread);
|
||||||
};
|
};
|
||||||
threadSocket.on('v1-thread:response:create', handleCreateThread);
|
threadSocket.on('v1-thread:response:create', handleCreateThread);
|
||||||
@@ -142,7 +143,7 @@ const ThreadSocketResponsesDev = ({ setMessages, modeRef, messages }: ThreadSock
|
|||||||
comments: data.data.comments,
|
comments: data.data.comments,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('data.data?._id: ', data.data?._id);
|
// console.log('data.data?._id: ', data.data?._id);
|
||||||
updateComment(data.data?._id, editedThread);
|
updateComment(data.data?._id, editedThread);
|
||||||
setSelectedComment(editedThread)
|
setSelectedComment(editedThread)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR
|
|||||||
|
|
||||||
export const getAllThreads = async (projectId: string) => {
|
export const getAllThreads = async (projectId: string) => {
|
||||||
try {
|
try {
|
||||||
console.log("projectId: ", projectId);
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${url_Backend_dwinzo}/api/v1/Threads/${projectId}`,
|
`${url_Backend_dwinzo}/api/v1/Threads/${projectId}`,
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user