From bf5eb8f4bc697592b2bd1b5b1f23d3a275c9d10e Mon Sep 17 00:00:00 2001
From: Alexandru Munteanu <alexandru.munt@gmail.com>
Date: Thu, 10 May 2018 15:15:00 +0300
Subject: [PATCH] feat(files): add preview and download components

---
 .../src/components/Files.js                   | 29 +------
 .../src/components/ManuscriptDetails.js       |  8 +-
 .../src/components/ManuscriptLayout.js        | 17 ++--
 .../src/components/ManuscriptPage.js          |  7 --
 .../src/components/ReviewerReportForm.js      | 16 +---
 .../src/components/Files/FileDownload.js      | 78 +++++++++++++++++++
 .../src/components/Files/FileItem.js          | 39 +++-------
 .../src/components/Files/FilePreview.js       | 41 ++++++++++
 .../src/components/Files/FileSection.js       | 14 ++--
 .../src/components/Files/Files.js             | 23 +-----
 .../src/components/Files/index.js             |  2 +
 11 files changed, 152 insertions(+), 122 deletions(-)
 create mode 100644 packages/components-faraday/src/components/Files/FileDownload.js
 create mode 100644 packages/components-faraday/src/components/Files/FilePreview.js

diff --git a/packages/component-manuscript/src/components/Files.js b/packages/component-manuscript/src/components/Files.js
index 100f05634..7311a7801 100644
--- a/packages/component-manuscript/src/components/Files.js
+++ b/packages/component-manuscript/src/components/Files.js
@@ -5,8 +5,6 @@ import { th } from '@pubsweet/ui'
 import { FileItem } from 'pubsweet-components-faraday/src/components/Files'
 
 const Files = ({
-  previewFile,
-  downloadFile,
   files: { manuscripts = [], coverLetter = [], supplementary = [] },
 }) => (
   <Root>
@@ -17,14 +15,7 @@ const Files = ({
           <div />
         </Header>
         {manuscripts.map(file => (
-          <FileItem
-            compact
-            id={file.id}
-            key={file.id}
-            {...file}
-            downloadFile={downloadFile}
-            previewFile={previewFile}
-          />
+          <FileItem compact id={file.id} key={file.id} {...file} />
         ))}
       </Fragment>
     )}
@@ -35,14 +26,7 @@ const Files = ({
           <div />
         </Header>
         {supplementary.map(file => (
-          <FileItem
-            compact
-            id={file.id}
-            key={file.id}
-            {...file}
-            downloadFile={downloadFile}
-            previewFile={previewFile}
-          />
+          <FileItem compact id={file.id} key={file.id} {...file} />
         ))}
       </Fragment>
     )}
@@ -53,14 +37,7 @@ const Files = ({
           <div />
         </Header>
         {coverLetter.map(file => (
-          <FileItem
-            compact
-            id={file.id}
-            key={file.id}
-            {...file}
-            downloadFile={downloadFile}
-            previewFile={previewFile}
-          />
+          <FileItem compact id={file.id} key={file.id} {...file} />
         ))}
       </Fragment>
     )}
diff --git a/packages/component-manuscript/src/components/ManuscriptDetails.js b/packages/component-manuscript/src/components/ManuscriptDetails.js
index b6e26a84f..b439f1efd 100644
--- a/packages/component-manuscript/src/components/ManuscriptDetails.js
+++ b/packages/component-manuscript/src/components/ManuscriptDetails.js
@@ -7,8 +7,6 @@ import { Authors, Files } from './'
 import Expandable from '../molecules/Expandable'
 
 const ManuscriptDetails = ({
-  previewFile,
-  downloadFile,
   collection: { authors = [] },
   fragment: { conflicts = {}, files = {}, metadata: { abstract = '' } },
 }) => (
@@ -31,11 +29,7 @@ const ManuscriptDetails = ({
       )}
       {!isEmpty(files) && (
         <Expandable label="FILES">
-          <Files
-            downloadFile={downloadFile}
-            files={files}
-            previewFile={previewFile}
-          />
+          <Files files={files} />
         </Expandable>
       )}
     </Expandable>
diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js
index 811a048eb..312a61ad8 100644
--- a/packages/component-manuscript/src/components/ManuscriptLayout.js
+++ b/packages/component-manuscript/src/components/ManuscriptLayout.js
@@ -20,16 +20,14 @@ import {
 } from './'
 
 const ManuscriptLayout = ({
-  currentUser,
-  currentUserIs,
-  editorInChief,
-  updateManuscript,
   project,
   version,
   journal,
-  previewFile,
-  downloadFile,
   history,
+  currentUser,
+  currentUserIs,
+  editorInChief,
+  updateManuscript,
 }) => (
   <Root>
     <Container flex={3}>
@@ -46,12 +44,7 @@ const ManuscriptLayout = ({
         </RightDetails>
       </Header>
       <ManuscriptHeader journal={journal} project={project} version={version} />
-      <ManuscriptDetails
-        collection={project}
-        downloadFile={downloadFile}
-        fragment={version}
-        previewFile={previewFile}
-      />
+      <ManuscriptDetails collection={project} fragment={version} />
       <ReviewsAndReports
         currentUserIs={currentUserIs}
         project={project}
diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js
index 7b130779b..61760e51a 100644
--- a/packages/component-manuscript/src/components/ManuscriptPage.js
+++ b/packages/component-manuscript/src/components/ManuscriptPage.js
@@ -64,13 +64,6 @@ export default compose(
         id: version.id,
         ...data,
       }),
-    previewFile: ({ getSignedUrl }) => fileId => e => {
-      e.preventDefault()
-      getSignedUrl(fileId).then(({ signedUrl }) => {
-        const windowReference = window.open()
-        windowReference.location = signedUrl
-      })
-    },
     downloadFile: ({ getSignedUrl }) => (fileId, fileName) => e => {
       e.preventDefault()
       getSignedUrl(fileId).then(({ signedUrl }) => {
diff --git a/packages/component-manuscript/src/components/ReviewerReportForm.js b/packages/component-manuscript/src/components/ReviewerReportForm.js
index 3f8fe374c..172669256 100644
--- a/packages/component-manuscript/src/components/ReviewerReportForm.js
+++ b/packages/component-manuscript/src/components/ReviewerReportForm.js
@@ -62,14 +62,13 @@ const options = [
 
 const ReviewerReportForm = ({
   error,
+  addFile,
+  removeFile,
+  review = {},
   isSubmitting,
   changeField,
   handleSubmit,
   formValues = {},
-  review = {},
-  addFile,
-  removeFile,
-  previewFile,
 }) => (
   <Root>
     <Row>
@@ -127,8 +126,6 @@ const ReviewerReportForm = ({
             id={file.id}
             key={file.id}
             {...file}
-            downloadFile={previewFile}
-            previewFile={previewFile}
             removeFile={removeFile}
           />
         ))}
@@ -254,13 +251,6 @@ export default compose(
       const newFiles = files.filter(f => f.id !== id)
       changeForm('reviewerReport', 'files', newFiles)
     },
-    previewFile: ({ getSignedUrl }) => fileId => e => {
-      e.preventDefault()
-      const windowReference = window.open()
-      getSignedUrl(fileId).then(({ signedUrl }) => {
-        windowReference.location = signedUrl
-      })
-    },
   }),
   reduxForm({
     form: 'reviewerReport',
diff --git a/packages/components-faraday/src/components/Files/FileDownload.js b/packages/components-faraday/src/components/Files/FileDownload.js
new file mode 100644
index 000000000..c44a1ff1e
--- /dev/null
+++ b/packages/components-faraday/src/components/Files/FileDownload.js
@@ -0,0 +1,78 @@
+import React from 'react'
+import qs from 'querystring'
+import { connect } from 'react-redux'
+import styled from 'styled-components'
+import { Icon, th } from '@pubsweet/ui'
+import { compose, withHandlers } from 'recompose'
+
+const createAnchorElement = (file, filename) => {
+  const url = URL.createObjectURL(file)
+  const a = document.createElement('a')
+
+  a.href = url
+  a.download = filename
+  document.body.appendChild(a)
+
+  return {
+    a,
+    url,
+  }
+}
+
+const removeAnchorElement = (a, url) => {
+  document.body.removeChild(a)
+  URL.revokeObjectURL(url)
+}
+
+const FileDownload = ({ downloadFile }) => (
+  <IconButton onClick={downloadFile}>
+    <Icon primary size={3}>
+      download
+    </Icon>
+  </IconButton>
+)
+
+export default compose(
+  connect(state => ({
+    token: state.currentUser.user.token,
+  })),
+  withHandlers({
+    downloadFile: ({ fileId, token, fileName = 'file' }) => () => {
+      const fileURL = `${
+        window.location.origin
+      }/api/files/${fileId}?${qs.stringify({
+        download: true,
+      })}`
+
+      const xhr = new XMLHttpRequest()
+      xhr.onreadystatechange = function onXhrStateChange() {
+        if (this.readyState === 4) {
+          if (this.status >= 200 && this.status < 300) {
+            const f = new File([this.response], fileName)
+
+            const { a, url } = createAnchorElement(f, fileName)
+            a.click()
+            removeAnchorElement(a, url)
+          }
+        }
+      }
+      xhr.open('GET', fileURL)
+      xhr.responseType = 'blob'
+      xhr.setRequestHeader('Authorization', `Bearer ${token}`)
+      xhr.send()
+    },
+  }),
+)(FileDownload)
+
+// #region styled-components
+const IconButton = styled.div`
+  align-items: center;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  margin: 0 ${th('subGridUnit')};
+  &:hover {
+    opacity: 0.7;
+  }
+`
+// #endregion
diff --git a/packages/components-faraday/src/components/Files/FileItem.js b/packages/components-faraday/src/components/Files/FileItem.js
index 960781cb0..2970a310f 100644
--- a/packages/components-faraday/src/components/Files/FileItem.js
+++ b/packages/components-faraday/src/components/Files/FileItem.js
@@ -1,7 +1,9 @@
 import React, { Fragment } from 'react'
 import { last } from 'lodash'
 import { Icon, th } from '@pubsweet/ui'
-import styled, { withTheme, css } from 'styled-components'
+import styled, { css } from 'styled-components'
+
+import { FilePreview, FileDownload } from './'
 
 const parseFileSize = size => {
   const kbSize = size / 1000
@@ -24,34 +26,19 @@ const hasPreview = (name = '') => {
 }
 
 const FileItem = ({
-  dragHandle,
+  id,
   name,
   size,
-  id,
+  theme,
+  dragHandle,
   removeFile,
-  previewFile,
-  downloadFile,
   compact = false,
-  theme,
-  ...rest
 }) => (
   <Fragment>
     {compact ? (
       <FileRoot data-test={`file-${id}`}>
-        {hasPreview(name) && (
-          <IconButton onClick={previewFile(id)}>
-            <Icon primary size={3}>
-              eye
-            </Icon>
-          </IconButton>
-        )}
-        {downloadFile && (
-          <IconButton onClick={downloadFile(id, name)}>
-            <Icon primary size={3}>
-              download
-            </Icon>
-          </IconButton>
-        )}
+        {hasPreview(name) && <FilePreview fileId={id} />}
+        <FileDownload fileId={id} fileName={name} />
         <FileName>{name}</FileName>
         <FileSize>{parseFileSize(size)}</FileSize>
         {removeFile && (
@@ -70,14 +57,10 @@ const FileItem = ({
           <span>{parseFileSize(size)}</span>
         </Info>
         <Buttons>
-          <button onClick={previewFile(id)}>
-            <Icon color={theme.colorPrimary} size={3}>
-              eye
-            </Icon>
-          </button>
+          {hasPreview(name) && <FilePreview fileId={id} />}
           {removeFile && (
             <button onClick={removeFile(id)} title="Delete">
-              <Icon color={theme.colorPrimary} size={3}>
+              <Icon primary size={3}>
                 trash-2
               </Icon>
             </button>
@@ -88,7 +71,7 @@ const FileItem = ({
   </Fragment>
 )
 
-export default withTheme(FileItem)
+export default FileItem
 
 // #region styles
 const defaultText = css`
diff --git a/packages/components-faraday/src/components/Files/FilePreview.js b/packages/components-faraday/src/components/Files/FilePreview.js
new file mode 100644
index 000000000..d57fa6067
--- /dev/null
+++ b/packages/components-faraday/src/components/Files/FilePreview.js
@@ -0,0 +1,41 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import styled from 'styled-components'
+import { Icon, th } from '@pubsweet/ui'
+import { compose, withHandlers } from 'recompose'
+
+import { getSignedUrl } from '../../redux/files'
+
+const FilePreview = ({ previewFile }) => (
+  <IconButton onClick={previewFile}>
+    <Icon primary size={3}>
+      eye
+    </Icon>
+  </IconButton>
+)
+
+export default compose(
+  connect(null, { getSignedUrl }),
+  withHandlers({
+    previewFile: ({ fileId, getSignedUrl }) => e => {
+      e.preventDefault()
+      const windowReference = window.open()
+      getSignedUrl(fileId).then(({ signedUrl }) => {
+        windowReference.location = signedUrl
+      })
+    },
+  }),
+)(FilePreview)
+
+// #region styled-components
+const IconButton = styled.div`
+  align-items: center;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  margin: 0 ${th('subGridUnit')};
+  &:hover {
+    opacity: 0.7;
+  }
+`
+// #endregion
diff --git a/packages/components-faraday/src/components/Files/FileSection.js b/packages/components-faraday/src/components/Files/FileSection.js
index 130766471..e2ab84274 100644
--- a/packages/components-faraday/src/components/Files/FileSection.js
+++ b/packages/components-faraday/src/components/Files/FileSection.js
@@ -28,6 +28,7 @@ const FileSection = ({
   error,
   title,
   files,
+  theme,
   listId,
   isOver,
   isLast,
@@ -35,18 +36,16 @@ const FileSection = ({
   addFile,
   canDrop,
   moveItem,
-  theme,
   isFileOver,
   removeFile,
-  connectFileDrop,
-  connectDropTarget,
-  allowedFileExtensions,
   isFetching,
   canDropFile,
-  disabledFilepicker,
-  dropSortableFile,
-  previewFile,
   requestPending,
+  connectFileDrop,
+  dropSortableFile,
+  connectDropTarget,
+  disabledFilepicker,
+  allowedFileExtensions,
 }) => (
   <DropSection
     innerRef={instance => {
@@ -95,7 +94,6 @@ const FileSection = ({
       listId={listId}
       listItem={FileItem}
       moveItem={moveItem}
-      previewFile={previewFile}
       removeFile={removeFile}
     />
     <InfoContainer>
diff --git a/packages/components-faraday/src/components/Files/Files.js b/packages/components-faraday/src/components/Files/Files.js
index 14014ae1b..f8b340238 100644
--- a/packages/components-faraday/src/components/Files/Files.js
+++ b/packages/components-faraday/src/components/Files/Files.js
@@ -19,21 +19,18 @@ import FileSection from './FileSection'
 import {
   uploadFile,
   deleteFile,
-  getRequestStatus,
-  getSignedUrl,
   getFileError,
+  getRequestStatus,
 } from '../../redux/files'
 
 const Files = ({
   files,
+  error,
   addFile,
   moveItem,
   removeFile,
   changeList,
   dropSortableFile,
-  previewFile,
-  error,
-  ...rest
 }) => (
   <div>
     <Error show={error}>Error uploading file, please try again.</Error>
@@ -47,7 +44,6 @@ const Files = ({
       listId="manuscripts"
       maxFiles={Number.POSITIVE_INFINITY}
       moveItem={moveItem('manuscripts')}
-      previewFile={previewFile}
       removeFile={removeFile('manuscripts')}
       title="Main manuscript"
     />
@@ -59,7 +55,6 @@ const Files = ({
       listId="supplementary"
       maxFiles={Number.POSITIVE_INFINITY}
       moveItem={moveItem('supplementary')}
-      previewFile={previewFile}
       removeFile={removeFile('supplementary')}
       title="Supplementarry files"
     />
@@ -73,7 +68,6 @@ const Files = ({
       listId="coverLetter"
       maxFiles={1}
       moveItem={moveItem('coverLetter')}
-      previewFile={previewFile}
       removeFile={removeFile('coverLetter')}
       title="Cover letter"
     />
@@ -92,7 +86,6 @@ export default compose(
       changeForm,
       uploadFile,
       deleteFile,
-      getSignedUrl,
     },
   ),
   withState('files', 'setFiles', {
@@ -111,18 +104,6 @@ export default compose(
     },
   }),
   withHandlers({
-    previewFile: ({
-      files,
-      version,
-      getFileName,
-      getSignedUrl,
-    }) => fileId => e => {
-      e.preventDefault()
-      const windowReference = window.open()
-      getSignedUrl(fileId).then(({ signedUrl }) => {
-        windowReference.location = signedUrl
-      })
-    },
     dropSortableFile: ({ files, setFiles, changeForm }) => (
       otherProps,
       dragProps,
diff --git a/packages/components-faraday/src/components/Files/index.js b/packages/components-faraday/src/components/Files/index.js
index ab85f18a5..a4308c6b8 100644
--- a/packages/components-faraday/src/components/Files/index.js
+++ b/packages/components-faraday/src/components/Files/index.js
@@ -1,3 +1,5 @@
 export { default as Files } from './Files'
 export { default as FileItem } from './FileItem'
 export { default as FilePicker } from './FilePicker'
+export { default as FilePreview } from './FilePreview'
+export { default as FileDownload } from './FileDownload'
-- 
GitLab