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 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: * * * 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.} 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 ( ); } } 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;