240 lines
6.7 KiB
TypeScript
240 lines
6.7 KiB
TypeScript
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import type { FormEventHandler, FunctionComponent } from 'react';
|
|
import React, { useRef, useState } from 'react';
|
|
|
|
import type { LocalizerType } from '../../../types/Util';
|
|
import { Modal } from '../../Modal';
|
|
import { AvatarEditor } from '../../AvatarEditor';
|
|
import { AvatarPreview } from '../../AvatarPreview';
|
|
import { Button, ButtonVariant } from '../../Button';
|
|
import { Spinner } from '../../Spinner';
|
|
import { GroupDescriptionInput } from '../../GroupDescriptionInput';
|
|
import { GroupTitleInput } from '../../GroupTitleInput';
|
|
import { RequestState } from './util';
|
|
import type {
|
|
AvatarDataType,
|
|
DeleteAvatarFromDiskActionType,
|
|
ReplaceAvatarActionType,
|
|
SaveAvatarToDiskActionType,
|
|
} from '../../../types/Avatar';
|
|
import type { AvatarColorType } from '../../../types/Colors';
|
|
|
|
type PropsType = {
|
|
avatarColor?: AvatarColorType;
|
|
avatarPath?: string;
|
|
conversationId: string;
|
|
groupDescription?: string;
|
|
i18n: LocalizerType;
|
|
initiallyFocusDescription: boolean;
|
|
makeRequest: (
|
|
_: Readonly<{
|
|
avatar?: undefined | Uint8Array;
|
|
description?: string;
|
|
title?: undefined | string;
|
|
}>
|
|
) => void;
|
|
onClose: () => void;
|
|
requestState: RequestState;
|
|
title: string;
|
|
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
|
replaceAvatar: ReplaceAvatarActionType;
|
|
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
|
userAvatarData: Array<AvatarDataType>;
|
|
};
|
|
|
|
export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
|
avatarColor,
|
|
avatarPath: externalAvatarPath,
|
|
conversationId,
|
|
groupDescription: externalGroupDescription = '',
|
|
i18n,
|
|
initiallyFocusDescription,
|
|
makeRequest,
|
|
onClose,
|
|
requestState,
|
|
title: externalTitle,
|
|
deleteAvatarFromDisk,
|
|
replaceAvatar,
|
|
saveAvatarToDisk,
|
|
userAvatarData,
|
|
}) => {
|
|
const focusDescriptionRef = useRef<undefined | boolean>(
|
|
initiallyFocusDescription
|
|
);
|
|
const focusDescription = focusDescriptionRef.current;
|
|
|
|
const startingTitleRef = useRef<string>(externalTitle);
|
|
const startingAvatarPathRef = useRef<undefined | string>(externalAvatarPath);
|
|
|
|
const [editingAvatar, setEditingAvatar] = useState(false);
|
|
const [avatar, setAvatar] = useState<undefined | Uint8Array>();
|
|
const [rawTitle, setRawTitle] = useState(externalTitle);
|
|
const [rawGroupDescription, setRawGroupDescription] = useState(
|
|
externalGroupDescription
|
|
);
|
|
const [hasAvatarChanged, setHasAvatarChanged] = useState(false);
|
|
|
|
const trimmedTitle = rawTitle.trim();
|
|
const trimmedDescription = rawGroupDescription.trim();
|
|
|
|
const focusRef = (el: null | HTMLElement) => {
|
|
if (el) {
|
|
el.focus();
|
|
focusDescriptionRef.current = undefined;
|
|
}
|
|
};
|
|
|
|
const hasChangedExternally =
|
|
startingAvatarPathRef.current !== externalAvatarPath ||
|
|
startingTitleRef.current !== externalTitle;
|
|
const hasTitleChanged = trimmedTitle !== externalTitle.trim();
|
|
const hasGroupDescriptionChanged =
|
|
externalGroupDescription.trim() !== trimmedDescription;
|
|
|
|
const isRequestActive = requestState === RequestState.Active;
|
|
|
|
const canSubmit =
|
|
!isRequestActive &&
|
|
(hasChangedExternally ||
|
|
hasTitleChanged ||
|
|
hasAvatarChanged ||
|
|
hasGroupDescriptionChanged) &&
|
|
trimmedTitle.length > 0;
|
|
|
|
const onSubmit: FormEventHandler<HTMLFormElement> = event => {
|
|
event.preventDefault();
|
|
|
|
const request: {
|
|
avatar?: undefined | Uint8Array;
|
|
description?: string;
|
|
title?: string;
|
|
} = {};
|
|
if (hasAvatarChanged) {
|
|
request.avatar = avatar;
|
|
}
|
|
if (hasTitleChanged) {
|
|
request.title = trimmedTitle;
|
|
}
|
|
if (hasGroupDescriptionChanged) {
|
|
request.description = trimmedDescription;
|
|
}
|
|
makeRequest(request);
|
|
};
|
|
|
|
const avatarPathForPreview = hasAvatarChanged
|
|
? undefined
|
|
: externalAvatarPath;
|
|
|
|
let content: JSX.Element;
|
|
if (editingAvatar) {
|
|
content = (
|
|
<AvatarEditor
|
|
avatarColor={avatarColor}
|
|
avatarPath={avatarPathForPreview}
|
|
avatarValue={avatar}
|
|
conversationId={conversationId}
|
|
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
|
i18n={i18n}
|
|
isGroup
|
|
onCancel={() => {
|
|
setHasAvatarChanged(false);
|
|
setEditingAvatar(false);
|
|
}}
|
|
onSave={newAvatar => {
|
|
setAvatar(newAvatar);
|
|
setHasAvatarChanged(true);
|
|
setEditingAvatar(false);
|
|
}}
|
|
userAvatarData={userAvatarData}
|
|
replaceAvatar={replaceAvatar}
|
|
saveAvatarToDisk={saveAvatarToDisk}
|
|
/>
|
|
);
|
|
} else {
|
|
content = (
|
|
<form
|
|
onSubmit={onSubmit}
|
|
className="module-EditConversationAttributesModal"
|
|
>
|
|
<AvatarPreview
|
|
avatarColor={avatarColor}
|
|
avatarPath={avatarPathForPreview}
|
|
avatarValue={avatar}
|
|
i18n={i18n}
|
|
isEditable
|
|
isGroup
|
|
onClick={() => {
|
|
setEditingAvatar(true);
|
|
}}
|
|
style={{
|
|
height: 96,
|
|
width: 96,
|
|
}}
|
|
/>
|
|
|
|
<GroupTitleInput
|
|
disabled={isRequestActive}
|
|
i18n={i18n}
|
|
onChangeValue={setRawTitle}
|
|
ref={focusDescription === false ? focusRef : undefined}
|
|
value={rawTitle}
|
|
/>
|
|
|
|
<GroupDescriptionInput
|
|
disabled={isRequestActive}
|
|
i18n={i18n}
|
|
onChangeValue={setRawGroupDescription}
|
|
ref={focusDescription === true ? focusRef : undefined}
|
|
value={rawGroupDescription}
|
|
/>
|
|
|
|
<div className="module-EditConversationAttributesModal__description-warning">
|
|
{i18n('EditConversationAttributesModal__description-warning')}
|
|
</div>
|
|
|
|
{requestState === RequestState.InactiveWithError && (
|
|
<div className="module-EditConversationAttributesModal__error-message">
|
|
{i18n('updateGroupAttributes__error-message')}
|
|
</div>
|
|
)}
|
|
|
|
<Modal.ButtonFooter>
|
|
<Button
|
|
disabled={isRequestActive}
|
|
onClick={onClose}
|
|
variant={ButtonVariant.Secondary}
|
|
>
|
|
{i18n('cancel')}
|
|
</Button>
|
|
|
|
<Button
|
|
type="submit"
|
|
variant={ButtonVariant.Primary}
|
|
disabled={!canSubmit}
|
|
>
|
|
{isRequestActive ? (
|
|
<Spinner size="20px" svgSize="small" direction="on-avatar" />
|
|
) : (
|
|
i18n('save')
|
|
)}
|
|
</Button>
|
|
</Modal.ButtonFooter>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
modalName="EditConversationAttributesModal"
|
|
hasStickyButtons
|
|
hasXButton
|
|
i18n={i18n}
|
|
onClose={onClose}
|
|
title={i18n('updateGroupAttributes__title')}
|
|
>
|
|
{content}
|
|
</Modal>
|
|
);
|
|
};
|