chore: broke apart big Message selectors into smaller ones

pull/2793/head
Audric Ackermann 2 years ago
parent 461b192f37
commit f2cddb83c8

@ -1,19 +1,18 @@
import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useDisableDrag } from '../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { isEqual } from 'lodash';
import { import {
useAvatarPath, useAvatarPath,
useConversationUsername, useConversationUsername,
useIsClosedGroup, useIsClosedGroup,
} from '../../hooks/useParamSelector'; } from '../../hooks/useParamSelector';
import { isMessageSelectionMode } from '../../state/selectors/conversations';
import { SessionIcon } from '../icon';
import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder'; import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder';
import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar'; import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar';
import { useDisableDrag } from '../../hooks/useDisableDrag';
import styled from 'styled-components';
import { SessionIcon } from '../icon';
import { useSelector } from 'react-redux';
import { isMessageSelectionMode } from '../../state/selectors/conversations';
export enum AvatarSize { export enum AvatarSize {
XS = 28, XS = 28,
@ -90,7 +89,7 @@ const AvatarImage = (props: {
datatestId?: string; datatestId?: string;
handleImageError: () => any; handleImageError: () => any;
}) => { }) => {
const { avatarPath, base64Data, name, imageBroken, datatestId, handleImageError } = props; const { avatarPath, base64Data, imageBroken, datatestId, handleImageError } = props;
const disableDrag = useDisableDrag(); const disableDrag = useDisableDrag();
@ -103,7 +102,6 @@ const AvatarImage = (props: {
<img <img
onError={handleImageError} onError={handleImageError}
onDragStart={disableDrag} onDragStart={disableDrag}
alt={window.i18n('contactAvatarAlt', [name || 'avatar'])}
src={dataToDisplay} src={dataToDisplay}
data-testid={datatestId} data-testid={datatestId}
/> />
@ -173,4 +171,4 @@ const AvatarInner = (props: Props) => {
); );
}; };
export const Avatar = React.memo(AvatarInner, isEqual); export const Avatar = AvatarInner;

@ -1,9 +1,13 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { MessageRenderingProps } from '../../../../models/messageType';
import { PubKey } from '../../../../session/types'; import { PubKey } from '../../../../session/types';
import { getMessageAuthorProps } from '../../../../state/selectors/conversations'; import {
useAuthorName,
useAuthorProfileName,
useFirstMessageOfSeries,
useMessageAuthor,
useMessageDirection,
} from '../../../../state/selectors';
import { import {
useSelectedIsGroup, useSelectedIsGroup,
useSelectedIsPublic, useSelectedIsPublic,
@ -11,11 +15,6 @@ import {
import { Flex } from '../../../basic/Flex'; import { Flex } from '../../../basic/Flex';
import { ContactName } from '../../ContactName'; import { ContactName } from '../../ContactName';
export type MessageAuthorSelectorProps = Pick<
MessageRenderingProps,
'authorName' | 'authorProfileName' | 'sender' | 'direction' | 'firstMessageOfSeries'
>;
type Props = { type Props = {
messageId: string; messageId: string;
}; };
@ -25,15 +24,17 @@ const StyledAuthorContainer = styled(Flex)`
`; `;
export const MessageAuthorText = (props: Props) => { export const MessageAuthorText = (props: Props) => {
const selected = useSelector(state => getMessageAuthorProps(state as any, props.messageId));
const isPublic = useSelectedIsPublic(); const isPublic = useSelectedIsPublic();
const isGroup = useSelectedIsGroup(); const isGroup = useSelectedIsGroup();
const authorProfileName = useAuthorProfileName(props.messageId);
const authorName = useAuthorName(props.messageId);
const sender = useMessageAuthor(props.messageId);
const direction = useMessageDirection(props.messageId);
const firstMessageOfSeries = useFirstMessageOfSeries(props.messageId);
if (!selected) { if (!props.messageId || !sender || !direction) {
return null; return null;
} }
const { authorName, sender, authorProfileName, direction, firstMessageOfSeries } = selected;
const title = authorName ? authorName : sender; const title = authorName ? authorName : sender;

@ -9,10 +9,18 @@ import { getSodiumRenderer } from '../../../../session/crypto';
import { PubKey } from '../../../../session/types'; import { PubKey } from '../../../../session/types';
import { openConversationWithMessages } from '../../../../state/ducks/conversations'; import { openConversationWithMessages } from '../../../../state/ducks/conversations';
import { updateUserDetailsModal } from '../../../../state/ducks/modalDialog'; import { updateUserDetailsModal } from '../../../../state/ducks/modalDialog';
import { getMessageAvatarProps } from '../../../../state/selectors/conversations'; import {
useAuthorAvatarPath,
useAuthorName,
useAuthorProfileName,
useLastMessageOfSeries,
useMessageAuthor,
useMessageSenderIsAdmin,
} from '../../../../state/selectors/';
import { import {
getSelectedCanWrite, getSelectedCanWrite,
useSelectedConversationKey, useSelectedConversationKey,
useSelectedIsPublic,
} from '../../../../state/selectors/selectedConversation'; } from '../../../../state/selectors/selectedConversation';
import { Avatar, AvatarSize, CrownIcon } from '../../../avatar/Avatar'; import { Avatar, AvatarSize, CrownIcon } from '../../../avatar/Avatar';
// tslint:disable: use-simple-attributes // tslint:disable: use-simple-attributes
@ -26,13 +34,7 @@ const StyledAvatar = styled.div`
export type MessageAvatarSelectorProps = Pick< export type MessageAvatarSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
| 'authorAvatarPath' 'sender' | 'isSenderAdmin' | 'lastMessageOfSeries'
| 'authorName'
| 'sender'
| 'authorProfileName'
| 'isSenderAdmin'
| 'isPublic'
| 'lastMessageOfSeries'
>; >;
type Props = { messageId: string; noAvatar: boolean }; type Props = { messageId: string; noAvatar: boolean };
@ -41,26 +43,18 @@ export const MessageAvatar = (props: Props) => {
const { messageId, noAvatar } = props; const { messageId, noAvatar } = props;
const dispatch = useDispatch(); const dispatch = useDispatch();
const avatarProps = useSelector(state => getMessageAvatarProps(state as any, messageId));
const selectedConvoKey = useSelectedConversationKey(); const selectedConvoKey = useSelectedConversationKey();
const isTypingEnabled = useSelector(getSelectedCanWrite); const isTypingEnabled = useSelector(getSelectedCanWrite);
const isPublic = useSelectedIsPublic();
if (!avatarProps) { const authorName = useAuthorName(messageId);
return null; const authorProfileName = useAuthorProfileName(messageId);
} const authorAvatarPath = useAuthorAvatarPath(messageId);
const sender = useMessageAuthor(messageId);
const { const lastMessageOfSeries = useLastMessageOfSeries(messageId);
authorAvatarPath, const isSenderAdmin = useMessageSenderIsAdmin(messageId);
authorName,
sender, if (noAvatar || !sender) {
authorProfileName,
isSenderAdmin,
lastMessageOfSeries,
isPublic,
} = avatarProps;
if (noAvatar) {
return null; return null;
} }

@ -10,8 +10,8 @@ import {
getMessageContentSelectorProps, getMessageContentSelectorProps,
getQuotedMessageToAnimate, getQuotedMessageToAnimate,
getShouldHighlightMessage, getShouldHighlightMessage,
useMessageIsDeleted,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { useMessageIsDeleted } from '../../../../state/selectors';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer'; import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
import { MessageAttachment } from './MessageAttachment'; import { MessageAttachment } from './MessageAttachment';
import { MessageLinkPreview } from './MessageLinkPreview'; import { MessageLinkPreview } from './MessageLinkPreview';

@ -4,12 +4,14 @@ import { isImageAttachment } from '../../../../types/Attachment';
import { Image } from '../../Image'; import { Image } from '../../Image';
import { MessageRenderingProps } from '../../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import { getIsMessageSelectionMode } from '../../../../state/selectors/conversations';
getIsMessageSelectionMode,
getMessageLinkPreviewProps,
} from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../../icon'; import { SessionIcon } from '../../../icon';
import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm'; import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm';
import {
useMessageAttachments,
useMessageDirection,
useMessageLinkPreview,
} from '../../../../state/selectors';
export type MessageLinkPreviewSelectorProps = Pick< export type MessageLinkPreviewSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
@ -24,14 +26,15 @@ type Props = {
const linkPreviewsImageSize = 100; const linkPreviewsImageSize = 100;
export const MessageLinkPreview = (props: Props) => { export const MessageLinkPreview = (props: Props) => {
const selected = useSelector(state => getMessageLinkPreviewProps(state as any, props.messageId));
const dispatch = useDispatch(); const dispatch = useDispatch();
const direction = useMessageDirection(props.messageId);
const attachments = useMessageAttachments(props.messageId);
const previews = useMessageLinkPreview(props.messageId);
const isMessageSelectionMode = useSelector(getIsMessageSelectionMode); const isMessageSelectionMode = useSelector(getIsMessageSelectionMode);
if (!selected) { if (!props.messageId) {
return null; return null;
} }
const { direction, attachments, previews } = selected;
// Attachments take precedence over Link Previews // Attachments take precedence over Link Previews
if (attachments && attachments.length) { if (attachments && attachments.length) {

@ -5,7 +5,6 @@ import { MessageRenderingProps } from '../../../../models/messageType';
import { PubKey } from '../../../../session/types'; import { PubKey } from '../../../../session/types';
import { openConversationToSpecificMessage } from '../../../../state/ducks/conversations'; import { openConversationToSpecificMessage } from '../../../../state/ducks/conversations';
import { import {
getMessageQuoteProps,
isMessageDetailView, isMessageDetailView,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
@ -13,6 +12,7 @@ import { Quote } from './Quote';
import { ToastUtils } from '../../../../session/utils'; import { ToastUtils } from '../../../../session/utils';
import { Data } from '../../../../data/data'; import { Data } from '../../../../data/data';
import { MessageModel } from '../../../../models/message'; import { MessageModel } from '../../../../models/message';
import { useMessageDirection, useMessageQuote } from '../../../../state/selectors';
// tslint:disable: use-simple-attributes // tslint:disable: use-simple-attributes
@ -23,13 +23,11 @@ type Props = {
export type MessageQuoteSelectorProps = Pick<MessageRenderingProps, 'quote' | 'direction'>; export type MessageQuoteSelectorProps = Pick<MessageRenderingProps, 'quote' | 'direction'>;
export const MessageQuote = (props: Props) => { export const MessageQuote = (props: Props) => {
const selected = useSelector(state => getMessageQuoteProps(state as any, props.messageId)); const quote = useMessageQuote(props.messageId);
const direction = useMessageDirection(props.messageId);
const multiSelectMode = useSelector(isMessageSelectionMode); const multiSelectMode = useSelector(isMessageSelectionMode);
const isMessageDetailViewMode = useSelector(isMessageDetailView); const isMessageDetailViewMode = useSelector(isMessageDetailView);
const quote = selected ? selected.quote : undefined;
const direction = selected ? selected.direction : undefined;
const onQuoteClick = useCallback( const onQuoteClick = useCallback(
async (event: React.MouseEvent<HTMLDivElement>) => { async (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault(); event.preventDefault();
@ -76,7 +74,7 @@ export const MessageQuote = (props: Props) => {
}, },
[quote, multiSelectMode, props.messageId] [quote, multiSelectMode, props.messageId]
); );
if (!selected) { if (!props.messageId) {
return null; return null;
} }

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { getMessageStatusProps } from '../../../../state/selectors/conversations';
import { OutgoingMessageStatus } from './OutgoingMessageStatus'; import { OutgoingMessageStatus } from './OutgoingMessageStatus';
import { useMessageDirection, useMessageStatus } from '../../../../state/selectors';
type Props = { type Props = {
isCorrectSide: boolean; isCorrectSide: boolean;
@ -14,12 +13,12 @@ export type MessageStatusSelectorProps = Pick<MessageRenderingProps, 'direction'
export const MessageStatus = (props: Props) => { export const MessageStatus = (props: Props) => {
const { isCorrectSide, dataTestId } = props; const { isCorrectSide, dataTestId } = props;
const direction = useMessageDirection(props.messageId);
const status = useMessageStatus(props.messageId);
const selected = useSelector(state => getMessageStatusProps(state as any, props.messageId)); if (!props.messageId) {
if (!selected) {
return null; return null;
} }
const { status, direction } = selected;
if (!isCorrectSide) { if (!isCorrectSide) {
return null; return null;

@ -10,14 +10,12 @@ import {
closeMessageDetailsView, closeMessageDetailsView,
ContactPropsMessageDetail, ContactPropsMessageDetail,
} from '../../../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { import { getMessageDetailsViewProps } from '../../../../state/selectors/conversations';
getMessageDetailsViewProps,
getMessageIsDeletable,
} from '../../../../state/selectors/conversations';
import { ContactName } from '../../ContactName'; import { ContactName } from '../../ContactName';
// 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';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../../../basic/SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../../../basic/SessionButton';
import { useMessageIsDeletable } from '../../../../state/selectors';
const AvatarItem = (props: { pubkey: string }) => { const AvatarItem = (props: { pubkey: string }) => {
const { pubkey } = props; const { pubkey } = props;
@ -98,9 +96,7 @@ export const MessageDetail = () => {
const { i18n } = window; const { i18n } = window;
const messageDetailProps = useSelector(getMessageDetailsViewProps); const messageDetailProps = useSelector(getMessageDetailsViewProps);
const isDeletable = useSelector(state => const isDeletable = useMessageIsDeletable(messageDetailProps?.messageId);
getMessageIsDeletable(state as any, messageDetailProps?.messageId || '')
);
const dispatch = useDispatch(); const dispatch = useDispatch();

@ -122,7 +122,7 @@ const animation = (props: {
}; };
//tslint:disable no-unnecessary-callback-wrapper //tslint:disable no-unnecessary-callback-wrapper
const Svg = React.memo(styled.svg<StyledSvgProps>` const Svg = styled.svg<StyledSvgProps>`
width: ${props => props.width}; width: ${props => props.width};
transform: ${props => `rotate(${props.iconRotation}deg)`}; transform: ${props => `rotate(${props.iconRotation}deg)`};
animation: ${props => animation(props)}; animation: ${props => animation(props)};
@ -134,7 +134,7 @@ const Svg = React.memo(styled.svg<StyledSvgProps>`
fill: ${props => (props.iconColor ? props.iconColor : '--button-icon-stroke-color')}; fill: ${props => (props.iconColor ? props.iconColor : '--button-icon-stroke-color')};
padding: ${props => (props.iconPadding ? props.iconPadding : '')}; padding: ${props => (props.iconPadding ? props.iconPadding : '')};
transition: inherit; transition: inherit;
`); `;
// tslint:enable no-unnecessary-callback-wrapper // tslint:enable no-unnecessary-callback-wrapper
const SessionSvg = (props: { const SessionSvg = (props: {

@ -140,4 +140,4 @@ const ConversationListItem = (props: Props) => {
); );
}; };
export const MemoConversationListItemWithDetails = React.memo(ConversationListItem, _.isEqual); export const MemoConversationListItemWithDetails = ConversationListItem;

@ -77,13 +77,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
); );
}; };
function propsAreEqual(prev: PropsContextConversationItem, next: PropsContextConversationItem) { export const MemoConversationListItemContextMenu = ConversationListItemContextMenu;
return _.isEqual(prev, next);
}
export const MemoConversationListItemContextMenu = React.memo(
ConversationListItemContextMenu,
propsAreEqual
);
export const PinConversationMenuItem = (): JSX.Element | null => { export const PinConversationMenuItem = (): JSX.Element | null => {
const conversationId = useConvoIdFromContext(); const conversationId = useConvoIdFromContext();

@ -12,6 +12,7 @@ import {
sortBy, sortBy,
throttle, throttle,
uniq, uniq,
xor,
} from 'lodash'; } from 'lodash';
import { SignalService } from '../protobuf'; import { SignalService } from '../protobuf';
import { getMessageQueue } from '../session'; import { getMessageQueue } from '../session';
@ -41,7 +42,6 @@ import { toHex } from '../session/utils/String';
import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout'; import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout';
import { import {
actions as conversationActions, actions as conversationActions,
conversationChanged,
conversationsChanged, conversationsChanged,
markConversationFullyRead, markConversationFullyRead,
MessageModelPropsWithoutConvoProps, MessageModelPropsWithoutConvoProps,
@ -163,9 +163,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.typingRefreshTimer = null; this.typingRefreshTimer = null;
this.typingPauseTimer = null; this.typingPauseTimer = null;
window.inboxStore?.dispatch( window.inboxStore?.dispatch(conversationsChanged([this.getConversationModelProps()]));
conversationChanged({ id: this.id, data: this.getConversationModelProps() })
);
} }
public idForLogging() { public idForLogging() {
@ -394,13 +392,13 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
* @returns true if the groupAdmins where not the same (and thus updated) * @returns true if the groupAdmins where not the same (and thus updated)
*/ */
public async updateGroupAdmins(groupAdmins: Array<string>, shouldCommit: boolean) { public async updateGroupAdmins(groupAdmins: Array<string>, shouldCommit: boolean) {
const sortedExistingAdmins = uniq(sortBy(this.getGroupAdmins()));
const sortedNewAdmins = uniq(sortBy(groupAdmins)); const sortedNewAdmins = uniq(sortBy(groupAdmins));
if (isEqual(sortedExistingAdmins, sortedNewAdmins)) { // check if there is any difference betwewen the two, if yes, override it with what we got.
if (!xor(this.getGroupAdmins(), groupAdmins).length) {
return false; return false;
} }
this.set({ groupAdmins }); this.set({ groupAdmins: sortedNewAdmins });
if (shouldCommit) { if (shouldCommit) {
await this.commit(); await this.commit();
} }

@ -77,6 +77,7 @@ import {
PropsForGroupUpdateLeft, PropsForGroupUpdateLeft,
PropsForGroupUpdateName, PropsForGroupUpdateName,
PropsForMessageWithoutConvoProps, PropsForMessageWithoutConvoProps,
ReduxQuoteType,
} from '../state/ducks/conversations'; } from '../state/ducks/conversations';
import { AttachmentTypeWithPath, isVoiceMessage } from '../types/Attachment'; import { AttachmentTypeWithPath, isVoiceMessage } from '../types/Attachment';
import { getAttachmentMetadata } from '../types/message/initializeAttachmentMetadata'; import { getAttachmentMetadata } from '../types/message/initializeAttachmentMetadata';
@ -615,15 +616,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
} }
const firstAttachment = quote.attachments && quote.attachments[0]; const firstAttachment = quote.attachments && quote.attachments[0];
const quoteProps: { const quoteProps: ReduxQuoteType = {
referencedMessageNotFound?: boolean;
sender: string;
messageId: string;
authorName: string;
text?: string;
attachment?: any;
isFromMe?: boolean;
} = {
sender: author, sender: author,
messageId: id, messageId: id,
authorName: authorName || 'Unknown', authorName: authorName || 'Unknown',

@ -432,10 +432,7 @@ export class ConversationController {
this.conversations.remove(conversation); this.conversations.remove(conversation);
window?.inboxStore?.dispatch( window?.inboxStore?.dispatch(
conversationActions.conversationChanged({ conversationActions.conversationsChanged([conversation.getConversationModelProps()])
id: convoId,
data: conversation.getConversationModelProps(),
})
); );
} }
window.inboxStore?.dispatch(conversationActions.conversationRemoved(convoId)); window.inboxStore?.dispatch(conversationActions.conversationRemoved(convoId));

@ -161,6 +161,17 @@ export type PropsForAttachment = {
} | null; } | null;
}; };
export type ReduxQuoteType = {
text?: string;
attachment?: QuotedAttachmentType;
isFromMe?: boolean;
sender: string;
authorProfileName?: string;
authorName?: string;
messageId?: string;
referencedMessageNotFound?: boolean;
} | null;
export type PropsForMessageWithoutConvoProps = { export type PropsForMessageWithoutConvoProps = {
id: string; // messageId id: string; // messageId
direction: MessageModelType; direction: MessageModelType;
@ -177,16 +188,7 @@ export type PropsForMessageWithoutConvoProps = {
reacts?: ReactionList; reacts?: ReactionList;
reactsIndex?: number; reactsIndex?: number;
previews?: Array<any>; previews?: Array<any>;
quote?: { quote?: ReduxQuoteType;
text?: string;
attachment?: QuotedAttachmentType;
isFromMe?: boolean;
sender: string;
authorProfileName?: string;
authorName?: string;
messageId?: string;
referencedMessageNotFound?: boolean;
} | null;
messageHash?: string; messageHash?: string;
isDeleted?: boolean; isDeleted?: boolean;
isUnread?: boolean; isUnread?: boolean;
@ -197,12 +199,8 @@ export type PropsForMessageWithoutConvoProps = {
}; };
export type PropsForMessageWithConvoProps = PropsForMessageWithoutConvoProps & { export type PropsForMessageWithConvoProps = PropsForMessageWithoutConvoProps & {
authorName: string | null;
authorProfileName: string | null;
conversationType: ConversationTypeEnum; conversationType: ConversationTypeEnum;
authorAvatarPath: string | null;
isPublic: boolean; isPublic: boolean;
isOpenGroupV2: boolean;
isKickedFromGroup: boolean; isKickedFromGroup: boolean;
weAreAdmin: boolean; weAreAdmin: boolean;
isSenderAdmin: boolean; isSenderAdmin: boolean;
@ -634,16 +632,7 @@ const conversationsSlice = createSlice({
}, },
}; };
}, },
conversationChanged(
state: ConversationsStateType,
action: PayloadAction<{
id: string;
data: ReduxConversationType;
}>
) {
const { payload } = action;
return applyConversationChanged(state, payload);
},
conversationsChanged( conversationsChanged(
state: ConversationsStateType, state: ConversationsStateType,
action: PayloadAction<Array<ReduxConversationType>> action: PayloadAction<Array<ReduxConversationType>>
@ -652,12 +641,7 @@ const conversationsSlice = createSlice({
let updatedState = state; let updatedState = state;
if (payload.length) { if (payload.length) {
payload.forEach(convoProps => { updatedState = applyConversationsChanged(updatedState, payload);
updatedState = applyConversationChanged(updatedState, {
id: convoProps.id,
data: convoProps,
});
});
} }
return updatedState; return updatedState;
@ -972,47 +956,47 @@ const conversationsSlice = createSlice({
}, },
}); });
function applyConversationChanged( function applyConversationsChanged(
state: ConversationsStateType, state: ConversationsStateType,
payload: { id: string; data: ReduxConversationType } payload: Array<ReduxConversationType>
) { ) {
const { id, data } = payload;
const { conversationLookup, selectedConversation } = state; const { conversationLookup, selectedConversation } = state;
const existing = conversationLookup[id]; for (let index = 0; index < payload.length; index++) {
// In the change case we only modify the lookup if we already had that conversation const convoProps = payload[index];
if (!existing) { const { id } = convoProps;
return state; // In the `change` case we only modify the lookup if we already had that conversation
} const existing = conversationLookup[id];
if (!existing) {
continue;
}
if (
state.selectedConversation &&
convoProps.isPrivate &&
convoProps.id === selectedConversation &&
convoProps.priority &&
convoProps.priority < CONVERSATION_PRIORITIES.default
) {
// A private conversation hidden cannot be a selected.
// When opening a hidden conversation, we unhide it so it can be selected again.
state.selectedConversation = undefined;
}
let selected = selectedConversation; state.conversationLookup[id] = {
if ( ...convoProps,
data && isInitialFetchingInProgress: existing.isInitialFetchingInProgress,
data.isPrivate && };
data.id === selectedConversation &&
data.priority &&
data.priority < CONVERSATION_PRIORITIES.default
) {
// A private conversation hidden cannot be a selected.
// When opening a hidden conversation, we unhide it so it can be selected again.
selected = undefined;
} }
return { return state;
...state,
selectedConversation: selected,
conversationLookup: {
...conversationLookup,
[id]: { ...data, isInitialFetchingInProgress: existing.isInitialFetchingInProgress },
},
};
} }
export const { actions, reducer } = conversationsSlice; export const { actions, reducer } = conversationsSlice;
export const { export const {
// conversation and messages list // conversation and messages list
conversationAdded, conversationAdded,
conversationChanged,
conversationsChanged, conversationsChanged,
conversationRemoved, conversationRemoved,
removeAllConversations, removeAllConversations,

@ -1,9 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { sortBy, uniq } from 'lodash'; import { sortBy, uniq, xor } from 'lodash';
import { import {
getCanWriteOutsideRedux, getCanWriteOutsideRedux,
getCurrentSubscriberCountOutsideRedux, getCurrentSubscriberCountOutsideRedux,
getModeratorsOutsideRedux,
} from '../selectors/sogsRoomInfo'; } from '../selectors/sogsRoomInfo';
type RoomInfo = { type RoomInfo = {
@ -53,8 +52,16 @@ const sogsRoomInfosSlice = createSlice({
}, },
setModerators(state, action: PayloadAction<{ convoId: string; moderators: Array<string> }>) { setModerators(state, action: PayloadAction<{ convoId: string; moderators: Array<string> }>) {
addEmptyEntryIfNeeded(state, action.payload.convoId); addEmptyEntryIfNeeded(state, action.payload.convoId);
const existing = state.rooms[action.payload.convoId].moderators;
const newMods = sortBy(uniq(action.payload.moderators));
state.rooms[action.payload.convoId].moderators = sortBy(uniq(action.payload.moderators)); // check if there is any changes (order excluded) between those two arrays
const xord = xor(existing, newMods);
if (!xord.length) {
return state;
}
state.rooms[action.payload.convoId].moderators = newMods;
return state; return state;
}, },
@ -93,10 +100,6 @@ function setCanWriteOutsideRedux(convoId: string, canWrite: boolean) {
* @param moderators the updated list of moderators * @param moderators the updated list of moderators
*/ */
function setModeratorsOutsideRedux(convoId: string, moderators: Array<string>) { function setModeratorsOutsideRedux(convoId: string, moderators: Array<string>) {
const currentMods = getModeratorsOutsideRedux(convoId);
if (sortBy(uniq(currentMods)) === sortBy(uniq(moderators))) {
return;
}
window.inboxStore?.dispatch( window.inboxStore?.dispatch(
setModerators({ setModerators({
convoId, convoId,

@ -14,14 +14,9 @@ import { StateType } from '../reducer';
import { ReplyingToMessageProps } from '../../components/conversation/composition/CompositionBox'; import { ReplyingToMessageProps } from '../../components/conversation/composition/CompositionBox';
import { MessageAttachmentSelectorProps } from '../../components/conversation/message/message-content/MessageAttachment'; import { MessageAttachmentSelectorProps } from '../../components/conversation/message/message-content/MessageAttachment';
import { MessageAuthorSelectorProps } from '../../components/conversation/message/message-content/MessageAuthorText';
import { MessageAvatarSelectorProps } from '../../components/conversation/message/message-content/MessageAvatar';
import { MessageContentSelectorProps } from '../../components/conversation/message/message-content/MessageContent'; import { MessageContentSelectorProps } from '../../components/conversation/message/message-content/MessageContent';
import { MessageContentWithStatusSelectorProps } from '../../components/conversation/message/message-content/MessageContentWithStatus'; import { MessageContentWithStatusSelectorProps } from '../../components/conversation/message/message-content/MessageContentWithStatus';
import { MessageContextMenuSelectorProps } from '../../components/conversation/message/message-content/MessageContextMenu'; import { MessageContextMenuSelectorProps } from '../../components/conversation/message/message-content/MessageContextMenu';
import { MessageLinkPreviewSelectorProps } from '../../components/conversation/message/message-content/MessageLinkPreview';
import { MessageQuoteSelectorProps } from '../../components/conversation/message/message-content/MessageQuote';
import { MessageStatusSelectorProps } from '../../components/conversation/message/message-content/MessageStatus';
import { MessageTextSelectorProps } from '../../components/conversation/message/message-content/MessageText'; import { MessageTextSelectorProps } from '../../components/conversation/message/message-content/MessageText';
import { GenericReadableMessageSelectorProps } from '../../components/conversation/message/message-item/GenericReadableMessage'; import { GenericReadableMessageSelectorProps } from '../../components/conversation/message/message-item/GenericReadableMessage';
import { LightBoxOptions } from '../../components/conversation/SessionConversation'; import { LightBoxOptions } from '../../components/conversation/SessionConversation';
@ -40,21 +35,17 @@ import { getIntl } from './user';
import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash'; import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash';
import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions'; import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions';
import { getModeratorsOutsideRedux } from './sogsRoomInfo';
import { getSelectedConversation, getSelectedConversationKey } from './selectedConversation'; import { getSelectedConversation, getSelectedConversationKey } from './selectedConversation';
import { useSelector } from 'react-redux'; import { getModeratorsOutsideRedux } from './sogsRoomInfo';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations; export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
export const getConversationLookup = createSelector( export const getConversationLookup = (state: StateType): ConversationLookupType => {
getConversations, return state.conversations.conversationLookup;
(state: ConversationsStateType): ConversationLookupType => { };
return state.conversationLookup;
}
);
export const getConversationsCount = createSelector(getConversationLookup, (state): number => { export const getConversationsCount = createSelector(getConversationLookup, (state): number => {
return Object.values(state).length; return Object.keys(state).length;
}); });
export const getOurPrimaryConversation = createSelector( export const getOurPrimaryConversation = createSelector(
@ -63,10 +54,9 @@ export const getOurPrimaryConversation = createSelector(
state.conversationLookup[Storage.get('primaryDevicePubKey') as string] state.conversationLookup[Storage.get('primaryDevicePubKey') as string]
); );
const getMessagesOfSelectedConversation = createSelector( const getMessagesOfSelectedConversation = (
getConversations, state: StateType
(state: ConversationsStateType): Array<MessageModelPropsWithoutConvoProps> => state.messages ): Array<MessageModelPropsWithoutConvoProps> => state.conversations.messages;
);
// Redux recommends to do filtered and deriving state in a selector rather than ourself // Redux recommends to do filtered and deriving state in a selector rather than ourself
export const getSortedMessagesOfSelectedConversation = createSelector( export const getSortedMessagesOfSelectedConversation = createSelector(
@ -97,12 +87,9 @@ export const hasSelectedConversationIncomingMessages = createSelector(
} }
); );
export const getFirstUnreadMessageId = createSelector( export const getFirstUnreadMessageId = (state: StateType): string | undefined => {
getConversations, return state.conversations.firstUnreadMessageId;
(state: ConversationsStateType): string | undefined => { };
return state.firstUnreadMessageId;
}
);
export type MessagePropsType = export type MessagePropsType =
| 'group-notification' | 'group-notification'
@ -489,76 +476,47 @@ export const getGlobalUnreadMessageCount = createSelector(getLeftPaneLists, (sta
return state.globalUnreadCount; return state.globalUnreadCount;
}); });
export const isMessageDetailView = createSelector( export const isMessageDetailView = (state: StateType): boolean =>
getConversations, state.conversations.messageDetailProps !== undefined;
(state: ConversationsStateType): boolean => state.messageDetailProps !== undefined
);
export const getMessageDetailsViewProps = createSelector( export const getMessageDetailsViewProps = (state: StateType): MessagePropsDetails | undefined =>
getConversations, state.conversations.messageDetailProps;
(state: ConversationsStateType): MessagePropsDetails | undefined => state.messageDetailProps
);
export const isRightPanelShowing = createSelector( export const isRightPanelShowing = (state: StateType): boolean =>
getConversations, state.conversations.showRightPanel;
(state: ConversationsStateType): boolean => state.showRightPanel
);
export const isMessageSelectionMode = createSelector( export const isMessageSelectionMode = (state: StateType): boolean =>
getConversations, state.conversations.selectedMessageIds.length > 0;
(state: ConversationsStateType): boolean => Boolean(state.selectedMessageIds.length > 0)
);
export const getSelectedMessageIds = createSelector( export const getSelectedMessageIds = (state: StateType): Array<string> =>
getConversations, state.conversations.selectedMessageIds;
(state: ConversationsStateType): Array<string> => state.selectedMessageIds
);
export const getIsMessageSelectionMode = createSelector( export const getIsMessageSelectionMode = (state: StateType): boolean =>
getSelectedMessageIds, Boolean(getSelectedMessageIds(state).length);
(state: Array<string>): boolean => Boolean(state.length)
);
export const getLightBoxOptions = createSelector( export const getLightBoxOptions = (state: StateType): LightBoxOptions | undefined =>
getConversations, state.conversations.lightBox;
(state: ConversationsStateType): LightBoxOptions | undefined => state.lightBox
);
export const getQuotedMessage = createSelector( export const getQuotedMessage = (state: StateType): ReplyingToMessageProps | undefined =>
getConversations, state.conversations.quotedMessage;
(state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage
);
export const areMoreMessagesBeingFetched = createSelector( export const areMoreMessagesBeingFetched = (state: StateType): boolean =>
getConversations, state.conversations.areMoreMessagesBeingFetched || false;
(state: ConversationsStateType): boolean => state.areMoreMessagesBeingFetched || false
);
export const getShowScrollButton = createSelector( export const getShowScrollButton = (state: StateType): boolean =>
getConversations, state.conversations.showScrollButton || false;
(state: ConversationsStateType): boolean => state.showScrollButton || false
);
export const getQuotedMessageToAnimate = createSelector( export const getQuotedMessageToAnimate = (state: StateType): string | undefined =>
getConversations, state.conversations.animateQuotedMessageId || undefined;
(state: ConversationsStateType): string | undefined => state.animateQuotedMessageId || undefined
);
export const getShouldHighlightMessage = createSelector( export const getShouldHighlightMessage = (state: StateType): boolean =>
getConversations, Boolean(state.conversations.animateQuotedMessageId && state.conversations.shouldHighlightMessage);
(state: ConversationsStateType): boolean =>
Boolean(state.animateQuotedMessageId && state.shouldHighlightMessage)
);
export const getNextMessageToPlayId = createSelector( export const getNextMessageToPlayId = (state: StateType): string | undefined =>
getConversations, state.conversations.nextMessageToPlayId || undefined;
(state: ConversationsStateType): string | undefined => state.nextMessageToPlayId || undefined
);
export const getMentionsInput = createSelector( export const getMentionsInput = (state: StateType): MentionsMembersType =>
getConversations, state.conversations.mentionMembers;
(state: ConversationsStateType): MentionsMembersType => state.mentionMembers
);
/// Those calls are just related to ordering messages in the redux store. /// Those calls are just related to ordering messages in the redux store.
@ -621,12 +579,9 @@ function sortMessages(
* This returns the most recent message id in the database. This is not the most recent message shown, * This returns the most recent message id in the database. This is not the most recent message shown,
* but the most recent one, which could still not be loaded. * but the most recent one, which could still not be loaded.
*/ */
export const getMostRecentMessageId = createSelector( export const getMostRecentMessageId = (state: StateType): string | null => {
getConversations, return state.conversations.mostRecentMessageId;
(state: ConversationsStateType): string | null => { };
return state.mostRecentMessageId;
}
);
export const getOldestMessageId = createSelector( export const getOldestMessageId = createSelector(
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
@ -674,20 +629,22 @@ export const isFirstUnreadMessageIdAbove = createSelector(
} }
); );
const getMessageId = (_whatever: any, id: string) => id; const getMessageId = (_whatever: any, id: string | undefined) => id;
// tslint:disable: cyclomatic-complexity // tslint:disable: cyclomatic-complexity
export const getMessagePropsByMessageId = createSelector( export const getMessagePropsByMessageId = createSelector(
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
getConversationLookup, getSelectedConversation,
getMessageId, getMessageId,
( (
messages: Array<SortedMessageModelProps>, messages: Array<SortedMessageModelProps>,
conversations, selectedConvo,
id id
): MessageModelPropsWithConvoProps | undefined => { ): MessageModelPropsWithConvoProps | undefined => {
if (!id) {
return undefined;
}
const foundMessageProps: SortedMessageModelProps | undefined = messages?.find( const foundMessageProps: SortedMessageModelProps | undefined = messages?.find(
m => m?.propsForMessage?.id === id m => m?.propsForMessage?.id === id
); );
@ -697,27 +654,20 @@ export const getMessagePropsByMessageId = createSelector(
} }
const sender = foundMessageProps?.propsForMessage?.sender; const sender = foundMessageProps?.propsForMessage?.sender;
// foundMessageConversation is the conversation this message is // we can only show messages when the convo is selected.
const foundMessageConversation = conversations[foundMessageProps.propsForMessage.convoId]; if (!selectedConvo || !sender) {
if (!foundMessageConversation || !sender) {
return undefined;
}
const foundSenderConversation = conversations[sender];
if (!foundSenderConversation) {
return undefined; return undefined;
} }
const ourPubkey = UserUtils.getOurPubKeyStrFromCache(); const ourPubkey = UserUtils.getOurPubKeyStrFromCache();
const isGroup = !foundMessageConversation.isPrivate; const isGroup = !selectedConvo.isPrivate;
const isPublic = foundMessageConversation.isPublic; const isPublic = selectedConvo.isPublic;
const groupAdmins = (isGroup && foundMessageConversation.groupAdmins) || []; const groupAdmins = (isGroup && selectedConvo.groupAdmins) || [];
const weAreAdmin = groupAdmins.includes(ourPubkey) || false; const weAreAdmin = groupAdmins.includes(ourPubkey) || false;
const weAreModerator = const weAreModerator =
(isPublic && getModeratorsOutsideRedux(foundMessageConversation.id).includes(ourPubkey)) || (isPublic && getModeratorsOutsideRedux(selectedConvo.id).includes(ourPubkey)) || false;
false;
// A message is deletable if // A message is deletable if
// either we sent it, // either we sent it,
// or the convo is not a public one (in this case, we will only be able to delete for us) // or the convo is not a public one (in this case, we will only be able to delete for us)
@ -732,33 +682,20 @@ export const getMessagePropsByMessageId = createSelector(
sender === ourPubkey || (isPublic && (weAreAdmin || weAreModerator)) || false; sender === ourPubkey || (isPublic && (weAreAdmin || weAreModerator)) || false;
const isSenderAdmin = groupAdmins.includes(sender); const isSenderAdmin = groupAdmins.includes(sender);
const senderIsUs = sender === ourPubkey;
const authorName =
foundSenderConversation.nickname || foundSenderConversation.displayNameInProfile || null;
const authorProfileName = senderIsUs
? window.i18n('you')
: foundSenderConversation.nickname ||
foundSenderConversation.displayNameInProfile ||
window.i18n('anonymous');
const messageProps: MessageModelPropsWithConvoProps = { const messageProps: MessageModelPropsWithConvoProps = {
...foundMessageProps, ...foundMessageProps,
propsForMessage: { propsForMessage: {
...foundMessageProps.propsForMessage, ...foundMessageProps.propsForMessage,
isBlocked: !!foundMessageConversation.isBlocked, isBlocked: !!selectedConvo.isBlocked,
isPublic: !!isPublic, isPublic: !!isPublic,
isOpenGroupV2: !!isPublic,
isSenderAdmin, isSenderAdmin,
isDeletable, isDeletable,
isDeletableForEveryone, isDeletableForEveryone,
weAreAdmin, weAreAdmin,
conversationType: foundMessageConversation.type, conversationType: selectedConvo.type,
sender, sender,
authorAvatarPath: foundSenderConversation.avatarPath || null, isKickedFromGroup: selectedConvo.isKickedFromGroup || false,
isKickedFromGroup: foundMessageConversation.isKickedFromGroup || false,
authorProfileName: authorProfileName || 'Unknown',
authorName,
}, },
}; };
@ -766,30 +703,6 @@ export const getMessagePropsByMessageId = createSelector(
} }
); );
export const getMessageAvatarProps = createSelector(getMessagePropsByMessageId, (props):
| MessageAvatarSelectorProps
| undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const messageAvatarProps: MessageAvatarSelectorProps = {
lastMessageOfSeries: props.lastMessageOfSeries,
...pick(props.propsForMessage, [
'authorAvatarPath',
'authorName',
'sender',
'authorProfileName',
'conversationType',
'direction',
'isPublic',
'isSenderAdmin',
]),
};
return messageAvatarProps;
});
export const getMessageReactsProps = createSelector(getMessagePropsByMessageId, (props): export const getMessageReactsProps = createSelector(getMessagePropsByMessageId, (props):
| MessageReactsSelectorProps | MessageReactsSelectorProps
| undefined => { | undefined => {
@ -800,7 +713,6 @@ export const getMessageReactsProps = createSelector(getMessagePropsByMessageId,
const msgProps: MessageReactsSelectorProps = pick(props.propsForMessage, [ const msgProps: MessageReactsSelectorProps = pick(props.propsForMessage, [
'convoId', 'convoId',
'conversationType', 'conversationType',
'isPublic',
'reacts', 'reacts',
'serverId', 'serverId',
]); ]);
@ -825,46 +737,6 @@ export const getMessageReactsProps = createSelector(getMessagePropsByMessageId,
return msgProps; return msgProps;
}); });
export const getMessageLinkPreviewProps = createSelector(getMessagePropsByMessageId, (props):
| MessageLinkPreviewSelectorProps
| undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const msgProps: MessageLinkPreviewSelectorProps = pick(props.propsForMessage, [
'direction',
'attachments',
'previews',
]);
return msgProps;
});
export const getMessageQuoteProps = createSelector(getMessagePropsByMessageId, (props):
| MessageQuoteSelectorProps
| undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const msgProps: MessageQuoteSelectorProps = pick(props.propsForMessage, ['direction', 'quote']);
return msgProps;
});
export const getMessageStatusProps = createSelector(getMessagePropsByMessageId, (props):
| MessageStatusSelectorProps
| undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const msgProps: MessageStatusSelectorProps = pick(props.propsForMessage, ['direction', 'status']);
return msgProps;
});
export const getMessageTextProps = createSelector(getMessagePropsByMessageId, (props): export const getMessageTextProps = createSelector(getMessagePropsByMessageId, (props):
| MessageTextSelectorProps | MessageTextSelectorProps
| undefined => { | undefined => {
@ -883,11 +755,6 @@ export const getMessageTextProps = createSelector(getMessagePropsByMessageId, (p
return msgProps; return msgProps;
}); });
export const useMessageIsDeleted = (messageId: string): boolean => {
const props = useSelector((state: StateType) => getMessagePropsByMessageId(state, messageId));
return props?.propsForMessage.isDeleted || false;
};
/** /**
* TODO probably not something which should be memoized with createSelector as we rememoize it for each message (and override the previous one). Not sure what is the right way to do a lookup. But maybe something like having the messages as a record<id, message> and do a simple lookup to grab the details. * TODO probably not something which should be memoized with createSelector as we rememoize it for each message (and override the previous one). Not sure what is the right way to do a lookup. But maybe something like having the messages as a record<id, message> and do a simple lookup to grab the details.
* And the the sorting would be done in a memoized selector * And the the sorting would be done in a memoized selector
@ -915,32 +782,6 @@ export const getMessageContextMenuProps = createSelector(getMessagePropsByMessag
return msgProps; return msgProps;
}); });
export const getMessageAuthorProps = createSelector(getMessagePropsByMessageId, (props):
| MessageAuthorSelectorProps
| undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const msgProps: MessageAuthorSelectorProps = {
firstMessageOfSeries: props.firstMessageOfSeries,
...pick(props.propsForMessage, ['authorName', 'sender', 'authorProfileName', 'direction']),
};
return msgProps;
});
export const getMessageIsDeletable = createSelector(
getMessagePropsByMessageId,
(props): boolean => {
if (!props || isEmpty(props)) {
return false;
}
return props.propsForMessage.isDeletable;
}
);
export const getMessageAttachmentProps = createSelector(getMessagePropsByMessageId, (props): export const getMessageAttachmentProps = createSelector(getMessagePropsByMessageId, (props):
| MessageAttachmentSelectorProps | MessageAttachmentSelectorProps
| undefined => { | undefined => {
@ -1038,22 +879,14 @@ export const getGenericReadableMessageSelectorProps = createSelector(
} }
); );
export const getOldTopMessageId = createSelector( export const getOldTopMessageId = (state: StateType): string | null =>
getConversations, state.conversations.oldTopMessageId || null;
(state: ConversationsStateType): string | null => state.oldTopMessageId || null
);
// TODOLATER get rid of all the unneeded createSelector calls export const getOldBottomMessageId = (state: StateType): string | null =>
state.conversations.oldBottomMessageId || null;
export const getOldBottomMessageId = createSelector( export const getIsSelectedConvoInitialLoadingInProgress = (state: StateType): boolean =>
getConversations, Boolean(getSelectedConversation(state)?.isInitialFetchingInProgress);
(state: ConversationsStateType): string | null => state.oldBottomMessageId || null
);
export const getIsSelectedConvoInitialLoadingInProgress = createSelector(
getSelectedConversation,
(convo: ReduxConversationType | undefined): boolean => Boolean(convo?.isInitialFetchingInProgress)
);
export function getCurrentlySelectedConversationOutsideRedux() { export function getCurrentlySelectedConversationOutsideRedux() {
return window?.inboxStore?.getState().conversations.selectedConversation as string | undefined; return window?.inboxStore?.getState().conversations.selectedConversation as string | undefined;

@ -25,3 +25,5 @@ export {
UserConfigSelectors, UserConfigSelectors,
UserSelectors, UserSelectors,
}; };
export * from './messages';

@ -0,0 +1,112 @@
import { useSelector } from 'react-redux';
import { UserUtils } from '../../session/utils';
import {
MessageModelPropsWithConvoProps,
ReduxConversationType,
PropsForAttachment,
ReduxQuoteType,
LastMessageStatusType,
} from '../ducks/conversations';
import { StateType } from '../reducer';
import { getMessagePropsByMessageId } from './conversations';
const useMessageIdProps = (messageId: string | undefined) => {
return useSelector((state: StateType) => getMessagePropsByMessageId(state, messageId));
};
const useSenderConvoProps = (
msgProps: MessageModelPropsWithConvoProps | undefined
): ReduxConversationType | undefined => {
return useSelector((state: StateType) => {
const sender = msgProps?.propsForMessage.sender;
if (!sender) {
return undefined;
}
return state.conversations.conversationLookup[sender] || undefined;
});
};
export const useAuthorProfileName = (messageId: string): string | null => {
const msg = useMessageIdProps(messageId);
const senderProps = useSenderConvoProps(msg);
if (!msg || !senderProps) {
return null;
}
const senderIsUs = msg.propsForMessage.sender === UserUtils.getOurPubKeyStrFromCache();
const authorProfileName = senderIsUs
? window.i18n('you')
: senderProps.nickname || senderProps.displayNameInProfile || window.i18n('anonymous');
return authorProfileName || window.i18n('unknown');
};
export const useAuthorName = (messageId: string): string | null => {
const msg = useMessageIdProps(messageId);
const senderProps = useSenderConvoProps(msg);
if (!msg || !senderProps) {
return null;
}
const authorName = senderProps.nickname || senderProps.displayNameInProfile || null;
return authorName;
};
export const useAuthorAvatarPath = (messageId: string): string | null => {
const msg = useMessageIdProps(messageId);
const senderProps = useSenderConvoProps(msg);
if (!msg || !senderProps) {
return null;
}
return senderProps.avatarPath || null;
};
export const useMessageIsDeleted = (messageId: string): boolean => {
const props = useMessageIdProps(messageId);
return props?.propsForMessage.isDeleted || false;
};
export const useFirstMessageOfSeries = (messageId: string | undefined): boolean => {
return useMessageIdProps(messageId)?.firstMessageOfSeries || false;
};
export const useLastMessageOfSeries = (messageId: string | undefined): boolean => {
return useMessageIdProps(messageId)?.lastMessageOfSeries || false;
};
export const useMessageAuthor = (messageId: string | undefined): string | undefined => {
return useMessageIdProps(messageId)?.propsForMessage.sender;
};
export const useMessageDirection = (messageId: string | undefined): string | undefined => {
return useMessageIdProps(messageId)?.propsForMessage.direction;
};
export const useMessageLinkPreview = (messageId: string | undefined): any[] | undefined => {
return useMessageIdProps(messageId)?.propsForMessage.previews;
};
export const useMessageAttachments = (
messageId: string | undefined
): Array<PropsForAttachment> | undefined => {
return useMessageIdProps(messageId)?.propsForMessage.attachments;
};
export const useMessageSenderIsAdmin = (messageId: string | undefined): boolean => {
return useMessageIdProps(messageId)?.propsForMessage.isSenderAdmin || false;
};
export const useMessageIsDeletable = (messageId: string | undefined): boolean => {
return useMessageIdProps(messageId)?.propsForMessage.isDeletable || false;
};
export const useMessageQuote = (messageId: string | undefined): ReduxQuoteType | undefined => {
return useMessageIdProps(messageId)?.propsForMessage.quote;
};
export const useMessageStatus = (
messageId: string | undefined
): LastMessageStatusType | undefined => {
return useMessageIdProps(messageId)?.propsForMessage.status;
};

@ -10,13 +10,11 @@ import { getSelectedConversationKey } from './selectedConversation';
export const getSearch = (state: StateType): SearchStateType => state.search; export const getSearch = (state: StateType): SearchStateType => state.search;
export const getQuery = createSelector(getSearch, (state: SearchStateType): string => state.query); export const getQuery = (state: StateType): string => getSearch(state).query;
export const isSearching = createSelector(getSearch, (state: SearchStateType) => { export const isSearching = (state: StateType) => {
const { query } = state; return !!getSearch(state)?.query?.trim();
};
return Boolean(query && query.trim().length > 1);
});
export const getSearchResults = createSelector( export const getSearchResults = createSelector(
[getSearch, getConversationLookup, getSelectedConversationKey], [getSearch, getConversationLookup, getSelectedConversationKey],

Loading…
Cancel
Save