diff --git a/package-lock.json b/package-lock.json index 03cad1f..15ffbc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@mui/lab": "^5.0.0-alpha.173", "@mui/material": "^5.16.7", "@reduxjs/toolkit": "^2.2.7", + "@tanstack/react-virtual": "^3.10.8", "@testing-library/jest-dom": "^6.4.6", "@testing-library/user-event": "^14.5.2", "@tiptap/extension-color": "^2.5.9", @@ -1899,6 +1900,31 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@tanstack/react-virtual": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", + "integrity": "sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==", + "dependencies": { + "@tanstack/virtual-core": "3.10.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz", + "integrity": "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", diff --git a/package.json b/package.json index 185fe71..204cbdb 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@mui/lab": "^5.0.0-alpha.173", "@mui/material": "^5.16.7", "@reduxjs/toolkit": "^2.2.7", + "@tanstack/react-virtual": "^3.10.8", "@testing-library/jest-dom": "^6.4.6", "@testing-library/user-event": "^14.5.2", "@tiptap/extension-color": "^2.5.9", diff --git a/src/App.tsx b/src/App.tsx index fe1f497..a983363 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -149,7 +149,7 @@ const defaultValues: MyContextInterface = { message: "", }, }; -export let isMobile = true; +export let isMobile = false; const isMobileDevice = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index ec83bc6..a1b8862 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -1,40 +1,52 @@ -import React, { useCallback, useState, useEffect, useRef } from 'react'; -import { Virtuoso } from 'react-virtuoso'; +import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; +import { useVirtualizer } from '@tanstack/react-virtual'; import { MessageItem } from './MessageItem'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; +import { useInView } from 'react-intersection-observer' export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply, handleReaction, chatReferences, tempChatReferences }) => { - const virtuosoRef = useRef(); + const parentRef = useRef(); const [messages, setMessages] = useState(initialMessages); const [showScrollButton, setShowScrollButton] = useState(false); const hasLoadedInitialRef = useRef(false); - const isAtBottomRef = useRef(true); // - // Update message list with unique signatures and tempMessages + const isAtBottomRef = useRef(true); + // const [ref, inView] = useInView({ + // threshold: 0.7 + // }) + // useEffect(() => { + // if (inView) { + + // } + // }, [inView]) + // Update message list with unique signatures and tempMessages useEffect(() => { let uniqueInitialMessagesMap = new Map(); - + // Only add a message if it doesn't already exist in the Map initialMessages.forEach((message) => { if (!uniqueInitialMessagesMap.has(message.signature)) { 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 && !msg?.chatReference); - - if (virtuosoRef.current) { - if (virtuosoRef.current && !isAtBottomRef.current && hasUnreadMessages) { + console.log('hasUnreadMessages', hasUnreadMessages) + if (parentRef.current) { + const { scrollTop, scrollHeight, clientHeight } = parentRef.current; + const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed + console.log('atBottom', atBottom, {scrollTop, scrollHeight, clientHeight}) + if (!atBottom && hasUnreadMessages) { setShowScrollButton(hasUnreadMessages); } else { handleMessageSeen(); @@ -46,46 +58,32 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR } }, 500); }, [initialMessages, tempMessages]); - - - const handleMessageSeen = useCallback(() => { + console.log('hello handle seen') setMessages((prevMessages) => prevMessages.map((msg) => ({ ...msg, unread: false, })) ); + setShowScrollButton(false) }, []); - const scrollToItem = useCallback((index) => { - if (virtuosoRef.current) { - virtuosoRef.current.scrollToIndex({ index, behavior: 'smooth' }); - } - }, []); + // const scrollToBottom = (initialMsgs) => { + // const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1; + // if (parentRef.current) { + // parentRef.current.scrollToIndex(index); + // } + // }; const scrollToBottom = (initialMsgs) => { - - const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1 - if (virtuosoRef.current) { - virtuosoRef.current.scrollToIndex({ index}); + const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1; + if (rowVirtualizer) { + rowVirtualizer.scrollToIndex(index, { align: 'end' }); } }; - - const handleScroll = (scrollState) => { - const { scrollTop, scrollHeight, clientHeight } = scrollState; - const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50; - const hasUnreadMessages = messages.some((msg) => msg.unread); - - if (isAtBottom) { - handleMessageSeen(); - } - - setShowScrollButton(!isAtBottom && hasUnreadMessages); - }; - const sentNewMessageGroupFunc = useCallback(() => { scrollToBottom(); }, [messages]); @@ -97,96 +95,136 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR }; }, [sentNewMessageGroupFunc]); - const rowRenderer = (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) - reply - if(message?.message?.repliedTo && replyIndex !== -1){ - reply = messages[replyIndex] - } - message = { - ...(message?.message || {}), - isTemp: true, - unread: false + const lastSignature = useMemo(()=> { + if(!messages || messages?.length === 0) return null + const lastIndex = messages.length - 1 + return messages[lastIndex]?.signature + }, [messages]) + + console.log('messages', messages) + + // Initialize the virtualizer + const rowVirtualizer = useVirtualizer({ + count: messages.length, + 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 + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? element => { + console.log('height', element?.getBoundingClientRect().height) + return element?.getBoundingClientRect().height } - } - - 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 ( -