Moves ConversationDetails to react panels
This commit is contained in:
parent
ff3ef0179b
commit
d4124abb01
16 changed files with 220 additions and 199 deletions
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Meta, Story } from '@storybook/react';
|
import type { Meta, Story } from '@storybook/react';
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
|
|
||||||
import type { Props } from './AddUserToAnotherGroupModal';
|
import type { Props } from './AddUserToAnotherGroupModal';
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
@ -30,12 +29,8 @@ export default {
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultValue: i18n,
|
defaultValue: i18n,
|
||||||
},
|
},
|
||||||
addMemberToGroup: {
|
addMembersToGroup: { action: true },
|
||||||
defaultValue: action('addMemberToGroup'),
|
toggleAddUserToAnotherGroupModal: { action: true },
|
||||||
},
|
|
||||||
toggleAddUserToAnotherGroupModal: {
|
|
||||||
defaultValue: action('toggleAddUserToAnotherGroupModal'),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,13 @@ type OwnProps = {
|
||||||
|
|
||||||
type DispatchProps = {
|
type DispatchProps = {
|
||||||
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
||||||
addMemberToGroup: (
|
addMembersToGroup: (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
contactId: string,
|
contactIds: Array<string>,
|
||||||
onComplete: () => void
|
opts: {
|
||||||
|
onSuccess?: () => unknown;
|
||||||
|
onFailure?: () => unknown;
|
||||||
|
}
|
||||||
) => void;
|
) => void;
|
||||||
showToast: (toastType: ToastType, parameters?: ReplacementValuesType) => void;
|
showToast: (toastType: ToastType, parameters?: ReplacementValuesType) => void;
|
||||||
};
|
};
|
||||||
|
@ -47,7 +50,7 @@ export function AddUserToAnotherGroupModal({
|
||||||
theme,
|
theme,
|
||||||
contact,
|
contact,
|
||||||
toggleAddUserToAnotherGroupModal,
|
toggleAddUserToAnotherGroupModal,
|
||||||
addMemberToGroup,
|
addMembersToGroup,
|
||||||
showToast,
|
showToast,
|
||||||
candidateConversations,
|
candidateConversations,
|
||||||
regionCode,
|
regionCode,
|
||||||
|
@ -203,12 +206,13 @@ export function AddUserToAnotherGroupModal({
|
||||||
showToast(ToastType.AddingUserToGroup, {
|
showToast(ToastType.AddingUserToGroup, {
|
||||||
contact: contact.title,
|
contact: contact.title,
|
||||||
});
|
});
|
||||||
addMemberToGroup(selectedGroupId, contact.id, () =>
|
addMembersToGroup(selectedGroupId, [contact.id], {
|
||||||
showToast(ToastType.UserAddedToGroup, {
|
onSuccess: () =>
|
||||||
contact: contact.title,
|
showToast(ToastType.UserAddedToGroup, {
|
||||||
group: selectedGroup.title,
|
contact: contact.title,
|
||||||
})
|
group: selectedGroup.title,
|
||||||
);
|
}),
|
||||||
|
});
|
||||||
toggleAddUserToAnotherGroupModal(undefined);
|
toggleAddUserToAnotherGroupModal(undefined);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,7 +37,6 @@ const commonProps = {
|
||||||
|
|
||||||
i18n,
|
i18n,
|
||||||
|
|
||||||
onShowConversationDetails: action('onShowConversationDetails'),
|
|
||||||
setDisappearingMessages: action('setDisappearingMessages'),
|
setDisappearingMessages: action('setDisappearingMessages'),
|
||||||
destroyMessages: action('destroyMessages'),
|
destroyMessages: action('destroyMessages'),
|
||||||
onSearchInConversation: action('onSearchInConversation'),
|
onSearchInConversation: action('onSearchInConversation'),
|
||||||
|
|
|
@ -93,7 +93,6 @@ export type PropsActionsType = {
|
||||||
onOutgoingVideoCallInConversation: (conversationId: string) => void;
|
onOutgoingVideoCallInConversation: (conversationId: string) => void;
|
||||||
onSearchInConversation: () => void;
|
onSearchInConversation: () => void;
|
||||||
onShowAllMedia: () => void;
|
onShowAllMedia: () => void;
|
||||||
onShowConversationDetails: () => void;
|
|
||||||
pushPanelForConversation: PushPanelForConversationActionType;
|
pushPanelForConversation: PushPanelForConversationActionType;
|
||||||
setDisappearingMessages: (
|
setDisappearingMessages: (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
|
@ -352,7 +351,6 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
onMarkUnread,
|
onMarkUnread,
|
||||||
onMoveToInbox,
|
onMoveToInbox,
|
||||||
onShowAllMedia,
|
onShowAllMedia,
|
||||||
onShowConversationDetails,
|
|
||||||
pushPanelForConversation,
|
pushPanelForConversation,
|
||||||
setDisappearingMessages,
|
setDisappearingMessages,
|
||||||
setMuteExpiration,
|
setMuteExpiration,
|
||||||
|
@ -475,7 +473,13 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
))}
|
))}
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
{!isGroup || hasGV2AdminEnabled ? (
|
{!isGroup || hasGV2AdminEnabled ? (
|
||||||
<MenuItem onClick={onShowConversationDetails}>
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
pushPanelForConversation(id, {
|
||||||
|
type: PanelType.ConversationDetails,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
{isGroup
|
{isGroup
|
||||||
? i18n('showConversationDetails')
|
? i18n('showConversationDetails')
|
||||||
: i18n('showConversationDetails--direct')}
|
: i18n('showConversationDetails--direct')}
|
||||||
|
@ -552,8 +556,13 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderHeader(): ReactNode {
|
private renderHeader(): ReactNode {
|
||||||
const { conversationTitle, groupVersion, onShowConversationDetails, type } =
|
const {
|
||||||
this.props;
|
conversationTitle,
|
||||||
|
id,
|
||||||
|
groupVersion,
|
||||||
|
pushPanelForConversation,
|
||||||
|
type,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (conversationTitle !== undefined) {
|
if (conversationTitle !== undefined) {
|
||||||
return (
|
return (
|
||||||
|
@ -571,14 +580,16 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'direct':
|
case 'direct':
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
onShowConversationDetails();
|
pushPanelForConversation(id, { type: PanelType.ConversationDetails });
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'group': {
|
case 'group': {
|
||||||
const hasGV2AdminEnabled = groupVersion === 2;
|
const hasGV2AdminEnabled = groupVersion === 2;
|
||||||
onClick = hasGV2AdminEnabled
|
onClick = hasGV2AdminEnabled
|
||||||
? () => {
|
? () => {
|
||||||
onShowConversationDetails();
|
pushPanelForConversation(id, {
|
||||||
|
type: PanelType.ConversationDetails,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -41,8 +41,8 @@ const createProps = (
|
||||||
expireTimer?: DurationInSeconds
|
expireTimer?: DurationInSeconds
|
||||||
): Props => ({
|
): Props => ({
|
||||||
acceptConversation: action('acceptConversation'),
|
acceptConversation: action('acceptConversation'),
|
||||||
addMembers: async () => {
|
addMembersToGroup: async () => {
|
||||||
action('addMembers');
|
action('addMembersToGroup');
|
||||||
},
|
},
|
||||||
areWeASubscriber: false,
|
areWeASubscriber: false,
|
||||||
blockConversation: action('blockConversation'),
|
blockConversation: action('blockConversation'),
|
||||||
|
@ -57,10 +57,12 @@ const createProps = (
|
||||||
hasActiveCall: false,
|
hasActiveCall: false,
|
||||||
hasGroupLink,
|
hasGroupLink,
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
|
getProfilesForConversation: action('getProfilesForConversation'),
|
||||||
groupsInCommon: [],
|
groupsInCommon: [],
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
isGroup: true,
|
isGroup: true,
|
||||||
|
leaveGroup: action('leaveGroup'),
|
||||||
loadRecentMediaItems: action('loadRecentMediaItems'),
|
loadRecentMediaItems: action('loadRecentMediaItems'),
|
||||||
memberships: times(32, i => ({
|
memberships: times(32, i => ({
|
||||||
isAdmin: i === 1,
|
isAdmin: i === 1,
|
||||||
|
@ -78,7 +80,6 @@ const createProps = (
|
||||||
member: getDefaultConversation(),
|
member: getDefaultConversation(),
|
||||||
})),
|
})),
|
||||||
setDisappearingMessages: action('setDisappearingMessages'),
|
setDisappearingMessages: action('setDisappearingMessages'),
|
||||||
showAllMedia: action('showAllMedia'),
|
|
||||||
showContactModal: action('showContactModal'),
|
showContactModal: action('showContactModal'),
|
||||||
pushPanelForConversation: action('pushPanelForConversation'),
|
pushPanelForConversation: action('pushPanelForConversation'),
|
||||||
showConversation: action('showConversation'),
|
showConversation: action('showConversation'),
|
||||||
|
@ -86,7 +87,6 @@ const createProps = (
|
||||||
updateGroupAttributes: async () => {
|
updateGroupAttributes: async () => {
|
||||||
action('updateGroupAttributes')();
|
action('updateGroupAttributes')();
|
||||||
},
|
},
|
||||||
onLeave: action('onLeave'),
|
|
||||||
deleteAvatarFromDisk: action('deleteAvatarFromDisk'),
|
deleteAvatarFromDisk: action('deleteAvatarFromDisk'),
|
||||||
replaceAvatar: action('replaceAvatar'),
|
replaceAvatar: action('replaceAvatar'),
|
||||||
saveAvatarToDisk: action('saveAvatarToDisk'),
|
saveAvatarToDisk: action('saveAvatarToDisk'),
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Button, ButtonIconType, ButtonVariant } from '../../Button';
|
import { Button, ButtonIconType, ButtonVariant } from '../../Button';
|
||||||
import { Tooltip } from '../../Tooltip';
|
import { Tooltip } from '../../Tooltip';
|
||||||
|
@ -63,7 +63,6 @@ enum ModalState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
|
||||||
areWeASubscriber: boolean;
|
areWeASubscriber: boolean;
|
||||||
badges?: ReadonlyArray<BadgeType>;
|
badges?: ReadonlyArray<BadgeType>;
|
||||||
canEditGroupInfo: boolean;
|
canEditGroupInfo: boolean;
|
||||||
|
@ -81,15 +80,6 @@ export type StateProps = {
|
||||||
memberships: Array<GroupV2Membership>;
|
memberships: Array<GroupV2Membership>;
|
||||||
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||||
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||||
showAllMedia: () => void;
|
|
||||||
updateGroupAttributes: (
|
|
||||||
_: Readonly<{
|
|
||||||
avatar?: undefined | Uint8Array;
|
|
||||||
description?: string;
|
|
||||||
title?: string;
|
|
||||||
}>
|
|
||||||
) => Promise<void>;
|
|
||||||
onLeave: () => void;
|
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
userAvatarData: Array<AvatarDataType>;
|
userAvatarData: Array<AvatarDataType>;
|
||||||
renderChooseGroupMembersModal: (
|
renderChooseGroupMembersModal: (
|
||||||
|
@ -102,8 +92,18 @@ export type StateProps = {
|
||||||
|
|
||||||
type ActionProps = {
|
type ActionProps = {
|
||||||
acceptConversation: (id: string) => void;
|
acceptConversation: (id: string) => void;
|
||||||
|
addMembersToGroup: (
|
||||||
|
conversationId: string,
|
||||||
|
conversationIds: ReadonlyArray<string>,
|
||||||
|
opts: {
|
||||||
|
onSuccess?: () => unknown;
|
||||||
|
onFailure?: () => unknown;
|
||||||
|
}
|
||||||
|
) => unknown;
|
||||||
blockConversation: (id: string) => void;
|
blockConversation: (id: string) => void;
|
||||||
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
||||||
|
getProfilesForConversation: (id: string) => unknown;
|
||||||
|
leaveGroup: (conversationId: string) => void;
|
||||||
loadRecentMediaItems: (id: string, limit: number) => void;
|
loadRecentMediaItems: (id: string, limit: number) => void;
|
||||||
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
||||||
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
||||||
|
@ -117,13 +117,25 @@ type ActionProps = {
|
||||||
showConversation: ShowConversationType;
|
showConversation: ShowConversationType;
|
||||||
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
||||||
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
||||||
|
updateGroupAttributes: (
|
||||||
|
conversationId: string,
|
||||||
|
_: Readonly<{
|
||||||
|
avatar?: undefined | Uint8Array;
|
||||||
|
description?: string;
|
||||||
|
title?: string;
|
||||||
|
}>,
|
||||||
|
opts: {
|
||||||
|
onSuccess?: () => unknown;
|
||||||
|
onFailure?: () => unknown;
|
||||||
|
}
|
||||||
|
) => unknown;
|
||||||
} & Pick<ConversationDetailsMediaListPropsType, 'showLightboxWithMedia'>;
|
} & Pick<ConversationDetailsMediaListPropsType, 'showLightboxWithMedia'>;
|
||||||
|
|
||||||
export type Props = StateProps & ActionProps;
|
export type Props = StateProps & ActionProps;
|
||||||
|
|
||||||
export function ConversationDetails({
|
export function ConversationDetails({
|
||||||
acceptConversation,
|
acceptConversation,
|
||||||
addMembers,
|
addMembersToGroup,
|
||||||
areWeASubscriber,
|
areWeASubscriber,
|
||||||
badges,
|
badges,
|
||||||
blockConversation,
|
blockConversation,
|
||||||
|
@ -133,16 +145,17 @@ export function ConversationDetails({
|
||||||
deleteAvatarFromDisk,
|
deleteAvatarFromDisk,
|
||||||
hasGroupLink,
|
hasGroupLink,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
|
getProfilesForConversation,
|
||||||
groupsInCommon,
|
groupsInCommon,
|
||||||
hasActiveCall,
|
hasActiveCall,
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isGroup,
|
isGroup,
|
||||||
|
leaveGroup,
|
||||||
loadRecentMediaItems,
|
loadRecentMediaItems,
|
||||||
memberships,
|
memberships,
|
||||||
maxGroupSize,
|
maxGroupSize,
|
||||||
maxRecommendedGroupSize,
|
maxRecommendedGroupSize,
|
||||||
onLeave,
|
|
||||||
onOutgoingAudioCallInConversation,
|
onOutgoingAudioCallInConversation,
|
||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
pendingApprovalMemberships,
|
pendingApprovalMemberships,
|
||||||
|
@ -155,7 +168,6 @@ export function ConversationDetails({
|
||||||
searchInConversation,
|
searchInConversation,
|
||||||
setDisappearingMessages,
|
setDisappearingMessages,
|
||||||
setMuteExpiration,
|
setMuteExpiration,
|
||||||
showAllMedia,
|
|
||||||
showContactModal,
|
showContactModal,
|
||||||
showConversation,
|
showConversation,
|
||||||
showLightboxWithMedia,
|
showLightboxWithMedia,
|
||||||
|
@ -177,6 +189,10 @@ export function ConversationDetails({
|
||||||
throw new Error('ConversationDetails rendered without a conversation');
|
throw new Error('ConversationDetails rendered without a conversation');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getProfilesForConversation(conversation.id);
|
||||||
|
}, [conversation.id, getProfilesForConversation]);
|
||||||
|
|
||||||
const invitesCount =
|
const invitesCount =
|
||||||
pendingMemberships.length + pendingApprovalMemberships.length;
|
pendingMemberships.length + pendingApprovalMemberships.length;
|
||||||
|
|
||||||
|
@ -214,15 +230,17 @@ export function ConversationDetails({
|
||||||
) => {
|
) => {
|
||||||
setEditGroupAttributesRequestState(RequestState.Active);
|
setEditGroupAttributesRequestState(RequestState.Active);
|
||||||
|
|
||||||
try {
|
updateGroupAttributes(conversation.id, options, {
|
||||||
await updateGroupAttributes(options);
|
onSuccess: () => {
|
||||||
setModalState(ModalState.NothingOpen);
|
setModalState(ModalState.NothingOpen);
|
||||||
setEditGroupAttributesRequestState(RequestState.Inactive);
|
setEditGroupAttributesRequestState(RequestState.Inactive);
|
||||||
} catch (err) {
|
},
|
||||||
setEditGroupAttributesRequestState(
|
onFailure: () => {
|
||||||
RequestState.InactiveWithError
|
setEditGroupAttributesRequestState(
|
||||||
);
|
RequestState.InactiveWithError
|
||||||
}
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setModalState(ModalState.NothingOpen);
|
setModalState(ModalState.NothingOpen);
|
||||||
|
@ -259,13 +277,15 @@ export function ConversationDetails({
|
||||||
makeRequest={async conversationIds => {
|
makeRequest={async conversationIds => {
|
||||||
setAddGroupMembersRequestState(RequestState.Active);
|
setAddGroupMembersRequestState(RequestState.Active);
|
||||||
|
|
||||||
try {
|
addMembersToGroup(conversation.id, conversationIds, {
|
||||||
await addMembers(conversationIds);
|
onSuccess: () => {
|
||||||
setModalState(ModalState.NothingOpen);
|
setModalState(ModalState.NothingOpen);
|
||||||
setAddGroupMembersRequestState(RequestState.Inactive);
|
setAddGroupMembersRequestState(RequestState.Inactive);
|
||||||
} catch (err) {
|
},
|
||||||
setAddGroupMembersRequestState(RequestState.InactiveWithError);
|
onFailure: () => {
|
||||||
}
|
setAddGroupMembersRequestState(RequestState.InactiveWithError);
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
maxGroupSize={maxGroupSize}
|
maxGroupSize={maxGroupSize}
|
||||||
maxRecommendedGroupSize={maxRecommendedGroupSize}
|
maxRecommendedGroupSize={maxRecommendedGroupSize}
|
||||||
|
@ -545,7 +565,11 @@ export function ConversationDetails({
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
loadRecentMediaItems={loadRecentMediaItems}
|
loadRecentMediaItems={loadRecentMediaItems}
|
||||||
showAllMedia={showAllMedia}
|
showAllMedia={() =>
|
||||||
|
pushPanelForConversation(conversation.id, {
|
||||||
|
type: PanelType.AllMedia,
|
||||||
|
})
|
||||||
|
}
|
||||||
showLightboxWithMedia={showLightboxWithMedia}
|
showLightboxWithMedia={showLightboxWithMedia}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -570,7 +594,7 @@ export function ConversationDetails({
|
||||||
isBlocked={Boolean(conversation.isBlocked)}
|
isBlocked={Boolean(conversation.isBlocked)}
|
||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
left={Boolean(conversation.left)}
|
left={Boolean(conversation.left)}
|
||||||
onLeave={onLeave}
|
onLeave={() => leaveGroup(conversation.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -2416,43 +2416,6 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addMembersV2(conversationIds: ReadonlyArray<string>): Promise<void> {
|
|
||||||
await this.modifyGroupV2({
|
|
||||||
name: 'addMembersV2',
|
|
||||||
usingCredentialsFrom: conversationIds
|
|
||||||
.map(id => window.ConversationController.get(id))
|
|
||||||
.filter(isNotNil),
|
|
||||||
createGroupChange: () =>
|
|
||||||
window.Signal.Groups.buildAddMembersChange(
|
|
||||||
this.attributes,
|
|
||||||
conversationIds
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateGroupAttributesV2(
|
|
||||||
attributes: Readonly<{
|
|
||||||
avatar?: undefined | Uint8Array;
|
|
||||||
description?: string;
|
|
||||||
title?: string;
|
|
||||||
}>
|
|
||||||
): Promise<void> {
|
|
||||||
await this.modifyGroupV2({
|
|
||||||
name: 'updateGroupAttributesV2',
|
|
||||||
usingCredentialsFrom: [],
|
|
||||||
createGroupChange: () =>
|
|
||||||
window.Signal.Groups.buildUpdateAttributesChange(
|
|
||||||
{
|
|
||||||
id: this.id,
|
|
||||||
publicParams: this.get('publicParams'),
|
|
||||||
revision: this.get('revision'),
|
|
||||||
secretParams: this.get('secretParams'),
|
|
||||||
},
|
|
||||||
attributes
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async leaveGroupV2(): Promise<void> {
|
async leaveGroupV2(): Promise<void> {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
|
||||||
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
import { createConversationDetails } from './state/roots/createConversationDetails';
|
|
||||||
import { createApp } from './state/roots/createApp';
|
import { createApp } from './state/roots/createApp';
|
||||||
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
||||||
import { createMessageDetail } from './state/roots/createMessageDetail';
|
import { createMessageDetail } from './state/roots/createMessageDetail';
|
||||||
|
@ -395,7 +394,6 @@ export const setup = (options: {
|
||||||
|
|
||||||
const Roots = {
|
const Roots = {
|
||||||
createApp,
|
createApp,
|
||||||
createConversationDetails,
|
|
||||||
createGroupV2JoinModal,
|
createGroupV2JoinModal,
|
||||||
createMessageDetail,
|
createMessageDetail,
|
||||||
createSafetyNumberViewer,
|
createSafetyNumberViewer,
|
||||||
|
|
|
@ -114,11 +114,14 @@ import { addReportSpamJob } from '../../jobs/helpers/addReportSpamJob';
|
||||||
import { reportSpamJobQueue } from '../../jobs/reportSpamJobQueue';
|
import { reportSpamJobQueue } from '../../jobs/reportSpamJobQueue';
|
||||||
import {
|
import {
|
||||||
modifyGroupV2,
|
modifyGroupV2,
|
||||||
|
buildAddMembersChange,
|
||||||
buildPromotePendingAdminApprovalMemberChange,
|
buildPromotePendingAdminApprovalMemberChange,
|
||||||
|
buildUpdateAttributesChange,
|
||||||
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
||||||
} from '../../groups';
|
} from '../../groups';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { getMessageById } from '../../messages/getMessageById';
|
||||||
import type { PanelRenderType } from '../../types/Panels';
|
import type { PanelRenderType } from '../../types/Panels';
|
||||||
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -856,7 +859,7 @@ export type ConversationActionType =
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
acceptConversation,
|
acceptConversation,
|
||||||
addMemberToGroup,
|
addMembersToGroup,
|
||||||
approvePendingMembershipFromGroupV2,
|
approvePendingMembershipFromGroupV2,
|
||||||
blockAndReportSpam,
|
blockAndReportSpam,
|
||||||
blockConversation,
|
blockConversation,
|
||||||
|
@ -888,7 +891,9 @@ export const actions = {
|
||||||
discardMessages,
|
discardMessages,
|
||||||
doubleCheckMissingQuoteReference,
|
doubleCheckMissingQuoteReference,
|
||||||
generateNewGroupLink,
|
generateNewGroupLink,
|
||||||
|
getProfilesForConversation,
|
||||||
initiateMigrationToGroupV2,
|
initiateMigrationToGroupV2,
|
||||||
|
leaveGroup,
|
||||||
loadRecentMediaItems,
|
loadRecentMediaItems,
|
||||||
messageChanged,
|
messageChanged,
|
||||||
messageDeleted,
|
messageDeleted,
|
||||||
|
@ -942,6 +947,7 @@ export const actions = {
|
||||||
toggleGroupsForStorySend,
|
toggleGroupsForStorySend,
|
||||||
toggleHideStories,
|
toggleHideStories,
|
||||||
updateConversationModelSharedGroups,
|
updateConversationModelSharedGroups,
|
||||||
|
updateGroupAttributes,
|
||||||
verifyConversationsStoppingSend,
|
verifyConversationsStoppingSend,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1911,6 +1917,20 @@ function selectMessage(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getProfilesForConversation(conversationId: string): NoopActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('getProfilesForConversation: no conversation found');
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.getProfiles();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function conversationStoppedByMissingVerification(payload: {
|
function conversationStoppedByMissingVerification(payload: {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
distributionId?: string;
|
distributionId?: string;
|
||||||
|
@ -2808,22 +2828,102 @@ function removeMemberFromGroup(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMemberToGroup(
|
function addMembersToGroup(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
contactId: string,
|
contactIds: ReadonlyArray<string>,
|
||||||
onComplete: () => void
|
{
|
||||||
|
onSuccess,
|
||||||
|
onFailure,
|
||||||
|
}: {
|
||||||
|
onSuccess?: () => unknown;
|
||||||
|
onFailure?: () => unknown;
|
||||||
|
} = {}
|
||||||
): ThunkAction<void, RootStateType, unknown, never> {
|
): ThunkAction<void, RootStateType, unknown, never> {
|
||||||
return async () => {
|
return async () => {
|
||||||
const conversationModel = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (conversationModel) {
|
if (!conversation) {
|
||||||
const idForLogging = conversationModel.idForLogging();
|
throw new Error('addMembersToGroup: No conversation found');
|
||||||
await longRunningTaskWrapper({
|
|
||||||
name: 'addMemberToGroup',
|
|
||||||
idForLogging,
|
|
||||||
task: () => conversationModel.addMembersV2([contactId]),
|
|
||||||
});
|
|
||||||
onComplete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idForLogging = conversation.idForLogging();
|
||||||
|
try {
|
||||||
|
await longRunningTaskWrapper({
|
||||||
|
name: 'addMembersToGroup',
|
||||||
|
idForLogging,
|
||||||
|
task: () =>
|
||||||
|
modifyGroupV2({
|
||||||
|
name: 'addMembersToGroup',
|
||||||
|
conversation,
|
||||||
|
usingCredentialsFrom: contactIds
|
||||||
|
.map(id => window.ConversationController.get(id))
|
||||||
|
.filter(isNotNil),
|
||||||
|
createGroupChange: async () =>
|
||||||
|
buildAddMembersChange(conversation.attributes, contactIds),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
onSuccess?.();
|
||||||
|
} catch {
|
||||||
|
onFailure?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGroupAttributes(
|
||||||
|
conversationId: string,
|
||||||
|
attributes: Readonly<{
|
||||||
|
avatar?: undefined | Uint8Array;
|
||||||
|
description?: string;
|
||||||
|
title?: string;
|
||||||
|
}>,
|
||||||
|
{
|
||||||
|
onSuccess,
|
||||||
|
onFailure,
|
||||||
|
}: {
|
||||||
|
onSuccess?: () => unknown;
|
||||||
|
onFailure?: () => unknown;
|
||||||
|
} = {}
|
||||||
|
): ThunkAction<void, RootStateType, unknown, never> {
|
||||||
|
return async () => {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('updateGroupAttributes: No conversation found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, publicParams, revision, secretParams } =
|
||||||
|
conversation.attributes;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await modifyGroupV2({
|
||||||
|
name: 'updateGroupAttributes',
|
||||||
|
conversation,
|
||||||
|
usingCredentialsFrom: [],
|
||||||
|
createGroupChange: async () =>
|
||||||
|
buildUpdateAttributesChange(
|
||||||
|
{ id, publicParams, revision, secretParams },
|
||||||
|
attributes
|
||||||
|
),
|
||||||
|
});
|
||||||
|
onSuccess?.();
|
||||||
|
} catch {
|
||||||
|
onFailure?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function leaveGroup(
|
||||||
|
conversationId: string
|
||||||
|
): ThunkAction<void, RootStateType, unknown, never> {
|
||||||
|
return async () => {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('leaveGroup: No conversation found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await longRunningTaskWrapper({
|
||||||
|
idForLogging: conversation.idForLogging(),
|
||||||
|
name: 'leaveGroup',
|
||||||
|
task: () => conversation.leaveGroupV2(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
|
|
||||||
import type { Store } from 'redux';
|
|
||||||
|
|
||||||
import type { SmartConversationDetailsProps } from '../smart/ConversationDetails';
|
|
||||||
import { SmartConversationDetails } from '../smart/ConversationDetails';
|
|
||||||
|
|
||||||
export const createConversationDetails = (
|
|
||||||
store: Store,
|
|
||||||
props: SmartConversationDetailsProps
|
|
||||||
): React.ReactElement => (
|
|
||||||
<Provider store={store}>
|
|
||||||
<SmartConversationDetails {...props} />
|
|
||||||
</Provider>
|
|
||||||
);
|
|
|
@ -35,16 +35,7 @@ import {
|
||||||
} from '../../groups/limits';
|
} from '../../groups/limits';
|
||||||
|
|
||||||
export type SmartConversationDetailsProps = {
|
export type SmartConversationDetailsProps = {
|
||||||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
showAllMedia: () => void;
|
|
||||||
updateGroupAttributes: (
|
|
||||||
_: Readonly<{
|
|
||||||
avatar?: undefined | Uint8Array;
|
|
||||||
title?: string;
|
|
||||||
}>
|
|
||||||
) => Promise<void>;
|
|
||||||
onLeave: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||||
|
|
|
@ -36,7 +36,6 @@ export type OwnProps = {
|
||||||
onMoveToInbox: () => void;
|
onMoveToInbox: () => void;
|
||||||
onSearchInConversation: () => void;
|
onSearchInConversation: () => void;
|
||||||
onShowAllMedia: () => void;
|
onShowAllMedia: () => void;
|
||||||
onShowConversationDetails: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOutgoingCallButtonStyle = (
|
const getOutgoingCallButtonStyle = (
|
||||||
|
|
|
@ -14,11 +14,12 @@ import { ConversationView } from '../../components/conversation/ConversationView
|
||||||
import { PanelType } from '../../types/Panels';
|
import { PanelType } from '../../types/Panels';
|
||||||
import { SmartChatColorPicker } from './ChatColorPicker';
|
import { SmartChatColorPicker } from './ChatColorPicker';
|
||||||
import { SmartCompositionArea } from './CompositionArea';
|
import { SmartCompositionArea } from './CompositionArea';
|
||||||
import { SmartConversationNotificationsSettings } from './ConversationNotificationsSettings';
|
import { SmartConversationDetails } from './ConversationDetails';
|
||||||
import { SmartConversationHeader } from './ConversationHeader';
|
import { SmartConversationHeader } from './ConversationHeader';
|
||||||
|
import { SmartConversationNotificationsSettings } from './ConversationNotificationsSettings';
|
||||||
|
import { SmartGV1Members } from './GV1Members';
|
||||||
import { SmartGroupLinkManagement } from './GroupLinkManagement';
|
import { SmartGroupLinkManagement } from './GroupLinkManagement';
|
||||||
import { SmartGroupV2Permissions } from './GroupV2Permissions';
|
import { SmartGroupV2Permissions } from './GroupV2Permissions';
|
||||||
import { SmartGV1Members } from './GV1Members';
|
|
||||||
import { SmartPendingInvites } from './PendingInvites';
|
import { SmartPendingInvites } from './PendingInvites';
|
||||||
import { SmartStickerManager } from './StickerManager';
|
import { SmartStickerManager } from './StickerManager';
|
||||||
import { SmartTimeline } from './Timeline';
|
import { SmartTimeline } from './Timeline';
|
||||||
|
@ -102,6 +103,14 @@ export function SmartConversationView({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (topPanel.type === PanelType.ConversationDetails) {
|
||||||
|
return (
|
||||||
|
<div className="panel conversation-details-pane">
|
||||||
|
<SmartConversationDetails conversationId={conversationId} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (topPanel.type === PanelType.GroupInvites) {
|
if (topPanel.type === PanelType.GroupInvites) {
|
||||||
return (
|
return (
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
|
|
|
@ -30,6 +30,7 @@ export type ReactPanelRenderType =
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
| { type: PanelType.ConversationDetails }
|
||||||
| { type: PanelType.GroupInvites }
|
| { type: PanelType.GroupInvites }
|
||||||
| { type: PanelType.GroupLinkManagement }
|
| { type: PanelType.GroupLinkManagement }
|
||||||
| { type: PanelType.GroupPermissions }
|
| { type: PanelType.GroupPermissions }
|
||||||
|
@ -39,7 +40,6 @@ export type ReactPanelRenderType =
|
||||||
|
|
||||||
export type BackbonePanelRenderType =
|
export type BackbonePanelRenderType =
|
||||||
| { type: PanelType.AllMedia }
|
| { type: PanelType.AllMedia }
|
||||||
| { type: PanelType.ConversationDetails }
|
|
||||||
| { type: PanelType.MessageDetails; args: { messageId: string } };
|
| { type: PanelType.MessageDetails; args: { messageId: string } };
|
||||||
|
|
||||||
export type PanelRenderType = ReactPanelRenderType | BackbonePanelRenderType;
|
export type PanelRenderType = ReactPanelRenderType | BackbonePanelRenderType;
|
||||||
|
@ -54,6 +54,7 @@ export function isPanelHandledByReact(
|
||||||
return (
|
return (
|
||||||
panel.type === PanelType.ChatColorEditor ||
|
panel.type === PanelType.ChatColorEditor ||
|
||||||
panel.type === PanelType.ContactDetails ||
|
panel.type === PanelType.ContactDetails ||
|
||||||
|
panel.type === PanelType.ConversationDetails ||
|
||||||
panel.type === PanelType.GroupInvites ||
|
panel.type === PanelType.GroupInvites ||
|
||||||
panel.type === PanelType.GroupLinkManagement ||
|
panel.type === PanelType.GroupLinkManagement ||
|
||||||
panel.type === PanelType.GroupPermissions ||
|
panel.type === PanelType.GroupPermissions ||
|
||||||
|
|
|
@ -184,9 +184,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
const { searchInConversation } = window.reduxActions.search;
|
const { searchInConversation } = window.reduxActions.search;
|
||||||
searchInConversation(this.model.id);
|
searchInConversation(this.model.id);
|
||||||
},
|
},
|
||||||
onShowConversationDetails: () => {
|
|
||||||
this.showConversationDetails();
|
|
||||||
},
|
|
||||||
onShowAllMedia: () => {
|
onShowAllMedia: () => {
|
||||||
this.showAllMedia();
|
this.showAllMedia();
|
||||||
},
|
},
|
||||||
|
@ -783,53 +780,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showConversationDetails(): void {
|
|
||||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
|
||||||
type: PanelType.ConversationDetails,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getConversationDetails(): Backbone.View {
|
|
||||||
// Run a getProfiles in case member's capabilities have changed
|
|
||||||
// Redux should cover us on the return here so no need to await this.
|
|
||||||
if (this.model.throttledGetProfiles) {
|
|
||||||
this.model.throttledGetProfiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
// these methods are used in more than one place and should probably be
|
|
||||||
// dried up and hoisted to methods on ConversationView
|
|
||||||
|
|
||||||
const onLeave = () => {
|
|
||||||
longRunningTaskWrapper({
|
|
||||||
idForLogging: this.model.idForLogging(),
|
|
||||||
name: 'onLeave',
|
|
||||||
task: () => this.model.leaveGroupV2(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
addMembers: this.model.addMembersV2.bind(this.model),
|
|
||||||
conversationId: this.model.get('id'),
|
|
||||||
showAllMedia: this.showAllMedia.bind(this),
|
|
||||||
updateGroupAttributes: this.model.updateGroupAttributesV2.bind(
|
|
||||||
this.model
|
|
||||||
),
|
|
||||||
onLeave,
|
|
||||||
};
|
|
||||||
|
|
||||||
const view = new ReactWrapperView({
|
|
||||||
className: 'conversation-details-pane panel',
|
|
||||||
JSX: window.Signal.State.Roots.createConversationDetails(
|
|
||||||
window.reduxStore,
|
|
||||||
props
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
view.render();
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
showMessageDetail(messageId: string): void {
|
showMessageDetail(messageId: string): void {
|
||||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
type: PanelType.MessageDetails,
|
type: PanelType.MessageDetails,
|
||||||
|
@ -904,8 +854,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
let view: Backbone.View | undefined;
|
let view: Backbone.View | undefined;
|
||||||
if (type === PanelType.AllMedia) {
|
if (type === PanelType.AllMedia) {
|
||||||
view = this.getAllMedia();
|
view = this.getAllMedia();
|
||||||
} else if (type === PanelType.ConversationDetails) {
|
|
||||||
view = this.getConversationDetails();
|
|
||||||
} else if (panel.type === PanelType.MessageDetails) {
|
} else if (panel.type === PanelType.MessageDetails) {
|
||||||
view = this.getMessageDetail(panel.args);
|
view = this.getMessageDetail(panel.args);
|
||||||
}
|
}
|
||||||
|
|
2
ts/window.d.ts
vendored
2
ts/window.d.ts
vendored
|
@ -37,7 +37,6 @@ import type { ConversationController } from './ConversationController';
|
||||||
import type { ReduxActions } from './state/types';
|
import type { ReduxActions } from './state/types';
|
||||||
import type { createStore } from './state/createStore';
|
import type { createStore } from './state/createStore';
|
||||||
import type { createApp } from './state/roots/createApp';
|
import type { createApp } from './state/roots/createApp';
|
||||||
import type { createConversationDetails } from './state/roots/createConversationDetails';
|
|
||||||
import type { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
import type { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
||||||
import type { createMessageDetail } from './state/roots/createMessageDetail';
|
import type { createMessageDetail } from './state/roots/createMessageDetail';
|
||||||
import type { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
|
import type { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
|
||||||
|
@ -161,7 +160,6 @@ export type SignalCoreType = {
|
||||||
createStore: typeof createStore;
|
createStore: typeof createStore;
|
||||||
Roots: {
|
Roots: {
|
||||||
createApp: typeof createApp;
|
createApp: typeof createApp;
|
||||||
createConversationDetails: typeof createConversationDetails;
|
|
||||||
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
||||||
createMessageDetail: typeof createMessageDetail;
|
createMessageDetail: typeof createMessageDetail;
|
||||||
createSafetyNumberViewer: typeof createSafetyNumberViewer;
|
createSafetyNumberViewer: typeof createSafetyNumberViewer;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue