Improvements to Group Settings screen
This commit is contained in:
parent
dfa5005e7d
commit
12bba24dbd
11 changed files with 146 additions and 79 deletions
|
@ -3390,6 +3390,10 @@
|
|||
"message": "Admin",
|
||||
"description": "Label for a group administrator"
|
||||
},
|
||||
"GroupV2--only-admins": {
|
||||
"message": "Only Admins",
|
||||
"description": "Label for group administrators -- used in drop-downs to select permissions that apply to admins"
|
||||
},
|
||||
"GroupV2--all-members": {
|
||||
"message": "All members",
|
||||
"description": "Label for describing the general non-privileged members of a group"
|
||||
|
|
|
@ -23,9 +23,11 @@ const conversation: ConversationType = {
|
|||
id: '',
|
||||
lastUpdated: 0,
|
||||
markedUnread: false,
|
||||
memberships: Array.from(Array(32)).map(() => ({
|
||||
memberships: Array.from(Array(32)).map((_, i) => ({
|
||||
isAdmin: false,
|
||||
member: getDefaultConversation({}),
|
||||
member: getDefaultConversation({
|
||||
isMe: i === 2,
|
||||
}),
|
||||
metadata: {
|
||||
conversationId: '',
|
||||
joinedAtVersion: 0,
|
||||
|
|
|
@ -119,7 +119,7 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
/>
|
||||
|
||||
<PanelSection>
|
||||
{isAdmin ? (
|
||||
{isAdmin || hasGroupLink ? (
|
||||
<PanelRow
|
||||
icon={
|
||||
<ConversationDetailsIcon
|
||||
|
|
|
@ -34,15 +34,17 @@ const createMemberships = (
|
|||
isAdmin: i % 3 === 0,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metadata: {} as any,
|
||||
member: getDefaultConversation({}),
|
||||
member: getDefaultConversation({
|
||||
isMe: i === 2,
|
||||
}),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createProps = (overrideProps: Partial<Props>): Props => ({
|
||||
i18n,
|
||||
showContactModal: action('showContactModal'),
|
||||
memberships: overrideProps.memberships || [],
|
||||
showContactModal: action('showContactModal'),
|
||||
});
|
||||
|
||||
story.add('Few', () => {
|
||||
|
|
|
@ -26,26 +26,66 @@ export type Props = {
|
|||
|
||||
const MAX_MEMBER_COUNT = 5;
|
||||
|
||||
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||
function sortConversationTitles(
|
||||
left: GroupV2Membership,
|
||||
right: GroupV2Membership
|
||||
) {
|
||||
const leftTitle = left.member.title;
|
||||
const rightTitle = right.member.title;
|
||||
return collator.compare(leftTitle, rightTitle);
|
||||
}
|
||||
|
||||
function sortMemberships(
|
||||
memberships: ReadonlyArray<GroupV2Membership>
|
||||
): Array<GroupV2Membership> {
|
||||
let you: undefined | GroupV2Membership;
|
||||
const admins: Array<GroupV2Membership> = [];
|
||||
const nonAdmins: Array<GroupV2Membership> = [];
|
||||
memberships.forEach(membershipInfo => {
|
||||
const { isAdmin, member } = membershipInfo;
|
||||
if (member.isMe) {
|
||||
you = membershipInfo;
|
||||
} else if (isAdmin) {
|
||||
admins.push(membershipInfo);
|
||||
} else {
|
||||
nonAdmins.push(membershipInfo);
|
||||
}
|
||||
});
|
||||
admins.sort(sortConversationTitles);
|
||||
nonAdmins.sort(sortConversationTitles);
|
||||
|
||||
const sortedMemberships = [];
|
||||
if (you) {
|
||||
sortedMemberships.push(you);
|
||||
}
|
||||
sortedMemberships.push(...admins);
|
||||
sortedMemberships.push(...nonAdmins);
|
||||
|
||||
return sortedMemberships;
|
||||
}
|
||||
|
||||
export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
|
||||
memberships,
|
||||
showContactModal,
|
||||
i18n,
|
||||
}) => {
|
||||
const [showAllMembers, setShowAllMembers] = React.useState<boolean>(false);
|
||||
const sortedMemberships = sortMemberships(memberships);
|
||||
|
||||
const shouldHideRestMembers = memberships.length - MAX_MEMBER_COUNT > 1;
|
||||
const shouldHideRestMembers = sortedMemberships.length - MAX_MEMBER_COUNT > 1;
|
||||
const membersToShow =
|
||||
shouldHideRestMembers && !showAllMembers
|
||||
? MAX_MEMBER_COUNT
|
||||
: memberships.length;
|
||||
: sortedMemberships.length;
|
||||
|
||||
return (
|
||||
<PanelSection
|
||||
title={i18n('ConversationDetailsMembershipList--title', [
|
||||
memberships.length.toString(),
|
||||
sortedMemberships.length.toString(),
|
||||
])}
|
||||
>
|
||||
{memberships.slice(0, membersToShow).map(({ isAdmin, member }) => (
|
||||
{sortedMemberships.slice(0, membersToShow).map(({ isAdmin, member }) => (
|
||||
<PanelRow
|
||||
key={member.id}
|
||||
onClick={() => showContactModal(member.id)}
|
||||
|
|
|
@ -51,36 +51,50 @@ function getConversation(
|
|||
};
|
||||
}
|
||||
|
||||
const createProps = (conversation?: ConversationType): PropsType => ({
|
||||
const createProps = (
|
||||
conversation?: ConversationType,
|
||||
isAdmin = false
|
||||
): PropsType => ({
|
||||
accessEnum: AccessEnum,
|
||||
changeHasGroupLink: action('changeHasGroupLink'),
|
||||
conversation: conversation || getConversation(),
|
||||
copyGroupLink: action('copyGroupLink'),
|
||||
generateNewGroupLink: action('generateNewGroupLink'),
|
||||
i18n,
|
||||
isAdmin,
|
||||
setAccessControlAddFromInviteLinkSetting: action(
|
||||
'setAccessControlAddFromInviteLinkSetting'
|
||||
),
|
||||
});
|
||||
|
||||
story.add('Off', () => {
|
||||
const props = createProps();
|
||||
story.add('Off (Admin)', () => {
|
||||
const props = createProps(undefined, true);
|
||||
|
||||
return <GroupLinkManagement {...props} />;
|
||||
});
|
||||
|
||||
story.add('On', () => {
|
||||
story.add('On (Admin)', () => {
|
||||
const props = createProps(
|
||||
getConversation('https://signal.group/1', AccessEnum.ANY),
|
||||
true
|
||||
);
|
||||
|
||||
return <GroupLinkManagement {...props} />;
|
||||
});
|
||||
|
||||
story.add('On (Admin + Admin Approval Needed)', () => {
|
||||
const props = createProps(
|
||||
getConversation('https://signal.group/1', AccessEnum.ADMINISTRATOR),
|
||||
true
|
||||
);
|
||||
|
||||
return <GroupLinkManagement {...props} />;
|
||||
});
|
||||
|
||||
story.add('On (Non-admin)', () => {
|
||||
const props = createProps(
|
||||
getConversation('https://signal.group/1', AccessEnum.ANY)
|
||||
);
|
||||
|
||||
return <GroupLinkManagement {...props} />;
|
||||
});
|
||||
|
||||
story.add('On (Admin Approval Needed)', () => {
|
||||
const props = createProps(
|
||||
getConversation('https://signal.group/1', AccessEnum.ADMINISTRATOR)
|
||||
);
|
||||
|
||||
return <GroupLinkManagement {...props} />;
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ export type PropsType = {
|
|||
copyGroupLink: (groupLink: string) => void;
|
||||
generateNewGroupLink: () => void;
|
||||
i18n: LocalizerType;
|
||||
isAdmin: boolean;
|
||||
setAccessControlAddFromInviteLinkSetting: (value: boolean) => void;
|
||||
};
|
||||
|
||||
|
@ -27,6 +28,7 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
|
|||
copyGroupLink,
|
||||
generateNewGroupLink,
|
||||
i18n,
|
||||
isAdmin,
|
||||
setAccessControlAddFromInviteLinkSetting,
|
||||
}) => {
|
||||
if (conversation === undefined) {
|
||||
|
@ -54,19 +56,21 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
|
|||
info={groupLinkInfo}
|
||||
label={i18n('ConversationDetails--group-link')}
|
||||
right={
|
||||
<div className="module-conversation-details-select">
|
||||
<select
|
||||
onChange={createEventHandler(changeHasGroupLink)}
|
||||
value={String(Boolean(hasGroupLink))}
|
||||
>
|
||||
<option value="true" aria-label={i18n('on')}>
|
||||
{i18n('on')}
|
||||
</option>
|
||||
<option value="false" aria-label={i18n('off')}>
|
||||
{i18n('off')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
isAdmin ? (
|
||||
<div className="module-conversation-details-select">
|
||||
<select
|
||||
onChange={createEventHandler(changeHasGroupLink)}
|
||||
value={String(Boolean(hasGroupLink))}
|
||||
>
|
||||
<option value="true" aria-label={i18n('on')}>
|
||||
{i18n('on')}
|
||||
</option>
|
||||
<option value="false" aria-label={i18n('off')}>
|
||||
{i18n('off')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</PanelSection>
|
||||
|
@ -88,41 +92,45 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<PanelRow
|
||||
icon={
|
||||
<ConversationDetailsIcon
|
||||
ariaLabel={i18n('GroupLinkManagement--reset')}
|
||||
icon="reset"
|
||||
/>
|
||||
}
|
||||
label={i18n('GroupLinkManagement--reset')}
|
||||
onClick={generateNewGroupLink}
|
||||
/>
|
||||
{isAdmin ? (
|
||||
<PanelRow
|
||||
icon={
|
||||
<ConversationDetailsIcon
|
||||
ariaLabel={i18n('GroupLinkManagement--reset')}
|
||||
icon="reset"
|
||||
/>
|
||||
}
|
||||
label={i18n('GroupLinkManagement--reset')}
|
||||
onClick={generateNewGroupLink}
|
||||
/>
|
||||
) : null}
|
||||
</PanelSection>
|
||||
|
||||
<PanelSection>
|
||||
<PanelRow
|
||||
info={i18n('GroupLinkManagement--approve-info')}
|
||||
label={i18n('GroupLinkManagement--approve-label')}
|
||||
right={
|
||||
<div className="module-conversation-details-select">
|
||||
<select
|
||||
onChange={createEventHandler(
|
||||
setAccessControlAddFromInviteLinkSetting
|
||||
)}
|
||||
value={String(membersNeedAdminApproval)}
|
||||
>
|
||||
<option value="true" aria-label={i18n('on')}>
|
||||
{i18n('on')}
|
||||
</option>
|
||||
<option value="false" aria-label={i18n('off')}>
|
||||
{i18n('off')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</PanelSection>
|
||||
{isAdmin ? (
|
||||
<PanelSection>
|
||||
<PanelRow
|
||||
info={i18n('GroupLinkManagement--approve-info')}
|
||||
label={i18n('GroupLinkManagement--approve-label')}
|
||||
right={
|
||||
<div className="module-conversation-details-select">
|
||||
<select
|
||||
onChange={createEventHandler(
|
||||
setAccessControlAddFromInviteLinkSetting
|
||||
)}
|
||||
value={String(membersNeedAdminApproval)}
|
||||
>
|
||||
<option value="true" aria-label={i18n('on')}>
|
||||
{i18n('on')}
|
||||
</option>
|
||||
<option value="false" aria-label={i18n('off')}>
|
||||
{i18n('off')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</PanelSection>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
@ -135,7 +135,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
|
||||
verifiedEnum?: typeof window.textsecure.storage.protocol.VerifiedStatus;
|
||||
|
||||
intlCollator = new Intl.Collator();
|
||||
intlCollator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||
|
||||
private cachedLatestGroupCallEraId?: string;
|
||||
|
||||
|
@ -183,14 +183,12 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
// eslint-disable-next-line class-methods-use-this
|
||||
getContactCollection(): Backbone.Collection<ConversationModel> {
|
||||
const collection = new window.Backbone.Collection<ConversationModel>();
|
||||
const collator = new Intl.Collator();
|
||||
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||
collection.comparator = (
|
||||
left: ConversationModel,
|
||||
right: ConversationModel
|
||||
) => {
|
||||
const leftLower = left.getTitle().toLowerCase();
|
||||
const rightLower = right.getTitle().toLowerCase();
|
||||
return collator.compare(leftLower, rightLower);
|
||||
return collator.compare(left.getTitle(), right.getTitle());
|
||||
};
|
||||
return collection;
|
||||
}
|
||||
|
@ -5229,9 +5227,7 @@ const sortConversationTitles = (
|
|||
right: SortableByTitle,
|
||||
collator: Intl.Collator
|
||||
) => {
|
||||
const leftLower = left.getTitle().toLowerCase();
|
||||
const rightLower = right.getTitle().toLowerCase();
|
||||
return collator.compare(leftLower, rightLower);
|
||||
return collator.compare(left.getTitle(), right.getTitle());
|
||||
};
|
||||
|
||||
// We need a custom collection here to get the sorting we need
|
||||
|
@ -5239,7 +5235,7 @@ window.Whisper.GroupConversationCollection = window.Backbone.Collection.extend({
|
|||
model: window.Whisper.GroupMemberConversation,
|
||||
|
||||
initialize() {
|
||||
this.collator = new Intl.Collator();
|
||||
this.collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||
},
|
||||
|
||||
comparator(left: WhatIsThis, right: WhatIsThis) {
|
||||
|
|
|
@ -39,8 +39,7 @@ const mapStateToProps = (
|
|||
conversation && conversation.canEditGroupInfo
|
||||
? conversation.canEditGroupInfo
|
||||
: false;
|
||||
const isAdmin =
|
||||
conversation && conversation.areWeAdmin ? conversation.areWeAdmin : false;
|
||||
const isAdmin = Boolean(conversation?.areWeAdmin);
|
||||
|
||||
return {
|
||||
...props,
|
||||
|
|
|
@ -26,11 +26,13 @@ const mapStateToProps = (
|
|||
props: SmartGroupLinkManagementProps
|
||||
): PropsType => {
|
||||
const conversation = getConversationSelector(state)(props.conversationId);
|
||||
const isAdmin = Boolean(conversation?.areWeAdmin);
|
||||
|
||||
return {
|
||||
...props,
|
||||
conversation,
|
||||
i18n: getIntl(state),
|
||||
isAdmin,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export function getAccessControlOptions(
|
|||
value: accessEnum.MEMBER,
|
||||
},
|
||||
{
|
||||
name: i18n('GroupV2--admin'),
|
||||
name: i18n('GroupV2--only-admins'),
|
||||
value: accessEnum.ADMINISTRATOR,
|
||||
},
|
||||
];
|
||||
|
|
Loading…
Add table
Reference in a new issue