@uimkit/uikit-react
Version:
<img style="width:64px" src="https://mgmt.uimkit.chat/media/img/avatar.png"/>
235 lines (232 loc) • 16.1 kB
JavaScript
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