Skip to content

Commit 66d233e

Browse files
committed
Rewrite drag-n-drop Cut#1
1 parent 4e26765 commit 66d233e

7 files changed

Lines changed: 237 additions & 274 deletions

File tree

package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"isparta": "^4.0.0",
7777
"istanbul": "^1.1.0-alpha.1",
7878
"jest": "^21.0.1",
79+
"jest-cli": "^21.0.1",
7980
"jsdom": "^9.12.0",
8081
"mocha": "^3.4.2",
8182
"node-sass": "^4.5.3",
@@ -88,17 +89,13 @@
8889
"semantic-release": "^6.3.6",
8990
"sinon": "^2.3.2",
9091
"style-loader": "^0.18.2",
91-
"webpack": "^3.0.0",
92-
"jest-cli": "^21.0.1"
92+
"webpack": "^3.0.0"
9393
},
9494
"dependencies": {
9595
"immutability-helper": "^2.4.0",
9696
"lodash": "^4.17.4",
9797
"prop-types": "^15.6.0",
98-
"react-dnd": "^2.2.4",
99-
"react-dnd-html5-backend": "^2.2.4",
100-
"react-dnd-multi-backend": "^2.3.7",
101-
"react-dnd-touch-backend": "^0.3.11",
98+
"react-beautiful-dnd": "^4.0.1",
10299
"react-redux": "^5.0.3",
103100
"redux": "^3.6.0",
104101
"redux-actions": "^1.2.2",

src/components/BoardContainer.js

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ import isEqual from 'lodash/isEqual'
55
import {BoardDiv} from '../styles/Base'
66
import {bindActionCreators} from 'redux'
77
import {connect} from 'react-redux'
8-
import {DragDropContext} from 'react-dnd'
9-
import MultiBackend from 'react-dnd-multi-backend'
10-
import HTML5toTouch from 'react-dnd-multi-backend/lib/HTML5toTouch'
118
import Lane from './Lane'
129

13-
const boardActions = require('../actions/BoardActions')
14-
const laneActions = require('../actions/LaneActions')
10+
import * as boardActions from '../actions/BoardActions'
11+
import * as laneActions from '../actions/LaneActions'
12+
import {DragDropContext, Droppable} from 'react-beautiful-dnd'
1513

1614
class BoardContainer extends Component {
1715
wireEventBus = () => {
@@ -31,15 +29,15 @@ class BoardContainer extends Component {
3129
eventBusHandle(eventBus)
3230
}
3331

34-
componentWillMount () {
32+
componentWillMount() {
3533
const {actions, eventBusHandle} = this.props
3634
actions.loadBoard(this.props.data)
3735
if (eventBusHandle) {
3836
this.wireEventBus()
3937
}
4038
}
4139

42-
componentWillReceiveProps (nextProps) {
40+
componentWillReceiveProps(nextProps) {
4341
// nextProps.data changes when external Board input props change and nextProps.reducerData changes due to event bus or UI changes
4442
const {data, reducerData, onDataChange} = this.props
4543
if (nextProps.reducerData && !isEqual(reducerData, nextProps.reducerData)) {
@@ -51,42 +49,58 @@ class BoardContainer extends Component {
5149
}
5250
}
5351

54-
render () {
52+
onDragStart = initial => {
53+
console.log('onDragStart')
54+
console.log(initial)
55+
}
56+
57+
onDragEnd = result => {
58+
console.log('onDragEnd')
59+
console.log(result)
60+
const {source, destination, draggableId} = result
61+
destination && this.props.actions.moveCardAcrossLanes({fromLaneId: source.droppableId, toLaneId: destination.droppableId, cardId: draggableId, index: destination.index})
62+
}
63+
64+
render() {
5565
const {reducerData, style, ...otherProps} = this.props
66+
// Stick to whitelisting attributes to segregate board and lane props
67+
const passthroughProps = pick(this.props, [
68+
'onLaneScroll',
69+
'onCardClick',
70+
'onCardDelete',
71+
'onCardAdd',
72+
'onLaneClick',
73+
'addCardLink',
74+
'laneSortFunction',
75+
'draggable',
76+
'editable',
77+
'handleDragStart',
78+
'handleDragEnd',
79+
'customCardLayout',
80+
'newCardTemplate',
81+
'customLaneHeader',
82+
'tagStyle',
83+
'children'
84+
])
85+
5686
return (
57-
<BoardDiv style={style} {...otherProps}>
58-
{reducerData.lanes.map(lane => {
59-
const {id, droppable, ...otherProps} = lane
60-
// Stick to whitelisting attributes to segregate board and lane props
61-
const passthroughProps = pick(this.props, [
62-
'onLaneScroll',
63-
'onCardClick',
64-
'onCardDelete',
65-
'onCardAdd',
66-
'onLaneClick',
67-
'addCardLink',
68-
'laneSortFunction',
69-
'draggable',
70-
'editable',
71-
'handleDragStart',
72-
'handleDragEnd',
73-
'customCardLayout',
74-
'newCardTemplate',
75-
'customLaneHeader',
76-
'tagStyle',
77-
'children'
78-
])
79-
return (
80-
<Lane
81-
key={`${id}`}
82-
id={id}
83-
droppable={droppable === undefined ? true : droppable}
84-
{...otherProps}
85-
{...passthroughProps}
86-
/>
87-
)
88-
})}
89-
</BoardDiv>
87+
<DragDropContext onDragStart={this.onDragStart} onDragEnd={this.onDragEnd}>
88+
<BoardDiv style={style} {...otherProps}>
89+
{reducerData.lanes.map((lane, index) => {
90+
const {id, droppable, ...otherProps} = lane
91+
return (
92+
<Lane
93+
key={`${id}`}
94+
id={id}
95+
index={index}
96+
droppable={droppable === undefined ? true : droppable}
97+
{...otherProps}
98+
{...passthroughProps}
99+
/>
100+
)
101+
})}
102+
</BoardDiv>
103+
</DragDropContext>
90104
)
91105
}
92106
}
@@ -107,15 +121,15 @@ BoardContainer.propTypes = {
107121
handleDragStart: PropTypes.func,
108122
handleDragEnd: PropTypes.func,
109123
customCardLayout: PropTypes.bool,
110-
newCardTemplate: PropTypes.node,
124+
newCardTemplate: PropTypes.node,
111125
customLaneHeader: PropTypes.element,
112126
style: PropTypes.object,
113127
tagStyle: PropTypes.object
114128
}
115129

116130
BoardContainer.defaultProps = {
117131
onDataChange: () => {},
118-
editable: false,
132+
editable: false,
119133
draggable: false
120134
}
121135

@@ -125,4 +139,4 @@ const mapStateToProps = state => {
125139

126140
const mapDispatchToProps = dispatch => ({actions: bindActionCreators({...boardActions, ...laneActions}, dispatch)})
127141

128-
export default connect(mapStateToProps, mapDispatchToProps)(DragDropContext(MultiBackend(HTML5toTouch))(BoardContainer))
142+
export default connect(mapStateToProps, mapDispatchToProps)(BoardContainer)

src/components/Card.js

Lines changed: 40 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import React, {Component} from 'react';
2-
import PropTypes from 'prop-types';
3-
import {
4-
CardHeader, CardRightContent, CardTitle, Detail, Footer,
5-
MovableCardWrapper,
6-
} from '../styles/Base';
7-
import {DragType} from '../helpers/DragType';
8-
import {DragSource, DropTarget} from 'react-dnd';
9-
import {findDOMNode} from 'react-dom';
10-
import Tag from './Tag';
11-
import flow from 'lodash/flow';
12-
import DeleteButton from './widgets/DeleteButton';
1+
import React, {Component} from 'react'
2+
import PropTypes from 'prop-types'
3+
import {CardHeader, CardRightContent, CardTitle, Detail, Footer, MovableCardWrapper} from '../styles/Base'
4+
import Tag from './Tag'
5+
import DeleteButton from './widgets/DeleteButton'
6+
import {Draggable} from 'react-beautiful-dnd'
137

148
class Card extends Component {
159
removeCard = e => {
@@ -22,8 +16,7 @@ class Card extends Component {
2216
renderBody = () => {
2317
if (this.props.customCardLayout) {
2418
const {customCard, ...otherProps} = this.props
25-
const customCardWithProps = React.cloneElement(customCard, {...otherProps})
26-
return customCardWithProps
19+
return React.cloneElement(customCard, {...otherProps})
2720
} else {
2821
const {title, description, label, tags} = this.props
2922
return (
@@ -39,94 +32,43 @@ class Card extends Component {
3932
}
4033
}
4134

42-
render () {
43-
const {id, connectDragSource, connectDropTarget, isDragging, cardStyle, editable, customCardLayout, ...otherProps} = this.props
44-
const opacity = isDragging ? 0 : 1
45-
const background = isDragging ? '#CCC' : '#E3E3E3'
35+
getItemStyle = (isDragging, draggableStyle) => ({
36+
backgroundColor: isDragging ? '#fbfbbc' : '#fff',
37+
...draggableStyle
38+
})
39+
40+
render() {
41+
const {id, index, cardStyle, editable, customCardLayout, ...otherProps} = this.props
4642
const style = customCardLayout ? {...cardStyle, padding: 0} : cardStyle
47-
return connectDragSource(
48-
connectDropTarget(
49-
<div style={{background: background}}>
50-
<MovableCardWrapper key={id} data-id={id} {...otherProps} style={{...style, opacity: opacity}}>
51-
{this.renderBody()}
52-
{editable && <DeleteButton onClick={this.removeCard} />}
53-
</MovableCardWrapper>
54-
</div>
55-
)
43+
return (
44+
<Draggable key={id} draggableId={id} index={index}>
45+
{(dragProvided, dragSnapshot) => {
46+
const dragStyle = this.getItemStyle(dragSnapshot.isDragging, dragProvided.draggableProps.style)
47+
return (
48+
<div>
49+
<MovableCardWrapper
50+
key={id}
51+
data-id={id}
52+
{...otherProps}
53+
innerRef={dragProvided.innerRef}
54+
{...dragProvided.draggableProps}
55+
{...dragProvided.dragHandleProps}
56+
style={{
57+
...style,
58+
...dragStyle
59+
}}>
60+
{this.renderBody()}
61+
{editable && <DeleteButton onClick={this.removeCard} />}
62+
</MovableCardWrapper>
63+
{dragProvided.placeholder}
64+
</div>
65+
)
66+
}}
67+
</Draggable>
5668
)
5769
}
5870
}
5971

60-
const cardSource = {
61-
canDrag (props) {
62-
return props.draggable
63-
},
64-
65-
beginDrag (props) {
66-
props.handleDragStart && props.handleDragStart(props.id, props.laneId)
67-
return {
68-
id: props.id,
69-
laneId: props.laneId,
70-
index: props.index,
71-
card: props
72-
}
73-
},
74-
75-
endDrag (props, monitor) {
76-
const item = monitor.getItem()
77-
const dropResult = monitor.getDropResult()
78-
if (dropResult) {
79-
if (dropResult.laneId !== item.laneId) {
80-
props.moveCardAcrossLanes(item.laneId, dropResult.laneId, item.id)
81-
}
82-
props.handleDragEnd && props.handleDragEnd(item.id, item.laneId, dropResult.laneId)
83-
}
84-
}
85-
}
86-
87-
const cardTarget = {
88-
hover (props, monitor, component) {
89-
const dragIndex = monitor.getItem().index
90-
const hoverIndex = props.index
91-
const sourceListId = monitor.getItem().laneId
92-
93-
if (dragIndex === hoverIndex) {
94-
return
95-
}
96-
97-
// Determine rectangle on screen
98-
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect()
99-
100-
// Get vertical middle
101-
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
102-
103-
// Determine mouse position
104-
const clientOffset = monitor.getClientOffset()
105-
106-
// Get pixels to the top
107-
const hoverClientY = clientOffset.y - hoverBoundingRect.top
108-
109-
// Only perform the move when the mouse has crossed half of the items height
110-
// When dragging downwards, only move when the cursor is below 50%
111-
// When dragging upwards, only move when the cursor is above 50%
112-
113-
// Dragging downwards
114-
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
115-
return
116-
}
117-
118-
// Dragging upwards
119-
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
120-
return
121-
}
122-
123-
if (props.laneId === sourceListId) {
124-
props.moveCard(dragIndex, hoverIndex)
125-
monitor.getItem().index = hoverIndex
126-
}
127-
}
128-
}
129-
13072
Card.defaultProps = {
13173
cardStyle: {},
13274
customCardLayout: false,
@@ -142,21 +84,11 @@ Card.propTypes = {
14284
onClick: PropTypes.func,
14385
onDelete: PropTypes.func,
14486
metadata: PropTypes.object,
145-
connectDragSource: PropTypes.func.isRequired,
146-
isDragging: PropTypes.bool.isRequired,
14787
handleDragStart: PropTypes.func,
14888
handleDragEnd: PropTypes.func,
14989
customCardLayout: PropTypes.bool,
15090
customCard: PropTypes.node,
15191
editable: PropTypes.bool
15292
}
15393

154-
export default flow(
155-
DropTarget(DragType.CARD, cardTarget, connect => ({
156-
connectDropTarget: connect.dropTarget()
157-
})),
158-
DragSource(DragType.CARD, cardSource, (connect, monitor) => ({
159-
connectDragSource: connect.dragSource(),
160-
isDragging: monitor.isDragging()
161-
}))
162-
)(Card)
94+
export default Card

0 commit comments

Comments
 (0)