chore: merge conversation selectors into a single file

as the circular dependency was causing a createSelector called with
undefined issue on startup without user loggedin
pull/2784/head
Audric Ackermann 2 years ago
parent adf5574318
commit 1d9279c79c

@ -10,7 +10,7 @@ import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { VideoLoadingSpinner } from './InConversationCallContainer';
import { getSection } from '../../state/selectors/section';
import { SectionType } from '../../state/ducks/section';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../../state/selectors/conversations';
export const DraggableCallWindow = styled.div`
position: absolute;

@ -7,8 +7,10 @@ import {
declineConversationWithConfirm,
} from '../../interactions/conversationInteractions';
import { getConversationController } from '../../session/conversations';
import { hasSelectedConversationIncomingMessages } from '../../state/selectors/conversations';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import {
hasSelectedConversationIncomingMessages,
useSelectedConversationKey,
} from '../../state/selectors/conversations';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { ConversationRequestExplanation } from './SubtleNotification';

@ -16,6 +16,7 @@ import {
getOldBottomMessageId,
getOldTopMessageId,
getSortedMessagesTypesOfSelectedConversation,
useSelectedConversationKey,
} from '../../state/selectors/conversations';
import { MessageDateBreak } from './message/message-item/DateBreak';
import { GroupInvitation } from './message/message-item/GroupInvitation';
@ -24,7 +25,6 @@ import { Message } from './message/message-item/Message';
import { MessageRequestResponse } from './message/message-item/MessageRequestResponse';
import { CallNotification } from './message/message-item/notification-bubble/CallNotification';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
import { TimerNotification } from './TimerNotification';

@ -18,12 +18,12 @@ import { StateType } from '../../state/reducer';
import {
getQuotedMessageToAnimate,
getSelectedConversation,
getSelectedConversationKey,
getSortedMessagesOfSelectedConversation,
} from '../../state/selectors/conversations';
import { getSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { ConversationMessageRequestButtons } from './MessageRequestButtons';
import { SessionMessagesList } from './SessionMessagesList';
import { TypingBubble } from './TypingBubble';
import { ConversationMessageRequestButtons } from './MessageRequestButtons';
export type SessionMessageListProps = {
messageContainerRef: React.RefObject<HTMLDivElement>;

@ -5,7 +5,7 @@ import {
removeAllStagedAttachmentsInConversation,
removeStagedAttachmentInConversation,
} from '../../state/ducks/stagedAttachments';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../../state/selectors/conversations';
import {
areAllAttachmentsVisual,
AttachmentType,

@ -1,19 +1,17 @@
import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useIsIncomingRequest } from '../../hooks/useParamSelector';
import {
getSelectedCanWrite,
getSelectedHasMessages,
hasSelectedConversationIncomingMessages,
} from '../../state/selectors/conversations';
import {
getSelectedCanWrite,
useSelectedConversationKey,
useSelectedNicknameOrProfileNameOrShortenedPubkey,
useSelectedIsNoteToSelf,
} from '../../state/selectors/selectedConversation';
useSelectedNicknameOrProfileNameOrShortenedPubkey,
} from '../../state/selectors/conversations';
import { LocalizerKeys } from '../../types/LocalizerKeys';
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { useIsIncomingRequest } from '../../hooks/useParamSelector';
const Container = styled.div`
display: flex;

