2023-01-03 11:55:46 -08:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
2021-02-23 14:34:28 -06:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { last } from 'lodash';
|
2021-10-26 14:15:33 -05:00
|
|
|
import type { ReactChild } from 'react';
|
|
|
|
import React from 'react';
|
2021-02-23 14:34:28 -06:00
|
|
|
|
2021-10-26 14:15:33 -05:00
|
|
|
import type { ToFindType } from './LeftPaneHelper';
|
2022-04-30 11:24:20 +06:00
|
|
|
import type {
|
|
|
|
ConversationType,
|
2022-06-16 15:12:50 -04:00
|
|
|
ShowConversationType,
|
2022-04-30 11:24:20 +06:00
|
|
|
} from '../../state/ducks/conversations';
|
2021-10-26 14:15:33 -05:00
|
|
|
import { LeftPaneHelper } from './LeftPaneHelper';
|
2021-02-23 14:34:28 -06:00
|
|
|
import { getConversationInDirection } from './getConversationInDirection';
|
2021-10-26 14:15:33 -05:00
|
|
|
import type { Row } from '../ConversationList';
|
|
|
|
import { RowType } from '../ConversationList';
|
2024-08-13 16:34:42 -07:00
|
|
|
import { NavSidebarEmpty } from '../NavSidebar';
|
2021-10-26 14:15:33 -05:00
|
|
|
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
|
|
|
|
import type { LocalizerType } from '../../types/Util';
|
2021-11-01 13:43:02 -05:00
|
|
|
import { handleKeydownForSearch } from './handleKeydownForSearch';
|
2022-02-14 12:57:11 -05:00
|
|
|
import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
|
2021-02-23 14:34:28 -06:00
|
|
|
|
|
|
|
export type LeftPaneInboxPropsType = {
|
|
|
|
conversations: ReadonlyArray<ConversationListItemPropsType>;
|
|
|
|
archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
|
|
|
pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
2023-02-22 14:21:59 -05:00
|
|
|
isAboutToSearch: boolean;
|
2024-03-08 20:08:30 -05:00
|
|
|
isSearchingGlobally: boolean;
|
2021-10-12 18:59:08 -05:00
|
|
|
startSearchCounter: number;
|
2022-01-27 17:12:26 -05:00
|
|
|
searchDisabled: boolean;
|
|
|
|
searchTerm: string;
|
|
|
|
searchConversation: undefined | ConversationType;
|
2024-11-13 13:33:41 -06:00
|
|
|
filterByUnread: boolean;
|
2021-02-23 14:34:28 -06:00
|
|
|
};
|
|
|
|
|
2021-04-26 11:38:50 -05:00
|
|
|
export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType> {
|
2025-01-14 11:11:52 -08:00
|
|
|
readonly #conversations: ReadonlyArray<ConversationListItemPropsType>;
|
|
|
|
readonly #archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
|
|
|
readonly #pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
|
|
|
readonly #isAboutToSearch: boolean;
|
|
|
|
readonly #isSearchingGlobally: boolean;
|
|
|
|
readonly #startSearchCounter: number;
|
|
|
|
readonly #searchDisabled: boolean;
|
|
|
|
readonly #searchTerm: string;
|
|
|
|
readonly #searchConversation: undefined | ConversationType;
|
|
|
|
readonly #filterByUnread: boolean;
|
2024-11-13 13:33:41 -06:00
|
|
|
|
2021-02-23 14:34:28 -06:00
|
|
|
constructor({
|
|
|
|
conversations,
|
|
|
|
archivedConversations,
|
|
|
|
pinnedConversations,
|
2023-02-22 14:21:59 -05:00
|
|
|
isAboutToSearch,
|
2024-03-08 20:08:30 -05:00
|
|
|
isSearchingGlobally,
|
2021-10-12 18:59:08 -05:00
|
|
|
startSearchCounter,
|
2022-01-27 17:12:26 -05:00
|
|
|
searchDisabled,
|
|
|
|
searchTerm,
|
|
|
|
searchConversation,
|
2024-11-13 13:33:41 -06:00
|
|
|
filterByUnread,
|
2021-02-23 14:34:28 -06:00
|
|
|
}: Readonly<LeftPaneInboxPropsType>) {
|
|
|
|
super();
|
|
|
|
|
2025-01-14 11:11:52 -08:00
|
|
|
this.#conversations = conversations;
|
|
|
|
this.#archivedConversations = archivedConversations;
|
|
|
|
this.#pinnedConversations = pinnedConversations;
|
|
|
|
this.#isAboutToSearch = isAboutToSearch;
|
|
|
|
this.#isSearchingGlobally = isSearchingGlobally;
|
|
|
|
this.#startSearchCounter = startSearchCounter;
|
|
|
|
this.#searchDisabled = searchDisabled;
|
|
|
|
this.#searchTerm = searchTerm;
|
|
|
|
this.#searchConversation = searchConversation;
|
|
|
|
this.#filterByUnread = filterByUnread;
|
2021-02-23 14:34:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
getRowCount(): number {
|
2025-01-14 11:11:52 -08:00
|
|
|
const headerCount = this.#hasPinnedAndNonpinned() ? 2 : 0;
|
|
|
|
const buttonCount = this.#archivedConversations.length ? 1 : 0;
|
2021-02-23 14:34:28 -06:00
|
|
|
return (
|
|
|
|
headerCount +
|
2025-01-14 11:11:52 -08:00
|
|
|
this.#pinnedConversations.length +
|
|
|
|
this.#conversations.length +
|
2021-02-23 14:34:28 -06:00
|
|
|
buttonCount
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-27 17:12:26 -05:00
|
|
|
override getSearchInput({
|
|
|
|
clearConversationSearch,
|
2024-11-13 13:33:41 -06:00
|
|
|
clearSearchQuery,
|
2024-03-08 20:08:30 -05:00
|
|
|
endConversationSearch,
|
|
|
|
endSearch,
|
2022-01-27 17:12:26 -05:00
|
|
|
i18n,
|
2022-06-16 15:12:50 -04:00
|
|
|
showConversation,
|
2022-01-27 17:12:26 -05:00
|
|
|
updateSearchTerm,
|
2024-11-13 13:33:41 -06:00
|
|
|
updateFilterByUnread,
|
2022-01-27 17:12:26 -05:00
|
|
|
}: Readonly<{
|
|
|
|
clearConversationSearch: () => unknown;
|
2024-11-13 13:33:41 -06:00
|
|
|
clearSearchQuery: () => unknown;
|
2024-03-08 20:08:30 -05:00
|
|
|
endConversationSearch: () => unknown;
|
|
|
|
endSearch: () => unknown;
|
2022-01-27 17:12:26 -05:00
|
|
|
i18n: LocalizerType;
|
2022-06-16 15:12:50 -04:00
|
|
|
showConversation: ShowConversationType;
|
2022-01-27 17:12:26 -05:00
|
|
|
updateSearchTerm: (searchTerm: string) => unknown;
|
2024-11-13 13:33:41 -06:00
|
|
|
updateFilterByUnread: (filterByUnread: boolean) => void;
|
2022-01-27 17:12:26 -05:00
|
|
|
}>): ReactChild {
|
|
|
|
return (
|
2022-02-14 12:57:11 -05:00
|
|
|
<LeftPaneSearchInput
|
2022-01-27 17:12:26 -05:00
|
|
|
clearConversationSearch={clearConversationSearch}
|
2024-11-13 13:33:41 -06:00
|
|
|
clearSearchQuery={clearSearchQuery}
|
2024-03-08 20:08:30 -05:00
|
|
|
endConversationSearch={endConversationSearch}
|
|
|
|
endSearch={endSearch}
|
2025-01-14 11:11:52 -08:00
|
|
|
disabled={this.#searchDisabled}
|
2022-01-27 17:12:26 -05:00
|
|
|
i18n={i18n}
|
2025-01-14 11:11:52 -08:00
|
|
|
isSearchingGlobally={this.#isSearchingGlobally}
|
|
|
|
searchConversation={this.#searchConversation}
|
|
|
|
searchTerm={this.#searchTerm}
|
2022-06-16 15:12:50 -04:00
|
|
|
showConversation={showConversation}
|
2025-01-14 11:11:52 -08:00
|
|
|
startSearchCounter={this.#startSearchCounter}
|
2022-01-27 17:12:26 -05:00
|
|
|
updateSearchTerm={updateSearchTerm}
|
2024-11-13 13:33:41 -06:00
|
|
|
onFilterClick={updateFilterByUnread}
|
2025-01-14 11:11:52 -08:00
|
|
|
filterButtonEnabled={!this.#searchConversation}
|
|
|
|
filterPressed={this.#filterByUnread}
|
2022-01-27 17:12:26 -05:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-08-13 16:34:42 -07:00
|
|
|
override getBackgroundNode({
|
2021-04-20 16:16:49 -07:00
|
|
|
i18n,
|
2022-01-27 17:12:26 -05:00
|
|
|
}: Readonly<{
|
|
|
|
i18n: LocalizerType;
|
|
|
|
}>): ReactChild | null {
|
2021-04-20 16:16:49 -07:00
|
|
|
if (this.getRowCount() === 0) {
|
|
|
|
return (
|
2024-08-13 16:34:42 -07:00
|
|
|
<NavSidebarEmpty
|
|
|
|
title={i18n('icu:emptyInbox__title')}
|
|
|
|
subtitle={i18n('icu:emptyInbox__subtitle')}
|
|
|
|
/>
|
2021-04-20 16:16:49 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-23 14:34:28 -06:00
|
|
|
getRow(rowIndex: number): undefined | Row {
|
2025-01-14 11:11:52 -08:00
|
|
|
const pinnedConversations = this.#pinnedConversations;
|
|
|
|
const archivedConversations = this.#archivedConversations;
|
|
|
|
const conversations = this.#conversations;
|
2021-02-23 14:34:28 -06:00
|
|
|
|
|
|
|
const archivedConversationsCount = archivedConversations.length;
|
|
|
|
|
2025-01-14 11:11:52 -08:00
|
|
|
if (this.#hasPinnedAndNonpinned()) {
|
2021-02-23 14:34:28 -06:00
|
|
|
switch (rowIndex) {
|
|
|
|
case 0:
|
|
|
|
return {
|
|
|
|
type: RowType.Header,
|
2023-03-29 17:03:25 -07:00
|
|
|
getHeaderText: i18n => i18n('icu:LeftPane--pinned'),
|
2021-02-23 14:34:28 -06:00
|
|
|
};
|
|
|
|
case pinnedConversations.length + 1:
|
|
|
|
return {
|
|
|
|
type: RowType.Header,
|
2023-03-29 17:03:25 -07:00
|
|
|
getHeaderText: i18n => i18n('icu:LeftPane--chats'),
|
2021-02-23 14:34:28 -06:00
|
|
|
};
|
|
|
|
case pinnedConversations.length + conversations.length + 2:
|
|
|
|
if (archivedConversationsCount) {
|
|
|
|
return {
|
|
|
|
type: RowType.ArchiveButton,
|
|
|
|
archivedConversationsCount,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
default: {
|
|
|
|
const pinnedConversation = pinnedConversations[rowIndex - 1];
|
|
|
|
if (pinnedConversation) {
|
|
|
|
return {
|
|
|
|
type: RowType.Conversation,
|
|
|
|
conversation: pinnedConversation,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
const conversation =
|
|
|
|
conversations[rowIndex - pinnedConversations.length - 2];
|
|
|
|
return conversation
|
|
|
|
? {
|
|
|
|
type: RowType.Conversation,
|
|
|
|
conversation,
|
|
|
|
}
|
|
|
|
: undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const onlyConversations = pinnedConversations.length
|
|
|
|
? pinnedConversations
|
|
|
|
: conversations;
|
|
|
|
if (rowIndex < onlyConversations.length) {
|
|
|
|
const conversation = onlyConversations[rowIndex];
|
|
|
|
return conversation
|
|
|
|
? {
|
|
|
|
type: RowType.Conversation,
|
|
|
|
conversation,
|
|
|
|
}
|
|
|
|
: undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rowIndex === onlyConversations.length && archivedConversationsCount) {
|
|
|
|
return {
|
|
|
|
type: RowType.ArchiveButton,
|
|
|
|
archivedConversationsCount,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-11-12 17:44:20 -06:00
|
|
|
override getRowIndexToScrollTo(
|
2021-02-23 14:34:28 -06:00
|
|
|
selectedConversationId: undefined | string
|
|
|
|
): undefined | number {
|
|
|
|
if (!selectedConversationId) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const isConversationSelected = (
|
|
|
|
conversation: Readonly<ConversationListItemPropsType>
|
|
|
|
) => conversation.id === selectedConversationId;
|
2025-01-14 11:11:52 -08:00
|
|
|
const hasHeaders = this.#hasPinnedAndNonpinned();
|
2021-02-23 14:34:28 -06:00
|
|
|
|
2025-01-14 11:11:52 -08:00
|
|
|
const pinnedConversationIndex = this.#pinnedConversations.findIndex(
|
2021-02-23 14:34:28 -06:00
|
|
|
isConversationSelected
|
|
|
|
);
|
|
|
|
if (pinnedConversationIndex !== -1) {
|
|
|
|
const headerOffset = hasHeaders ? 1 : 0;
|
|
|
|
return pinnedConversationIndex + headerOffset;
|
|
|
|
}
|
|
|
|
|
2025-01-14 11:11:52 -08:00
|
|
|
const conversationIndex = this.#conversations.findIndex(
|
2021-02-23 14:34:28 -06:00
|
|
|
isConversationSelected
|
|
|
|
);
|
|
|
|
if (conversationIndex !== -1) {
|
2025-01-14 11:11:52 -08:00
|
|
|
const pinnedOffset = this.#pinnedConversations.length;
|
2021-02-23 14:34:28 -06:00
|
|
|
const headerOffset = hasHeaders ? 2 : 0;
|
|
|
|
return conversationIndex + pinnedOffset + headerOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-11-12 17:44:20 -06:00
|
|
|
override requiresFullWidth(): boolean {
|
2021-10-12 18:59:08 -05:00
|
|
|
const hasNoConversations =
|
2025-01-14 11:11:52 -08:00
|
|
|
!this.#conversations.length &&
|
|
|
|
!this.#pinnedConversations.length &&
|
|
|
|
!this.#archivedConversations.length;
|
|
|
|
return hasNoConversations || this.#isAboutToSearch;
|
2021-10-12 18:59:08 -05:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:34:28 -06:00
|
|
|
shouldRecomputeRowHeights(old: Readonly<LeftPaneInboxPropsType>): boolean {
|
2025-01-14 11:11:52 -08:00
|
|
|
return old.pinnedConversations.length !== this.#pinnedConversations.length;
|
2021-02-23 14:34:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
getConversationAndMessageAtIndex(
|
|
|
|
conversationIndex: number
|
|
|
|
): undefined | { conversationId: string } {
|
2025-01-14 11:11:52 -08:00
|
|
|
const pinnedConversations = this.#pinnedConversations;
|
|
|
|
const conversations = this.#conversations;
|
2021-02-23 14:34:28 -06:00
|
|
|
const conversation =
|
|
|
|
pinnedConversations[conversationIndex] ||
|
|
|
|
conversations[conversationIndex - pinnedConversations.length] ||
|
|
|
|
last(conversations) ||
|
|
|
|
last(pinnedConversations);
|
|
|
|
return conversation ? { conversationId: conversation.id } : undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
getConversationAndMessageInDirection(
|
|
|
|
toFind: Readonly<ToFindType>,
|
|
|
|
selectedConversationId: undefined | string,
|
2023-03-20 15:23:53 -07:00
|
|
|
_targetedMessageId: unknown
|
2021-02-23 14:34:28 -06:00
|
|
|
): undefined | { conversationId: string } {
|
|
|
|
return getConversationInDirection(
|
2025-01-14 11:11:52 -08:00
|
|
|
[...this.#pinnedConversations, ...this.#conversations],
|
2021-02-23 14:34:28 -06:00
|
|
|
toFind,
|
|
|
|
selectedConversationId
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-12 17:44:20 -06:00
|
|
|
override onKeyDown(
|
2021-11-01 13:43:02 -05:00
|
|
|
event: KeyboardEvent,
|
|
|
|
options: Readonly<{
|
|
|
|
searchInConversation: (conversationId: string) => unknown;
|
|
|
|
selectedConversationId: undefined | string;
|
|
|
|
startSearch: () => unknown;
|
|
|
|
}>
|
|
|
|
): void {
|
|
|
|
handleKeydownForSearch(event, options);
|
|
|
|
}
|
|
|
|
|
2025-01-14 11:11:52 -08:00
|
|
|
#hasPinnedAndNonpinned(): boolean {
|
2021-02-23 14:34:28 -06:00
|
|
|
return Boolean(
|
2025-01-14 11:11:52 -08:00
|
|
|
this.#pinnedConversations.length && this.#conversations.length
|
2021-02-23 14:34:28 -06:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|