Spaces:
Running
Running
import bindAll from 'lodash.bindall'; | |
import PropTypes from 'prop-types'; | |
import React from 'react'; | |
import omit from 'lodash.omit'; | |
import { connect } from 'react-redux'; | |
/** | |
* Higher Order Component to give components the ability to react to drag overs | |
* and drops of objects stored in the assetDrag redux state. | |
* | |
* Example: You want to enable MyComponent to receive drops from a drag type | |
* Wrapped = DropAreaHOC([...dragTypes])( | |
* <MyComponent /> | |
* ) | |
* | |
* MyComponent now receives 2 new props | |
* containerRef: a ref that must be set on the container element | |
* dragOver: boolean if an asset is being dragged above the component | |
* | |
* Use the wrapped component: | |
* <Wrapped onDrop={yourDropHandler} /> | |
* | |
* NB: This HOC _only_ works with objects that drag using the assetDrag reducer. | |
* This _does not_ handle drags for blocks coming from the workspace. | |
* | |
* @param {Array.<string>} dragTypes Types to respond to, from DragConstants | |
* @returns {function} The HOC, specialized for those drag types | |
*/ | |
const DropAreaHOC = function (dragTypes) { | |
/** | |
* Return the HOC, specialized for the dragTypes | |
* @param {React.Component} WrappedComponent component to receive drop behaviors | |
* @returns {React.Component} component with drag over/drop behavior | |
*/ | |
return function (WrappedComponent) { | |
class DropAreaWrapper extends React.Component { | |
constructor(props) { | |
super(props); | |
bindAll(this, [ | |
'setRef' | |
]); | |
this.state = { | |
dragOver: false | |
}; | |
this.ref = null; | |
this.containerBox = null; | |
} | |
componentWillReceiveProps(newProps) { | |
// If `dragging` becomes true, record the drop area rectangle | |
if (newProps.dragInfo.dragging && !this.props.dragInfo.dragging) { | |
this.dropAreaRect = this.ref && this.ref.getBoundingClientRect(); | |
// If `dragging` becomes false, call the drop handler | |
} else if (!newProps.dragInfo.dragging && this.props.dragInfo.dragging && this.state.dragOver) { | |
this.props.onDrop(this.props.dragInfo); | |
this.setState({ dragOver: false }); | |
} | |
// If a drag is in progress (currentOffset) and it matches the relevant drag types, | |
// test if the drag is within the drop area rect and set the state accordingly. | |
if (this.dropAreaRect && newProps.dragInfo.currentOffset && | |
dragTypes.includes(newProps.dragInfo.dragType)) { | |
const { x, y } = newProps.dragInfo.currentOffset; | |
const { top, right, bottom, left } = this.dropAreaRect; | |
if (x > left && x < right && y > top && y < bottom) { | |
this.setState({ dragOver: true }); | |
} else { | |
this.setState({ dragOver: false }); | |
} | |
} | |
} | |
setRef(el) { | |
this.ref = el; | |
if (this.props.componentRef) { | |
this.props.componentRef(this.ref); | |
} | |
} | |
render() { | |
const componentProps = omit(this.props, ['onDrop', 'dragInfo', 'componentRef']); | |
return ( | |
<WrappedComponent | |
containerRef={this.setRef} | |
dragOver={this.state.dragOver} | |
{...componentProps} | |
/> | |
); | |
} | |
} | |
DropAreaWrapper.propTypes = { | |
componentRef: PropTypes.func, | |
dragInfo: PropTypes.shape({ | |
currentOffset: PropTypes.shape({ | |
x: PropTypes.number, | |
y: PropTypes.number | |
}), | |
dragType: PropTypes.string, | |
dragging: PropTypes.bool, | |
index: PropTypes.number | |
}), | |
onDrop: PropTypes.func | |
}; | |
const mapStateToProps = state => ({ | |
dragInfo: state.scratchGui.assetDrag | |
}); | |
const mapDispatchToProps = () => ({}); | |
return connect( | |
mapStateToProps, | |
mapDispatchToProps | |
)(DropAreaWrapper); | |
}; | |
}; | |
export default DropAreaHOC; | |