From 5f9c165c67aeee9a9f45253e33a47de0885a27f1 Mon Sep 17 00:00:00 2001
From: Alexandru Munteanu <alexandru.munteanu@thinslices.com>
Date: Wed, 17 Jan 2018 13:49:35 +0200
Subject: [PATCH] Add drag handle to sortable list

---
 .../src/components/AuthorList.js              | 165 ++++++++++++++++++
 .../src/components/AuthorList.local.scss      |  45 +++++
 .../src/components/SortableList.js            |  39 ++++-
 .../src/components/SortableList.local.scss    |   5 +
 .../component-wizard/src/components/Wizard.js |   3 +-
 .../src/components/WizardStep.js              |   3 +
 .../component-wizard/src/components/index.js  |   2 +
 packages/xpub-faraday/app/app.js              |   2 +-
 .../app/config/journal/manuscript-types.js    |  38 +++-
 .../app/config/journal/submit-wizard.js       |   4 +
 .../app/config/journal/wizard-validators.js   |  14 ++
 11 files changed, 308 insertions(+), 12 deletions(-)
 create mode 100644 packages/component-wizard/src/components/AuthorList.js
 create mode 100644 packages/component-wizard/src/components/AuthorList.local.scss
 create mode 100644 packages/component-wizard/src/components/SortableList.local.scss
 create mode 100644 packages/xpub-faraday/app/config/journal/wizard-validators.js

diff --git a/packages/component-wizard/src/components/AuthorList.js b/packages/component-wizard/src/components/AuthorList.js
new file mode 100644
index 000000000..264be4159
--- /dev/null
+++ b/packages/component-wizard/src/components/AuthorList.js
@@ -0,0 +1,165 @@
+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)
diff --git a/packages/component-wizard/src/components/AuthorList.local.scss b/packages/component-wizard/src/components/AuthorList.local.scss
new file mode 100644
index 000000000..56e988847
--- /dev/null
+++ b/packages/component-wizard/src/components/AuthorList.local.scss
@@ -0,0 +1,45 @@
+.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;
+  }
+}
diff --git a/packages/component-wizard/src/components/SortableList.js b/packages/component-wizard/src/components/SortableList.js
index 1273b9e12..95453bec0 100644
--- a/packages/component-wizard/src/components/SortableList.js
+++ b/packages/component-wizard/src/components/SortableList.js
@@ -2,6 +2,9 @@ import React from 'react'
 import { compose } from 'recompose'
 import { findDOMNode } from 'react-dom'
 import { DragSource, DropTarget } from 'react-dnd'
+import classnames from 'classnames'
+import { Icon } from '@pubsweet/ui'
+import classes from './SortableList.local.scss'
 
 const itemSource = {
   beginDrag(props) {
@@ -31,14 +34,39 @@ const itemTarget = {
     if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
       return
     }
-    moveItem(dragIndex, hoverIndex)
+    if (typeof moveItem === 'function') {
+      moveItem(dragIndex, hoverIndex)
+    }
     monitor.getItem().index = hoverIndex
   },
 }
 
-const Item = ({ connectDragSource, connectDropTarget, listItem, ...rest }) =>
-  connectDragSource(
-    connectDropTarget(<div>{React.createElement(listItem, rest)}</div>),
+const DragHandle = () => (
+  <div className={classnames(classes['drag-handle'])}>
+    <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(
@@ -48,6 +76,7 @@ const DecoratedItem = compose(
   })),
   DragSource('item', itemSource, (connect, monitor) => ({
     connectDragSource: connect.dragSource(),
+    connectDragPreview: connect.dragPreview(),
     isDragging: monitor.isDragging(),
   })),
 )(Item)
