From c355518d36596ff13c5a2f721b6539255d9b0f65 Mon Sep 17 00:00:00 2001
From: Alexandru Munteanu <alexandru.munteanu@thinslices.com>
Date: Tue, 30 Jan 2018 11:13:33 +0200
Subject: [PATCH] Add file dragging

---
 .../src/components/FileDropzone.js            |  26 +++++
 .../src/components/FileDropzone.local.scss    |  12 +++
 .../src/components/FileItem.js                |  23 ++--
 .../src/components/FileItem.local.scss        |   3 +-
 .../src/components/FileSection.js             | 102 ++++++++++++++++++
 .../src/components/FileSection.local.scss     |  46 ++++++++
 .../component-wizard/src/components/Files.js  |  76 ++++---------
 .../src/components/Files.local.scss           |  56 ----------
 .../src/components/WizardPage.js              |   3 +
 .../src/components/AuthorList/AuthorList.js   |   1 +
 .../components/SortableList/SortableList.js   |   8 +-
 11 files changed, 232 insertions(+), 124 deletions(-)
 create mode 100644 packages/component-wizard/src/components/FileDropzone.js
 create mode 100644 packages/component-wizard/src/components/FileDropzone.local.scss
 create mode 100644 packages/component-wizard/src/components/FileSection.js
 create mode 100644 packages/component-wizard/src/components/FileSection.local.scss

diff --git a/packages/component-wizard/src/components/FileDropzone.js b/packages/component-wizard/src/components/FileDropzone.js
new file mode 100644
index 000000000..b97ee67cc
--- /dev/null
+++ b/packages/component-wizard/src/components/FileDropzone.js
@@ -0,0 +1,26 @@
+import React from 'react'
+import classnames from 'classnames'
+
+import classes from './FileDropzone.local.scss'
+
+const FileDropzone = ({ ...props }) => (
+  <div className={classnames(classes.dropzone)}>
+    <span>Drag items here or use the upload button</span>
+  </div>
+)
+
+// export default compose(
+//   DropTarget(
+//     'item',
+//     {
+//       drop(props) {
+//         console.log('s-a dat drop', props)
+//       },
+//     },
+//     (connect, monitor) => ({
+//       connectDropTarget: connect.dropTarget(),
+//     }),
+//   ),
+// )(FileDropzone)
+
+export default FileDropzone
diff --git a/packages/component-wizard/src/components/FileDropzone.local.scss b/packages/component-wizard/src/components/FileDropzone.local.scss
new file mode 100644
index 000000000..fccfe9120
--- /dev/null
+++ b/packages/component-wizard/src/components/FileDropzone.local.scss
@@ -0,0 +1,12 @@
+.dropzone {
+  align-items: center;
+  display: flex;
+  height: 60px;
+  justify-content: center;
+  margin: 10px 0;
+
+  span {
+    color: #888;
+    font-size: 14px;
+  }
+}
diff --git a/packages/component-wizard/src/components/FileItem.js b/packages/component-wizard/src/components/FileItem.js
index d630b483f..a87bb3333 100644
--- a/packages/component-wizard/src/components/FileItem.js
+++ b/packages/component-wizard/src/components/FileItem.js
@@ -4,25 +4,30 @@ import { Icon } from '@pubsweet/ui'
 
 import classes from './FileItem.local.scss'
 
