From a7c4ce77a1b3dd1c8dd13fc7e28894967ddee3f3 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 26 Oct 2020 16:38:06 +1100 Subject: [PATCH] enable back attachments download on context menu --- js/models/messages.js | 6 ---- ts/components/conversation/Message.tsx | 4 +-- .../conversation/SessionConversation.tsx | 11 ++++--- .../SessionConversationMessagesList.tsx | 12 +++---- .../conversation/SessionRightPanel.tsx | 3 +- ts/test/types/Attachment_test.ts | 31 +++++++++++-------- ts/types/Attachment.ts | 17 ++++------ 7 files changed, 40 insertions(+), 44 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 3b9af8484..5b55a7317 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -621,12 +621,6 @@ onDelete: () => this.trigger('delete', this), onClickLinkPreview: url => this.trigger('navigate-to', url), - onDownload: isDangerous => - this.trigger('download', { - attachment: firstAttachment, - message: this, - isDangerous, - }), onShowUserDetails: pubkey => window.Whisper.events.trigger('onShowUserDetails', { userPubKey: pubkey, diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index e8d76f32e..d5c1d3294 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -112,7 +112,7 @@ export interface Props { onSelectMessage: (messageId: string) => void; onReply?: (messagId: number) => void; onRetrySend?: () => void; - onDownload?: (isDangerous: boolean) => void; + onDownload?: (attachment: AttachmentType) => void; onDelete?: () => void; onCopyPubKey?: () => void; onBanUser?: () => void; @@ -853,7 +853,7 @@ export class Message extends React.PureComponent { onClick={(e: any) => { e.event.stopPropagation(); if (onDownload) { - onDownload(isDangerous); + onDownload(attachments[0]); } }} > diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 83266724c..e4795aea7 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -22,7 +22,7 @@ import { SessionConversationMessagesList } from './SessionConversationMessagesLi import { LightboxGallery, MediaItemType } from '../../LightboxGallery'; import { Message } from '../../conversation/media-gallery/types/Message'; -import { AttachmentType } from '../../../types/Attachment'; +import { AttachmentType, save } from '../../../types/Attachment'; import { ToastUtils } from '../../../session/utils'; import * as MIME from '../../../types/MIME'; import { SessionFileDropzone } from './SessionFileDropzone'; @@ -133,6 +133,7 @@ export class SessionConversation extends React.Component { this.replyToMessage = this.replyToMessage.bind(this); this.onClickAttachment = this.onClickAttachment.bind(this); + this.downloadAttachment = this.downloadAttachment.bind(this); this.getMessages = this.getMessages.bind(this); // Keyboard navigation @@ -435,7 +436,6 @@ export class SessionConversation extends React.Component { ), members, subscriberCount: conversation.get('subscriberCount'), - selectedMessages: this.state.selectedMessages?.length, isKickedFromGroup: conversation.get('isKickedFromGroup'), expirationSettingName, showBackButton: Boolean(this.state.infoViewState), @@ -534,6 +534,7 @@ export class SessionConversation extends React.Component { replyToMessage: this.replyToMessage, doneInitialScroll: this.state.doneInitialScroll, onClickAttachment: this.onClickAttachment, + onDownloadAttachment: this.downloadAttachment, messageContainerRef: this.messageContainerRef, }; } @@ -965,12 +966,12 @@ export class SessionConversation extends React.Component { index, }: { attachment: AttachmentType; - message: Message; - index: number; + message?: Message; + index?: number; }) { const { getAbsoluteAttachmentPath } = window.Signal.Migrations; - window.Signal.Types.Attachment.save({ + save({ attachment, document, getAbsolutePath: getAbsoluteAttachmentPath, diff --git a/ts/components/session/conversation/SessionConversationMessagesList.tsx b/ts/components/session/conversation/SessionConversationMessagesList.tsx index b81dc56f1..67d2f9de8 100644 --- a/ts/components/session/conversation/SessionConversationMessagesList.tsx +++ b/ts/components/session/conversation/SessionConversationMessagesList.tsx @@ -8,6 +8,8 @@ import { ResetSessionNotification } from '../../conversation/ResetSessionNotific import { Constants } from '../../../session'; import _ from 'lodash'; import { ConversationModel } from '../../../../js/models/conversations'; +import { contextMenu } from 'react-contexify'; +import { AttachmentType } from '../../../types/Attachment'; interface State { isScrolledToBottom: boolean; @@ -31,6 +33,7 @@ interface Props { ) => Promise<{ previousTopMessage: string }>; replyToMessage: (messageId: number) => Promise; onClickAttachment: (attachment: any, message: any) => void; + onDownloadAttachment: ({ attachment }: { attachment: any}) => void; } export class SessionConversationMessagesList extends React.Component< @@ -122,8 +125,6 @@ export class SessionConversationMessagesList extends React.Component< <> {messages.map((message: any) => { const messageProps = message.propsForMessage; - // const quoteProps = messageProps.quote; - // console.warn('propsForQuote', quoteProps); const timerProps = message.propsForTimerNotification; const resetSessionProps = message.propsForResetSessionNotification; @@ -136,7 +137,6 @@ export class SessionConversationMessagesList extends React.Component< // in a series of messages from the same user item = messageProps ? this.renderMessage( - message, messageProps, message.firstMessageOfSeries, multiSelectMode @@ -149,7 +149,6 @@ export class SessionConversationMessagesList extends React.Component< ) : ( item ); - // item = attachmentProps ? this.renderMessage(timerProps) : item; return item; })} @@ -158,7 +157,6 @@ export class SessionConversationMessagesList extends React.Component< } public renderMessage( - message: any, messageProps: any, firstMessageOfSeries: boolean, multiSelectMode: boolean @@ -182,7 +180,9 @@ export class SessionConversationMessagesList extends React.Component< messageProps.onClickAttachment = (attachment: any) => { this.props.onClickAttachment(attachment, messageProps); }; - // messageProps.onDownload = () + messageProps.onDownload = (attachment: AttachmentType) => { + this.props.onDownloadAttachment({attachment}); + }; return ; } diff --git a/ts/components/session/conversation/SessionRightPanel.tsx b/ts/components/session/conversation/SessionRightPanel.tsx index 8ece88f54..27fd72364 100644 --- a/ts/components/session/conversation/SessionRightPanel.tsx +++ b/ts/components/session/conversation/SessionRightPanel.tsx @@ -15,6 +15,7 @@ import { ConversationAvatar, usingClosedConversationDetails, } from '../usingClosedConversationDetails'; +import { save } from '../../../types/Attachment'; interface Props { id: string; @@ -184,7 +185,7 @@ class SessionRightPanel extends React.Component { const saveAttachment = async ({ attachment, message }: any = {}) => { const timestamp = message.received_at; - window.Signal.Types.Attachment.save({ + save({ attachment, document, getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath, diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts index 07f2ddbc9..e3c18b2c8 100644 --- a/ts/test/types/Attachment_test.ts +++ b/ts/test/types/Attachment_test.ts @@ -7,19 +7,22 @@ import { SignalService } from '../../protobuf'; // @ts-ignore import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer'; +// tslint:disable-next-line: max-func-body-length describe('Attachment', () => { describe('getFileExtension', () => { it('should return file extension from content type', () => { - const input: Attachment.Attachment = { - data: stringToArrayBuffer('foo'), - contentType: MIME.IMAGE_GIF, + const input: Attachment.AttachmentType = { + fileName: 'funny-cat.mov', + url: 'funny-cat.mov', + contentType: MIME.VIDEO_QUICKTIME, }; assert.strictEqual(Attachment.getFileExtension(input), 'gif'); }); it('should return file extension for QuickTime videos', () => { - const input: Attachment.Attachment = { - data: stringToArrayBuffer('foo'), + const input: Attachment.AttachmentType = { + fileName: 'funny-cat.mov', + url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, }; assert.strictEqual(Attachment.getFileExtension(input), 'mov'); @@ -29,9 +32,9 @@ describe('Attachment', () => { describe('getSuggestedFilename', () => { context('for attachment with filename', () => { it('should return existing filename if present', () => { - const attachment: Attachment.Attachment = { + const attachment: Attachment.AttachmentType = { fileName: 'funny-cat.mov', - data: stringToArrayBuffer('foo'), + url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, }; const actual = Attachment.getSuggestedFilename({ attachment }); @@ -41,9 +44,11 @@ describe('Attachment', () => { }); context('for attachment without filename', () => { it('should generate a filename based on timestamp', () => { - const attachment: Attachment.Attachment = { - data: stringToArrayBuffer('foo'), + const attachment: Attachment.AttachmentType = { contentType: MIME.VIDEO_QUICKTIME, + url: 'funny-cat.mov', + fileName: 'funny-cat.mov', + }; const timestamp = moment('2000-01-01').toDate(); const actual = Attachment.getSuggestedFilename({ @@ -56,10 +61,10 @@ describe('Attachment', () => { }); context('for attachment with index', () => { it('should generate a filename based on timestamp', () => { - const attachment: Attachment.Attachment = { - data: stringToArrayBuffer('foo'), - contentType: MIME.VIDEO_QUICKTIME, - }; + const attachment: Attachment.AttachmentType = { + fileName: 'funny-cat.mov', + url: 'funny-cat.mov', + contentType: MIME.VIDEO_QUICKTIME, }; const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const actual = Attachment.getSuggestedFilename({ attachment, diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index ee8982884..09f6af521 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -330,19 +330,14 @@ export const save = ({ getAbsolutePath, timestamp, }: { - attachment: Attachment; + attachment: AttachmentType; document: Document; - index: number; + index?: number; getAbsolutePath: (relativePath: string) => string; timestamp?: number; }): void => { - const isObjectURLRequired = is.undefined(attachment.path); - const url = !is.undefined(attachment.path) - ? getAbsolutePath(attachment.path) - : arrayBufferToObjectURL({ - data: attachment.data, - type: MIME.APPLICATION_OCTET_STREAM, - }); + const isObjectURLRequired = is.undefined(attachment.fileName); + const url = getAbsolutePath(attachment.fileName); const filename = getSuggestedFilename({ attachment, timestamp, index }); saveURLAsFile({ url, filename, document }); if (isObjectURLRequired) { @@ -355,7 +350,7 @@ export const getSuggestedFilename = ({ timestamp, index, }: { - attachment: Attachment; + attachment: AttachmentType; timestamp?: number | Date; index?: number; }): string => { @@ -375,7 +370,7 @@ export const getSuggestedFilename = ({ }; export const getFileExtension = ( - attachment: Attachment + attachment: AttachmentType ): string | undefined => { if (!attachment.contentType) { return;