Moves saveAttachment to a redux action
This commit is contained in:
parent
c8068cd501
commit
b138774454
34 changed files with 190 additions and 216 deletions
|
@ -158,7 +158,6 @@ import type AccountManager from './textsecure/AccountManager';
|
||||||
import { onStoryRecipientUpdate } from './util/onStoryRecipientUpdate';
|
import { onStoryRecipientUpdate } from './util/onStoryRecipientUpdate';
|
||||||
import { StoryViewModeType, StoryViewTargetType } from './types/Stories';
|
import { StoryViewModeType, StoryViewTargetType } from './types/Stories';
|
||||||
import { downloadOnboardingStory } from './util/downloadOnboardingStory';
|
import { downloadOnboardingStory } from './util/downloadOnboardingStory';
|
||||||
import { saveAttachmentFromMessage } from './util/saveAttachment';
|
|
||||||
|
|
||||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||||
|
|
||||||
|
@ -1661,7 +1660,9 @@ export async function startApp(): Promise<void> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
saveAttachmentFromMessage(selectedMessage);
|
window.reduxActions.conversations.saveAttachmentFromMessage(
|
||||||
|
selectedMessage
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ type PropsType = {
|
||||||
isMaximized: boolean;
|
isMaximized: boolean;
|
||||||
isFullScreen: boolean;
|
isFullScreen: boolean;
|
||||||
menuOptions: MenuOptionsType;
|
menuOptions: MenuOptionsType;
|
||||||
|
openFileInFolder: (target: string) => unknown;
|
||||||
hasCustomTitleBar: boolean;
|
hasCustomTitleBar: boolean;
|
||||||
hideMenuBar: boolean;
|
hideMenuBar: boolean;
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ export function App({
|
||||||
hasCustomTitleBar,
|
hasCustomTitleBar,
|
||||||
menuOptions,
|
menuOptions,
|
||||||
openInbox,
|
openInbox,
|
||||||
|
openFileInFolder,
|
||||||
registerSingleDevice,
|
registerSingleDevice,
|
||||||
renderCallManager,
|
renderCallManager,
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
|
@ -178,7 +180,12 @@ export function App({
|
||||||
'dark-theme': theme === ThemeType.dark,
|
'dark-theme': theme === ThemeType.dark,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ToastManager hideToast={hideToast} i18n={i18n} toast={toast} />
|
<ToastManager
|
||||||
|
hideToast={hideToast}
|
||||||
|
i18n={i18n}
|
||||||
|
openFileInFolder={openFileInFolder}
|
||||||
|
toast={toast}
|
||||||
|
/>
|
||||||
{renderGlobalModalContainer()}
|
{renderGlobalModalContainer()}
|
||||||
{renderCallManager()}
|
{renderCallManager()}
|
||||||
{renderLightbox()}
|
{renderLightbox()}
|
||||||
|
|
|
@ -30,8 +30,9 @@ export function AvatarLightbox({
|
||||||
<Lightbox
|
<Lightbox
|
||||||
closeLightbox={onClose}
|
closeLightbox={onClose}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
media={[]}
|
|
||||||
isViewOnce
|
isViewOnce
|
||||||
|
media={[]}
|
||||||
|
saveAttachment={noop}
|
||||||
toggleForwardMessageModal={noop}
|
toggleForwardMessageModal={noop}
|
||||||
>
|
>
|
||||||
<AvatarPreview
|
<AvatarPreview
|
||||||
|
|
|
@ -59,6 +59,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
i18n,
|
i18n,
|
||||||
isViewOnce: Boolean(overrideProps.isViewOnce),
|
isViewOnce: Boolean(overrideProps.isViewOnce),
|
||||||
media: overrideProps.media || [],
|
media: overrideProps.media || [],
|
||||||
|
saveAttachment: action('saveAttachment'),
|
||||||
selectedIndex: number('selectedIndex', overrideProps.selectedIndex || 0),
|
selectedIndex: number('selectedIndex', overrideProps.selectedIndex || 0),
|
||||||
toggleForwardMessageModal: action('toggleForwardMessageModal'),
|
toggleForwardMessageModal: action('toggleForwardMessageModal'),
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,10 @@ import { createPortal } from 'react-dom';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
import { useSpring, animated, to } from '@react-spring/web';
|
import { useSpring, animated, to } from '@react-spring/web';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type {
|
||||||
|
ConversationType,
|
||||||
|
SaveAttachmentActionCreatorType,
|
||||||
|
} from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { MediaItemType, MediaItemMessageType } from '../types/MediaItem';
|
import type { MediaItemType, MediaItemMessageType } from '../types/MediaItem';
|
||||||
import * as GoogleChrome from '../util/GoogleChrome';
|
import * as GoogleChrome from '../util/GoogleChrome';
|
||||||
|
@ -18,7 +21,6 @@ import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { IMAGE_PNG, isImage, isVideo } from '../types/MIME';
|
import { IMAGE_PNG, isImage, isVideo } from '../types/MIME';
|
||||||
import { formatDuration } from '../util/formatDuration';
|
import { formatDuration } from '../util/formatDuration';
|
||||||
import { isGIF } from '../types/Attachment';
|
import { isGIF } from '../types/Attachment';
|
||||||
import { saveAttachment } from '../util/saveAttachment';
|
|
||||||
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
@ -28,6 +30,7 @@ export type PropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isViewOnce?: boolean;
|
isViewOnce?: boolean;
|
||||||
media: Array<MediaItemType>;
|
media: Array<MediaItemType>;
|
||||||
|
saveAttachment: SaveAttachmentActionCreatorType;
|
||||||
selectedIndex?: number;
|
selectedIndex?: number;
|
||||||
toggleForwardMessageModal: (messageId: string) => unknown;
|
toggleForwardMessageModal: (messageId: string) => unknown;
|
||||||
};
|
};
|
||||||
|
@ -53,6 +56,7 @@ export function Lightbox({
|
||||||
media,
|
media,
|
||||||
i18n,
|
i18n,
|
||||||
isViewOnce = false,
|
isViewOnce = false,
|
||||||
|
saveAttachment,
|
||||||
selectedIndex: initialSelectedIndex = 0,
|
selectedIndex: initialSelectedIndex = 0,
|
||||||
toggleForwardMessageModal,
|
toggleForwardMessageModal,
|
||||||
}: PropsType): JSX.Element | null {
|
}: PropsType): JSX.Element | null {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { SendStatus } from '../messages/MessageSendState';
|
||||||
import { Theme } from '../util/theme';
|
import { Theme } from '../util/theme';
|
||||||
import { formatDateTimeLong } from '../util/timestamp';
|
import { formatDateTimeLong } from '../util/timestamp';
|
||||||
import { DurationInSeconds } from '../util/durations';
|
import { DurationInSeconds } from '../util/durations';
|
||||||
import type { saveAttachment } from '../util/saveAttachment';
|
import type { SaveAttachmentActionCreatorType } from '../state/ducks/conversations';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import { ThemeType } from '../types/Util';
|
import { ThemeType } from '../types/Util';
|
||||||
import { Time } from './Time';
|
import { Time } from './Time';
|
||||||
|
@ -27,7 +27,7 @@ export type PropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isInternalUser?: boolean;
|
isInternalUser?: boolean;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
saveAttachment: typeof saveAttachment;
|
saveAttachment: SaveAttachmentActionCreatorType;
|
||||||
sender: StoryViewType['sender'];
|
sender: StoryViewType['sender'];
|
||||||
sendState?: Array<StorySendStateType>;
|
sendState?: Array<StorySendStateType>;
|
||||||
attachment?: AttachmentType;
|
attachment?: AttachmentType;
|
||||||
|
|
|
@ -12,7 +12,10 @@ import React, {
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { DraftBodyRangesType, LocalizerType } from '../types/Util';
|
import type { DraftBodyRangesType, LocalizerType } from '../types/Util';
|
||||||
import type { ContextMenuOptionType } from './ContextMenu';
|
import type { ContextMenuOptionType } from './ContextMenu';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type {
|
||||||
|
ConversationType,
|
||||||
|
SaveAttachmentActionCreatorType,
|
||||||
|
} from '../state/ducks/conversations';
|
||||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { RenderEmojiPickerProps } from './conversation/ReactionPicker';
|
import type { RenderEmojiPickerProps } from './conversation/ReactionPicker';
|
||||||
|
@ -44,7 +47,6 @@ import { ToastType } from '../state/ducks/toast';
|
||||||
import { getAvatarColor } from '../types/Colors';
|
import { getAvatarColor } from '../types/Colors';
|
||||||
import { getStoryBackground } from '../util/getStoryBackground';
|
import { getStoryBackground } from '../util/getStoryBackground';
|
||||||
import { getStoryDuration } from '../util/getStoryDuration';
|
import { getStoryDuration } from '../util/getStoryDuration';
|
||||||
import type { saveAttachment } from '../util/saveAttachment';
|
|
||||||
import { isVideoAttachment } from '../types/Attachment';
|
import { isVideoAttachment } from '../types/Attachment';
|
||||||
import { graphemeAndLinkAwareSlice } from '../util/graphemeAndLinkAwareSlice';
|
import { graphemeAndLinkAwareSlice } from '../util/graphemeAndLinkAwareSlice';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
|
@ -100,7 +102,7 @@ export type PropsType = {
|
||||||
renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element;
|
renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element;
|
||||||
replyState?: ReplyStateType;
|
replyState?: ReplyStateType;
|
||||||
retrySend: (messageId: string) => unknown;
|
retrySend: (messageId: string) => unknown;
|
||||||
saveAttachment: typeof saveAttachment;
|
saveAttachment: SaveAttachmentActionCreatorType;
|
||||||
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
|
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastActionCreatorType;
|
||||||
skinTone?: number;
|
skinTone?: number;
|
||||||
|
|
|
@ -62,6 +62,7 @@ const MESSAGE_DEFAULT_PROPS = {
|
||||||
openLink: shouldNeverBeCalled,
|
openLink: shouldNeverBeCalled,
|
||||||
previews: [],
|
previews: [],
|
||||||
renderAudioAttachment: () => <div />,
|
renderAudioAttachment: () => <div />,
|
||||||
|
saveAttachment: shouldNeverBeCalled,
|
||||||
scrollToQuotedMessage: shouldNeverBeCalled,
|
scrollToQuotedMessage: shouldNeverBeCalled,
|
||||||
showContactDetail: shouldNeverBeCalled,
|
showContactDetail: shouldNeverBeCalled,
|
||||||
showContactModal: shouldNeverBeCalled,
|
showContactModal: shouldNeverBeCalled,
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
import { ToastDangerousFileType } from './ToastDangerousFileType';
|
|
||||||
|
|
||||||
import { setupI18n } from '../util/setupI18n';
|
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
i18n,
|
|
||||||
onClose: action('onClose'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Components/ToastDangerousFileType',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const _ToastDangerousFileType = (): JSX.Element => (
|
|
||||||
<ToastDangerousFileType {...defaultProps} />
|
|
||||||
);
|
|
||||||
|
|
||||||
_ToastDangerousFileType.story = {
|
|
||||||
name: 'ToastDangerousFileType',
|
|
||||||
};
|
|
|
@ -1,18 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
|
||||||
import { Toast } from './Toast';
|
|
||||||
|
|
||||||
type PropsType = {
|
|
||||||
i18n: LocalizerType;
|
|
||||||
onClose: () => unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ToastDangerousFileType({
|
|
||||||
i18n,
|
|
||||||
onClose,
|
|
||||||
}: PropsType): JSX.Element {
|
|
||||||
return <Toast onClose={onClose}>{i18n('dangerousFileType')}</Toast>;
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
import { ToastFileSaved } from './ToastFileSaved';
|
|
||||||
|
|
||||||
import { setupI18n } from '../util/setupI18n';
|
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
i18n,
|
|
||||||
onClose: action('onClose'),
|
|
||||||
onOpenFile: action('onOpenFile'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Components/ToastFileSaved',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const _ToastFileSaved = (): JSX.Element => (
|
|
||||||
<ToastFileSaved {...defaultProps} />
|
|
||||||
);
|
|
||||||
|
|
||||||
_ToastFileSaved.story = {
|
|
||||||
name: 'ToastFileSaved',
|
|
||||||
};
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
|
||||||
import { Toast } from './Toast';
|
|
||||||
|
|
||||||
export type ToastPropsType = {
|
|
||||||
onOpenFile: () => unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PropsType = {
|
|
||||||
i18n: LocalizerType;
|
|
||||||
onClose: () => unknown;
|
|
||||||
} & ToastPropsType;
|
|
||||||
|
|
||||||
export function ToastFileSaved({
|
|
||||||
i18n,
|
|
||||||
onClose,
|
|
||||||
onOpenFile,
|
|
||||||
}: PropsType): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Toast
|
|
||||||
onClose={onClose}
|
|
||||||
toastAction={{
|
|
||||||
label: i18n('attachmentSavedShow'),
|
|
||||||
onClick: onOpenFile,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n('attachmentSaved')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -126,6 +126,16 @@ FailedToDeleteUsername.args = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FileSaved = Template.bind({});
|
||||||
|
FileSaved.args = {
|
||||||
|
toast: {
|
||||||
|
toastType: ToastType.FileSaved,
|
||||||
|
parameters: {
|
||||||
|
fullPath: '/image.png',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const FileSize = Template.bind({});
|
export const FileSize = Template.bind({});
|
||||||
FileSize.args = {
|
FileSize.args = {
|
||||||
toast: {
|
toast: {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { missingCaseError } from '../util/missingCaseError';
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
hideToast: () => unknown;
|
hideToast: () => unknown;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
openFileInFolder: (target: string) => unknown;
|
||||||
toast?: {
|
toast?: {
|
||||||
toastType: ToastType;
|
toastType: ToastType;
|
||||||
parameters?: ReplacementValuesType;
|
parameters?: ReplacementValuesType;
|
||||||
|
@ -23,6 +24,7 @@ const SHORT_TIMEOUT = 3 * SECOND;
|
||||||
export function ToastManager({
|
export function ToastManager({
|
||||||
hideToast,
|
hideToast,
|
||||||
i18n,
|
i18n,
|
||||||
|
openFileInFolder,
|
||||||
toast,
|
toast,
|
||||||
}: PropsType): JSX.Element | null {
|
}: PropsType): JSX.Element | null {
|
||||||
if (toast === undefined) {
|
if (toast === undefined) {
|
||||||
|
@ -117,6 +119,24 @@ export function ToastManager({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.FileSaved) {
|
||||||
|
return (
|
||||||
|
<Toast
|
||||||
|
onClose={hideToast}
|
||||||
|
toastAction={{
|
||||||
|
label: i18n('attachmentSavedShow'),
|
||||||
|
onClick: () => {
|
||||||
|
if (toast.parameters && 'fullPath' in toast.parameters) {
|
||||||
|
openFileInFolder(String(toast.parameters.fullPath));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n('attachmentSaved')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (toastType === ToastType.FileSize) {
|
if (toastType === ToastType.FileSize) {
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast}>
|
<Toast onClose={hideToast}>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
ConversationTypeType,
|
ConversationTypeType,
|
||||||
InteractionModeType,
|
InteractionModeType,
|
||||||
|
SaveAttachmentActionCreatorType,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import type { ViewStoryActionCreatorType } from '../../state/ducks/stories';
|
import type { ViewStoryActionCreatorType } from '../../state/ducks/stories';
|
||||||
import type { ReadStatus } from '../../messages/MessageReadStatus';
|
import type { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
|
@ -87,7 +88,6 @@ import { PaymentEventKind } from '../../types/Payment';
|
||||||
import type { AnyPaymentEvent } from '../../types/Payment';
|
import type { AnyPaymentEvent } from '../../types/Payment';
|
||||||
import { Emojify } from './Emojify';
|
import { Emojify } from './Emojify';
|
||||||
import { getPaymentEventDescription } from '../../messages/helpers';
|
import { getPaymentEventDescription } from '../../messages/helpers';
|
||||||
import { saveAttachment } from '../../util/saveAttachment';
|
|
||||||
|
|
||||||
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 10;
|
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 10;
|
||||||
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
|
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
|
||||||
|
@ -319,6 +319,7 @@ export type PropsActions = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
}) => void;
|
}) => void;
|
||||||
markViewed(messageId: string): void;
|
markViewed(messageId: string): void;
|
||||||
|
saveAttachment: SaveAttachmentActionCreatorType;
|
||||||
showLightbox: (options: {
|
showLightbox: (options: {
|
||||||
attachment: AttachmentType;
|
attachment: AttachmentType;
|
||||||
messageId: string;
|
messageId: string;
|
||||||
|
@ -2380,8 +2381,13 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public openGenericAttachment = (event?: React.MouseEvent): void => {
|
public openGenericAttachment = (event?: React.MouseEvent): void => {
|
||||||
const { id, attachments, timestamp, kickOffAttachmentDownload } =
|
const {
|
||||||
this.props;
|
id,
|
||||||
|
attachments,
|
||||||
|
saveAttachment,
|
||||||
|
timestamp,
|
||||||
|
kickOffAttachmentDownload,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -82,6 +82,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
openGiftBadge: action('openGiftBadge'),
|
openGiftBadge: action('openGiftBadge'),
|
||||||
openLink: action('openLink'),
|
openLink: action('openLink'),
|
||||||
renderAudioAttachment: () => <div>*AudioAttachment*</div>,
|
renderAudioAttachment: () => <div>*AudioAttachment*</div>,
|
||||||
|
saveAttachment: action('saveAttachment'),
|
||||||
showContactDetail: action('showContactDetail'),
|
showContactDetail: action('showContactDetail'),
|
||||||
showContactModal: action('showContactModal'),
|
showContactModal: action('showContactModal'),
|
||||||
showExpiredIncomingTapToViewToast: action(
|
showExpiredIncomingTapToViewToast: action(
|
||||||
|
|
|
@ -96,6 +96,7 @@ export type PropsReduxActions = Pick<
|
||||||
| 'checkForAccount'
|
| 'checkForAccount'
|
||||||
| 'clearSelectedMessage'
|
| 'clearSelectedMessage'
|
||||||
| 'doubleCheckMissingQuoteReference'
|
| 'doubleCheckMissingQuoteReference'
|
||||||
|
| 'saveAttachment'
|
||||||
| 'showContactModal'
|
| 'showContactModal'
|
||||||
| 'showLightbox'
|
| 'showLightbox'
|
||||||
| 'showLightboxForViewOnceMedia'
|
| 'showLightboxForViewOnceMedia'
|
||||||
|
@ -293,6 +294,7 @@ export class MessageDetail extends React.Component<Props> {
|
||||||
openGiftBadge,
|
openGiftBadge,
|
||||||
openLink,
|
openLink,
|
||||||
renderAudioAttachment,
|
renderAudioAttachment,
|
||||||
|
saveAttachment,
|
||||||
showContactDetail,
|
showContactDetail,
|
||||||
showContactModal,
|
showContactModal,
|
||||||
showExpiredIncomingTapToViewToast,
|
showExpiredIncomingTapToViewToast,
|
||||||
|
@ -338,6 +340,7 @@ export class MessageDetail extends React.Component<Props> {
|
||||||
openGiftBadge={openGiftBadge}
|
openGiftBadge={openGiftBadge}
|
||||||
openLink={openLink}
|
openLink={openLink}
|
||||||
renderAudioAttachment={renderAudioAttachment}
|
renderAudioAttachment={renderAudioAttachment}
|
||||||
|
saveAttachment={saveAttachment}
|
||||||
shouldCollapseAbove={false}
|
shouldCollapseAbove={false}
|
||||||
shouldCollapseBelow={false}
|
shouldCollapseBelow={false}
|
||||||
shouldHideMetadata={false}
|
shouldHideMetadata={false}
|
||||||
|
|
|
@ -124,6 +124,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
||||||
setQuoteByMessageId: action('default--setQuoteByMessageId'),
|
setQuoteByMessageId: action('default--setQuoteByMessageId'),
|
||||||
retrySend: action('default--retrySend'),
|
retrySend: action('default--retrySend'),
|
||||||
retryDeleteForEveryone: action('default--retryDeleteForEveryone'),
|
retryDeleteForEveryone: action('default--retryDeleteForEveryone'),
|
||||||
|
saveAttachment: action('saveAttachment'),
|
||||||
scrollToQuotedMessage: action('default--scrollToQuotedMessage'),
|
scrollToQuotedMessage: action('default--scrollToQuotedMessage'),
|
||||||
selectMessage: action('default--selectMessage'),
|
selectMessage: action('default--selectMessage'),
|
||||||
shouldCollapseAbove: false,
|
shouldCollapseAbove: false,
|
||||||
|
|
|
@ -283,6 +283,7 @@ const actions = () => ({
|
||||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||||
showMessageDetail: action('showMessageDetail'),
|
showMessageDetail: action('showMessageDetail'),
|
||||||
openConversation: action('openConversation'),
|
openConversation: action('openConversation'),
|
||||||
|
saveAttachment: action('saveAttachment'),
|
||||||
showContactDetail: action('showContactDetail'),
|
showContactDetail: action('showContactDetail'),
|
||||||
showContactModal: action('showContactModal'),
|
showContactModal: action('showContactModal'),
|
||||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||||
|
|
|
@ -253,6 +253,7 @@ const getActions = createSelector(
|
||||||
'kickOffAttachmentDownload',
|
'kickOffAttachmentDownload',
|
||||||
'markAttachmentAsCorrupted',
|
'markAttachmentAsCorrupted',
|
||||||
'messageExpanded',
|
'messageExpanded',
|
||||||
|
'saveAttachment',
|
||||||
'showLightbox',
|
'showLightbox',
|
||||||
'showLightboxForViewOnceMedia',
|
'showLightboxForViewOnceMedia',
|
||||||
'openLink',
|
'openLink',
|
||||||
|
|
|
@ -80,6 +80,7 @@ const getDefaultProps = () => ({
|
||||||
showMessageDetail: action('showMessageDetail'),
|
showMessageDetail: action('showMessageDetail'),
|
||||||
openConversation: action('openConversation'),
|
openConversation: action('openConversation'),
|
||||||
openGiftBadge: action('openGiftBadge'),
|
openGiftBadge: action('openGiftBadge'),
|
||||||
|
saveAttachment: action('saveAttachment'),
|
||||||
showContactDetail: action('showContactDetail'),
|
showContactDetail: action('showContactDetail'),
|
||||||
showContactModal: action('showContactModal'),
|
showContactModal: action('showContactModal'),
|
||||||
showLightbox: action('showLightbox'),
|
showLightbox: action('showLightbox'),
|
||||||
|
|
|
@ -293,6 +293,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
renderEmojiPicker,
|
renderEmojiPicker,
|
||||||
renderReactionPicker,
|
renderReactionPicker,
|
||||||
renderAudioAttachment,
|
renderAudioAttachment,
|
||||||
|
saveAttachment: action('saveAttachment'),
|
||||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||||
retrySend: action('retrySend'),
|
retrySend: action('retrySend'),
|
||||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { doesMessageBodyOverflow } from './MessageBodyReadMore';
|
||||||
import type { Props as ReactionPickerProps } from './ReactionPicker';
|
import type { Props as ReactionPickerProps } from './ReactionPicker';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||||
import { useToggleReactionPicker } from '../../hooks/useKeyboardShortcuts';
|
import { useToggleReactionPicker } from '../../hooks/useKeyboardShortcuts';
|
||||||
import { saveAttachment } from '../../util/saveAttachment';
|
|
||||||
|
|
||||||
export type PropsData = {
|
export type PropsData = {
|
||||||
canDownload: boolean;
|
canDownload: boolean;
|
||||||
|
@ -172,7 +171,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
});
|
});
|
||||||
|
|
||||||
const openGenericAttachment = (event?: React.MouseEvent): void => {
|
const openGenericAttachment = (event?: React.MouseEvent): void => {
|
||||||
const { kickOffAttachmentDownload } = props;
|
const { kickOffAttachmentDownload, saveAttachment } = props;
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -122,7 +122,6 @@ type MigrationsModuleType = {
|
||||||
loadStickerData: (
|
loadStickerData: (
|
||||||
sticker: StickerType | undefined
|
sticker: StickerType | undefined
|
||||||
) => Promise<StickerWithHydratedData | undefined>;
|
) => Promise<StickerWithHydratedData | undefined>;
|
||||||
openFileInFolder: (target: string) => Promise<void>;
|
|
||||||
readAttachmentData: (path: string) => Promise<Uint8Array>;
|
readAttachmentData: (path: string) => Promise<Uint8Array>;
|
||||||
readDraftData: (path: string) => Promise<Uint8Array>;
|
readDraftData: (path: string) => Promise<Uint8Array>;
|
||||||
readStickerData: (path: string) => Promise<Uint8Array>;
|
readStickerData: (path: string) => Promise<Uint8Array>;
|
||||||
|
@ -185,7 +184,6 @@ export function initializeMigrations({
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
getBadgesPath,
|
getBadgesPath,
|
||||||
getTempPath,
|
getTempPath,
|
||||||
openFileInFolder,
|
|
||||||
saveAttachmentToDisk,
|
saveAttachmentToDisk,
|
||||||
} = Attachments;
|
} = Attachments;
|
||||||
const {
|
const {
|
||||||
|
@ -266,7 +264,6 @@ export function initializeMigrations({
|
||||||
loadPreviewData,
|
loadPreviewData,
|
||||||
loadQuoteData,
|
loadQuoteData,
|
||||||
loadStickerData,
|
loadStickerData,
|
||||||
openFileInFolder,
|
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
readDraftData,
|
readDraftData,
|
||||||
readStickerData,
|
readStickerData,
|
||||||
|
@ -364,7 +361,6 @@ type AttachmentsModuleType = {
|
||||||
) => (relativePath: string) => string;
|
) => (relativePath: string) => string;
|
||||||
|
|
||||||
createDoesExist: (root: string) => (relativePath: string) => Promise<boolean>;
|
createDoesExist: (root: string) => (relativePath: string) => Promise<boolean>;
|
||||||
openFileInFolder: (target: string) => Promise<void>;
|
|
||||||
saveAttachmentToDisk: ({
|
saveAttachmentToDisk: ({
|
||||||
data,
|
data,
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -21,6 +21,8 @@ import { getOwn } from '../../util/getOwn';
|
||||||
import { assertDev, strictAssert } from '../../util/assert';
|
import { assertDev, strictAssert } from '../../util/assert';
|
||||||
import type { DurationInSeconds } from '../../util/durations';
|
import type { DurationInSeconds } from '../../util/durations';
|
||||||
import * as universalExpireTimer from '../../util/universalExpireTimer';
|
import * as universalExpireTimer from '../../util/universalExpireTimer';
|
||||||
|
import * as Attachment from '../../types/Attachment';
|
||||||
|
import { isFileDangerous } from '../../util/isFileDangerous';
|
||||||
import type {
|
import type {
|
||||||
ShowSendAnywayDialogActionType,
|
ShowSendAnywayDialogActionType,
|
||||||
ToggleProfileEditorErrorActionType,
|
ToggleProfileEditorErrorActionType,
|
||||||
|
@ -901,6 +903,8 @@ export const actions = {
|
||||||
reviewGroupMemberNameCollision,
|
reviewGroupMemberNameCollision,
|
||||||
reviewMessageRequestNameCollision,
|
reviewMessageRequestNameCollision,
|
||||||
revokePendingMembershipsFromGroupV2,
|
revokePendingMembershipsFromGroupV2,
|
||||||
|
saveAttachment,
|
||||||
|
saveAttachmentFromMessage,
|
||||||
saveAvatarToDisk,
|
saveAvatarToDisk,
|
||||||
scrollToMessage,
|
scrollToMessage,
|
||||||
selectMessage,
|
selectMessage,
|
||||||
|
@ -2451,6 +2455,83 @@ function loadRecentMediaItems(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SaveAttachmentActionCreatorType = (
|
||||||
|
attachment: AttachmentType,
|
||||||
|
timestamp?: number,
|
||||||
|
index?: number
|
||||||
|
) => unknown;
|
||||||
|
|
||||||
|
function saveAttachment(
|
||||||
|
attachment: AttachmentType,
|
||||||
|
timestamp = Date.now(),
|
||||||
|
index = 0
|
||||||
|
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||||
|
return async dispatch => {
|
||||||
|
const { fileName = '' } = attachment;
|
||||||
|
|
||||||
|
const isDangerous = isFileDangerous(fileName);
|
||||||
|
|
||||||
|
if (isDangerous) {
|
||||||
|
dispatch({
|
||||||
|
type: SHOW_TOAST,
|
||||||
|
payload: {
|
||||||
|
toastType: ToastType.DangerousFileType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { readAttachmentData, saveAttachmentToDisk } =
|
||||||
|
window.Signal.Migrations;
|
||||||
|
|
||||||
|
const fullPath = await Attachment.save({
|
||||||
|
attachment,
|
||||||
|
index: index + 1,
|
||||||
|
readAttachmentData,
|
||||||
|
saveAttachmentToDisk,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fullPath) {
|
||||||
|
dispatch({
|
||||||
|
type: SHOW_TOAST,
|
||||||
|
payload: {
|
||||||
|
toastType: ToastType.FileSaved,
|
||||||
|
parameters: {
|
||||||
|
fullPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAttachmentFromMessage(
|
||||||
|
messageId: string,
|
||||||
|
providedAttachment?: AttachmentType
|
||||||
|
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const message = await getMessageById(messageId);
|
||||||
|
if (!message) {
|
||||||
|
throw new Error(
|
||||||
|
`saveAttachmentFromMessage: Message ${messageId} missing!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { attachments, sent_at: timestamp } = message.attributes;
|
||||||
|
if (!attachments || attachments.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment =
|
||||||
|
providedAttachment && attachments.includes(providedAttachment)
|
||||||
|
? providedAttachment
|
||||||
|
: attachments[0];
|
||||||
|
|
||||||
|
saveAttachment(attachment, timestamp)(dispatch, getState, null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function clearInvitedUuidsForNewlyCreatedGroup(): ClearInvitedUuidsForNewlyCreatedGroupActionType {
|
function clearInvitedUuidsForNewlyCreatedGroup(): ClearInvitedUuidsForNewlyCreatedGroupActionType {
|
||||||
return { type: 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP' };
|
return { type: 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP' };
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
} from '../../util/GoogleChrome';
|
} from '../../util/GoogleChrome';
|
||||||
import { isTapToView } from '../selectors/message';
|
import { isTapToView } from '../selectors/message';
|
||||||
import { SHOW_TOAST, ToastType } from './toast';
|
import { SHOW_TOAST, ToastType } from './toast';
|
||||||
import { saveAttachmentFromMessage } from '../../util/saveAttachment';
|
import { saveAttachmentFromMessage } from './conversations';
|
||||||
import { showStickerPackPreview } from './globalModals';
|
import { showStickerPackPreview } from './globalModals';
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ function showLightbox(opts: {
|
||||||
| ShowStickerPackPreviewActionType
|
| ShowStickerPackPreviewActionType
|
||||||
| ShowToastActionType
|
| ShowToastActionType
|
||||||
> {
|
> {
|
||||||
return async dispatch => {
|
return async (dispatch, getState) => {
|
||||||
const { attachment, messageId } = opts;
|
const { attachment, messageId } = opts;
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await getMessageById(messageId);
|
||||||
|
@ -233,7 +233,11 @@ function showLightbox(opts: {
|
||||||
!isImageTypeSupported(contentType) &&
|
!isImageTypeSupported(contentType) &&
|
||||||
!isVideoTypeSupported(contentType)
|
!isVideoTypeSupported(contentType)
|
||||||
) {
|
) {
|
||||||
await saveAttachmentFromMessage(messageId, attachment);
|
saveAttachmentFromMessage(messageId, attachment)(
|
||||||
|
dispatch,
|
||||||
|
getState,
|
||||||
|
null
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import type { NoopActionType } from './noop';
|
||||||
import type { ReplacementValuesType } from '../../types/Util';
|
import type { ReplacementValuesType } from '../../types/Util';
|
||||||
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
export enum ToastType {
|
export enum ToastType {
|
||||||
AddingUserToGroup = 'AddingUserToGroup',
|
AddingUserToGroup = 'AddingUserToGroup',
|
||||||
|
@ -18,6 +21,7 @@ export enum ToastType {
|
||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
Expired = 'Expired',
|
Expired = 'Expired',
|
||||||
FailedToDeleteUsername = 'FailedToDeleteUsername',
|
FailedToDeleteUsername = 'FailedToDeleteUsername',
|
||||||
|
FileSaved = 'FileSaved',
|
||||||
FileSize = 'FileSize',
|
FileSize = 'FileSize',
|
||||||
InvalidConversation = 'InvalidConversation',
|
InvalidConversation = 'InvalidConversation',
|
||||||
LeftGroup = 'LeftGroup',
|
LeftGroup = 'LeftGroup',
|
||||||
|
@ -72,6 +76,14 @@ function hideToast(): HideToastActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFileInFolder(target: string): NoopActionType {
|
||||||
|
ipcRenderer.send('show-item-in-folder', target);
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type ShowToastActionCreatorType = (
|
export type ShowToastActionCreatorType = (
|
||||||
toastType: ToastType,
|
toastType: ToastType,
|
||||||
parameters?: ReplacementValuesType
|
parameters?: ReplacementValuesType
|
||||||
|
@ -92,6 +104,7 @@ export const showToast: ShowToastActionCreatorType = (
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
hideToast,
|
hideToast,
|
||||||
|
openFileInFolder,
|
||||||
showToast,
|
showToast,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import type { StateType } from '../reducer';
|
||||||
import { Lightbox } from '../../components/Lightbox';
|
import { Lightbox } from '../../components/Lightbox';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useLightboxActions } from '../ducks/lightbox';
|
import { useLightboxActions } from '../ducks/lightbox';
|
||||||
import {
|
import {
|
||||||
|
@ -22,6 +23,7 @@ import {
|
||||||
|
|
||||||
export function SmartLightbox(): JSX.Element | null {
|
export function SmartLightbox(): JSX.Element | null {
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||||
|
const { saveAttachment } = useConversationsActions();
|
||||||
const { closeLightbox } = useLightboxActions();
|
const { closeLightbox } = useLightboxActions();
|
||||||
const { toggleForwardMessageModal } = useGlobalModalActions();
|
const { toggleForwardMessageModal } = useGlobalModalActions();
|
||||||
|
|
||||||
|
@ -45,6 +47,7 @@ export function SmartLightbox(): JSX.Element | null {
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isViewOnce={isViewOnce}
|
isViewOnce={isViewOnce}
|
||||||
media={media}
|
media={media}
|
||||||
|
saveAttachment={saveAttachment}
|
||||||
selectedIndex={selectedIndex || 0}
|
selectedIndex={selectedIndex || 0}
|
||||||
toggleForwardMessageModal={toggleForwardMessageModal}
|
toggleForwardMessageModal={toggleForwardMessageModal}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
shouldShowStoriesView,
|
shouldShowStoriesView,
|
||||||
} from '../selectors/stories';
|
} from '../selectors/stories';
|
||||||
import { retryMessageSend } from '../../util/retryMessageSend';
|
import { retryMessageSend } from '../../util/retryMessageSend';
|
||||||
import { saveAttachment } from '../../util/saveAttachment';
|
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useStoriesActions } from '../ducks/stories';
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
|
@ -34,7 +33,8 @@ function renderStoryCreator(): JSX.Element {
|
||||||
|
|
||||||
export function SmartStories(): JSX.Element | null {
|
export function SmartStories(): JSX.Element | null {
|
||||||
const storiesActions = useStoriesActions();
|
const storiesActions = useStoriesActions();
|
||||||
const { showConversation, toggleHideStories } = useConversationsActions();
|
const { saveAttachment, showConversation, toggleHideStories } =
|
||||||
|
useConversationsActions();
|
||||||
const { showStoriesSettings, toggleForwardMessageModal } =
|
const { showStoriesSettings, toggleForwardMessageModal } =
|
||||||
useGlobalModalActions();
|
useGlobalModalActions();
|
||||||
const { showToast } = useToastActions();
|
const { showToast } = useToastActions();
|
||||||
|
|
|
@ -29,7 +29,6 @@ import { isInFullScreenCall } from '../selectors/calling';
|
||||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
import { renderEmojiPicker } from './renderEmojiPicker';
|
import { renderEmojiPicker } from './renderEmojiPicker';
|
||||||
import { retryMessageSend } from '../../util/retryMessageSend';
|
import { retryMessageSend } from '../../util/retryMessageSend';
|
||||||
import { saveAttachment } from '../../util/saveAttachment';
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { asyncShouldNeverBeCalled } from '../../util/shouldNeverBeCalled';
|
import { asyncShouldNeverBeCalled } from '../../util/shouldNeverBeCalled';
|
||||||
import { useActions as useEmojisActions } from '../ducks/emojis';
|
import { useActions as useEmojisActions } from '../ducks/emojis';
|
||||||
|
@ -42,7 +41,8 @@ import { useIsWindowActive } from '../../hooks/useIsWindowActive';
|
||||||
export function SmartStoryViewer(): JSX.Element | null {
|
export function SmartStoryViewer(): JSX.Element | null {
|
||||||
const storiesActions = useStoriesActions();
|
const storiesActions = useStoriesActions();
|
||||||
const { onUseEmoji } = useEmojisActions();
|
const { onUseEmoji } = useEmojisActions();
|
||||||
const { showConversation, toggleHideStories } = useConversationsActions();
|
const { saveAttachment, showConversation, toggleHideStories } =
|
||||||
|
useConversationsActions();
|
||||||
const { onSetSkinTone } = useItemsActions();
|
const { onSetSkinTone } = useItemsActions();
|
||||||
const { showToast } = useToastActions();
|
const { showToast } = useToastActions();
|
||||||
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
|
||||||
import * as Attachment from '../types/Attachment';
|
|
||||||
import { ToastDangerousFileType } from '../components/ToastDangerousFileType';
|
|
||||||
import { ToastFileSaved } from '../components/ToastFileSaved';
|
|
||||||
import { isFileDangerous } from './isFileDangerous';
|
|
||||||
import { showToast } from './showToast';
|
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
|
||||||
|
|
||||||
export async function saveAttachment(
|
|
||||||
attachment: AttachmentType,
|
|
||||||
timestamp = Date.now(),
|
|
||||||
index = 0
|
|
||||||
): Promise<void> {
|
|
||||||
const { fileName = '' } = attachment;
|
|
||||||
|
|
||||||
const isDangerous = isFileDangerous(fileName);
|
|
||||||
|
|
||||||
if (isDangerous) {
|
|
||||||
showToast(ToastDangerousFileType);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { openFileInFolder, readAttachmentData, saveAttachmentToDisk } =
|
|
||||||
window.Signal.Migrations;
|
|
||||||
|
|
||||||
const fullPath = await Attachment.save({
|
|
||||||
attachment,
|
|
||||||
index: index + 1,
|
|
||||||
readAttachmentData,
|
|
||||||
saveAttachmentToDisk,
|
|
||||||
timestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fullPath) {
|
|
||||||
showToast(ToastFileSaved, {
|
|
||||||
onOpenFile: () => {
|
|
||||||
openFileInFolder(fullPath);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveAttachmentFromMessage(
|
|
||||||
messageId: string,
|
|
||||||
providedAttachment?: AttachmentType
|
|
||||||
): Promise<void> {
|
|
||||||
const message = await getMessageById(messageId);
|
|
||||||
if (!message) {
|
|
||||||
throw new Error(`saveAttachmentFromMessage: Message ${messageId} missing!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { attachments, sent_at: timestamp } = message.attributes;
|
|
||||||
if (!attachments || attachments.length < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment =
|
|
||||||
providedAttachment && attachments.includes(providedAttachment)
|
|
||||||
? providedAttachment
|
|
||||||
: attachments[0];
|
|
||||||
|
|
||||||
return saveAttachment(attachment, timestamp);
|
|
||||||
}
|
|
|
@ -22,10 +22,6 @@ import type {
|
||||||
ToastInternalError,
|
ToastInternalError,
|
||||||
ToastPropsType as ToastInternalErrorPropsType,
|
ToastPropsType as ToastInternalErrorPropsType,
|
||||||
} from '../components/ToastInternalError';
|
} from '../components/ToastInternalError';
|
||||||
import type {
|
|
||||||
ToastFileSaved,
|
|
||||||
ToastPropsType as ToastFileSavedPropsType,
|
|
||||||
} from '../components/ToastFileSaved';
|
|
||||||
import type {
|
import type {
|
||||||
ToastFileSize,
|
ToastFileSize,
|
||||||
ToastPropsType as ToastFileSizePropsType,
|
ToastPropsType as ToastFileSizePropsType,
|
||||||
|
@ -61,10 +57,6 @@ export function showToast(
|
||||||
Toast: typeof ToastInternalError,
|
Toast: typeof ToastInternalError,
|
||||||
props: ToastInternalErrorPropsType
|
props: ToastInternalErrorPropsType
|
||||||
): void;
|
): void;
|
||||||
export function showToast(
|
|
||||||
Toast: typeof ToastFileSaved,
|
|
||||||
props: ToastFileSavedPropsType
|
|
||||||
): void;
|
|
||||||
export function showToast(
|
export function showToast(
|
||||||
Toast: typeof ToastFileSize,
|
Toast: typeof ToastFileSize,
|
||||||
props: ToastFileSizePropsType
|
props: ToastFileSizePropsType
|
||||||
|
|
|
@ -52,7 +52,6 @@ import {
|
||||||
removeLinkPreview,
|
removeLinkPreview,
|
||||||
suspendLinkPreviews,
|
suspendLinkPreviews,
|
||||||
} from '../services/LinkPreview';
|
} from '../services/LinkPreview';
|
||||||
import { saveAttachment } from '../util/saveAttachment';
|
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
import { startConversation } from '../util/startConversation';
|
import { startConversation } from '../util/startConversation';
|
||||||
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
||||||
|
@ -749,7 +748,10 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
}: ItemClickEvent) => {
|
}: ItemClickEvent) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'documents': {
|
case 'documents': {
|
||||||
saveAttachment(attachment, message.sent_at);
|
window.reduxActions.conversations.saveAttachment(
|
||||||
|
attachment,
|
||||||
|
message.sent_at
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,10 +197,6 @@ export const createDoesExist = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openFileInFolder = async (target: string): Promise<void> => {
|
|
||||||
ipcRenderer.send('show-item-in-folder', target);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showSaveDialog = (
|
const showSaveDialog = (
|
||||||
defaultPath: string
|
defaultPath: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
|
Loading…
Add table
Reference in a new issue