+const parseFileSize = size => {
+  const kbSize = size / 1000
+  const mbSize = kbSize / 1000
+  const gbSize = mbSize / 1000
+
+  if (Math.floor(gbSize)) {
+    return `${Math.floor(gbSize)} GB`
+  } else if (Math.floor(mbSize)) {
+    return `${Math.floor(mbSize)} MB`
+  }
+  return `${Math.floor(kbSize)} kB`
+}
+
 const FileItem = ({ dragHandle, name, size, removeFile }) => (
   <div className={classnames(classes['file-item'])}>
     {dragHandle}
     <div className={classnames(classes.info)}>
       <span>{name}</span>
-      <span>{size}</span>
+      <span>{parseFileSize(size)}</span>
     </div>
     <div className={classnames(classes.buttons)}>
       <button onClick={removeFile(name)} title="Preview">
         <Icon color="#666">eye</Icon>
       </button>
-      <button
-        onClick={e => {
-          e.preventDefault()
-        }}
-        title="Download"
-      >
-        <Icon color="#666">download</Icon>
-      </button>
       <button onClick={removeFile(name)} title="Delete">
         <Icon color="#666">trash-2</Icon>
       </button>
diff --git a/packages/component-wizard/src/components/FileItem.local.scss b/packages/component-wizard/src/components/FileItem.local.scss
index 5e6d16ca9..076d3f041 100644
--- a/packages/component-wizard/src/components/FileItem.local.scss
+++ b/packages/component-wizard/src/components/FileItem.local.scss
@@ -8,7 +8,8 @@
     border-right: 1px solid black;
     display: flex;
     flex: 1;
-    padding: 2px 0;
+    justify-content: space-between;
+    padding: 2px 10px 2px 0;
   }
 
   .buttons {
diff --git a/packages/component-wizard/src/components/FileSection.js b/packages/component-wizard/src/components/FileSection.js
new file mode 100644
index 000000000..6e5c6f58a
--- /dev/null
+++ b/packages/component-wizard/src/components/FileSection.js
@@ -0,0 +1,102 @@
+import React from 'react'
+import { compose } from 'recompose'
+import classnames from 'classnames'
+import { Icon } from '@pubsweet/ui'
+import { DropTarget } from 'react-dnd'
+import { NativeTypes } from 'react-dnd-html5-backend'
+import { SortableList } from 'pubsweet-components-faraday/src/components'
+
+import FileItem from './FileItem'
+import FilePicker from './FilePicker'
+import FileDropzone from './FileDropzone'
+import classes from './FileSection.local.scss'
+
+const DragHandle = () => (
+  <div className={classnames(classes['drag-handle'])}>
+    <Icon size={14}>chevron_up</Icon>
+    <Icon size={10}>menu</Icon>
+    <Icon size={14}>chevron_down</Icon>
+  </div>
+)
+
+const FileSection = ({
+  title,
+  files,
+  listId,
+  isLast,
+  isFirst,
+  addFile,
+  moveItem,
+  removeFile,
+  connectDropTarget,
+  isOver,
+  canDrop,
+  connectFileDrop,
+  isFileOver,
+}) =>
+  connectFileDrop(
+    connectDropTarget(
+      <div
+        className={classnames({
+          [classes['drop-section']]: true,
+          [classes['no-border-top']]: !isFirst,
+          [classes['dashed-border']]: !isLast,
+          [classes['is-over']]: isFileOver || (isOver && canDrop),
+        })}
+      >
+        <div className={classnames(classes.header)}>
+          <span className={classnames(classes.title)}>{title}</span>
+          <FilePicker onUpload={addFile}>
+            <div className={classnames(classes['upload-button'])}>
+              <Icon>file-plus</Icon>
+            </div>
+          </FilePicker>
+        </div>
+        <SortableList
+          beginDragProps={['index', 'name', 'listId']}
+          dragHandle={DragHandle}
+          items={files}
+          listId={listId}
+          listItem={FileItem}
+          moveItem={moveItem}
+          removeFile={removeFile}
+        />
+        <FileDropzone />
+      </div>,
+    ),
+  )
+
+export default compose(
+  DropTarget(
+    'item',
+    {
+      drop({ changeList, listId: toListId }, monitor) {
+        const { listId: fromListId, name } = monitor.getItem()
+        if (toListId === fromListId) return
+        changeList(fromListId, toListId, name)
+      },
+      canDrop({ listId: toListId }, monitor) {
+        const { listId: fromListId } = monitor.getItem()
+        return toListId !== fromListId
+      },
+    },
+    (connect, monitor) => ({
+      connectDropTarget: connect.dropTarget(),
+      isOver: monitor.isOver(),
+      canDrop: monitor.canDrop(),
+    }),
+  ),
+  DropTarget(
+    NativeTypes.FILE,
+    {
+      drop({ addFile }, monitor) {
+        const [file] = monitor.getItem().files
+        addFile(file)
+      },
+    },
+    (connect, monitor) => ({
+      connectFileDrop: connect.dropTarget(),
+      isFileOver: monitor.isOver(),
+    }),
+  ),
+)(FileSection)
diff --git a/packages/component-wizard/src/components/FileSection.local.scss b/packages/component-wizard/src/components/FileSection.local.scss
new file mode 100644
index 000000000..f69a27147
--- /dev/null
+++ b/packages/component-wizard/src/components/FileSection.local.scss
@@ -0,0 +1,46 @@
+.drop-section {
+  border: 1px solid black;
+  display: flex;
+  flex-direction: column;
+  padding: 5px;
+
+  .header {
+    align-items: center;
+    display: flex;
+    justify-content: flex-start;
+
+    .upload-button {
+      cursor: pointer;
+      display: flex;
+      margin-left: 5px;
+    }
+
+    .title {
+      margin: 5px;
+      text-transform: uppercase;
+    }
+  }
+}
+
+.no-border-top {
+  border-top: none;
+}
+
+.dashed-border {
+  border-bottom: 1px dashed black;
+}
+
+.drag-handle {
+  align-items: center;
+  border-right: 1px solid black;
+  cursor: move;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  margin-right: 10px;
+  padding: 3px;
+}
+
+.is-over {
+  background-color: #ddd;
+}
diff --git a/packages/component-wizard/src/components/Files.js b/packages/component-wizard/src/components/Files.js
index fb7aa0a66..2f4be3cd1 100644
--- a/packages/component-wizard/src/components/Files.js
+++ b/packages/component-wizard/src/components/Files.js
@@ -1,80 +1,36 @@
 import React from 'react'
-import classnames from 'classnames'
-import { Icon } from '@pubsweet/ui'
 import { compose, withState, withHandlers } from 'recompose'
 import { SortableList } from 'pubsweet-components-faraday/src/components'
 
-import classes from './Files.local.scss'
+import FileSection from './FileSection'
 
-import FilePicker from './FilePicker'
-import FileItem from './FileItem'
-
-const DragHandle = () => (
-  <div className={classnames(classes['drag-handle'])}>
-    <Icon size={14}>chevron_up</Icon>
-    <Icon size={10}>menu</Icon>
-    <Icon size={14}>chevron_down</Icon>
-  </div>
-)
-
-const DropSection = ({
-  files,
-  title,
-  isFirst,
-  isLast,
-  moveItem,
-  addFile,
-  removeFile,
-}) => (
-  <div
-    className={classnames({
-      [classes['drop-section']]: true,
-      [classes['no-border-top']]: !isFirst,
-      [classes['dashed-border']]: !isLast,
-    })}
-  >
-    <div className={classnames(classes.header)}>
-      <span className={classnames(classes.title)}>{title}</span>
-      <FilePicker onUpload={addFile}>
-        <div className={classnames(classes['upload-button'])}>
-          <Icon>file-plus</Icon>
-        </div>
-      </FilePicker>
-    </div>
-    <SortableList
-      dragHandle={DragHandle}
-      items={files}
-      listItem={FileItem}
-      moveItem={moveItem}
-      removeFile={removeFile}
-    />
-    <div className={classnames(classes.empty)}>
-      <span>Drag items here or use the upload button</span>
-    </div>
-  </div>
-)
-
-const Files = ({ files, addFile, moveItem, removeFile }) => (
-  <div className={classnames(classes.container)}>
-    <DropSection
+const Files = ({ files, addFile, moveItem, removeFile, changeList }) => (
+  <div>
+    <FileSection
       addFile={addFile('main')}
+      changeList={changeList}
       files={files.main}
       isFirst
+      listId="main"
       moveItem={moveItem('main')}
       removeFile={removeFile('main')}
       title="Main manuscript"
     />
-    <DropSection
+    <FileSection
       addFile={addFile('supplemental')}
+      changeList={changeList}
       files={files.supplemental}
+      listId="supplemental"
       moveItem={moveItem('supplemental')}
       removeFile={removeFile('supplemental')}
       title="Supplemental files"
     />
-    <DropSection
+    <FileSection
       addFile={addFile('letter')}
+      changeList={changeList}
       files={files.letter}
       isLast
+      listId="letter"
       moveItem={moveItem('letter')}
       removeFile={removeFile('letter')}
       title="Cover letter"
@@ -85,6 +41,14 @@ const Files = ({ files, addFile, moveItem, removeFile }) => (
 export default compose(
   withState('files', 'changeFiles', { main: [], supplemental: [], letter: [] }),
   withHandlers({
+    changeList: ({ files, changeFiles }) => (fromListId, toListId, name) => {
+      const changedFile = files[fromListId].find(f => f.name === name)
+      changeFiles(prev => ({
+        ...prev,
+        [fromListId]: prev[fromListId].filter(f => f.name !== name),
+        [toListId]: [...prev[toListId], changedFile],
+      }))
+    },
     addFile: ({ changeFiles }) => type => file => {
       changeFiles(prev => ({
         ...prev,
diff --git a/packages/component-wizard/src/components/Files.local.scss b/packages/component-wizard/src/components/Files.local.scss
index 1f4c74db9..5e6d16ca9 100644
--- a/packages/component-wizard/src/components/Files.local.scss
+++ b/packages/component-wizard/src/components/Files.local.scss
@@ -1,59 +1,3 @@
-.drop-section {
-  border: 1px solid black;
-  display: flex;
-  flex-direction: column;
-  padding: 5px;
-
-  .empty {
-    align-items: center;
-    display: flex;
-    height: 60px;
-    justify-content: center;
-    margin: 10px 0;
-
-    span {
-      color: #888;
-      font-size: 14px;
-    }
-  }
-
-  .header {
-    align-items: center;
-    display: flex;
-    justify-content: flex-start;
-
-    .upload-button {
-      cursor: pointer;
-      display: flex;
-      margin-left: 5px;
-    }
-
-    .title {
-      margin: 5px;
-      text-transform: uppercase;
-    }
-  }
-}
-
-.no-border-top {
-  border-top: none;
-}
-
-.dashed-border {
-  border-bottom: 1px dashed black;
-}
-
-.drag-handle {
-  align-items: center;
-  border-right: 1px solid black;
-  cursor: move;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  margin-right: 10px;
-  padding: 3px;
-}
-
 .file-item {
   align-items: center;
   border: 1px solid black;
diff --git a/packages/component-wizard/src/components/WizardPage.js b/packages/component-wizard/src/components/WizardPage.js
index da00a100e..500ee3c0b 100644
--- a/packages/component-wizard/src/components/WizardPage.js
+++ b/packages/component-wizard/src/components/WizardPage.js
@@ -6,6 +6,8 @@ import { withJournal } from 'xpub-journal'
 import { ConnectPage } from 'xpub-connect'
 import { selectCollection, selectFragment } from 'xpub-selectors'
 import { compose, withHandlers, withState, withContext } from 'recompose'
+import HTML5Backend from 'react-dnd-html5-backend'
+import { DragDropContext } from 'react-dnd'
 
 import Wizard from './Wizard'
 
@@ -78,4 +80,5 @@ export default compose(
       toggleConfirmation,
     }),
   ),
+  DragDropContext(HTML5Backend),
 )(Wizard)
diff --git a/packages/components-faraday/src/components/AuthorList/AuthorList.js b/packages/components-faraday/src/components/AuthorList/AuthorList.js
index b287d6726..9576a82d9 100644
--- a/packages/components-faraday/src/components/AuthorList/AuthorList.js
+++ b/packages/components-faraday/src/components/AuthorList/AuthorList.js
@@ -63,6 +63,7 @@ const Authors = ({
       />
     ) : (
       <SortableList
+        beginDragProps={['index']}
         dragHandle={DragHandle}
         dropItem={dropItem}
         editedAuthor={editedAuthor}
diff --git a/packages/components-faraday/src/components/SortableList/SortableList.js b/packages/components-faraday/src/components/SortableList/SortableList.js
index 86f710b6d..9eca28a1f 100644
--- a/packages/components-faraday/src/components/SortableList/SortableList.js
+++ b/packages/components-faraday/src/components/SortableList/SortableList.js
@@ -1,4 +1,5 @@
 import React from 'react'
+import { pick } from 'lodash'
 import { compose } from 'recompose'
 import { findDOMNode } from 'react-dom'
 import HTML5Backend from 'react-dnd-html5-backend'
@@ -6,7 +7,9 @@ import { DragSource, DropTarget, DragDropContext } from 'react-dnd'
 
 const itemSource = {
   beginDrag(props) {
-    return { index: props.index }
+    console.log('beginning drag', props)
+    // return { index: props.index }
+    return pick(props, props.beginDragProps)
   },
 }
 
@@ -124,4 +127,5 @@ SortableList.moveItem = (items, dragIndex, hoverIndex) => {
   ]
 }
 
-export default DragDropContext(HTML5Backend)(SortableList)
+// export default DragDropContext(HTML5Backend)(SortableList)
+export default SortableList
-- 
GitLab