move some actions to redux in hooks

This commit is contained in:
audric 2021-07-20 16:58:51 +10:00
parent 0e4d7ec21a
commit 23e9a6d31c
14 changed files with 498 additions and 454 deletions

View File

@ -22,41 +22,28 @@ import {
isImageAttachment,
isVideo,
} from '../../../ts/types/Attachment';
import { AttachmentType } from '../../types/Attachment';
import { getIncrement } from '../../util/timer';
import { isFileDangerous } from '../../util/isFileDangerous';
import _ from 'lodash';
import { animation, contextMenu, Item, Menu } from 'react-contexify';
import { contextMenu, Menu } from 'react-contexify';
import uuid from 'uuid';
import { InView } from 'react-intersection-observer';
import { MessageMetadata } from './message/MessageMetadata';
import { PubKey } from '../../session/types';
import { MessageRegularProps } from '../../models/messageType';
import {
addSenderAsModerator,
removeSenderFromModerator,
} from '../../interactions/messageInteractions';
import { updateUserDetailsModal } from '../../state/ducks/modalDialog';
import { MessageInteraction } from '../../interactions';
import autoBind from 'auto-bind';
import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer';
import { ClickToTrustSender } from './message/ClickToTrustSender';
import { getMessageById } from '../../data/data';
import { deleteMessagesById, replyToMessage } from '../../interactions/conversationInteractions';
import { connect } from 'react-redux';
import { StateType } from '../../state/reducer';
import { getSelectedMessageIds } from '../../state/selectors/conversations';
import {
PropsForAttachment,
PropsForMessage,
showLightBox,
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../state/ducks/conversations';
import { showLightBox, toggleSelectedMessageId } from '../../state/ducks/conversations';
import { saveAttachmentToDisk } from '../../util/attachmentsUtil';
import { LightBoxOptions } from '../session/conversation/SessionConversation';
import { pushUnblockToSend } from '../../session/utils/Toast';
import { MessageContextMenu } from './MessageContextMenu';
// Same as MIN_WIDTH in ImageGrid.tsx
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
@ -194,7 +181,6 @@ class MessageInner extends React.PureComponent<Props, State> {
conversationType,
direction,
quote,
multiSelectMode,
isTrustedForAttachmentDownload,
} = this.props;
const { imageBroken } = this.state;
@ -234,16 +220,7 @@ class MessageInner extends React.PureComponent<Props, State> {
withContentBelow={withContentBelow}
bottomOverlay={!collapseMetadata}
onError={this.handleImageError}
onClickAttachment={(attachment: AttachmentTypeWithPath) => {
if (multiSelectMode) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
} else {
void onClickAttachment({
attachment,
messageId: id,
});
}
}}
onClickAttachment={this.onClickOnImageGrid}
/>
</div>
);
@ -286,17 +263,7 @@ class MessageInner extends React.PureComponent<Props, State> {
<div
role="button"
className="module-message__generic-attachment__icon"
onClick={(e: any) => {
e.stopPropagation();
const messageTimestamp = this.props.timestamp || this.props.serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: firstAttachment,
messageTimestamp,
messageSender: this.props.authorPhoneNumber,
conversationId: this.props.convoId,
});
}}
onClick={this.onClickOnGenericAttachment}
>
{extension ? (
<div className="module-message__generic-attachment__icon__extension">
@ -414,22 +381,11 @@ class MessageInner extends React.PureComponent<Props, State> {
}
public renderQuote() {
const {
conversationType,
direction,
quote,
isPublic,
convoId,
id,
multiSelectMode,
} = this.props;
const { conversationType, direction, quote, isPublic, convoId } = this.props;
if (!quote || !quote.authorPhoneNumber || !quote.messageId) {
return null;
}
const quoteId = _.toNumber(quote.messageId);
const { authorPhoneNumber, referencedMessageNotFound } = quote;
const withContentAbove = conversationType === 'group' && direction === 'incoming';
const shortenedPubkey = PubKey.shorten(quote.authorPhoneNumber);
@ -438,20 +394,7 @@ class MessageInner extends React.PureComponent<Props, State> {
return (
<Quote
onClick={(e: any) => {
e.preventDefault();
e.stopPropagation();
if (multiSelectMode && id) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
return;
}
void this.props.onQuoteClick?.({
quoteAuthor: authorPhoneNumber,
quoteId,
referencedMessageNotFound,
});
}}
onClick={this.onQuoteClick}
text={quote.text}
attachment={quote.attachment}
isIncoming={direction === 'incoming'}
@ -497,15 +440,7 @@ class MessageInner extends React.PureComponent<Props, State> {
avatarPath={authorAvatarPath}
name={userName}
size={AvatarSize.S}
onAvatarClick={() => {
window.inboxStore?.dispatch(
updateUserDetailsModal({
conversationId: authorPhoneNumber,
userName,
authorAvatarPath,
})
);
}}
onAvatarClick={this.onMessageAvatarClick}
pubkey={authorPhoneNumber}
/>
{isPublic && isAdmin && (
@ -562,142 +497,6 @@ class MessageInner extends React.PureComponent<Props, State> {
);
}
public renderContextMenu() {
const {
attachments,
authorPhoneNumber,
convoId,
direction,
status,
isDeletable,
id,
isPublic,
isOpenGroupV2,
weAreAdmin,
isAdmin,
text,
} = this.props;
const showRetry = status === 'error' && direction === 'outgoing';
const multipleAttachments = attachments && attachments.length > 1;
const onContextMenuShown = () => {
window.contextMenuShown = true;
};
const onContextMenuHidden = () => {
// This function will called before the click event
// on the message would trigger (and I was unable to
// prevent propagation in this case), so use a short timeout
setTimeout(() => {
window.contextMenuShown = false;
}, 100);
};
const onShowDetail = async () => {
const found = await getMessageById(this.props.id);
if (found) {
const messageDetailsProps = await found.getPropsForMessageDetail();
window.inboxStore?.dispatch(showMessageDetailsView(messageDetailsProps));
} else {
window.log.warn(`Message ${this.props.id} not found in db`);
}
};
const selectMessageText = window.i18n('selectMessage');
const deleteMessageText = window.i18n('deleteMessage');
return (
<Menu
id={this.ctxMenuID}
onShown={onContextMenuShown}
onHidden={onContextMenuHidden}
animation={animation.fade}
>
{!multipleAttachments && attachments && attachments[0] ? (
<Item
onClick={(e: any) => {
e.stopPropagation();
const messageTimestamp = this.props.timestamp || this.props.serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[0],
messageTimestamp,
messageSender: this.props.authorPhoneNumber,
conversationId: this.props.convoId,
});
}}
>
{window.i18n('downloadAttachment')}
</Item>
) : null}
<Item
onClick={() => {
MessageInteraction.copyBodyToClipboard(text);
}}
>
{window.i18n('copyMessage')}
</Item>
<Item onClick={this.onReplyPrivate}>{window.i18n('replyToMessage')}</Item>
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item>
{showRetry ? (
<Item
onClick={async () => {
const found = await getMessageById(id);
if (found) {
await found.retrySend();
}
}}
>
{window.i18n('resend')}
</Item>
) : null}
{isDeletable ? (
<>
<Item
onClick={() => {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
}}
>
{selectMessageText}
</Item>
<Item
onClick={() => {
void deleteMessagesById([id], convoId, false);
}}
>
{deleteMessageText}
</Item>
</>
) : null}
{weAreAdmin && isPublic ? (
<Item
onClick={() => {
MessageInteraction.banUser(authorPhoneNumber, convoId);
}}
>
{window.i18n('banUser')}
</Item>
) : null}
{weAreAdmin && isOpenGroupV2 ? (
<Item
onClick={() => {
MessageInteraction.unbanUser(authorPhoneNumber, convoId);
}}
>
{window.i18n('unbanUser')}
</Item>
) : null}
{weAreAdmin && isPublic && !isAdmin ? (
<Item onClick={this.onAddModerator}>{window.i18n('addAsModerator')}</Item>
) : null}
{weAreAdmin && isPublic && isAdmin ? (
<Item onClick={this.onRemoveFromModerator}>{window.i18n('removeFromModerators')}</Item>
) : null}
</Menu>
);
}
public getWidth(): number | undefined {
const { attachments, previews } = this.props;
@ -824,26 +623,7 @@ class MessageInner extends React.PureComponent<Props, State> {
expiring ? 'module-message--expired' : null
)}
role="button"
onClick={event => {
const selection = window.getSelection();
// Text is being selected
if (selection && selection.type === 'Range') {
return;
}
// User clicked on message body
const target = event.target as HTMLDivElement;
if (
(!multiSelectMode && target.className === 'text-selectable') ||
window.contextMenuShown
) {
return;
}
if (id) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
}
}}
onClick={this.onClickOnMessageOuterContainer}
>
{this.renderError(isIncoming)}
@ -856,19 +636,7 @@ class MessageInner extends React.PureComponent<Props, State> {
width: isShowingImage ? width : undefined,
}}
role="button"
onClick={event => {
const selection = window.getSelection();
// Text is being selected
if (selection && selection.type === 'Range') {
return;
}
// User clicked on message body
const target = event.target as HTMLDivElement;
if (target.className === 'text-selectable' || window.contextMenuShown) {
return;
}
}}
onClick={this.onClickOnMessageInnerContainer}
>
{this.renderAuthor()}
{this.renderQuote()}
@ -876,19 +644,40 @@ class MessageInner extends React.PureComponent<Props, State> {
{this.renderPreview()}
{this.renderText()}
<MessageMetadata
{..._.omit(
this.props,
'onDeleteMessage',
'onReply',
'onClickAttachment',
'onDownload',
'onQuoteClick'
)}
direction={this.props.direction}
id={this.props.id}
timestamp={this.props.timestamp}
collapseMetadata={this.props.collapseMetadata}
expirationLength={this.props.expirationLength}
isAdmin={this.props.isAdmin}
serverTimestamp={this.props.serverTimestamp}
isPublic={this.props.isPublic}
status={this.props.status}
expirationTimestamp={this.props.expirationTimestamp}
text={this.props.text}
isShowingImage={this.isShowingImage()}
/>
</div>
{this.renderError(!isIncoming)}
{this.renderContextMenu()}
<MessageContextMenu
authorPhoneNumber={this.props.authorPhoneNumber}
convoId={this.props.convoId}
contextMenuId={this.ctxMenuID}
direction={this.props.direction}
isBlocked={this.props.isBlocked}
isDeletable={this.props.isDeletable}
messageId={this.props.id}
text={this.props.text}
timestamp={this.props.timestamp}
serverTimestamp={this.props.serverTimestamp}
attachments={this.props.attachments}
isAdmin={this.props.isAdmin}
isOpenGroupV2={this.props.isOpenGroupV2}
isPublic={this.props.isPublic}
status={this.props.status}
weAreAdmin={this.props.weAreAdmin}
/>
</div>
</InView>
);
@ -910,6 +699,28 @@ class MessageInner extends React.PureComponent<Props, State> {
}
}
private onQuoteClick(e: any) {
const { quote, multiSelectMode, id } = this.props;
if (!quote) {
window.log.warn('onQuoteClick: quote not valid');
return;
}
const quoteId = _.toNumber(quote.messageId);
const { authorPhoneNumber, referencedMessageNotFound } = quote;
e.preventDefault();
e.stopPropagation();
if (multiSelectMode && id) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
return;
}
void this.props.onQuoteClick?.({
quoteAuthor: authorPhoneNumber,
quoteId,
referencedMessageNotFound,
});
}
private renderAuthor() {
const {
authorName,
@ -944,21 +755,83 @@ class MessageInner extends React.PureComponent<Props, State> {
);
}
private onReplyPrivate(e: any) {
if (this.props.isBlocked) {
pushUnblockToSend();
private onMessageAvatarClick() {
const userName =
this.props.authorName || this.props.authorProfileName || this.props.authorPhoneNumber;
window.inboxStore?.dispatch(
updateUserDetailsModal({
conversationId: this.props.authorPhoneNumber,
userName,
authorAvatarPath: this.props.authorAvatarPath,
})
);
}
private onClickOnImageGrid(attachment: AttachmentTypeWithPath) {
const { multiSelectMode, id } = this.props;
if (multiSelectMode) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
} else {
void onClickAttachment({
attachment,
messageId: id,
});
}
}
private onClickOnGenericAttachment(e: any) {
const { timestamp, serverTimestamp, authorPhoneNumber, attachments, convoId } = this.props;
e.stopPropagation();
if (!attachments?.length) {
return;
}
void replyToMessage(this.props.id);
const firstAttachment = attachments[0];
const messageTimestamp = timestamp || serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: firstAttachment,
messageTimestamp,
messageSender: authorPhoneNumber,
conversationId: convoId,
});
}
private async onAddModerator() {
await addSenderAsModerator(this.props.authorPhoneNumber, this.props.convoId);
private onClickOnMessageOuterContainer(event: any) {
const { multiSelectMode, id } = this.props;
const selection = window.getSelection();
// Text is being selected
if (selection && selection.type === 'Range') {
return;
}
// User clicked on message body
const target = event.target as HTMLDivElement;
if ((!multiSelectMode && target.className === 'text-selectable') || window.contextMenuShown) {
return;
}
if (id) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
}
}
private async onRemoveFromModerator() {
await removeSenderFromModerator(this.props.authorPhoneNumber, this.props.convoId);
private onClickOnMessageInnerContainer(event: any) {
const selection = window.getSelection();
// Text is being selected
if (selection && selection.type === 'Range') {
return;
}
// User clicked on message body
const target = event.target as HTMLDivElement;
if (target.className === 'text-selectable' || window.contextMenuShown) {
return;
}
}
}

