diff --git a/images/icons/v2/compose-24.svg b/images/icons/v2/compose-24.svg new file mode 100644 index 000000000000..0b1eaa75999b --- /dev/null +++ b/images/icons/v2/compose-24.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 0485fd0e01fc..f457c8983136 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -2660,14 +2660,19 @@ button.ConversationDetails__action-button { height: calc(#{$header-height} + var(--title-bar-drag-area-height)); width: 100%; - padding-left: 24px; - padding-right: 24px; + padding-left: 16px; + padding-right: 16px; padding-top: var(--title-bar-drag-area-height); display: flex; + justify-content: space-between; flex-direction: row; align-items: center; + .module-left-pane--width-narrow & { + justify-content: center; + } + & > * { margin-right: 12px; @@ -2681,6 +2686,10 @@ button.ConversationDetails__action-button { &--container { position: relative; + + .module-left-pane--width-narrow & { + display: none; + } } &--badged { @@ -2693,14 +2702,10 @@ button.ConversationDetails__action-button { top: 0; right: 0; } - - .module-left-pane--width-narrow & { - display: none; - } } &__compose-icon { - $icon: '../images/icons/v2/compose-outline-24.svg'; + $icon: '../images/icons/v2/compose-24.svg'; width: 24px; height: 24px; diff --git a/stylesheets/components/LeftPaneDialog.scss b/stylesheets/components/LeftPaneDialog.scss index f404a34c38b7..ec6b37049b1c 100644 --- a/stylesheets/components/LeftPaneDialog.scss +++ b/stylesheets/components/LeftPaneDialog.scss @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only @keyframes progress-animation { @@ -21,12 +21,12 @@ align-items: center; background: $default-background-color; color: $default-text-color; + cursor: inherit; display: flex; - width: 100%; min-height: 64px; padding: 12px 18px; user-select: none; - cursor: inherit; + width: 100%; .module-left-pane--width-narrow & { padding-left: 36px; diff --git a/stylesheets/components/LeftPaneSearchInput.scss b/stylesheets/components/LeftPaneSearchInput.scss index ecce62425d50..4b8ee818b8eb 100644 --- a/stylesheets/components/LeftPaneSearchInput.scss +++ b/stylesheets/components/LeftPaneSearchInput.scss @@ -1,31 +1,29 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only .LeftPaneSearchInput { - -webkit-app-region: no-drag; - display: flex; - flex-direction: row; - flex-grow: 1; position: relative; + margin: 0 16px; + margin-bottom: 16px; &__input { @include font-body-2; - @include rounded-corners; border: none; + border-radius: 8px; height: 28px; padding-left: 30px; padding-right: 5px; width: 100%; @include light-theme { - background-color: $color-gray-05; + background-color: $color-gray-15; + border: solid 1px $color-gray-15; color: $color-gray-90; - border: solid 1px $color-gray-02; } @include dark-theme { + background-color: $color-gray-65; + border: solid 1px $color-gray-65; color: $color-gray-05; - background-color: $color-gray-95; - border: solid 1px $color-gray-80; } &:placeholder { @@ -75,10 +73,10 @@ top: 3px; @include light-theme { - background-color: $color-gray-15; + background-color: $color-gray-25; } @include dark-theme { - background-color: $color-gray-75; + background-color: $color-gray-80; } &__x-button { diff --git a/stylesheets/components/SearchInput.scss b/stylesheets/components/SearchInput.scss index 75462c3b7b44..a9c719924df2 100644 --- a/stylesheets/components/SearchInput.scss +++ b/stylesheets/components/SearchInput.scss @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only .module-SearchInput { @@ -12,14 +12,14 @@ @include font-body-2; @include light-theme { - background-color: $color-black-alpha-08; - border: solid 1px $color-gray-02; + background-color: $color-gray-15; + border: solid 1px $color-gray-15; color: $color-gray-90; } @include dark-theme { - background: $color-gray-90; - border: solid 1px $color-gray-90; + background-color: $color-gray-65; + border: solid 1px $color-gray-65; color: $color-gray-05; } diff --git a/ts/components/LeftPane.stories.tsx b/ts/components/LeftPane.stories.tsx index 59714fcf0e62..2c795769a507 100644 --- a/ts/components/LeftPane.stories.tsx +++ b/ts/components/LeftPane.stories.tsx @@ -35,6 +35,13 @@ const defaultConversations: Array = [ }), ]; +const defaultSearchProps = { + searchConversation: undefined, + searchDisabled: false, + searchTerm: 'hello', + startSearchCounter: 0, +}; + const defaultGroups: Array = [ getDefaultConversation({ id: 'biking-group', @@ -72,18 +79,19 @@ const pinnedConversations: Array = [ ]; const defaultModeSpecificProps = { + ...defaultSearchProps, mode: LeftPaneMode.Inbox as const, pinnedConversations, conversations: defaultConversations, archivedConversations: defaultArchivedConversations, isAboutToSearchInAConversation: false, - startSearchCounter: 0, }; const emptySearchResultsGroup = { isLoading: false, results: [] }; const useProps = (overrideProps: Partial = {}): PropsType => ({ cantAddContactToGroup: action('cantAddContactToGroup'), + clearConversationSearch: action('clearConversationSearch'), clearGroupCreationError: action('clearGroupCreationError'), clearSearch: action('clearSearch'), closeCantAddContactToGroupModal: action('closeCantAddContactToGroupModal'), @@ -177,12 +185,12 @@ story.add('Inbox: no conversations', () => ( @@ -192,12 +200,12 @@ story.add('Inbox: only pinned conversations', () => ( @@ -207,12 +215,12 @@ story.add('Inbox: only non-pinned conversations', () => ( @@ -222,12 +230,12 @@ story.add('Inbox: only archived conversations', () => ( @@ -237,12 +245,12 @@ story.add('Inbox: pinned and archived conversations', () => ( @@ -252,12 +260,12 @@ story.add('Inbox: non-pinned and archived conversations', () => ( @@ -267,12 +275,12 @@ story.add('Inbox: pinned and non-pinned conversations', () => ( @@ -288,11 +296,11 @@ story.add('Search: no results when searching everywhere', () => ( ( ( ( ( ( }, contactResults: { isLoading: true }, messageResults: { isLoading: true }, - searchTerm: 'foo bar', primarySendsSms: false, }, })} @@ -367,6 +375,7 @@ story.add('Search: has conversations and contacts, but not messages', () => ( ( }, contactResults: { isLoading: false, results: defaultConversations }, messageResults: { isLoading: false, results: [] }, - searchTerm: 'foo bar', primarySendsSms: false, }, })} @@ -385,6 +393,7 @@ story.add('Search: all results', () => ( ( { id: 'msg2', conversationId: 'bar' }, ], }, - searchTerm: 'foo bar', primarySendsSms: false, }, })} @@ -614,12 +622,13 @@ story.add('Captcha dialog: required', () => ( ( ( ( })} /> )); + +story.add('Searching Conversation', () => ( + +)); diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index 0ecb70f11543..133192503233 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -96,6 +96,7 @@ export type PropsType = { // Action Creators cantAddContactToGroup: (conversationId: string) => void; + clearConversationSearch: () => void; clearGroupCreationError: () => void; clearSearch: () => void; closeCantAddContactToGroupModal: () => void; @@ -151,6 +152,7 @@ export const LeftPane: React.FC = ({ cantAddContactToGroup, challengeStatus, crashReportCount, + clearConversationSearch, clearGroupCreationError, clearSearch, closeCantAddContactToGroupModal, @@ -461,7 +463,9 @@ export const LeftPane: React.FC = ({ ]); const preRowsNode = helper.getPreRowsNode({ + clearConversationSearch, clearGroupCreationError, + clearSearch, closeCantAddContactToGroupModal, closeMaximumGroupSizeModal, closeRecommendedGroupSizeModal, @@ -470,14 +474,11 @@ export const LeftPane: React.FC = ({ composeSaveAvatarToDisk, createGroup, i18n, - setComposeGroupAvatar, - setComposeGroupName, - setComposeGroupExpireTimer, - toggleComposeEditingAvatar, - onChangeComposeSearchTerm: event => { - setComposeSearchTerm(event.target.value); - }, removeSelectedContact: toggleConversationInChooseMembers, + setComposeGroupAvatar, + setComposeGroupExpireTimer, + setComposeGroupName, + toggleComposeEditingAvatar, }); const footerContents = helper.getFooterContents({ createGroup, @@ -550,14 +551,21 @@ export const LeftPane: React.FC = ({ {/* eslint-enable jsx-a11y/no-static-element-interactions */}
{helper.getHeaderContents({ - clearSearch, i18n, showInbox, startComposing, showChooseGroupMembers, - updateSearchTerm, }) || renderMainHeader()}
+ {helper.getSearchInput({ + clearConversationSearch, + clearSearch, + i18n, + onChangeComposeSearchTerm: event => { + setComposeSearchTerm(event.target.value); + }, + updateSearchTerm, + })} {renderExpiredBuildDialog({ containerWidthBreakpoint: widthBreakpoint })} {renderRelinkDialog({ containerWidthBreakpoint: widthBreakpoint })} {renderNetworkStatus({ containerWidthBreakpoint: widthBreakpoint })} diff --git a/ts/components/LeftPaneMainSearchInput.tsx b/ts/components/LeftPaneMainSearchInput.tsx new file mode 100644 index 000000000000..e749b822a52e --- /dev/null +++ b/ts/components/LeftPaneMainSearchInput.tsx @@ -0,0 +1,92 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React, { useEffect, useRef } from 'react'; + +import type { LocalizerType } from '../types/Util'; +import type { ConversationType } from '../state/ducks/conversations'; +import { LeftPaneSearchInput } from './LeftPaneSearchInput'; +import { usePrevious } from '../hooks/usePrevious'; + +export type PropsType = { + clearConversationSearch: () => void; + clearSearch: () => void; + disabled?: boolean; + i18n: LocalizerType; + searchConversation: undefined | ConversationType; + searchTerm: string; + startSearchCounter: number; + updateSearchTerm: (searchTerm: string) => void; +}; + +export const LeftPaneMainSearchInput = ({ + clearConversationSearch, + clearSearch, + disabled, + i18n, + searchConversation, + searchTerm, + startSearchCounter, + updateSearchTerm, +}: PropsType): JSX.Element => { + const inputRef = useRef(null); + + const prevSearchConversationId = usePrevious( + undefined, + searchConversation?.id + ); + const prevSearchCounter = usePrevious(startSearchCounter, startSearchCounter); + + useEffect(() => { + // When user chooses to search in a given conversation we focus the field for them + if ( + searchConversation && + searchConversation.id !== prevSearchConversationId + ) { + inputRef.current?.focus(); + } + // When user chooses to start a new search, we focus the field + if (startSearchCounter !== prevSearchCounter) { + inputRef.current?.select(); + } + }, [ + prevSearchConversationId, + prevSearchCounter, + searchConversation, + startSearchCounter, + ]); + + return ( + { + if (!searchConversation && !searchTerm) { + clearSearch(); + } + }} + onChangeValue={nextSearchTerm => { + if (!nextSearchTerm) { + if (searchConversation) { + clearConversationSearch(); + } else { + clearSearch(); + } + + return; + } + + if (updateSearchTerm) { + updateSearchTerm(nextSearchTerm); + } + }} + onClear={() => { + clearSearch(); + inputRef.current?.focus(); + }} + ref={inputRef} + searchConversation={searchConversation} + value={searchTerm} + /> + ); +}; diff --git a/ts/components/MainHeader.stories.tsx b/ts/components/MainHeader.stories.tsx index d7407d579821..0cfc1f5427b6 100644 --- a/ts/components/MainHeader.stories.tsx +++ b/ts/components/MainHeader.stories.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; @@ -10,7 +10,6 @@ import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; import type { PropsType } from './MainHeader'; import { MainHeader } from './MainHeader'; -import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { ThemeType } from '../types/Util'; const i18n = setupI18n('en', enMessages); @@ -23,10 +22,6 @@ const optionalText = (name: string, value: string | undefined) => text(name, value || '') || undefined; const createProps = (overrideProps: Partial = {}): PropsType => ({ - searchTerm: requiredText('searchTerm', overrideProps.searchTerm), - searchConversation: overrideProps.searchConversation, - selectedConversation: undefined, - startSearchCounter: 0, theme: ThemeType.light, phoneNumber: optionalText('phoneNumber', overrideProps.phoneNumber), @@ -37,9 +32,6 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ i18n, - updateSearchTerm: action('updateSearchTerm'), - clearConversationSearch: action('clearConversationSearch'), - clearSearch: action('clearSearch'), startUpdate: action('startUpdate'), showArchivedConversations: action('showArchivedConversations'), @@ -71,35 +63,6 @@ story.add('Phone Number', () => { return ; }); -story.add('Search Term', () => { - const props = createProps({ - name: 'John Smith', - searchTerm: 'Hewwo?', - title: 'John Smith', - }); - - return ; -}); - -story.add('Searching Conversation', () => { - const props = createProps({ - name: 'John Smith', - searchConversation: getDefaultConversation(), - }); - - return ; -}); - -story.add('Searching Conversation with Term', () => { - const props = createProps({ - name: 'John Smith', - searchTerm: 'address', - searchConversation: getDefaultConversation(), - }); - - return ; -}); - story.add('Update Available', () => { const props = createProps({ hasPendingUpdate: true }); diff --git a/ts/components/MainHeader.tsx b/ts/components/MainHeader.tsx index d1598a3113f5..26e88324710a 100644 --- a/ts/components/MainHeader.tsx +++ b/ts/components/MainHeader.tsx @@ -1,4 +1,4 @@ -// Copyright 2018-2021 Signal Messenger, LLC +// Copyright 2018-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; @@ -10,39 +10,25 @@ import { Avatar } from './Avatar'; import { AvatarPopup } from './AvatarPopup'; import type { LocalizerType, ThemeType } from '../types/Util'; import type { AvatarColorType } from '../types/Colors'; -import type { ConversationType } from '../state/ducks/conversations'; -import { LeftPaneSearchInput } from './LeftPaneSearchInput'; import type { BadgeType } from '../badges/types'; export type PropsType = { - searchTerm: string; - searchConversation: undefined | ConversationType; - startSearchCounter: number; - selectedConversation: undefined | ConversationType; - - // For display - phoneNumber?: string; - isMe?: boolean; - name?: string; - color?: AvatarColorType; - disabled?: boolean; - isVerified?: boolean; - profileName?: string; - title: string; avatarPath?: string; badge?: BadgeType; + color?: AvatarColorType; hasPendingUpdate: boolean; - theme: ThemeType; - i18n: LocalizerType; - - updateSearchTerm: (searchTerm: string) => void; - startUpdate: () => unknown; - clearConversationSearch: () => void; - clearSearch: () => void; + isMe?: boolean; + isVerified?: boolean; + name?: string; + phoneNumber?: string; + profileName?: string; + theme: ThemeType; + title: string; showArchivedConversations: () => void; startComposing: () => void; + startUpdate: () => unknown; toggleProfileEditor: () => void; }; @@ -52,35 +38,15 @@ type StateType = { }; export class MainHeader extends React.Component { - private readonly inputRef: React.RefObject; - constructor(props: PropsType) { super(props); - this.inputRef = React.createRef(); - this.state = { showingAvatarPopup: false, popperRoot: null, }; } - public override componentDidUpdate(prevProps: PropsType): void { - const { searchConversation, startSearchCounter } = this.props; - - // When user chooses to search in a given conversation we focus the field for them - if ( - searchConversation && - searchConversation.id !== prevProps.searchConversation?.id - ) { - this.setFocus(); - } - // When user chooses to start a new search, we focus the field - if (startSearchCounter !== prevProps.startSearchCounter) { - this.setSelected(); - } - } - public handleOutsideClick = ({ target }: MouseEvent): void => { const { popperRoot, showingAvatarPopup } = this.state; @@ -134,42 +100,6 @@ export class MainHeader extends React.Component { } } - private updateSearch = (searchTerm: string): void => { - const { - updateSearchTerm, - clearConversationSearch, - clearSearch, - searchConversation, - } = this.props; - - if (!searchTerm) { - if (searchConversation) { - clearConversationSearch(); - } else { - clearSearch(); - } - - return; - } - - if (updateSearchTerm) { - updateSearchTerm(searchTerm); - } - }; - - public clearSearch = (): void => { - const { clearSearch } = this.props; - clearSearch(); - this.setFocus(); - }; - - private handleInputBlur = (): void => { - const { clearSearch, searchConversation, searchTerm } = this.props; - if (!searchConversation && !searchTerm) { - clearSearch(); - } - }; - public handleGlobalKeyDown = (event: KeyboardEvent): void => { const { showingAvatarPopup } = this.state; const { key } = event; @@ -179,31 +109,16 @@ export class MainHeader extends React.Component { } }; - public setFocus = (): void => { - if (this.inputRef.current) { - this.inputRef.current.focus(); - } - }; - - public setSelected = (): void => { - if (this.inputRef.current) { - this.inputRef.current.select(); - } - }; - public override render(): JSX.Element { const { avatarPath, badge, color, - disabled, hasPendingUpdate, i18n, name, phoneNumber, profileName, - searchConversation, - searchTerm, showArchivedConversations, startComposing, startUpdate, @@ -213,8 +128,6 @@ export class MainHeader extends React.Component { } = this.props; const { showingAvatarPopup, popperRoot } = this.state; - const isSearching = Boolean(searchConversation || searchTerm.trim().length); - return (
@@ -291,25 +204,13 @@ export class MainHeader extends React.Component { ) : null} - - {!isSearching && ( -
); } diff --git a/ts/components/leftPane/LeftPaneArchiveHelper.tsx b/ts/components/leftPane/LeftPaneArchiveHelper.tsx index 39d9ff152abd..7064186c41ff 100644 --- a/ts/components/leftPane/LeftPaneArchiveHelper.tsx +++ b/ts/components/leftPane/LeftPaneArchiveHelper.tsx @@ -49,15 +49,11 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper void; i18n: LocalizerType; showInbox: () => void; - updateSearchTerm: (query: string) => void; }>): ReactChild { return (
@@ -69,29 +65,44 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper
- {this.searchConversation ? ( - { - updateSearchTerm(newValue); - }} - onClear={() => { - clearSearch(); - }} - ref={el => { - el?.focus(); - }} - searchConversation={this.searchConversation} - value={this.searchTerm} - /> - ) : ( - i18n('archivedConversations') - )} + {i18n('archivedConversations')}
); } + override getSearchInput({ + clearSearch, + i18n, + updateSearchTerm, + }: Readonly<{ + clearConversationSearch: () => unknown; + clearSearch: () => unknown; + i18n: LocalizerType; + updateSearchTerm: (searchTerm: string) => unknown; + }>): ReactChild | null { + if (!this.searchConversation) { + return null; + } + + return ( + { + updateSearchTerm(newValue); + }} + onClear={() => { + clearSearch(); + }} + ref={el => { + el?.focus(); + }} + searchConversation={this.searchConversation} + value={this.searchTerm} + /> + ); + } + override getBackAction({ showInbox }: { showInbox: () => void }): () => void { return showInbox; } diff --git a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx index 0c2c939a0811..548e9379d62b 100644 --- a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx +++ b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactChild, ChangeEvent } from 'react'; @@ -105,21 +105,37 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper + ) => unknown; + }>): ReactChild { + return ( + + ); + } + override getPreRowsNode({ closeCantAddContactToGroupModal, closeMaximumGroupSizeModal, closeRecommendedGroupSizeModal, i18n, - onChangeComposeSearchTerm, removeSelectedContact, }: Readonly<{ closeCantAddContactToGroupModal: () => unknown; closeMaximumGroupSizeModal: () => unknown; closeRecommendedGroupSizeModal: () => unknown; i18n: LocalizerType; - onChangeComposeSearchTerm: ( - event: ChangeEvent - ) => unknown; removeSelectedContact: (conversationId: string) => unknown; }>): ReactChild { let modalNode: undefined | ReactChild; @@ -154,14 +170,6 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper - - {Boolean(this.selectedContacts.length) && ( {this.selectedContacts.map(contact => ( diff --git a/ts/components/leftPane/LeftPaneComposeHelper.tsx b/ts/components/leftPane/LeftPaneComposeHelper.tsx index 5adb825bead4..a375cbf6812b 100644 --- a/ts/components/leftPane/LeftPaneComposeHelper.tsx +++ b/ts/components/leftPane/LeftPaneComposeHelper.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactChild, ChangeEvent } from 'react'; @@ -94,7 +94,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper unknown; }>): ReactChild { return ( - <> - + + ); + } - {this.getRowCount() ? null : ( -
- {i18n('noConversationsFound')} -
- )} - + override getPreRowsNode({ + i18n, + }: Readonly<{ + i18n: LocalizerType; + }>): ReactChild | null { + return this.getRowCount() ? null : ( +
+ {i18n('noConversationsFound')} +
); } diff --git a/ts/components/leftPane/LeftPaneHelper.tsx b/ts/components/leftPane/LeftPaneHelper.tsx index 92900ac7f9d2..f18aabe7a56c 100644 --- a/ts/components/leftPane/LeftPaneHelper.tsx +++ b/ts/components/leftPane/LeftPaneHelper.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ChangeEvent, ReactChild } from 'react'; @@ -24,12 +24,24 @@ export type ToFindType = { export abstract class LeftPaneHelper { getHeaderContents( _: Readonly<{ - clearSearch: () => void; i18n: LocalizerType; showInbox: () => void; startComposing: () => void; showChooseGroupMembers: () => void; - updateSearchTerm: (query: string) => void; + }> + ): null | ReactChild { + return null; + } + + getSearchInput( + _: Readonly<{ + clearConversationSearch: () => unknown; + clearSearch: () => unknown; + i18n: LocalizerType; + onChangeComposeSearchTerm: ( + event: ChangeEvent + ) => unknown; + updateSearchTerm: (searchTerm: string) => unknown; }> ): null | ReactChild { return null; @@ -47,7 +59,9 @@ export abstract class LeftPaneHelper { getPreRowsNode( _: Readonly<{ + clearConversationSearch: () => unknown; clearGroupCreationError: () => void; + clearSearch: () => unknown; closeCantAddContactToGroupModal: () => unknown; closeMaximumGroupSizeModal: () => unknown; closeRecommendedGroupSizeModal: () => unknown; @@ -56,13 +70,10 @@ export abstract class LeftPaneHelper { composeSaveAvatarToDisk: SaveAvatarToDiskActionType; createGroup: () => unknown; i18n: LocalizerType; - setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown; - setComposeGroupName: (_: string) => unknown; - setComposeGroupExpireTimer: (_: number) => void; - onChangeComposeSearchTerm: ( - event: ChangeEvent - ) => unknown; removeSelectedContact: (_: string) => unknown; + setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown; + setComposeGroupExpireTimer: (_: number) => void; + setComposeGroupName: (_: string) => unknown; toggleComposeEditingAvatar: () => unknown; }> ): null | ReactChild { diff --git a/ts/components/leftPane/LeftPaneInboxHelper.tsx b/ts/components/leftPane/LeftPaneInboxHelper.tsx index 91e05d0c23d1..79e9950dcbd1 100644 --- a/ts/components/leftPane/LeftPaneInboxHelper.tsx +++ b/ts/components/leftPane/LeftPaneInboxHelper.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { last } from 'lodash'; @@ -7,6 +7,7 @@ import React from 'react'; import { Intl } from '../Intl'; import type { ToFindType } from './LeftPaneHelper'; +import type { ConversationType } from '../../state/ducks/conversations'; import { LeftPaneHelper } from './LeftPaneHelper'; import { getConversationInDirection } from './getConversationInDirection'; import type { Row } from '../ConversationList'; @@ -14,6 +15,7 @@ import { RowType } from '../ConversationList'; import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem'; import type { LocalizerType } from '../../types/Util'; import { handleKeydownForSearch } from './handleKeydownForSearch'; +import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput'; export type LeftPaneInboxPropsType = { conversations: ReadonlyArray; @@ -21,6 +23,9 @@ export type LeftPaneInboxPropsType = { pinnedConversations: ReadonlyArray; isAboutToSearchInAConversation: boolean; startSearchCounter: number; + searchDisabled: boolean; + searchTerm: string; + searchConversation: undefined | ConversationType; }; export class LeftPaneInboxHelper extends LeftPaneHelper { @@ -34,12 +39,21 @@ export class LeftPaneInboxHelper extends LeftPaneHelper private readonly startSearchCounter: number; + private readonly searchDisabled: boolean; + + private readonly searchTerm: string; + + private readonly searchConversation: undefined | ConversationType; + constructor({ conversations, archivedConversations, pinnedConversations, isAboutToSearchInAConversation, startSearchCounter, + searchDisabled, + searchTerm, + searchConversation, }: Readonly) { super(); @@ -48,6 +62,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper this.pinnedConversations = pinnedConversations; this.isAboutToSearchInAConversation = isAboutToSearchInAConversation; this.startSearchCounter = startSearchCounter; + this.searchDisabled = searchDisabled; + this.searchTerm = searchTerm; + this.searchConversation = searchConversation; } getRowCount(): number { @@ -61,9 +78,36 @@ export class LeftPaneInboxHelper extends LeftPaneHelper ); } + override getSearchInput({ + clearConversationSearch, + clearSearch, + i18n, + updateSearchTerm, + }: Readonly<{ + clearConversationSearch: () => unknown; + clearSearch: () => unknown; + i18n: LocalizerType; + updateSearchTerm: (searchTerm: string) => unknown; + }>): ReactChild { + return ( + + ); + } + override getPreRowsNode({ i18n, - }: Readonly<{ i18n: LocalizerType }>): null | ReactChild { + }: Readonly<{ + i18n: LocalizerType; + }>): ReactChild | null { if (this.getRowCount() === 0) { return (
diff --git a/ts/components/leftPane/LeftPaneSearchHelper.tsx b/ts/components/leftPane/LeftPaneSearchHelper.tsx index affa8afd1655..fe0a4a899b33 100644 --- a/ts/components/leftPane/LeftPaneSearchHelper.tsx +++ b/ts/components/leftPane/LeftPaneSearchHelper.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactChild } from 'react'; @@ -11,6 +11,8 @@ import type { Row } from '../ConversationList'; import { RowType } from '../ConversationList'; import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem'; import { handleKeydownForSearch } from './handleKeydownForSearch'; +import type { ConversationType } from '../../state/ducks/conversations'; +import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput'; import { Intl } from '../Intl'; import { Emojify } from '../conversation/Emojify'; @@ -37,6 +39,9 @@ export type LeftPaneSearchPropsType = { searchConversationName?: string; primarySendsSms: boolean; searchTerm: string; + startSearchCounter: number; + searchDisabled: boolean; + searchConversation: undefined | ConversationType; }; const searchResultKeys: Array< @@ -61,30 +66,70 @@ export class LeftPaneSearchHelper extends LeftPaneHelper) { super(); - this.conversationResults = conversationResults; this.contactResults = contactResults; + this.conversationResults = conversationResults; this.messageResults = messageResults; - this.searchConversationName = searchConversationName; this.primarySendsSms = primarySendsSms; + this.searchConversation = searchConversation; + this.searchConversationName = searchConversationName; + this.searchDisabled = searchDisabled; this.searchTerm = searchTerm; + this.startSearchCounter = startSearchCounter; + } + + override getSearchInput({ + clearConversationSearch, + clearSearch, + i18n, + updateSearchTerm, + }: Readonly<{ + clearConversationSearch: () => unknown; + clearSearch: () => unknown; + i18n: LocalizerType; + updateSearchTerm: (searchTerm: string) => unknown; + }>): ReactChild { + return ( + + ); } override getPreRowsNode({ i18n, - }: Readonly<{ i18n: LocalizerType }>): null | ReactChild { + }: Readonly<{ + i18n: LocalizerType; + }>): ReactChild | null { const mightHaveSearchResults = this.allResults().some( searchResult => searchResult.isLoading || searchResult.results.length ); + if (mightHaveSearchResults) { return null; } diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 518ddac486a1..f15bc80630a2 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import memoizee from 'memoizee'; @@ -133,25 +133,6 @@ export const getSelectedConversationId = createSelector( } ); -export const getSelectedConversation = createSelector( - getSelectedConversationId, - getConversationLookup, - ( - selectedConversationId: string | undefined, - conversationLookup: ConversationLookupType - ): undefined | ConversationType => { - if (!selectedConversationId) { - return undefined; - } - const conversation = getOwn(conversationLookup, selectedConversationId); - assert( - conversation, - 'getSelectedConversation: could not find selected conversation in lookup; returning undefined' - ); - return conversation; - } -); - type SelectedMessageType = { id: string; counter: number; diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts index f226b8cadc0a..ce5d72125b84 100644 --- a/ts/state/selectors/search.ts +++ b/ts/state/selectors/search.ts @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import memoizee from 'memoizee'; @@ -95,7 +95,14 @@ export const getSearchResults = createSelector( state: SearchStateType, searchConversationName, conversationLookup: ConversationLookupType - ): Omit => { + ): Pick< + LeftPaneSearchPropsType, + | 'conversationResults' + | 'contactResults' + | 'messageResults' + | 'searchConversationName' + | 'searchTerm' + > => { const { contactIds, conversationIds, diff --git a/ts/state/smart/LeftPane.tsx b/ts/state/smart/LeftPane.tsx index b97a23857a73..701155036cfe 100644 --- a/ts/state/smart/LeftPane.tsx +++ b/ts/state/smart/LeftPane.tsx @@ -118,12 +118,18 @@ const getModeSpecificProps = ( return { mode: LeftPaneMode.Search, primarySendsSms, + searchConversation: getSearchConversation(state), + searchDisabled: state.network.challengeStatus !== 'idle', + startSearchCounter: getStartSearchCounter(state), ...getSearchResults(state), }; } return { mode: LeftPaneMode.Inbox, isAboutToSearchInAConversation: getIsSearchingInAConversation(state), + searchConversation: getSearchConversation(state), + searchDisabled: state.network.challengeStatus !== 'idle', + searchTerm: getQuery(state), startSearchCounter: getStartSearchCounter(state), ...getLeftPaneLists(state), }; diff --git a/ts/state/smart/MainHeader.tsx b/ts/state/smart/MainHeader.tsx index 0c8b1e47fbb6..1e4aab908538 100644 --- a/ts/state/smart/MainHeader.tsx +++ b/ts/state/smart/MainHeader.tsx @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { connect } from 'react-redux'; @@ -8,11 +8,6 @@ import { MainHeader } from '../../components/MainHeader'; import type { StateType } from '../reducer'; import { getPreferredBadgeSelector } from '../selectors/badges'; -import { - getQuery, - getSearchConversation, - getStartSearchCounter, -} from '../selectors/search'; import { getIntl, getRegionCode, @@ -21,18 +16,13 @@ import { getUserNumber, getUserUuid, } from '../selectors/user'; -import { getMe, getSelectedConversation } from '../selectors/conversations'; +import { getMe } from '../selectors/conversations'; const mapStateToProps = (state: StateType) => { const me = getMe(state); return { - disabled: state.network.challengeStatus !== 'idle', hasPendingUpdate: Boolean(state.updates.didSnooze), - searchTerm: getQuery(state), - searchConversation: getSearchConversation(state), - selectedConversation: getSelectedConversation(state), - startSearchCounter: getStartSearchCounter(state), regionCode: getRegionCode(state), ourConversationId: getUserConversationId(state), ourNumber: getUserNumber(state), diff --git a/ts/test-both/state/selectors/conversations_test.ts b/ts/test-both/state/selectors/conversations_test.ts index 4471fb9b86ff..734f8872b844 100644 --- a/ts/test-both/state/selectors/conversations_test.ts +++ b/ts/test-both/state/selectors/conversations_test.ts @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; @@ -39,7 +39,6 @@ import { getNumberOfMessagesPendingBecauseOfVerification, getPlaceholderContact, getRecommendedGroupSizeModalState, - getSelectedConversation, getSelectedConversationId, hasGroupCreationError, isCreatingGroup, @@ -1744,36 +1743,6 @@ describe('both/state/selectors/conversations', () => { }); }); - describe('#getSelectedConversation', () => { - it('returns undefined if no conversation is selected', () => { - const state = { - ...getEmptyRootState(), - conversations: { - ...getEmptyState(), - conversationLookup: { - abc123: makeConversation('abc123'), - }, - }, - }; - assert.isUndefined(getSelectedConversation(state)); - }); - - it('returns the selected conversation', () => { - const conversation = makeConversation('abc123'); - const state = { - ...getEmptyRootState(), - conversations: { - ...getEmptyState(), - conversationLookup: { - abc123: conversation, - }, - selectedConversationId: 'abc123', - }, - }; - assert.strictEqual(getSelectedConversation(state), conversation); - }); - }); - describe('#getContactNameColorSelector', () => { it('returns the right color order sorted by UUID ASC', () => { const group = makeConversation('group'); diff --git a/ts/test-node/components/leftPane/LeftPaneInboxHelper_test.tsx b/ts/test-node/components/leftPane/LeftPaneInboxHelper_test.tsx index 024292d47e28..8512416b7728 100644 --- a/ts/test-node/components/leftPane/LeftPaneInboxHelper_test.tsx +++ b/ts/test-node/components/leftPane/LeftPaneInboxHelper_test.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; @@ -12,10 +12,13 @@ import { LeftPaneInboxHelper } from '../../../components/leftPane/LeftPaneInboxH describe('LeftPaneInboxHelper', () => { const defaultProps: LeftPaneInboxPropsType = { - conversations: [], - pinnedConversations: [], archivedConversations: [], + conversations: [], isAboutToSearchInAConversation: false, + pinnedConversations: [], + searchConversation: undefined, + searchDisabled: false, + searchTerm: '', startSearchCounter: 0, }; diff --git a/ts/test-node/components/leftPane/LeftPaneSearchHelper_test.ts b/ts/test-node/components/leftPane/LeftPaneSearchHelper_test.ts index a8c7e5f0dbb1..3cf81a8ce7a6 100644 --- a/ts/test-node/components/leftPane/LeftPaneSearchHelper_test.ts +++ b/ts/test-node/components/leftPane/LeftPaneSearchHelper_test.ts @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; @@ -23,6 +23,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.isUndefined( @@ -44,6 +47,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }).getRowCount(), 100 ); @@ -57,6 +63,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }).getRowCount(), 100 ); @@ -67,6 +76,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [fakeMessage()] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }).getRowCount(), 100 ); @@ -79,6 +91,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.strictEqual(helper.getRowCount(), 0); @@ -94,6 +109,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [fakeMessage()] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.strictEqual(helper.getRowCount(), 5); @@ -109,6 +127,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }), new LeftPaneSearchHelper({ conversationResults: { @@ -119,6 +140,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }), new LeftPaneSearchHelper({ conversationResults: { isLoading: true }, @@ -126,6 +150,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [fakeMessage()] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }), ]; @@ -159,6 +186,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: messages }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.deepEqual(helper.getRow(0), { @@ -208,6 +238,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: messages }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.deepEqual(helper.getRow(0), { @@ -248,6 +281,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: messages }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.deepEqual(helper.getRow(0), { @@ -290,6 +326,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.deepEqual(helper.getRow(0), { @@ -324,6 +363,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }), new LeftPaneSearchHelper({ conversationResults: { @@ -334,6 +376,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }), new LeftPaneSearchHelper({ conversationResults: { isLoading: true }, @@ -341,6 +386,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [fakeMessage()] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }), ]; @@ -362,6 +410,9 @@ describe('LeftPaneSearchHelper', () => { }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.isTrue(helper.isScrollable()); }); @@ -381,6 +432,9 @@ describe('LeftPaneSearchHelper', () => { }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.isFalse( @@ -396,6 +450,9 @@ describe('LeftPaneSearchHelper', () => { }, searchTerm: 'bar', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }) ); }); @@ -407,6 +464,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.isFalse( @@ -419,6 +479,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'bar', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }) ); }); @@ -430,6 +493,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [fakeMessage()] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.isTrue( @@ -442,6 +508,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [fakeMessage()] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }) ); }); @@ -456,6 +525,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: false, results: [] }, searchTerm: 'foo', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }); assert.isTrue( @@ -468,6 +540,9 @@ describe('LeftPaneSearchHelper', () => { messageResults: { isLoading: true }, searchTerm: 'bar', primarySendsSms: false, + searchConversation: undefined, + searchDisabled: false, + startSearchCounter: 0, }) ); }); diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index c1a1f5cf764a..4cb8c232ca58 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -7488,20 +7488,11 @@ "updated": "2021-10-11T21:21:08.188Z" }, { - "rule": "React-createRef", - "path": "ts/components/MainHeader.js", - "line": " this.inputRef = react_1.default.createRef();", + "rule": "React-useRef", + "path": "ts/components/LeftPaneMainSearchInput.tsx", + "line": " const inputRef = useRef(null);", "reasonCategory": "usageTrusted", - "updated": "2020-02-14T20:02:37.507Z", - "reasonDetail": "Used only to set focus" - }, - { - "rule": "React-createRef", - "path": "ts/components/MainHeader.tsx", - "line": " this.inputRef = React.createRef();", - "reasonCategory": "usageTrusted", - "updated": "2020-02-14T20:02:37.507Z", - "reasonDetail": "Used only to set focus" + "updated": "2022-01-26T23:11:05.369Z" }, { "rule": "React-useRef", @@ -8411,4 +8402,4 @@ "reasonCategory": "usageTrusted", "updated": "2021-09-17T21:02:59.414Z" } -] \ No newline at end of file +]