// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { ReactChild, ChangeEvent } from 'react'; import { LeftPaneHelper } from './LeftPaneHelper'; import { Row, RowType } from '../ConversationList'; import { ConversationType } from '../../state/ducks/conversations'; import { ContactCheckboxDisabledReason } from '../conversationList/ContactCheckbox'; import { ContactPills } from '../ContactPills'; import { ContactPill } from '../ContactPill'; import { AddGroupMemberErrorDialog, AddGroupMemberErrorDialogMode, } from '../AddGroupMemberErrorDialog'; import { Button } from '../Button'; import { LocalizerType } from '../../types/Util'; import { getGroupSizeRecommendedLimit, getGroupSizeHardLimit, } from '../../groups/limits'; export type LeftPaneChooseGroupMembersPropsType = { candidateContacts: ReadonlyArray; cantAddContactForModal: undefined | ConversationType; isShowingRecommendedGroupSizeModal: boolean; isShowingMaximumGroupSizeModal: boolean; searchTerm: string; selectedContacts: Array; }; /* eslint-disable class-methods-use-this */ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper< LeftPaneChooseGroupMembersPropsType > { private readonly candidateContacts: ReadonlyArray; private readonly cantAddContactForModal: | undefined | Readonly<{ title: string }>; private readonly isShowingMaximumGroupSizeModal: boolean; private readonly isShowingRecommendedGroupSizeModal: boolean; private readonly searchTerm: string; private readonly selectedContacts: Array; private readonly selectedConversationIdsSet: Set; constructor({ candidateContacts, cantAddContactForModal, isShowingMaximumGroupSizeModal, isShowingRecommendedGroupSizeModal, searchTerm, selectedContacts, }: Readonly) { super(); this.candidateContacts = candidateContacts; this.cantAddContactForModal = cantAddContactForModal; this.isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal; this.isShowingRecommendedGroupSizeModal = isShowingRecommendedGroupSizeModal; this.searchTerm = searchTerm; this.selectedContacts = selectedContacts; this.selectedConversationIdsSet = new Set( selectedContacts.map(contact => contact.id) ); } getHeaderContents({ i18n, startComposing, }: Readonly<{ i18n: LocalizerType; startComposing: () => void; }>): ReactChild { const backButtonLabel = i18n('chooseGroupMembers__back-button'); return (
); } 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; if (this.isShowingMaximumGroupSizeModal) { modalNode = ( ); } else if (this.isShowingRecommendedGroupSizeModal) { modalNode = ( ); } else if (this.cantAddContactForModal) { modalNode = ( ); } return ( <>
{Boolean(this.selectedContacts.length) && ( {this.selectedContacts.map(contact => ( ))} )} {this.getRowCount() ? null : (
{i18n('noContactsFound')}
)} {modalNode} ); } getFooterContents({ i18n, startSettingGroupMetadata, }: Readonly<{ i18n: LocalizerType; startSettingGroupMetadata: () => void; }>): ReactChild { return ( ); } getRowCount(): number { if (!this.candidateContacts.length) { return 0; } return this.candidateContacts.length + 2; } getRow(rowIndex: number): undefined | Row { if (!this.candidateContacts.length) { return undefined; } if (rowIndex === 0) { return { type: RowType.Header, i18nKey: 'contactsHeader', }; } // This puts a blank row for the footer. if (rowIndex === this.candidateContacts.length + 1) { return { type: RowType.Blank }; } const contact = this.candidateContacts[rowIndex - 1]; if (!contact) { return undefined; } const isChecked = this.selectedConversationIdsSet.has(contact.id); let disabledReason: undefined | ContactCheckboxDisabledReason; if (!isChecked) { if (this.hasSelectedMaximumNumberOfContacts()) { disabledReason = ContactCheckboxDisabledReason.MaximumContactsSelected; } else if (!contact.isGroupV2Capable) { disabledReason = ContactCheckboxDisabledReason.NotCapable; } } return { type: RowType.ContactCheckbox, contact, isChecked, disabledReason, }; } // This is deliberately unimplemented because these keyboard shortcuts shouldn't work in // the composer. The same is true for the "in direction" function below. getConversationAndMessageAtIndex( ..._args: ReadonlyArray ): undefined { return undefined; } getConversationAndMessageInDirection( ..._args: ReadonlyArray ): undefined { return undefined; } shouldRecomputeRowHeights(_old: unknown): boolean { return false; } private hasSelectedMaximumNumberOfContacts(): boolean { return this.selectedContacts.length >= this.getMaximumNumberOfContacts(); } private hasExceededMaximumNumberOfContacts(): boolean { // It should be impossible to reach this state. This is here as a failsafe. return this.selectedContacts.length > this.getMaximumNumberOfContacts(); } private getRecommendedMaximumNumberOfContacts(): number { return getGroupSizeRecommendedLimit(151) - 1; } private getMaximumNumberOfContacts(): number { return getGroupSizeHardLimit(1001) - 1; } } function focusRef(el: HTMLElement | null) { if (el) { el.focus(); } }