View File

@ -0,0 +1,179 @@
import React, { useCallback } from 'react';
import { AttachmentTypeWithPath } from '../../types/Attachment';
import _ from 'lodash';
import { animation, Item, Menu } from 'react-contexify';
import { MessageInteraction } from '../../interactions';
import { getMessageById } from '../../data/data';
import { deleteMessagesById, replyToMessage } from '../../interactions/conversationInteractions';
import { showMessageDetailsView, toggleSelectedMessageId } from '../../state/ducks/conversations';
import { saveAttachmentToDisk } from '../../util/attachmentsUtil';
import {
addSenderAsModerator,
removeSenderFromModerator,
} from '../../interactions/messageInteractions';
import { MessageDeliveryStatus, MessageModelType } from '../../models/messageType';
import { pushUnblockToSend } from '../../session/utils/Toast';
export type PropsForMessageContextMenu = {
messageId: string;
authorPhoneNumber: string;
direction: MessageModelType;
timestamp: number;
serverTimestamp?: number;
convoId: string;
isPublic?: boolean;
isBlocked: boolean;
attachments?: Array<AttachmentTypeWithPath>;
status?: MessageDeliveryStatus | null;
isOpenGroupV2?: boolean;
isDeletable: boolean;
text: string | null;
isAdmin?: boolean;
weAreAdmin?: boolean;
contextMenuId: string;
};
export const MessageContextMenu = (props: PropsForMessageContextMenu) => {
const {
attachments,
authorPhoneNumber,
convoId,
direction,
status,
isDeletable,
messageId,
contextMenuId,
isPublic,
isOpenGroupV2,
weAreAdmin,
isAdmin,
text,
serverTimestamp,
timestamp,
isBlocked,
} = props;
const showRetry = status === 'error' && direction === 'outgoing';
const multipleAttachments = attachments && attachments.length > 1;
const onContextMenuShown = useCallback(() => {
window.contextMenuShown = true;
}, []);
const onContextMenuHidden = useCallback(() => {
// This function will called before the click event
// on the message would trigger (and I was unable to
// prevent propagation in this case), so use a short timeout
setTimeout(() => {
window.contextMenuShown = false;
}, 100);
}, []);
const onShowDetail = async () => {
const found = await getMessageById(messageId);
if (found) {
const messageDetailsProps = await found.getPropsForMessageDetail();
window.inboxStore?.dispatch(showMessageDetailsView(messageDetailsProps));
} else {
window.log.warn(`Message ${messageId} not found in db`);
}
};
const selectMessageText = window.i18n('selectMessage');
const deleteMessageText = window.i18n('deleteMessage');
const addModerator = useCallback(() => {
void addSenderAsModerator(authorPhoneNumber, convoId);
}, [authorPhoneNumber, convoId]);
const removeModerator = useCallback(() => {
void removeSenderFromModerator(authorPhoneNumber, convoId);
}, [authorPhoneNumber, convoId]);
const onReply = useCallback(() => {
if (isBlocked) {
pushUnblockToSend();
return;
}
void replyToMessage(messageId);
}, [isBlocked, messageId]);
const saveAttachment = useCallback(
(e: any) => {
e.stopPropagation();
if (!attachments?.length) {
return;
}
const messageTimestamp = timestamp || serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[0],
messageTimestamp,
messageSender: authorPhoneNumber,
conversationId: convoId,
});
},
[convoId, authorPhoneNumber, timestamp, serverTimestamp, convoId, attachments]
);
const copyText = useCallback(() => {
MessageInteraction.copyBodyToClipboard(text);
}, [text]);
const onRetry = useCallback(async () => {
const found = await getMessageById(messageId);
if (found) {
await found.retrySend();
}
}, [messageId]);
const onBan = useCallback(() => {
MessageInteraction.banUser(authorPhoneNumber, convoId);
}, [authorPhoneNumber, convoId]);
const onUnban = useCallback(() => {
MessageInteraction.unbanUser(authorPhoneNumber, convoId);
}, [authorPhoneNumber, convoId]);
const onSelect = useCallback(() => {
window.inboxStore?.dispatch(toggleSelectedMessageId(messageId));
}, [messageId]);
const onDelete = useCallback(() => {
void deleteMessagesById([messageId], convoId, false);
}, [convoId, messageId]);
return (
<Menu
id={contextMenuId}
onShown={onContextMenuShown}
onHidden={onContextMenuHidden}
animation={animation.fade}
>
{!multipleAttachments && attachments && attachments[0] ? (
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
) : null}
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
<Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item>
{showRetry ? <Item onClick={onRetry}>{window.i18n('resend')}</Item> : null}
{isDeletable ? (
<>
<Item onClick={onSelect}>{selectMessageText}</Item>
<Item onClick={onDelete}>{deleteMessageText}</Item>
</>
) : null}
{weAreAdmin && isPublic ? <Item onClick={onBan}>{window.i18n('banUser')}</Item> : null}
{weAreAdmin && isOpenGroupV2 ? (
<Item onClick={onUnban}>{window.i18n('unbanUser')}</Item>
) : null}
{weAreAdmin && isPublic && !isAdmin ? (
<Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item>
) : null}
{weAreAdmin && isPublic && isAdmin ? (
<Item onClick={removeModerator}>{window.i18n('removeFromModerators')}</Item>
) : null}
</Menu>
);
};

