UNPKG

@droppii-org/chat-sdk

Version:

Droppii React Chat SDK

124 lines (123 loc) 11.1 kB
"use client"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { SessionType, } from "@openim/wasm-client-sdk"; import { useChatContext } from "../../context/ChatContext"; import { useMessage } from "../../hooks/message/useMessage"; import { Avatar, Button, Input, Tooltip } from "antd"; import { Icon } from "../icon"; import { useCallback, useEffect, useRef, useState } from "react"; import clsx from "clsx"; import { useSendMessage, } from "../../hooks/message/useSendMessage"; import InfiniteLoader from "react-window-infinite-loader"; import { VariableSizeList as List, } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; const MessageList = (props) => { var _a, _b; const { conversationId, conversationData, onClose } = props; const { messageList, groupMessages, refetch } = useMessage(conversationId); const scrollRef = useRef(null); const shouldScrollToBottomRef = useRef(true); const lastMessageCountRef = useRef((messageList === null || messageList === void 0 ? void 0 : messageList.length) || 0); const { user } = useChatContext(); const { sendTextMessage } = useSendMessage({ recvID: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType) !== SessionType.Single ? "" : (conversationData === null || conversationData === void 0 ? void 0 : conversationData.userID) || "", groupID: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType) === SessionType.Single ? "" : (conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID) || "", }); const [textMessage, setTextMessage] = useState(""); const [composing, setComposing] = useState(false); const [showScrollToBottomButton, setShowScrollToBottomButton] = useState(false); const scrollToBottom = useCallback((force = false) => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; if (force) { shouldScrollToBottomRef.current = true; // If forced, ensure auto-scroll is re-enabled } } }, []); const handleScroll = useCallback(() => { if (!scrollRef.current) return; const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; const distanceFromBottom = scrollHeight - scrollTop - clientHeight; const SCROLL_UP_THRESHOLD = 200; // If user scrolls up more than 200px from bottom, disable auto-scroll const SCROLL_DOWN_THRESHOLD = 5; // If user scrolls within 5px of bottom, re-enable auto-scroll if (distanceFromBottom > SCROLL_UP_THRESHOLD) { shouldScrollToBottomRef.current = false; } else if (distanceFromBottom <= SCROLL_DOWN_THRESHOLD) { shouldScrollToBottomRef.current = true; } // Show button if not at bottom AND auto-scroll is disabled setShowScrollToBottomButton(distanceFromBottom > SCROLL_DOWN_THRESHOLD && !shouldScrollToBottomRef.current); }, []); const onSendTextMessage = useCallback(async () => { setTextMessage(""); const lastMessage = messageList === null || messageList === void 0 ? void 0 : messageList[(messageList === null || messageList === void 0 ? void 0 : messageList.length) - 1]; const res = await sendTextMessage(textMessage, lastMessage); if (res) { refetch(); } }, [textMessage, sendTextMessage, refetch, messageList]); const renderMessageItem = useCallback((listItemProps) => { const { index } = listItemProps; const groupMessage = groupMessages === null || groupMessages === void 0 ? void 0 : groupMessages[index]; const messagesInGroup = (groupMessage === null || groupMessage === void 0 ? void 0 : groupMessage.messages) || []; return (_jsxs("div", { children: [_jsx("div", { children: groupMessage === null || groupMessage === void 0 ? void 0 : groupMessage.sendTime }), messagesInGroup === null || messagesInGroup === void 0 ? void 0 : messagesInGroup.map((message) => { var _a, _b, _c; const isMine = (message === null || message === void 0 ? void 0 : message.sendID) === (user === null || user === void 0 ? void 0 : user.userID); return (_jsx("div", { className: clsx("flex", isMine ? "justify-end" : "justify-start"), children: _jsxs("div", { className: "flex items-end gap-2", children: [!isMine && (_jsx(Avatar, { children: ((_b = (_a = message === null || message === void 0 ? void 0 : message.senderNickname) === null || _a === void 0 ? void 0 : _a.charAt) === null || _b === void 0 ? void 0 : _b.call(_a, 0)) || "A" })), _jsxs("div", { className: "flex flex-col items-start", children: [!isMine && (_jsx("span", { className: "text-xs text-gray-500 mb-1 px-3", children: message === null || message === void 0 ? void 0 : message.senderNickname })), _jsx("div", { className: clsx("px-3 py-2 sm:px-4 sm:py-2 rounded-2xl max-w-full break-words", isMine ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-900"), children: _jsx("p", { className: "text-sm sm:text-base whitespace-pre-wrap", children: ((_c = message === null || message === void 0 ? void 0 : message.textElem) === null || _c === void 0 ? void 0 : _c.content) || "Tin nhắn không khả dụng" }) })] })] }) }, message === null || message === void 0 ? void 0 : message.clientMsgID)); })] })); }, [user === null || user === void 0 ? void 0 : user.userID, groupMessages]); useEffect(() => { const currentMessageCount = (messageList === null || messageList === void 0 ? void 0 : messageList.length) || 0; const previousMessageCount = lastMessageCountRef.current; if (currentMessageCount > previousMessageCount) { const newMessages = messageList === null || messageList === void 0 ? void 0 : messageList.slice(previousMessageCount); const hasNewMessageFromCurrentUser = newMessages === null || newMessages === void 0 ? void 0 : newMessages.some((msg) => msg.sendID === (user === null || user === void 0 ? void 0 : user.userID)); // If current user sent a message, always scroll to bottom // If another user sent a message, only scroll if shouldScrollToBottomRef is true (user is already at bottom) if (hasNewMessageFromCurrentUser) { setTimeout(() => scrollToBottom(true), 50); // Force scroll for own messages } else if (shouldScrollToBottomRef.current) { setTimeout(() => scrollToBottom(), 50); // Scroll if auto-scroll is enabled for others' messages } } lastMessageCountRef.current = currentMessageCount; }, [messageList, user === null || user === void 0 ? void 0 : user.userID, scrollToBottom]); return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full bg-white", children: [_jsxs("div", { className: "px-4 py-3 flex items-center border-b gap-3", children: [_jsx(Avatar, { src: conversationData === null || conversationData === void 0 ? void 0 : conversationData.faceURL, children: ((_b = (_a = conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName) === null || _a === void 0 ? void 0 : _a.charAt) === null || _b === void 0 ? void 0 : _b.call(_a, 0)) || "A" }), _jsxs("div", { className: "flex flex-col flex-1", children: [_jsx("p", { children: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName) || "" }), _jsx("p", { className: "text-xs text-gray-500", children: "2 thành viên" })] }), _jsx(Button, { type: "text", shape: "circle", icon: _jsx(Icon, { icon: "align-justify-o", size: 24 }) }), !!onClose && (_jsx(Button, { type: "text", shape: "circle", icon: _jsx(Icon, { icon: "close-b", size: 24 }), onClick: onClose }))] }), _jsx("div", { className: "flex flex-col flex-1 min-h-0", children: _jsx("div", { className: "relative h-full", children: _jsx(AutoSizer, { children: ({ height, width }) => (_jsx(InfiniteLoader, { isItemLoaded: () => true, loadMoreItems: () => { }, itemCount: (groupMessages === null || groupMessages === void 0 ? void 0 : groupMessages.length) || 0, children: (_a) => { var { onItemsRendered, ref } = _a, rest = __rest(_a, ["onItemsRendered", "ref"]); return (_jsx(List, Object.assign({ ref: ref }, rest, { itemCount: (groupMessages === null || groupMessages === void 0 ? void 0 : groupMessages.length) || 0, onItemsRendered: onItemsRendered, height: height, width: width, itemSize: (index) => { const groupMessage = groupMessages === null || groupMessages === void 0 ? void 0 : groupMessages[index]; const messagesInGroup = (groupMessage === null || groupMessage === void 0 ? void 0 : groupMessage.messages) || []; return (messagesInGroup === null || messagesInGroup === void 0 ? void 0 : messagesInGroup.length) * 50; }, children: renderMessageItem }))); } })) }) }) }), _jsx("div", { className: "border-t px-4 py-3", children: _jsx("div", { className: "border rounded-lg bg-gray-50", children: _jsxs("div", { className: "px-4 py-3 flex items-center gap-4", children: [_jsx(Input, { placeholder: "Nh\u1EADp tin nh\u1EAFn", size: "small", variant: "borderless", value: textMessage, onChange: (e) => setTextMessage(e.target.value), onKeyDown: (e) => { if (composing) { return; } if (e.key === "Enter") { onSendTextMessage(); } }, onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false) }), _jsx(Tooltip, { title: "G\u1EEDi tin nh\u1EAFn", children: _jsx(Button, { type: "primary", shape: "circle", size: "middle", icon: _jsx(Icon, { icon: "send-b", color: "white", size: 16 }), disabled: textMessage.length === 0, onClick: onSendTextMessage }) })] }) }) })] })); }; export default MessageList;