mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-02-11 17:55:49 +00:00
fix chat list scroll issue
This commit is contained in:
parent
e4078e3976
commit
35f299b6cb
2347
package-lock.json
generated
2347
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mime": "^4.0.4",
|
||||
"moment": "^2.30.1",
|
||||
"npm": "^10.8.3",
|
||||
"quill-image-resize-module-react": "^3.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
@ -53,6 +54,7 @@
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"react-virtuoso": "^4.10.4",
|
||||
"short-unique-id": "^5.2.0",
|
||||
"slate": "^0.103.0",
|
||||
"slate-react": "^0.109.0",
|
||||
|
@ -484,13 +484,15 @@ const clearEditorContent = () => {
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset'
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
overflow: !isMobile && "auto",
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{replyMessage && (
|
||||
<Box sx={{
|
||||
|
@ -416,13 +416,15 @@ const clearEditorContent = () => {
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset'
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
overflow: !isMobile && "auto",
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{replyMessage && (
|
||||
<Box sx={{
|
||||
|
@ -1,26 +1,58 @@
|
||||
import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { List, AutoSizer, CellMeasurerCache, CellMeasurer } from 'react-virtualized';
|
||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { MessageItem } from './MessageItem';
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
||||
|
||||
// const cache = new CellMeasurerCache({
|
||||
// fixedWidth: true,
|
||||
// defaultHeight: 50,
|
||||
// });
|
||||
|
||||
export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply }) => {
|
||||
|
||||
const hasLoadedInitialRef = useRef(false);
|
||||
const listRef = useRef();
|
||||
const virtuosoRef = useRef();
|
||||
const [messages, setMessages] = useState(initialMessages);
|
||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
const cache = useMemo(() => new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 50,
|
||||
}), [chatId]); // Recreate cache when chatId changes
|
||||
const hasLoadedInitialRef = useRef(false);
|
||||
const isAtBottomRef = useRef(true); //
|
||||
// Update message list with unique signatures and tempMessages
|
||||
useEffect(() => {
|
||||
cache.clearAll();
|
||||
}, []);
|
||||
let uniqueInitialMessagesMap = new Map();
|
||||
|
||||
initialMessages.forEach((message) => {
|
||||
uniqueInitialMessagesMap.set(message.signature, message);
|
||||
});
|
||||
|
||||
const uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort(
|
||||
(a, b) => a.timestamp - b.timestamp
|
||||
);
|
||||
const totalMessages = [...uniqueInitialMessages, ...(tempMessages || [])];
|
||||
|
||||
if (totalMessages.length === 0) return;
|
||||
|
||||
setMessages(totalMessages);
|
||||
|
||||
setTimeout(() => {
|
||||
const hasUnreadMessages = totalMessages.some((msg) => msg.unread);
|
||||
|
||||
if (virtuosoRef.current) {
|
||||
|
||||
|
||||
if (virtuosoRef.current && !isAtBottomRef.current) {
|
||||
|
||||
|
||||
|
||||
|
||||
setShowScrollButton(hasUnreadMessages);
|
||||
} else {
|
||||
handleMessageSeen();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (!hasLoadedInitialRef.current) {
|
||||
scrollToBottom(totalMessages);
|
||||
hasLoadedInitialRef.current = true;
|
||||
}
|
||||
}, 500);
|
||||
}, [initialMessages, tempMessages]);
|
||||
|
||||
|
||||
|
||||
const handleMessageSeen = useCallback(() => {
|
||||
setMessages((prevMessages) =>
|
||||
@ -31,72 +63,50 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleScroll = ({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
const scrollToItem = useCallback((index) => {
|
||||
if (virtuosoRef.current) {
|
||||
virtuosoRef.current.scrollToIndex({ index, behavior: 'smooth' });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const scrollToBottom = (initialMsgs) => {
|
||||
console.log('initialMsgs', {
|
||||
initialMsgs,
|
||||
messages
|
||||
})
|
||||
const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1
|
||||
if (virtuosoRef.current) {
|
||||
virtuosoRef.current.scrollToIndex({ index, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleScroll = (scrollState) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollState;
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
|
||||
const hasUnreadMessages = messages.some((msg) => msg.unread);
|
||||
if(isAtBottom){
|
||||
handleMessageSeen()
|
||||
|
||||
if (isAtBottom) {
|
||||
handleMessageSeen();
|
||||
}
|
||||
|
||||
setShowScrollButton(!isAtBottom && hasUnreadMessages);
|
||||
};
|
||||
|
||||
const debounce = (func, delay) => {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func(...args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
const handleScrollDebounced = debounce(handleScroll, 100);
|
||||
|
||||
const scrollToBottom = (initialmsgs) => {
|
||||
if (listRef.current) {
|
||||
const msgs = initialmsgs?.length ? initialmsgs : messages
|
||||
|
||||
listRef.current?.recomputeRowHeights();
|
||||
|
||||
listRef.current.scrollToRow(msgs.length - 1);
|
||||
setTimeout(() => {
|
||||
|
||||
listRef.current.scrollToRow(msgs.length - 1);
|
||||
}, 100);
|
||||
setShowScrollButton(false);
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToItem = useCallback((index) => {
|
||||
listRef.current.scrollToRow(index); // This scrolls to the specific index
|
||||
}, []);
|
||||
|
||||
const recomputeListHeights = () => {
|
||||
if (listRef.current) {
|
||||
listRef.current.recomputeRowHeights();
|
||||
}
|
||||
};
|
||||
|
||||
const sentNewMessageGroupFunc = ()=> {
|
||||
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("sent-new-message-group", sentNewMessageGroupFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("sent-new-message-group", sentNewMessageGroupFunc);
|
||||
};
|
||||
const sentNewMessageGroupFunc = useCallback(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent('sent-new-message-group', sentNewMessageGroupFunc);
|
||||
return () => {
|
||||
unsubscribeFromEvent('sent-new-message-group', sentNewMessageGroupFunc);
|
||||
};
|
||||
}, [sentNewMessageGroupFunc]);
|
||||
|
||||
const rowRenderer = ({ index, key, parent, style }) => {
|
||||
const rowRenderer = (index) => {
|
||||
let message = messages[index];
|
||||
const isLargeMessage = message.text?.length > 200; // Adjust based on your message size threshold
|
||||
|
||||
// const reply = message?.repliedTo ? messages.find((msg)=> msg?.signature === message?.repliedTo) : undefined
|
||||
|
||||
let replyIndex = messages.findIndex((msg)=> msg?.signature === message?.repliedTo)
|
||||
let reply
|
||||
if(message?.repliedTo && replyIndex !== -1){
|
||||
@ -114,108 +124,45 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
|
||||
unread: false
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
{({ measure }) => (
|
||||
<div style={style}>
|
||||
<div
|
||||
onLoad={() => {
|
||||
if (isLargeMessage) {
|
||||
measure(); // Ensure large messages are properly measured
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
marginBottom: '10px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<MessageItem
|
||||
isLast={index === messages.length - 1}
|
||||
message={message}
|
||||
onSeen={handleMessageSeen}
|
||||
isTemp={!!message?.isTemp}
|
||||
myAddress={myAddress}
|
||||
onReply={onReply}
|
||||
reply={reply}
|
||||
scrollToItem={scrollToItem}
|
||||
replyIndex={replyIndex}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
<div style={{ padding: '10px 0', display: 'flex', justifyContent: 'center', width: '100%' }}>
|
||||
<MessageItem
|
||||
isLast={index === messages.length - 1}
|
||||
message={message}
|
||||
onSeen={handleMessageSeen}
|
||||
isTemp={!!message?.isTemp}
|
||||
myAddress={myAddress}
|
||||
onReply={onReply}
|
||||
reply={reply}
|
||||
replyIndex={replyIndex}
|
||||
scrollToItem={scrollToItem}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let uniqueInitialMessagesMap = new Map();
|
||||
|
||||
// Iterate over initialMessages and add only unique messages based on signature
|
||||
initialMessages.forEach((message) => {
|
||||
uniqueInitialMessagesMap.set(message.signature, message);
|
||||
});
|
||||
|
||||
// Convert the map back to an array and sort by timestamp (old to new)
|
||||
let uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort((a, b) => a.timestamp - b.timestamp);
|
||||
const totalMessages = [...uniqueInitialMessages, ...(tempMessages || [])]
|
||||
if(totalMessages.length === 0) return
|
||||
setMessages(totalMessages);
|
||||
// cache.clearAll(); // Clear cache so the list can properly re-render with new messages
|
||||
setTimeout(() => {
|
||||
if (listRef.current) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = listRef.current.Grid._scrollingContainer;
|
||||
handleScroll({ scrollTop, scrollHeight, clientHeight });
|
||||
recomputeListHeights(); // Ensure heights are recomputed on message load
|
||||
setTimeout(() => {
|
||||
if(!hasLoadedInitialRef.current){
|
||||
scrollToBottom(totalMessages);
|
||||
hasLoadedInitialRef.current = true
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}, 500);
|
||||
}, [tempMessages, initialMessages]);
|
||||
|
||||
// useEffect(() => {
|
||||
// // Scroll to the bottom on initial load or when messages change
|
||||
// if (listRef.current && messages.length > 0 && !hasLoadedInitialRef.current) {
|
||||
// scrollToBottom();
|
||||
// hasLoadedInitialRef.current = true;
|
||||
// } else if (messages.length > 0 && messages[messages.length - 1].sender === myAddress) {
|
||||
// scrollToBottom();
|
||||
// }
|
||||
// }, [messages, myAddress]);
|
||||
const handleAtBottomStateChange = (atBottom) => {
|
||||
isAtBottomRef.current = atBottom;
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', marginTop: '14px', flexGrow: 1, width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
ref={listRef}
|
||||
width={width}
|
||||
height={height}
|
||||
rowCount={messages.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={handleScrollDebounced}
|
||||
deferredMeasurementCache={cache}
|
||||
onRowsRendered={recomputeListHeights} // Force recompute on render
|
||||
overscanRowCount={10} // For performance: pre-render some rows
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<div style={{ position: 'relative', height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={messages}
|
||||
itemContent={rowRenderer}
|
||||
atBottomThreshold={50}
|
||||
followOutput="smooth"
|
||||
onScroll={handleScroll}
|
||||
overscan={10}
|
||||
increaseViewportBy={300}
|
||||
atBottomStateChange={handleAtBottomStateChange} // Detect bottom status
|
||||
/>
|
||||
|
||||
{showScrollButton && (
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
onClick={()=> scrollToBottom()}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
@ -230,9 +177,7 @@ let uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort((
|
||||
>
|
||||
Scroll to Unread Messages
|
||||
</button>
|
||||
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user