From 9d1b8fe6006d5e185b956b1f7f5636abf52e34eb Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Sun, 30 Jun 2019 20:37:36 -0500 Subject: [PATCH] Add a context menu to attachment items with the direct-open / save options #1548 --- .../attachments/lib/message-attachments.tsx | 31 +---- app/src/components/attachment-items.tsx | 120 ++++++++++++------ app/src/flux/actions.ts | 1 - app/src/flux/stores/attachment-store.ts | 7 - app/tsconfig.json | 2 +- 5 files changed, 90 insertions(+), 71 deletions(-) diff --git a/app/internal_packages/attachments/lib/message-attachments.tsx b/app/internal_packages/attachments/lib/message-attachments.tsx index 1f5de7fc1..af7ab2808 100644 --- a/app/internal_packages/attachments/lib/message-attachments.tsx +++ b/app/internal_packages/attachments/lib/message-attachments.tsx @@ -31,26 +31,6 @@ class MessageAttachments extends Component { filePreviewPaths: {}, }; - onOpenAttachment = file => { - Actions.fetchAndOpenFile(file); - }; - - onRemoveAttachment = file => { - const { headerMessageId } = this.props; - Actions.removeAttachment({ - headerMessageId: headerMessageId, - file: file, - }); - }; - - onDownloadAttachment = file => { - Actions.fetchAndSaveFile(file); - }; - - onAbortDownload = file => { - Actions.abortFetchFile(file); - }; - renderAttachment(AttachmentRenderer, file) { const { canRemoveAttachments, downloads, filePreviewPaths } = this.props; const download = downloads[file.id]; @@ -73,10 +53,13 @@ class MessageAttachments extends Component { displaySize={displaySize} fileIconName={fileIconName} filePreviewPath={filePreviewPath} - onOpenAttachment={() => this.onOpenAttachment(file)} - onDownloadAttachment={() => this.onDownloadAttachment(file)} - onAbortDownload={() => this.onAbortDownload(file)} - onRemoveAttachment={canRemoveAttachments ? () => this.onRemoveAttachment(file) : null} + onOpenAttachment={() => Actions.fetchAndOpenFile(file)} + onSaveAttachment={() => Actions.fetchAndSaveFile(file)} + onRemoveAttachment={ + canRemoveAttachments + ? () => Actions.removeAttachment({ headerMessageId: this.props.headerMessageId, file }) + : null + } /> ); } diff --git a/app/src/components/attachment-items.tsx b/app/src/components/attachment-items.tsx index fa184b3ef..e483aff45 100644 --- a/app/src/components/attachment-items.tsx +++ b/app/src/components/attachment-items.tsx @@ -9,6 +9,7 @@ import { pickHTMLProps } from 'pick-react-known-prop'; import { RetinaImg } from './retina-img'; import { Flexbox } from './flexbox'; import { Spinner } from './spinner'; +import { localized } from '../intl'; const propTypes = { className: PropTypes.string, @@ -26,8 +27,7 @@ const propTypes = { filePreviewPath: PropTypes.string, onOpenAttachment: PropTypes.func, onRemoveAttachment: PropTypes.func, - onDownloadAttachment: PropTypes.func, - onAbortDownload: PropTypes.func, + onSaveAttachment: PropTypes.func, }; const defaultProps = { @@ -36,8 +36,47 @@ const defaultProps = { const SPACE = ' '; -function ProgressBar(props) { - const { download } = props; +function buildContextMenu(fns: { + onOpenAttachment?: () => void; + onPreviewAttachment?: () => void; + onRemoveAttachment?: () => void; + onSaveAttachment?: () => void; +}) { + const { remote } = require('electron'); + const template: Electron.MenuItemConstructorOptions[] = []; + if (fns.onOpenAttachment) { + template.push({ + click: () => fns.onOpenAttachment(), + label: localized('Open'), + }); + } + if (fns.onRemoveAttachment) { + template.push({ + click: () => fns.onRemoveAttachment(), + label: localized('Remove'), + }); + } + if (fns.onPreviewAttachment) { + template.push({ + click: () => fns.onPreviewAttachment(), + label: localized('Preview'), + }); + } + if (fns.onSaveAttachment) { + template.push({ + click: () => fns.onSaveAttachment(), + label: localized('Save Into...'), + }); + } + remote.Menu.buildFromTemplate(template).popup({}); +} + +const ProgressBar: React.FunctionComponent<{ + download: { + state: string; + percent: number; + }; +}> = ({ download }) => { const isDownloading = download ? download.state === 'downloading' : false; if (!isDownloading) { return ; @@ -52,8 +91,7 @@ function ProgressBar(props) { ); -} -ProgressBar.propTypes = propTypes; +}; function AttachmentActionIcon(props) { const { @@ -61,9 +99,8 @@ function AttachmentActionIcon(props) { removeIcon, downloadIcon, retinaImgMode, - onAbortDownload, onRemoveAttachment, - onDownloadAttachment, + onSaveAttachment, } = props; const isRemovable = onRemoveAttachment != null; @@ -74,10 +111,8 @@ function AttachmentActionIcon(props) { event.stopPropagation(); // Prevent 'onOpenAttachment' if (isRemovable) { onRemoveAttachment(); - } else if (isDownloading && onAbortDownload != null) { - onAbortDownload(); - } else if (onDownloadAttachment != null) { - onDownloadAttachment(); + } else if (onSaveAttachment != null) { + onSaveAttachment(); } }; @@ -109,8 +144,8 @@ interface AttachmentItemProps { fileIconName?: string; filePreviewPath?: string; onOpenAttachment?: () => void; + onSaveAttachment?: () => void; onRemoveAttachment: () => void; - onDownloadAttachment?: () => void; } export class AttachmentItem extends Component { @@ -142,13 +177,6 @@ export class AttachmentItem extends Component { } }; - _onOpenAttachment = () => { - const { onOpenAttachment } = this.props; - if (onOpenAttachment != null) { - onOpenAttachment(); - } - }; - _onAttachmentKeyDown = event => { if (event.key === SPACE && this.props.filePreviewPath) { event.preventDefault(); @@ -163,9 +191,11 @@ export class AttachmentItem extends Component { } }; - _onClickQuicklookIcon = event => { - event.preventDefault(); - event.stopPropagation(); + _onClickQuicklookIcon = (event?: React.MouseEvent) => { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } Actions.quickPreviewFile(this.props.filePath); }; @@ -179,6 +209,8 @@ export class AttachmentItem extends Component { displaySize, fileIconName, filePreviewPath, + onOpenAttachment, + onSaveAttachment, ...extraProps } = this.props; const classes = classnames({ @@ -197,8 +229,15 @@ export class AttachmentItem extends Component { tabIndex={tabIndex} onKeyDown={focusable ? this._onAttachmentKeyDown : null} draggable={draggable} - onDoubleClick={this._onOpenAttachment} + onDoubleClick={onOpenAttachment} onDragStart={this._onDragStart} + onContextMenu={() => + buildContextMenu({ + onPreviewAttachment: this._onClickQuicklookIcon, + onOpenAttachment, + onSaveAttachment, + }) + } {...pickHTMLProps(extraProps)} > {filePreviewPath ? ( @@ -228,11 +267,11 @@ export class AttachmentItem extends Component { {displaySize ? `(${displaySize})` : ''} - {filePreviewPath ? ( + {filePreviewPath && (
- ) : null} + )} { - const { onOpenAttachment } = this.props; - if (onOpenAttachment != null) { - onOpenAttachment(); - } - }; - _onImgLoaded = () => { // on load, modify our DOM just /slightly/. This causes DOM mutation listeners // watching the DOM to trigger. This is a good thing, because the image may @@ -301,10 +333,19 @@ export class ImageAttachmentItem extends Component +
-
+
buildContextMenu({ onOpenAttachment, onSaveAttachment })} + >
{displayName} diff --git a/app/src/flux/actions.ts b/app/src/flux/actions.ts index ca4b9b53c..9c6a886ac 100644 --- a/app/src/flux/actions.ts +++ b/app/src/flux/actions.ts @@ -463,7 +463,6 @@ export const fetchAndOpenFile = create('fetchAndOpenFile', ActionScopeWindow); export const fetchAndSaveFile = create('fetchAndSaveFile', ActionScopeWindow); export const fetchAndSaveAllFiles = create('fetchAndSaveAllFiles', ActionScopeWindow); export const fetchFile = create('fetchFile', ActionScopeWindow); -export const abortFetchFile = create('abortFetchFile', ActionScopeWindow); export const quickPreviewFile = create('quickPreviewFile', ActionScopeWindow); /* diff --git a/app/src/flux/stores/attachment-store.ts b/app/src/flux/stores/attachment-store.ts index f44864d8a..2bc7eb901 100644 --- a/app/src/flux/stores/attachment-store.ts +++ b/app/src/flux/stores/attachment-store.ts @@ -42,7 +42,6 @@ class AttachmentStore extends MailspringStore { this.listenTo(Actions.fetchAndOpenFile, this._fetchAndOpen); this.listenTo(Actions.fetchAndSaveFile, this._fetchAndSave); this.listenTo(Actions.fetchAndSaveAllFiles, this._fetchAndSaveAll); - this.listenTo(Actions.abortFetchFile, this._abortFetchFile); this.listenTo(Actions.quickPreviewFile, this._quickPreviewFile); // sending @@ -260,12 +259,6 @@ class AttachmentStore extends MailspringStore { }); }; - _abortFetchFile = () => { - // file - // put this back if we ever support downloading individual files again - return; - }; - _defaultSaveDir() { let home = ''; if (process.platform === 'win32') { diff --git a/app/tsconfig.json b/app/tsconfig.json index 2acc96a99..0a27d3ac0 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -7,7 +7,7 @@ "moduleResolution": "node", "esModuleInterop": true, "experimentalDecorators": true, - "lib": ["es2017", "dom"], + "lib": ["es2017", "dom", "esnext.array"], "removeComments": true, "resolveJsonModule": true, "inlineSourceMap": true,