157 lines
5.0 KiB
TypeScript
157 lines
5.0 KiB
TypeScript
|
|
import React, { useEffect, useRef, useState } from "react";
|
||
|
|
import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons";
|
||
|
|
import Messages from "./Messages";
|
||
|
|
import { ExpandIcon } from "../../icons/SimulationIcons";
|
||
|
|
import { adjustHeight } from "./function/textAreaHeightAdjust";
|
||
|
|
|
||
|
|
const ThreadChat: React.FC = () => {
|
||
|
|
const [openThreadOptions, setOpenThreadOptions] = useState(false);
|
||
|
|
const [inputActive, setInputActive] = useState(false);
|
||
|
|
const [value, setValue] = useState<string>("");
|
||
|
|
|
||
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||
|
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||
|
|
|
||
|
|
const [dragging, setDragging] = useState(false);
|
||
|
|
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||
|
|
const [position, setPosition] = useState({ x: 100, y: 100 });
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||
|
|
}, [value]);
|
||
|
|
|
||
|
|
const clamp = (val: number, min: number, max: number) => {
|
||
|
|
return Math.min(Math.max(val, min), max);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||
|
|
if (event.button !== 0) return;
|
||
|
|
|
||
|
|
const wrapper = wrapperRef.current;
|
||
|
|
if (!wrapper) return;
|
||
|
|
|
||
|
|
const rect = wrapper.getBoundingClientRect();
|
||
|
|
const offsetX = event.clientX - rect.left;
|
||
|
|
const offsetY = event.clientY - rect.top;
|
||
|
|
|
||
|
|
setDragging(true);
|
||
|
|
setDragOffset({ x: offsetX, y: offsetY });
|
||
|
|
|
||
|
|
wrapper.setPointerCapture(event.pointerId);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
||
|
|
if (!dragging) 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;
|
||
|
|
|
||
|
|
const maxX = containerRect.width - wrapper.offsetWidth;
|
||
|
|
const maxY = containerRect.height - wrapper.offsetHeight;
|
||
|
|
|
||
|
|
newX = clamp(newX, 0, maxX);
|
||
|
|
newY = clamp(newY, 0, maxY);
|
||
|
|
|
||
|
|
setPosition({ x: newX, y: newY });
|
||
|
|
};
|
||
|
|
|
||
|
|
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||
|
|
if (!dragging) return;
|
||
|
|
setDragging(false);
|
||
|
|
const wrapper = wrapperRef.current;
|
||
|
|
if (wrapper) wrapper.releasePointerCapture(event.pointerId);
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
ref={wrapperRef}
|
||
|
|
className="thread-chat-wrapper"
|
||
|
|
onPointerDown={handlePointerDown}
|
||
|
|
onPointerMove={handlePointerMove}
|
||
|
|
onPointerUp={handlePointerUp}
|
||
|
|
style={{
|
||
|
|
position: "absolute",
|
||
|
|
left: position.x,
|
||
|
|
top: position.y,
|
||
|
|
cursor: dragging ? "grabbing" : "grab",
|
||
|
|
userSelect: "none",
|
||
|
|
zIndex: 9999,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div className="thread-chat-container">
|
||
|
|
<div className="header-wrapper">
|
||
|
|
<div className="header">Comment</div>
|
||
|
|
<div className="header-options">
|
||
|
|
<button
|
||
|
|
className="options-button"
|
||
|
|
onClick={() => setOpenThreadOptions(!openThreadOptions)}
|
||
|
|
>
|
||
|
|
<KebabIcon />
|
||
|
|
</button>
|
||
|
|
{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>
|
||
|
|
)}
|
||
|
|
<button className="close-button">
|
||
|
|
<CloseIcon />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="messages-wrapper">
|
||
|
|
{messages.map((val, i) => (
|
||
|
|
<Messages val={val as any} i={i} key={val.replyId} />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="send-message-wrapper">
|
||
|
|
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
||
|
|
<textarea
|
||
|
|
placeholder="type something"
|
||
|
|
ref={textareaRef}
|
||
|
|
value={value}
|
||
|
|
onChange={(e) => setValue(e.target.value)}
|
||
|
|
onFocus={() => setInputActive(true)}
|
||
|
|
onBlur={() => setInputActive(false)}
|
||
|
|
style={{ resize: "none" }}
|
||
|
|
/>
|
||
|
|
<div className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}>
|
||
|
|
<ExpandIcon />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default ThreadChat;
|