diff --git a/packages/component-wizard/src/components/index.js b/packages/component-wizard/src/components/index.js index 0c688b4063ed20527e1eb26cf90fbf1f54adddc8..276990a4799cd9b46ff99e66ecfe34e15ca1388d 100644 --- a/packages/component-wizard/src/components/index.js +++ b/packages/component-wizard/src/components/index.js @@ -1,4 +1,3 @@ -export { default as Files } from './Files' export { default as Wizard } from './Wizard' export { default as Progress } from './Progress' export { default as WizardPage } from './WizardPage' diff --git a/packages/component-wizard/src/index.js b/packages/component-wizard/src/index.js index b8ae61ac65482a61395c249a822ebc02d757d7e1..b43332ee55fa1efd10fa25e23eb759af7ccb18ea 100644 --- a/packages/component-wizard/src/index.js +++ b/packages/component-wizard/src/index.js @@ -4,7 +4,6 @@ module.exports = { reducers: { wizardConversion: () => require('./redux/conversion').default, autosave: () => require('./redux/autosave').default, - files: () => require('./redux/files').default, }, }, } diff --git a/packages/component-wizard/src/redux/index.js b/packages/component-wizard/src/redux/index.js index ac24b732b1d86380230cca1ac035c37ec58d9769..f767d735102b76804d7a5ba91ec0d08707d68bae 100644 --- a/packages/component-wizard/src/redux/index.js +++ b/packages/component-wizard/src/redux/index.js @@ -1,3 +1,2 @@ -export { default as files } from './files' export { default as autosave } from './autosave' export { default as conversion } from './conversion' diff --git a/packages/components-faraday/src/components/AuthorList/AuthorAdder.js b/packages/components-faraday/src/components/AuthorList/AuthorAdder.js index 5e9587c4091a115ba4184efcf983ec6648317597..a3380004f8142fa5c4498b6a82ee3c1468075db2 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorAdder.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorAdder.js @@ -7,7 +7,9 @@ import { reduxForm } from 'redux-form' import { compose, withProps } from 'recompose' import { selectCurrentUser } from 'xpub-selectors' +import { Spinner } from '../UIComponents/' import classes from './AuthorList.local.scss' +import { getAuthorFetching } from '../../redux/authors' import { MenuItem, ValidatedTextField } from './FormItems' const countries = [ @@ -22,7 +24,13 @@ const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/) const emailValidator = value => emailRegex.test(value) ? undefined : 'Invalid email' -const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => ( +const AuthorAdder = ({ + authors, + editMode, + setEditMode, + handleSubmit, + isFetching, +}) => ( <div className={classnames(classes.adder)}> <Button onClick={setEditMode(true)} primary> {authors.length === 0 ? '+ Add submitting author' : '+ Add author'} @@ -54,9 +62,13 @@ const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => ( </div> <div className={classnames(classes['form-buttons'])}> <Button onClick={setEditMode(false)}>Cancel</Button> - <Button onClick={handleSubmit} primary> - Save - </Button> + {!isFetching ? ( + <Button onClick={handleSubmit} primary> + Save + </Button> + ) : ( + <Spinner /> + )} </div> </div> )} @@ -66,6 +78,7 @@ const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => ( export default compose( connect(state => ({ currentUser: selectCurrentUser(state), + isFetching: getAuthorFetching(state), })), withProps(({ currentUser: { admin, username, email }, authors }) => { if (!admin && authors.length === 0) { @@ -83,7 +96,7 @@ export default compose( onSubmit: ( values, dispatch, - { authors, addAuthor, setEditMode, reset, match }, + { authors, addAuthor, setEditMode, setFormAuthors, reset, match }, ) => { const collectionId = get(match, 'params.project') const fragmentId = get(match, 'params.version') @@ -96,7 +109,9 @@ export default compose( }, collectionId, fragmentId, - ).then(() => { + ).then(author => { + const newAuthors = [...authors, author] + setFormAuthors(newAuthors) reset() setEditMode(false)() }) diff --git a/packages/components-faraday/src/components/AuthorList/AuthorEditor.js b/packages/components-faraday/src/components/AuthorList/AuthorEditor.js index 6ceffb8430c9b9f93459763d1a68b04a551a19d4..10f4f5a81c2915fc8ff19986f2b87a21f1d3e88b 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorEditor.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorEditor.js @@ -1,14 +1,13 @@ import React from 'react' -import PropTypes from 'prop-types' import classnames from 'classnames' -import { connect } from 'react-redux' +import { compose } from 'recompose' import { Button } from '@pubsweet/ui' +import { connect } from 'react-redux' import { reduxForm } from 'redux-form' -import { withRouter } from 'react-router-dom' -import { compose, getContext } from 'recompose' +import { Spinner } from '../UIComponents' +import { getAuthorFetching } from '../../redux/authors' import { ValidatedTextField, MenuItem } from './FormItems' -import { getFragmentAuthors, setAuthors } from '../../redux/authors' import classes from './AuthorList.local.scss' @@ -24,7 +23,7 @@ const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/) const emailValidator = value => emailRegex.test(value) ? undefined : 'Invalid email' -const AuthorEdit = ({ setAuthorEdit, handleSubmit }) => ( +const AuthorEdit = ({ isFetching, setAuthorEdit, handleSubmit }) => ( <div className={classnames(classes['editor-body'])}> <div className={classnames(classes.row)}> <ValidatedTextField isRequired label="First name" name="edit.firstName" /> @@ -49,30 +48,27 @@ const AuthorEdit = ({ setAuthorEdit, handleSubmit }) => ( <div className={classnames(classes['form-buttons'])}> <Button onClick={setAuthorEdit(-1)}>Cancel</Button> - <Button onClick={handleSubmit} primary> - Save - </Button> + {!isFetching ? ( + <Button onClick={handleSubmit} primary> + Save + </Button> + ) : ( + <Spinner /> + )} </div> </div> ) export default compose( - withRouter, - getContext({ version: PropTypes.object, project: PropTypes.object }), - connect( - (state, { match: { params: { version } } }) => ({ - authors: getFragmentAuthors(state, version), - }), - { - setAuthors, - }, - ), + connect(state => ({ + isFetching: getAuthorFetching(state), + })), reduxForm({ form: 'edit', onSubmit: ( values, dispatch, - { setAuthorEdit, setAuthors, project, version, authors, index, ...rest }, + { setAuthorEdit, setAuthors, authors, index, changeForm }, ) => { const newAuthors = [ ...authors.slice(0, index), @@ -80,7 +76,7 @@ export default compose( ...authors.slice(index + 1), ] setAuthorEdit(-1)() - setAuthors(newAuthors, version.id) + setAuthors(newAuthors) }, }), )(AuthorEdit) diff --git a/packages/components-faraday/src/components/AuthorList/AuthorList.js b/packages/components-faraday/src/components/AuthorList/AuthorList.js index 9576a82d93eea2f15673629b88232beafe349129..06f4f7bb27e3fb4350c902059019f360770690db 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorList.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorList.js @@ -9,15 +9,10 @@ import { lifecycle, withState, } from 'recompose' -import { change } from 'redux-form' +import { change as changeForm } from 'redux-form' import { SortableList } from 'pubsweet-components-faraday/src/components' -import { - addAuthor, - getFragmentAuthors, - setAuthors, - moveAuthors, -} from '../../redux/authors' +import { addAuthor } from '../../redux/authors' import Author from './Author' import StaticList from './StaticList' @@ -43,6 +38,7 @@ const Authors = ({ editMode, setEditMode, editedAuthor, + setFormAuthors, ...rest }) => ( <div> @@ -53,12 +49,14 @@ const Authors = ({ editMode={editMode} match={match} setEditMode={setEditMode} + setFormAuthors={setFormAuthors} /> {editedAuthor > -1 ? ( <StaticList authors={authors} editComponent={AuthorEditor} editIndex={editedAuthor} + setFormAuthors={setFormAuthors} {...rest} /> ) : ( @@ -79,38 +77,38 @@ const Authors = ({ export default compose( withRouter, getContext({ version: PropTypes.object, project: PropTypes.object }), - connect( - (state, { match: { params: { version } } }) => ({ - authors: getFragmentAuthors(state, version), - }), - { - addAuthor, - setAuthors, - moveAuthors, - formChange: change, - }, - ), + connect(null, { + addAuthor, + changeForm, + }), + withState('authors', 'setAuthors', []), lifecycle({ componentDidMount() { const { version, setAuthors } = this.props - setAuthors(version.authors, version.id) + setAuthors(version.authors) }, }), withState('editMode', 'setEditMode', false), withState('editedAuthor', 'setEditedAuthor', -1), withHandlers({ - setAuthorEdit: ({ setEditedAuthor, formChange }) => editedAuthor => e => { + setFormAuthors: ({ setAuthors, changeForm }) => authors => { + setAuthors(authors) + changeForm('wizard', 'authors', authors) + }, + }), + withHandlers({ + setAuthorEdit: ({ setEditedAuthor, changeForm }) => editedAuthor => e => { e && e.preventDefault && e.preventDefault() - formChange('wizard', 'editMode', editedAuthor > -1) + changeForm('wizard', 'editMode', editedAuthor > -1) setEditedAuthor(prev => editedAuthor) }, - setEditMode: ({ setEditMode, formChange }) => mode => e => { + setEditMode: ({ setEditMode, changeForm }) => mode => e => { e && e.preventDefault() - formChange('wizard', 'editMode', mode) + changeForm('wizard', 'editMode', mode) setEditMode(v => mode) }, - dropItem: ({ authors, project, version, setAuthors }) => () => { - setAuthors(authors, version.id) + dropItem: ({ authors, setFormAuthors }) => () => { + setFormAuthors(authors) }, countryParser: () => countryCode => countries.find(c => c.value === countryCode).label, @@ -119,36 +117,23 @@ export default compose( if (isCorresponding) return `#${index + 1} Corresponding author` return `#${index + 1} Author` }, - moveAuthor: ({ - authors, - moveAuthors, - project, - version, - match: { params }, - }) => (dragIndex, hoverIndex) => { + moveAuthor: ({ authors, setAuthors, changeForm }) => ( + dragIndex, + hoverIndex, + ) => { const newAuthors = SortableList.moveItem(authors, dragIndex, hoverIndex) - moveAuthors(newAuthors, params.version) + setAuthors(newAuthors) }, - removeAuthor: ({ - authors, - project, - version, - setAuthors, - }) => authorEmail => () => { + removeAuthor: ({ authors, setFormAuthors }) => authorEmail => () => { const newAuthors = authors.filter(a => a.email !== authorEmail) - setAuthors(newAuthors, version.id) + setFormAuthors(newAuthors) }, - setAsCorresponding: ({ - authors, - setAuthors, - version, - project, - }) => authorEmail => () => { + setAsCorresponding: ({ authors, setFormAuthors }) => authorEmail => () => { const newAuthors = authors.map(a => ({ ...a, isCorresponding: a.isSubmitting || a.email === authorEmail, })) - setAuthors(newAuthors, version.id) + setFormAuthors(newAuthors) }, }), )(Authors) diff --git a/packages/components-faraday/src/components/AuthorList/StaticList.js b/packages/components-faraday/src/components/AuthorList/StaticList.js index 5cb6432fb475ead78532bd019ae4f80a8b09b8fd..7fe49d1efc1aaf9be939954d8a86ea62b1027430 100644 --- a/packages/components-faraday/src/components/AuthorList/StaticList.js +++ b/packages/components-faraday/src/components/AuthorList/StaticList.js @@ -5,6 +5,7 @@ import Author from './Author' export default ({ authors, editIndex, + setFormAuthors, removeAuthor, countryParser, editComponent, @@ -14,22 +15,24 @@ export default ({ }) => ( <div> {authors.map( - (a, index) => + (author, index) => index === editIndex ? ( React.createElement(editComponent, { key: 'author-editor', + authors, index, initialValues: { - edit: a, + edit: author, }, + setAuthors: setFormAuthors, setAuthorEdit, countryParser, parseAuthorType, }) ) : ( <Author - key={a.firstName} - {...a} + key={author.firstName} + {...author} countryParser={countryParser} index={index} parseAuthorType={parseAuthorType} diff --git a/packages/component-wizard/src/components/FileDropzone.js b/packages/components-faraday/src/components/Files/FileDropzone.js similarity index 100% rename from packages/component-wizard/src/components/FileDropzone.js rename to packages/components-faraday/src/components/Files/FileDropzone.js diff --git a/packages/component-wizard/src/components/FileDropzone.local.scss b/packages/components-faraday/src/components/Files/FileDropzone.local.scss similarity index 100% rename from packages/component-wizard/src/components/FileDropzone.local.scss rename to packages/components-faraday/src/components/Files/FileDropzone.local.scss diff --git a/packages/component-wizard/src/components/FileItem.js b/packages/components-faraday/src/components/Files/FileItem.js similarity index 100% rename from packages/component-wizard/src/components/FileItem.js rename to packages/components-faraday/src/components/Files/FileItem.js diff --git a/packages/component-wizard/src/components/FileItem.local.scss b/packages/components-faraday/src/components/Files/FileItem.local.scss similarity index 100% rename from packages/component-wizard/src/components/FileItem.local.scss rename to packages/components-faraday/src/components/Files/FileItem.local.scss diff --git a/packages/component-wizard/src/components/FilePicker.js b/packages/components-faraday/src/components/Files/FilePicker.js similarity index 100% rename from packages/component-wizard/src/components/FilePicker.js rename to packages/components-faraday/src/components/Files/FilePicker.js diff --git a/packages/component-wizard/src/components/FileSection.js b/packages/components-faraday/src/components/Files/FileSection.js similarity index 95% rename from packages/component-wizard/src/components/FileSection.js rename to packages/components-faraday/src/components/Files/FileSection.js index 5f3726de3d4bea1d9aba109b9feeafba4c9e4b73..f495196cc460ea3493e08b689ef16626aa30973a 100644 --- a/packages/component-wizard/src/components/FileSection.js +++ b/packages/components-faraday/src/components/Files/FileSection.js @@ -5,7 +5,10 @@ import { Icon } from '@pubsweet/ui' import { DropTarget } from 'react-dnd' import { NativeTypes } from 'react-dnd-html5-backend' import { compose, getContext, withHandlers, withState } from 'recompose' -import { SortableList } from 'pubsweet-components-faraday/src/components' +import { + SortableList, + Spinner, +} from 'pubsweet-components-faraday/src/components' import FileItem from './FileItem' import FilePicker from './FilePicker' @@ -73,9 +76,7 @@ const FileSection = ({ </div> </FilePicker> ) : ( - <div className={classnames(classes.rotate, classes.icon)}> - <Icon size={16}>loader</Icon> - </div> + <Spinner /> )} </div> <span className={classnames(classes.error)}>{error}</span> diff --git a/packages/component-wizard/src/components/FileSection.local.scss b/packages/components-faraday/src/components/Files/FileSection.local.scss similarity index 50% rename from packages/component-wizard/src/components/FileSection.local.scss rename to packages/components-faraday/src/components/Files/FileSection.local.scss index 6f1429c741c0d8b4a020a7bd0e0f48efc30594e6..709f425a064251d4bf60391f3517e96d2e4bb7a9 100644 --- a/packages/component-wizard/src/components/FileSection.local.scss +++ b/packages/components-faraday/src/components/Files/FileSection.local.scss @@ -60,48 +60,3 @@ .is-over { background-color: #ddd; } - -@keyframes rotating { - from { - -o-transform: rotate(0deg); - -ms-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - to { - -o-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -@-webkit-keyframes rotating { - from { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.rotate { - -webkit-animation: rotating 1.5s linear infinite; - -moz-animation: rotating 1.5s linear infinite; - -ms-animation: rotating 1.5s linear infinite; - -o-animation: rotating 1.5s linear infinite; - animation: rotating 1.5s linear infinite; -} - -.icon { - align-items: center; - display: flex; - justify-content: center; - margin: 0 5px 0 0; -} diff --git a/packages/component-wizard/src/components/Files.js b/packages/components-faraday/src/components/Files/Files.js similarity index 99% rename from packages/component-wizard/src/components/Files.js rename to packages/components-faraday/src/components/Files/Files.js index b4e3683ae3fda8054b55f618f65b7903831bae1d..f61eaca9b74c0e6dc61161f5300acf1a80d817d8 100644 --- a/packages/component-wizard/src/components/Files.js +++ b/packages/components-faraday/src/components/Files/Files.js @@ -21,7 +21,7 @@ import { deleteFile, getRequestStatus, getSignedUrl, -} from '../redux/files' +} from '../../redux/files' const Files = ({ files, diff --git a/packages/component-wizard/src/components/Files.local.scss b/packages/components-faraday/src/components/Files/Files.local.scss similarity index 100% rename from packages/component-wizard/src/components/Files.local.scss rename to packages/components-faraday/src/components/Files/Files.local.scss diff --git a/packages/components-faraday/src/components/Files/index.js b/packages/components-faraday/src/components/Files/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6df727395c45e4df9e5db1d031a4d43146ad8440 --- /dev/null +++ b/packages/components-faraday/src/components/Files/index.js @@ -0,0 +1 @@ +export { default as Files } from './Files' diff --git a/packages/components-faraday/src/components/SortableList/SortableList.js b/packages/components-faraday/src/components/SortableList/SortableList.js index b19cc9d884818c75682cf6637b6c499a4f2fc9f4..ccbe292df8aa82e92a813650483ba8fb5b90c1d2 100644 --- a/packages/components-faraday/src/components/SortableList/SortableList.js +++ b/packages/components-faraday/src/components/SortableList/SortableList.js @@ -93,6 +93,7 @@ const DecoratedItem = compose( const SortableList = ({ items, + itemKey = 'id', moveItem, listItem, dragHandle, @@ -104,7 +105,7 @@ const SortableList = ({ <DecoratedItem dragHandle={dragHandle} index={i} - key={item.name || Math.random()} + key={item[itemKey]} listItem={listItem} moveItem={moveItem} {...item} diff --git a/packages/components-faraday/src/components/SortableList/SortableList.md b/packages/components-faraday/src/components/SortableList/SortableList.md index e6da217d33f181dc1fca556935a0f214ff3aeb2d..3eb03059c3b668e86c210ea3c31221abb05349ec 100644 --- a/packages/components-faraday/src/components/SortableList/SortableList.md +++ b/packages/components-faraday/src/components/SortableList/SortableList.md @@ -2,13 +2,15 @@ A sortable list implemented with `react-dnd`. ## Props -| Prop | Description | Required | Default | -| :--------: | :------------------------------------------------------------------------------------------------------------------------------------------: | :------: | :-------------------: | -| items | The items of the sortable list. | true | [] | -| listItem | A React component that will be rendered for each item of the list. Receives `isDragging`, `isOver` and all other props from the items array. | true | none | -| moveItem | Function to be called when moving an item through the list. SortableList will provide the dragIndex of hoverIndex of the items. | true | SortableList.moveItem | -| dragHandle | A React component for the drag handle. If not present, the whole item can be dragged. | false | none | -| dropItem | Function to be called when dropping an item. The index of the dragged item is passed. | false | none | +| Prop | Description | Required | Default | Type | +| :--------: | :------------------------------------------------------------------------------------------------------------------------------------------: | :------: | :-------------------: | :---: | +| items | The items of the sortable list. | true | [] | Array | +| itemKey | Value used for key when mapping over items. | true | 'id' | string | +| listItem | A React component that will be rendered for each item of the list. Receives `isDragging`, `isOver` and all other props from the items array. | true | none | React component | +| moveItem | Function to be called when moving an item through the list. SortableList will provide the dragIndex of hoverIndex of the items. | true | none | function | +| dragHandle | A React component for the drag handle. If not present, the whole item can be dragged. | false | none | React component | +| dropItem | Function to be called when dropping an item. The index of the dragged item is passed. | false | none | function | +| beginDragProps | Array of keys to pick from the dragged object when beginning drag. | false | [] | Array(string) | ## Usage diff --git a/packages/components-faraday/src/components/UIComponents/Spinner.js b/packages/components-faraday/src/components/UIComponents/Spinner.js new file mode 100644 index 0000000000000000000000000000000000000000..42c828c4bd85664bc3885daf6c0b0b70eec33a0e --- /dev/null +++ b/packages/components-faraday/src/components/UIComponents/Spinner.js @@ -0,0 +1,13 @@ +import React from 'react' +import classnames from 'classnames' +import { Icon } from '@pubsweet/ui' + +import classes from './Spinner.local.scss' + +const Spinner = () => ( + <div className={classnames(classes.rotate, classes.icon)}> + <Icon size={16}>loader</Icon> + </div> +) + +export default Spinner diff --git a/packages/components-faraday/src/components/UIComponents/Spinner.local.scss b/packages/components-faraday/src/components/UIComponents/Spinner.local.scss new file mode 100644 index 0000000000000000000000000000000000000000..b82f01d0e4f0d7113d1e63f67845f40120027477 --- /dev/null +++ b/packages/components-faraday/src/components/UIComponents/Spinner.local.scss @@ -0,0 +1,44 @@ +@keyframes rotating { + from { + -o-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + to { + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@-webkit-keyframes rotating { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.rotate { + -webkit-animation: rotating 1.8s linear infinite; + -moz-animation: rotating 1.8s linear infinite; + -ms-animation: rotating 1.8s linear infinite; + -o-animation: rotating 1.8s linear infinite; + animation: rotating 1.8s linear infinite; +} + +.icon { + align-items: center; + display: flex; + justify-content: center; + margin: 0 5px 0 0; +} diff --git a/packages/components-faraday/src/components/UIComponents/index.js b/packages/components-faraday/src/components/UIComponents/index.js index 8c2c826a28aa0777ba243088dec43647bbc965e4..0f79f62b6f773549c5da460c041232e47820dfb3 100644 --- a/packages/components-faraday/src/components/UIComponents/index.js +++ b/packages/components-faraday/src/components/UIComponents/index.js @@ -1,2 +1,3 @@ export { default as Logo } from './Logo' +export { default as Spinner } from './Spinner' export { default as Dropdown } from './Dropdown' diff --git a/packages/components-faraday/src/components/index.js b/packages/components-faraday/src/components/index.js index da51b2a179ed3cefad04ed44d0c2dc808d45a476..210934a75eb51d1f7c9f9b38e344ab3af2852003 100644 --- a/packages/components-faraday/src/components/index.js +++ b/packages/components-faraday/src/components/index.js @@ -1,5 +1,6 @@ -export { default as SortableList } from './SortableList/SortableList' +export { default as Files } from './Files/Files' export { default as AuthorList } from './AuthorList/AuthorList' +export { default as SortableList } from './SortableList/SortableList' -export { Dropdown, Logo } from './UIComponents' export { DragHandle } from './AuthorList/FormItems' +export { Dropdown, Logo, Spinner } from './UIComponents' diff --git a/packages/components-faraday/src/index.js b/packages/components-faraday/src/index.js index 70c96833395b805503aba42b54417baee276e117..3c0ed73cabca75cdc957af41d97563815a4e1498 100644 --- a/packages/components-faraday/src/index.js +++ b/packages/components-faraday/src/index.js @@ -3,6 +3,7 @@ module.exports = { components: [() => require('./components')], reducers: { authors: () => require('./redux/authors').default, + files: () => require('./redux/files').default, filters: () => require('./components/Dashboard/redux/filters').default, }, }, diff --git a/packages/components-faraday/src/redux/authors.js b/packages/components-faraday/src/redux/authors.js index dc4d4ad5255f7e31cbc1a3aef6833670d4c6b76c..8a9e3a9c7f480b1badf25146c66e9de7f88d84ce 100644 --- a/packages/components-faraday/src/redux/authors.js +++ b/packages/components-faraday/src/redux/authors.js @@ -1,49 +1,64 @@ import { get } from 'lodash' -import { actions } from 'pubsweet-client' import * as api from 'pubsweet-client/src/helpers/api' -import { change } from 'redux-form' // constants -export const SET_AUTHORS = 'authors/SET_AUTHORS' +const REQUEST = 'authors/REQUEST' +const FAILURE = 'authors/FAILURE' +const SUCCESS = 'authors/SUCCESS' -const _setAuthors = (authors, fragmentId) => ({ - type: SET_AUTHORS, - authors, - fragmentId, +// actions +export const authorRequest = () => ({ + type: REQUEST, }) -// actions -export const setAuthors = (authors, fragmentId) => dispatch => { - dispatch(change('wizard', 'authors', authors)) - dispatch(_setAuthors(authors, fragmentId)) -} +export const authorFaiure = error => ({ + type: FAILURE, + error, +}) -export const moveAuthors = (authors, fragmentId) => dispatch => { - dispatch(_setAuthors(authors, fragmentId)) -} +export const authorSuccess = () => ({ + type: SUCCESS, +}) -export const addAuthor = (author, collectionId, fragmentId) => dispatch => - api +export const addAuthor = (author, collectionId, fragmentId) => dispatch => { + dispatch(authorRequest()) + return api .create( `/collections/${collectionId}/fragments/${fragmentId}/authors`, author, ) - .then(() => - dispatch(actions.getFragment({ id: collectionId }, { id: fragmentId })), - ) - .then(({ fragment: { authors, id } }) => dispatch(setAuthors(authors, id))) + .then(author => { + dispatch(authorSuccess()) + return author + }) + .catch(err => dispatch(authorFaiure(err))) +} // selectors export const getFragmentAuthors = (state, fragmentId) => get(state, `authors.${fragmentId}`) || [] -export default (state = {}, action) => { +export const getAuthorFetching = state => state.authors.isFetching +export const getAuthorError = state => state.authors.error + +const initialState = { isFetching: false, error: null } + +export default (state = initialState, action) => { switch (action.type) { - case SET_AUTHORS: + case 'UPDATE_FRAGMENT_REQUEST': + case REQUEST: + return { + ...initialState, + isFetching: true, + } + case FAILURE: return { - ...state, - [action.fragmentId]: action.authors, + ...initialState, + error: action.error, } + case 'UPDATE_FRAGMENT_SUCCESS': + case SUCCESS: + return initialState default: return state } diff --git a/packages/component-wizard/src/redux/files.js b/packages/components-faraday/src/redux/files.js similarity index 100% rename from packages/component-wizard/src/redux/files.js rename to packages/components-faraday/src/redux/files.js diff --git a/packages/xpub-aws/config/upload-validations-test.js b/packages/xpub-aws/config/upload-validations-test.js new file mode 100644 index 0000000000000000000000000000000000000000..23fa37d7d5a7b0403731429eb910f5f402799000 --- /dev/null +++ b/packages/xpub-aws/config/upload-validations-test.js @@ -0,0 +1,19 @@ +const Joi = require('joi') + +module.exports = { + manuscripts: Joi.any() + .valid([ + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/pdf', + 'application/msword', + ]) + .error(new Error('Only Word documents and PDFs are allowed')), + supplementary: Joi.any(), + coverLetter: Joi.any() + .valid([ + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/pdf', + 'application/msword', + ]) + .error(new Error('Only Word documents and PDFs are allowed')), +} diff --git a/packages/xpub-aws/src/middeware/upload.js b/packages/xpub-aws/src/middeware/upload.js index e15c07ff36589eecaf6ded895da7e2a417fe2fa2..73d9a14367f4708d162d916f0fcfeba84fa14fda 100644 --- a/packages/xpub-aws/src/middeware/upload.js +++ b/packages/xpub-aws/src/middeware/upload.js @@ -1,6 +1,12 @@ const multer = require('multer') const multerS3 = require('multer-s3') const uuid = require('uuid') +const Joi = require('joi') +const _ = require('lodash') +const config = require('config') + +const s3Config = _.get(config, 'pubsweet-component-aws-s3') +const uploadValidations = require(s3Config.validations) const setupMulter = s3 => { const upload = multer({ @@ -22,21 +28,16 @@ const setupMulter = s3 => { } const validateFile = (req, file, cb) => { - if ( - req.body.fileType === 'manuscripts' || - req.body.fileType === 'coverLetter' - ) { - if ( - file.mimetype === 'application/pdf' || - file.mimetype === 'application/msword' || - file.mimetype === - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - ) { - return cb(null, true) - } - req.fileValidationError = 'Only Word documents and PDFs are allowed' + const fileType = req.body.fileType + const mimetype = file.mimetype + + const valid = Joi.validate({ [fileType]: mimetype }, uploadValidations) + + if (valid.error) { + req.fileValidationError = valid.error.message return cb(null, false) } + return cb(null, true) } diff --git a/packages/xpub-faraday/app/config/journal/submit-wizard.js b/packages/xpub-faraday/app/config/journal/submit-wizard.js index 261390aa2c296cd39170e2d5dadb2171331dc079..3a63214bbc415175b315d8bcefd0a597ce6d0717 100644 --- a/packages/xpub-faraday/app/config/journal/submit-wizard.js +++ b/packages/xpub-faraday/app/config/journal/submit-wizard.js @@ -3,8 +3,7 @@ import { AbstractEditor, TitleEditor } from 'xpub-edit' import { Menu, CheckboxGroup, YesOrNo, TextField } from '@pubsweet/ui' import uploadFileFn from 'xpub-upload' import { required, minChars, minSize } from 'xpub-validators' -import { Files } from 'pubsweet-component-wizard/src/components/' -import { AuthorList } from 'pubsweet-components-faraday/src/components' +import { AuthorList, Files } from 'pubsweet-components-faraday/src/components' import { declarations } from './' import issueTypes from './issues-types' diff --git a/packages/xpub-faraday/app/config/journal/wizard-validators.js b/packages/xpub-faraday/app/config/journal/wizard-validators.js index 7816aee812bfed54a505ccb95dfb240c3a7a0c14..8504e61f02765ae8e83a9f2f43063801258e2d10 100644 --- a/packages/xpub-faraday/app/config/journal/wizard-validators.js +++ b/packages/xpub-faraday/app/config/journal/wizard-validators.js @@ -31,7 +31,8 @@ export const editModeEnabled = value => { } export const requiredFiles = (valus, formValues) => { - if (get(formValues, 'files.manuscripts').length === 0) { + const manuscripts = get(formValues, 'files.manuscripts') + if (manuscripts && manuscripts.length === 0) { return 'At least one main manuscript file is needed.' } return undefined diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index ac142cf190c5a2949aeb6142ab589fbb04fa27e3..66110156bd835611193475733fbc26f0c23635d1 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -46,5 +46,17 @@ module.exports = { 'editoria-typescript': '2', }, }, - publicKeys: ['pubsweet-client', 'authsome', 'validations'], + 'pubsweet-component-aws-s3': { + secretAccessKey: process.env.AWS_SECRET_KEY, + accessKeyId: process.env.AWS_ACCESS_KEY, + region: process.env.AWS_REGION, + sender: process.env.PUBSWEET_EMAIL_SENDER || 'dev@mailinator.com', + validations: path.resolve(__dirname, 'upload-validations.js'), + }, + publicKeys: [ + 'pubsweet-client', + 'authsome', + 'validations', + 'pubsweet-component-aws-s3', + ], } diff --git a/packages/xpub-faraday/config/upload-validations.js b/packages/xpub-faraday/config/upload-validations.js new file mode 100644 index 0000000000000000000000000000000000000000..23fa37d7d5a7b0403731429eb910f5f402799000 --- /dev/null +++ b/packages/xpub-faraday/config/upload-validations.js @@ -0,0 +1,19 @@ +const Joi = require('joi') + +module.exports = { + manuscripts: Joi.any() + .valid([ + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/pdf', + 'application/msword', + ]) + .error(new Error('Only Word documents and PDFs are allowed')), + supplementary: Joi.any(), + coverLetter: Joi.any() + .valid([ + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/pdf', + 'application/msword', + ]) + .error(new Error('Only Word documents and PDFs are allowed')), +}