UNPKG

@uimkit/uikit-react

Version:

<img style="width:64px" src="https://mgmt.uimkit.chat/media/img/avatar.png"/>

235 lines (232 loc) 16.1 kB
import { __rest, __assign, __awaiter, __generator } from 'tslib'; import React__default, { useMemo, useRef, useCallback, useEffect, useState } from 'react'; import '../../types/models.js'; import '../../types/events.js'; import '../../context/TranslationContext.js'; import '../../context/UIKitContext.js'; import { useComponentContext } from '../../context/ComponentContext.js'; import { useChatActionContext } from '../../context/ChatActionContext.js'; import '../../context/MessageInputContext.js'; import '../../context/UIMessageContext.js'; import { useChatStateContext } from '../../context/ChatStateContext.js'; import { EmptyStateIndicator } from '../EmptyStateIndicator/EmptyStateIndicator.js'; import { MessageListNotifications } from './MessageListNotifications.js'; import '../Loading/LoadingErrorIndicator.js'; import { LoadingIndicator } from '../Loading/LoadingIndicator.js'; import { MessageNotification } from './MessageNotification.js'; import { Virtuoso } from '../../node_modules/.pnpm/react-virtuoso@4.1.0_biqbaboplfbrettd7655fr4n2y/node_modules/react-virtuoso/dist/index.mjs.js'; import { DateSeparator } from '../DateSeparator/DateSeparator.js'; import { useShouldForceScrollToBottom } from './hooks/useShouldForceScrollToBottom.js'; import { useNewMessageNotification } from './hooks/useNewMessageNotification.js'; import { usePrependedMessagesCount } from './hooks/usePrependedMessagesCount.js'; import { UIMessage } from '../UIMessage/UIMessage.js'; import '../UIMessage/MessagePlugins.js'; import '../UIMessage/MessageStatus.js'; import '../UIMessage/MessageProgress.js'; var PREPEND_OFFSET = Math.pow(10, 7); function captureResizeObserverExceededError(e) { if (e.message === 'ResizeObserver loop completed with undelivered notifications.' || e.message === 'ResizeObserver loop limit exceeded') { e.stopImmediatePropagation(); } } function useCaptureResizeObserverExceededError() { useEffect(function () { window.addEventListener('error', captureResizeObserverExceededError); return function () { window.removeEventListener('error', captureResizeObserverExceededError); }; }, []); } function fractionalItemSize(element) { return element.getBoundingClientRect().height; } function findMessageIndex(messages, id) { return messages.findIndex(function (message) { return message.id === id; }); } function calculateInitialTopMostItemIndex(messages, highlightedMessageId) { if (highlightedMessageId) { var index = findMessageIndex(messages, highlightedMessageId); if (index !== -1) { console.log('calculateInitialTopMostItemIndex: ', index); return { align: 'center', index: index }; } } return messages.length - 1; } var VirtualizedMessageListWithContext = function (props) { var additionalVirtuosoProps = props.additionalVirtuosoProps, conversation = props.conversation, messages = props.messages, highlightedMessageId = props.highlightedMessageId, intervalsTimer = props.intervalsTimer, hasMore = props.hasMore, loadMore = props.loadMore, loadingMore = props.loadingMore, hasMoreNewer = props.hasMoreNewer, loadMoreNewer = props.loadMoreNewer, loadingMoreNewer = props.loadingMoreNewer, suppressAutoscroll = props.suppressAutoscroll, jumpToLatestMessage = props.jumpToLatestMessage, head = props.head, defaultItemHeight = props.defaultItemHeight, customMessageRenderer = props.customMessageRenderer, _a = props.stickToBottomScrollBehavior, stickToBottomScrollBehavior = _a === void 0 ? 'smooth' : _a, _b = props.overscan, overscan = _b === void 0 ? 0 : _b, scrollSeekPlaceHolder = props.scrollSeekPlaceHolder, _c = props.scrollToLatestMessageOnFocus, scrollToLatestMessageOnFocus = _c === void 0 ? false : _c, propMessage = props.UIMessage; useCaptureResizeObserverExceededError(); var _d = useComponentContext('VirtualizedMessageList'), _e = _d.UIMessage, contextMessage = _e === void 0 ? UIMessage : _e, _f = _d.EmptyStateIndicator, EmptyStateIndicator$1 = _f === void 0 ? EmptyStateIndicator : _f, _g = _d.LoadingIndicator, LoadingIndicator$1 = _g === void 0 ? LoadingIndicator : _g, _h = _d.MessageListNotifications, MessageListNotifications$1 = _h === void 0 ? MessageListNotifications : _h, _j = _d.MessageNotification, MessageNotification$1 = _j === void 0 ? MessageNotification : _j; var MessageUIComponent = propMessage || contextMessage; var processedMessages = useMemo(function () { if (typeof messages === 'undefined') { return []; } return messages; }, [ messages, messages === null || messages === void 0 ? void 0 : messages.length, ]); var virtuoso = useRef(null); var _k = useNewMessageNotification(processedMessages, conversation.account, hasMoreNewer), atBottom = _k.atBottom, isMessageListScrolledToBottom = _k.isMessageListScrolledToBottom, newMessagesNotification = _k.newMessagesNotification, setIsMessageListScrolledToBottom = _k.setIsMessageListScrolledToBottom, setNewMessagesNotification = _k.setNewMessagesNotification; var scrollToBottom = useCallback(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (!hasMoreNewer) return [3 /*break*/, 2]; return [4 /*yield*/, jumpToLatestMessage()]; case 1: _a.sent(); return [2 /*return*/]; case 2: if (virtuoso.current) { virtuoso.current.scrollToIndex(processedMessages.length - 1); } setNewMessagesNotification(false); return [2 /*return*/]; } }); }); }, [ virtuoso, processedMessages, setNewMessagesNotification, // processedMessages were incorrectly rebuilt with a new object identity at some point, hence the .length usage processedMessages.length, hasMoreNewer, jumpToLatestMessage, ]); var _l = React__default.useState(false), newMessagesReceivedInBackground = _l[0], setNewMessagesReceivedInBackground = _l[1]; var resetNewMessagesReceivedInBackground = useCallback(function () { setNewMessagesReceivedInBackground(false); }, []); useEffect(function () { setNewMessagesReceivedInBackground(true); }, [messages]); var scrollToBottomIfConfigured = useCallback(function (event) { if (scrollToLatestMessageOnFocus && event.target === window) { if (newMessagesReceivedInBackground) { setTimeout(scrollToBottom, 100); } } }, [scrollToLatestMessageOnFocus, scrollToBottom, newMessagesReceivedInBackground]); useEffect(function () { if (typeof window !== 'undefined') { window.addEventListener('focus', scrollToBottomIfConfigured); window.addEventListener('blur', resetNewMessagesReceivedInBackground); } return function () { window.removeEventListener('focus', scrollToBottomIfConfigured); window.removeEventListener('blur', resetNewMessagesReceivedInBackground); }; }, [scrollToBottomIfConfigured]); // 在前面追加的消息数,也就是 loadMore 加载的消息总量 var numItemsPrepended = usePrependedMessagesCount(processedMessages); var _m = useState(+new Date()), messageSetKey = _m[0], setMessageSetKey = _m[1]; var firstMessageId = useRef(); useEffect(function () { var _a; var continuousSet = messages === null || messages === void 0 ? void 0 : messages.find(function (message) { return message.id === firstMessageId.current; }); if (!continuousSet) { setMessageSetKey(+new Date()); } firstMessageId.current = (_a = messages === null || messages === void 0 ? void 0 : messages[0]) === null || _a === void 0 ? void 0 : _a.id; }, [messages]); // 是否要强制滚动到最底部 var shouldForceScrollToBottom = useShouldForceScrollToBottom(processedMessages, conversation.account); // 列表 totalCount 改变时调用 var followOutput = function (isAtBottom) { if (hasMoreNewer || suppressAutoscroll) { return false; } if (shouldForceScrollToBottom()) { return isAtBottom ? stickToBottomScrollBehavior : 'auto'; } // a message from another user has been received - don't scroll to bottom unless already there return isAtBottom ? stickToBottomScrollBehavior : false; }; var messageRenderer = useCallback(function (messages, virtuosoIndex) { var _a, _b; var messageIndex = virtuosoIndex + numItemsPrepended - PREPEND_OFFSET; // use custom renderer supplied by client if present and skip the rest if (customMessageRenderer) { return customMessageRenderer(messages, messageIndex); } var message = messages[messageIndex]; var preMessageTimer = messageIndex > 0 ? (_a = messages[messageIndex - 1]) === null || _a === void 0 ? void 0 : _a.sent_at : -1; var currrentTimer = (_b = message === null || message === void 0 ? void 0 : message.sent_at) !== null && _b !== void 0 ? _b : 0; var isShowIntervalsTimer = preMessageTimer !== -1 ? (currrentTimer - preMessageTimer) >= intervalsTimer : false; if (!message) return React__default.createElement("div", { style: { height: '1px' } }); // returning null or zero height breaks the virtuoso return (React__default.createElement("li", { className: "message-list-item" }, isShowIntervalsTimer && React__default.createElement(DateSeparator, { date: currrentTimer ? new Date(currrentTimer) : null }), React__default.createElement(MessageUIComponent, { message: message }))); }, [customMessageRenderer, numItemsPrepended]); var virtuosoComponents = useMemo(function () { var EmptyPlaceholder = function () { return (React__default.createElement(React__default.Fragment, null, EmptyStateIndicator$1 && (React__default.createElement(EmptyStateIndicator$1, { listType: 'message' })))); }; var Header = function () { return loadingMore ? (React__default.createElement("div", { className: 'uim__virtual-list__loading' }, React__default.createElement(LoadingIndicator$1, { size: 20 }))) : (head || null); }; /* const Footer: Components['Footer'] = () => TypingIndicator ? <TypingIndicator avatarSize={24} /> : <></>; */ var Footer = function () { return React__default.createElement(React__default.Fragment, null); }; return { EmptyPlaceholder: EmptyPlaceholder, Footer: Footer, Header: Header, }; }, [loadingMore, head]); var atBottomStateChange = function (isAtBottom) { atBottom.current = isAtBottom; setIsMessageListScrolledToBottom(isAtBottom); if (isAtBottom && newMessagesNotification) { setNewMessagesNotification(false); } }; var startReached = function () { console.log('startReached hasMore: ', hasMore, !!loadMore); if (hasMore && loadMore) { loadMore(); } }; var endReached = function () { console.log('endReached hasMoreNewer: ', hasMoreNewer, !!loadMoreNewer); if (hasMoreNewer && loadMoreNewer) { loadMoreNewer(); } }; useEffect(function () { var _a; if (highlightedMessageId) { var index = findMessageIndex(processedMessages, highlightedMessageId); if (index !== -1) { (_a = virtuoso.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex({ align: 'center', index: index }); } } }, [highlightedMessageId]); console.log("\n messages: ".concat(processedMessages.length, ",\n hasMore: ").concat(hasMore, ",\n hasMoreNewer: ").concat(hasMoreNewer, ",\n loadingMore: ").concat(loadingMore, ",\n loadingMoreNewer: ").concat(loadingMoreNewer, "\n ")); return (React__default.createElement("div", { className: "uim-message-list" }, React__default.createElement(Virtuoso, __assign({ atBottomStateChange: atBottomStateChange, atBottomThreshold: 200, className: 'uim__message-list-scroll', components: virtuosoComponents, computeItemKey: function (index) { return processedMessages[numItemsPrepended + index - PREPEND_OFFSET].id; }, endReached: endReached, firstItemIndex: PREPEND_OFFSET - numItemsPrepended, followOutput: followOutput, increaseViewportBy: { bottom: 200, top: 0 }, initialTopMostItemIndex: calculateInitialTopMostItemIndex(processedMessages, highlightedMessageId), itemContent: function (i) { return messageRenderer(processedMessages, i); }, itemSize: fractionalItemSize, key: messageSetKey, overscan: overscan, ref: virtuoso, startReached: startReached, style: { overflowX: 'hidden' }, totalCount: processedMessages.length }, additionalVirtuosoProps, (scrollSeekPlaceHolder ? { scrollSeek: scrollSeekPlaceHolder } : {}), (defaultItemHeight ? { defaultItemHeight: defaultItemHeight } : {}))), React__default.createElement(MessageListNotifications$1, { hasNewMessages: newMessagesNotification, isMessageListScrolledToBottom: isMessageListScrolledToBottom, isNotAtLatestMessageSet: hasMoreNewer, MessageNotification: MessageNotification$1, scrollToBottom: scrollToBottom }))); }; var VirtualizedMessageList = function (props) { var propMessages = props.messages, propsIntervalsTimer = props.intervalsTimer, propHasMore = props.hasMore, propLoadMore = props.loadMore, propLoadingMore = props.loadingMore, propHasMoreNewer = props.hasMoreNewer; props.loadingMoreNewer; var propLoadMoreNewer = props.loadMoreNewer, rest = __rest(props, ["messages", "intervalsTimer", "hasMore", "loadMore", "loadingMore", "hasMoreNewer", "loadingMoreNewer", "loadMoreNewer"]); var _a = useChatActionContext('VirtualizedMessageList'), jumpToLatestMessage = _a.jumpToLatestMessage, contextLoadMore = _a.loadMore, contextLoadMoreNewer = _a.loadMoreNewer; var _b = useChatStateContext('VirtualizedMessageList'), conversation = _b.conversation, highlightedMessageId = _b.highlightedMessageId, suppressAutoscroll = _b.suppressAutoscroll, contextMessages = _b.messages, contextHasMore = _b.hasMore, contextLoadingMore = _b.loadingMore, contextHasMoreNewer = _b.hasMoreNewer, contextLoadingMoreNewer = _b.loadingMoreNewer; var hasMore = propHasMore !== null && propHasMore !== void 0 ? propHasMore : contextHasMore; var loadMore = propLoadMore !== null && propLoadMore !== void 0 ? propLoadMore : contextLoadMore; var loadingMore = propLoadingMore !== null && propLoadingMore !== void 0 ? propLoadingMore : contextLoadingMore; var hasMoreNewer = propHasMoreNewer !== null && propHasMoreNewer !== void 0 ? propHasMoreNewer : contextHasMoreNewer; var loadingMoreNewer = propHasMoreNewer !== null && propHasMoreNewer !== void 0 ? propHasMoreNewer : contextLoadingMoreNewer; var loadMoreNewer = propLoadMoreNewer !== null && propLoadMoreNewer !== void 0 ? propLoadMoreNewer : contextLoadMoreNewer; var intervalsTimer = (propsIntervalsTimer !== null && propsIntervalsTimer !== void 0 ? propsIntervalsTimer : 30) * 60; var messages = propMessages || contextMessages; return (React__default.createElement(VirtualizedMessageListWithContext, __assign({ conversation: conversation, hasMore: !!hasMore, hasMoreNewer: !!hasMoreNewer, highlightedMessageId: highlightedMessageId, jumpToLatestMessage: jumpToLatestMessage, loadingMore: !!loadingMore, loadingMoreNewer: !!loadingMoreNewer, loadMore: loadMore, loadMoreNewer: loadMoreNewer, messages: messages, suppressAutoscroll: suppressAutoscroll, intervalsTimer: intervalsTimer }, rest))); }; export { VirtualizedMessageList }; //# sourceMappingURL=VirtualizedMessageList.js.map