signal-desktop/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx

223 lines
5.6 KiB
TypeScript

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { ReactChild } from 'react';
import { LeftPaneHelper } from './LeftPaneHelper';
import { Row, RowType } from '../ConversationList';
import { PropsDataType as ContactListItemPropsType } from '../conversationList/ContactListItem';
import { LocalizerType } from '../../types/Util';
import { AvatarInput } from '../AvatarInput';
import { Alert } from '../Alert';
import { Spinner } from '../Spinner';
import { Button } from '../Button';
import { GroupTitleInput } from '../GroupTitleInput';
export type LeftPaneSetGroupMetadataPropsType = {
groupAvatar: undefined | ArrayBuffer;
groupName: string;
hasError: boolean;
isCreating: boolean;
selectedContacts: ReadonlyArray<ContactListItemPropsType>;
};
/* eslint-disable class-methods-use-this */
export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<
LeftPaneSetGroupMetadataPropsType
> {
private readonly groupAvatar: undefined | ArrayBuffer;
private readonly groupName: string;
private readonly hasError: boolean;
private readonly isCreating: boolean;
private readonly selectedContacts: ReadonlyArray<ContactListItemPropsType>;
constructor({
groupAvatar,
groupName,
isCreating,
hasError,
selectedContacts,
}: Readonly<LeftPaneSetGroupMetadataPropsType>) {
super();
this.groupAvatar = groupAvatar;
this.groupName = groupName;
this.hasError = hasError;
this.isCreating = isCreating;
this.selectedContacts = selectedContacts;
}
getHeaderContents({
i18n,
showChooseGroupMembers,
}: Readonly<{
i18n: LocalizerType;
showChooseGroupMembers: () => void;
}>): ReactChild {
const backButtonLabel = i18n('setGroupMetadata__back-button');
return (
<div className="module-left-pane__header__contents">
<button
aria-label={backButtonLabel}
className="module-left-pane__header__contents__back-button"
disabled={this.isCreating}
onClick={this.getBackAction({ showChooseGroupMembers })}
title={backButtonLabel}
type="button"
/>
<div className="module-left-pane__header__contents__text">
{i18n('setGroupMetadata__title')}
</div>
</div>
);
}
getBackAction({
showChooseGroupMembers,
}: {
showChooseGroupMembers: () => void;
}): undefined | (() => void) {
return this.isCreating ? undefined : showChooseGroupMembers;
}
getPreRowsNode({
clearGroupCreationError,
createGroup,
i18n,
setComposeGroupAvatar,
setComposeGroupName,
}: Readonly<{
clearGroupCreationError: () => unknown;
createGroup: () => unknown;
i18n: LocalizerType;
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown;
setComposeGroupName: (_: string) => unknown;
}>): ReactChild {
const disabled = this.isCreating;
return (
<form
className="module-left-pane__header__form"
onSubmit={event => {
event.preventDefault();
event.stopPropagation();
if (!this.canCreateGroup()) {
return;
}
createGroup();
}}
>
<AvatarInput
contextMenuId="left pane group avatar uploader"
disabled={disabled}
i18n={i18n}
onChange={setComposeGroupAvatar}
value={this.groupAvatar}
/>
<GroupTitleInput
disabled={disabled}
i18n={i18n}
onChangeValue={setComposeGroupName}
ref={focusRef}
value={this.groupName}
/>
{this.hasError && (
<Alert
body={i18n('setGroupMetadata__error-message')}
i18n={i18n}
onClose={clearGroupCreationError}
/>
)}
</form>
);
}
getFooterContents({
createGroup,
i18n,
}: Readonly<{
createGroup: () => unknown;
i18n: LocalizerType;
}>): ReactChild {
return (
<Button disabled={!this.canCreateGroup()} onClick={createGroup}>
{this.isCreating ? (
<Spinner size="20px" svgSize="small" direction="on-avatar" />
) : (
i18n('setGroupMetadata__create-group')
)}
</Button>
);
}
getRowCount(): number {
if (!this.selectedContacts.length) {
return 0;
}
return this.selectedContacts.length + 2;
}
getRow(rowIndex: number): undefined | Row {
if (!this.selectedContacts.length) {
return undefined;
}
if (rowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'setGroupMetadata__members-header',
};
}
// This puts a blank row for the footer.
if (rowIndex === this.selectedContacts.length + 1) {
return { type: RowType.Blank };
}
const contact = this.selectedContacts[rowIndex - 1];
return contact
? {
type: RowType.Contact,
contact,
isClickable: false,
}
: undefined;
}
// 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<unknown>
): undefined {
return undefined;
}
getConversationAndMessageInDirection(
..._args: ReadonlyArray<unknown>
): undefined {
return undefined;
}
shouldRecomputeRowHeights(_old: unknown): boolean {
return false;
}
private canCreateGroup(): boolean {
return !this.isCreating && Boolean(this.groupName.trim());
}
}
function focusRef(el: HTMLElement | null) {
if (el) {
el.focus();
}
}