split up load more messages from top or bottom

also split up just fetching the last messages from fetching based on
unread/ lastTopMessageId
pull/2142/head
audric 3 years ago
parent 381cb77ad9
commit 12b00720f4

@ -71,6 +71,7 @@ module.exports = {
getOutgoingWithoutExpiresAt, getOutgoingWithoutExpiresAt,
getNextExpiringMessage, getNextExpiringMessage,
getMessagesByConversation, getMessagesByConversation,
getLastMessagesByConversation,
getFirstUnreadMessageIdInConversation, getFirstUnreadMessageIdInConversation,
hasConversationOutgoingMessage, hasConversationOutgoingMessage,
trimMessages, trimMessages,
@ -2230,27 +2231,83 @@ function getUnreadCountByConversation(conversationId) {
// Note: Sorting here is necessary for getting the last message (with limit 1) // Note: Sorting here is necessary for getting the last message (with limit 1)
// be sure to update the sorting order to sort messages on redux too (sortMessages) // be sure to update the sorting order to sort messages on redux too (sortMessages)
const orderByClause =
'ORDER BY serverTimestamp DESC, serverId DESC, sent_at DESC, received_at DESC';
function getMessagesByConversation(conversationId, { messageId = null } = {}) {
const absLimit = 30;
// If messageId is given it means we are opening the conversation to that specific messageId,
// or that we just scrolled to it by a quote click and needs to load around it.
// If messageId is null, it means we are just opening the convo to the last unread message, or at the bottom
const firstUnread = getFirstUnreadMessageIdInConversation(conversationId);
if (messageId || firstUnread) {
const messageFound = getMessageById(messageId || firstUnread);
if (messageFound && messageFound.conversationId === conversationId) {
console.warn('firstUnread', messageId, firstUnread, messageFound);
const rows = globalInstance
.prepare(
`WITH cte AS (
SELECT id, json, row_number() OVER (${orderByClause}) as row_number
FROM messages
), current AS (
SELECT row_number
FROM cte
WHERE id = $messageId
)
SELECT cte.*
FROM cte, current
WHERE ABS(cte.row_number - current.row_number) <= $limit
ORDER BY cte.row_number;
`
)
.all({
conversationId,
messageId: messageId || firstUnread,
limit: absLimit,
});
console.warn('rows', rows);
return map(rows, row => jsonToObject(row.json));
}
console.warn(
`getMessagesByConversation: Could not find messageId ${messageId} in db with conversationId: ${conversationId}. Just fetching the convo as usual.`
);
}
function getMessagesByConversation(
conversationId,
{ limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } = {}
) {
const rows = globalInstance const rows = globalInstance
.prepare( .prepare(
` `
SELECT json FROM ${MESSAGES_TABLE} WHERE SELECT json FROM ${MESSAGES_TABLE} WHERE
conversationId = $conversationId AND conversationId = $conversationId
received_at < $received_at AND ${orderByClause}
type LIKE $type LIMIT $limit;
ORDER BY serverTimestamp DESC, serverId DESC, sent_at DESC, received_at DESC `
)
.all({
conversationId,
limit: 2 * absLimit,
});
return map(rows, row => jsonToObject(row.json));
}
function getLastMessagesByConversation(conversationId, limit) {
if (!isNumber(limit)) {
throw new Error('limit must be a number');
}
const rows = globalInstance
.prepare(
`
SELECT json FROM ${MESSAGES_TABLE} WHERE
conversationId = $conversationId
${orderByClause}
LIMIT $limit; LIMIT $limit;
` `
) )
.all({ .all({
conversationId, conversationId,
received_at: receivedAt,
limit, limit,
type,
}); });
return map(rows, row => jsonToObject(row.json)); return map(rows, row => jsonToObject(row.json));
} }

@ -87,7 +87,7 @@ export class SessionConversation extends React.Component<Props, State> {
}; };
this.messageContainerRef = React.createRef(); this.messageContainerRef = React.createRef();
this.dragCounter = 0; this.dragCounter = 0;
this.updateMemberList = _.debounce(this.updateMemberListBouncy.bind(this), 1000); this.updateMemberList = _.debounce(this.updateMemberListBouncy.bind(this), 10000);
autoBind(this); autoBind(this);
} }