View File

@ -299,7 +299,7 @@ export const QuoteReferenceWarning = (props: any) => {
};
export const Quote = (props: QuotePropsWithListener) => {
const [imageBroken, setImageBroken] = useState(false);
const [_imageBroken, setImageBroken] = useState(false);
const handleImageErrorBound = null;
@ -316,33 +316,31 @@ export const Quote = (props: QuotePropsWithListener) => {
}
return (
<>
<div
className={classNames(
'module-quote-container',
withContentAbove ? 'module-quote-container--with-content-above' : null
)}
>
<div
onClick={onClick}
role="button"
className={classNames(
'module-quote-container',
withContentAbove ? 'module-quote-container--with-content-above' : null
'module-quote',
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
!onClick ? 'module-quote--no-click' : null,
withContentAbove ? 'module-quote--with-content-above' : null,
referencedMessageNotFound ? 'module-quote--with-reference-warning' : null
)}
>
<div
onClick={onClick}
role="button"
className={classNames(
'module-quote',
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
!onClick ? 'module-quote--no-click' : null,
withContentAbove ? 'module-quote--with-content-above' : null,
referencedMessageNotFound ? 'module-quote--with-reference-warning' : null
)}
>
<div className="module-quote__primary">
<QuoteAuthor {...props} />
<QuoteGenericFile {...props} />
<QuoteText {...props} />
</div>
<QuoteIconContainer {...props} handleImageErrorBound={handleImageErrorBound} />
<div className="module-quote__primary">
<QuoteAuthor {...props} />
<QuoteGenericFile {...props} />
<QuoteText {...props} />
</div>
<QuoteReferenceWarning {...props} />
<QuoteIconContainer {...props} handleImageErrorBound={handleImageErrorBound} />
</div>
</>
<QuoteReferenceWarning {...props} />
</div>
);
};

