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