Various search UI improvements

This commit is contained in:
Evan Hahn 2021-11-01 13:43:02 -05:00 committed by GitHub
parent 630394d91d
commit a9cb621eb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 835 additions and 577 deletions

View file

@ -12,28 +12,54 @@ import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import type { LocalizerType } from '../../types/Util';
import type { ConversationType } from '../../state/ducks/conversations';
import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
import type { LeftPaneSearchPropsType } from './LeftPaneSearchHelper';
import { LeftPaneSearchHelper } from './LeftPaneSearchHelper';
export type LeftPaneArchivePropsType = {
type LeftPaneArchiveBasePropsType = {
archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
searchConversation: undefined | ConversationType;
searchTerm: string;
};
export type LeftPaneArchivePropsType =
| LeftPaneArchiveBasePropsType
| (LeftPaneArchiveBasePropsType & LeftPaneSearchPropsType);
/* eslint-disable class-methods-use-this */
export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsType> {
private readonly archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
constructor({ archivedConversations }: Readonly<LeftPaneArchivePropsType>) {
private readonly searchConversation: undefined | ConversationType;
private readonly searchTerm: string;
private readonly searchHelper: undefined | LeftPaneSearchHelper;
constructor(props: Readonly<LeftPaneArchivePropsType>) {
super();
this.archivedConversations = archivedConversations;
this.archivedConversations = props.archivedConversations;
this.searchConversation = props.searchConversation;
this.searchTerm = props.searchTerm;
if ('conversationResults' in props) {
this.searchHelper = new LeftPaneSearchHelper(props);
}
}
getHeaderContents({
clearSearch,
i18n,
showInbox,
updateSearchTerm,
}: Readonly<{
clearSearch: () => void;
i18n: LocalizerType;
showInbox: () => void;
updateSearchTerm: (query: string) => void;
}>): ReactChild {
return (
<div className="module-left-pane__header__contents">
@ -45,7 +71,24 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
type="button"
/>
<div className="module-left-pane__header__contents__text">
{i18n('archivedConversations')}
{this.searchConversation ? (
<LeftPaneSearchInput
i18n={i18n}
onChangeValue={newValue => {
updateSearchTerm(newValue);
}}
onClear={() => {
clearSearch();
}}
ref={el => {
el?.focus();
}}
searchConversation={this.searchConversation}
value={this.searchTerm}
/>
) : (
i18n('archivedConversations')
)}
</div>
</div>
);
@ -55,7 +98,13 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
return showInbox;
}
getPreRowsNode({ i18n }: Readonly<{ i18n: LocalizerType }>): ReactChild {
getPreRowsNode({
i18n,
}: Readonly<{ i18n: LocalizerType }>): ReactChild | null {
if (this.searchHelper) {
return this.searchHelper.getPreRowsNode({ i18n });
}
return (
<div className="module-left-pane__archive-helper-text">
{i18n('archiveHelperText')}
@ -64,10 +113,16 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
}
getRowCount(): number {
return this.archivedConversations.length;
return (
this.searchHelper?.getRowCount() ?? this.archivedConversations.length
);
}
getRow(rowIndex: number): undefined | Row {
if (this.searchHelper) {
return this.searchHelper.getRow(rowIndex);
}
const conversation = this.archivedConversations[rowIndex];
return conversation
? {
@ -80,6 +135,10 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
getRowIndexToScrollTo(
selectedConversationId: undefined | string
): undefined | number {
if (this.searchHelper) {
return this.searchHelper.getRowIndexToScrollTo(selectedConversationId);
}
if (!selectedConversationId) {
return undefined;
}
@ -92,7 +151,12 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
getConversationAndMessageAtIndex(
conversationIndex: number
): undefined | { conversationId: string } {
const { archivedConversations } = this;
const { archivedConversations, searchHelper } = this;
if (searchHelper) {
return searchHelper.getConversationAndMessageAtIndex(conversationIndex);
}
const conversation =
archivedConversations[conversationIndex] || last(archivedConversations);
return conversation ? { conversationId: conversation.id } : undefined;
@ -101,8 +165,16 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
getConversationAndMessageInDirection(
toFind: Readonly<ToFindType>,
selectedConversationId: undefined | string,
_selectedMessageId: unknown
selectedMessageId: unknown
): undefined | { conversationId: string } {
if (this.searchHelper) {
return this.searchHelper.getConversationAndMessageInDirection(
toFind,
selectedConversationId,
selectedMessageId
);
}
return getConversationInDirection(
this.archivedConversations,
toFind,
@ -110,7 +182,51 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
);
}
shouldRecomputeRowHeights(_old: unknown): boolean {
shouldRecomputeRowHeights(old: Readonly<LeftPaneArchivePropsType>): boolean {
const hasSearchingChanged =
'conversationResults' in old !== Boolean(this.searchHelper);
if (hasSearchingChanged) {
return true;
}
if ('conversationResults' in old && this.searchHelper) {
return this.searchHelper.shouldRecomputeRowHeights(old);
}
return false;
}
onKeyDown(
event: KeyboardEvent,
{
searchInConversation,
selectedConversationId,
}: Readonly<{
searchInConversation: (conversationId: string) => unknown;
selectedConversationId: undefined | string;
}>
): void {
if (!selectedConversationId) {
return;
}
const { ctrlKey, metaKey, shiftKey, key } = event;
const commandKey = window.platform === 'darwin' && metaKey;
const controlKey = window.platform !== 'darwin' && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
const commandAndCtrl = commandKey && ctrlKey;
if (
commandOrCtrl &&
!commandAndCtrl &&
shiftKey &&
key.toLowerCase() === 'f' &&
this.archivedConversations.some(({ id }) => id === selectedConversationId)
) {
searchInConversation(selectedConversationId);
event.preventDefault();
event.stopPropagation();
}
}
}

View file

@ -26,10 +26,12 @@ export type ToFindType = {
export abstract class LeftPaneHelper<T> {
getHeaderContents(
_: Readonly<{
clearSearch: () => void;
i18n: LocalizerType;
showInbox: () => void;
startComposing: () => void;
showChooseGroupMembers: () => void;
updateSearchTerm: (query: string) => void;
}>
): null | ReactChild {
return null;
@ -97,6 +99,17 @@ export abstract class LeftPaneHelper<T> {
return true;
}
onKeyDown(
_event: KeyboardEvent,
_options: Readonly<{
searchInConversation: (conversationId: string) => unknown;
selectedConversationId: undefined | string;
startSearch: () => unknown;
}>
): void {
return undefined;
}
abstract getConversationAndMessageAtIndex(
conversationIndex: number
): undefined | { conversationId: string; messageId?: string };

View file

@ -13,6 +13,7 @@ import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import type { LocalizerType } from '../../types/Util';
import { handleKeydownForSearch } from './handleKeydownForSearch';
export type LeftPaneInboxPropsType = {
conversations: ReadonlyArray<ConversationListItemPropsType>;
@ -229,6 +230,17 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
);
}
onKeyDown(
event: KeyboardEvent,
options: Readonly<{
searchInConversation: (conversationId: string) => unknown;
selectedConversationId: undefined | string;
startSearch: () => unknown;
}>
): void {
handleKeydownForSearch(event, options);
}
private hasPinnedAndNonpinned(): boolean {
return Boolean(
this.pinnedConversations.length && this.conversations.length

View file

@ -10,6 +10,7 @@ import type { LocalizerType } from '../../types/Util';
import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import { handleKeydownForSearch } from './handleKeydownForSearch';
import { Intl } from '../Intl';
import { Emojify } from '../conversation/Emojify';
@ -42,6 +43,8 @@ const searchResultKeys: Array<
'conversationResults' | 'contactResults' | 'messageResults'
> = ['conversationResults', 'contactResults', 'messageResults'];
/* eslint-disable class-methods-use-this */
export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType> {
private readonly conversationResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
@ -270,6 +273,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
return undefined;
}
onKeyDown(
event: KeyboardEvent,
options: Readonly<{
searchInConversation: (conversationId: string) => unknown;
selectedConversationId: undefined | string;
startSearch: () => unknown;
}>
): void {
handleKeydownForSearch(event, options);
}
private allResults() {
return [this.conversationResults, this.contactResults, this.messageResults];
}

View file

@ -0,0 +1,33 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export function handleKeydownForSearch(
event: Readonly<KeyboardEvent>,
{
searchInConversation,
selectedConversationId,
startSearch,
}: Readonly<{
searchInConversation: (conversationId: string) => unknown;
selectedConversationId: undefined | string;
startSearch: () => unknown;
}>
): void {
const { ctrlKey, metaKey, shiftKey, key } = event;
const commandKey = window.platform === 'darwin' && metaKey;
const controlKey = window.platform !== 'darwin' && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
const commandAndCtrl = commandKey && ctrlKey;
if (commandOrCtrl && !commandAndCtrl && key.toLowerCase() === 'f') {
if (!shiftKey) {
startSearch();
event.preventDefault();
event.stopPropagation();
} else if (selectedConversationId) {
searchInConversation(selectedConversationId);
event.preventDefault();
event.stopPropagation();
}
}
}