1717 *
1818 */
1919
20- import { MutableRefObject , useEffect , useMemo , useState } from 'react' ;
20+ import { MutableRefObject , useEffect , useLayoutEffect , useMemo , useRef , useState } from 'react' ;
2121
2222import { useVirtualizer } from '@tanstack/react-virtual' ;
2323import cx from 'classnames' ;
@@ -28,22 +28,22 @@ import {MessagesListParams} from 'Components/MessagesList/MessageList.types';
2828import { UploadAssets } from 'Components/MessagesList/UploadAssets' ;
2929import { verticallyCenterMessage } from 'Components/MessagesList/utils/helpers' ;
3030import { filterMessages } from 'Components/MessagesList/utils/messagesFilter' ;
31+ import { useLoadConversation } from 'Components/MessagesList/utils/useLoadConversation' ;
32+ import { useLoadInitialMessage } from 'Components/MessagesList/utils/useLoadInitialMessage' ;
3133import { groupMessagesBySenderAndTime , isMarker } from 'Components/MessagesList/utils/virtualizedMessagesGroup' ;
3234import { useLoadMessages } from 'Components/MessagesList/VirtualizedMessagesList/useLoadMessages' ;
3335import { useScrollMessages } from 'Components/MessagesList/VirtualizedMessagesList/useScrollMessages' ;
3436import { useRoveFocus } from 'Hooks/useRoveFocus' ;
35- import { Conversation } from 'Repositories/entity/Conversation' ;
36- import { Message as MessageEntity } from 'Repositories/entity/message/Message' ;
3737import { useKoSubscribableChildren } from 'Util/ComponentUtil' ;
3838
3939import { VirtualizedJumpToLastMessageButton } from '../VirtualizedJumpToLastMessageButton' ;
4040
4141const ESTIMATED_ELEMENT_SIZE = 48 ;
4242
43- interface Props extends Omit < MessagesListParams , 'isRightSidebarOpen' | 'onLoading' | 'isConversationLoaded' > {
43+ interface Props extends Omit < MessagesListParams , 'isRightSidebarOpen' | 'onLoading' > {
4444 parentElement : HTMLDivElement ;
4545 conversationLastReadTimestamp : MutableRefObject < number > ;
46- loadConversation : ( conversation : Conversation ) => Promise < MessageEntity [ ] > ;
46+ onLoading : ( isLoading : boolean ) => void ;
4747}
4848
4949export const VirtualizedMessagesList = ( {
@@ -68,7 +68,8 @@ export const VirtualizedMessagesList = ({
6868 setMsgElementsFocusable,
6969 conversationLastReadTimestamp,
7070 updateConversationLastRead,
71- loadConversation,
71+ onLoading,
72+ isConversationLoaded,
7273} : Props ) => {
7374 const {
7475 messages : allMessages ,
@@ -77,13 +78,17 @@ export const VirtualizedMessagesList = ({
7778 isGuestAndServicesRoom,
7879 isActiveParticipant,
7980 inTeam,
81+ isLoadingMessages,
82+ hasAdditionalMessages,
8083 } = useKoSubscribableChildren ( conversation , [
8184 'inTeam' ,
8285 'isActiveParticipant' ,
8386 'messages' ,
8487 'lastDeliveredMessage' ,
8588 'isGuestRoom' ,
8689 'isGuestAndServicesRoom' ,
90+ 'isLoadingMessages' ,
91+ 'hasAdditionalMessages' ,
8792 ] ) ;
8893
8994 const filteredMessages = filterMessages ( allMessages ) ;
@@ -92,26 +97,8 @@ export const VirtualizedMessagesList = ({
9297 return groupMessagesBySenderAndTime ( filteredMessages , conversationLastReadTimestamp . current ) ;
9398 } , [ conversationLastReadTimestamp , filteredMessages ] ) ;
9499
95- const initialMessageId = conversation . initialMessage ( ) ?. id ;
96-
97- const [ highlightedMessage , setHighlightedMessage ] = useState < string | undefined > ( undefined ) ;
100+ const [ highlightedMessage , setHighlightedMessage ] = useState < string | undefined > ( conversation . initialMessage ( ) ?. id ) ;
98101 const [ loadingMessages , setLoadingMessages ] = useState ( false ) ;
99- const [ alreadyScrolledToLastMessage , setAlreadyScrolledToLastMessage ] = useState ( false ) ;
100-
101- useEffect ( ( ) => {
102- if ( ! initialMessageId ) {
103- return ( ) => undefined ;
104- }
105-
106- setHighlightedMessage ( initialMessageId ) ;
107-
108- const timeout = setTimeout ( ( ) => {
109- setHighlightedMessage ( undefined ) ;
110- conversation . initialMessage ( undefined ) ;
111- } , 3000 ) ;
112-
113- return ( ) => clearTimeout ( timeout ) ;
114- } , [ conversation , initialMessageId ] ) ;
115102
116103 const { focusedId, handleKeyDown, setFocusedId} = useRoveFocus ( filteredMessages . map ( message => message . id ) ) ;
117104
@@ -122,24 +109,39 @@ export const VirtualizedMessagesList = ({
122109 getScrollElement : ( ) => parentElement ,
123110 estimateSize : ( ) => ESTIMATED_ELEMENT_SIZE ,
124111 measureElement : element => element ?. getBoundingClientRect ( ) . height || ESTIMATED_ELEMENT_SIZE ,
125- overscan : 2 ,
126112 } ) ;
127113
114+ // Hook for load current conversation
115+ const { loadConversation} = useLoadConversation ( {
116+ conversation,
117+ conversationRepository,
118+ conversationLastReadTimestamp,
119+ onLoading,
120+ } ) ;
121+
122+ // Hook for loading the initial message ( it takes initial message from search, last unread message or last message )
123+ useLoadInitialMessage ( virtualizer , {
124+ conversation,
125+ isConversationLoaded,
126+ allMessages,
127+ conversationLastReadTimestamp,
128+ } ) ;
129+
130+ // Hook for loading preceding / following messages when user scrolls to top / bottom
128131 useLoadMessages ( virtualizer , {
129132 conversation,
130133 conversationRepository,
131134 loadingMessages,
132135 onLoadingMessages : setLoadingMessages ,
133136 itemsLength : groupedMessages . length ,
134- initialMessageId ,
137+ shouldPullMessages : ! isLoadingMessages && hasAdditionalMessages ,
135138 } ) ;
136139
140+ // Hook for scrolling messages when a new message is sent or received
137141 useScrollMessages ( virtualizer , {
138142 messages : groupedMessages ,
139- highlightedMessage,
140143 userId : selfUser . id ,
141- conversationLastReadTimestamp,
142- setAlreadyScrolledToLastMessage,
144+ isConversationLoaded,
143145 } ) ;
144146
145147 // When a new conversation is opened using keyboard(enter), focus on the last message
@@ -155,24 +157,41 @@ export const VirtualizedMessagesList = ({
155157 }
156158 } , [ ] ) ;
157159
160+ const scrolledToHighlightedMessage = useRef ( false ) ;
161+
158162 const onTimestampClick = async ( messageId : string ) => {
163+ scrolledToHighlightedMessage . current = false ;
159164 setHighlightedMessage ( messageId ) ;
160165
161- const highlightMessageTimeout = setTimeout ( ( ) => setHighlightedMessage ( undefined ) , 3000 ) ;
162- clearTimeout ( highlightMessageTimeout ) ;
166+ const clearHighlightedMessage = setTimeout ( ( ) => {
167+ setHighlightedMessage ( undefined ) ;
168+ scrolledToHighlightedMessage . current = false ;
169+ clearTimeout ( clearHighlightedMessage ) ;
170+ } , 5000 ) ;
163171
164172 const messageIsLoaded = conversation . getMessage ( messageId ) ;
165173
166174 if ( ! messageIsLoaded ) {
167175 const messageEntity = await messageRepository . getMessageInConversationById ( conversation , messageId ) ;
168176 conversation . removeMessages ( ) ;
169- conversationRepository . getMessagesWithOffset ( conversation , messageEntity ) ;
177+ void conversationRepository . getMessagesWithOffset ( conversation , messageEntity ) ;
170178 }
171179 } ;
172180
173- const virtualItems = virtualizer . getVirtualItems ( ) ;
174- const lastIndex = groupedMessages . length - 1 ;
175- const isLastMessageVisible = virtualItems . some ( item => item . index === lastIndex ) ;
181+ useLayoutEffect ( ( ) => {
182+ if ( highlightedMessage && ! scrolledToHighlightedMessage . current ) {
183+ const highlightedMessageIndex = groupedMessages . findIndex (
184+ msg => ! isMarker ( msg ) && msg . message . id === highlightedMessage ,
185+ ) ;
186+
187+ virtualizer . scrollToIndex ( highlightedMessageIndex , { align : 'center' } ) ;
188+
189+ const setScrolledToHighlightedMessageTimeout = setTimeout ( ( ) => {
190+ scrolledToHighlightedMessage . current = true ;
191+ clearTimeout ( setScrolledToHighlightedMessageTimeout ) ;
192+ } , 100 ) ;
193+ }
194+ } , [ groupedMessages , highlightedMessage , virtualizer ] ) ;
176195
177196 const onJumpToLastMessageClick = async ( ) => {
178197 setHighlightedMessage ( undefined ) ;
@@ -185,17 +204,22 @@ export const VirtualizedMessagesList = ({
185204 await loadConversation ( conversation ) ;
186205 }
187206
188- conversationLastReadTimestamp . current = groupedMessages [ groupedMessages . length - 1 ] . timestamp ;
207+ conversationLastReadTimestamp . current = allMessages [ allMessages . length - 1 ] . timestamp ( ) ;
189208
190- requestAnimationFrame ( ( ) => {
191- virtualizer . scrollToIndex ( groupedMessages . length - 1 , { align : 'end' } ) ;
192- } ) ;
209+ const scrollTimeout = setTimeout ( ( ) => {
210+ virtualizer . scrollToIndex ( allMessages . length - 1 , { align : 'end' } ) ;
211+ clearTimeout ( scrollTimeout ) ;
212+ } , 100 ) ;
193213 } ;
194214
215+ const virtualItems = virtualizer . getVirtualItems ( ) ;
216+ const lastIndex = groupedMessages . length - 1 ;
217+ const isLastMessageVisible = virtualItems . some ( item => item . index === lastIndex ) ;
218+
195219 useEffect ( ( ) => {
196220 // Timeout to ensure that the messages are rendered before calling getVisibleCallback
197221 const timeout = setTimeout ( ( ) => {
198- if ( ! alreadyScrolledToLastMessage ) {
222+ if ( ! isConversationLoaded ) {
199223 return ;
200224 }
201225
@@ -208,7 +232,11 @@ export const VirtualizedMessagesList = ({
208232 } , 100 ) ;
209233
210234 return ( ) => clearTimeout ( timeout ) ;
211- } , [ alreadyScrolledToLastMessage , virtualItems ] ) ;
235+ } , [ isConversationLoaded , virtualItems ] ) ;
236+
237+ if ( ! isConversationLoaded ) {
238+ return null ;
239+ }
212240
213241 return (
214242 < >
0 commit comments