File size: 4,472 Bytes
f2bee8a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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;