Support for local deletes synced to all your devices
This commit is contained in:
parent
06f71a7ef8
commit
11eb1782a7
39 changed files with 2094 additions and 72 deletions
78
ts/components/DeleteMessagesModal.stories.tsx
Normal file
78
ts/components/DeleteMessagesModal.stories.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import DeleteMessagesModal from './DeleteMessagesModal';
|
||||
import type { DeleteMessagesModalProps } from './DeleteMessagesModal';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
export default {
|
||||
title: 'Components/DeleteMessagesModal',
|
||||
component: DeleteMessagesModal,
|
||||
args: {
|
||||
i18n,
|
||||
isMe: false,
|
||||
isDeleteSyncSendEnabled: false,
|
||||
canDeleteForEveryone: true,
|
||||
messageCount: 1,
|
||||
onClose: action('onClose'),
|
||||
onDeleteForMe: action('onDeleteForMe'),
|
||||
onDeleteForEveryone: action('onDeleteForEveryone'),
|
||||
showToast: action('showToast'),
|
||||
},
|
||||
} satisfies Meta<DeleteMessagesModalProps>;
|
||||
|
||||
function createProps(args: Partial<DeleteMessagesModalProps>) {
|
||||
return {
|
||||
i18n,
|
||||
isMe: false,
|
||||
isDeleteSyncSendEnabled: false,
|
||||
canDeleteForEveryone: true,
|
||||
messageCount: 1,
|
||||
onClose: action('onClose'),
|
||||
onDeleteForMe: action('onDeleteForMe'),
|
||||
onDeleteForEveryone: action('onDeleteForEveryone'),
|
||||
showToast: action('showToast'),
|
||||
...args,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const Template: StoryFn<DeleteMessagesModalProps> = args => {
|
||||
return <DeleteMessagesModal {...args} />;
|
||||
};
|
||||
|
||||
export const OneMessage = Template.bind({});
|
||||
|
||||
export const ThreeMessages = Template.bind({});
|
||||
ThreeMessages.args = createProps({
|
||||
messageCount: 3,
|
||||
});
|
||||
|
||||
export const IsMe = Template.bind({});
|
||||
IsMe.args = createProps({
|
||||
isMe: true,
|
||||
});
|
||||
|
||||
export const IsMeThreeMessages = Template.bind({});
|
||||
IsMeThreeMessages.args = createProps({
|
||||
isMe: true,
|
||||
messageCount: 3,
|
||||
});
|
||||
|
||||
export const DeleteSyncEnabled = Template.bind({});
|
||||
DeleteSyncEnabled.args = createProps({
|
||||
isDeleteSyncSendEnabled: true,
|
||||
});
|
||||
|
||||
export const IsMeDeleteSyncEnabled = Template.bind({});
|
||||
IsMeDeleteSyncEnabled.args = createProps({
|
||||
isDeleteSyncSendEnabled: true,
|
||||
isMe: true,
|
||||
});
|
|
@ -8,8 +8,9 @@ import type { LocalizerType } from '../types/Util';
|
|||
import type { ShowToastAction } from '../state/ducks/toast';
|
||||
import { ToastType } from '../types/Toast';
|
||||
|
||||
type DeleteMessagesModalProps = Readonly<{
|
||||
export type DeleteMessagesModalProps = Readonly<{
|
||||
isMe: boolean;
|
||||
isDeleteSyncSendEnabled: boolean;
|
||||
canDeleteForEveryone: boolean;
|
||||
i18n: LocalizerType;
|
||||
messageCount: number;
|
||||
|
@ -23,6 +24,7 @@ const MAX_DELETE_FOR_EVERYONE = 30;
|
|||
|
||||
export default function DeleteMessagesModal({
|
||||
isMe,
|
||||
isDeleteSyncSendEnabled,
|
||||
canDeleteForEveryone,
|
||||
i18n,
|
||||
messageCount,
|
||||
|
@ -33,15 +35,22 @@ export default function DeleteMessagesModal({
|
|||
}: DeleteMessagesModalProps): JSX.Element {
|
||||
const actions: Array<ActionSpec> = [];
|
||||
|
||||
const syncNoteToSelfDelete = isMe && isDeleteSyncSendEnabled;
|
||||
|
||||
let deleteForMeText = i18n('icu:DeleteMessagesModal--deleteForMe');
|
||||
if (syncNoteToSelfDelete) {
|
||||
deleteForMeText = i18n('icu:DeleteMessagesModal--noteToSelf--deleteSync');
|
||||
} else if (isMe) {
|
||||
deleteForMeText = i18n('icu:DeleteMessagesModal--deleteFromThisDevice');
|
||||
}
|
||||
|
||||
actions.push({
|
||||
action: onDeleteForMe,
|
||||
style: 'negative',
|
||||
text: isMe
|
||||
? i18n('icu:DeleteMessagesModal--deleteFromThisDevice')
|
||||
: i18n('icu:DeleteMessagesModal--deleteForMe'),
|
||||
text: deleteForMeText,
|
||||
});
|
||||
|
||||
if (canDeleteForEveryone) {
|
||||
if (canDeleteForEveryone && !syncNoteToSelfDelete) {
|
||||
const tooManyMessages = messageCount > MAX_DELETE_FOR_EVERYONE;
|
||||
actions.push({
|
||||
'aria-disabled': tooManyMessages,
|
||||
|
@ -63,6 +72,20 @@ export default function DeleteMessagesModal({
|
|||
});
|
||||
}
|
||||
|
||||
let descriptionText = i18n('icu:DeleteMessagesModal--description', {
|
||||
count: messageCount,
|
||||
});
|
||||
if (syncNoteToSelfDelete) {
|
||||
descriptionText = i18n(
|
||||
'icu:DeleteMessagesModal--description--noteToSelf--deleteSync',
|
||||
{ count: messageCount }
|
||||
);
|
||||
} else if (isMe) {
|
||||
descriptionText = i18n('icu:DeleteMessagesModal--description--noteToSelf', {
|
||||
count: messageCount,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
actions={actions}
|
||||
|
@ -74,13 +97,7 @@ export default function DeleteMessagesModal({
|
|||
})}
|
||||
moduleClassName="DeleteMessagesModal"
|
||||
>
|
||||
{isMe
|
||||
? i18n('icu:DeleteMessagesModal--description--noteToSelf', {
|
||||
count: messageCount,
|
||||
})
|
||||
: i18n('icu:DeleteMessagesModal--description', {
|
||||
count: messageCount,
|
||||
})}
|
||||
{descriptionText}
|
||||
</ConfirmationDialog>
|
||||
);
|
||||
}
|
||||
|
|
30
ts/components/LocalDeleteWarningModal.stories.tsx
Normal file
30
ts/components/LocalDeleteWarningModal.stories.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import React from 'react';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import type { PropsType } from './LocalDeleteWarningModal';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { LocalDeleteWarningModal } from './LocalDeleteWarningModal';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
export default {
|
||||
title: 'Components/LocalDeleteWarningModal',
|
||||
component: LocalDeleteWarningModal,
|
||||
args: {
|
||||
i18n,
|
||||
onClose: action('onClose'),
|
||||
},
|
||||
} satisfies Meta<PropsType>;
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const Template: StoryFn<PropsType> = args => (
|
||||
<LocalDeleteWarningModal {...args} />
|
||||
);
|
||||
|
||||
export const Modal = Template.bind({});
|
||||
Modal.args = {};
|
53
ts/components/LocalDeleteWarningModal.tsx
Normal file
53
ts/components/LocalDeleteWarningModal.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
import { I18n } from './I18n';
|
||||
import { Modal } from './Modal';
|
||||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
onClose: () => unknown;
|
||||
};
|
||||
|
||||
export function LocalDeleteWarningModal({
|
||||
i18n,
|
||||
onClose,
|
||||
}: PropsType): JSX.Element {
|
||||
return (
|
||||
<Modal
|
||||
modalName="LocalDeleteWarningModal"
|
||||
moduleClassName="LocalDeleteWarningModal"
|
||||
i18n={i18n}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="LocalDeleteWarningModal">
|
||||
<div className="LocalDeleteWarningModal__image">
|
||||
<img
|
||||
src="images/local-delete-sync.svg"
|
||||
height="92"
|
||||
width="138"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="LocalDeleteWarningModal__header">
|
||||
<I18n i18n={i18n} id="icu:LocalDeleteWarningModal__header" />
|
||||
</div>
|
||||
|
||||
<div className="LocalDeleteWarningModal__description">
|
||||
<I18n i18n={i18n} id="icu:LocalDeleteWarningModal__description" />
|
||||
</div>
|
||||
|
||||
<div className="LocalDeleteWarningModal__button">
|
||||
<Button onClick={onClose} variant={ButtonVariant.Primary}>
|
||||
<I18n i18n={i18n} id="icu:LocalDeleteWarningModal__confirm" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -46,6 +46,10 @@ const commonProps = {
|
|||
|
||||
i18n,
|
||||
|
||||
localDeleteWarningShown: true,
|
||||
isDeleteSyncSendEnabled: true,
|
||||
setLocalDeleteWarningShown: action('setLocalDeleteWarningShown'),
|
||||
|
||||
onConversationAccept: action('onConversationAccept'),
|
||||
onConversationArchive: action('onConversationArchive'),
|
||||
onConversationBlock: action('onConversationBlock'),
|
||||
|
@ -412,3 +416,32 @@ export function Unaccepted(): JSX.Element {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function NeedsDeleteConfirmation(): JSX.Element {
|
||||
const [localDeleteWarningShown, setLocalDeleteWarningShown] =
|
||||
React.useState(false);
|
||||
const props = {
|
||||
...commonProps,
|
||||
conversation: getDefaultConversation(),
|
||||
localDeleteWarningShown,
|
||||
setLocalDeleteWarningShown: () => setLocalDeleteWarningShown(true),
|
||||
};
|
||||
const theme = useContext(StorybookThemeContext);
|
||||
|
||||
return <ConversationHeader {...props} theme={theme} />;
|
||||
}
|
||||
|
||||
export function NeedsDeleteConfirmationButNotEnabled(): JSX.Element {
|
||||
const [localDeleteWarningShown, setLocalDeleteWarningShown] =
|
||||
React.useState(false);
|
||||
const props = {
|
||||
...commonProps,
|
||||
conversation: getDefaultConversation(),
|
||||
localDeleteWarningShown,
|
||||
isDeleteSyncSendEnabled: false,
|
||||
setLocalDeleteWarningShown: () => setLocalDeleteWarningShown(true),
|
||||
};
|
||||
const theme = useContext(StorybookThemeContext);
|
||||
|
||||
return <ConversationHeader {...props} theme={theme} />;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
MessageRequestState,
|
||||
} from './MessageRequestActionsConfirmation';
|
||||
import type { MinimalConversation } from '../../hooks/useMinimalConversation';
|
||||
import { LocalDeleteWarningModal } from '../LocalDeleteWarningModal';
|
||||
|
||||
function HeaderInfoTitle({
|
||||
name,
|
||||
|
@ -92,6 +93,8 @@ export type PropsDataType = {
|
|||
conversationName: ContactNameData;
|
||||
hasPanelShowing?: boolean;
|
||||
hasStories?: HasStories;
|
||||
localDeleteWarningShown: boolean;
|
||||
isDeleteSyncSendEnabled: boolean;
|
||||
isMissingMandatoryProfileSharing?: boolean;
|
||||
isSelectMode: boolean;
|
||||
isSignalConversation?: boolean;
|
||||
|
@ -102,6 +105,8 @@ export type PropsDataType = {
|
|||
};
|
||||
|
||||
export type PropsActionsType = {
|
||||
setLocalDeleteWarningShown: () => void;
|
||||
|
||||
onConversationAccept: () => void;
|
||||
onConversationArchive: () => void;
|
||||
onConversationBlock: () => void;
|
||||
|
@ -147,10 +152,12 @@ export const ConversationHeader = memo(function ConversationHeader({
|
|||
hasPanelShowing,
|
||||
hasStories,
|
||||
i18n,
|
||||
isDeleteSyncSendEnabled,
|
||||
isMissingMandatoryProfileSharing,
|
||||
isSelectMode,
|
||||
isSignalConversation,
|
||||
isSMSOnly,
|
||||
localDeleteWarningShown,
|
||||
onConversationAccept,
|
||||
onConversationArchive,
|
||||
onConversationBlock,
|
||||
|
@ -174,6 +181,7 @@ export const ConversationHeader = memo(function ConversationHeader({
|
|||
onViewRecentMedia,
|
||||
onViewUserStories,
|
||||
outgoingCallButtonStyle,
|
||||
setLocalDeleteWarningShown,
|
||||
sharedGroupNames,
|
||||
theme,
|
||||
}: PropsType): JSX.Element | null {
|
||||
|
@ -223,13 +231,16 @@ export const ConversationHeader = memo(function ConversationHeader({
|
|||
{hasDeleteMessagesConfirmation && (
|
||||
<DeleteMessagesConfirmationDialog
|
||||
i18n={i18n}
|
||||
onDestoryMessages={() => {
|
||||
isDeleteSyncSendEnabled={isDeleteSyncSendEnabled}
|
||||
localDeleteWarningShown={localDeleteWarningShown}
|
||||
onDestroyMessages={() => {
|
||||
setHasDeleteMessagesConfirmation(false);
|
||||
onConversationDeleteMessages();
|
||||
}}
|
||||
onClose={() => {
|
||||
setHasDeleteMessagesConfirmation(false);
|
||||
}}
|
||||
setLocalDeleteWarningShown={setLocalDeleteWarningShown}
|
||||
/>
|
||||
)}
|
||||
{hasLeaveGroupConfirmation && (
|
||||
|
@ -923,14 +934,29 @@ function CannotLeaveGroupBecauseYouAreLastAdminAlert({
|
|||
}
|
||||
|
||||
function DeleteMessagesConfirmationDialog({
|
||||
isDeleteSyncSendEnabled,
|
||||
i18n,
|
||||
onDestoryMessages,
|
||||
localDeleteWarningShown,
|
||||
onDestroyMessages,
|
||||
onClose,
|
||||
setLocalDeleteWarningShown,
|
||||
}: {
|
||||
isDeleteSyncSendEnabled: boolean;
|
||||
i18n: LocalizerType;
|
||||
onDestoryMessages: () => void;
|
||||
localDeleteWarningShown: boolean;
|
||||
onDestroyMessages: () => void;
|
||||
onClose: () => void;
|
||||
setLocalDeleteWarningShown: () => void;
|
||||
}) {
|
||||
if (!localDeleteWarningShown && isDeleteSyncSendEnabled) {
|
||||
return (
|
||||
<LocalDeleteWarningModal
|
||||
i18n={i18n}
|
||||
onClose={setLocalDeleteWarningShown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
dialogName="ConversationHeader.destroyMessages"
|
||||
|
@ -939,7 +965,7 @@ function DeleteMessagesConfirmationDialog({
|
|||
)}
|
||||
actions={[
|
||||
{
|
||||
action: onDestoryMessages,
|
||||
action: onDestroyMessages,
|
||||
style: 'negative',
|
||||
text: i18n('icu:delete'),
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue