diff --git a/packages/component-wizard/src/components/FileDropzone.js b/packages/component-wizard/src/components/FileDropzone.js new file mode 100644 index 0000000000000000000000000000000000000000..b97ee67cca4c8f93d4932358cfc1675b3e0628de --- /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 0000000000000000000000000000000000000000..fccfe9120bf2a32e4721e44813a3d7ab21d4d705 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..a87bb3333064a27cb1b964140025098ff1b3bddd --- /dev/null +++ b/packages/component-wizard/src/components/FileItem.js @@ -0,0 +1,38 @@ +import React from 'react' +import classnames from 'classnames' +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>{parseFileSize(size)}</span> + </div> + <div className={classnames(classes.buttons)}> + <button onClick={removeFile(name)} title="Preview"> + <Icon color="#666">eye</Icon> + </button> + <button onClick={removeFile(name)} title="Delete"> + <Icon color="#666">trash-2</Icon> + </button> + </div> + </div> +) + +export default FileItem diff --git a/packages/component-wizard/src/components/FileItem.local.scss b/packages/component-wizard/src/components/FileItem.local.scss new file mode 100644 index 0000000000000000000000000000000000000000..076d3f0412ba3073bfe818bb7d0f565bee5185d4 --- /dev/null +++ b/packages/component-wizard/src/components/FileItem.local.scss @@ -0,0 +1,31 @@ +.file-item { + align-items: center; + border: 1px solid black; + display: flex; + margin: 5px; + + .info { + border-right: 1px solid black; + display: flex; + flex: 1; + justify-content: space-between; + padding: 2px 10px 2px 0; + } + + .buttons { + align-items: center; + display: flex; + justify-content: center; + margin: 0 5px; + + button { + border: none; + cursor: pointer; + + &:active, + &:focus { + outline: none; + } + } + } +} diff --git a/packages/component-wizard/src/components/FilePicker.js b/packages/component-wizard/src/components/FilePicker.js new file mode 100644 index 0000000000000000000000000000000000000000..cf1e354211971410f23c67b625a4b7d0bc5afbc7 --- /dev/null +++ b/packages/component-wizard/src/components/FilePicker.js @@ -0,0 +1,31 @@ +import React, { Component } from 'react' + +class FilePicker extends Component { + handleUpload = e => { + const { onUpload } = this.props + onUpload(e.target.files[0]) + this.fileInput.value = null + } + + render() { + const { children } = this.props + return ( + <div> + <input + onChange={this.handleUpload} + ref={input => (this.fileInput = input)} + style={{ display: 'none' }} + type="file" + /> + {React.cloneElement(children, { + onClick: e => { + e.preventDefault() + this.fileInput.click() + }, + })} + </div> + ) + } +} + +export default FilePicker diff --git a/packages/component-wizard/src/components/FileSection.js b/packages/component-wizard/src/components/FileSection.js new file mode 100644 index 0000000000000000000000000000000000000000..6e5c6f58ad450c75384e36908ba44b3d15a97e8d --- /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 0000000000000000000000000000000000000000..f69a27147b6f16a921a02175700b18e305185aef --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..2f4be3cd116556a67f19262aed3fdb63ac05fbd9 --- /dev/null +++ b/packages/component-wizard/src/components/Files.js @@ -0,0 +1,72 @@ +import React from 'react' +import { compose, withState, withHandlers } from 'recompose' +import { SortableList } from 'pubsweet-components-faraday/src/components' + +import FileSection from './FileSection' + +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" + /> + <FileSection + addFile={addFile('supplemental')} + changeList={changeList} + files={files.supplemental} + listId="supplemental" + moveItem={moveItem('supplemental')} + removeFile={removeFile('supplemental')} + title="Supplemental files" + /> + <FileSection + addFile={addFile('letter')} + changeList={changeList} + files={files.letter} + isLast + listId="letter" + moveItem={moveItem('letter')} + removeFile={removeFile('letter')} + title="Cover letter" + /> + </div> +) + +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, + [type]: [...prev[type], { name: file.name, size: file.size }], + })) + }, + moveItem: ({ changeFiles }) => type => (dragIndex, hoverIndex) => { + changeFiles(prev => ({ + ...prev, + [type]: SortableList.moveItem(prev[type], dragIndex, hoverIndex), + })) + }, + removeFile: ({ changeFiles }) => type => name => e => { + e.preventDefault() + changeFiles(prev => ({ + ...prev, + [type]: prev[type].filter(f => f.name !== name), + })) + }, + }), +)(Files) diff --git a/packages/component-wizard/src/components/Files.local.scss b/packages/component-wizard/src/components/Files.local.scss new file mode 100644 index 0000000000000000000000000000000000000000..5e6d16ca97f87741459a3d88f0f3a1a6c11cdc94 --- /dev/null +++ b/packages/component-wizard/src/components/Files.local.scss @@ -0,0 +1,30 @@ +.file-item { + align-items: center; + border: 1px solid black; + display: flex; + margin: 5px; + + .info { + border-right: 1px solid black; + display: flex; + flex: 1; + padding: 2px 0; + } + + .buttons { + align-items: center; + display: flex; + justify-content: center; + margin: 0 5px; + + button { + border: none; + cursor: pointer; + + &:active, + &:focus { + outline: none; + } + } + } +} diff --git a/packages/component-wizard/src/components/WizardPage.js b/packages/component-wizard/src/components/WizardPage.js index da00a100ed26da75cd2736e9887894704777bfbd..500ee3c0b6a8ecde73863f78e5fdc73f88818fa9 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/component-wizard/src/components/WizardStep.js b/packages/component-wizard/src/components/WizardStep.js index ff5bdc3b9f3ba264388d94a8e9ca4f0054f84017..9f0a4b8c51bffcc518b3f4c9154ccfc86d0e7865 100644 --- a/packages/component-wizard/src/components/WizardStep.js +++ b/packages/component-wizard/src/components/WizardStep.js @@ -5,6 +5,7 @@ import { ValidatedField, Button } from '@pubsweet/ui' import classes from './WizardStep.local.scss' import AutosaveIndicator from './AutosaveIndicator' +import Files from './Files' export default ({ children: stepChildren, @@ -65,6 +66,7 @@ export default ({ ) }, )} + <Files /> <div className={classnames(classes.buttons)}> <Button onClick={isFirst ? () => history.push('/') : prevStep}> {isFirst diff --git a/packages/components-faraday/src/components/AuthorList/AuthorList.js b/packages/components-faraday/src/components/AuthorList/AuthorList.js index b287d67260c4d77f49bf77836a144bc8de75bb17..9576a82d93eea2f15673629b88232beafe349129 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/AuthorList/StaticList.js b/packages/components-faraday/src/components/AuthorList/StaticList.js index 4acf4dbdbad1cc66ec0eef9b77c6a46cededf40b..5cb6432fb475ead78532bd019ae4f80a8b09b8fd 100644 --- a/packages/components-faraday/src/components/AuthorList/StaticList.js +++ b/packages/components-faraday/src/components/AuthorList/StaticList.js @@ -31,9 +31,9 @@ export default ({ key={a.firstName} {...a} countryParser={countryParser} + index={index} parseAuthorType={parseAuthorType} removeAuthor={removeAuthor} - index={index} {...rest} /> ), diff --git a/packages/components-faraday/src/components/SortableList/SortableList.js b/packages/components-faraday/src/components/SortableList/SortableList.js index 86f710b6db530e668b1454aab6ea5ca982a39685..9eca28a1feb282f1894ebd4cda0503fa1b7f6cf5 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 diff --git a/packages/components-faraday/src/components/index.js b/packages/components-faraday/src/components/index.js index 9a7465c876911d5e084438021aafb74431583ce4..da51b2a179ed3cefad04ed44d0c2dc808d45a476 100644 --- a/packages/components-faraday/src/components/index.js +++ b/packages/components-faraday/src/components/index.js @@ -2,3 +2,4 @@ export { default as SortableList } from './SortableList/SortableList' export { default as AuthorList } from './AuthorList/AuthorList' export { Dropdown, Logo } from './UIComponents' +export { DragHandle } from './AuthorList/FormItems' diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index ac142cf190c5a2949aeb6142ab589fbb04fa27e3..36f785c3693194e7e9c1d5098f5f287f6f687de2 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -24,7 +24,7 @@ module.exports = { 'pubsweet-client': { API_ENDPOINT: '/api', 'login-redirect': '/', - 'redux-log': true, + 'redux-log': false, theme: process.env.PUBSWEET_THEME, }, 'mail-transport': {