mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-03-28 08:15:55 +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",
|
"lodash": "^4.17.21",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"npm": "^10.8.3",
|
||||||
"quill-image-resize-module-react": "^3.0.0",
|
"quill-image-resize-module-react": "^3.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
@ -53,6 +54,7 @@
|
|||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-virtualized": "^9.22.5",
|
"react-virtualized": "^9.22.5",
|
||||||
|
"react-virtuoso": "^4.10.4",
|
||||||
"short-unique-id": "^5.2.0",
|
"short-unique-id": "^5.2.0",
|
||||||
"slate": "^0.103.0",
|
"slate": "^0.103.0",
|
||||||
"slate-react": "^0.109.0",
|
"slate-react": "^0.109.0",
|
||||||
|
@ -484,13 +484,15 @@ const clearEditorContent = () => {
|
|||||||
position: isFocusedParent ? 'fixed' : 'relative',
|
position: isFocusedParent ? 'fixed' : 'relative',
|
||||||
bottom: isFocusedParent ? '0px' : 'unset',
|
bottom: isFocusedParent ? '0px' : 'unset',
|
||||||
top: isFocusedParent ? '0px' : 'unset',
|
top: isFocusedParent ? '0px' : 'unset',
|
||||||
zIndex: isFocusedParent ? 5 : 'unset'
|
zIndex: isFocusedParent ? 5 : 'unset',
|
||||||
|
flexShrink: 0
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flexGrow: isMobile && 1,
|
flexGrow: isMobile && 1,
|
||||||
overflow: !isMobile && "auto",
|
overflow: !isMobile && "auto",
|
||||||
|
flexShrink: 0
|
||||||
}}>
|
}}>
|
||||||
{replyMessage && (
|
{replyMessage && (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
@ -416,13 +416,15 @@ const clearEditorContent = () => {
|
|||||||
position: isFocusedParent ? 'fixed' : 'relative',
|
position: isFocusedParent ? 'fixed' : 'relative',
|
||||||
bottom: isFocusedParent ? '0px' : 'unset',
|
bottom: isFocusedParent ? '0px' : 'unset',
|
||||||
top: isFocusedParent ? '0px' : 'unset',
|
top: isFocusedParent ? '0px' : 'unset',
|
||||||
zIndex: isFocusedParent ? 5 : 'unset'
|
zIndex: isFocusedParent ? 5 : 'unset',
|
||||||
|
flexShrink: 0
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flexGrow: isMobile && 1,
|
flexGrow: isMobile && 1,
|
||||||
overflow: !isMobile && "auto",
|
overflow: !isMobile && "auto",
|
||||||
|
flexShrink: 0
|
||||||
}}>
|
}}>
|
||||||
{replyMessage && (
|
{replyMessage && (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
@ -1,26 +1,58 @@
|
|||||||
import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||||
import { List, AutoSizer, CellMeasurerCache, CellMeasurer } from 'react-virtualized';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { MessageItem } from './MessageItem';
|
import { MessageItem } from './MessageItem';
|
||||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
||||||
|
|
||||||
// const cache = new CellMeasurerCache({
|
|
||||||
// fixedWidth: true,
|
|
||||||
// defaultHeight: 50,
|
|
||||||
// });
|
|
||||||
|
|
||||||
export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply }) => {
|
export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply }) => {
|
||||||
|
const virtuosoRef = useRef();
|
||||||
const hasLoadedInitialRef = useRef(false);
|
|
||||||
const listRef = useRef();
|
|
||||||
const [messages, setMessages] = useState(initialMessages);
|
const [messages, setMessages] = useState(initialMessages);
|
||||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||||
const cache = useMemo(() => new CellMeasurerCache({
|
const hasLoadedInitialRef = useRef(false);
|
||||||
fixedWidth: true,
|
const isAtBottomRef = useRef(true); //
|
||||||
defaultHeight: 50,
|
// Update message list with unique signatures and tempMessages
|
||||||
}), [chatId]); // Recreate cache when chatId changes
|
|
||||||
useEffect(() => {
|
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(() => {
|
const handleMessageSeen = useCallback(() => {
|
||||||
setMessages((prevMessages) =>
|
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 isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
|
||||||
const hasUnreadMessages = messages.some((msg) => msg.unread);
|
const hasUnreadMessages = messages.some((msg) => msg.unread);
|
||||||
if(isAtBottom){
|
|
||||||
handleMessageSeen()
|
if (isAtBottom) {
|
||||||
|
handleMessageSeen();
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowScrollButton(!isAtBottom && hasUnreadMessages);
|
setShowScrollButton(!isAtBottom && hasUnreadMessages);
|
||||||
};
|
};
|
||||||
|
|
||||||
const debounce = (func, delay) => {
|
const sentNewMessageGroupFunc = useCallback(() => {
|
||||||
let timer;
|
scrollToBottom();
|
||||||
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);
|
|
||||||
};
|
|
||||||
}, [messages]);
|
}, [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];
|
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 replyIndex = messages.findIndex((msg)=> msg?.signature === message?.repliedTo)
|
||||||
let reply
|
let reply
|
||||||
if(message?.repliedTo && replyIndex !== -1){
|
if(message?.repliedTo && replyIndex !== -1){
|
||||||
@ -114,108 +124,45 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
|
|||||||
unread: false
|
unread: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellMeasurer
|
<div style={{ padding: '10px 0', display: 'flex', justifyContent: 'center', width: '100%' }}>
|
||||||
key={key}
|
<MessageItem
|
||||||
cache={cache}
|
isLast={index === messages.length - 1}
|
||||||
parent={parent}
|
message={message}
|
||||||
columnIndex={0}
|
onSeen={handleMessageSeen}
|
||||||
rowIndex={index}
|
isTemp={!!message?.isTemp}
|
||||||
>
|
myAddress={myAddress}
|
||||||
{({ measure }) => (
|
onReply={onReply}
|
||||||
<div style={style}>
|
reply={reply}
|
||||||
<div
|
replyIndex={replyIndex}
|
||||||
onLoad={() => {
|
scrollToItem={scrollToItem}
|
||||||
if (isLargeMessage) {
|
/>
|
||||||
measure(); // Ensure large messages are properly measured
|
</div>
|
||||||
}
|
|
||||||
}}
|
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleAtBottomStateChange = (atBottom) => {
|
||||||
let uniqueInitialMessagesMap = new Map();
|
isAtBottomRef.current = atBottom;
|
||||||
|
};
|
||||||
// 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]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', marginTop: '14px', flexGrow: 1, width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
|
<div style={{ position: 'relative', height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
<AutoSizer>
|
<Virtuoso
|
||||||
{({ height, width }) => (
|
ref={virtuosoRef}
|
||||||
<List
|
data={messages}
|
||||||
ref={listRef}
|
itemContent={rowRenderer}
|
||||||
width={width}
|
atBottomThreshold={50}
|
||||||
height={height}
|
followOutput="smooth"
|
||||||
rowCount={messages.length}
|
onScroll={handleScroll}
|
||||||
rowHeight={cache.rowHeight}
|
overscan={10}
|
||||||
rowRenderer={rowRenderer}
|
increaseViewportBy={300}
|
||||||
onScroll={handleScrollDebounced}
|
atBottomStateChange={handleAtBottomStateChange} // Detect bottom status
|
||||||
deferredMeasurementCache={cache}
|
/>
|
||||||
onRowsRendered={recomputeListHeights} // Force recompute on render
|
|
||||||
overscanRowCount={10} // For performance: pre-render some rows
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
{showScrollButton && (
|
{showScrollButton && (
|
||||||
<button
|
<button
|
||||||
onClick={scrollToBottom}
|
onClick={()=> scrollToBottom()}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
@ -230,9 +177,7 @@ let uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort((
|
|||||||
>
|
>
|
||||||
Scroll to Unread Messages
|
Scroll to Unread Messages
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user