mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
keep scrolled position when adding messages at the bottom
This commit is contained in:
parent
06dfaa2482
commit
119b6e1baf
11 changed files with 50 additions and 109 deletions
|
@ -23,7 +23,7 @@ export const DataExtractionNotification = (props: PropsForDataExtractionNotifica
|
|||
flexDirection="column"
|
||||
alignItems="center"
|
||||
margin={theme.common.margins.sm}
|
||||
id={`data-extraction-${messageId}`}
|
||||
id={`msg-${messageId}`}
|
||||
>
|
||||
<SessionIcon
|
||||
iconType={SessionIconType.Upload}
|
||||
|
|
|
@ -15,7 +15,7 @@ export const GroupInvitation = (props: PropsForGroupInvitation) => {
|
|||
const openGroupInvitation = window.i18n('openGroupInvitation');
|
||||
|
||||
return (
|
||||
<div className="group-invitation-container" id={`group-invit-${props.messageId}`}>
|
||||
<div className="group-invitation-container" id={`msg-${props.messageId}`}>
|
||||
<div className={classNames(classes)}>
|
||||
<div className="contents">
|
||||
<SessionIconButton
|
||||
|
|
|
@ -91,7 +91,7 @@ function renderChange(change: PropsForGroupUpdateType) {
|
|||
export const GroupNotification = (props: PropsForGroupUpdate) => {
|
||||
const { changes } = props;
|
||||
return (
|
||||
<div className="module-group-notification" id={`group-notif-${props.messageId}`}>
|
||||
<div className="module-group-notification" id={`msg-${props.messageId}`}>
|
||||
{(changes || []).map((change, index) => (
|
||||
<div key={index} className="module-group-notification__change">
|
||||
{renderChange(change)}
|
||||
|
|
|
@ -652,11 +652,13 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
// when the view first loads, it needs to scroll to the unread messages.
|
||||
// we need to disable the inview on the first loading
|
||||
if (!this.props.haveDoneFirstScroll) {
|
||||
console.warn('waiting for first scroll');
|
||||
if (inView === true) {
|
||||
window.log.info('onVisible but waiting for first scroll event');
|
||||
}
|
||||
return;
|
||||
}
|
||||
// we are the bottom message
|
||||
if (this.props.mostRecentMessageId === messageId) {
|
||||
if (this.props.mostRecentMessageId === messageId && isElectronWindowFocused()) {
|
||||
if (inView === true) {
|
||||
window.inboxStore?.dispatch(showScrollToBottomButton(false));
|
||||
void getConversationController()
|
||||
|
@ -671,12 +673,8 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
window.inboxStore?.dispatch(showScrollToBottomButton(true));
|
||||
}
|
||||
}
|
||||
console.warn('oldestMessageId', this.props.oldestMessageId);
|
||||
console.warn('mostRecentMessageId', this.props.mostRecentMessageId);
|
||||
console.warn('messageId', messageId);
|
||||
if (inView === true && this.props.oldestMessageId === messageId && !fetchingMore) {
|
||||
console.warn('loadMoreMessages');
|
||||
|
||||
if (inView === true && this.props.oldestMessageId === messageId && !fetchingMore) {
|
||||
this.loadMoreMessages();
|
||||
}
|
||||
if (inView === true && shouldMarkReadWhenVisible && isElectronWindowFocused()) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { useFocus } from '../../hooks/useFocus';
|
||||
import { InView, useInView } from 'react-intersection-observer';
|
||||
import { InView } from 'react-intersection-observer';
|
||||
|
||||
type ReadableMessageProps = {
|
||||
children: React.ReactNode;
|
||||
|
@ -16,11 +16,11 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
|
|||
|
||||
return (
|
||||
<InView
|
||||
id={`inview-${messageId}`}
|
||||
id={`msg-${messageId}`}
|
||||
{...props}
|
||||
as="div"
|
||||
threshold={0.5}
|
||||
delay={20}
|
||||
delay={100}
|
||||
triggerOnce={false}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
@ -34,7 +34,7 @@ const TimerNotificationContent = (props: PropsForExpirationTimer) => {
|
|||
|
||||
export const TimerNotification = (props: PropsForExpirationTimer) => {
|
||||
return (
|
||||
<div className="module-timer-notification" id={props.messageId}>
|
||||
<div className="module-timer-notification" id={`msg-${props.messageId}`}>
|
||||
<div className="module-timer-notification__message">
|
||||
<div>
|
||||
<SessionIcon
|
||||
|
|
|
@ -19,7 +19,6 @@ import { ToastUtils, UserUtils } from '../../../session/utils';
|
|||
import * as MIME from '../../../types/MIME';
|
||||
import { SessionFileDropzone } from './SessionFileDropzone';
|
||||
import {
|
||||
fetchMessagesForConversation,
|
||||
quoteMessage,
|
||||
ReduxConversationType,
|
||||
resetSelectedMessageIds,
|
||||
|
@ -158,7 +157,6 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
if (newConversationKey !== oldConversationKey) {
|
||||
void this.loadInitialMessages();
|
||||
this.setState({
|
||||
showRecordingView: false,
|
||||
stagedAttachments: [],
|
||||
|
@ -293,26 +291,6 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~~~ GETTER METHODS ~~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
public async loadInitialMessages() {
|
||||
const { selectedConversation, selectedConversationKey } = this.props;
|
||||
|
||||
if (!selectedConversation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// lets load only 50 messages and let the user scroll up if he needs more context
|
||||
(window.inboxStore?.dispatch as any)(
|
||||
fetchMessagesForConversation({
|
||||
conversationKey: selectedConversationKey,
|
||||
count: 30, // first page
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~ MICROPHONE METHODS ~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
import { SessionScrollButton } from '../SessionScrollButton';
|
||||
import { Constants } from '../../../session';
|
||||
import _ from 'lodash';
|
||||
import { contextMenu } from 'react-contexify';
|
||||
import {
|
||||
fetchMessagesForConversation,
|
||||
markConversationFullyRead,
|
||||
quotedMessageToAnimate,
|
||||
ReduxConversationType,
|
||||
setNextMessageToPlay,
|
||||
|
@ -25,14 +22,12 @@ import { ConversationTypeEnum } from '../../../models/conversation';
|
|||
import { StateType } from '../../../state/reducer';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
areMoreMessagesBeingFetched,
|
||||
getQuotedMessageToAnimate,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationKey,
|
||||
getShowScrollButton,
|
||||
getSortedMessagesOfSelectedConversation,
|
||||
} from '../../../state/selectors/conversations';
|
||||
import { isElectronWindowFocused } from '../../../session/utils/WindowUtils';
|
||||
import { SessionMessagesList } from './SessionMessagesList';
|
||||
|
||||
export type SessionMessageListProps = {
|
||||
|
@ -70,13 +65,8 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(
|
||||
prevProps: Props,
|
||||
_prevState: any,
|
||||
snapshot: { scrollHeight: number; scrollTop: number }
|
||||
) {
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
const isSameConvo = prevProps.conversationKey === this.props.conversationKey;
|
||||
const messageLengthChanged = prevProps.messagesProps.length !== this.props.messagesProps.length;
|
||||
if (
|
||||
!isSameConvo ||
|
||||
(prevProps.messagesProps.length === 0 && this.props.messagesProps.length !== 0)
|
||||
|
@ -85,47 +75,9 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
|
||||
// displayed conversation changed. We have a bit of cleaning to do here
|
||||
this.initialMessageLoadingPosition();
|
||||
} else {
|
||||
// if we got new message for this convo, and we are scrolled to bottom
|
||||
if (isSameConvo && messageLengthChanged) {
|
||||
// If we have a snapshot value, we've just added new items.
|
||||
// Adjust scroll so these new items don't push the old ones out of view.
|
||||
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
|
||||
if (prevProps.messagesProps.length && snapshot !== null) {
|
||||
const list = this.props.messageContainerRef.current;
|
||||
|
||||
// if we added a message at the top, keep position from the bottom.
|
||||
if (
|
||||
prevProps.messagesProps[0].propsForMessage.id ===
|
||||
this.props.messagesProps[0].propsForMessage.id
|
||||
) {
|
||||
list.scrollTop = list.scrollHeight - (snapshot.scrollHeight - snapshot.scrollTop);
|
||||
} else {
|
||||
// if we added a message at the bottom, keep position from the bottom.
|
||||
list.scrollTop = snapshot.scrollTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getSnapshotBeforeUpdate(prevProps: Props) {
|
||||
// getSnapshotBeforeUpdate is kind of pain to do in react hooks, so better keep the message list as a
|
||||
// class component for now
|
||||
|
||||
// Are we adding new items to the list?
|
||||
// Capture the scroll position so we can adjust scroll later.
|
||||
if (prevProps.messagesProps.length < this.props.messagesProps.length) {
|
||||
const list = this.props.messageContainerRef.current;
|
||||
console.warn('getSnapshotBeforeUpdate ', {
|
||||
scrollHeight: list.scrollHeight,
|
||||
scrollTop: list.scrollTop,
|
||||
});
|
||||
return { scrollHeight: list.scrollHeight, scrollTop: list.scrollTop };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { conversationKey, conversation } = this.props;
|
||||
|
||||
|
@ -203,21 +155,33 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
*/
|
||||
private initialMessageLoadingPosition() {
|
||||
const { messagesProps, conversation } = this.props;
|
||||
if (!conversation) {
|
||||
if (!conversation || !messagesProps.length) {
|
||||
return;
|
||||
}
|
||||
if (conversation.unreadCount > 0 && messagesProps.length) {
|
||||
|
||||
if (conversation.unreadCount <= 0) {
|
||||
this.scrollToBottom();
|
||||
} else {
|
||||
// just assume that this need to be shown by default
|
||||
window.inboxStore?.dispatch(showScrollToBottomButton(true));
|
||||
|
||||
// conversation.unreadCount > 0
|
||||
// either we loaded all unread messages or not
|
||||
if (conversation.unreadCount < messagesProps.length) {
|
||||
// if we loaded all unread messages, scroll to the first one unread
|
||||
const firstUnread = Math.max(conversation.unreadCount, 0);
|
||||
this.scrollToMessage(messagesProps[firstUnread].propsForMessage.id);
|
||||
const idToStringTo = messagesProps[conversation.unreadCount - 1].propsForMessage.id;
|
||||
|
||||
this.scrollToMessage(idToStringTo, 'end');
|
||||
} else {
|
||||
// if we did not load all unread messages, just scroll to the middle of the loaded messages list. so the user can choose to go up or down from there
|
||||
// just scroll to the middle as we don't have enough loaded message nevertheless
|
||||
const middle = Math.floor(messagesProps.length / 2);
|
||||
this.scrollToMessage(messagesProps[middle].propsForMessage.id);
|
||||
const idToStringTo = messagesProps[middle].propsForMessage.id;
|
||||
this.scrollToMessage(idToStringTo, 'center');
|
||||
}
|
||||
}
|
||||
// window.inboxStore?.dispatch(updateHaveDoneFirstScroll());
|
||||
|
||||
setTimeout(() => {
|
||||
window.inboxStore?.dispatch(updateHaveDoneFirstScroll());
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,11 +205,11 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
private scrollToMessage(messageId: string) {
|
||||
const messageElementDom = document.getElementById(`inview-${messageId}`);
|
||||
private scrollToMessage(messageId: string, block: 'center' | 'end' | 'nearest' | 'start') {
|
||||
const messageElementDom = document.getElementById(`msg-${messageId}`);
|
||||
messageElementDom?.scrollIntoView({
|
||||
behavior: 'auto',
|
||||
block: 'center',
|
||||
block,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -254,7 +218,6 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
if (!messageContainer) {
|
||||
return;
|
||||
}
|
||||
console.warn('scrollToBottom on messageslistcontainer');
|
||||
messageContainer.scrollTop = messageContainer.scrollHeight - messageContainer.clientHeight;
|
||||
}
|
||||
|
||||
|
@ -304,7 +267,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
}
|
||||
|
||||
const databaseId = targetMessage.propsForMessage.id;
|
||||
this.scrollToMessage(databaseId);
|
||||
this.scrollToMessage(databaseId, 'center');
|
||||
// Highlight this message on the UI
|
||||
window.inboxStore?.dispatch(quotedMessageToAnimate(databaseId));
|
||||
this.setupTimeoutResetQuotedHighlightedMessage(databaseId);
|
||||
|
|
|
@ -190,7 +190,6 @@ export const sendViaOnion = async (
|
|||
);
|
||||
} catch (e) {
|
||||
window?.log?.warn('sendViaOnionRetryable failed ', e);
|
||||
// console.warn('error to show to user', e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -787,6 +787,7 @@ export async function openConversationWithMessages(args: {
|
|||
const { conversationKey, messageId } = args;
|
||||
const firstUnreadIdOnOpen = await getFirstUnreadMessageIdInConversation(conversationKey);
|
||||
|
||||
// preload 30 messages
|
||||
const initialMessages = await getMessages(conversationKey, 30);
|
||||
|
||||
window.inboxStore?.dispatch(
|
||||
|
|
|
@ -415,19 +415,21 @@ export const getFirstUnreadMessageId = createSelector(
|
|||
);
|
||||
|
||||
export const getMostRecentMessageId = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): string | undefined => {
|
||||
return state.messages.length ? state.messages[0].propsForMessage.id : undefined;
|
||||
getSortedMessagesOfSelectedConversation,
|
||||
(messages: Array<MessageModelProps>): string | undefined => {
|
||||
return messages.length ? messages[0].propsForMessage.id : undefined;
|
||||
}
|
||||
);
|
||||
|
||||
export const getOldestMessageId = createSelector(getConversations, (state: ConversationsStateType):
|
||||
| string
|
||||
| undefined => {
|
||||
return state.messages.length
|
||||
? state.messages[state.messages.length - 1].propsForMessage.id
|
||||
: undefined;
|
||||
});
|
||||
export const getOldestMessageId = createSelector(
|
||||
getSortedMessagesOfSelectedConversation,
|
||||
(messages: Array<MessageModelProps>): string | undefined => {
|
||||
const oldest =
|
||||
messages.length > 0 ? messages[messages.length - 1].propsForMessage.id : undefined;
|
||||
|
||||
return oldest;
|
||||
}
|
||||
);
|
||||
|
||||
export const getLoadedMessagesLength = createSelector(
|
||||
getConversations,
|
||||
|
|
Loading…
Reference in a new issue