@@ -57,7 +86,7 @@ const SortableList = ({ items, moveItem, listItem }) => (
     {items.map((item, i) => (
       <DecoratedItem
         index={i}
-        key={item.name}
+        key={item.name || Math.random()}
         listItem={listItem}
         moveItem={moveItem}
         {...item}
diff --git a/packages/component-wizard/src/components/SortableList.local.scss b/packages/component-wizard/src/components/SortableList.local.scss
new file mode 100644
index 000000000..41607ab0e
--- /dev/null
+++ b/packages/component-wizard/src/components/SortableList.local.scss
@@ -0,0 +1,5 @@
+.drag-handle {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
diff --git a/packages/component-wizard/src/components/Wizard.js b/packages/component-wizard/src/components/Wizard.js
index aaf92a3d7..e13aaa0af 100644
--- a/packages/component-wizard/src/components/Wizard.js
+++ b/packages/component-wizard/src/components/Wizard.js
@@ -10,10 +10,9 @@ const { Step } = Progress
 export default ({
   journal: { wizard: { showProgress, steps } },
   getSteps,
-  step,
   nextStep,
   prevStep,
-  ...rest
+  step,
 }) => (
   <div className={classnames(classes.container)}>
     {showProgress && (
diff --git a/packages/component-wizard/src/components/WizardStep.js b/packages/component-wizard/src/components/WizardStep.js
index 672f22d3f..15af64420 100644
--- a/packages/component-wizard/src/components/WizardStep.js
+++ b/packages/component-wizard/src/components/WizardStep.js
@@ -5,6 +5,8 @@ import { ValidatedField, Button } from '@pubsweet/ui'
 
 import classes from './WizardStep.local.scss'
 
+import AuthorList from './AuthorList'
+
 export default ({
   children: stepChildren,
   title,
@@ -45,6 +47,7 @@ export default ({
             )
           },
         )}
+      <AuthorList />
       <div className={classnames(classes.buttons)}>
         <Button onClick={isFirst ? goBack : prevStep}>
           {isFirst ? 'Cancel' : 'Back'}
diff --git a/packages/component-wizard/src/components/index.js b/packages/component-wizard/src/components/index.js
index 6fc7c641c..710ecfaa0 100644
--- a/packages/component-wizard/src/components/index.js
+++ b/packages/component-wizard/src/components/index.js
@@ -5,3 +5,5 @@ export { default as WizardPage } from './WizardPage'
 export { default as WizardStep } from './WizardStep'
 export { default as SortableList } from './SortableList'
 export { default as WizardFormStep } from './WizardFormStep'
+
+export { default as AuthorList } from './AuthorList'
diff --git a/packages/xpub-faraday/app/app.js b/packages/xpub-faraday/app/app.js
index fb7eae11e..6ca64de09 100644
--- a/packages/xpub-faraday/app/app.js
+++ b/packages/xpub-faraday/app/app.js
@@ -11,7 +11,7 @@ import * as journal from './config/journal'
 import Routes from './routes'
 
 const history = createHistory()
-const store = configureStore(history, {})
+export const store = configureStore(history, {})
 const theme = {}
 
 const render = () => {
diff --git a/packages/xpub-faraday/app/config/journal/manuscript-types.js b/packages/xpub-faraday/app/config/journal/manuscript-types.js
index 93488dc15..a4a62ffd6 100644
--- a/packages/xpub-faraday/app/config/journal/manuscript-types.js
+++ b/packages/xpub-faraday/app/config/journal/manuscript-types.js
@@ -1,42 +1,72 @@
 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',
     value: 'clinical-study',
     author: true,
     peerReview: true,
+    abstractRequired: true,
   },
   {
     label: 'Case report',
     value: 'case-report',
     author: true,
     peerReview: true,
+    abstractRequired: true,
   },
   {
     label: 'Letter to the editor',
     value: 'letter-to-editor',
     author: true,
     peerReview: false,
+    abstractRequired: false,
+  },
+  {
+    label: 'Editorial',
+    value: 'editorial',
+    author: false,
+    peerReview: false,
+    abstractRequired: false,
   },
-  { label: 'Editorial', value: 'editorial', author: false, peerReview: false },
   {
     label: 'Corrigendum',
     value: 'corrigendum',
     author: 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',
     value: 'expression-of-concern',
     author: false,
     peerReview: false,
+    abstractRequired: true,
   },
   {
     label: 'Retraction',
     value: 'retraction',
     author: false,
     peerReview: false,
+    abstractRequired: false,
   },
 ]
diff --git a/packages/xpub-faraday/app/config/journal/submit-wizard.js b/packages/xpub-faraday/app/config/journal/submit-wizard.js
index aac92887f..9b138d17e 100644
--- a/packages/xpub-faraday/app/config/journal/submit-wizard.js
+++ b/packages/xpub-faraday/app/config/journal/submit-wizard.js
@@ -14,6 +14,8 @@ import { declarations } from './'
 import issueTypes from './issues-types'
 import manuscriptTypes from './manuscript-types'
 
+import { requiredBasedOnType } from './wizard-validators'
+
 const min3Chars = minChars(3)
 const declarationsMinSize = minSize(declarations.options.length)
 
@@ -94,6 +96,7 @@ export default {
           renderComponent: AbstractEditor,
           title: 'Abstract',
           placeholder: 'Write an abstract',
+          validate: [requiredBasedOnType],
         },
         {
           fieldId: 'conflicts.hasConflicts',
@@ -110,6 +113,7 @@ export default {
           label: 'Conflict of interest details',
           validate: [required, min3Chars],
         },
+        {},
       ],
     },
     {
diff --git a/packages/xpub-faraday/app/config/journal/wizard-validators.js b/packages/xpub-faraday/app/config/journal/wizard-validators.js
new file mode 100644
index 000000000..9a9b0a9d4
--- /dev/null
+++ b/packages/xpub-faraday/app/config/journal/wizard-validators.js
@@ -0,0 +1,14 @@
+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
+}
-- 
GitLab