Support for announcement-only groups

This commit is contained in:
Josh Perez 2021-07-20 16:18:35 -04:00 committed by GitHub
parent 863ae9ed83
commit 56d5d283bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1057 additions and 455 deletions

View file

@ -41,6 +41,8 @@ export type PropsDataType = {
} & Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'announcementsOnly'
| 'areWeAdmin'
| 'avatarPath'
| 'canChangeTimer'
| 'color'
@ -291,6 +293,8 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
private renderOutgoingCallButtons(): ReactNode {
const {
announcementsOnly,
areWeAdmin,
i18n,
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
@ -301,15 +305,18 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
const videoButton = (
<button
type="button"
onClick={onOutgoingVideoCallInConversation}
aria-label={i18n('makeOutgoingVideoCall')}
className={classNames(
'module-ConversationHeader__button',
'module-ConversationHeader__button--video',
showBackButton ? null : 'module-ConversationHeader__button--show'
showBackButton ? null : 'module-ConversationHeader__button--show',
!showBackButton && announcementsOnly && !areWeAdmin
? 'module-ConversationHeader__button--show-disabled'
: undefined
)}
disabled={showBackButton}
aria-label={i18n('makeOutgoingVideoCall')}
onClick={onOutgoingVideoCallInConversation}
type="button"
/>
);
@ -341,14 +348,14 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
return (
<button
aria-label={i18n('joinOngoingCall')}
type="button"
onClick={onOutgoingVideoCallInConversation}
className={classNames(
'module-ConversationHeader__button',
'module-ConversationHeader__button--join-call',
showBackButton ? null : 'module-ConversationHeader__button--show'
)}
disabled={showBackButton}
onClick={onOutgoingVideoCallInConversation}
type="button"
>
{isNarrow ? null : i18n('joinOngoingCall')}
</button>

View file

@ -1383,4 +1383,62 @@ storiesOf('Components/Conversation/GroupV2Change', module)
)}
</>
);
})
.add('Announcement Group (Change)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'announcements-only',
announcementsOnly: true,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'announcements-only',
announcementsOnly: true,
},
],
})}
{renderChange({
details: [
{
type: 'announcements-only',
announcementsOnly: true,
},
],
})}
{renderChange({
from: OUR_ID,
details: [
{
type: 'announcements-only',
announcementsOnly: false,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'announcements-only',
announcementsOnly: false,
},
],
})}
{renderChange({
details: [
{
type: 'announcements-only',
announcementsOnly: false,
},
],
})}
</>
);
});

View file

@ -139,3 +139,15 @@ story.add('Group Links On', () => {
return <ConversationDetails {...props} isAdmin />;
});
story.add('Group add with missing capabilities', () => (
<ConversationDetails
{...createProps()}
canEditGroupInfo
addMembers={async () => {
const error = new Error();
error.code = 'E_NO_CAPABILITY';
throw error;
}}
/>
));

View file

@ -30,6 +30,7 @@ import {
import { EditConversationAttributesModal } from './EditConversationAttributesModal';
import { RequestState } from './util';
import { getCustomColorStyle } from '../../../util/getCustomColorStyle';
import { ConfirmationDialog } from '../../ConfirmationDialog';
enum ModalState {
NothingOpen,
@ -109,6 +110,9 @@ export const ConversationDetails: React.ComponentType<Props> = ({
addGroupMembersRequestState,
setAddGroupMembersRequestState,
] = useState<RequestState>(RequestState.Inactive);
const [membersMissingCapability, setMembersMissingCapability] = useState(
false
);
if (conversation === undefined) {
throw new Error('ConversationDetails rendered without a conversation');
@ -194,7 +198,12 @@ export const ConversationDetails: React.ComponentType<Props> = ({
setModalState(ModalState.NothingOpen);
setAddGroupMembersRequestState(RequestState.Inactive);
} catch (err) {
setAddGroupMembersRequestState(RequestState.InactiveWithError);
if (err.code === 'E_NO_CAPABILITY') {
setMembersMissingCapability(true);
setAddGroupMembersRequestState(RequestState.InactiveWithError);
} else {
setAddGroupMembersRequestState(RequestState.InactiveWithError);
}
}
}}
onClose={() => {
@ -211,6 +220,16 @@ export const ConversationDetails: React.ComponentType<Props> = ({
return (
<div className="conversation-details-panel">
{membersMissingCapability && (
<ConfirmationDialog
cancelText={i18n('Confirmation--confirm')}
i18n={i18n}
onClose={() => setMembersMissingCapability(false)}
>
{i18n('GroupV2--add--missing-capability')}
</ConfirmationDialog>
)}
<ConversationDetailsHeader
canEdit={canEditGroupInfo}
conversation={conversation}

View file

@ -27,6 +27,8 @@ const conversation: ConversationType = getDefaultConversation({
title: 'Some Conversation',
type: 'group',
sharedGroupNames: [],
announcementsOnlyReady: true,
areWeAdmin: true,
});
const createProps = (): PropsType => ({
@ -36,6 +38,7 @@ const createProps = (): PropsType => ({
'setAccessControlAttributesSetting'
),
setAccessControlMembersSetting: action('setAccessControlMembersSetting'),
setAnnouncementsOnly: action('setAnnouncementsOnly'),
});
story.add('Basic', () => {

View file

@ -6,6 +6,7 @@ import React from 'react';
import { ConversationType } from '../../../state/ducks/conversations';
import { LocalizerType } from '../../../types/Util';
import { getAccessControlOptions } from '../../../util/getAccessControlOptions';
import { SignalService as Proto } from '../../../protobuf';
import { PanelRow } from './PanelRow';
import { PanelSection } from './PanelSection';
@ -16,14 +17,16 @@ export type PropsType = {
i18n: LocalizerType;
setAccessControlAttributesSetting: (value: number) => void;
setAccessControlMembersSetting: (value: number) => void;
setAnnouncementsOnly: (value: boolean) => void;
};
export const GroupV2Permissions: React.ComponentType<PropsType> = ({
export const GroupV2Permissions = ({
conversation,
i18n,
setAccessControlAttributesSetting,
setAccessControlMembersSetting,
}) => {
setAnnouncementsOnly,
}: PropsType): JSX.Element => {
if (conversation === undefined) {
throw new Error('GroupV2Permissions rendered without a conversation');
}
@ -34,7 +37,16 @@ export const GroupV2Permissions: React.ComponentType<PropsType> = ({
const updateAccessControlMembers = (value: string) => {
setAccessControlMembersSetting(Number(value));
};
const AccessControlEnum = Proto.AccessControl.AccessRequired;
const updateAnnouncementsOnly = (value: string) => {
setAnnouncementsOnly(Number(value) === AccessControlEnum.ADMINISTRATOR);
};
const accessControlOptions = getAccessControlOptions(i18n);
const announcementsOnlyValue = String(
conversation.announcementsOnly
? AccessControlEnum.ADMINISTRATOR
: AccessControlEnum.MEMBER
);
return (
<PanelSection>
@ -60,6 +72,19 @@ export const GroupV2Permissions: React.ComponentType<PropsType> = ({
/>
}
/>
{conversation.areWeAdmin && conversation.announcementsOnlyReady && (
<PanelRow
label={i18n('ConversationDetails--announcement-label')}
info={i18n('ConversationDetails--announcement-info')}
right={
<Select
onChange={updateAnnouncementsOnly}
options={accessControlOptions}
value={announcementsOnlyValue}
/>
}
/>
)}
</PanelSection>
);
};