From 666756301f0a79440e070d3795ae670f6981bd98 Mon Sep 17 00:00:00 2001 From: Jure Triglav <juretriglav@gmail.com> Date: Thu, 6 Aug 2020 15:37:28 +0200 Subject: [PATCH] feat: extract and reuse the Dropzone component --- .../src/components/SubmitPage.js | 5 +- .../src/components/Supplementary.js | 39 ++--- .../src/components/UploadButton.js | 33 ++++ .../src/components/UploadManuscript.js | 14 +- .../src/components/UploadingFile.js | 141 ++++++++++++++++++ app/components/shared/Dropzone.jsx | 11 ++ app/components/shared/index.js | 1 + 7 files changed, 213 insertions(+), 31 deletions(-) create mode 100644 app/components/component-submit/src/components/UploadButton.js create mode 100644 app/components/component-submit/src/components/UploadingFile.js create mode 100644 app/components/shared/Dropzone.jsx diff --git a/app/components/component-submit/src/components/SubmitPage.js b/app/components/component-submit/src/components/SubmitPage.js index c510504b92..1ba8a7c7f3 100644 --- a/app/components/component-submit/src/components/SubmitPage.js +++ b/app/components/component-submit/src/components/SubmitPage.js @@ -153,7 +153,7 @@ const SubmitPage = ({ match, history, ...props }) => { const [createFile] = useMutation(createFileMutation) - const createSupplementaryFile = file => { + const createSupplementaryFile = async file => { const meta = { filename: file.name, mimeType: file.type, @@ -163,12 +163,13 @@ const SubmitPage = ({ match, history, ...props }) => { objectId: match.params.version, } - createFile({ + const { data } = await createFile({ variables: { file, meta, }, }) + return data } const [update] = useMutation(updateMutation) diff --git a/app/components/component-submit/src/components/Supplementary.js b/app/components/component-submit/src/components/Supplementary.js index 0432c9488d..68b8962961 100644 --- a/app/components/component-submit/src/components/Supplementary.js +++ b/app/components/component-submit/src/components/Supplementary.js @@ -1,30 +1,33 @@ import React from 'react' import { cloneDeep } from 'lodash' import { FieldArray } from 'formik' -import { Flexbox, UploadButton, UploadingFile } from '@pubsweet/ui' +import { Flexbox } from '@pubsweet/ui' +import styled from 'styled-components' +import UploadingFile from './UploadingFile' +import { Dropzone } from '../../../shared' -const renderFilesUpload = (onChange, createSupplementaryFile) => ({ +const Root = styled.div`` +const renderFilesUpload = createSupplementaryFile => ({ form: { values, setFieldValue }, push, insert, }) => ( <> - <UploadButton - buttonText="↑ Upload files" - onChange={event => { - const fileArray = Array.from(event.target.files).map(file => { - const fileUpload = { - fileType: 'supplementary', - filename: file.name, - } - return fileUpload - }) - setFieldValue('files', fileArray.concat(values.files)) - Array.from(event.target.files).forEach(file => { - createSupplementaryFile(file) + <Dropzone + onDrop={async files => { + Array.from(files).forEach(async file => { + const data = await createSupplementaryFile(file) + push(data.createFile) }) }} - /> + > + {({ getRootProps, getInputProps }) => ( + <Root {...getRootProps()}> + <input {...getInputProps()} /> + <p>Your files here</p> + </Root> + )} + </Dropzone> <Flexbox> {cloneDeep(values.files || []) .filter(val => val.fileType === 'supplementary') @@ -36,10 +39,10 @@ const renderFilesUpload = (onChange, createSupplementaryFile) => ({ </> ) -const Supplementary = ({ onChange, createSupplementaryFile }) => ( +const Supplementary = ({ createSupplementaryFile }) => ( <FieldArray name="files" - render={renderFilesUpload(onChange, createSupplementaryFile)} + render={renderFilesUpload(createSupplementaryFile)} /> ) diff --git a/app/components/component-submit/src/components/UploadButton.js b/app/components/component-submit/src/components/UploadButton.js new file mode 100644 index 0000000000..fb6c96449d --- /dev/null +++ b/app/components/component-submit/src/components/UploadButton.js @@ -0,0 +1,33 @@ +import React from 'react' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' + +const Button = styled.button.attrs(() => ({ + type: 'button', +}))` + background: transparent; + border: ${th('borderWidth')} dashed ${th('colorBorder')}; + height: calc(${th('gridUnit')} * 3); + cursor: pointer; + margin-bottom: calc(${th('gridUnit')} * 3); + padding: ${th('gridUnit')}; +` + +const UploadButton = ({ name, buttonText, onChange }) => { + let fileInput + return ( + <React.Fragment> + <Button onClick={() => fileInput.click()}>{buttonText}</Button> + <input + multiple + name={name} + onChange={onChange} + ref={input => (fileInput = input)} + style={{ display: 'none' }} + type="file" + /> + </React.Fragment> + ) +} + +export default UploadButton diff --git a/app/components/component-submit/src/components/UploadManuscript.js b/app/components/component-submit/src/components/UploadManuscript.js index cdb5e7e0be..807e004c43 100644 --- a/app/components/component-submit/src/components/UploadManuscript.js +++ b/app/components/component-submit/src/components/UploadManuscript.js @@ -2,17 +2,9 @@ import React, { useContext } from 'react' import styled, { keyframes, withTheme } from 'styled-components' import { Icon, Action } from '@pubsweet/ui' import { th } from '@pubsweet/ui-toolkit' -import Dropzone from 'react-dropzone' import { XpubContext } from '../../../xpub-with-context/src' import upload from '../upload' - -const StyledDropzone = styled(({ disableUpload, ...props }) => ( - <Dropzone {...props} /> -))` - border: none; - cursor: pointer; - ${({ disableUpload }) => disableUpload && 'pointer-events: none;'}; -` +import { Dropzone } from '../../../shared' const StatusIcon = withTheme(({ children, theme }) => ( <Icon color={theme.colorPrimary}>{children}</Icon> @@ -188,7 +180,7 @@ const UploadManuscript = ({ acceptFiles, ...props }) => { return ( <> - <StyledDropzone + <Dropzone accept={acceptFiles} data-testid="dropzone" disableUpload={converting ? 'disableUpload' : null} @@ -222,7 +214,7 @@ const UploadManuscript = ({ acceptFiles, ...props }) => { </SubInfo> </Root> )} - </StyledDropzone> + </Dropzone> <Action onClick={() => uploadManuscript()}>Submit a URL instead</Action> </> ) diff --git a/app/components/component-submit/src/components/UploadingFile.js b/app/components/component-submit/src/components/UploadingFile.js new file mode 100644 index 0000000000..c3d3b6628b --- /dev/null +++ b/app/components/component-submit/src/components/UploadingFile.js @@ -0,0 +1,141 @@ +import React from 'react' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' + +const Icon = styled.div` + background: ${th('colorFurniture')}; + height: calc(${th('gridUnit')} * 15); + margin-bottom: ${th('gridUnit')}; + opacity: 0.5; + position: relative; + width: calc(${th('gridUnit')} * 9); +` + +const Progress = styled.div` + color: ${th('colorTextReverse')}; + display: block; + position: absolute; + bottom: ${th('gridUnit')}; + left: calc(${th('gridUnit')} * 4); +` + +const Extension = styled.div` + background: ${th('colorText')}; + color: ${th('colorTextReverse')}; + font-size: ${th('fontSizeBaseSmall')}; + line-height: ${th('lineHeightBaseSmall')}; + left: calc(${th('gridUnit')} * 2); + position: absolute; + right: 0; + text-align: center; + text-transform: uppercase; + top: calc(${th('gridUnit')} * 2); +` + +const Filename = styled.div` + color: ${th('colorText')}; + font-size: ${th('fontSizeBaseSmall')}; + line-height: ${th('lineHeightBaseSmall')}; + font-style: italic; + max-width: calc(${th('gridUnit')} * 30); +` + +const Uploading = styled.div` + align-items: center; + display: inline-flex; + flex-direction: column; + margin-bottom: calc(${th('gridUnit')} * 3); + margin-right: calc(${th('gridUnit')} * 3); + position: relative; + width: calc(${th('gridUnit')} * 30); +` + +const Uploaded = styled(Uploading)` + &::before, + &::after { + cursor: pointer; + transition: transform ${th('transitionDuration')}; + font-size: ${th('fontSizeBaseSmall')}; + line-height: ${th('lineHeightBaseSmall')}; + left: 65%; + padding: 0 ${th('gridUnit')} 0 ${th('gridUnit')}; + position: absolute; + border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorTextReverse')}; + color: ${th('colorTextReverse')}; + text-transform: uppercase; + transform: scaleX(0); + transform-origin: 0 0; + } + + &::after { + background: ${th('colorError')}; + content: 'Remove'; + top: calc(${th('gridUnit')} * 5); + z-index: 2; + } + + &::before { + background: ${th('colorPrimary')}; + content: 'Replace'; + top: calc(${th('gridUnit')} * 10); + z-index: 3; + } + + &:hover ${Extension} { + background: ${th('colorTextReverse')}; + color: ${th('colorPrimary')}; + } + + &:hover ${Icon} { + opacity: 1; + background: ${th('colorPrimary')}; + transform: skewY(6deg) rotate(-6deg); + } + + &:hover::after, + &:hover::before { + transform: scaleX(1); + } +` + +const ErrorWrapper = styled.div` + background: ${th('colorError')}; + border: calc(${th('borderWidth')} * 2) ${th('borderStyle')} + ${th('colorTextReverse')}; + color: ${th('colorTextReverse')}; + font-size: ${th('fontSizeBaseSmall')}; + line-height: ${th('lineHeightBaseSmall')}; + letter-spacing: 0.01em; + opacity: 1; + padding: ${th('gridUnit')} ${th('gridUnit')}; + position: absolute; + top: 25%; + z-index: 4; +` + +const getFileExtension = ({ name }) => name.replace(/^.+\./, '') + +const UploadingFile = ({ file, progress, error, uploaded }) => { + const Root = uploaded ? Uploaded : Uploading + + return ( + <Root> + {!!error && <ErrorWrapper>{error}</ErrorWrapper>} + <Icon> + {!!progress && <Progress>{progress * 100}%</Progress>} + <Extension>{getFileExtension(file)}</Extension> + </Icon> + <Filename> + {uploaded ? ( + <a download={file.name} href={file.url}> + {file.name} + </a> + ) : ( + file.name + )} + </Filename> + </Root> + ) +} + +export default UploadingFile diff --git a/app/components/shared/Dropzone.jsx b/app/components/shared/Dropzone.jsx new file mode 100644 index 0000000000..29f979895c --- /dev/null +++ b/app/components/shared/Dropzone.jsx @@ -0,0 +1,11 @@ +import React from 'react' +import styled from 'styled-components' +import ReactDropzone from 'react-dropzone' + +export const Dropzone = styled(({ disableUpload, ...props }) => ( + <ReactDropzone {...props} /> +))` + border: none; + cursor: pointer; + ${({ disableUpload }) => disableUpload && 'pointer-events: none;'}; +` diff --git a/app/components/shared/index.js b/app/components/shared/index.js index 9c03d08396..62ccca06d7 100644 --- a/app/components/shared/index.js +++ b/app/components/shared/index.js @@ -8,3 +8,4 @@ export * from './Table' export * from './General' export * from './Badge' export * from './Select' +export * from './Dropzone' -- GitLab