{
throw new Error('Loaded image was not a canvas');
}
- return (await canvasToBlob(image)).arrayBuffer();
-}
-
-function canvasToBlob(canvas: HTMLCanvasElement): Promise
{
- return new Promise((resolve, reject) => {
- canvas.toBlob(blob => {
- if (blob) {
- resolve(blob);
- } else {
- reject(new Error("Couldn't convert the canvas to a Blob"));
- }
- }, 'image/webp');
- });
+ return canvasToArrayBuffer(image);
}
diff --git a/ts/components/Button.tsx b/ts/components/Button.tsx
index 596902d6452c..229886996d7c 100644
--- a/ts/components/Button.tsx
+++ b/ts/components/Button.tsx
@@ -16,9 +16,15 @@ type PropsType = {
children: ReactNode;
className?: string;
disabled?: boolean;
- onClick: MouseEventHandler;
variant?: ButtonVariant;
-};
+} & (
+ | {
+ onClick: MouseEventHandler;
+ }
+ | {
+ type: 'submit';
+ }
+);
const VARIANT_CLASS_NAMES = new Map([
[ButtonVariant.Primary, 'module-Button--primary'],
@@ -27,16 +33,24 @@ const VARIANT_CLASS_NAMES = new Map([
]);
export const Button = React.forwardRef(
- (
- {
+ (props, ref) => {
+ const {
children,
className,
disabled = false,
- onClick,
variant = ButtonVariant.Primary,
- },
- ref
- ) => {
+ } = props;
+
+ let onClick: undefined | MouseEventHandler;
+ let type: 'button' | 'submit';
+ if ('onClick' in props) {
+ ({ onClick } = props);
+ type = 'button';
+ } else {
+ onClick = undefined;
+ ({ type } = props);
+ }
+
const variantClassName = VARIANT_CLASS_NAMES.get(variant);
assert(variantClassName, '
diff --git a/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx
index 99a816eb1f49..457ff6bce409 100644
--- a/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx
+++ b/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx
@@ -60,6 +60,7 @@ const createProps = (hasGroupLink = false): Props => ({
showGroupV2Permissions: action('showGroupV2Permissions'),
showPendingInvites: action('showPendingInvites'),
showLightboxForMedia: action('showLightboxForMedia'),
+ updateGroupAttributes: action('updateGroupAttributes'),
onBlockAndDelete: action('onBlockAndDelete'),
onDelete: action('onDelete'),
});
diff --git a/ts/components/conversation/conversation-details/ConversationDetails.tsx b/ts/components/conversation/conversation-details/ConversationDetails.tsx
index 7a5897b23a93..9d150412e7d1 100644
--- a/ts/components/conversation/conversation-details/ConversationDetails.tsx
+++ b/ts/components/conversation/conversation-details/ConversationDetails.tsx
@@ -1,7 +1,7 @@
-// Copyright 2020 Signal Messenger, LLC
+// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-import React from 'react';
+import React, { useState } from 'react';
import { ConversationType } from '../../../state/ducks/conversations';
import {
@@ -18,6 +18,10 @@ import { ConversationDetailsHeader } from './ConversationDetailsHeader';
import { ConversationDetailsIcon } from './ConversationDetailsIcon';
import { ConversationDetailsMediaList } from './ConversationDetailsMediaList';
import { ConversationDetailsMembershipList } from './ConversationDetailsMembershipList';
+import {
+ EditConversationAttributesModal,
+ RequestState as EditGroupAttributesRequestState,
+} from './EditConversationAttributesModal';
export type StateProps = {
canEditGroupInfo: boolean;
@@ -36,6 +40,12 @@ export type StateProps = {
selectedMediaItem: MediaItemType,
media: Array
) => void;
+ updateGroupAttributes: (
+ _: Readonly<{
+ avatar?: undefined | ArrayBuffer;
+ title?: string;
+ }>
+ ) => void;
onBlockAndDelete: () => void;
onDelete: () => void;
};
@@ -56,9 +66,20 @@ export const ConversationDetails: React.ComponentType = ({
showGroupV2Permissions,
showPendingInvites,
showLightboxForMedia,
+ updateGroupAttributes,
onBlockAndDelete,
onDelete,
}) => {
+ const [isEditingGroupAttributes, setIsEditingGroupAttributes] = useState(
+ false
+ );
+ const [
+ editGroupAttributesRequestState,
+ setEditGroupAttributesRequestState,
+ ] = useState(
+ EditGroupAttributesRequestState.Inactive
+ );
+
const updateExpireTimer = (event: React.ChangeEvent) => {
setDisappearingMessages(parseInt(event.target.value, 10));
};
@@ -75,7 +96,14 @@ export const ConversationDetails: React.ComponentType = ({
return (
-
+
{
+ setIsEditingGroupAttributes(true);
+ }}
+ />
{canEditGroupInfo ? (
@@ -171,6 +199,43 @@ export const ConversationDetails: React.ComponentType = ({
onDelete={onDelete}
onBlockAndDelete={onBlockAndDelete}
/>
+
+ {isEditingGroupAttributes && (
+
+ ) => {
+ setEditGroupAttributesRequestState(
+ EditGroupAttributesRequestState.Active
+ );
+
+ try {
+ await updateGroupAttributes(options);
+ setIsEditingGroupAttributes(false);
+ setEditGroupAttributesRequestState(
+ EditGroupAttributesRequestState.Inactive
+ );
+ } catch (err) {
+ setEditGroupAttributesRequestState(
+ EditGroupAttributesRequestState.InactiveWithError
+ );
+ }
+ }}
+ onClose={() => {
+ setIsEditingGroupAttributes(false);
+ setEditGroupAttributesRequestState(
+ EditGroupAttributesRequestState.Inactive
+ );
+ }}
+ requestState={editGroupAttributesRequestState}
+ title={conversation.title}
+ />
+ )}
);
};
diff --git a/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx
index 9bbe6ea285dd..c869286f8b0f 100644
--- a/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx
+++ b/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx
@@ -1,9 +1,10 @@
-// Copyright 2020 Signal Messenger, LLC
+// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
import { number, text } from '@storybook/addon-knobs';
import { setup as setupI18n } from '../../../../js/modules/i18n';
@@ -15,7 +16,7 @@ import { ConversationDetailsHeader, Props } from './ConversationDetailsHeader';
const i18n = setupI18n('en', enMessages);
const story = storiesOf(
- 'Components/Conversation/ConversationDetails/ConversationDetailHeader',
+ 'Components/Conversation/ConversationDetails/ConversationDetailsHeader',
module
);
@@ -28,9 +29,12 @@ const createConversation = (): ConversationType => ({
memberships: new Array(number('conversation members length', 0)),
});
-const createProps = (): Props => ({
+const createProps = (overrideProps: Partial = {}): Props => ({
conversation: createConversation(),
i18n,
+ canEdit: false,
+ startEditing: action('startEditing'),
+ ...overrideProps,
});
story.add('Basic', () => {
@@ -38,3 +42,9 @@ story.add('Basic', () => {
return ;
});
+
+story.add('Editable', () => {
+ const props = createProps({ canEdit: true });
+
+ return ;
+});
diff --git a/ts/components/conversation/conversation-details/ConversationDetailsHeader.tsx b/ts/components/conversation/conversation-details/ConversationDetailsHeader.tsx
index 25111fcf2eed..2fd6dc93c2fd 100644
--- a/ts/components/conversation/conversation-details/ConversationDetailsHeader.tsx
+++ b/ts/components/conversation/conversation-details/ConversationDetailsHeader.tsx
@@ -1,4 +1,4 @@
-// Copyright 2020 Signal Messenger, LLC
+// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
@@ -9,20 +9,24 @@ import { ConversationType } from '../../../state/ducks/conversations';
import { bemGenerator } from './util';
export type Props = {
- i18n: LocalizerType;
+ canEdit: boolean;
conversation: ConversationType;
+ i18n: LocalizerType;
+ startEditing: () => void;
};
const bem = bemGenerator('module-conversation-details-header');
export const ConversationDetailsHeader: React.ComponentType = ({
- i18n,
+ canEdit,
conversation,
+ i18n,
+ startEditing,
}) => {
const memberships = conversation.memberships || [];
- return (
-
+ const contents = (
+ <>
= ({
])}
-
+ >
);
+
+ if (canEdit) {
+ return (
+