From c973bf0e1a30b39915fa1e48c6cbef1e84566f89 Mon Sep 17 00:00:00 2001
From: Paul Kaplan <pkaplan@media.mit.edu>
Date: Wed, 31 Oct 2018 10:17:10 -0400
Subject: [PATCH] Make the blocks workspace a drop area for BACKPACK_CODE drag
 types

---
 src/components/backpack/backpack.jsx |  4 ++--
 src/components/blocks/blocks.css     | 16 ++++++++++++++
 src/components/blocks/blocks.jsx     | 13 ++++++++----
 src/containers/blocks.jsx            | 22 +++++++++++++++++---
 src/lib/drag-constants.js            |  1 +
 src/lib/drop-area-hoc.jsx            | 31 ++++++++++++++++++++++++++++
 6 files changed, 78 insertions(+), 9 deletions(-)

diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx
index 04cdc2937..9e9a4f75c 100644
--- a/src/components/backpack/backpack.jsx
+++ b/src/components/backpack/backpack.jsx
@@ -10,10 +10,10 @@ import styles from './backpack.css';
 // TODO make sprite selector item not require onClick
 const noop = () => {};
 
-const dragTypeMap = {
+const dragTypeMap = { // Keys correspond with the backpack-server item types
     costume: DragConstants.BACKPACK_COSTUME,
     sound: DragConstants.BACKPACK_SOUND,
-    code: DragConstants.BACKPACK_CODE,
+    script: DragConstants.BACKPACK_CODE,
     sprite: DragConstants.BACKPACK_SPRITE
 };
 
diff --git a/src/components/blocks/blocks.css b/src/components/blocks/blocks.css
index 3d8740589..e0b07ce26 100644
--- a/src/components/blocks/blocks.css
+++ b/src/components/blocks/blocks.css
@@ -1,6 +1,22 @@
 @import "../../css/units.css";
 @import "../../css/colors.css";
 
+.blocks {
+    height: 100%;
+}
+
+.drag-over:after {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    opacity: 0.75;
+    background-color: #8cbcff;
+    transition: all 0.25s ease;
+}
+
 .blocks :global(.injectionDiv){
     position: absolute;
     top: 0;
diff --git a/src/components/blocks/blocks.jsx b/src/components/blocks/blocks.jsx
index 683da23c1..a812b039f 100644
--- a/src/components/blocks/blocks.jsx
+++ b/src/components/blocks/blocks.jsx
@@ -1,22 +1,27 @@
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import React from 'react';
 import Box from '../box/box.jsx';
 import styles from './blocks.css';
 
 const BlocksComponent = props => {
     const {
-        componentRef,
+        containerRef,
+        dragOver,
         ...componentProps
     } = props;
     return (
         <Box
-            className={styles.blocks}
-            componentRef={componentRef}
+            className={classNames(styles.blocks, {
+                [styles.dragOver]: dragOver
+            })}
             {...componentProps}
+            componentRef={containerRef}
         />
     );
 };
 BlocksComponent.propTypes = {
-    componentRef: PropTypes.func
+    containerRef: PropTypes.func,
+    dragOver: PropTypes.bool
 };
 export default BlocksComponent;
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 73a265be9..89e926d3d 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -16,6 +16,8 @@ import extensionData from '../lib/libraries/extensions/index.jsx';
 import CustomProcedures from './custom-procedures.jsx';
 import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
 import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants';
+import DropAreaHOC from '../lib/drop-area-hoc.jsx';
+import DragConstants from '../lib/drag-constants';
 
 import {connect} from 'react-redux';
 import {updateToolbox} from '../reducers/toolbox';
@@ -38,6 +40,10 @@ const addFunctionListener = (object, property, callback) => {
     };
 };
 
+const DroppableBlocks = DropAreaHOC([
+    DragConstants.BACKPACK_CODE
+])(BlocksComponent);
+
 class Blocks extends React.Component {
     constructor (props) {
         super(props);
@@ -47,6 +53,7 @@ class Blocks extends React.Component {
             'detachVM',
             'handleCategorySelected',
             'handleConnectionModalStart',
+            'handleDrop',
             'handleStatusButtonUpdate',
             'handleOpenSoundRecorder',
             'handlePromptStart',
@@ -404,6 +411,14 @@ class Blocks extends React.Component {
         ws.refreshToolboxSelection_();
         ws.toolbox_.scrollToCategoryById('myBlocks');
     }
+    handleDrop (dragInfo) {
+        fetch(dragInfo.payload.bodyUrl)
+            .then(response => response.json())
+            .then(blocks => {
+                this.props.vm.shareBlocksToTarget(blocks, this.props.vm.editingTarget.id);
+                this.props.vm.refreshWorkspace();
+            });
+    }
     render () {
         /* eslint-disable no-unused-vars */
         const {
@@ -427,9 +442,10 @@ class Blocks extends React.Component {
         } = this.props;
         /* eslint-enable no-unused-vars */
         return (
-            <div>
-                <BlocksComponent
+            <React.Fragment>
+                <DroppableBlocks
                     componentRef={this.setBlocks}
+                    onDrop={this.handleDrop}
                     {...props}
                 />
                 {this.state.prompt ? (
@@ -458,7 +474,7 @@ class Blocks extends React.Component {
                         onRequestClose={this.handleCustomProceduresClose}
                     />
                 ) : null}
-            </div>
+            </React.Fragment>
         );
     }
 }
diff --git a/src/lib/drag-constants.js b/src/lib/drag-constants.js
index ce4da5af2..2fe13b0b0 100644
--- a/src/lib/drag-constants.js
+++ b/src/lib/drag-constants.js
@@ -2,6 +2,7 @@ export default {
     SOUND: 'SOUND',
     COSTUME: 'COSTUME',
     SPRITE: 'SPRITE',
+    CODE: 'CODE',
 
     BACKPACK_SOUND: 'BACKPACK_SOUND',
     BACKPACK_COSTUME: 'BACKPACK_COSTUME',
diff --git a/src/lib/drop-area-hoc.jsx b/src/lib/drop-area-hoc.jsx
index 9f44293e8..a5bfc6499 100644
--- a/src/lib/drop-area-hoc.jsx
+++ b/src/lib/drop-area-hoc.jsx
@@ -4,7 +4,34 @@ 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) {
@@ -46,6 +73,9 @@ const DropAreaHOC = function (dragTypes) {
             }
             setRef (el) {
                 this.ref = el;
+                if (this.props.componentRef) {
+                    this.props.componentRef(this.ref);
+                }
             }
             render () {
                 const componentProps = omit(this.props, ['onDrop', 'dragInfo']);
@@ -60,6 +90,7 @@ const DropAreaHOC = function (dragTypes) {
         }
 
         DropAreaWrapper.propTypes = {
+            componentRef: PropTypes.func,
             dragInfo: PropTypes.shape({
                 currentOffset: PropTypes.shape({
                     x: PropTypes.number,
-- 
GitLab