diff --git a/app/components/component-submit/src/components/SubmitPage.js b/app/components/component-submit/src/components/SubmitPage.js index c510504b92d171e1d410ae6b3df910f63d339f59..1ba8a7c7f318eedbe64dfdd28069db0288e7449b 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 0432c9488da0896b8e7e21522fc7629516d82cc2..68b89629614c478069428d1d7c8f0e64ea38b868 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 0000000000000000000000000000000000000000..fb6c96449dceff7ad115457742617e4072406a7d --- /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 cdb5e7e0bed5cceb05818b51c8313b0736ebd587..807e004c4350ab54642dd32e46261722fb931738 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 0000000000000000000000000000000000000000..c3d3b6628b9ff57578d4bfe1f512065677fe9bc9 --- /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 0000000000000000000000000000000000000000..29f979895c8b23e1da7ae91c4551ecc3737c6a7c --- /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 9c03d0839662980df9d89035eb141a227f8a089b..62ccca06d70da3fdf4956ae870a9c8b11b4f45f9 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'