diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index ce3745ce0117574d4688b99c38a3480e9a14c183..ea3c21e2a366384e16ffbf697a8ac4828992f064 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -14,6 +14,7 @@ import { import { SideBarRoles, SideBarActions, + SubmitRevision, ManuscriptHeader, ReviewsAndReports, ManuscriptDetails, @@ -60,6 +61,9 @@ const ManuscriptLayout = ({ recommendations={editorialRecommendations} /> )} + {version.revision && ( + <SubmitRevision project={project} version={version} /> + )} </Container> <SideBar flex={1}> <SideBarActions project={project} version={version} /> diff --git a/packages/component-manuscript/src/components/ReviewerReportForm.js b/packages/component-manuscript/src/components/ReviewerReportForm.js index 2da95ea14fcfeb5d219f7f47a57829a27facb1c6..4ec7c32476a16eaab4b13f2da8ac8711149492a9 100644 --- a/packages/component-manuscript/src/components/ReviewerReportForm.js +++ b/packages/component-manuscript/src/components/ReviewerReportForm.js @@ -169,7 +169,7 @@ const ReviewerReportForm = ({ </Row> )} <Row> - <ActionButton onClick={handleSubmit}> Submit report </ActionButton> + <ActionButton onClick={handleSubmit}>Submit report</ActionButton> <AutosaveIndicator formName="reviewerReport" lastUpdated={review.updatedOn} diff --git a/packages/component-manuscript/src/components/SubmitRevision.js b/packages/component-manuscript/src/components/SubmitRevision.js new file mode 100644 index 0000000000000000000000000000000000000000..a25bb1539a11e616b98aa5ce40c79a191205139a --- /dev/null +++ b/packages/component-manuscript/src/components/SubmitRevision.js @@ -0,0 +1,266 @@ +import React from 'react' +import { get, omit, debounce } from 'lodash' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { th } from '@pubsweet/ui-toolkit' +import { actions } from 'pubsweet-client' +import { DragDropContext } from 'react-dnd' +import { + Field, + reduxForm, + getFormValues, + change as changeForm, +} from 'redux-form' +import styled, { css } from 'styled-components' +import HTML5Backend from 'react-dnd-html5-backend' +import { ValidatedField, Button } from '@pubsweet/ui' +import { AbstractEditor, TitleEditor } from 'xpub-edit' +import { AuthorList, Files } from 'pubsweet-components-faraday/src/components' +import AutosaveIndicator from 'pubsweet-component-wizard/src/components/AutosaveIndicator' +import { + toClass, + compose, + withProps, + withContext, + withHandlers, +} from 'recompose' +import { + FileItem, + FilePicker, +} from 'pubsweet-components-faraday/src/components/Files' +import { + uploadFile, + deleteFile, + getSignedUrl, + getRequestStatus, +} from 'pubsweet-components-faraday/src/redux/files' + +import { Expandable } from '../molecules/' + +const TextAreaField = input => <Textarea {...input} height={70} rows={6} /> +const SubmitRevision = ({ + addFile, + project, + version, + removeFile, + handleSubmit, + responseFiles, +}) => ( + <Root> + <Expandable label="Submit Revision" startExpanded> + <Expandable label="DETAILS & AUTHORS"> + <Title>MANUSCRIPT TITLE*</Title> + <ValidatedField + component={input => <TitleEditor {...input} />} + name="metadata.title" + /> + <Title>ABSTRACT*</Title> + <ValidatedField + component={input => <AbstractEditor {...input} />} + name="metadata.abstract" + /> + <Title>AUTHORS DETAILS*</Title> + <Field + component={() => ( + <AuthorList authorPath="revision.authors" parentForm="revision" /> + )} + name="authors" + /> + </Expandable> + <Expandable label="FILES"> + <Field + component={() => ( + <Files filePath="revision.files" parentForm="revision" /> + )} + name="files" + /> + </Expandable> + <Expandable label="RESPONSE TO REVIEWER COMMENTS" startExpanded> + <Title>Reply text*</Title> + <Row> + <FullWidth className="full-width"> + <ValidatedField component={TextAreaField} name="response" /> + </FullWidth> + </Row> + <Row left> + {responseFiles.map(file => ( + <FileItem + compact + id={file.id} + key={file.id} + {...file} + removeFile={removeFile} + /> + ))} + </Row> + <FilePicker + allowedFileExtensions={['pdf', 'doc', 'docx', 'png']} + onUpload={addFile} + > + <ActionText left={12}>Upload file</ActionText> + </FilePicker> + </Expandable> + <SubmitContainer> + <AutosaveIndicator formName="revision" /> + <Button onClick={handleSubmit} primary> + SUBMIT REVISION + </Button> + </SubmitContainer> + </Expandable> + </Root> +) + +const onChange = (values, dispatch, { project, version }) => { + const newValues = omit(values, 'authorForm') + dispatch( + actions.updateFragment(project, { + ...version, + revision: newValues, + }), + ) +} + +export default compose( + withContext( + { + version: PropTypes.object, + project: PropTypes.object, + }, + ({ project, version }) => ({ + project, + version, + }), + ), + connect( + state => ({ + fileFetching: getRequestStatus(state), + formValues: getFormValues('revision')(state), + }), + { + changeForm, + uploadFile, + deleteFile, + getSignedUrl, + getRequestStatus, + }, + ), + withHandlers({ + addFile: ({ formValues = {}, uploadFile, changeForm, version }) => file => { + uploadFile(file, 'responseToReviewers', version.id) + .then(file => { + const { files } = formValues + const newFiles = files.responseToReviewers + ? [...files.responseToReviewers, file] + : [file] + changeForm('revision', 'files', { + ...files, + responseToReviewers: newFiles, + }) + }) + .catch(e => console.error(`Couldn't upload file.`, e)) + }, + removeFile: ({ + formValues: { files }, + changeForm, + deleteFile, + }) => id => e => { + deleteFile(id) + .then(r => { + const newFiles = files.responseToReviewers.filter(f => f.id !== id) + changeForm('revision', 'files', { + ...files, + responseToReviewers: newFiles, + }) + }) + .catch(e => console.error(`Couldn't delete the file.`, e)) + }, + }), + withProps(({ version, formValues }) => ({ + initialValues: { + metadata: { + title: get(version, 'revision.metadata.title') || '', + abstract: get(version, 'revision.metadata.abstract') || '', + }, + authors: get(version, 'revision.authors'), + files: get(version, 'revision.files') || [], + response: get(version, 'revision.response'), + }, + responseFiles: get(formValues, 'files.responseToReviewers') || [], + })), + reduxForm({ + form: 'revision', + onChange: debounce(onChange, 1000, { maxWait: 5000 }), + onSubmit: () => {}, + }), + DragDropContext(HTML5Backend), + toClass, +)(SubmitRevision) + +// #region styled-components +const defaultText = css` + color: ${th('colorPrimary')}; + font-family: ${th('fontReading')}; + font-size: ${th('fontSizeBaseSmall')}; +` + +const Textarea = styled.textarea` + border-color: ${({ hasError }) => + hasError ? th('colorError') : th('colorPrimary')}; + font-size: ${th('fontSizeBaseSmall')}; + font-family: ${th('fontWriting')}; + padding: calc(${th('subGridUnit')} * 2); + transition: all 300ms linear; + width: 100%; + + &:read-only { + background-color: ${th('colorBackgroundHue')}; + } +` + +const FullWidth = styled.div` + flex: 1; + > div { + flex: 1; + } +` + +const Row = styled.div` + ${defaultText}; + align-items: center; + box-sizing: border-box; + display: flex; + flex-direction: row; + flex: 1; + flex-wrap: wrap; + justify-content: ${({ left }) => (left ? 'left' : 'space-between')}; + + div[role='alert'] { + margin-top: 0; + } +` + +const ActionText = styled.span` + ${defaultText}; + cursor: pointer; + margin-left: ${({ left }) => left || 0}px; + text-decoration: underline; +` + +const Root = styled.div` + background-color: ${th('colorBackground')}; +` + +const Title = styled.span` + align-self: center; + font-size: ${th('fontSizeBase')}; + margin-bottom: 10px; +` + +const SubmitContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; + justify-content: flex-end; + margin-top: calc(${th('subGridUnit')} * 2); +` +// #endregion diff --git a/packages/component-manuscript/src/components/index.js b/packages/component-manuscript/src/components/index.js index dadf8d4e6ffd637ee1faed3f90c00d99978b5c20..8c668d3d54aff7712e20f3e6060f601772499f61 100644 --- a/packages/component-manuscript/src/components/index.js +++ b/packages/component-manuscript/src/components/index.js @@ -4,6 +4,7 @@ export { default as ShowMore } from './ShowMore' export { default as SideBarRoles } from './SideBarRoles' export { default as ManuscriptPage } from './ManuscriptPage' export { default as SideBarActions } from './SideBarActions' +export { default as SubmitRevision } from './SubmitRevision' export { default as EditorialComment } from './EditorialComment' export { default as ReviewReportCard } from './ReviewReportCard' export { default as ManuscriptHeader } from './ManuscriptHeader' @@ -12,5 +13,5 @@ export { default as ManuscriptDetails } from './ManuscriptDetails' export { default as ManuscriptVersion } from './ManuscriptVersion' export { default as ReviewsAndReports } from './ReviewsAndReports' export { default as EditorialComments } from './EditorialComments' -export { default as ReviewerReportForm } from './ReviewerReportForm' export { default as ReviewReportsList } from './ReviewReportsList' +export { default as ReviewerReportForm } from './ReviewerReportForm' diff --git a/packages/component-wizard/src/components/AutosaveIndicator.js b/packages/component-wizard/src/components/AutosaveIndicator.js index 67f658c843283ca7aaa0b29bf441aa3d2d8a24a2..ea41ee9dbb6434bbfbb34743af57ef67308c05b1 100644 --- a/packages/component-wizard/src/components/AutosaveIndicator.js +++ b/packages/component-wizard/src/components/AutosaveIndicator.js @@ -63,9 +63,9 @@ const Indicator = ({ ) export default compose( - connect((state, { formName }) => ({ + connect((state, { formName = 'wizard' }) => ({ autosave: getAutosave(state), - form: !!getFormValues(formName || 'wizard')(state), + form: !!getFormValues(formName)(state), })), withProps(({ autosave: { isFetching, error, lastUpdate }, form }) => ({ isVisibile: form && Boolean(isFetching || lastUpdate || error), diff --git a/packages/components-faraday/src/components/AuthorList/AuthorAdder.js b/packages/components-faraday/src/components/AuthorList/AuthorAdder.js index 023eaa2c99b3243b6b7d9a010facd6ae17f87513..b5ba3e1e94f19e9b14520b6fe510e8e97b4bac95 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorAdder.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorAdder.js @@ -105,7 +105,15 @@ export default compose( onSubmit: ( values, dispatch, - { reset, match, changeForm, addAuthor, setEditMode, authors = [] }, + { + reset, + match, + changeForm, + addAuthor, + setEditMode, + authors = [], + parentForm, + }, ) => { const collectionId = get(match, 'params.project') const fragmentId = get(match, 'params.version') @@ -116,7 +124,7 @@ export default compose( isCorresponding: isFirstAuthor, } addAuthor(newAuthor, collectionId, fragmentId).then(author => { - changeForm('wizard', 'authors', [...authors, author]) + changeForm(parentForm, 'authors', [...authors, author]) setEditMode(false)() reset() }) diff --git a/packages/components-faraday/src/components/AuthorList/AuthorEditor.js b/packages/components-faraday/src/components/AuthorList/AuthorEditor.js index 185296d68773dbb6c6abd6cbdad0d1d5b7acbb95..c8dd1d3d28672334d1818d3459ffc7c5bb5fb192 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorEditor.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorEditor.js @@ -95,10 +95,10 @@ export default compose( onSubmit: ( { edit: newAuthor }, dispatch, - { authors, changeForm, setAuthorEdit }, + { authors, changeForm, setAuthorEdit, parentForm }, ) => { const newAuthors = parseEditedAuthors(newAuthor, authors) - changeForm('wizard', 'authors', newAuthors) + changeForm(parentForm, 'authors', newAuthors) setAuthorEdit(-1)() }, }), diff --git a/packages/components-faraday/src/components/AuthorList/AuthorList.js b/packages/components-faraday/src/components/AuthorList/AuthorList.js index 81a2561a58d7d6d3a21b4dde5b9cd1a6da263730..81579608a6a4e223d15bb75b8554cb5153ab4605 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorList.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorList.js @@ -27,8 +27,6 @@ import { import { DragHandle } from './FormItems' import { Author, StaticList, AuthorAdder, AuthorEditor } from './' -const wizardSelector = formValueSelector('wizard') - const Authors = ({ match, error, @@ -43,6 +41,7 @@ const Authors = ({ setEditMode, editedAuthor, setFormAuthors, + parentForm = 'wizard', ...rest }) => ( <Root> @@ -52,6 +51,7 @@ const Authors = ({ editAuthor={editAuthor} editMode={addMode} match={match} + parentForm={parentForm} setEditMode={setEditMode} setFormAuthors={setFormAuthors} /> @@ -60,6 +60,7 @@ const Authors = ({ authors={authors} editComponent={AuthorEditor} editIndex={editMode} + parentForm={parentForm} setFormAuthors={setFormAuthors} version={version} {...rest} @@ -83,11 +84,11 @@ export default compose( withRouter, getContext({ version: PropTypes.object, project: PropTypes.object }), connect( - (state, { project }) => ({ + (state, { project, parentForm = 'wizard' }) => ({ error: getAuthorError(state), currentUser: get(state, 'currentUser.user'), version: selectCurrentVersion(state, project), - authorForm: wizardSelector(state, 'authorForm'), + authorForm: formValueSelector(parentForm)(state, 'authorForm'), }), { addAuthor, @@ -97,25 +98,30 @@ export default compose( }, ), withState('authors', 'setAuthors', []), - withProps(({ version, authorForm }) => ({ - authors: get(version, 'authors') || [], + withProps(({ version, authorPath = 'authors', authorForm }) => ({ + authors: get(version, authorPath) || [], addMode: isBoolean(authorForm) && authorForm, editMode: isNumber(authorForm) ? authorForm : -1, })), withHandlers({ - setFormAuthors: ({ setAuthors, changeForm }) => (authors = []) => { + setFormAuthors: ({ setAuthors, changeForm, parentForm = 'wizard' }) => ( + authors = [], + ) => { setAuthors(authors) - changeForm('wizard', 'authors', authors) + changeForm(parentForm, 'authors', authors) }, }), withHandlers({ - setAuthorEdit: ({ changeForm }) => authorIndex => e => { + setAuthorEdit: ({ + changeForm, + parentForm = 'wizard', + }) => authorIndex => e => { e && e.preventDefault && e.preventDefault() - changeForm('wizard', 'authorForm', authorIndex) + changeForm(parentForm, 'authorForm', authorIndex) }, - setEditMode: ({ changeForm }) => mode => e => { + setEditMode: ({ changeForm, parentForm = 'wizard' }) => mode => e => { e && e.preventDefault() - changeForm('wizard', 'authorForm', mode) + changeForm(parentForm, 'authorForm', mode) }, parseAuthorType: () => (isSubmitting, isCorresponding, index) => { if (isSubmitting) return `#${index + 1} Submitting author` diff --git a/packages/components-faraday/src/components/AuthorList/StaticList.js b/packages/components-faraday/src/components/AuthorList/StaticList.js index f6f82fc5bd649641563777bceb170b1e161847e5..c9e6fc756411e091cb0ef48b726fe993008ef1b2 100644 --- a/packages/components-faraday/src/components/AuthorList/StaticList.js +++ b/packages/components-faraday/src/components/AuthorList/StaticList.js @@ -7,6 +7,7 @@ export default ({ project, authors, editIndex, + parentForm, removeAuthor, editComponent, setAuthorEdit, @@ -28,6 +29,7 @@ export default ({ setAuthors: setFormAuthors, setAuthorEdit, parseAuthorType, + parentForm, project, version, ...author, diff --git a/packages/components-faraday/src/components/Files/Files.js b/packages/components-faraday/src/components/Files/Files.js index f99ea6586e41b6af2ac0cabfefb960a3c9f48e8d..aa5035fe22c718dd16d80cb7f0e36b4d2e7e78eb 100644 --- a/packages/components-faraday/src/components/Files/Files.js +++ b/packages/components-faraday/src/components/Files/Files.js @@ -120,36 +120,43 @@ export default compose( }), lifecycle({ componentDidMount() { - const { version: { files }, setFiles } = this.props + const { version, setFiles, filePath = 'files' } = this.props setFiles(prev => ({ - manuscripts: get(files, 'manuscripts') || [], - coverLetter: get(files, 'coverLetter') || [], - supplementary: get(files, 'supplementary') || [], - responseToReviewers: get(files, 'responseToReviewers') || [], + manuscripts: get(version, `${filePath}.manuscripts`) || [], + coverLetter: get(version, `${filePath}.coverLetter`) || [], + supplementary: get(version, `${filePath}.supplementary`) || [], + responseToReviewers: + get(version, `${filePath}.responseToReviewers`) || [], })) }, componentWillReceiveProps(nextProps) { - const { setFiles, version: { files: previousFiles } } = this.props - const { version: { files } } = nextProps + const { setFiles, version, filePath = 'files' } = this.props + const { version: newVersion } = nextProps + + const files = get(newVersion, filePath) + const previousFiles = get(version, filePath) + if (!isEqual(previousFiles, files)) { setFiles(files) } }, }), withHandlers({ - dropSortableFile: ({ files, setFiles, changeForm }) => ( - otherProps, - dragProps, - ) => { + dropSortableFile: ({ + files, + setFiles, + changeForm, + parentForm = 'wizard', + }) => (otherProps, dragProps) => { // do something if the file is not changing list const { listId: fromListId } = otherProps const { listId: toListId } = dragProps if (fromListId === toListId) { setFiles(files) - changeForm('wizard', 'files', files) + changeForm(parentForm, 'files', files) } }, - changeList: ({ files, setFiles, changeForm }) => ( + changeList: ({ files, setFiles, changeForm, parentForm = 'wizard' }) => ( fromListId, toListId, id, @@ -165,7 +172,7 @@ export default compose( [fromListId]: fromFiles, } setFiles(newFiles) - changeForm('wizard', 'files', newFiles) + changeForm(parentForm, 'files', newFiles) }, addFile: ({ files, @@ -173,6 +180,7 @@ export default compose( setFiles, uploadFile, changeForm, + parentForm = 'wizard', }) => type => file => { uploadFile(file, type, version.id) .then(file => { @@ -181,7 +189,7 @@ export default compose( [type]: [...files[type], file], } setFiles(newFiles) - changeForm('wizard', 'files', newFiles) + changeForm(parentForm, 'files', newFiles) }) .catch(e => console.error(`Couldn't upload file.`, e)) }, @@ -196,11 +204,12 @@ export default compose( setFiles(newFiles) }, removeFile: ({ + files, + version, setFiles, changeForm, - files, deleteFile, - version, + parentForm = 'wizard', }) => type => id => e => { e.preventDefault() deleteFile(id, type) @@ -210,7 +219,7 @@ export default compose( [type]: files[type].filter(f => f.id !== id), } setFiles(newFiles) - changeForm('wizard', 'files', newFiles) + changeForm(parentForm, 'files', newFiles) }) .catch(e => console.error(`Couldn't delete file.`, e)) }, diff --git a/packages/xpub-faraday/app/config/journal/submit-wizard.js b/packages/xpub-faraday/app/config/journal/submit-wizard.js index a7c54767357c09f25fe2d67e4e7084471749ad02..721801a6752d04af76db334f1ef5b1ab98a1e8d5 100644 --- a/packages/xpub-faraday/app/config/journal/submit-wizard.js +++ b/packages/xpub-faraday/app/config/journal/submit-wizard.js @@ -1,6 +1,5 @@ import React from 'react' import styled from 'styled-components' -import uploadFileFn from 'xpub-upload' import { AbstractEditor, TitleEditor } from 'xpub-edit' import { Menu, YesOrNo, CheckboxGroup } from '@pubsweet/ui' import { required, minChars, minSize } from 'xpub-validators' @@ -47,8 +46,6 @@ const journal = { value: 'hindawi-faraday', } -const uploadFile = input => uploadFileFn(input) - export default { showProgress: true, formSectionKeys: [ @@ -59,7 +56,7 @@ export default { 'files', ], submissionRedirect: '/confirmation-page', - dispatchFunctions: [uploadFile], + dispatchFunctions: [], steps: [ { label: 'Journal details', diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js index fa6222be84d3181f4942373e7b06a7b6cb6f9b3d..3f3fdf542bedbaacffe7e287e96963c9ed095bd5 100644 --- a/packages/xpub-faraday/config/validations.js +++ b/packages/xpub-faraday/config/validations.js @@ -84,6 +84,7 @@ module.exports = { decision: Joi.object(), authors: Joi.array(), invitations: Joi.array(), + revision: Joi.any(), recommendations: Joi.array().items( Joi.object({ id: Joi.string().required(),