|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
import React, { useMemo, useRef } from 'react';
|
|
|
|
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
|
import { buildMessageItemRow } from './Item';
|
|
|
|
|
import type { MessageListProps } from './types';
|
|
|
|
|
import {
|
|
|
|
@ -11,6 +11,7 @@ import {
|
|
|
|
|
useMemoizedFn,
|
|
|
|
|
useSharedEventHandler,
|
|
|
|
|
} from 'tailchat-shared';
|
|
|
|
|
import { ScrollToBottom } from './ScrollToBottom';
|
|
|
|
|
|
|
|
|
|
const PREPEND_OFFSET = 10 ** 7;
|
|
|
|
|
|
|
|
|
@ -18,6 +19,11 @@ const virtuosoStyle: React.CSSProperties = {
|
|
|
|
|
height: '100%',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const overscan = {
|
|
|
|
|
main: 1000,
|
|
|
|
|
reverse: 1000,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 新版的虚拟列表
|
|
|
|
|
* 参考: https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/VirtualizedMessageList.tsx
|
|
|
|
@ -25,16 +31,18 @@ const virtuosoStyle: React.CSSProperties = {
|
|
|
|
|
export const VirtualizedMessageList: React.FC<MessageListProps> = React.memo(
|
|
|
|
|
(props) => {
|
|
|
|
|
const listRef = useRef<VirtuosoHandle>(null);
|
|
|
|
|
const scrollerRef = useRef<HTMLElement>();
|
|
|
|
|
const numItemsPrepended = usePrependedMessagesCount(props.messages);
|
|
|
|
|
|
|
|
|
|
useSharedEventHandler('sendMessage', () => {
|
|
|
|
|
listRef.current?.scrollToIndex({
|
|
|
|
|
index: 'LAST',
|
|
|
|
|
align: 'end',
|
|
|
|
|
const scrollToBottom = useMemoizedFn(() => {
|
|
|
|
|
listRef.current?.scrollTo({
|
|
|
|
|
top: scrollerRef.current?.scrollHeight,
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useSharedEventHandler('sendMessage', scrollToBottom);
|
|
|
|
|
|
|
|
|
|
const handleLoadMore = useMemoizedFn(() => {
|
|
|
|
|
if (props.isLoadingMore) {
|
|
|
|
|
return;
|
|
|
|
@ -71,27 +79,38 @@ export const VirtualizedMessageList: React.FC<MessageListProps> = React.memo(
|
|
|
|
|
return buildMessageItemRow(props.messages, index);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
|
|
|
|
|
const atBottomStateChange = useMemoizedFn((atBottom: boolean) => {
|
|
|
|
|
if (atBottom) {
|
|
|
|
|
setShowScrollToBottom(false);
|
|
|
|
|
} else {
|
|
|
|
|
setShowScrollToBottom(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<Virtuoso
|
|
|
|
|
style={virtuosoStyle}
|
|
|
|
|
ref={listRef}
|
|
|
|
|
scrollerRef={(ref) => (scrollerRef.current = ref as HTMLElement)}
|
|
|
|
|
firstItemIndex={PREPEND_OFFSET - numItemsPrepended}
|
|
|
|
|
initialTopMostItemIndex={Math.max(props.messages.length - 1, 0)}
|
|
|
|
|
computeItemKey={computeItemKey}
|
|
|
|
|
totalCount={props.messages.length}
|
|
|
|
|
overscan={{
|
|
|
|
|
main: 1000,
|
|
|
|
|
reverse: 1000,
|
|
|
|
|
}}
|
|
|
|
|
overscan={overscan}
|
|
|
|
|
itemContent={itemContent}
|
|
|
|
|
alignToBottom={true}
|
|
|
|
|
startReached={handleLoadMore}
|
|
|
|
|
atBottomStateChange={atBottomStateChange}
|
|
|
|
|
followOutput={followOutput}
|
|
|
|
|
defaultItemHeight={25}
|
|
|
|
|
atTopThreshold={100}
|
|
|
|
|
atBottomThreshold={40}
|
|
|
|
|
useWindowScroll={false}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{showScrollToBottom && <ScrollToBottom onClick={scrollToBottom} />}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|