@ -3,7 +3,7 @@ import React from 'react';
import { TypingAnimation } from './TypingAnimation';
import styled from 'styled-components';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { useSelectedIsGroup } from '../../state/selectors/selectedConversation';
import { useSelectedIsGroup } from '../../state/selectors/conversations';
interface TypingBubbleProps {
conversationType: ConversationTypeEnum;

@ -1,30 +1,17 @@
import React from 'react';
import _, { debounce, isEmpty } from 'lodash';
import React from 'react';
import * as MIME from '../../../types/MIME';
import { SessionEmojiPanel, StyledEmojiPanel } from '../SessionEmojiPanel';
import { SessionRecording } from '../SessionRecording';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from '../SessionStagedLinkPreview';
import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import { Mention, MentionsInput, SuggestionDataItem } from 'react-mentions';
import autoBind from 'auto-bind';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { AttachmentType } from '../../../types/Attachment';
import { Mention, MentionsInput, SuggestionDataItem } from 'react-mentions';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { SettingsKey } from '../../../data/settings-key';
import { showLinkSharingConfirmationModalDialog } from '../../../interactions/conversationInteractions';
import { getConversationController } from '../../../session/conversations';
import { ToastUtils } from '../../../session/utils';
@ -34,32 +21,43 @@ import { StateType } from '../../../state/reducer';
import {
getMentionsInput,
getQuotedMessage,
getSelectedCanWrite,
getSelectedConversation,
getSelectedConversationKey,
} from '../../../state/selectors/conversations';
import { AttachmentUtil } from '../../../util';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { StagedAttachmentList } from '../StagedAttachmentList';
import { AttachmentType } from '../../../types/Attachment';
import { processNewAttachment } from '../../../types/MessageAttachment';
import { FixedBaseEmoji } from '../../../types/Reaction';
import { AttachmentUtil } from '../../../util';
import {
StagedAttachmentImportedType,
StagedPreviewImportedType,
} from '../../../util/attachmentsUtil';
import { LinkPreviews } from '../../../util/linkPreviews';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from '../SessionStagedLinkPreview';
import { StagedAttachmentList } from '../StagedAttachmentList';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import {
cleanMentions,
mentionsRegex,
renderUserMentionRow,
styleForCompositionBoxSuggestions,
} from './UserMentions';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import { LinkPreviews } from '../../../util/linkPreviews';
import styled from 'styled-components';
import { FixedBaseEmoji } from '../../../types/Reaction';
import {
getSelectedCanWrite,
getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation';
import { SettingsKey } from '../../../data/settings-key';
export interface ReplyingToMessageProps {
convoId: string;

@ -8,7 +8,7 @@ import {
import { closeMessageDetailsView, openRightPanel } from '../../../state/ducks/conversations';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../../../state/selectors/conversations';
import { Flex } from '../../basic/Flex';
import { ConversationHeaderMenu } from '../../menu/ConversationHeaderMenu';
import { AvatarHeader, BackButton, CallButton, TripleDotsMenu } from './ConversationHeaderItems';

@ -14,7 +14,7 @@ import {
useSelectedIsNoteToSelf,
useSelectedIsPrivate,
useSelectedIsPrivateFriend,
} from '../../../state/selectors/selectedConversation';
} from '../../../state/selectors/conversations';
const TripleDotContainer = styled.div`
user-select: none;

@ -5,7 +5,11 @@ import {
deleteMessagesByIdForEveryone,
} from '../../../interactions/conversations/unsendingInteractions';
import { resetSelectedMessageIds } from '../../../state/ducks/conversations';
import { getSelectedMessageIds } from '../../../state/selectors/conversations';
import {
getSelectedMessageIds,
useSelectedConversationKey,
useSelectedIsPublic,
} from '../../../state/selectors/conversations';
import {
SessionButton,
SessionButtonColor,
@ -13,10 +17,6 @@ import {
SessionButtonType,
} from '../../basic/SessionButton';
import { SessionIconButton } from '../../icon';
import {
useSelectedConversationKey,
useSelectedIsPublic,
} from '../../../state/selectors/selectedConversation';
export const SelectionOverlay = () => {
const selectedMessageIds = useSelector(getSelectedMessageIds);

@ -4,8 +4,8 @@ import { useDispatch, useSelector } from 'react-redux';
import { useConversationUsername } from '../../../hooks/useParamSelector';
import { closeRightPanel, openRightPanel } from '../../../state/ducks/conversations';
import { resetRightOverlayMode, setRightOverlayMode } from '../../../state/ducks/section';
import { isRightPanelShowing } from '../../../state/selectors/conversations';
import {
isRightPanelShowing,
useSelectedConversationKey,
useSelectedExpirationType,
useSelectedExpireTimer,
@ -16,7 +16,7 @@ import {
useSelectedMembers,
useSelectedNotificationSetting,
useSelectedSubscriberCount,
} from '../../../state/selectors/selectedConversation';
} from '../../../state/selectors/conversations';
import { ExpirationTimerOptions } from '../../../util/expiringMessages';
import { ConversationHeaderSubtitle } from './ConversationHeaderSubtitle';

@ -6,7 +6,7 @@ import moment from 'moment';
import formatFileSize from 'filesize';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import { MediaItemType } from '../../lightbox/LightboxGallery';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../../../state/selectors/conversations';
type Props = {
// Required

@ -8,10 +8,7 @@ import {
useMessageAuthor,
useMessageDirection,
} from '../../../../state/selectors';
import {
useSelectedIsGroup,
useSelectedIsPublic,
} from '../../../../state/selectors/selectedConversation';
import { useSelectedIsGroup, useSelectedIsPublic } from '../../../../state/selectors/conversations';
import { Flex } from '../../../basic/Flex';
import { ContactName } from '../../ContactName';

@ -21,7 +21,7 @@ import {
getSelectedCanWrite,
useSelectedConversationKey,
useSelectedIsPublic,
} from '../../../../state/selectors/selectedConversation';
} from '../../../../state/selectors/conversations';
import { Avatar, AvatarSize, CrownIcon } from '../../../avatar/Avatar';
// tslint:disable: use-simple-attributes

@ -23,14 +23,14 @@ import {
toggleSelectedMessageId,
} from '../../../../state/ducks/conversations';
import { StateType } from '../../../../state/reducer';
import { getMessageContextMenuProps } from '../../../../state/selectors/conversations';
import {
getMessageContextMenuProps,
useSelectedConversationKey,
useSelectedIsBlocked,
useSelectedIsPublic,
useSelectedWeAreAdmin,
useSelectedWeAreModerator,
} from '../../../../state/selectors/selectedConversation';
} from '../../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
import { Reactions } from '../../../../util/reactions';
import { SessionContextMenuContainer } from '../../../SessionContextMenuContainer';

@ -3,7 +3,7 @@ import React, { ReactElement, useEffect, useState } from 'react';
import styled from 'styled-components';
import { useMessageReactsPropsById } from '../../../../hooks/useParamSelector';
import { MessageRenderingProps } from '../../../../models/messageType';
import { useSelectedIsGroup } from '../../../../state/selectors/selectedConversation';
import { useSelectedIsGroup } from '../../../../state/selectors/conversations';
import { SortedReactionList } from '../../../../types/Reaction';
import { nativeEmojiData } from '../../../../util/emoji';
import { Flex } from '../../../basic/Flex';

@ -11,7 +11,7 @@ import { PubKey } from '../../../../session/types';
import {
useSelectedIsPrivate,
useSelectedIsPublic,
} from '../../../../state/selectors/selectedConversation';
} from '../../../../state/selectors/conversations';
import { ContactName } from '../../ContactName';
import { MessageBody } from './MessageBody';

@ -19,9 +19,9 @@ import {
getQuotedMessageToAnimate,
getShowScrollButton,
getYoungestMessageId,
useSelectedConversationKey,
} from '../../../../state/selectors/conversations';
import { getIsAppFocused } from '../../../../state/selectors/section';
import { useSelectedConversationKey } from '../../../../state/selectors/selectedConversation';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
export type ReadableMessageProps = {

@ -9,7 +9,7 @@ import {
useSelectedConversationKey,
useSelectedDisplayNameInProfile,
useSelectedNickname,
} from '../../../../../state/selectors/selectedConversation';
} from '../../../../../state/selectors/conversations';
import { LocalizerKeys } from '../../../../../types/LocalizerKeys';
import { SessionIconType } from '../../../../icon';
import { ExpirableReadableMessage } from '../ExpirableReadableMessage';

@ -20,8 +20,8 @@ import {
import { Constants } from '../../../../session';
import { closeRightPanel } from '../../../../state/ducks/conversations';
import { setRightOverlayMode } from '../../../../state/ducks/section';
import { isRightPanelShowing } from '../../../../state/selectors/conversations';
import {
isRightPanelShowing,
useSelectedConversationKey,
useSelectedDisplayNameInProfile,
useSelectedIsActive,
@ -32,7 +32,7 @@ import {
useSelectedIsPublic,
useSelectedSubscriberCount,
useSelectedWeAreAdmin,
} from '../../../../state/selectors/selectedConversation';
} from '../../../../state/selectors/conversations';
import { AttachmentTypeWithPath } from '../../../../types/Attachment';
import { getAbsoluteAttachmentPath } from '../../../../types/MessageAttachment';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';

@ -14,7 +14,7 @@ import {
useSelectedExpireTimer,
useSelectedIsGroup,
useSelectedWeAreAdmin,
} from '../../../../../state/selectors/selectedConversation';
} from '../../../../../state/selectors/conversations';
import {
DEFAULT_TIMER_OPTION,
DisappearingMessageConversationType,

@ -14,7 +14,7 @@ import {
import {
useSelectedIsPublic,
useSelectedWeAreModerator,
} from '../../state/selectors/selectedConversation';
} from '../../state/selectors/conversations';
import { SortedReactionList } from '../../types/Reaction';
import { nativeEmojiData } from '../../util/emoji';
import { Reactions } from '../../util/reactions';

@ -19,7 +19,7 @@ import {
useMentionedUs,
} from '../../../hooks/useParamSelector';
import { isSearching } from '../../../state/selectors/search';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../../../state/selectors/conversations';
import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu';
import { ContextConversationProvider, useConvoIdFromContext } from './ConvoIdContext';
import { ConversationListItemHeaderItem } from './HeaderItem';

@ -8,8 +8,10 @@ import { declineConversationWithoutConfirm } from '../../../interactions/convers
import { forceSyncConfigurationNowIfNeeded } from '../../../session/utils/sync/syncUtils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { resetOverlayMode } from '../../../state/ducks/section';
import { getConversationRequestsIds } from '../../../state/selectors/conversations';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import {
getConversationRequestsIds,
useSelectedConversationKey,
} from '../../../state/selectors/conversations';
import { SessionButton, SessionButtonColor } from '../../basic/SessionButton';
import { SpacerLG } from '../../basic/Text';
import { ConversationListItem } from '../conversation-list-item/ConversationListItem';

@ -9,7 +9,7 @@ import { Lightbox } from './Lightbox';
import { useDispatch } from 'react-redux';
import useKey from 'react-use/lib/useKey';
import { showLightBox } from '../../state/ducks/conversations';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../../state/selectors/conversations';
import { MIME } from '../../types';
import { AttachmentTypeWithPath } from '../../types/Attachment';
import { saveAttachmentToDisk } from '../../util/attachmentsUtil';

@ -6,7 +6,7 @@ import {
useSelectedConversationKey,
useSelectedIsPrivate,
useSelectedIsPrivateFriend,
} from '../../state/selectors/selectedConversation';
} from '../../state/selectors/conversations';
import { ContextConversationProvider } from '../leftpane/conversation-list-item/ConvoIdContext';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';

@ -52,7 +52,7 @@ import {
useSelectedIsPrivate,
useSelectedIsPrivateFriend,
useSelectedNotificationSetting,
} from '../../state/selectors/selectedConversation';
} from '../../state/selectors/conversations';
import { SessionButtonColor } from '../basic/SessionButton';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
import {

@ -4,12 +4,15 @@ import {
hasValidIncomingRequestValues,
hasValidOutgoingRequestValues,
} from '../models/conversation';
import { CONVERSATION } from '../session/constants';
import { PubKey } from '../session/types';
import { UserUtils } from '../session/utils';
import { StateType } from '../state/reducer';
import { isPrivateAndFriend } from '../state/selectors/selectedConversation';
import { CONVERSATION } from '../session/constants';
import { getMessageExpirationProps, getMessageReactsProps } from '../state/selectors/conversations';
import {
getMessageExpirationProps,
getMessageReactsProps,
isPrivateAndFriend,
} from '../state/selectors/conversations';
export function useAvatarPath(convoId: string | undefined) {
const convoProps = useConversationPropsById(convoId);

@ -10,7 +10,7 @@ import {
removeVideoEventsListener,
} from '../session/utils/calling/CallManager';
import { getCallIsInFullScreen, getHasOngoingCallWithPubkey } from '../state/selectors/call';
import { useSelectedConversationKey } from '../state/selectors/selectedConversation';
import { useSelectedConversationKey } from '../state/selectors/conversations';
export function useVideoCallEventsListener(uniqueId: string, onSame: boolean) {
const selectedConversationKey = useSelectedConversationKey();

@ -92,7 +92,6 @@ export async function createClosedGroup(groupName: string, members: Array<string
/**
* Sends a group invite message to each member of the group.
* @returns Array of promises for group invite messages sent to group members.
*/
async function sendToGroupMembers(
listOfMembers: Array<string>,

@ -2,8 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { CallStateType, CallStatusEnum } from '../ducks/call';
import { ConversationsStateType, ReduxConversationType } from '../ducks/conversations';
import { StateType } from '../reducer';
import { getConversations } from './conversations';
import { getSelectedConversationKey } from './selectedConversation';
import { getConversations, getSelectedConversationKey } from './conversations';
const getCallState = (state: StateType): CallStateType => state.call;

@ -22,7 +22,11 @@ import { MessageContextMenuSelectorProps } from '../../components/conversation/m
import { MessageTextSelectorProps } from '../../components/conversation/message/message-content/MessageText';
import { GenericReadableMessageSelectorProps } from '../../components/conversation/message/message-item/GenericReadableMessage';
import { hasValidIncomingRequestValues } from '../../models/conversation';
import { CONVERSATION_PRIORITIES, isOpenOrClosedGroup } from '../../models/conversationAttributes';
import {
CONVERSATION_PRIORITIES,
ConversationTypeEnum,
isOpenOrClosedGroup,
} from '../../models/conversationAttributes';
import { getConversationController } from '../../session/conversations';
import { UserUtils } from '../../session/utils';
import { LocalizerType } from '../../types/Util';
@ -30,10 +34,17 @@ import { BlockedNumberController } from '../../util';
import { Storage } from '../../util/storage';
import { getIntl } from './user';
import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash';
import { filter, isEmpty, isNumber, isString, pick, sortBy } from 'lodash';
import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions';
import { getSelectedConversationKey } from './selectedConversation';
import { getModeratorsOutsideRedux } from './sogsRoomInfo';
import {
getCanWrite,
getModerators,
getModeratorsOutsideRedux,
getSubscriberCount,
} from './sogsRoomInfo';
import { useSelector } from 'react-redux';
import { PubKey } from '../../session/types';
import { DisappearingMessageConversationSetting } from '../../util/expiringMessages';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -329,6 +340,10 @@ const _getGlobalUnreadCount = (sortedConversations: Array<ReduxConversationType>
return globalUnreadCount;
};
export const getSelectedConversationKey = (state: StateType): string | undefined => {
return state.conversations.selectedConversation;
};
export const _getSortedConversations = (
lookup: ConversationLookupType,
comparator: (left: ReduxConversationType, right: ReduxConversationType) => number
@ -918,3 +933,337 @@ export const getIsSelectedConvoInitialLoadingInProgress = (state: StateType): bo
export function getCurrentlySelectedConversationOutsideRedux() {
return window?.inboxStore?.getState().conversations.selectedConversation as string | undefined;
}
/**
* Selected conversation selectors & hooks
*
*/
/**
* Returns the formatted text for notification setting.
*/
const getCurrentNotificationSettingText = (state: StateType): string | undefined => {
if (!state) {
return undefined;
}
const currentNotificationSetting = getSelectedConversation(state)?.currentNotificationSetting;
switch (currentNotificationSetting) {
case 'all':
return window.i18n('notificationForConvo_all');
case 'mentions_only':
return window.i18n('notificationForConvo_mentions_only');
case 'disabled':
return window.i18n('notificationForConvo_disabled');
default:
return window.i18n('notificationForConvo_all');
}
};
const getIsSelectedPrivate = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isPrivate) || false;
};
const getIsSelectedBlocked = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isBlocked) || false;
};
const getSelectedIsApproved = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isApproved) || false;
};
const getSelectedApprovedMe = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.didApproveMe) || false;
};
/**
* Returns true if the currently selected conversation is active (has an active_at field > 0)
*/
const getIsSelectedActive = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.activeAt) || false;
};
const getIsSelectedNoteToSelf = (state: StateType): boolean => {
return getSelectedConversation(state)?.isMe || false;
};
/**
* Returns true if the current conversation selected is a public group and false otherwise.
*/
export const getSelectedConversationIsPublic = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isPublic) || false;
};
/**
* Returns true if the current conversation selected can be typed into
*/
export function getSelectedCanWrite(state: StateType) {
const selectedConvoPubkey = getSelectedConversationKey(state);
if (!selectedConvoPubkey) {
return false;
}
const selectedConvo = getSelectedConversation(state);
if (!selectedConvo) {
return false;
}
const canWriteSogs = getCanWrite(state, selectedConvoPubkey);
const { isBlocked, isKickedFromGroup, left, isPublic } = selectedConvo;
return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWriteSogs));
}
/**
* Returns true if the current conversation selected is a group conversation.
* Returns false if the current conversation selected is not a group conversation, or none are selected
*/
const getSelectedConversationIsGroup = (state: StateType): boolean => {
const selected = getSelectedConversation(state);
if (!selected || !selected.type) {
return false;
}
return selected.type ? isOpenOrClosedGroup(selected.type) : false;
};
/**
* Returns true if the current conversation selected is a closed group and false otherwise.
*/
export const isClosedGroupConversation = (state: StateType): boolean => {
const selected = getSelectedConversation(state);
if (!selected) {
return false;
}
return (
(selected.type === ConversationTypeEnum.GROUP && !selected.isPublic) ||
selected.type === ConversationTypeEnum.GROUPV3 ||
false
);
};
const getGroupMembers = (state: StateType): Array<string> => {
const selected = getSelectedConversation(state);
if (!selected) {
return [];
}
return selected.members || [];
};
const getSelectedSubscriberCount = (state: StateType): number | undefined => {
const convo = getSelectedConversation(state);
if (!convo) {
return undefined;
}
return getSubscriberCount(state, convo.id);
};
// ============== SELECTORS RELEVANT TO SELECTED/OPENED CONVERSATION ==============
export function useSelectedConversationKey() {
return useSelector(getSelectedConversationKey);
}
export function useSelectedIsGroup() {
return useSelector(getSelectedConversationIsGroup);
}
export function useSelectedIsPublic() {
return useSelector(getSelectedConversationIsPublic);
}
export function useSelectedIsPrivate() {
return useSelector(getIsSelectedPrivate);
}
export function useSelectedIsBlocked() {
return useSelector(getIsSelectedBlocked);
}
export function useSelectedIsApproved() {
return useSelector(getSelectedIsApproved);
}
export function useSelectedApprovedMe() {
return useSelector(getSelectedApprovedMe);
}
/**
* Returns true if the given arguments corresponds to a private contact which is approved both sides. i.e. a friend.
*/
export function isPrivateAndFriend({
approvedMe,
isApproved,
isPrivate,
}: {
isPrivate: boolean;
isApproved: boolean;
approvedMe: boolean;
}) {
return isPrivate && isApproved && approvedMe;
}
/**
* Returns true if the selected conversation is private and is approved both sides
*/
export function useSelectedIsPrivateFriend() {
const isPrivate = useSelectedIsPrivate();
const isApproved = useSelectedIsApproved();
const approvedMe = useSelectedApprovedMe();
return isPrivateAndFriend({ isPrivate, isApproved, approvedMe });
}
export function useSelectedIsActive() {
return useSelector(getIsSelectedActive);
}
export function useSelectedIsNoteToSelf() {
return useSelector(getIsSelectedNoteToSelf);
}
export function useSelectedMembers() {
return useSelector(getGroupMembers);
}
export function useSelectedSubscriberCount() {
return useSelector(getSelectedSubscriberCount);
}
export function useSelectedNotificationSetting() {
return useSelector(getCurrentNotificationSettingText);
}
export function useSelectedIsKickedFromGroup() {
return useSelector(
(state: StateType) => Boolean(getSelectedConversation(state)?.isKickedFromGroup) || false
);
}
export function useSelectedExpireTimer(): number | undefined {
return useSelector((state: StateType) => getSelectedConversation(state)?.expireTimer);
}
export function useSelectedExpirationType(): string | undefined {
return useSelector((state: StateType) => getSelectedConversation(state)?.expirationType);
}
export function useSelectedIsLeft() {
return useSelector((state: StateType) => Boolean(getSelectedConversation(state)?.left) || false);
}
export function useSelectedNickname() {
return useSelector((state: StateType) => getSelectedConversation(state)?.nickname);
}
export function useSelectedDisplayNameInProfile() {
return useSelector((state: StateType) => getSelectedConversation(state)?.displayNameInProfile);
}
/**
* For a private chat, this returns the (xxxx...xxxx) shortened pubkey
* If this is a private chat, but somehow, we have no pubkey, this returns the localized `anonymous` string
* Otherwise, this returns the localized `unknown` string
*/
export function useSelectedShortenedPubkeyOrFallback() {
const isPrivate = useSelectedIsPrivate();
const selected = useSelectedConversationKey();
if (isPrivate && selected) {
return PubKey.shorten(selected);
}
if (isPrivate) {
return window.i18n('anonymous');
}
return window.i18n('unknown');
}
/**
* That's a very convoluted way to say "nickname or profile name or shortened pubkey or ("Anonymous" or "unknown" depending on the type of conversation).
* This also returns the localized "Note to Self" if the conversation is the note to self.
*/
export function useSelectedNicknameOrProfileNameOrShortenedPubkey() {
const nickname = useSelectedNickname();
const profileName = useSelectedDisplayNameInProfile();
const shortenedPubkey = useSelectedShortenedPubkeyOrFallback();
const isMe = useSelectedIsNoteToSelf();
if (isMe) {
return window.i18n('noteToSelf');
}
return nickname || profileName || shortenedPubkey;
}
export function useSelectedWeAreAdmin() {
return useSelector((state: StateType) => getSelectedConversation(state)?.weAreAdmin || false);
}
export const getSelectedConversationExpirationModes = createSelector(
getSelectedConversation,
(convo: ReduxConversationType | undefined) => {
if (!convo) {
return null;
}
let modes = DisappearingMessageConversationSetting;
// TODO legacy messages support will be removed in a future release
// TODO remove legacy mode
modes = modes.slice(0, -1);
// Note to Self and Closed Groups only support deleteAfterSend
const isClosedGroup = !convo.isPrivate && !convo.isPublic;
if (convo?.isMe || isClosedGroup) {
modes = [modes[0], modes[2]];
}
const modesWithDisabledState: Record<string, boolean> = {};
if (modes && modes.length > 1) {
modes.forEach(mode => {
modesWithDisabledState[mode] = isClosedGroup ? !convo.weAreAdmin : false;
});
}
return modesWithDisabledState;
}
);
// TODO legacy messages support will be removed in a future release
export const getSelectedConversationExpirationModesWithLegacy = createSelector(
getSelectedConversation,
(convo: ReduxConversationType | undefined) => {
// this just won't happen
if (!convo) {
return null;
}
let modes = DisappearingMessageConversationSetting;
// Note to Self and Closed Groups only support deleteAfterSend and legacy modes
const isClosedGroup = !convo.isPrivate && !convo.isPublic;
if (convo?.isMe || isClosedGroup) {
modes = [modes[0], ...modes.slice(2)];
}
// Legacy mode is the 2nd option in the UI
modes = [modes[0], modes[modes.length - 1], ...modes.slice(1, modes.length - 1)];
// TODO it would be nice to type those with something else that string but it causes a lot of issues
const modesWithDisabledState: Record<string, boolean> = {};
// The new modes are disabled by default
if (modes && modes.length > 1) {
modes.forEach(mode => {
modesWithDisabledState[mode] = Boolean(
(mode !== 'legacy' && mode !== 'off') || (isClosedGroup && !convo.weAreAdmin)
);
});
}
return modesWithDisabledState;
}
);
/**
* Only for communities.
* @returns true if the selected convo is a community and we are one of the moderators
*/
export function useSelectedWeAreModerator() {
// TODO might be something to memoize let's see
const isPublic = useSelectedIsPublic();
const selectedConvoKey = useSelectedConversationKey();
const us = UserUtils.getOurPubKeyStrFromCache();
const mods = useSelector((state: StateType) => getModerators(state, selectedConvoKey));
const weAreModerator = mods.includes(us);
return isPublic && isString(selectedConvoKey) && weAreModerator;
}

@ -1,344 +0,0 @@
import { isString } from 'lodash';
import { useSelector } from 'react-redux';
import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes';
import { PubKey } from '../../session/types';
import { ReduxConversationType } from '../ducks/conversations';
import { StateType } from '../reducer';
import { createSelector } from '@reduxjs/toolkit';
import { UserUtils } from '../../session/utils';
import { DisappearingMessageConversationSetting } from '../../util/expiringMessages';
import { getSelectedConversation } from './conversations';
import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo';
/**
* Returns the formatted text for notification setting.
*/
const getCurrentNotificationSettingText = (state: StateType): string | undefined => {
if (!state) {
return undefined;
}
const currentNotificationSetting = getSelectedConversation(state)?.currentNotificationSetting;
switch (currentNotificationSetting) {
case 'all':
return window.i18n('notificationForConvo_all');
case 'mentions_only':
return window.i18n('notificationForConvo_mentions_only');
case 'disabled':
return window.i18n('notificationForConvo_disabled');
default:
return window.i18n('notificationForConvo_all');
}
};
const getIsSelectedPrivate = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isPrivate) || false;
};
const getIsSelectedBlocked = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isBlocked) || false;
};
const getSelectedIsApproved = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isApproved) || false;
};
const getSelectedApprovedMe = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.didApproveMe) || false;
};
/**
* Returns true if the currently selected conversation is active (has an active_at field > 0)
*/
const getIsSelectedActive = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.activeAt) || false;
};
const getIsSelectedNoteToSelf = (state: StateType): boolean => {
return getSelectedConversation(state)?.isMe || false;
};
export const getSelectedConversationKey = (state: StateType): string | undefined => {
return state.conversations.selectedConversation;
};
/**
* Returns true if the current conversation selected is a public group and false otherwise.
*/
export const getSelectedConversationIsPublic = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isPublic) || false;
};
/**
* Returns true if the current conversation selected can be typed into
*/
export function getSelectedCanWrite(state: StateType) {
const selectedConvoPubkey = getSelectedConversationKey(state);
if (!selectedConvoPubkey) {
return false;
}
const selectedConvo = getSelectedConversation(state);
if (!selectedConvo) {
return false;
}
const canWriteSogs = getCanWrite(state, selectedConvoPubkey);
const { isBlocked, isKickedFromGroup, left, isPublic } = selectedConvo;
return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWriteSogs));
}
/**
* Returns true if the current conversation selected is a group conversation.
* Returns false if the current conversation selected is not a group conversation, or none are selected
*/
const getSelectedConversationIsGroup = (state: StateType): boolean => {
const selected = getSelectedConversation(state);
if (!selected || !selected.type) {
return false;
}
return selected.type ? isOpenOrClosedGroup(selected.type) : false;
};
/**
* Returns true if the current conversation selected is a closed group and false otherwise.
*/
export const isClosedGroupConversation = (state: StateType): boolean => {
const selected = getSelectedConversation(state);
if (!selected) {
return false;
}
return (
(selected.type === ConversationTypeEnum.GROUP && !selected.isPublic) ||
selected.type === ConversationTypeEnum.GROUPV3 ||
false
);
};
const getGroupMembers = (state: StateType): Array<string> => {
const selected = getSelectedConversation(state);
if (!selected) {
return [];
}
return selected.members || [];
};
const getSelectedSubscriberCount = (state: StateType): number | undefined => {
const convo = getSelectedConversation(state);
if (!convo) {
return undefined;
}
return getSubscriberCount(state, convo.id);
};
// ============== SELECTORS RELEVANT TO SELECTED/OPENED CONVERSATION ==============
export function useSelectedConversationKey() {
return useSelector(getSelectedConversationKey);
}
export function useSelectedIsGroup() {
return useSelector(getSelectedConversationIsGroup);
}
export function useSelectedIsPublic() {
return useSelector(getSelectedConversationIsPublic);
}
export function useSelectedIsPrivate() {
return useSelector(getIsSelectedPrivate);
}
export function useSelectedIsBlocked() {
return useSelector(getIsSelectedBlocked);
}
export function useSelectedIsApproved() {
return useSelector(getSelectedIsApproved);
}
export function useSelectedApprovedMe() {
return useSelector(getSelectedApprovedMe);
}
/**
* Returns true if the given arguments corresponds to a private contact which is approved both sides. i.e. a friend.
*/
export function isPrivateAndFriend({
approvedMe,
isApproved,
isPrivate,
}: {
isPrivate: boolean;
isApproved: boolean;
approvedMe: boolean;
}) {
return isPrivate && isApproved && approvedMe;
}
/**
* Returns true if the selected conversation is private and is approved both sides
*/
export function useSelectedIsPrivateFriend() {
const isPrivate = useSelectedIsPrivate();
const isApproved = useSelectedIsApproved();
const approvedMe = useSelectedApprovedMe();
return isPrivateAndFriend({ isPrivate, isApproved, approvedMe });
}
export function useSelectedIsActive() {
return useSelector(getIsSelectedActive);
}
export function useSelectedIsNoteToSelf() {
return useSelector(getIsSelectedNoteToSelf);
}
export function useSelectedMembers() {
return useSelector(getGroupMembers);
}
export function useSelectedSubscriberCount() {
return useSelector(getSelectedSubscriberCount);
}
export function useSelectedNotificationSetting() {
return useSelector(getCurrentNotificationSettingText);
}
export function useSelectedIsKickedFromGroup() {
return useSelector(
(state: StateType) => Boolean(getSelectedConversation(state)?.isKickedFromGroup) || false
);
}
export function useSelectedExpireTimer(): number | undefined {
return useSelector((state: StateType) => getSelectedConversation(state)?.expireTimer);
}
export function useSelectedExpirationType(): string | undefined {
return useSelector((state: StateType) => getSelectedConversation(state)?.expirationType);
}
export function useSelectedIsLeft() {
return useSelector((state: StateType) => Boolean(getSelectedConversation(state)?.left) || false);
}
export function useSelectedNickname() {
return useSelector((state: StateType) => getSelectedConversation(state)?.nickname);
}
export function useSelectedDisplayNameInProfile() {
return useSelector((state: StateType) => getSelectedConversation(state)?.displayNameInProfile);
}
/**
* For a private chat, this returns the (xxxx...xxxx) shortened pubkey
* If this is a private chat, but somehow, we have no pubkey, this returns the localized `anonymous` string
* Otherwise, this returns the localized `unknown` string
*/
export function useSelectedShortenedPubkeyOrFallback() {
const isPrivate = useSelectedIsPrivate();
const selected = useSelectedConversationKey();
if (isPrivate && selected) {
return PubKey.shorten(selected);
}
if (isPrivate) {
return window.i18n('anonymous');
}
return window.i18n('unknown');
}
/**
* That's a very convoluted way to say "nickname or profile name or shortened pubkey or ("Anonymous" or "unknown" depending on the type of conversation).
* This also returns the localized "Note to Self" if the conversation is the note to self.
*/
export function useSelectedNicknameOrProfileNameOrShortenedPubkey() {
const nickname = useSelectedNickname();
const profileName = useSelectedDisplayNameInProfile();
const shortenedPubkey = useSelectedShortenedPubkeyOrFallback();
const isMe = useSelectedIsNoteToSelf();
if (isMe) {
return window.i18n('noteToSelf');
}
return nickname || profileName || shortenedPubkey;
}
export function useSelectedWeAreAdmin() {
return useSelector((state: StateType) => getSelectedConversation(state)?.weAreAdmin || false);
}
export const getSelectedConversationExpirationModes = createSelector(
getSelectedConversation,
(convo: ReduxConversationType | undefined) => {
if (!convo) {
return null;
}
let modes = DisappearingMessageConversationSetting;
// TODO legacy messages support will be removed in a future release
// TODO remove legacy mode
modes = modes.slice(0, -1);
// Note to Self and Closed Groups only support deleteAfterSend
const isClosedGroup = !convo.isPrivate && !convo.isPublic;
if (convo?.isMe || isClosedGroup) {
modes = [modes[0], modes[2]];
}
const modesWithDisabledState: Record<string, boolean> = {};
if (modes && modes.length > 1) {
modes.forEach(mode => {
modesWithDisabledState[mode] = isClosedGroup ? !convo.weAreAdmin : false;
});
}
return modesWithDisabledState;
}
);
// TODO legacy messages support will be removed in a future release
export const getSelectedConversationExpirationModesWithLegacy = createSelector(
getSelectedConversation,
(convo: ReduxConversationType | undefined) => {
// this just won't happen
if (!convo) {
return null;
}
let modes = DisappearingMessageConversationSetting;
// Note to Self and Closed Groups only support deleteAfterSend and legacy modes
const isClosedGroup = !convo.isPrivate && !convo.isPublic;
if (convo?.isMe || isClosedGroup) {
modes = [modes[0], ...modes.slice(2)];
}
// Legacy mode is the 2nd option in the UI
modes = [modes[0], modes[modes.length - 1], ...modes.slice(1, modes.length - 1)];
// TODO it would be nice to type those with something else that string but it causes a lot of issues
const modesWithDisabledState: Record<string, boolean> = {};
// The new modes are disabled by default
if (modes && modes.length > 1) {
modes.forEach(mode => {
modesWithDisabledState[mode] = Boolean(
(mode !== 'legacy' && mode !== 'off') || (isClosedGroup && !convo.weAreAdmin)
);
});
}
return modesWithDisabledState;
}
);
/**
* Only for communities.
* @returns true if the selected convo is a community and we are one of the moderators
*/
export function useSelectedWeAreModerator() {
// TODO might be something to memoize let's see
const isPublic = useSelectedIsPublic();
const selectedConvoKey = useSelectedConversationKey();
const us = UserUtils.getOurPubKeyStrFromCache();
const mods = useSelector((state: StateType) => getModerators(state, selectedConvoKey));
const weAreModerator = mods.includes(us);
return isPublic && isString(selectedConvoKey) && weAreModerator;
}

@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { StagedAttachmentType } from '../../components/conversation/composition/CompositionBox';
import { StagedAttachmentsStateType } from '../ducks/stagedAttachments';
import { StateType } from '../reducer';
import { getSelectedConversationKey } from './selectedConversation';
import { getSelectedConversationKey } from './conversations';
export const getStagedAttachmentsState = (state: StateType): StagedAttachmentsStateType =>
state.stagedAttachments;

@ -7,12 +7,12 @@ import {
getIsSelectedConvoInitialLoadingInProgress,
getLightBoxOptions,
getSelectedConversation,
getSelectedConversationKey,
getSelectedMessageIds,
getSortedMessagesOfSelectedConversation,
isMessageDetailView,
isRightPanelShowing,
} from '../selectors/conversations';
import { getSelectedConversationKey } from '../selectors/selectedConversation';
import { getStagedAttachmentsForCurrentConversation } from '../selectors/stagedAttachments';
import { getTheme } from '../selectors/theme';
import { getOurNumber } from '../selectors/user';

Loading…
Cancel
Save