Skip to content
Snippets Groups Projects
Commit 5f9c165c authored by Alexandru Munteanu's avatar Alexandru Munteanu
Browse files

Add drag handle to sortable list

parent df98ebbc
No related branches found
No related tags found
No related merge requests found
Showing
with 308 additions and 12 deletions
import React from 'react'
import { get } from 'lodash'
import classnames from 'classnames'
import { TextField, Menu } from '@pubsweet/ui'
import { compose, withState, withHandlers } from 'recompose'
import classes from './AuthorList.local.scss'
import SortableList from './SortableList'
const countries = [
{ label: 'Romania', value: 'ro' },
{ label: 'United Kingdom', value: 'uk' },
{ label: 'Germany', value: 'de' },
{ label: 'France', value: 'fr' },
]
const AuthorAdder = ({
author: { firstName, middleName, lastName, email, affiliation, country },
editAuthor,
addAuthor,
}) => (
<div className={classnames(classes.adder)}>
<button onClick={addAuthor}>Add author</button>
<span className={classnames(classes.title)}>Author</span>
<div className={classnames(classes.row)}>
<TextField
label="First name"
onChange={editAuthor('firstName')}
value={firstName}
/>
<TextField
label="Midle name"
onChange={editAuthor('middleName')}
value={middleName}
/>
<TextField
label="Last name"
onChange={editAuthor('lastName')}
value={lastName}
/>
</div>
<div className={classnames(classes.row)}>
<TextField
label="Email"
onChange={editAuthor('email')}
type="email"
value={email}
/>
<TextField
label="Affiliation"
onChange={editAuthor('affiliation')}
value={affiliation}
/>
<Menu
onChange={editAuthor('country')}
options={countries}
value={country}
/>
</div>
</div>
)
const Label = ({ label, value }) => (
<div className={classnames(classes['label-container'])}>
<span className={classnames(classes.label)}>{label}</span>
<span className={classnames(classes.value)}>{value}</span>
</div>
)
const Author = ({
firstName,
middleName,
lastName,
email,
affiliation,
isDragging,
children,
}) => (
<div className={classnames(classes.author)}>
<span className={classnames(classes.title)}>Author</span>
{!isDragging && (
<div className={classnames(classes.row)}>
<Label label="First name" value={firstName} />
<Label label="Middle name" value={middleName} />
<Label label="Last name" value={lastName} />
</div>
)}
{!isDragging && (
<div className={classnames(classes.row)}>
<Label label="Email" value={email} />
<Label label="Affiliation" value={affiliation} />
<Label label="Affiliation" value={affiliation} />
</div>
)}
</div>
)
const Authors = ({ author, authors, moveAuthor, addAuthor, editAuthor }) => (
<div>
<AuthorAdder
addAuthor={addAuthor}
author={author}
editAuthor={editAuthor}
/>
<SortableList items={authors} listItem={Author} moveItem={moveAuthor} />
</div>
)
export default compose(
withState('author', 'changeAuthor', {
firstName: '',
middleName: '',
lastName: '',
email: '',
affiliation: '',
country: 'ro',
}),
withState('authors', 'changeAuthors', [
{
firstName: 'Razvan',
middleName: 'Petru',
lastName: 'Tudosa',
email: 'rzv@gmail.com',
affiliation: 'rock',
},
{
firstName: 'Alexandru',
middleName: 'Ioan',
lastName: 'Munteanu',
email: 'alexmntn@gmail.com',
affiliation: 'rap',
},
{
firstName: 'Bogdan',
middleName: 'Alexandru',
lastName: 'Cochior',
email: 'bog1@gmail.com',
affiliation: 'metal',
},
]),
withHandlers({
addAuthor: ({ author, changeAuthors, changeAuthor }) => e => {
e.preventDefault()
changeAuthors(prevAuthors => [author, ...prevAuthors])
changeAuthor(prev => ({
firstName: '',
middleName: '',
lastName: '',
email: '',
affiliation: '',
country: 'ro',
}))
},
moveAuthor: ({ changeAuthors }) => (dragIndex, hoverIndex) => {
changeAuthors(prev => SortableList.moveItem(prev, dragIndex, hoverIndex))
},
editAuthor: ({ changeAuthor }) => field => e => {
const v = get(e, 'target.value') || e
changeAuthor(prev => ({
...prev,
[field]: v,
}))
},
}),
)(Authors)
.row {
display: flex;
flex-direction: row;
}
.author {
border: 1px solid #444;
.title {
font-size: 16px;
font-weight: 600;
margin: 5px;
}
.label-container {
display: flex;
flex: 1;
flex-direction: column;
margin: 5px;
.label {
font-size: 14px;
font-weight: 300;
text-transform: uppercase;
}
.value {
font-size: 16px;
font-weight: 600;
}
}
}
.adder {
background-color: aquamarine;
display: flex;
flex-direction: column;
margin: 10px 0;
padding: 5px;
.title {
font-size: 18px;
font-weight: 500;
}
}
...@@ -2,6 +2,9 @@ import React from 'react' ...@@ -2,6 +2,9 @@ import React from 'react'
import { compose } from 'recompose' import { compose } from 'recompose'
import { findDOMNode } from 'react-dom' import { findDOMNode } from 'react-dom'
import { DragSource, DropTarget } from 'react-dnd' import { DragSource, DropTarget } from 'react-dnd'
import classnames from 'classnames'
import { Icon } from '@pubsweet/ui'
import classes from './SortableList.local.scss'
const itemSource = { const itemSource = {
beginDrag(props) { beginDrag(props) {
...@@ -31,14 +34,39 @@ const itemTarget = { ...@@ -31,14 +34,39 @@ const itemTarget = {
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return return
} }
moveItem(dragIndex, hoverIndex) if (typeof moveItem === 'function') {
moveItem(dragIndex, hoverIndex)
}
monitor.getItem().index = hoverIndex monitor.getItem().index = hoverIndex
}, },
} }
const Item = ({ connectDragSource, connectDropTarget, listItem, ...rest }) => const DragHandle = () => (
connectDragSource( <div className={classnames(classes['drag-handle'])}>
connectDropTarget(<div>{React.createElement(listItem, rest)}</div>), <Icon>chevron_up</Icon>
<Icon>chevron_down</Icon>
</div>
)
const Item = ({
connectDragPreview,
connectDragSource,
connectDropTarget,
listItem,
...rest
}) =>
connectDragPreview(
<div style={{ display: 'flex' }}>
{connectDragSource(
<div className={classnames(classes['drag-handle'])}>
<Icon>chevron_up</Icon>
<Icon>chevron_down</Icon>
</div>,
)}
{connectDropTarget(
<div style={{ flex: 1 }}>{React.createElement(listItem, rest)}</div>,
)}
</div>,
) )
const DecoratedItem = compose( const DecoratedItem = compose(
...@@ -48,6 +76,7 @@ const DecoratedItem = compose( ...@@ -48,6 +76,7 @@ const DecoratedItem = compose(
})), })),
DragSource('item', itemSource, (connect, monitor) => ({ DragSource('item', itemSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(), connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(), isDragging: monitor.isDragging(),
})), })),
)(Item) )(Item)
...@@ -57,7 +86,7 @@ const SortableList = ({ items, moveItem, listItem }) => ( ...@@ -57,7 +86,7 @@ const SortableList = ({ items, moveItem, listItem }) => (
{items.map((item, i) => ( {items.map((item, i) => (
<DecoratedItem <DecoratedItem
index={i} index={i}
key={item.name} key={item.name || Math.random()}
listItem={listItem} listItem={listItem}
moveItem={moveItem} moveItem={moveItem}
{...item} {...item}
......
.drag-handle {
display: flex;
flex-direction: column;
justify-content: center;
}
...@@ -10,10 +10,9 @@ const { Step } = Progress ...@@ -10,10 +10,9 @@ const { Step } = Progress
export default ({ export default ({
journal: { wizard: { showProgress, steps } }, journal: { wizard: { showProgress, steps } },
getSteps, getSteps,
step,
nextStep, nextStep,
prevStep, prevStep,
...rest step,
}) => ( }) => (
<div className={classnames(classes.container)}> <div className={classnames(classes.container)}>
{showProgress && ( {showProgress && (
......
...@@ -5,6 +5,8 @@ import { ValidatedField, Button } from '@pubsweet/ui' ...@@ -5,6 +5,8 @@ import { ValidatedField, Button } from '@pubsweet/ui'
import classes from './WizardStep.local.scss' import classes from './WizardStep.local.scss'
import AuthorList from './AuthorList'
export default ({ export default ({
children: stepChildren, children: stepChildren,
title, title,
...@@ -45,6 +47,7 @@ export default ({ ...@@ -45,6 +47,7 @@ export default ({
) )
}, },
)} )}
<AuthorList />
<div className={classnames(classes.buttons)}> <div className={classnames(classes.buttons)}>
<Button onClick={isFirst ? goBack : prevStep}> <Button onClick={isFirst ? goBack : prevStep}>
{isFirst ? 'Cancel' : 'Back'} {isFirst ? 'Cancel' : 'Back'}
......
...@@ -5,3 +5,5 @@ export { default as WizardPage } from './WizardPage' ...@@ -5,3 +5,5 @@ export { default as WizardPage } from './WizardPage'
export { default as WizardStep } from './WizardStep' export { default as WizardStep } from './WizardStep'
export { default as SortableList } from './SortableList' export { default as SortableList } from './SortableList'
export { default as WizardFormStep } from './WizardFormStep' export { default as WizardFormStep } from './WizardFormStep'
export { default as AuthorList } from './AuthorList'
...@@ -11,7 +11,7 @@ import * as journal from './config/journal' ...@@ -11,7 +11,7 @@ import * as journal from './config/journal'
import Routes from './routes' import Routes from './routes'
const history = createHistory() const history = createHistory()
const store = configureStore(history, {}) export const store = configureStore(history, {})
const theme = {} const theme = {}
const render = () => { const render = () => {
......
export default [ export default [
{ label: 'Research', value: 'research', author: true, peerReview: true }, {
{ label: 'Review', value: 'review', author: true, peerReview: true }, label: 'Research',
value: 'research',
author: true,
peerReview: true,
abstractRequired: true,
},
{
label: 'Review',
value: 'review',
author: true,
peerReview: true,
abstractRequired: true,
},
{ {
label: 'Clinical study', label: 'Clinical study',
value: 'clinical-study', value: 'clinical-study',
author: true, author: true,
peerReview: true, peerReview: true,
abstractRequired: true,
}, },
{ {
label: 'Case report', label: 'Case report',
value: 'case-report', value: 'case-report',
author: true, author: true,
peerReview: true, peerReview: true,
abstractRequired: true,
}, },
{ {
label: 'Letter to the editor', label: 'Letter to the editor',
value: 'letter-to-editor', value: 'letter-to-editor',
author: true, author: true,
peerReview: false, peerReview: false,
abstractRequired: false,
},
{
label: 'Editorial',
value: 'editorial',
author: false,
peerReview: false,
abstractRequired: false,
}, },
{ label: 'Editorial', value: 'editorial', author: false, peerReview: false },
{ {
label: 'Corrigendum', label: 'Corrigendum',
value: 'corrigendum', value: 'corrigendum',
author: false, author: false,
peerReview: false, peerReview: false,
abstractRequired: false,
},
{
label: 'Erratum',
value: 'erratum',
author: false,
peerReview: false,
abstractRequired: false,
}, },
{ label: 'Erratum', value: 'erratum', author: false, peerReview: false },
{ {
label: 'Expression of concern', label: 'Expression of concern',
value: 'expression-of-concern', value: 'expression-of-concern',
author: false, author: false,
peerReview: false, peerReview: false,
abstractRequired: true,
}, },
{ {
label: 'Retraction', label: 'Retraction',
value: 'retraction', value: 'retraction',
author: false, author: false,
peerReview: false, peerReview: false,
abstractRequired: false,
}, },
] ]
...@@ -14,6 +14,8 @@ import { declarations } from './' ...@@ -14,6 +14,8 @@ import { declarations } from './'
import issueTypes from './issues-types' import issueTypes from './issues-types'
import manuscriptTypes from './manuscript-types' import manuscriptTypes from './manuscript-types'
import { requiredBasedOnType } from './wizard-validators'
const min3Chars = minChars(3) const min3Chars = minChars(3)
const declarationsMinSize = minSize(declarations.options.length) const declarationsMinSize = minSize(declarations.options.length)
...@@ -94,6 +96,7 @@ export default { ...@@ -94,6 +96,7 @@ export default {
renderComponent: AbstractEditor, renderComponent: AbstractEditor,
title: 'Abstract', title: 'Abstract',
placeholder: 'Write an abstract', placeholder: 'Write an abstract',
validate: [requiredBasedOnType],
}, },
{ {
fieldId: 'conflicts.hasConflicts', fieldId: 'conflicts.hasConflicts',
...@@ -110,6 +113,7 @@ export default { ...@@ -110,6 +113,7 @@ export default {
label: 'Conflict of interest details', label: 'Conflict of interest details',
validate: [required, min3Chars], validate: [required, min3Chars],
}, },
{},
], ],
}, },
{ {
......
import { get } from 'lodash'
import manuscriptTypes from './manuscript-types'
const requiredTypes = manuscriptTypes
.filter(t => t.abstractRequired)
.map(t => t.value)
export const requiredBasedOnType = (value, formValues) => {
if (requiredTypes.includes(get(formValues, 'metadata.type'))) {
return 'Required'
}
return undefined
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment