fix chat list

This commit is contained in:
PhilReact 2024-11-22 07:22:50 +02:00
parent 3615a6e873
commit 78e85f9e79

View File

@ -1,25 +1,85 @@
import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; import React, {
import { useVirtualizer } from '@tanstack/react-virtual'; useCallback,
import { MessageItem } from './MessageItem'; useState,
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; useEffect,
import { useInView } from 'react-intersection-observer' useRef,
import { Box } from '@mui/material'; useMemo,
import { ChatOptions } from './ChatOptions'; } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
import { MessageItem } from "./MessageItem";
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { useInView } from "react-intersection-observer";
import { Box } from "@mui/material";
import { ChatOptions } from "./ChatOptions";
export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply, handleReaction, chatReferences, tempChatReferences, members, myName, selectedGroup, enableMentions }) => { export const ChatList = ({
initialMessages,
myAddress,
tempMessages,
chatId,
onReply,
handleReaction,
chatReferences,
tempChatReferences,
members,
myName,
selectedGroup,
enableMentions,
}) => {
const parentRef = useRef(); const parentRef = useRef();
const [messages, setMessages] = useState(initialMessages); const [messages, setMessages] = useState(initialMessages);
const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false);
const [showScrollDownButton, setShowScrollDownButton] = useState(false); const [showScrollDownButton, setShowScrollDownButton] = useState(false);
const hasLoadedInitialRef = useRef(false); const hasLoadedInitialRef = useRef(false);
const scrollingIntervalRef = useRef(null);
const showScrollButtonRef = useRef(showScrollButton); // Initialize the virtualizer
const rowVirtualizer = useVirtualizer({
count: messages.length,
getItemKey: (index) => messages[index].signature,
getScrollElement: () => parentRef?.current,
estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed
overscan: 10, // Number of items to render outside the visible area to improve smoothness
});
// Update the ref whenever the state changes const isAtBottom = useMemo(()=> {
useEffect(() => { if (parentRef.current && rowVirtualizer?.isScrolling !== undefined) {
showScrollButtonRef.current = showScrollButton; const { scrollTop, scrollHeight, clientHeight } = parentRef.current;
}, [showScrollButton]); const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed
return atBottom
}
return false
}, [rowVirtualizer?.isScrolling])
useEffect(() => {
if (!parentRef.current || rowVirtualizer?.isScrolling === undefined) return;
if(isAtBottom){
if (scrollingIntervalRef.current) {
clearTimeout(scrollingIntervalRef.current);
}
setShowScrollDownButton(false);
return;
} else
if (rowVirtualizer?.isScrolling) {
if (scrollingIntervalRef.current) {
clearTimeout(scrollingIntervalRef.current);
}
setShowScrollDownButton(false);
return;
}
const { scrollTop, scrollHeight, clientHeight } = parentRef.current;
const atBottom = scrollHeight - scrollTop - clientHeight <= 300;
if (!atBottom) {
scrollingIntervalRef.current = setTimeout(() => {
setShowScrollDownButton(true);
}, 250);
} else {
setShowScrollDownButton(false);
}
}, [rowVirtualizer?.isScrolling, isAtBottom]);
// Update message list with unique signatures and tempMessages // Update message list with unique signatures and tempMessages
useEffect(() => { useEffect(() => {
@ -32,30 +92,35 @@ useEffect(() => {
} }
}); });
const uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort( const uniqueInitialMessages = Array.from(
(a, b) => a.timestamp - b.timestamp uniqueInitialMessagesMap.values()
); ).sort((a, b) => a.timestamp - b.timestamp);
const totalMessages = [...uniqueInitialMessages, ...(tempMessages || [])] const totalMessages = [...uniqueInitialMessages, ...(tempMessages || [])];
if (totalMessages.length === 0) return; if (totalMessages.length === 0) return;
setMessages(totalMessages); setMessages(totalMessages);
setTimeout(() => { setTimeout(() => {
const hasUnreadMessages = totalMessages.some((msg) => msg.unread && !msg?.chatReference && !msg?.isTemp); const hasUnreadMessages = totalMessages.some(
(msg) => msg.unread && !msg?.chatReference && !msg?.isTemp
);
if (parentRef.current) { if (parentRef.current) {
const { scrollTop, scrollHeight, clientHeight } = parentRef.current; const { scrollTop, scrollHeight, clientHeight } = parentRef.current;
const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed
if (!atBottom && hasUnreadMessages) { if (!atBottom && hasUnreadMessages) {
setShowScrollButton(hasUnreadMessages); setShowScrollButton(hasUnreadMessages);
setShowScrollDownButton(false) setShowScrollDownButton(false);
} else { } else {
handleMessageSeen(); handleMessageSeen();
} }
} }
if (!hasLoadedInitialRef.current) { if (!hasLoadedInitialRef.current) {
const findDivideIndex = totalMessages.findIndex((item)=> !!item?.divide) const findDivideIndex = totalMessages.findIndex(
const divideIndex = findDivideIndex !== -1 ? findDivideIndex : undefined (item) => !!item?.divide
);
const divideIndex =
findDivideIndex !== -1 ? findDivideIndex : undefined;
scrollToBottom(totalMessages, divideIndex); scrollToBottom(totalMessages, divideIndex);
hasLoadedInitialRef.current = true; hasLoadedInitialRef.current = true;
} }
@ -65,19 +130,15 @@ useEffect(() => {
const scrollToBottom = (initialMsgs, divideIndex) => { const scrollToBottom = (initialMsgs, divideIndex) => {
const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1; const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1;
if (rowVirtualizer) { if (rowVirtualizer) {
if(divideIndex){ if (divideIndex) {
rowVirtualizer.scrollToIndex(divideIndex, { align: 'start' }) rowVirtualizer.scrollToIndex(divideIndex, { align: "start" });
} else { } else {
rowVirtualizer.scrollToIndex(index, { align: 'end' }) rowVirtualizer.scrollToIndex(index, { align: "end" });
} }
} }
handleMessageSeen() handleMessageSeen();
}; };
const handleMessageSeen = useCallback(() => { const handleMessageSeen = useCallback(() => {
setMessages((prevMessages) => setMessages((prevMessages) =>
prevMessages.map((msg) => ({ prevMessages.map((msg) => ({
@ -85,236 +146,214 @@ useEffect(() => {
unread: false, unread: false,
})) }))
); );
setShowScrollButton(false) setShowScrollButton(false);
}, []); }, []);
const sentNewMessageGroupFunc = useCallback(() => { const sentNewMessageGroupFunc = useCallback(() => {
const { scrollHeight, scrollTop, clientHeight } = parentRef.current; const { scrollHeight, scrollTop, clientHeight } = parentRef.current;
// Check if the user is within 200px from the bottom // Check if the user is within 200px from the bottom
const distanceFromBottom = scrollHeight - scrollTop - clientHeight; const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
console.log('distanceFromBottom', distanceFromBottom) console.log("distanceFromBottom", distanceFromBottom);
if (distanceFromBottom <= 700) { if (distanceFromBottom <= 700) {
scrollToBottom(); scrollToBottom();
} }
}, [messages]); }, [messages]);
useEffect(() => { useEffect(() => {
subscribeToEvent('sent-new-message-group', sentNewMessageGroupFunc); subscribeToEvent("sent-new-message-group", sentNewMessageGroupFunc);
return () => { return () => {
unsubscribeFromEvent('sent-new-message-group', sentNewMessageGroupFunc); unsubscribeFromEvent("sent-new-message-group", sentNewMessageGroupFunc);
}; };
}, [sentNewMessageGroupFunc]); }, [sentNewMessageGroupFunc]);
const lastSignature = useMemo(()=> { const lastSignature = useMemo(() => {
if(!messages || messages?.length === 0) return null if (!messages || messages?.length === 0) return null;
const lastIndex = messages.length - 1 const lastIndex = messages.length - 1;
return messages[lastIndex]?.signature return messages[lastIndex]?.signature;
}, [messages]) }, [messages]);
const goToMessage = useCallback((idx) => {
// Initialize the virtualizer rowVirtualizer.scrollToIndex(idx);
const rowVirtualizer = useVirtualizer({ }, []);
count: messages.length,
getItemKey: React.useCallback(
(index) => messages[index].signature,
[messages]
),
getScrollElement: () => parentRef.current,
estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed
overscan: 10, // Number of items to render outside the visible area to improve smoothness
observeElementOffset: (instance, cb) => {
const offsetCheck = () => {
const { scrollHeight, scrollTop, clientHeight } = instance.scrollElement;
const atBottom = scrollHeight - scrollTop - clientHeight <= 300;
if(showScrollButtonRef.current){
setShowScrollDownButton(false)
} else
if(atBottom){
setShowScrollDownButton(false)
} else {
setShowScrollDownButton(true)
}
cb(scrollTop); // Pass scroll offset to callback
// setShowScrollToBottom(!atBottom);
};
// Initial check and continuous monitoring
offsetCheck();
instance.scrollElement.addEventListener('scroll', offsetCheck);
return () => instance.scrollElement.removeEventListener('scroll', offsetCheck);
},
});
const goToMessage = useCallback((idx)=> {
rowVirtualizer.scrollToIndex(idx)
}, [])
return ( return (
<Box sx={{ <Box
display: 'flex', sx={{
width: '100%', display: "flex",
height: '100%' width: "100%",
}}> height: "100%",
<div style={{ }}
height: '100%', >
position: 'relative', <div
display: 'flex', style={{
flexDirection: 'column', height: "100%",
width: '100%' position: "relative",
}}> display: "flex",
flexDirection: "column",
<div width: "100%",
ref={parentRef} }}
className="List"
style={{ flexGrow: 1, overflow: 'auto', position: 'relative', display: 'flex', height: '0px' }}
> >
<div <div
ref={parentRef}
className="List"
style={{ style={{
height: rowVirtualizer.getTotalSize(), flexGrow: 1,
width: '100%', overflow: "auto",
position: "relative",
display: "flex",
height: "0px",
}} }}
> >
<div <div
style={{ style={{
position: 'absolute', height: rowVirtualizer.getTotalSize(),
top: 0, width: "100%",
left: 0,
width: '100%',
}} }}
> >
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const index = virtualRow.index;
let message = messages[index];
let replyIndex = messages.findIndex((msg) => msg?.signature === message?.repliedTo);
let reply;
let reactions = null;
if (message?.repliedTo && replyIndex !== -1) {
reply = messages[replyIndex];
}
if (message?.message && message?.groupDirectId) {
replyIndex = messages.findIndex((msg) => msg?.signature === message?.message?.repliedTo);
if (message?.message?.repliedTo && replyIndex !== -1) {
reply = messages[replyIndex];
}
message = {
...(message?.message || {}),
isTemp: true,
unread: false,
status: message?.status
};
}
if (chatReferences && chatReferences[message?.signature]) {
if (chatReferences[message.signature]?.reactions) {
reactions = chatReferences[message.signature]?.reactions;
}
}
let isUpdating = false;
if (tempChatReferences && tempChatReferences?.find((item) => item?.chatReference === message?.signature)) {
isUpdating = true;
}
return (
<div <div
data-index={virtualRow.index} //needed for dynamic row height measurement
ref={rowVirtualizer.measureElement} //measure dynamic row height
key={message.signature}
style={{ style={{
position: 'absolute', position: "absolute",
top: 0, top: 0,
left: '50%', // Move to the center horizontally left: 0,
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering width: "100%",
width: '100%', // Control width (90% of the parent)
padding: '10px 0',
display: 'flex',
alignItems: 'center',
overscrollBehavior: 'none',
flexDirection: 'column',
gap: '5px'
}} }}
> >
<MessageItem {rowVirtualizer.getVirtualItems().map((virtualRow) => {
isLast={index === messages.length - 1} const index = virtualRow.index;
lastSignature={lastSignature} let message = messages[index];
message={message} let replyIndex = messages.findIndex(
onSeen={handleMessageSeen} (msg) => msg?.signature === message?.repliedTo
isTemp={!!message?.isTemp} );
myAddress={myAddress} let reply;
onReply={onReply} let reactions = null;
reply={reply}
replyIndex={replyIndex}
scrollToItem={goToMessage}
handleReaction={handleReaction}
reactions={reactions}
isUpdating={isUpdating}
/>
</div>
);
})}
</div>
</div>
</div>
{showScrollButton && (
<button
onClick={() => scrollToBottom()}
style={{
bottom: 20,
position: 'absolute',
right: 20,
backgroundColor: 'var(--unread)',
color: 'white',
padding: '10px 20px',
borderRadius: '20px',
cursor: 'pointer',
zIndex: 10,
border: 'none',
outline: 'none'
}}
>
Scroll to Unread Messages
</button>
)}
{showScrollDownButton && (
<button
onClick={() => scrollToBottom()}
style={{
bottom: 20,
position: 'absolute',
right: 20,
backgroundColor: 'var(--Mail-Background)',
color: 'white',
padding: '10px 20px',
borderRadius: '20px',
cursor: 'pointer',
zIndex: 10,
border: 'none',
outline: 'none',
fontSize: '16px'
}}
>
Scroll to bottom
</button>
)}
</div>
{enableMentions && (
<ChatOptions messages={messages} goToMessage={goToMessage} members={members} myName={myName} selectedGroup={selectedGroup}/>
)} if (message?.repliedTo && replyIndex !== -1) {
</Box> reply = messages[replyIndex];
}
if (message?.message && message?.groupDirectId) {
replyIndex = messages.findIndex(
(msg) => msg?.signature === message?.message?.repliedTo
);
if (message?.message?.repliedTo && replyIndex !== -1) {
reply = messages[replyIndex];
}
message = {
...(message?.message || {}),
isTemp: true,
unread: false,
status: message?.status,
};
}
if (chatReferences && chatReferences[message?.signature]) {
if (chatReferences[message.signature]?.reactions) {
reactions = chatReferences[message.signature]?.reactions;
}
}
let isUpdating = false;
if (
tempChatReferences &&
tempChatReferences?.find(
(item) => item?.chatReference === message?.signature
)
) {
isUpdating = true;
}
return (
<div
data-index={virtualRow.index} //needed for dynamic row height measurement
ref={rowVirtualizer.measureElement} //measure dynamic row height
key={message.signature}
style={{
position: "absolute",
top: 0,
left: "50%", // Move to the center horizontally
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering
width: "100%", // Control width (90% of the parent)
padding: "10px 0",
display: "flex",
alignItems: "center",
overscrollBehavior: "none",
flexDirection: "column",
gap: "5px",
}}
>
<MessageItem
isLast={index === messages.length - 1}
lastSignature={lastSignature}
message={message}
onSeen={handleMessageSeen}
isTemp={!!message?.isTemp}
myAddress={myAddress}
onReply={onReply}
reply={reply}
replyIndex={replyIndex}
scrollToItem={goToMessage}
handleReaction={handleReaction}
reactions={reactions}
isUpdating={isUpdating}
/>
</div>
);
})}
</div>
</div>
</div>
{showScrollButton && (
<button
onClick={() => scrollToBottom()}
style={{
bottom: 20,
position: "absolute",
right: 20,
backgroundColor: "var(--unread)",
color: "white",
padding: "10px 20px",
borderRadius: "20px",
cursor: "pointer",
zIndex: 10,
border: "none",
outline: "none",
}}
>
Scroll to Unread Messages
</button>
)}
{showScrollDownButton && (
<button
onClick={() => scrollToBottom()}
style={{
bottom: 20,
position: "absolute",
right: 20,
backgroundColor: "var(--Mail-Background)",
color: "white",
padding: "10px 20px",
borderRadius: "20px",
cursor: "pointer",
zIndex: 10,
border: "none",
outline: "none",
fontSize: "16px",
}}
>
Scroll to bottom
</button>
)}
</div>
{enableMentions && (
<ChatOptions
messages={messages}
goToMessage={goToMessage}
members={members}
myName={myName}
selectedGroup={selectedGroup}
/>
)}
</Box>
); );
}; };