fix: close messageInfo when message not in redux store

pull/3021/head
Audric Ackermann 4 months ago
parent 5cd0a9f71d
commit 037d87349f

@ -76,7 +76,6 @@ interface Props {
selectedConversation?: ReduxConversationType;
messagesProps: Array<SortedMessageModelProps>;
selectedMessages: Array<string>;
showMessageDetails: boolean;
isRightPanelShowing: boolean;
hasOngoingCallWithFocusedConvo: boolean;
htmlDirection: HTMLDirection;

@ -22,7 +22,7 @@ import { MessageRenderingProps } from '../../../../models/messageType';
import { pushUnblockToSend } from '../../../../session/utils/Toast';
import {
openRightPanel,
showMessageDetailsView,
showMessageInfoView,
toggleSelectedMessageId,
} from '../../../../state/ducks/conversations';
import { setRightOverlayMode } from '../../../../state/ducks/section';
@ -173,8 +173,7 @@ export const showMessageInfoOverlay = async ({
}) => {
const found = await Data.getMessageById(messageId);
if (found) {
const messageDetailsProps = await found.getPropsForMessageDetail();
dispatch(showMessageDetailsView(messageDetailsProps));
dispatch(showMessageInfoView(messageId));
dispatch(
setRightOverlayMode({
type: 'message_info',

@ -1,27 +1,44 @@
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { closeMessageDetailsView, closeRightPanel } from '../../../../../state/ducks/conversations';
import {
PropsForAttachment,
closeMessageInfoView,
closeRightPanel,
} from '../../../../../state/ducks/conversations';
import { resetRightOverlayMode, setRightOverlayMode } from '../../../../../state/ducks/section';
import { getMessageDetailsViewProps } from '../../../../../state/selectors/conversations';
import { getMessageInfoId } from '../../../../../state/selectors/conversations';
import { Flex } from '../../../../basic/Flex';
import { Header, HeaderTitle, StyledScrollContainer } from '../components';
import { Data } from '../../../../../data/data';
import {
replyToMessage,
resendMessage,
} from '../../../../../interactions/conversationInteractions';
import { deleteMessagesById } from '../../../../../interactions/conversations/unsendingInteractions';
import {
useMessageAttachments,
useMessageDirection,
useMessageIsDeletable,
useMessageQuote,
useMessageSender,
useMessageServerTimestamp,
useMessageText,
useMessageTimestamp,
} from '../../../../../state/selectors';
import { getRightOverlayMode } from '../../../../../state/selectors/section';
import { useSelectedConversationKey } from '../../../../../state/selectors/selectedConversation';
import { canDisplayImage } from '../../../../../types/Attachment';
import { isAudio } from '../../../../../types/MIME';
import {
getAudioDuration,
getVideoDuration,
} from '../../../../../types/attachments/VisualAttachment';
import { GoogleChrome } from '../../../../../util';
import { saveAttachmentToDisk } from '../../../../../util/attachmentsUtil';
import { SpacerLG, SpacerMD, SpacerXL } from '../../../../basic/Text';
import { PanelButtonGroup, PanelIconButton } from '../../../../buttons';
@ -78,36 +95,133 @@ const StyledMessageDetail = styled.div`
padding: var(--margins-sm) var(--margins-2xl) var(--margins-lg);
`;
type MessagePropsDetails = {
errors: Array<Error>;
attachments: Array<PropsForAttachment>;
};
async function getPropsForMessageDetail(
messageId: string | undefined,
attachments: Array<PropsForAttachment>
): Promise<MessagePropsDetails | null> {
if (!messageId) {
return null;
}
const found = await Data.getMessageById(messageId);
const attachmentsWithMediaDetails: Array<PropsForAttachment> = [];
if (found) {
// process attachments so we have the fileSize, url and screenshots
for (let i = 0; i < attachments.length; i++) {
const props = found.getPropsForAttachment(attachments[i]);
if (
props?.contentType &&
GoogleChrome.isVideoTypeSupported(props?.contentType) &&
!props.duration &&
props.url
) {
// eslint-disable-next-line no-await-in-loop
const duration = await getVideoDuration({
objectUrl: props.url,
contentType: props.contentType,
});
attachmentsWithMediaDetails.push({
...props,
duration,
});
} else if (props?.contentType && isAudio(props.contentType) && !props.duration && props.url) {
// eslint-disable-next-line no-await-in-loop
const duration = await getAudioDuration({
objectUrl: props.url,
contentType: props.contentType,
});
attachmentsWithMediaDetails.push({
...props,
duration,
});
} else if (props) {
attachmentsWithMediaDetails.push(props);
}
}
// This will make the error message for outgoing key errors a bit nicer
const errors = (found.get('errors') || []).map((error: any) => {
return error;
});
const toRet: MessagePropsDetails = {
errors,
attachments: attachmentsWithMediaDetails,
};
return toRet;
}
return null;
}
function useMessageDetailProps(messageId: string | undefined): MessagePropsDetails | null {
const [details, setDetails] = useState<MessagePropsDetails | null>(null);
const fromState = useMessageAttachments(messageId);
// this is not ideal, but also doesn't seem to create any performance issue at the moment.
// TODO: ideally, we'd want to save the attachment duration anytime we save one to the disk (incoming/outgoing), and just retrieve it from the redux state here.
useEffect(() => {
let mounted = true;
// eslint-disable-next-line more/no-then
void getPropsForMessageDetail(messageId, fromState || [])
.then(result => {
if (mounted) {
setDetails(result);
}
})
.catch(window.log.error);
return () => {
mounted = false;
};
}, [fromState, messageId]);
return details;
}
export const OverlayMessageInfo = () => {
const dispatch = useDispatch();
const rightOverlayMode = useSelector(getRightOverlayMode);
const messageDetailProps = useSelector(getMessageDetailsViewProps);
const isDeletable = useMessageIsDeletable(messageDetailProps?.messageId);
const messageId = useSelector(getMessageInfoId);
const messageDetailProps = useMessageDetailProps(messageId);
const isDeletable = useMessageIsDeletable(messageId);
const direction = useMessageDirection(messageId);
const timestamp = useMessageTimestamp(messageId);
const serverTimestamp = useMessageServerTimestamp(messageId);
const sender = useMessageSender(messageId);
const dispatch = useDispatch();
// we close the right panel when switching conversation so the convoId of the convoId of that message
// is always the currently selected conversation
const convoId = useSelectedConversationKey();
useKey('Escape', () => {
const closePanel = useCallback(() => {
dispatch(closeRightPanel());
dispatch(resetRightOverlayMode());
dispatch(closeMessageDetailsView());
});
dispatch(closeMessageInfoView());
}, [dispatch]);
if (!rightOverlayMode || !messageDetailProps) {
useKey('Escape', closePanel);
useEffect(() => {
if (!sender) {
closePanel();
}
}, [sender, closePanel]);
if (!rightOverlayMode || !messageDetailProps || !convoId || !messageId || !sender) {
return null;
}
const { params } = rightOverlayMode;
const visibleAttachmentIndex = params?.visibleAttachmentIndex || 0;
const {
convoId,
messageId,
sender,
attachments,
timestamp,
serverTimestamp,
errors,
direction,
} = messageDetailProps;
const { errors, attachments } = messageDetailProps;
const hasAttachments = attachments && attachments.length > 0;
const supportsAttachmentCarousel = canDisplayImage(attachments);
@ -145,7 +259,7 @@ export const OverlayMessageInfo = () => {
closeButtonOnClick={() => {
dispatch(closeRightPanel());
dispatch(resetRightOverlayMode());
dispatch(closeMessageDetailsView());
dispatch(closeMessageInfoView());
}}
>
<HeaderTitle>{window.i18n('messageInfo')}</HeaderTitle>
@ -178,7 +292,7 @@ export const OverlayMessageInfo = () => {
<SpacerMD />
</>
)}
<MessageInfo />
<MessageInfo messageId={messageId} errors={messageDetailProps.errors} />
<SpacerLG />
<PanelButtonGroup style={{ margin: '0' }}>
<PanelIconButton

@ -2,10 +2,16 @@ import { ipcRenderer } from 'electron';
import { isEmpty } from 'lodash';
import moment from 'moment';
import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { MessageFrom } from '.';
import { getMessageDetailsViewProps } from '../../../../../../state/selectors/conversations';
import {
useMessageDirection,
useMessageReceivedAt,
useMessageSender,
useMessageServerTimestamp,
useMessageTimestamp,
} from '../../../../../../state/selectors';
import { Flex } from '../../../../../basic/Flex';
import { SpacerSM } from '../../../../../basic/Text';
@ -51,16 +57,18 @@ const showDebugLog = () => {
ipcRenderer.send('show-debug-log');
};
export const MessageInfo = () => {
const messageDetailProps = useSelector(getMessageDetailsViewProps);
export const MessageInfo = ({ messageId, errors }: { messageId: string; errors: Array<any> }) => {
const sender = useMessageSender(messageId);
const direction = useMessageDirection(messageId);
const sentAt = useMessageTimestamp(messageId);
const serverTimestamp = useMessageServerTimestamp(messageId);
const receivedAt = useMessageReceivedAt(messageId);
if (!messageDetailProps) {
if (!messageId || !sender) {
return null;
}
const { errors, receivedAt, sentAt, direction, sender } = messageDetailProps;
const sentAtStr = `${moment(sentAt).format(formatTimestamps)}`;
const sentAtStr = `${moment(serverTimestamp || sentAt).format(formatTimestamps)}`;
const receivedAtStr = `${moment(receivedAt).format(formatTimestamps)}`;
const hasError = !isEmpty(errors);

@ -68,7 +68,6 @@ import {
FindAndFormatContactType,
LastMessageStatusType,
MessageModelPropsWithoutConvoProps,
MessagePropsDetails,
PropsForAttachment,
PropsForExpirationTimer,
PropsForExpiringMessage,
@ -84,7 +83,6 @@ import {
messagesChanged,
} from '../state/ducks/conversations';
import { AttachmentTypeWithPath, isVoiceMessage } from '../types/Attachment';
import { isAudio } from '../types/MIME';
import {
deleteExternalMessageFiles,
getAbsoluteAttachmentPath,
@ -93,10 +91,8 @@ import {
loadQuoteData,
} from '../types/MessageAttachment';
import { ReactionList } from '../types/Reaction';
import { getAudioDuration, getVideoDuration } from '../types/attachments/VisualAttachment';
import { getAttachmentMetadata } from '../types/message/initializeAttachmentMetadata';
import { assertUnreachable, roomHasBlindEnabled } from '../types/sqlSharedTypes';
import { GoogleChrome } from '../util';
import { LinkPreviews } from '../util/linkPreviews';
import { Notifications } from '../util/notifications';
import { Storage } from '../util/storage';
@ -730,62 +726,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
};
}
public async getPropsForMessageDetail(): Promise<MessagePropsDetails> {
// process attachments so we have the fileSize, url and screenshots
const attachments = this.get('attachments') || [];
for (let i = 0; i < attachments.length; i++) {
let props = this.getPropsForAttachment(attachments[i]);
if (
props?.contentType &&
GoogleChrome.isVideoTypeSupported(props?.contentType) &&
!props.duration &&
props.url
) {
// eslint-disable-next-line no-await-in-loop
const duration = await getVideoDuration({
objectUrl: props.url,
contentType: props.contentType,
});
props = {
...props,
duration,
};
}
if (props?.contentType && isAudio(props?.contentType) && !props.duration && props.url) {
// eslint-disable-next-line no-await-in-loop
const duration = await getAudioDuration({
objectUrl: props.url,
contentType: props.contentType,
});
props = {
...props,
duration,
};
}
attachments[i] = props;
}
// This will make the error message for outgoing key errors a bit nicer
const errors = (this.get('errors') || []).map((error: any) => {
return error;
});
const toRet: MessagePropsDetails = {
sentAt: this.get('sent_at') || 0,
receivedAt: this.get('received_at') || 0,
convoId: this.get('conversationId'),
messageId: this.get('id'),
errors,
direction: this.get('direction'),
sender: this.get('source'),
attachments,
timestamp: this.get('timestamp'),
serverTimestamp: this.get('serverTimestamp'),
};
return toRet;
}
/**
* Uploads attachments, previews and quotes.
*

@ -5,6 +5,10 @@ import { ReplyingToMessageProps } from '../../components/conversation/compositio
import { QuotedAttachmentType } from '../../components/conversation/message/message-content/quote/Quote';
import { LightBoxOptions } from '../../components/conversation/SessionConversation';
import { Data } from '../../data/data';
import {
ConversationInteractionStatus,
ConversationInteractionType,
} from '../../interactions/conversationInteractions';
import {
CONVERSATION_PRIORITIES,
ConversationNotificationSettingType,
@ -22,10 +26,6 @@ import {
DisappearingMessageType,
} from '../../session/disappearing_messages/types';
import { ReactionList } from '../../types/Reaction';
import {
ConversationInteractionStatus,
ConversationInteractionType,
} from '../../interactions/conversationInteractions';
export type CallNotificationType = 'missed-call' | 'started-call' | 'answered-a-call';
@ -60,19 +60,6 @@ export type ContactPropsMessageDetail = {
errors?: Array<Error>;
};
export type MessagePropsDetails = {
sentAt: number;
receivedAt: number;
errors: Array<Error>;
sender: string;
convoId: string;
messageId: string;
direction: MessageModelType;
attachments: Array<PropsForAttachment>;
timestamp?: number;
serverTimestamp?: number;
};
export type LastMessageStatusType = 'sending' | 'sent' | 'read' | 'error' | undefined;
export type FindAndFormatContactType = {
@ -322,7 +309,7 @@ export type ConversationsStateType = {
// NOTE the messages quoted by other messages which are in view
quotes: QuoteLookupType;
firstUnreadMessageId: string | undefined;
messageDetailProps?: MessagePropsDetails;
messageInfoId: string | undefined;
showRightPanel: boolean;
selectedMessageIds: Array<string>;
lightBox?: LightBoxOptions;
@ -523,7 +510,7 @@ export function getEmptyConversationState(): ConversationsStateType {
conversationLookup: {},
messages: [],
quotes: {},
messageDetailProps: undefined,
messageInfoId: undefined,
showRightPanel: false,
selectedMessageIds: [],
areMoreMessagesBeingFetched: false, // top or bottom
@ -677,16 +664,13 @@ const conversationsSlice = createSlice({
name: 'conversations',
initialState: getEmptyConversationState(),
reducers: {
showMessageDetailsView(
state: ConversationsStateType,
action: PayloadAction<MessagePropsDetails>
) {
showMessageInfoView(state: ConversationsStateType, action: PayloadAction<string>) {
// force the right panel to be hidden when showing message detail view
return { ...state, messageDetailProps: action.payload, showRightPanel: false };
return { ...state, messageInfoId: action.payload, showRightPanel: false };
},
closeMessageDetailsView(state: ConversationsStateType) {
return { ...state, messageDetailProps: undefined };
closeMessageInfoView(state: ConversationsStateType) {
return { ...state, messageInfoId: undefined };
},
openRightPanel(state: ConversationsStateType) {
@ -887,7 +871,7 @@ const conversationsSlice = createSlice({
selectedMessageIds: [],
lightBox: undefined,
messageDetailProps: undefined,
messageInfoId: undefined,
quotedMessage: undefined,
nextMessageToPlay: undefined,
@ -1134,8 +1118,8 @@ export const {
resetOldBottomMessageId,
markConversationFullyRead,
// layout stuff
showMessageDetailsView,
closeMessageDetailsView,
showMessageInfoView,
closeMessageInfoView,
openRightPanel,
closeRightPanel,
addMessageIdToSelection,

@ -9,7 +9,6 @@ import {
MentionsMembersType,
MessageModelPropsWithConvoProps,
MessageModelPropsWithoutConvoProps,
MessagePropsDetails,
PropsForQuote,
QuoteLookupType,
ReduxConversationType,
@ -493,11 +492,7 @@ export const getGlobalUnreadMessageCount = createSelector(
_getGlobalUnreadCount
);
export const isMessageDetailView = (state: StateType): boolean =>
state.conversations.messageDetailProps !== undefined;
export const getMessageDetailsViewProps = (state: StateType): MessagePropsDetails | undefined =>
state.conversations.messageDetailProps;
export const getMessageInfoId = (state: StateType) => state.conversations.messageInfoId;
export const isRightPanelShowing = (state: StateType): boolean =>
state.conversations.showRightPanel;

@ -108,7 +108,7 @@ export const useMessageStatus = (
return useMessagePropsByMessageId(messageId)?.propsForMessage.status;
};
export function useMessageSender(messageId: string) {
export function useMessageSender(messageId: string | undefined) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.sender;
}
@ -116,11 +116,15 @@ export function useMessageIsDeletableForEveryone(messageId: string | undefined)
return useMessagePropsByMessageId(messageId)?.propsForMessage.isDeletableForEveryone;
}
export function useMessageServerTimestamp(messageId: string) {
export function useMessageServerTimestamp(messageId: string | undefined) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.serverTimestamp;
}
export function useMessageTimestamp(messageId: string) {
export function useMessageReceivedAt(messageId: string | undefined) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.receivedAt;
}
export function useMessageTimestamp(messageId: string | undefined) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.timestamp;
}

@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { SessionConversation } from '../../components/conversation/SessionConversation';
import { HTMLDirection } from '../../util/i18n';
import { mapDispatchToProps } from '../actions';
import { StateType } from '../reducer';
import { getHasOngoingCallWithFocusedConvo } from '../selectors/call';
@ -9,14 +10,12 @@ import {
getSelectedConversation,
getSelectedMessageIds,
getSortedMessagesOfSelectedConversation,
isMessageDetailView,
isRightPanelShowing,
} from '../selectors/conversations';
import { getSelectedConversationKey } from '../selectors/selectedConversation';
import { getStagedAttachmentsForCurrentConversation } from '../selectors/stagedAttachments';
import { getTheme } from '../selectors/theme';
import { getOurDisplayNameInProfile, getOurNumber } from '../selectors/user';
import { HTMLDirection } from '../../util/i18n';
type SmartSessionConversationOwnProps = {
htmlDirection: HTMLDirection;
@ -30,7 +29,6 @@ const mapStateToProps = (state: StateType, ownProps: SmartSessionConversationOwn
messagesProps: getSortedMessagesOfSelectedConversation(state),
ourDisplayNameInProfile: getOurDisplayNameInProfile(state),
ourNumber: getOurNumber(state),
showMessageDetails: isMessageDetailView(state),
isRightPanelShowing: isRightPanelShowing(state),
selectedMessages: getSelectedMessageIds(state),
lightBoxOptions: getLightBoxOptions(state),

Loading…
Cancel
Save