From abd146c4cafdd4f1ee1593b11fd374dcdbc96956 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 16 Dec 2021 15:13:04 +1100 Subject: [PATCH] use React Provider for convoListItem (#2088) this is to avoid passing down the prop to all the components --- .../message-item/GroupUpdateMessage.tsx | 8 +- .../leftpane/ConversationListItem.tsx | 400 ------------------ .../leftpane/LeftPaneContactSection.tsx | 2 +- .../leftpane/LeftPaneMessageSection.tsx | 2 +- .../ConversationListItem.tsx | 142 +++++++ .../conversation-list-item/HeaderItem.tsx | 116 +++++ .../conversation-list-item/MessageItem.tsx | 61 +++ .../conversation-list-item/MessageRequest.tsx | 64 +++ .../conversation-list-item/UserItem.tsx | 34 ++ .../overlay/OverlayMessageRequest.tsx | 2 +- .../menu/ConversationListItemContextMenu.tsx | 36 +- ts/components/menu/Menu.tsx | 4 +- ts/components/search/SearchResults.tsx | 2 +- ts/components/search/UserSearchResults.tsx | 2 +- ts/hooks/useParamSelector.ts | 66 ++- 15 files changed, 468 insertions(+), 473 deletions(-) delete mode 100644 ts/components/leftpane/ConversationListItem.tsx create mode 100644 ts/components/leftpane/conversation-list-item/ConversationListItem.tsx create mode 100644 ts/components/leftpane/conversation-list-item/HeaderItem.tsx create mode 100644 ts/components/leftpane/conversation-list-item/MessageItem.tsx create mode 100644 ts/components/leftpane/conversation-list-item/MessageRequest.tsx create mode 100644 ts/components/leftpane/conversation-list-item/UserItem.tsx diff --git a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx index 86b61020f..fa5099002 100644 --- a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx +++ b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx @@ -8,7 +8,7 @@ import _ from 'underscore'; import { NotificationBubble } from './notification-bubble/NotificationBubble'; import { ReadableMessage } from './ReadableMessage'; import { arrayContainsUsOnly } from '../../../../models/message'; -import { useConversationsUsernameOrFull } from '../../../../hooks/useParamSelector'; +import { useConversationsUsernameWithQuoteOrFullPubkey } from '../../../../hooks/useParamSelector'; // This component is used to display group updates in the conversation view. @@ -16,7 +16,7 @@ const ChangeItemJoined = (added: Array): string => { if (!added.length) { throw new Error('Group update add is missing contacts'); } - const names = useConversationsUsernameOrFull(added); + const names = useConversationsUsernameWithQuoteOrFullPubkey(added); const joinKey = added.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup'; return window.i18n(joinKey, [names.join(', ')]); }; @@ -25,7 +25,7 @@ const ChangeItemKicked = (kicked: Array): string => { if (!kicked.length) { throw new Error('Group update kicked is missing contacts'); } - const names = useConversationsUsernameOrFull(kicked); + const names = useConversationsUsernameWithQuoteOrFullPubkey(kicked); if (arrayContainsUsOnly(kicked)) { return window.i18n('youGotKickedFromGroup'); @@ -40,7 +40,7 @@ const ChangeItemLeft = (left: Array): string => { throw new Error('Group update remove is missing contacts'); } - const names = useConversationsUsernameOrFull(left); + const names = useConversationsUsernameWithQuoteOrFullPubkey(left); if (arrayContainsUsOnly(left)) { return window.i18n('youLeftTheGroup'); diff --git a/ts/components/leftpane/ConversationListItem.tsx b/ts/components/leftpane/ConversationListItem.tsx deleted file mode 100644 index 4c2a813b3..000000000 --- a/ts/components/leftpane/ConversationListItem.tsx +++ /dev/null @@ -1,400 +0,0 @@ -import React, { useCallback } from 'react'; -import classNames from 'classnames'; -import { isEmpty } from 'lodash'; -import { contextMenu } from 'react-contexify'; - -import { Avatar, AvatarSize } from '../avatar/Avatar'; -import { Timestamp } from '../conversation/Timestamp'; -import { ContactName } from '../conversation/ContactName'; -import { TypingAnimation } from '../conversation/TypingAnimation'; - -import { createPortal } from 'react-dom'; -import styled from 'styled-components'; -import { PubKey } from '../../session/types'; -import { - LastMessageType, - openConversationWithMessages, - ReduxConversationType, -} from '../../state/ducks/conversations'; -import _ from 'underscore'; -import { useDispatch, useSelector } from 'react-redux'; -import { SectionType } from '../../state/ducks/section'; -import { getFocusedSection } from '../../state/selectors/section'; -import { ConversationNotificationSettingType } from '../../models/conversation'; -import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils'; -import { updateUserDetailsModal } from '../../state/ducks/modalDialog'; -import { approveConversation, blockConvoById } from '../../interactions/conversationInteractions'; -import { useAvatarPath, useConversationUsername, useIsMe } from '../../hooks/useParamSelector'; -import { MessageBody } from '../conversation/message/message-content/MessageBody'; -import { OutgoingMessageStatus } from '../conversation/message/message-content/OutgoingMessageStatus'; -import { SessionIcon, SessionIconButton } from '../icon'; -import { MemoConversationListItemContextMenu } from '../menu/ConversationListItemContextMenu'; - -// tslint:disable-next-line: no-empty-interface -export interface ConversationListItemProps extends ReduxConversationType {} - -export const StyledConversationListItemIconWrapper = styled.div` - svg { - margin: 0px 2px; - } - - display: flex; - flex-direction: row; -`; - -type PropsHousekeeping = { - style?: Object; - isMessageRequest?: boolean; -}; -// tslint:disable: use-simple-attributes - -type Props = ConversationListItemProps & PropsHousekeeping; - -const Portal = ({ children }: { children: any }) => { - return createPortal(children, document.querySelector('.inbox.index') as Element); -}; - -const HeaderItem = (props: { - unreadCount: number; - mentionedUs: boolean; - activeAt?: number; - conversationId: string; - isPinned: boolean; - currentNotificationSetting: ConversationNotificationSettingType; -}) => { - const { - unreadCount, - mentionedUs, - activeAt, - isPinned, - conversationId, - currentNotificationSetting, - } = props; - - let atSymbol = null; - let unreadCountDiv = null; - if (unreadCount > 0) { - atSymbol = mentionedUs ?

@

: null; - unreadCountDiv =

{unreadCount}

; - } - - const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message; - - const pinIcon = - isMessagesSection && isPinned ? ( - - ) : null; - - const NotificationSettingIcon = () => { - if (!isMessagesSection) { - return null; - } - - switch (currentNotificationSetting) { - case 'all': - return null; - case 'disabled': - return ( - - ); - case 'mentions_only': - return ( - - ); - default: - return null; - } - }; - - return ( -
-
0 ? 'module-conversation-list-item__header__name--with-unread' : null - )} - > - -
- - - {pinIcon} - - - {unreadCountDiv} - {atSymbol} - -
0 ? 'module-conversation-list-item__header__date--has-unread' : null - )} - > - -
-
- ); -}; - -const UserItem = (props: { conversationId: string }) => { - const { conversationId } = props; - - const shortenedPubkey = PubKey.shorten(conversationId); - const isMe = useIsMe(conversationId); - const username = useConversationUsername(conversationId); - - const displayedPubkey = username ? shortenedPubkey : conversationId; - const displayName = isMe ? window.i18n('noteToSelf') : username; - - let shouldShowPubkey = false; - if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) { - shouldShowPubkey = true; - } - - return ( -
- -
- ); -}; - -const MessageItem = (props: { - lastMessage?: LastMessageType; - isTyping: boolean; - unreadCount: number; - isMessageRequest: boolean; - conversationId: string; -}) => { - const { lastMessage, isTyping, unreadCount } = props; - - if (!lastMessage && !isTyping) { - return null; - } - const text = lastMessage?.text || ''; - - if (isEmpty(text)) { - return null; - } - - return ( -
-
0 ? 'module-conversation-list-item__message__text--has-unread' : null - )} - > - {isTyping ? ( - - ) : ( - - )} -
- - {lastMessage && lastMessage.status && !props.isMessageRequest ? ( - - ) : null} -
- ); -}; - -const AvatarItem = (props: { conversationId: string; isPrivate: boolean }) => { - const { isPrivate, conversationId } = props; - const userName = useConversationUsername(conversationId); - const avatarPath = useAvatarPath(conversationId); - const dispatch = useDispatch(); - - function onPrivateAvatarClick() { - dispatch( - updateUserDetailsModal({ - conversationId: conversationId, - userName: userName || '', - authorAvatarPath: avatarPath, - }) - ); - } - - return ( -
- -
- ); -}; - -const RejectMessageRequestButton = ({ conversationId }: { conversationId: string }) => { - /** - * Removes conversation from requests list, - * adds ID to block list, syncs the block with linked devices. - */ - const handleConversationBlock = async () => { - await blockConvoById(conversationId); - await forceSyncConfigurationNowIfNeeded(); - }; - return ( - - ); -}; - -const ApproveMessageRequestButton = ({ conversationId }: { conversationId: string }) => { - return ( - { - await approveConversation(conversationId); - }} - backgroundColor="var(--color-accent)" - iconColor="var(--color-foreground-primary)" - iconPadding="var(--margins-xs)" - borderRadius="2px" - /> - ); -}; - -const MessageRequestButtons = ({ - conversationId, - isMessageRequest, -}: { - conversationId: string; - isMessageRequest: boolean; -}) => { - if (!isMessageRequest) { - return null; - } - - return ( - <> - - - - ); -}; - -// tslint:disable: max-func-body-length -const ConversationListItem = (props: Props) => { - const { - activeAt, - unreadCount, - id: conversationId, - isSelected, - isBlocked, - style, - mentionedUs, - isMe, - isPinned, - isTyping, - lastMessage, - hasNickname, - isKickedFromGroup, - left, - type, - isPublic, - avatarPath, - isPrivate, - currentNotificationSetting, - weAreAdmin, - isMessageRequest, - } = props; - const triggerId = `conversation-item-${conversationId}-ctxmenu`; - const key = `conversation-item-${conversationId}`; - - const openConvo = useCallback( - async (e: React.MouseEvent) => { - // mousedown is invoked sooner than onClick, but for both right and left click - if (e.button === 0) { - await openConversationWithMessages({ conversationKey: conversationId }); - } - }, - [conversationId] - ); - - return ( -
-
{ - e.stopPropagation(); - e.preventDefault(); - }} - onContextMenu={(e: any) => { - contextMenu.show({ - id: triggerId, - event: e, - }); - }} - style={style} - className={classNames( - 'module-conversation-list-item', - unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, - unreadCount && unreadCount > 0 && mentionedUs - ? 'module-conversation-list-item--mentioned-us' - : null, - isSelected ? 'module-conversation-list-item--is-selected' : null, - isBlocked ? 'module-conversation-list-item--is-blocked' : null - )} - > - -
- - -
-
- - - -
- ); -}; - -export const MemoConversationListItemWithDetails = React.memo(ConversationListItem, _.isEqual); diff --git a/ts/components/leftpane/LeftPaneContactSection.tsx b/ts/components/leftpane/LeftPaneContactSection.tsx index eff84a39f..11b4c0a1f 100644 --- a/ts/components/leftpane/LeftPaneContactSection.tsx +++ b/ts/components/leftpane/LeftPaneContactSection.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { MemoConversationListItemWithDetails } from './ConversationListItem'; +import { MemoConversationListItemWithDetails } from './conversation-list-item/ConversationListItem'; import { AutoSizer, List } from 'react-virtualized'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; import { useSelector } from 'react-redux'; diff --git a/ts/components/leftpane/LeftPaneMessageSection.tsx b/ts/components/leftpane/LeftPaneMessageSection.tsx index e3a1c694d..15f879c77 100644 --- a/ts/components/leftpane/LeftPaneMessageSection.tsx +++ b/ts/components/leftpane/LeftPaneMessageSection.tsx @@ -3,7 +3,7 @@ import { AutoSizer, List } from 'react-virtualized'; import { ConversationListItemProps, MemoConversationListItemWithDetails, -} from './ConversationListItem'; +} from './conversation-list-item/ConversationListItem'; import { ReduxConversationType } from '../../state/ducks/conversations'; import { SearchResults, SearchResultsProps } from '../search/SearchResults'; import { UserUtils } from '../../session/utils'; diff --git a/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx b/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx new file mode 100644 index 000000000..bbbd100a3 --- /dev/null +++ b/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx @@ -0,0 +1,142 @@ +import React, { useCallback, useContext } from 'react'; +import classNames from 'classnames'; +import { contextMenu } from 'react-contexify'; + +import { Avatar, AvatarSize } from '../../avatar/Avatar'; + +import { createPortal } from 'react-dom'; +import { + openConversationWithMessages, + ReduxConversationType, +} from '../../../state/ducks/conversations'; +import _ from 'underscore'; +import { useDispatch } from 'react-redux'; +import { updateUserDetailsModal } from '../../../state/ducks/modalDialog'; + +import { + useAvatarPath, + useConversationUsername, + useIsPrivate, +} from '../../../hooks/useParamSelector'; +import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu'; +import { HeaderItem } from './HeaderItem'; +import { MessageItem } from './MessageItem'; + +// tslint:disable-next-line: no-empty-interface +export type ConversationListItemProps = Pick< + ReduxConversationType, + 'unreadCount' | 'id' | 'isSelected' | 'isBlocked' | 'mentionedUs' | 'unreadCount' | 'profileName' +>; + +/** + * This React context is used to share deeply in the tree of the ConversationListItem what is the ID we are currently rendering. + * This is to avoid passing the prop to all the subtree component + */ +export const ContextConversationId = React.createContext(''); + +type PropsHousekeeping = { + style?: Object; + isMessageRequest?: boolean; +}; +// tslint:disable: use-simple-attributes + +type Props = ConversationListItemProps & PropsHousekeeping; + +const Portal = ({ children }: { children: any }) => { + return createPortal(children, document.querySelector('.inbox.index') as Element); +}; + +const AvatarItem = () => { + const conversationId = useContext(ContextConversationId); + const userName = useConversationUsername(conversationId); + const isPrivate = useIsPrivate(conversationId); + const avatarPath = useAvatarPath(conversationId); + const dispatch = useDispatch(); + + function onPrivateAvatarClick() { + dispatch( + updateUserDetailsModal({ + conversationId: conversationId, + userName: userName || '', + authorAvatarPath: avatarPath, + }) + ); + } + + return ( +
+ +
+ ); +}; + +// tslint:disable: max-func-body-length +const ConversationListItem = (props: Props) => { + const { + unreadCount, + id: conversationId, + isSelected, + isBlocked, + style, + mentionedUs, + isMessageRequest, + } = props; + const triggerId = `conversation-item-${conversationId}-ctxmenu`; + const key = `conversation-item-${conversationId}`; + + const openConvo = useCallback( + async (e: React.MouseEvent) => { + // mousedown is invoked sooner than onClick, but for both right and left click + if (e.button === 0) { + await openConversationWithMessages({ conversationKey: conversationId }); + } + }, + [conversationId] + ); + + return ( + +
+
{ + e.stopPropagation(); + e.preventDefault(); + }} + onContextMenu={(e: any) => { + contextMenu.show({ + id: triggerId, + event: e, + }); + }} + style={style} + className={classNames( + 'module-conversation-list-item', + unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, + unreadCount && unreadCount > 0 && mentionedUs + ? 'module-conversation-list-item--mentioned-us' + : null, + isSelected ? 'module-conversation-list-item--is-selected' : null, + isBlocked ? 'module-conversation-list-item--is-blocked' : null + )} + > + +
+ + +
+
+ + + +
+
+ ); +}; + +export const MemoConversationListItemWithDetails = React.memo(ConversationListItem, _.isEqual); diff --git a/ts/components/leftpane/conversation-list-item/HeaderItem.tsx b/ts/components/leftpane/conversation-list-item/HeaderItem.tsx new file mode 100644 index 000000000..8589f9be0 --- /dev/null +++ b/ts/components/leftpane/conversation-list-item/HeaderItem.tsx @@ -0,0 +1,116 @@ +import classNames from 'classnames'; +import React, { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import styled from 'styled-components'; +import { useConversationPropsById, useIsPinned } from '../../../hooks/useParamSelector'; +import { SectionType } from '../../../state/ducks/section'; +import { getFocusedSection } from '../../../state/selectors/section'; +import { Timestamp } from '../../conversation/Timestamp'; +import { SessionIcon } from '../../icon'; +import { ContextConversationId } from './ConversationListItem'; +import { UserItem } from './UserItem'; + +const NotificationSettingIcon = (props: { isMessagesSection: boolean }) => { + const convoSetting = useSelector(useConversationPropsById)?.currentNotificationSetting; + + if (!props.isMessagesSection) { + return null; + } + + switch (convoSetting) { + case 'all': + return null; + case 'disabled': + return ( + + ); + case 'mentions_only': + return ( + + ); + default: + return null; + } +}; + +const StyledConversationListItemIconWrapper = styled.div` + svg { + margin: 0px 2px; + } + + display: flex; + flex-direction: row; +`; + +function useHeaderItemProps(conversationId: string) { + const convoProps = useConversationPropsById(conversationId); + if (!convoProps) { + return null; + } + return { + isPinned: !!convoProps.isPinned, + mentionedUs: convoProps.mentionedUs || false, + unreadCount: convoProps.unreadCount || 0, + activeAt: convoProps.activeAt, + }; +} + +const ListItemIcons = () => { + const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message; + const conversationId = useContext(ContextConversationId); + const isPinned = useIsPinned(conversationId); + + const pinIcon = + isMessagesSection && isPinned ? ( + + ) : null; + return ( + + {pinIcon} + + + ); +}; + +export const HeaderItem = () => { + const conversationId = useContext(ContextConversationId); + + const convoProps = useHeaderItemProps(conversationId); + if (!convoProps) { + return null; + } + const { unreadCount, mentionedUs, activeAt } = convoProps; + + let atSymbol = null; + let unreadCountDiv = null; + if (unreadCount > 0) { + atSymbol = mentionedUs ?

@

: null; + unreadCountDiv =

{unreadCount}

; + } + + return ( +
+
0 ? 'module-conversation-list-item__header__name--with-unread' : null + )} + > + +
+ + + {unreadCountDiv} + {atSymbol} + +
0 ? 'module-conversation-list-item__header__date--has-unread' : null + )} + > + +
+
+ ); +}; diff --git a/ts/components/leftpane/conversation-list-item/MessageItem.tsx b/ts/components/leftpane/conversation-list-item/MessageItem.tsx new file mode 100644 index 000000000..344c79378 --- /dev/null +++ b/ts/components/leftpane/conversation-list-item/MessageItem.tsx @@ -0,0 +1,61 @@ +import classNames from 'classnames'; +import React, { useContext } from 'react'; +import { isEmpty } from 'underscore'; +import { useConversationPropsById } from '../../../hooks/useParamSelector'; +import { MessageBody } from '../../conversation/message/message-content/MessageBody'; +import { OutgoingMessageStatus } from '../../conversation/message/message-content/OutgoingMessageStatus'; +import { TypingAnimation } from '../../conversation/TypingAnimation'; +import { ContextConversationId } from './ConversationListItem'; +import { MessageRequestButtons } from './MessageRequest'; + +function useMessageItemProps(convoId: string) { + const convoProps = useConversationPropsById(convoId); + if (!convoProps) { + return null; + } + return { + isTyping: !!convoProps.isTyping, + lastMessage: convoProps.lastMessage, + + unreadCount: convoProps.unreadCount || 0, + }; +} + +export const MessageItem = (props: { isMessageRequest: boolean }) => { + const conversationId = useContext(ContextConversationId); + const convoProps = useMessageItemProps(conversationId); + if (!convoProps) { + return null; + } + const { lastMessage, isTyping, unreadCount } = convoProps; + + if (!lastMessage && !isTyping) { + return null; + } + const text = lastMessage?.text || ''; + + if (isEmpty(text)) { + return null; + } + + return ( +
+
0 ? 'module-conversation-list-item__message__text--has-unread' : null + )} + > + {isTyping ? ( + + ) : ( + + )} +
+ + {lastMessage && lastMessage.status && !props.isMessageRequest ? ( + + ) : null} +
+ ); +}; diff --git a/ts/components/leftpane/conversation-list-item/MessageRequest.tsx b/ts/components/leftpane/conversation-list-item/MessageRequest.tsx new file mode 100644 index 000000000..d33129aec --- /dev/null +++ b/ts/components/leftpane/conversation-list-item/MessageRequest.tsx @@ -0,0 +1,64 @@ +import React, { useContext } from 'react'; +import { + approveConversation, + blockConvoById, +} from '../../../interactions/conversationInteractions'; +import { forceSyncConfigurationNowIfNeeded } from '../../../session/utils/syncUtils'; +import { SessionIconButton } from '../../icon'; +import { ContextConversationId } from './ConversationListItem'; + +const RejectMessageRequestButton = () => { + const conversationId = useContext(ContextConversationId); + + /** + * Removes conversation from requests list, + * adds ID to block list, syncs the block with linked devices. + */ + const handleConversationBlock = async () => { + await blockConvoById(conversationId); + await forceSyncConfigurationNowIfNeeded(); + }; + return ( + + ); +}; + +const ApproveMessageRequestButton = () => { + const conversationId = useContext(ContextConversationId); + + return ( + { + await approveConversation(conversationId); + }} + backgroundColor="var(--color-accent)" + iconColor="var(--color-foreground-primary)" + iconPadding="var(--margins-xs)" + borderRadius="2px" + /> + ); +}; + +export const MessageRequestButtons = ({ isMessageRequest }: { isMessageRequest: boolean }) => { + if (!isMessageRequest) { + return null; + } + + return ( + <> + + + + ); +}; diff --git a/ts/components/leftpane/conversation-list-item/UserItem.tsx b/ts/components/leftpane/conversation-list-item/UserItem.tsx new file mode 100644 index 000000000..6acdf8e2e --- /dev/null +++ b/ts/components/leftpane/conversation-list-item/UserItem.tsx @@ -0,0 +1,34 @@ +import React, { useContext } from 'react'; +import { useConversationUsername, useIsMe } from '../../../hooks/useParamSelector'; +import { PubKey } from '../../../session/types'; +import { ContactName } from '../../conversation/ContactName'; +import { ContextConversationId } from './ConversationListItem'; + +export const UserItem = () => { + const conversationId = useContext(ContextConversationId); + + const shortenedPubkey = PubKey.shorten(conversationId); + const isMe = useIsMe(conversationId); + const username = useConversationUsername(conversationId); + + const displayedPubkey = username ? shortenedPubkey : conversationId; + const displayName = isMe ? window.i18n('noteToSelf') : username; + + let shouldShowPubkey = false; + if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) { + shouldShowPubkey = true; + } + + return ( +
+ +
+ ); +}; diff --git a/ts/components/leftpane/overlay/OverlayMessageRequest.tsx b/ts/components/leftpane/overlay/OverlayMessageRequest.tsx index 8b04320c9..98e6b4f5b 100644 --- a/ts/components/leftpane/overlay/OverlayMessageRequest.tsx +++ b/ts/components/leftpane/overlay/OverlayMessageRequest.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { SpacerLG } from '../../basic/Text'; import { useDispatch, useSelector } from 'react-redux'; import { getConversationRequests } from '../../../state/selectors/conversations'; -import { MemoConversationListItemWithDetails } from '../ConversationListItem'; +import { MemoConversationListItemWithDetails } from '../conversation-list-item/ConversationListItem'; import styled from 'styled-components'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../../basic/SessionButton'; import { OverlayHeader } from './OverlayHeader'; diff --git a/ts/components/menu/ConversationListItemContextMenu.tsx b/ts/components/menu/ConversationListItemContextMenu.tsx index 934467803..cdc45d396 100644 --- a/ts/components/menu/ConversationListItemContextMenu.tsx +++ b/ts/components/menu/ConversationListItemContextMenu.tsx @@ -1,11 +1,13 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { animation, Menu } from 'react-contexify'; import _ from 'underscore'; -import { useAvatarPath, useConversationUsername } from '../../hooks/useParamSelector'; import { - ConversationNotificationSettingType, - ConversationTypeEnum, -} from '../../models/conversation'; + useAvatarPath, + useConversationPropsById, + useConversationUsername, +} from '../../hooks/useParamSelector'; +import { ConversationTypeEnum } from '../../models/conversation'; +import { ContextConversationId } from '../leftpane/conversation-list-item/ConversationListItem'; import { getBanMenuItem, @@ -25,26 +27,18 @@ import { } from './Menu'; export type PropsContextConversationItem = { - conversationId: string; triggerId: string; - type: ConversationTypeEnum; - isMe: boolean; - isPublic: boolean; - isPrivate: boolean; - isBlocked: boolean; - hasNickname: boolean; - isKickedFromGroup: boolean; - left: boolean; - theme?: any; - currentNotificationSetting: ConversationNotificationSettingType; - avatarPath: string | null; - weAreAdmin?: boolean; }; const ConversationListItemContextMenu = (props: PropsContextConversationItem) => { + const conversationId = useContext(ContextConversationId); + + const itemMenuProps = useConversationPropsById(conversationId); + const { triggerId } = props; + if (!itemMenuProps) { + return null; + } const { - conversationId, - triggerId, isBlocked, isMe, isPublic, @@ -55,7 +49,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) => currentNotificationSetting, isPrivate, weAreAdmin, - } = props; + } = itemMenuProps; const isGroup = type === 'group'; diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx index 6e55c3b1b..da980d193 100644 --- a/ts/components/menu/Menu.tsx +++ b/ts/components/menu/Menu.tsx @@ -449,7 +449,7 @@ export function getNotificationForConvoMenuItem({ left: boolean | undefined; isBlocked: boolean | undefined; isPrivate: boolean | undefined; - currentNotificationSetting: ConversationNotificationSettingType; + currentNotificationSetting: ConversationNotificationSettingType | undefined; conversationId: string; }): JSX.Element | null { if (showNotificationConvo(Boolean(isKickedFromGroup), Boolean(left), Boolean(isBlocked))) { @@ -461,7 +461,7 @@ export function getNotificationForConvoMenuItem({ ).map((n: ConversationNotificationSettingType) => { // do this separately so typescript's compiler likes it const keyToUse: LocalizerKeys = - n === 'all' + n === 'all' || !n ? 'notificationForConvo_all' : n === 'disabled' ? 'notificationForConvo_disabled' diff --git a/ts/components/search/SearchResults.tsx b/ts/components/search/SearchResults.tsx index 09b352694..2d81e98dc 100644 --- a/ts/components/search/SearchResults.tsx +++ b/ts/components/search/SearchResults.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ConversationListItemProps, MemoConversationListItemWithDetails, -} from '../leftpane/ConversationListItem'; +} from '../leftpane/conversation-list-item/ConversationListItem'; export type SearchResultsProps = { contacts: Array; diff --git a/ts/components/search/UserSearchResults.tsx b/ts/components/search/UserSearchResults.tsx index c83eddab2..c0c1c891e 100644 --- a/ts/components/search/UserSearchResults.tsx +++ b/ts/components/search/UserSearchResults.tsx @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import { PubKey } from '../../session/types'; -import { ConversationListItemProps } from '../leftpane/ConversationListItem'; +import { ConversationListItemProps } from '../leftpane/conversation-list-item/ConversationListItem'; export type Props = { contacts: Array; diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index 5cf8ce2f3..2049a0669 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -3,13 +3,9 @@ import { PubKey } from '../session/types'; import { UserUtils } from '../session/utils'; import { StateType } from '../state/reducer'; -export function useAvatarPath(pubkey: string | undefined) { - return useSelector((state: StateType) => { - if (!pubkey) { - return null; - } - return state.conversations.conversationLookup[pubkey]?.avatarPath || null; - }); +export function useAvatarPath(convoId: string | undefined) { + const convoProps = useConversationPropsById(convoId); + return convoProps?.avatarPath || null; } export function useOurAvatarPath() { @@ -20,39 +16,25 @@ export function useOurAvatarPath() { * * @returns convo.profileName || convo.name || convo.id or undefined if the convo is not found */ -export function useConversationUsername(pubkey?: string) { - return useSelector((state: StateType) => { - if (!pubkey) { - return undefined; - } - const convo = state.conversations.conversationLookup[pubkey]; - if (!convo) { - return pubkey; - } - return convo?.profileName || convo?.name || convo.id; - }); +export function useConversationUsername(convoId?: string) { + const convoProps = useConversationPropsById(convoId); + + return convoProps?.profileName || convoProps?.name || convoId; } /** * Returns either the nickname, profileName, or the shorten pubkey */ -export function useConversationUsernameOrShorten(pubkey?: string) { - return useSelector((state: StateType) => { - if (!pubkey) { - return undefined; - } - const convo = state.conversations.conversationLookup[pubkey]; - if (!convo) { - return PubKey.shorten(pubkey); - } - return convo?.profileName || convo?.name || PubKey.shorten(convo.id); - }); +export function useConversationUsernameOrShorten(convoId?: string) { + const convoProps = useConversationPropsById(convoId); + + return convoProps?.profileName || convoProps?.name || (convoId && PubKey.shorten(convoId)); } /** - * Returns either the nickname, profileName, or the shorten of the pubkeys given + * Returns either the nickname, the profileName, in '"' or the full pubkeys given */ -export function useConversationsUsernameOrFull(pubkeys: Array) { +export function useConversationsUsernameWithQuoteOrFullPubkey(pubkeys: Array) { return useSelector((state: StateType) => { return pubkeys.map(pubkey => { if (pubkey === UserUtils.getOurPubKeyStrFromCache() || pubkey.toLowerCase() === 'you') { @@ -73,16 +55,18 @@ export function useIsMe(pubkey?: string) { } export function useIsClosedGroup(convoId?: string) { - return useSelector((state: StateType) => { - if (!convoId) { - return false; - } - const convo = state.conversations.conversationLookup[convoId]; - if (!convo) { - return false; - } - return (convo.isGroup && !convo.isPublic) || false; - }); + const convoProps = useConversationPropsById(convoId); + return (convoProps && convoProps.isGroup && !convoProps.isPublic) || false; +} + +export function useIsPrivate(convoId?: string) { + const convoProps = useConversationPropsById(convoId); + return Boolean(convoProps && convoProps.isPrivate); +} + +export function useIsPinned(convoId?: string) { + const convoProps = useConversationPropsById(convoId); + return Boolean(convoProps && convoProps.isPinned); } export function useConversationPropsById(convoId?: string) {