Improve left pane UI when loading search results

This commit is contained in:
Evan Hahn 2021-04-02 17:32:55 -05:00 committed by Josh Perez
parent f05d45ac9b
commit d81aaf654f
15 changed files with 420 additions and 93 deletions

View file

@ -10,6 +10,14 @@ import { PropsData as ConversationListItemPropsType } from '../conversationList/
import { Intl } from '../Intl';
import { Emojify } from '../conversation/Emojify';
import { assert } from '../../util/assert';
// The "correct" thing to do is to measure the size of the left pane and render enough
// search results for the container height. But (1) that's slow (2) the list is
// virtualized (3) 99 rows is over 6000px tall, taller than most monitors (4) it's fine
// if, in some extremely tall window, we have some empty space. So we just hard-code a
// fairly big number.
const SEARCH_RESULTS_FAKE_ROW_COUNT = 99;
type MaybeLoadedSearchResultsType<T> =
| { isLoading: true }
@ -106,9 +114,14 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
}
getRowCount(): number {
if (this.isLoading()) {
// 1 for the header.
return 1 + SEARCH_RESULTS_FAKE_ROW_COUNT;
}
return this.allResults().reduce(
(result: number, searchResults) =>
result + getRowCountForSearchResult(searchResults),
result + getRowCountForLoadedSearchResults(searchResults),
0
);
}
@ -124,11 +137,21 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
getRow(rowIndex: number): undefined | Row {
const { conversationResults, contactResults, messageResults } = this;
const conversationRowCount = getRowCountForSearchResult(
if (this.isLoading()) {
if (rowIndex === 0) {
return { type: RowType.SearchResultsLoadingFakeHeader };
}
if (rowIndex + 1 <= SEARCH_RESULTS_FAKE_ROW_COUNT) {
return { type: RowType.SearchResultsLoadingFakeRow };
}
return undefined;
}
const conversationRowCount = getRowCountForLoadedSearchResults(
conversationResults
);
const contactRowCount = getRowCountForSearchResult(contactResults);
const messageRowCount = getRowCountForSearchResult(messageResults);
const contactRowCount = getRowCountForLoadedSearchResults(contactResults);
const messageRowCount = getRowCountForLoadedSearchResults(messageResults);
if (rowIndex < conversationRowCount) {
if (rowIndex === 0) {
@ -137,9 +160,10 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
i18nKey: 'conversationsHeader',
};
}
if (conversationResults.isLoading) {
return { type: RowType.Spinner };
}
assert(
!conversationResults.isLoading,
"We shouldn't get here with conversation results still loading"
);
const conversation = conversationResults.results[rowIndex - 1];
return conversation
? {
@ -157,9 +181,10 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
i18nKey: 'contactsHeader',
};
}
if (contactResults.isLoading) {
return { type: RowType.Spinner };
}
assert(
!contactResults.isLoading,
"We shouldn't get here with contact results still loading"
);
const conversation = contactResults.results[localIndex - 1];
return conversation
? {
@ -180,9 +205,10 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
i18nKey: 'messagesHeader',
};
}
if (messageResults.isLoading) {
return { type: RowType.Spinner };
}
assert(
!messageResults.isLoading,
"We shouldn't get here with message results still loading"
);
const message = messageResults.results[localIndex - 1];
return message
? {
@ -192,11 +218,23 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
: undefined;
}
isScrollable(): boolean {
return !this.isLoading();
}
shouldRecomputeRowHeights(old: Readonly<LeftPaneSearchPropsType>): boolean {
const oldIsLoading = new LeftPaneSearchHelper(old).isLoading();
const newIsLoading = this.isLoading();
if (oldIsLoading && newIsLoading) {
return false;
}
if (oldIsLoading !== newIsLoading) {
return true;
}
return searchResultKeys.some(
key =>
getRowCountForSearchResult(old[key]) !==
getRowCountForSearchResult(this[key])
getRowCountForLoadedSearchResults(old[key]) !==
getRowCountForLoadedSearchResults(this[key])
);
}
@ -221,20 +259,27 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<
private allResults() {
return [this.conversationResults, this.contactResults, this.messageResults];
}
private isLoading(): boolean {
return this.allResults().some(results => results.isLoading);
}
}
function getRowCountForSearchResult(
function getRowCountForLoadedSearchResults(
searchResults: Readonly<MaybeLoadedSearchResultsType<unknown>>
): number {
let hasHeader: boolean;
let resultRows: number;
// It's possible to call this helper with invalid results (e.g., ones that are loading).
// We could change the parameter of this function, but that adds a bunch of redundant
// checks that are, in the author's opinion, less clear.
if (searchResults.isLoading) {
hasHeader = true;
resultRows = 1; // For the spinner.
} else {
const resultCount = searchResults.results.length;
hasHeader = Boolean(resultCount);
resultRows = resultCount;
assert(
false,
'getRowCountForLoadedSearchResults: Expected this to be called with loaded search results. Returning 0'
);
return 0;
}
const resultRows = searchResults.results.length;
const hasHeader = Boolean(resultRows);
return (hasHeader ? 1 : 0) + resultRows;
}