View File

@ -8,9 +8,7 @@ import styled, { DefaultTheme, useTheme } from 'styled-components';
import { MessageDeliveryStatus, MessageModelType } from '../../../models/messageType';
type Props = {
disableMenu?: boolean;
isAdmin?: boolean;
isDeletable: boolean;
text?: string | null;
id: string;
collapseMetadata?: boolean;

View File

@ -106,6 +106,12 @@ export class SessionInboxView extends React.Component<any, State> {
messageDetailProps: undefined,
selectedMessageIds: [],
selectedConversation: undefined,
areMoreMessagesBeingFetched: false,
showScrollButton: false,
animateQuotedMessageId: undefined,
lightBox: undefined,
nextMessageToPlay: undefined,
quotedMessage: undefined,
},
user: {
ourNumber: UserUtils.getOurPubKeyStrFromCache(),

View File

@ -1,11 +1,12 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import styled, { ThemeContext } from 'styled-components';
import { getShowScrollButton } from '../../state/selectors/conversations';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
type Props = {
onClick?: () => any;
show?: boolean;
};
const SessionScrollButtonDiv = styled.div`
@ -18,9 +19,11 @@ const SessionScrollButtonDiv = styled.div`
export const SessionScrollButton = (props: Props) => {
const themeContext = useContext(ThemeContext);
const show = useSelector(getShowScrollButton);
return (
<>
{props.show && (
{show && (
<SessionScrollButtonDiv theme={themeContext}>
<SessionIconButton
iconType={SessionIconType.Chevron}

View File

@ -45,9 +45,14 @@ import {
getItemById,
hasLinkPreviewPopupBeenDisplayed,
} from '../../../data/data';
import { getQuotedMessage } from '../../../state/selectors/conversations';
import {
getQuotedMessage,
getSelectedConversation,
getSelectedConversationKey,
} from '../../../state/selectors/conversations';
import { connect } from 'react-redux';
import { StateType } from '../../../state/reducer';
import { getTheme } from '../../../state/selectors/theme';
export interface ReplyingToMessageProps {
convoId: string;
@ -76,19 +81,13 @@ interface Props {
onLoadVoiceNoteView: any;
onExitVoiceNoteView: any;
isBlocked: boolean;
isPrivate: boolean;
isKickedFromGroup: boolean;
left: boolean;
selectedConversationKey: string;
selectedConversation: ReduxConversationType | undefined;
isPublic: boolean;
quotedMessageProps?: ReplyingToMessageProps;
stagedAttachments: Array<StagedAttachmentType>;
clearAttachments: () => any;
removeAttachment: (toRemove: AttachmentType) => void;
onChoseAttachments: (newAttachments: Array<File>) => void;
theme: DefaultTheme;
}
interface State {
@ -304,13 +303,15 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
sendVoiceMessage={this.sendVoiceMessage}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
theme={this.props.theme}
/>
);
}
private isTypingEnabled(): boolean {
const { isBlocked, isKickedFromGroup, left, isPrivate } = this.props;
if (!this.props.selectedConversation) {
return false;
}
const { isBlocked, isKickedFromGroup, left } = this.props.selectedConversation;
return !(isBlocked || isKickedFromGroup || left);
}
@ -326,7 +327,6 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
iconType={SessionIconType.CirclePlus}
iconSize={SessionIconSize.Large}
onClick={this.onChooseAttachment}
theme={this.props.theme}
/>
)}
@ -344,7 +344,6 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
iconType={SessionIconType.Microphone}
iconSize={SessionIconSize.Huge}
onClick={this.onLoadVoiceNoteView}
theme={this.props.theme}
/>
)}
@ -364,7 +363,6 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
iconType={SessionIconType.Emoji}
iconSize={SessionIconSize.Large}
onClick={this.toggleEmojiPanel}
theme={this.props.theme}
/>
)}
<div className="send-message-button">
@ -373,7 +371,6 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
iconSize={SessionIconSize.Large}
iconRotation={90}
onClick={this.onSendMessage}
theme={this.props.theme}
/>
</div>
@ -391,7 +388,12 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
private renderTextArea() {
const { i18n } = window;
const { message } = this.state;
const { isKickedFromGroup, left, isPrivate, isBlocked, theme } = this.props;
if (!this.props.selectedConversation) {
return null;
}
const { isKickedFromGroup, left, isPrivate, isBlocked } = this.props.selectedConversation;
const messagePlaceHolder = isKickedFromGroup
? i18n('youGotKickedFromGroup')
: left
@ -471,11 +473,15 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
if (!query) {
overridenQuery = '';
}
if (this.props.isPublic) {
if (!this.props.selectedConversation) {
return;
}
if (this.props.selectedConversation.isPublic) {
this.fetchUsersForOpenGroup(overridenQuery, callback);
return;
}
if (!this.props.isPrivate) {
if (!this.props.selectedConversation.isPrivate) {
this.fetchUsersForClosedGroup(overridenQuery, callback);
return;
}
@ -799,13 +805,17 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
const messagePlaintext = cleanMentions(this.parseEmojis(this.state.message));
const { isBlocked, isPrivate, left, isKickedFromGroup } = this.props;
const { selectedConversation } = this.props;
if (isBlocked && isPrivate) {
if (!selectedConversation) {
return;
}
if (selectedConversation.isBlocked && selectedConversation.isPrivate) {
ToastUtils.pushUnblockToSend();
return;
}
if (isBlocked && !isPrivate) {
if (selectedConversation.isBlocked && !selectedConversation.isPrivate) {
ToastUtils.pushUnblockToSendGroup();
return;
}
@ -816,11 +826,11 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
return;
}
if (!isPrivate && left) {
if (!selectedConversation.isPrivate && selectedConversation.left) {
ToastUtils.pushYouLeftTheGroup();
return;
}
if (!isPrivate && isKickedFromGroup) {
if (!selectedConversation.isPrivate && selectedConversation.isKickedFromGroup) {
ToastUtils.pushYouLeftTheGroup();
return;
}
@ -992,6 +1002,9 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
const mapStateToProps = (state: StateType) => {
return {
quotedMessageProps: getQuotedMessage(state),
selectedConversation: getSelectedConversation(state),
selectedConversationKey: getSelectedConversationKey(state),
theme: getTheme(state),
};
};

View File

@ -235,13 +235,6 @@ export class SessionConversation extends React.Component<Props, State> {
</div>
<SessionCompositionBox
isBlocked={selectedConversation.isBlocked}
left={selectedConversation.left}
isKickedFromGroup={selectedConversation.isKickedFromGroup}
isPrivate={selectedConversation.isPrivate}
isPublic={selectedConversation.isPublic}
selectedConversationKey={selectedConversationKey}
selectedConversation={selectedConversation}
sendMessage={sendMessageFn}
stagedAttachments={stagedAttachments}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
@ -249,7 +242,6 @@ export class SessionConversation extends React.Component<Props, State> {
clearAttachments={this.clearAttachments}
removeAttachment={this.removeAttachment}
onChoseAttachments={this.onChoseAttachments}
theme={this.props.theme}
/>
</div>
<div
@ -305,15 +297,8 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~ KEYBOARD NAVIGATION ~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private onKeyDown(event: any) {
const messageContainer = this.messageContainerRef.current;
if (!messageContainer) {
return;
}
const selectionMode = !!this.props.selectedMessages.length;
const recordingMode = this.state.showRecordingView;
const pageHeight = messageContainer.clientHeight;
const arrowScrollPx = 50;
const pageScrollPx = pageHeight * 0.8;
if (event.key === 'Escape') {
// EXIT MEDIA VIEW
if (recordingMode) {
@ -328,19 +313,6 @@ export class SessionConversation extends React.Component<Props, State> {
window.inboxStore?.dispatch(resetSelectedMessageIds());
}
break;
// Scrolling
case 'ArrowUp':
messageContainer.scrollBy(0, -arrowScrollPx);
break;
case 'ArrowDown':
messageContainer.scrollBy(0, arrowScrollPx);
break;
case 'PageUp':
messageContainer.scrollBy(0, -pageScrollPx);
break;
case 'PageDown':
messageContainer.scrollBy(0, pageScrollPx);
break;
default:
}
}

View File

@ -14,7 +14,10 @@ import {
PropsForExpirationTimer,
PropsForGroupInvitation,
PropsForGroupUpdate,
quotedMessageToAnimate,
ReduxConversationType,
setNextMessageToPlay,
showScrollToBottomButton,
SortedMessageModelProps,
} from '../../../state/ducks/conversations';
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
@ -34,18 +37,16 @@ import { DataExtractionNotification } from '../../conversation/DataExtractionNot
import { StateType } from '../../../state/reducer';
import { connect, useSelector } from 'react-redux';
import {
areMoreMessagesBeingFetched,
getMessagesOfSelectedConversation,
getNextMessageToPlayIndex,
getQuotedMessageToAnimate,
getSelectedConversation,
getSelectedConversationKey,
getShowScrollButton,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
interface State {
showScrollButton: boolean;
animateQuotedMessageId?: string;
nextMessageToPlay: number | undefined;
}
export type SessionMessageListProps = {
messageContainerRef: React.RefObject<any>;
};
@ -55,6 +56,8 @@ type Props = SessionMessageListProps & {
messagesProps: Array<SortedMessageModelProps>;
conversation?: ReduxConversationType;
showScrollButton: boolean;
animateQuotedMessageId: string | undefined;
};
const UnreadIndicator = (props: { messageId: string; show: boolean }) => (
@ -124,26 +127,27 @@ const GenericMessageItem = (props: {
messageProps: SortedMessageModelProps;
playableMessageIndex?: number;
showUnreadIndicator: boolean;
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;
playNextMessage?: (value: number) => void;
}) => {
const multiSelectMode = useSelector(isMessageSelectionMode);
// const selectedConversation = useSelector(getSelectedConversationKey) as string;
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const nextMessageToPlay = useSelector(getNextMessageToPlayIndex);
const messageId = props.messageId;
console.warn('FIXME audric');
// const onQuoteClick = props.messageProps.propsForMessage.quote
// ? this.scrollToQuoteMessage
// : async () => {};
const onQuoteClick = props.messageProps.propsForMessage.quote
? props.scrollToQuoteMessage
: undefined;
const regularProps: MessageRegularProps = {
...props.messageProps.propsForMessage,
// firstMessageOfSeries,
firstMessageOfSeries: props.messageProps.firstMessageOfSeries,
multiSelectMode,
// isQuotedMessageToAnimate: messageId === this.state.animateQuotedMessageId,
// nextMessageToPlay: this.state.nextMessageToPlay,
// playNextMessage: this.playNextMessage,
// onQuoteClick,
isQuotedMessageToAnimate: messageId === quotedMessageToAnimate,
nextMessageToPlay,
playNextMessage: props.playNextMessage,
onQuoteClick,
};
return (
@ -152,7 +156,6 @@ const GenericMessageItem = (props: {
{...regularProps}
playableMessageIndex={props.playableMessageIndex}
multiSelectMode={multiSelectMode}
// onQuoteClick={onQuoteClick}
key={messageId}
/>
<UnreadIndicator messageId={props.messageId} show={props.showUnreadIndicator} />
@ -160,8 +163,13 @@ const GenericMessageItem = (props: {
);
};
const MessageList = ({ hasNextPage: boolean, isNextPageLoading, list, loadNextPage }) => {
const MessageList = (props: {
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;
playNextMessage?: (value: number) => void;
}) => {
const messagesProps = useSelector(getMessagesOfSelectedConversation);
const isFetchingMore = useSelector(areMoreMessagesBeingFetched);
let playableMessageIndex = 0;
return (
@ -177,7 +185,6 @@ const MessageList = ({ hasNextPage: boolean, isNextPageLoading, list, loadNextPa
// AND we are not scrolled all the way to the bottom
// THEN, show the unread banner for the current message
const showUnreadIndicator = Boolean(messageProps.firstUnread);
console.warn('&& this.getScrollOffsetBottomPx() !== 0');
if (groupNotificationProps) {
return (
@ -238,6 +245,8 @@ const MessageList = ({ hasNextPage: boolean, isNextPageLoading, list, loadNextPa
messageId={messageProps.propsForMessage.id}
messageProps={messageProps}
showUnreadIndicator={showUnreadIndicator}
scrollToQuoteMessage={props.scrollToQuoteMessage}
playNextMessage={props.playNextMessage}
/>
);
})}
@ -245,21 +254,18 @@ const MessageList = ({ hasNextPage: boolean, isNextPageLoading, list, loadNextPa
);
};
class SessionMessagesListInner extends React.Component<Props, State> {
class SessionMessagesListInner extends React.Component<Props> {
private scrollOffsetBottomPx: number = Number.MAX_VALUE;
private ignoreScrollEvents: boolean;
private timeoutResetQuotedScroll: NodeJS.Timeout | null = null;
private debouncedHandleScroll: any;
public constructor(props: Props) {
super(props);
this.state = {
showScrollButton: false,
nextMessageToPlay: undefined,
};
autoBind(this);
this.ignoreScrollEvents = true;
this.debouncedHandleScroll = _.throttle(this.handleScroll, 500);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -277,7 +283,7 @@ class SessionMessagesListInner extends React.Component<Props, State> {
}
}
public componentDidUpdate(prevProps: Props, _prevState: State) {
public componentDidUpdate(prevProps: Props) {
const isSameConvo = prevProps.conversationKey === this.props.conversationKey;
const messageLengthChanged = prevProps.messagesProps.length !== this.props.messagesProps.length;
if (
@ -288,13 +294,7 @@ class SessionMessagesListInner extends React.Component<Props, State> {
this.scrollOffsetBottomPx = Number.MAX_VALUE;
this.ignoreScrollEvents = true;
this.setupTimeoutResetQuotedHighlightedMessage(true);
this.setState(
{
showScrollButton: false,
animateQuotedMessageId: undefined,
},
this.scrollToUnread
);
this.scrollToUnread();
} else {
// if we got new message for this convo, and we are scrolled to bottom
if (isSameConvo && messageLengthChanged) {
@ -318,7 +318,6 @@ class SessionMessagesListInner extends React.Component<Props, State> {
public render() {
const { conversationKey, conversation } = this.props;
const { showScrollButton } = this.state;
if (!conversationKey || !conversation) {
return null;
@ -334,7 +333,7 @@ class SessionMessagesListInner extends React.Component<Props, State> {
return (
<div
className="messages-container"
onScroll={this.handleScroll}
onScroll={this.debouncedHandleScroll}
ref={this.props.messageContainerRef}
>
<TypingBubble
@ -345,13 +344,12 @@ class SessionMessagesListInner extends React.Component<Props, State> {
key="typing-bubble"
/>
<MessageList />
<SessionScrollButton
show={showScrollButton}
onClick={this.scrollToBottom}
key="scroll-down-button"
<MessageList
scrollToQuoteMessage={this.scrollToQuoteMessage}
playNextMessage={this.playNextMessage}
/>
<SessionScrollButton onClick={this.scrollToBottom} key="scroll-down-button" />
</div>
);
}
@ -385,7 +383,7 @@ class SessionMessagesListInner extends React.Component<Props, State> {
* Sets the targeted index for the next
* @param index index of message that just completed
*/
private readonly playNextMessage = (index: any) => {
private playNextMessage(index: any) {
const { messagesProps } = this.props;
let nextIndex: number | undefined = index - 1;
@ -393,9 +391,7 @@ class SessionMessagesListInner extends React.Component<Props, State> {
const latestMessagePlayed = index <= 0 || messagesProps.length < index - 1;
if (latestMessagePlayed) {
nextIndex = undefined;
this.setState({
nextMessageToPlay: nextIndex,
});
window.inboxStore?.dispatch(setNextMessageToPlay(nextIndex));
return;
}
@ -407,10 +403,8 @@ class SessionMessagesListInner extends React.Component<Props, State> {
nextIndex = undefined;
}
this.setState({
nextMessageToPlay: nextIndex,
});
};
window.inboxStore?.dispatch(setNextMessageToPlay(nextIndex));
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~ SCROLLING METHODS ~~~~~~~~~~~~~
@ -438,12 +432,12 @@ class SessionMessagesListInner extends React.Component<Props, State> {
const scrollOffsetPc = this.scrollOffsetBottomPx / clientHeight;
// Scroll button appears if you're more than 75% scrolled up
if (scrollOffsetPc > scrollButtonViewShowLimit && !this.state.showScrollButton) {
this.setState({ showScrollButton: true });
if (scrollOffsetPc > scrollButtonViewShowLimit && !this.props.showScrollButton) {
window.inboxStore?.dispatch(showScrollToBottomButton(true));
}
// Scroll button disappears if you're more less than 40% scrolled up
if (scrollOffsetPc < scrollButtonViewHideLimit && this.state.showScrollButton) {
this.setState({ showScrollButton: false });
if (scrollOffsetPc < scrollButtonViewHideLimit && this.props.showScrollButton) {
window.inboxStore?.dispatch(showScrollToBottomButton(false));
}
// Scrolled to bottom
@ -513,26 +507,24 @@ class SessionMessagesListInner extends React.Component<Props, State> {
if (clearOnly) {
return;
}
if (this.state.animateQuotedMessageId !== undefined) {
if (this.props.animateQuotedMessageId !== undefined) {
this.timeoutResetQuotedScroll = global.setTimeout(() => {
this.setState({ animateQuotedMessageId: undefined });
window.inboxStore?.dispatch(quotedMessageToAnimate(undefined));
}, 3000);
}
}
private scrollToMessage(messageId: string, smooth: boolean = false) {
const topUnreadMessage = document.getElementById(messageId);
topUnreadMessage?.scrollIntoView({
const messageElementDom = document.getElementById(messageId);
messageElementDom?.scrollIntoView({
behavior: smooth ? 'smooth' : 'auto',
block: 'center',
});
// we consider that a `smooth` set to true, means it's a quoted message, so highlight this message on the UI
if (smooth) {
this.setState(
{ animateQuotedMessageId: messageId },
this.setupTimeoutResetQuotedHighlightedMessage
);
window.inboxStore?.dispatch(quotedMessageToAnimate(messageId));
this.setupTimeoutResetQuotedHighlightedMessage;
}
const messageContainer = this.props.messageContainerRef.current;
@ -633,6 +625,8 @@ const mapStateToProps = (state: StateType) => {
conversationKey: getSelectedConversationKey(state),
conversation: getSelectedConversation(state),
messagesProps: getMessagesOfSelectedConversation(state),
showScrollButton: getShowScrollButton(state),
animateQuotedMessageId: getQuotedMessageToAnimate(state),
};
};

View File

@ -6,7 +6,6 @@ import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { Constants } from '../../../session';
import { ToastUtils } from '../../../session/utils';
import { DefaultTheme, withTheme } from 'styled-components';
import autoBind from 'auto-bind';
import MicRecorder from 'mic-recorder-to-mp3';
@ -14,7 +13,6 @@ interface Props {
onExitVoiceNoteView: any;
onLoadVoiceNoteView: any;
sendVoiceMessage: any;
theme: DefaultTheme;
}
interface State {
@ -36,9 +34,6 @@ function getTimestamp(asInt = false) {
}
class SessionRecordingInner extends React.Component<Props, State> {
private readonly visualisationRef: React.RefObject<HTMLDivElement>;
private readonly visualisationCanvas: React.RefObject<HTMLCanvasElement>;
private readonly playbackCanvas: React.RefObject<HTMLCanvasElement>;
private recorder: any;
private audioBlobMp3?: Blob;
private audioElement?: HTMLAudioElement | null;
@ -49,9 +44,6 @@ class SessionRecordingInner extends React.Component<Props, State> {
autoBind(this);
// Refs
this.visualisationRef = React.createRef();
this.visualisationCanvas = React.createRef();
this.playbackCanvas = React.createRef();
const now = getTimestamp();
const updateTimerInterval = global.setInterval(this.timerUpdate, 500);
@ -127,7 +119,6 @@ class SessionRecordingInner extends React.Component<Props, State> {
iconSize={SessionIconSize.Medium}
iconColor={Constants.UI.COLORS.DANGER_ALT}
onClick={actionPauseFn}
theme={this.props.theme}
/>
)}
{actionPauseAudio && (
@ -135,7 +126,6 @@ class SessionRecordingInner extends React.Component<Props, State> {
iconType={SessionIconType.Pause}
iconSize={SessionIconSize.Medium}
onClick={actionPauseFn}
theme={this.props.theme}
/>
)}
{actionPlayAudio && (
@ -143,7 +133,6 @@ class SessionRecordingInner extends React.Component<Props, State> {
iconType={SessionIconType.Play}
iconSize={SessionIconSize.Medium}
onClick={this.playAudio}
theme={this.props.theme}
/>
)}
@ -151,16 +140,10 @@ class SessionRecordingInner extends React.Component<Props, State> {
<SessionIconButton
iconType={SessionIconType.Microphone}
iconSize={SessionIconSize.Huge}
theme={this.props.theme}
/>
)}
</div>
<div className="session-recording--visualisation" ref={this.visualisationRef}>
{!isRecording && <canvas ref={this.playbackCanvas} />}
{isRecording && <canvas ref={this.visualisationCanvas} />}
</div>
<div className={classNames('session-recording--timer', !isRecording && 'playback-timer')}>
{displayTimeString}
{isRecording && <div className="session-recording--timer-light" />}
@ -173,7 +156,6 @@ class SessionRecordingInner extends React.Component<Props, State> {
iconSize={SessionIconSize.Large}
iconRotation={90}
onClick={this.onSendVoiceMessage}
theme={this.props.theme}
/>
</div>
)}
@ -343,4 +325,4 @@ class SessionRecordingInner extends React.Component<Props, State> {
}
}
export const SessionRecording = withTheme(SessionRecordingInner);
export const SessionRecording = SessionRecordingInner;

View File

@ -49,13 +49,12 @@ import {
uploadLinkPreviewsV2,
uploadQuoteThumbnailsV2,
} from '../session/utils/AttachmentsV2';
import { acceptOpenGroupInvitation } from '../interactions/messageInteractions';
import { OpenGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { getV2OpenGroupRoom } from '../data/opengroups';
import { getMessageController } from '../session/messages';
import { isUsFromCache } from '../session/utils/User';
import { perfEnd, perfStart } from '../session/utils/Performance';
import { AttachmentType, AttachmentTypeWithPath } from '../types/Attachment';
import { AttachmentTypeWithPath } from '../types/Attachment';
export class MessageModel extends Backbone.Model<MessageAttributes> {
constructor(attributes: MessageAttributesOptionals) {
@ -80,6 +79,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
void this.setToExpire();
autoBind(this);
this.dispatchMessageUpdate = _.debounce(this.dispatchMessageUpdate, 500);
window.contextMenuShown = false;
this.getProps();
@ -99,12 +100,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return messageProps;
}
// Keep props ready
public generateProps(): MessageModelProps {
const messageProps = this.getProps();
window.inboxStore?.dispatch(conversationActions.messageChanged(messageProps));
return messageProps;
private dispatchMessageUpdate() {
window.inboxStore?.dispatch(conversationActions.messageChanged(this.getProps()));
}
public idForLogging() {
@ -1082,7 +1079,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
perfStart(`messageCommit-${this.attributes.id}`);
const id = await saveMessage(this.attributes);
this.generateProps();
this.dispatchMessageUpdate();
perfEnd(`messageCommit-${this.attributes.id}`, 'messageCommit');
return id;

View File

@ -246,7 +246,7 @@ export interface MessageRegularProps {
isUnread: boolean;
isQuotedMessageToAnimate?: boolean;
isTrustedForAttachmentDownload: boolean;
onQuoteClick: (options: QuoteClickOptions) => Promise<void>;
onQuoteClick?: (options: QuoteClickOptions) => Promise<void>;
playableMessageIndex?: number;
nextMessageToPlay?: number;

View File

@ -233,12 +233,16 @@ export type ConversationsStateType = {
conversationLookup: ConversationLookupType;
selectedConversation?: string;
messages: Array<SortedMessageModelProps>;
messageDetailProps: MessagePropsDetails | undefined;
messageDetailProps?: MessagePropsDetails;
showRightPanel: boolean;
selectedMessageIds: Array<string>;
lightBox?: LightBoxOptions;
quotedMessage?: ReplyingToMessageProps;
areMoreMessagesBeingFetched: boolean;
showScrollButton: boolean;
animateQuotedMessageId?: string;
nextMessageToPlay?: number;
};
async function getMessages(
@ -394,6 +398,7 @@ function getEmptyState(): ConversationsStateType {
showRightPanel: false,
selectedMessageIds: [],
areMoreMessagesBeingFetched: false,
showScrollButton: false,
};
}
@ -708,14 +713,21 @@ const conversationsSlice = createSlice({
if (state.selectedConversation === action.payload.id) {
return state;
}
state.showRightPanel = false;
state.messageDetailProps = undefined;
state.selectedMessageIds = [];
state.selectedConversation = action.payload.id;
state.messages = [];
state.quotedMessage = undefined;
state.lightBox = undefined;
return state;
return {
conversationLookup: state.conversationLookup,
selectedConversation: action.payload.id,
areMoreMessagesBeingFetched: false,
messages: [],
showRightPanel: false,
selectedMessageIds: [],
lightBox: undefined,
messageDetailProps: undefined,
quotedMessage: undefined,
nextMessageToPlay: undefined,
showScrollButton: false,
animateQuotedMessageId: undefined,
};
},
showLightBox(
state: ConversationsStateType,
@ -724,6 +736,10 @@ const conversationsSlice = createSlice({
state.lightBox = action.payload;
return state;
},
showScrollToBottomButton(state: ConversationsStateType, action: PayloadAction<boolean>) {
state.showScrollButton = action.payload;
return state;
},
quoteMessage(
state: ConversationsStateType,
action: PayloadAction<ReplyingToMessageProps | undefined>
@ -731,6 +747,17 @@ const conversationsSlice = createSlice({
state.quotedMessage = action.payload;
return state;
},
quotedMessageToAnimate(
state: ConversationsStateType,
action: PayloadAction<string | undefined>
) {
state.animateQuotedMessageId = action.payload;
return state;
},
setNextMessageToPlay(state: ConversationsStateType, action: PayloadAction<number | undefined>) {
state.nextMessageToPlay = action.payload;
return state;
},
},
extraReducers: (builder: any) => {
// Add reducers for additional action types here, and handle loading state as needed
@ -750,22 +777,6 @@ const conversationsSlice = createSlice({
return state;
}
);
builder.addCase(
fetchMessagesForConversation.fulfilled,
(state: ConversationsStateType, action: any) => {
// this is called once the messages are loaded from the db for the currently selected conversation
const { messagesProps, conversationKey } = action.payload as FetchedMessageResults;
// double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) {
return {
...state,
messages: messagesProps,
areMoreMessagesBeingFetched: false,
};
}
return state;
}
);
builder.addCase(fetchMessagesForConversation.pending, (state: ConversationsStateType) => {
state.areMoreMessagesBeingFetched = true;
});
@ -800,4 +811,7 @@ export const {
toggleSelectedMessageId,
showLightBox,
quoteMessage,
showScrollToBottomButton,
quotedMessageToAnimate,
setNextMessageToPlay,
} = actions;

View File

@ -297,7 +297,22 @@ export const getQuotedMessage = createSelector(
(state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage
);
export const areMoreMessagesBeingFetched = createSelector(
getConversations,
(state: ConversationsStateType): boolean => state.areMoreMessagesBeingFetched || false
);
export const areMoreMessagesLoading = createSlice(getConversations,
(state: ConversationsStateType): boolean => state.
);
export const getShowScrollButton = createSelector(
getConversations,
(state: ConversationsStateType): boolean => state.showScrollButton || false
);
export const getQuotedMessageToAnimate = createSelector(
getConversations,
(state: ConversationsStateType): string | undefined => state.animateQuotedMessageId || undefined
);
export const getNextMessageToPlayIndex = createSelector(
getConversations,
(state: ConversationsStateType): number | undefined => state.nextMessageToPlay || undefined
);