diff --git a/packages/component-aws-download/src/FileBackend.js b/packages/component-aws-download/src/FileBackend.js index 52a907927cb3fb1a3b20b7f1700285dc51495ed9..74c4a5a76f1d952ccc1405dcd90ac39b52e26718 100644 --- a/packages/component-aws-download/src/FileBackend.js +++ b/packages/component-aws-download/src/FileBackend.js @@ -21,37 +21,41 @@ const FileBackend = app => { app.get('/api/fileZip/:fragmentId', authBearer, async (req, res) => { const archive = archiver('zip') const { fragmentId } = req.params + const getObject = util.promisify(s3.getObject.bind(s3)) + const listObjects = util.promisify(s3.listObjects.bind(s3)) - archive.pipe(res) - res.attachment(`${fragmentId}-archive.zip`) - - const params = { - Bucket: s3Config.bucket, - Prefix: `${fragmentId}`, - } + try { + archive.pipe(res) + res.attachment(`${fragmentId}-archive.zip`) - const listObjects = util.promisify(s3.listObjects.bind(s3)) - const getObject = util.promisify(s3.getObject.bind(s3)) + const params = { + Bucket: s3Config.bucket, + Prefix: `${fragmentId}`, + } - return listObjects(params).then(data => { - Promise.all( - data.Contents.map(content => + const s3Items = await listObjects(params) + const s3Files = await Promise.all( + s3Items.Contents.map(content => getObject({ Bucket: s3Config.bucket, Key: content.Key, }), ), - ).then(files => { - files.forEach((file, index) => { - archive.append(file.Body, { - name: `${_.get(file, 'Metadata.filetype') || - 'supplementary'}/${_.get(file, 'Metadata.filename') || - file.ETag}`, - }) + ) + + s3Files.forEach(f => { + archive.append(f.Body, { + name: `${_.get(f, 'Metadata.filetype') || 'supplementary'}/${_.get( + f, + 'Metadata.filename', + ) || f.ETag}`, }) - archive.finalize() }) - }) + + archive.finalize() + } catch (err) { + res.status(err.statusCode).json({ error: err.message }) + } }) } diff --git a/packages/components-faraday/src/components/Dashboard/DashboardCard.js b/packages/components-faraday/src/components/Dashboard/DashboardCard.js index 5c37210632998bb8f4dc152d678686d445ef3f5c..0b149c4e78ff85308716dfead4c4d7012bd53fe4 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardCard.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardCard.js @@ -1,12 +1,13 @@ import React from 'react' +import { get } from 'lodash' import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { get, isEmpty } from 'lodash' import { Button, Icon } from '@pubsweet/ui' import styled, { css } from 'styled-components' -import { compose, getContext, withHandlers } from 'recompose' +import { compose, getContext } from 'recompose' -import { parseVersion, getFilesURL } from './utils' +import { parseVersion } from './utils' + +import ZipFiles from './ZipFiles' const DashboardCard = ({ deleteProject, @@ -15,15 +16,14 @@ const DashboardCard = ({ version, showAbstractModal, journal, - getItems, ...rest }) => { const { submitted, title, type, version: vers } = parseVersion(version) - const files = getFilesURL(get(version, 'files')) const status = get(project, 'status') || 'Draft' - const hasFiles = !isEmpty(files) const abstract = get(version, 'metadata.abstract') const metadata = get(version, 'metadata') + const files = get(version, 'files') + const hasFiles = files ? Object.values(files).some(f => f.length > 0) : false const journalIssueType = journal.issueTypes.find( t => t.value === get(metadata, 'issue'), ) @@ -47,17 +47,11 @@ const DashboardCard = ({ </ManuscriptInfo> </Left> <Right> - {/* <form onSubmit={getItems}> - <Icon>download</Icon> - <button type="submit">DOWNLOAD</button> - </form> */} - <ClickableIcon - disabled={!hasFiles} - // onClick={() => (hasFiles ? downloadAll(files) : null)} - onClick={getItems} - > - <Icon>download</Icon> - </ClickableIcon> + <ZipFiles disabled={!hasFiles} fragmentId={version.id}> + <ClickableIcon disabled={!hasFiles}> + <Icon>download</Icon> + </ClickableIcon> + </ZipFiles> <ClickableIcon onClick={() => deleteProject(project)}> <Icon>trash-2</Icon> </ClickableIcon> @@ -122,35 +116,7 @@ const DashboardCard = ({ ) : null } -export default compose( - getContext({ journal: PropTypes.object }), - connect(state => ({ - token: state.currentUser.user.token, - })), - withHandlers({ - getItems: ({ version, token }) => () => { - const xhr = new XMLHttpRequest() - xhr.onreadystatechange = function onXhrStateChange() { - if (this.readyState === 4 && this.status === 200) { - const fileName = `${version.id}-archive.zip` - const f = new File([this.response], fileName, { - type: 'application/zip', - }) - const url = URL.createObjectURL(f) - const a = document.createElement('a') - a.href = url - a.download = fileName - document.body.appendChild(a) - a.click() - } - } - xhr.open('GET', `${window.location.origin}/api/fileZip/${version.id}`) - xhr.responseType = 'blob' - xhr.setRequestHeader('Authorization', `Bearer ${token}`) - xhr.send() - }, - }), -)(DashboardCard) +export default compose(getContext({ journal: PropTypes.object }))(DashboardCard) // #region styled-components const defaultText = css` diff --git a/packages/components-faraday/src/components/Dashboard/ZipFiles.js b/packages/components-faraday/src/components/Dashboard/ZipFiles.js new file mode 100644 index 0000000000000000000000000000000000000000..8f6e9ece1807e90e2ffa9ed214c47ed49e3c99e2 --- /dev/null +++ b/packages/components-faraday/src/components/Dashboard/ZipFiles.js @@ -0,0 +1,103 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import styled from 'styled-components' +import { compose, withHandlers, withState } from 'recompose' + +import { Spinner } from '../UIComponents/index' + +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 ZipFiles = ({ + disabled, + fragmentId, + fetching, + children, + downloadFiles, +}) => ( + <Root onClick={!disabled ? downloadFiles : null}> + {fetching ? <Spinner /> : children} + </Root> +) + +const cache = {} + +const Zip = compose( + connect(state => ({ + token: state.currentUser.user.token, + })), + withState('fetching', 'setFetching', false), + withHandlers({ + downloadFiles: ({ fragmentId, token, setFetching, archiveName }) => () => { + if (cache[fragmentId]) { + const fileName = archiveName || `${fragmentId}-archive.zip` + + const { a, url } = createAnchorElement(cache[fragmentId], fileName) + a.click() + removeAnchorElement(a, url) + } else { + setFetching(fetching => true) + const xhr = new XMLHttpRequest() + xhr.onreadystatechange = function onXhrStateChange() { + if (this.readyState === 4) { + setFetching(fetching => false) + if (this.status >= 200 && this.status < 300) { + const fileName = archiveName || `${fragmentId}-archive.zip` + const f = new File([this.response], fileName, { + type: 'application/zip', + }) + cache[fragmentId] = f + + const { a, url } = createAnchorElement(f, fileName) + a.click() + removeAnchorElement(a, url) + } + } + } + xhr.open('GET', `${window.location.origin}/api/fileZip/${fragmentId}`) + xhr.responseType = 'blob' + xhr.setRequestHeader('Authorization', `Bearer ${token}`) + xhr.send() + } + }, + }), +)(ZipFiles) + +Zip.propTypes = { + disabled: PropTypes.bool, + archiveName: PropTypes.string, + fragmentId: PropTypes.string.isRequired, +} + +Zip.defaultProps = { + disabled: false, +} + +export default Zip + +// #region styled components +const Root = styled.div` + align-items: center; + cursor: pointer; + display: flex; + margin: 0 7px; + width: 38px; +` +// #endregion diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index 71f7672f6f881a00488228551783c2093628de64..27413d8af8bb12feb820b5089e8ca04144f264b3 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -34,7 +34,7 @@ module.exports = { 'pubsweet-client': { API_ENDPOINT: '/api', 'login-redirect': '/', - 'redux-log': false, + 'redux-log': true, theme: process.env.PUBSWEET_THEME, }, 'mail-transport': {