@ -1,4 +1,4 @@
import React from 'react'; import React, { useLayoutEffect } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
@ -9,7 +9,10 @@ import {
PropsForGroupInvitation, PropsForGroupInvitation,
PropsForGroupUpdate, PropsForGroupUpdate,
} from '../../state/ducks/conversations'; } from '../../state/ducks/conversations';
import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations'; import {
getOldTopMessageId,
getSortedMessagesTypesOfSelectedConversation,
} from '../../state/selectors/conversations';
import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage'; import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage';
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification'; import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
import { MessageDateBreak } from './message/message-item/DateBreak'; import { MessageDateBreak } from './message/message-item/DateBreak';
@ -26,12 +29,32 @@ function isNotTextboxEvent(e: KeyboardEvent) {
export const SessionMessagesList = (props: { export const SessionMessagesList = (props: {
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>; scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;
scrollAfterLoadMore: (
messageIdToScrollTo: string,
block: ScrollLogicalPosition | undefined
) => void;
onPageUpPressed: () => void; onPageUpPressed: () => void;
onPageDownPressed: () => void; onPageDownPressed: () => void;
onHomePressed: () => void; onHomePressed: () => void;
onEndPressed: () => void; onEndPressed: () => void;
}) => { }) => {
const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation); const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation);
const oldTopMessageId = useSelector(getOldTopMessageId);
useLayoutEffect(() => {
const newTopMessageId = messagesProps.length
? messagesProps[messagesProps.length - 1].message.props.messageId
: undefined;
console.warn('useLayoutEffect ', {
oldTopMessageId,
newTopMessageId,
length: messagesProps.length,
});
if (oldTopMessageId !== newTopMessageId && oldTopMessageId && newTopMessageId) {
props.scrollAfterLoadMore(oldTopMessageId, 'center');
}
});
useKey('PageUp', () => { useKey('PageUp', () => {
props.onPageUpPressed(); props.onPageUpPressed();

@ -95,56 +95,59 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
public componentDidUpdate( public componentDidUpdate(
prevProps: Props, prevProps: Props,
_prevState: any, _prevState: any
snapShot: { fakeScrollTop: number; realScrollTop: number; scrollHeight: number } // snapShot: {
// fakeScrollTop: number;
// realScrollTop: number;
// scrollHeight: number;
// oldTopMessageId?: string;
// }
) { ) {
// this was hard to write, it should be hard to read // const { oldTopMessageId } = snapShot;
// just make sure you don't remove that as a bug in chrome makes the column-reverse do bad things // console.warn('didupdate with oldTopMessageId', oldTopMessageId);
// https://bugs.chromium.org/p/chromium/issues/detail?id=1189195&q=column-reverse&can=2#makechanges // // If you want to mess with this, be my guest.
const currentRef = this.props.messageContainerRef.current; // // just make sure you don't remove that as a bug in chrome makes the column-reverse do bad things
// // https://bugs.chromium.org/p/chromium/issues/detail?id=1189195&q=column-reverse&can=2#makechanges
const isSameConvo = prevProps.conversationKey === this.props.conversationKey; const isSameConvo = prevProps.conversationKey === this.props.conversationKey;
const prevMsgLength = prevProps.messagesProps.length; // if (isSameConvo && oldTopMessageId) {
const newMsgLength = this.props.messagesProps.length; // this.scrollToMessage(oldTopMessageId, 'center');
// // if (messageAddedWasMoreRecentOne) {
const prevFirstMesssageId = prevProps.messagesProps[0]?.propsForMessage.id; // // if (snapShot.scrollHeight - snapShot.realScrollTop < 50) {
const newFirstMesssageId = this.props.messagesProps[0]?.propsForMessage.id; // // // consider that we were scrolled to bottom
const messageAddedWasMoreRecentOne = prevFirstMesssageId !== newFirstMesssageId; // // currentRef.scrollTop = 0;
// // } else {
if (isSameConvo && snapShot?.realScrollTop && prevMsgLength !== newMsgLength && currentRef) { // // currentRef.scrollTop = -(currentRef.scrollHeight - snapShot.realScrollTop);
if (messageAddedWasMoreRecentOne) { // // }
if (snapShot.scrollHeight - snapShot.realScrollTop < 50) { // // } else {
// consider that we were scrolled to bottom // // currentRef.scrollTop = snapShot.fakeScrollTop;
currentRef.scrollTop = 0; // // }
} else { // }
currentRef.scrollTop = -(currentRef.scrollHeight - snapShot.realScrollTop); if (!isSameConvo) {
} console.info('Not same convo, resetting scrolling posiiton');
} else {
currentRef.scrollTop = snapShot.fakeScrollTop;
}
}
if (!isSameConvo || (prevMsgLength === 0 && newMsgLength !== 0)) {
this.setupTimeoutResetQuotedHighlightedMessage(this.props.animateQuotedMessageId); this.setupTimeoutResetQuotedHighlightedMessage(this.props.animateQuotedMessageId);
// displayed conversation changed. We have a bit of cleaning to do here // displayed conversation changed. We have a bit of cleaning to do here
this.initialMessageLoadingPosition(); this.initialMessageLoadingPosition();
} }
} }
public getSnapshotBeforeUpdate() { public getSnapshotBeforeUpdate() {
const messageContainer = this.props.messageContainerRef.current; // const messagePropsBeforeUpdate = this.props.messagesProps;
// const oldTopMessageId = messagePropsBeforeUpdate.length
const scrollTop = messageContainer?.scrollTop || undefined; // ? messagePropsBeforeUpdate[messagePropsBeforeUpdate.length - 1].propsForMessage.id
const scrollHeight = messageContainer?.scrollHeight || undefined; // : undefined;
// console.warn('oldTopMessageId', oldTopMessageId);
// as we use column-reverse for displaying message list // const messageContainer = this.props.messageContainerRef.current;
// the top is < 0 // const scrollTop = messageContainer?.scrollTop || undefined;
// tslint:disable-next-line: restrict-plus-operands // const scrollHeight = messageContainer?.scrollHeight || undefined;
const realScrollTop = scrollHeight && scrollTop ? scrollHeight + scrollTop : undefined; // // as we use column-reverse for displaying message list
return { // // the top is < 0
realScrollTop, // const realScrollTop = scrollHeight && scrollTop ? scrollHeight + scrollTop : undefined;
fakeScrollTop: scrollTop, // return {
scrollHeight: scrollHeight, // realScrollTop,
}; // fakeScrollTop: scrollTop,
// scrollHeight: scrollHeight,
// oldTopMessageId,
// };
} }
public render() { public render() {
@ -180,6 +183,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
<SessionMessagesList <SessionMessagesList
scrollToQuoteMessage={this.scrollToQuoteMessage} scrollToQuoteMessage={this.scrollToQuoteMessage}
scrollAfterLoadMore={this.scrollToMessage}
onPageDownPressed={this.scrollPgDown} onPageDownPressed={this.scrollPgDown}
onPageUpPressed={this.scrollPgUp} onPageUpPressed={this.scrollPgUp}
onHomePressed={this.scrollTop} onHomePressed={this.scrollTop}
@ -207,6 +211,8 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
return; return;
} }
console.warn('firstUnreadOnOpen', firstUnreadOnOpen);
if ( if (
(conversation.unreadCount && conversation.unreadCount <= 0) || (conversation.unreadCount && conversation.unreadCount <= 0) ||
firstUnreadOnOpen === undefined firstUnreadOnOpen === undefined
@ -218,6 +224,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
const firstUnreadIndex = messagesProps.findIndex( const firstUnreadIndex = messagesProps.findIndex(
m => m.propsForMessage.id === firstUnreadOnOpen m => m.propsForMessage.id === firstUnreadOnOpen
); );
console.warn('firstUnreadIndex', firstUnreadIndex);
if (firstUnreadIndex === -1) { if (firstUnreadIndex === -1) {
// the first unread message is not in the 30 most recent messages // the first unread message is not in the 30 most recent messages
@ -260,8 +267,10 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
} }
} }
private scrollToMessage(messageId: string, block: 'center' | 'end' | 'nearest' | 'start') { private scrollToMessage(messageId: string, block: ScrollLogicalPosition | undefined) {
const messageElementDom = document.getElementById(`msg-${messageId}`); const messageElementDom = document.getElementById(`msg-${messageId}`);
console.warn('scrollToMessage', messageElementDom);
debugger;
messageElementDom?.scrollIntoView({ messageElementDom?.scrollIntoView({
behavior: 'auto', behavior: 'auto',
block, block,

@ -37,6 +37,7 @@ async function getMediaGalleryProps(
documents: Array<MediaItemType>; documents: Array<MediaItemType>;
media: Array<MediaItemType>; media: Array<MediaItemType>;
}> { }> {
console.warn('getMediaGalleryProps');
// We fetch more documents than media as they dont require to be loaded // We fetch more documents than media as they dont require to be loaded
// into memory right away. Revisit this once we have infinite scrolling: // into memory right away. Revisit this once we have infinite scrolling:
const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, { const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, {

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { getMessageById, getMessagesByConversation } from '../../../../data/data'; import { getLastMessagesByConversation, getMessageById } from '../../../../data/data';
import { getConversationController } from '../../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { AttachmentDownloads } from '../../../../session/utils'; import { AttachmentDownloads } from '../../../../session/utils';
import { updateConfirmModal } from '../../../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../../../state/ducks/modalDialog';
@ -42,9 +42,7 @@ export const ClickToTrustSender = (props: { messageId: string }) => {
onClickOk: async () => { onClickOk: async () => {
convo.set({ isTrustedForAttachmentDownload: true }); convo.set({ isTrustedForAttachmentDownload: true });
await convo.commit(); await convo.commit();
const messagesInConvo = await getMessagesByConversation(convo.id, { const messagesInConvo = await getLastMessagesByConversation(convo.id, 100, false);
limit: 100,
});
await Promise.all( await Promise.all(
messagesInConvo.map(async message => { messagesInConvo.map(async message => {

@ -12,7 +12,6 @@ import { messageExpired } from '../../../../state/ducks/conversations';
import { import {
getGenericReadableMessageSelectorProps, getGenericReadableMessageSelectorProps,
getIsMessageSelected, getIsMessageSelected,
getQuotedMessageToAnimate,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { getIncrement } from '../../../../util/timer'; import { getIncrement } from '../../../../util/timer';

@ -3,15 +3,14 @@ import React, { useCallback } from 'react';
import { InView } from 'react-intersection-observer'; import { InView } from 'react-intersection-observer';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getMessageById } from '../../../../data/data'; import { getMessageById } from '../../../../data/data';
import { Constants } from '../../../../session';
import { getConversationController } from '../../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { import {
fetchMessagesForConversation, fetchTopMessagesForConversation,
markConversationFullyRead, markConversationFullyRead,
showScrollToBottomButton, showScrollToBottomButton,
} from '../../../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { import {
areMoreMessagesBeingFetched, areMoreTopMessagesBeingFetched,
getHaveDoneFirstScroll, getHaveDoneFirstScroll,
getLoadedMessagesLength, getLoadedMessagesLength,
getMostRecentMessageId, getMostRecentMessageId,
@ -29,13 +28,12 @@ type ReadableMessageProps = {
onContextMenu?: (e: React.MouseEvent<HTMLElement>) => void; onContextMenu?: (e: React.MouseEvent<HTMLElement>) => void;
}; };
const debouncedTriggerLoadMore = _.debounce( const debouncedTriggerLoadMoreTop = _.debounce(
(loadedMessagesLength: number, selectedConversationKey: string | undefined) => { (selectedConversationKey: string, oldestMessageId: string) => {
const numMessages = loadedMessagesLength + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
(window.inboxStore?.dispatch as any)( (window.inboxStore?.dispatch as any)(
fetchMessagesForConversation({ fetchTopMessagesForConversation({
conversationKey: selectedConversationKey as string, conversationKey: selectedConversationKey,
count: numMessages, oldTopMessageId: oldestMessageId,
}) })
); );
}, },
@ -53,7 +51,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
const haveDoneFirstScroll = useSelector(getHaveDoneFirstScroll); const haveDoneFirstScroll = useSelector(getHaveDoneFirstScroll);
const mostRecentMessageId = useSelector(getMostRecentMessageId); const mostRecentMessageId = useSelector(getMostRecentMessageId);
const oldestMessageId = useSelector(getOldestMessageId); const oldestMessageId = useSelector(getOldestMessageId);
const fetchingMore = useSelector(areMoreMessagesBeingFetched); const fetchingMore = useSelector(areMoreTopMessagesBeingFetched);
const shouldMarkReadWhenVisible = isUnread; const shouldMarkReadWhenVisible = isUnread;
const onVisible = useCallback( const onVisible = useCallback(
@ -83,8 +81,14 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
} }
} }
if (inView === true && isAppFocused && oldestMessageId === messageId && !fetchingMore) { if (
debouncedTriggerLoadMore(loadedMessagesLength, selectedConversationKey); inView === true &&
isAppFocused &&
oldestMessageId === messageId &&
!fetchingMore &&
selectedConversationKey
) {
debouncedTriggerLoadMoreTop(selectedConversationKey, oldestMessageId);
} }
// this part is just handling the marking of the message as read if needed // this part is just handling the marking of the message as read if needed
@ -115,7 +119,6 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
receivedAt, receivedAt,
shouldMarkReadWhenVisible, shouldMarkReadWhenVisible,
messageId, messageId,
debouncedTriggerLoadMore,
] ]
); );

@ -3,11 +3,7 @@ import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useUpdate from 'react-use/lib/useUpdate'; import useUpdate from 'react-use/lib/useUpdate';
import { import { createOrUpdateItem, hasLinkPreviewPopupBeenDisplayed } from '../../../data/data';
createOrUpdateItem,
fillWithTestData,
hasLinkPreviewPopupBeenDisplayed,
} from '../../../data/data';
import { ToastUtils } from '../../../session/utils'; import { ToastUtils } from '../../../session/utils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { toggleAudioAutoplay } from '../../../state/ducks/userConfig'; import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';

@ -125,6 +125,7 @@ const channelsToMake = {
getOutgoingWithoutExpiresAt, getOutgoingWithoutExpiresAt,
getNextExpiringMessage, getNextExpiringMessage,
getMessagesByConversation, getMessagesByConversation,
getLastMessagesByConversation,
getFirstUnreadMessageIdInConversation, getFirstUnreadMessageIdInConversation,
hasConversationOutgoingMessage, hasConversationOutgoingMessage,
getSeenMessagesByHashList, getSeenMessagesByHashList,
@ -753,18 +754,45 @@ export async function getUnreadCountByConversation(conversationId: string): Prom
export async function getMessagesByConversation( export async function getMessagesByConversation(
conversationId: string, conversationId: string,
{ limit = 100, receivedAt = Number.MAX_VALUE, type = '%', skipTimerInit = false } {
skipTimerInit = false,
messageId = null,
}: { limit?: number; skipTimerInit?: false; messageId: string | null }
): Promise<MessageCollection> { ): Promise<MessageCollection> {
const messages = await channels.getMessagesByConversation(conversationId, { const messages = await channels.getMessagesByConversation(conversationId, {
limit, messageId,
receivedAt,
type,
}); });
if (skipTimerInit) { if (skipTimerInit) {
for (const message of messages) { for (const message of messages) {
message.skipTimerInit = skipTimerInit; message.skipTimerInit = skipTimerInit;
} }
} }
console.warn('messages length got: ', messages.length);
return new MessageCollection(messages);
}
/**
* This function should only be used when you don't want to render the messages.
* It just grabs the last messages of a conversation.
*
* To be used when you want for instance to remove messages from a conversations, in order.
* Or to trigger downloads of a attachments from a just approved contact (clicktotrustSender)
* @param conversationId the conversationId to fetch messages from
* @param limit the maximum number of messages to return
* @param skipTimerInit see MessageModel.skipTimerInit
* @returns the fetched messageModels
*/
export async function getLastMessagesByConversation(
conversationId: string,
limit: number,
skipTimerInit: boolean
): Promise<MessageCollection> {
const messages = await channels.getLastMessagesByConversation(conversationId, limit);
if (skipTimerInit) {
for (const message of messages) {
message.skipTimerInit = skipTimerInit;
}
}
return new MessageCollection(messages); return new MessageCollection(messages);
} }
@ -795,12 +823,10 @@ export async function getSeenMessagesByHashList(hashes: Array<string>): Promise<
export async function removeAllMessagesInConversation(conversationId: string): Promise<void> { export async function removeAllMessagesInConversation(conversationId: string): Promise<void> {
let messages; let messages;
do { do {
// Yes, we really want the await in the loop. We're deleting 100 at a // Yes, we really want the await in the loop. We're deleting 500 at a
// time so we don't use too much memory. // time so we don't use too much memory.
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
messages = await getMessagesByConversation(conversationId, { messages = await getLastMessagesByConversation(conversationId, 500, false);
limit: 500,
});
if (!messages.length) { if (!messages.length) {
return; return;
} }

@ -12,7 +12,7 @@ import { MessageModel } from './message';
import { MessageAttributesOptionals, MessageModelType } from './messageType'; import { MessageAttributesOptionals, MessageModelType } from './messageType';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { import {
getMessagesByConversation, getLastMessagesByConversation,
getUnreadByConversation, getUnreadByConversation,
getUnreadCountByConversation, getUnreadCountByConversation,
removeMessage as dataRemoveMessage, removeMessage as dataRemoveMessage,
@ -787,10 +787,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (!this.get('active_at')) { if (!this.get('active_at')) {
return; return;
} }
const messages = await getMessagesByConversation(this.id, { const messages = await getLastMessagesByConversation(this.id, 1, true);
limit: 1,
skipTimerInit: true,
});
const lastMessageModel = messages.at(0); const lastMessageModel = messages.at(0);
const lastMessageJSON = lastMessageModel ? lastMessageModel.toJSON() : null; const lastMessageJSON = lastMessageModel ? lastMessageModel.toJSON() : null;
const lastMessageStatusModel = lastMessageModel const lastMessageStatusModel = lastMessageModel

@ -1,4 +1,3 @@
import { Constants } from '../../session';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { getFirstUnreadMessageIdInConversation, getMessagesByConversation } from '../../data/data'; import { getFirstUnreadMessageIdInConversation, getMessagesByConversation } from '../../data/data';
@ -273,7 +272,8 @@ export type ConversationsStateType = {
selectedMessageIds: Array<string>; selectedMessageIds: Array<string>;
lightBox?: LightBoxOptions; lightBox?: LightBoxOptions;
quotedMessage?: ReplyingToMessageProps; quotedMessage?: ReplyingToMessageProps;
areMoreMessagesBeingFetched: boolean; areMoreTopMessagesBeingFetched: boolean;
oldTopMessageId: string | null;
haveDoneFirstScroll: boolean; haveDoneFirstScroll: boolean;
showScrollButton: boolean; showScrollButton: boolean;
@ -287,28 +287,23 @@ export type MentionsMembersType = Array<{
authorProfileName: string; authorProfileName: string;
}>; }>;
async function getMessages( async function getMessages({
conversationKey: string, conversationKey,
numMessagesToFetch: number messageId,
): Promise<Array<MessageModelPropsWithoutConvoProps>> { }: {
conversationKey: string;
messageId: string | null;
}): Promise<Array<MessageModelPropsWithoutConvoProps>> {
const conversation = getConversationController().get(conversationKey); const conversation = getConversationController().get(conversationKey);
if (!conversation) { if (!conversation) {
// no valid conversation, early return // no valid conversation, early return
window?.log?.error('Failed to get convo on reducer.'); window?.log?.error('Failed to get convo on reducer.');
return []; return [];
} }
let msgCount = numMessagesToFetch; console.warn('getMessages with messageId', messageId);
msgCount =
msgCount > Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
? Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
: msgCount;
if (msgCount < Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) {
msgCount = Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
}
const messageSet = await getMessagesByConversation(conversationKey, { const messageSet = await getMessagesByConversation(conversationKey, {
limit: msgCount, messageId,
}); });
const messageProps: Array<MessageModelPropsWithoutConvoProps> = messageSet.models.map(m => const messageProps: Array<MessageModelPropsWithoutConvoProps> = messageSet.models.map(m =>
@ -325,24 +320,27 @@ export type SortedMessageModelProps = MessageModelPropsWithoutConvoProps & {
type FetchedMessageResults = { type FetchedMessageResults = {
conversationKey: string; conversationKey: string;
messagesProps: Array<MessageModelPropsWithoutConvoProps>; messagesProps: Array<MessageModelPropsWithoutConvoProps>;
oldTopMessageId: string | null;
}; };
export const fetchMessagesForConversation = createAsyncThunk( export const fetchTopMessagesForConversation = createAsyncThunk(
'messages/fetchByConversationKey', 'messages/fetchByConversationKey',
async ({ async ({
conversationKey, conversationKey,
count, oldTopMessageId,
}: { }: {
conversationKey: string; conversationKey: string;
count: number; oldTopMessageId: string | null;
}): Promise<FetchedMessageResults> => { }): Promise<FetchedMessageResults> => {
console.warn('fetchTopMessagesForConversation oldTop:', oldTopMessageId);
const beforeTimestamp = Date.now(); const beforeTimestamp = Date.now();
// tslint:disable-next-line: no-console perfStart('fetchTopMessagesForConversation');
perfStart('fetchMessagesForConversation'); const messagesProps = await getMessages({
const messagesProps = await getMessages(conversationKey, count); conversationKey,
messageId: oldTopMessageId,
});
const afterTimestamp = Date.now(); const afterTimestamp = Date.now();
// tslint:disable-next-line: no-console perfEnd('fetchTopMessagesForConversation', 'fetchTopMessagesForConversation');
perfEnd('fetchMessagesForConversation', 'fetchMessagesForConversation');
const time = afterTimestamp - beforeTimestamp; const time = afterTimestamp - beforeTimestamp;
window?.log?.info(`Loading ${messagesProps.length} messages took ${time}ms to load.`); window?.log?.info(`Loading ${messagesProps.length} messages took ${time}ms to load.`);
@ -350,6 +348,7 @@ export const fetchMessagesForConversation = createAsyncThunk(
return { return {
conversationKey, conversationKey,
messagesProps, messagesProps,
oldTopMessageId,
}; };
} }
); );
@ -363,11 +362,12 @@ export function getEmptyConversationState(): ConversationsStateType {
messageDetailProps: undefined, messageDetailProps: undefined,
showRightPanel: false, showRightPanel: false,
selectedMessageIds: [], selectedMessageIds: [],
areMoreMessagesBeingFetched: false, areMoreTopMessagesBeingFetched: false,
showScrollButton: false, showScrollButton: false,
mentionMembers: [], mentionMembers: [],
firstUnreadMessageId: undefined, firstUnreadMessageId: undefined,
haveDoneFirstScroll: false, haveDoneFirstScroll: false,
oldTopMessageId: null,
}; };
} }
@ -697,7 +697,7 @@ const conversationsSlice = createSlice({
conversationLookup: state.conversationLookup, conversationLookup: state.conversationLookup,
selectedConversation: action.payload.id, selectedConversation: action.payload.id,
areMoreMessagesBeingFetched: false, areMoreTopMessagesBeingFetched: false,
messages: action.payload.initialMessages, messages: action.payload.initialMessages,
showRightPanel: false, showRightPanel: false,
selectedMessageIds: [], selectedMessageIds: [],
@ -708,6 +708,7 @@ const conversationsSlice = createSlice({
nextMessageToPlay: undefined, nextMessageToPlay: undefined,
showScrollButton: false, showScrollButton: false,
animateQuotedMessageId: undefined, animateQuotedMessageId: undefined,
oldTopMessageId: null,
mentionMembers: [], mentionMembers: [],
firstUnreadMessageId: action.payload.firstUnreadIdOnOpen, firstUnreadMessageId: action.payload.firstUnreadIdOnOpen,
@ -762,26 +763,28 @@ const conversationsSlice = createSlice({
extraReducers: (builder: any) => { extraReducers: (builder: any) => {
// Add reducers for additional action types here, and handle loading state as needed // Add reducers for additional action types here, and handle loading state as needed
builder.addCase( builder.addCase(
fetchMessagesForConversation.fulfilled, fetchTopMessagesForConversation.fulfilled,
(state: ConversationsStateType, action: PayloadAction<FetchedMessageResults>) => { (state: ConversationsStateType, action: PayloadAction<FetchedMessageResults>) => {
// this is called once the messages are loaded from the db for the currently selected conversation // this is called once the messages are loaded from the db for the currently selected conversation
const { messagesProps, conversationKey } = action.payload; const { messagesProps, conversationKey, oldTopMessageId } = action.payload;
// double check that this update is for the shown convo // double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) { if (conversationKey === state.selectedConversation) {
console.warn('fullfilled', oldTopMessageId);
return { return {
...state, ...state,
oldTopMessageId,
messages: messagesProps, messages: messagesProps,
areMoreMessagesBeingFetched: false, areMoreTopMessagesBeingFetched: false,
}; };
} }
return state; return state;
} }
); );
builder.addCase(fetchMessagesForConversation.pending, (state: ConversationsStateType) => { builder.addCase(fetchTopMessagesForConversation.pending, (state: ConversationsStateType) => {
state.areMoreMessagesBeingFetched = true; state.areMoreTopMessagesBeingFetched = true;
}); });
builder.addCase(fetchMessagesForConversation.rejected, (state: ConversationsStateType) => { builder.addCase(fetchTopMessagesForConversation.rejected, (state: ConversationsStateType) => {
state.areMoreMessagesBeingFetched = false; state.areMoreTopMessagesBeingFetched = false;
}); });
}, },
}); });
@ -855,7 +858,10 @@ export async function openConversationWithMessages(args: {
// preload 30 messages // preload 30 messages
perfStart('getMessages'); perfStart('getMessages');
const initialMessages = await getMessages(conversationKey, 30); const initialMessages = await getMessages({
conversationKey,
messageId: null,
});
perfEnd('getMessages', 'getMessages'); perfEnd('getMessages', 'getMessages');
window.inboxStore?.dispatch( window.inboxStore?.dispatch(

@ -590,9 +590,9 @@ export const getQuotedMessage = createSelector(
(state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage (state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage
); );
export const areMoreMessagesBeingFetched = createSelector( export const areMoreTopMessagesBeingFetched = createSelector(
getConversations, getConversations,
(state: ConversationsStateType): boolean => state.areMoreMessagesBeingFetched || false (state: ConversationsStateType): boolean => state.areMoreTopMessagesBeingFetched || false
); );
export const getHaveDoneFirstScroll = createSelector( export const getHaveDoneFirstScroll = createSelector(
@ -1116,3 +1116,8 @@ export const getGenericReadableMessageSelectorProps = createSelector(
return msgProps; return msgProps;
} }
); );
export const getOldTopMessageId = createSelector(
getConversations,
(state: ConversationsStateType): string | null => state.oldTopMessageId || null
);

Loading…
Cancel
Save