From 63ea5b42010e2d239f4ed88fc813d4206bfcbc64 Mon Sep 17 00:00:00 2001
From: Paul Kaplan <pkaplan@media.mit.edu>
Date: Wed, 31 Oct 2018 10:21:39 -0400
Subject: [PATCH] Make the backpack a drop area for workspace code

---
 src/components/backpack/backpack.jsx | 30 ++++++++---
 src/containers/backpack.jsx          | 75 +++++++++++++++++++++++++---
 2 files changed, 92 insertions(+), 13 deletions(-)

diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx
index 9e9a4f75c..f5f450039 100644
--- a/src/components/backpack/backpack.jsx
+++ b/src/components/backpack/backpack.jsx
@@ -17,7 +17,19 @@ const dragTypeMap = { // Keys correspond with the backpack-server item types
     sprite: DragConstants.BACKPACK_SPRITE
 };
 
-const Backpack = ({containerRef, contents, dragOver, error, expanded, loading, onToggle, onDelete}) => (
+const Backpack = ({
+    blockDragOver,
+    containerRef,
+    contents,
+    dragOver,
+    error,
+    expanded,
+    loading,
+    onToggle,
+    onDelete,
+    onMouseEnter,
+    onMouseLeave
+}) => (
     <div className={styles.backpackContainer}>
         <div
             className={styles.backpackHeader}
@@ -44,8 +56,12 @@ const Backpack = ({containerRef, contents, dragOver, error, expanded, loading, o
         </div>
         {expanded ? (
             <div
-                className={styles.backpackList}
+                className={classNames(styles.backpackList, {
+                    [styles.dragOver]: dragOver || blockDragOver
+                })}
                 ref={containerRef}
+                onMouseEnter={onMouseEnter}
+                onMouseLeave={onMouseLeave}
             >
                 {error ? (
                     <div className={styles.statusMessage}>
@@ -66,11 +82,7 @@ const Backpack = ({containerRef, contents, dragOver, error, expanded, loading, o
                         </div>
                     ) : (
                         contents.length > 0 ? (
-                            <div
-                                className={classNames(styles.backpackListInner, {
-                                    [styles.dragOver]: dragOver
-                                })}
-                            >
+                            <div className={styles.backpackListInner}>
                                 {contents.map(item => (
                                     <SpriteSelectorItem
                                         className={styles.backpackItem}
@@ -104,6 +116,7 @@ const Backpack = ({containerRef, contents, dragOver, error, expanded, loading, o
 );
 
 Backpack.propTypes = {
+    blockDragOver: PropTypes.bool,
     containerRef: PropTypes.func,
     contents: PropTypes.arrayOf(PropTypes.shape({
         id: PropTypes.string,
@@ -116,10 +129,13 @@ Backpack.propTypes = {
     expanded: PropTypes.bool,
     loading: PropTypes.bool,
     onDelete: PropTypes.func,
+    onMouseEnter: PropTypes.func,
+    onMouseLeave: PropTypes.func,
     onToggle: PropTypes.func
 };
 
 Backpack.defaultProps = {
+    blockDragOver: false,
     contents: [],
     dragOver: false,
     expanded: false,
diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx
index 3f9b2dd59..755b3e3a5 100644
--- a/src/containers/backpack.jsx
+++ b/src/containers/backpack.jsx
@@ -8,7 +8,8 @@ import {
     deleteBackpackObject,
     soundPayload,
     costumePayload,
-    spritePayload
+    spritePayload,
+    codePayload
 } from '../lib/backpack-api';
 import DragConstants from '../lib/drag-constants';
 import DropAreaHOC from '../lib/drop-area-hoc.jsx';
@@ -28,10 +29,18 @@ class Backpack extends React.Component {
             'handleToggle',
             'handleDelete',
             'getBackpackAssetURL',
-            'refreshContents'
+            'refreshContents',
+            'handleMouseEnter',
+            'handleMouseLeave',
+            'handleBlockDragEnd',
+            'handleBlockDragUpdate'
         ]);
         this.state = {
-            dragOver: false,
+            // While the DroppableHOC manages drop interactions for asset tiles,
+            // we still need to micromanage drops coming from the block workspace.
+            // TODO this may be refactorable with the share-the-love logic in SpriteSelectorItem
+            blockDragOutsideWorkspace: false,
+            blockDragOverBackpack: false,
             error: false,
             offset: 0,
             itemsPerPage: 20,
@@ -50,12 +59,23 @@ class Backpack extends React.Component {
             storage._hasAddedBackpackSource = true;
         }
     }
+    componentDidMount () {
+        this.props.vm.addListener('BLOCK_DRAG_END', this.handleBlockDragEnd);
+        this.props.vm.addListener('BLOCK_DRAG_UPDATE', this.handleBlockDragUpdate);
+    }
+    componentWillUnmount () {
+        this.props.vm.removeListener('BLOCK_DRAG_END', this.handleBlockDragEnd);
+        this.props.vm.removeListener('BLOCK_DRAG_UPDATE', this.handleBlockDragUpdate);
+    }
     getBackpackAssetURL (asset) {
         return `${this.props.host}/${asset.assetId}.${asset.dataFormat}`;
     }
     handleToggle () {
         const newState = !this.state.expanded;
-        this.setState({expanded: newState, offset: 0});
+        this.setState({expanded: newState, offset: 0}, () => {
+            // Emit resize on window to get blocks to resize
+            window.dispatchEvent(new Event('resize'));
+        });
         if (newState) {
             this.refreshContents();
         }
@@ -72,6 +92,9 @@ class Backpack extends React.Component {
         case DragConstants.SPRITE:
             payloader = spritePayload;
             break;
+        case DragConstants.CODE:
+            payloader = codePayload;
+            break;
         }
         if (!payloader) return;
 
@@ -110,15 +133,47 @@ class Backpack extends React.Component {
                 });
         }
     }
+    handleBlockDragUpdate (isOutsideWorkspace) {
+        this.setState({
+            blockDragOutsideWorkspace: isOutsideWorkspace
+        });
+    }
+    handleMouseEnter () {
+        if (this.state.blockDragOutsideWorkspace) {
+            this.setState({
+                blockDragOverBackpack: true
+            });
+        }
+    }
+    handleMouseLeave () {
+        this.setState({
+            blockDragOverBackpack: false
+        });
+    }
+    handleBlockDragEnd (blocks) {
+        if (this.state.blockDragOverBackpack) {
+            this.handleDrop({
+                dragType: DragConstants.CODE,
+                payload: blocks
+            });
+        }
+        this.setState({
+            blockDragOverBackpack: false,
+            blockDragOutsideWorkspace: false
+        });
+    }
     render () {
         return (
             <DroppableBackpack
+                blockDragOver={this.state.blockDragOverBackpack}
                 contents={this.state.contents}
                 error={this.state.error}
                 expanded={this.state.expanded}
                 loading={this.state.loading}
                 onDelete={this.handleDelete}
                 onDrop={this.handleDrop}
+                onMouseEnter={this.handleMouseEnter}
+                onMouseLeave={this.handleMouseLeave}
                 onToggle={this.props.host ? this.handleToggle : null}
             />
         );
@@ -152,9 +207,17 @@ const getTokenAndUsername = state => {
 
 const mapStateToProps = state => Object.assign(
     {
-        vm: state.scratchGui.vm
+        dragInfo: state.scratchGui.assetDrag,
+        vm: state.scratchGui.vm,
+        blockDrag: state.scratchGui.blockDrag
     },
     getTokenAndUsername(state)
 );
 
-export default connect(mapStateToProps)(Backpack);
+const mapDispatchToProps = dispatch => ({
+    dispatchSetHoveredSprite: spriteId => {
+        dispatch(setHoveredSprite(spriteId));
+    }
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Backpack);
-- 
GitLab