Skip to content
Snippets Groups Projects
Unverified Commit f50cf118 authored by Paul Kaplan's avatar Paul Kaplan Committed by GitHub
Browse files

Merge pull request #2727 from paulkaplan/drop-area-for-stage

Share costumes and sounds to stage
parents 57f04784 c4f5820b
No related branches found
No related tags found
No related merge requests found
...@@ -17,7 +17,7 @@ const dragTypeMap = { ...@@ -17,7 +17,7 @@ const dragTypeMap = {
sprite: DragConstants.BACKPACK_SPRITE sprite: DragConstants.BACKPACK_SPRITE
}; };
const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, onToggle, onDelete}) => ( const Backpack = ({containerRef, contents, dragOver, error, expanded, loading, onToggle, onDelete}) => (
<div className={styles.backpackContainer}> <div className={styles.backpackContainer}>
<div <div
className={styles.backpackHeader} className={styles.backpackHeader}
...@@ -45,7 +45,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on ...@@ -45,7 +45,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on
{expanded ? ( {expanded ? (
<div <div
className={styles.backpackList} className={styles.backpackList}
ref={dropAreaRef} ref={containerRef}
> >
{error ? ( {error ? (
<div className={styles.statusMessage}> <div className={styles.statusMessage}>
...@@ -104,6 +104,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on ...@@ -104,6 +104,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on
); );
Backpack.propTypes = { Backpack.propTypes = {
containerRef: PropTypes.func,
contents: PropTypes.arrayOf(PropTypes.shape({ contents: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string, id: PropTypes.string,
thumbnailUrl: PropTypes.string, thumbnailUrl: PropTypes.string,
...@@ -111,7 +112,6 @@ Backpack.propTypes = { ...@@ -111,7 +112,6 @@ Backpack.propTypes = {
name: PropTypes.string name: PropTypes.string
})), })),
dragOver: PropTypes.bool, dragOver: PropTypes.bool,
dropAreaRef: PropTypes.func,
error: PropTypes.bool, error: PropTypes.bool,
expanded: PropTypes.bool, expanded: PropTypes.bool,
loading: PropTypes.bool, loading: PropTypes.bool,
......
...@@ -39,6 +39,8 @@ const messages = defineMessages({ ...@@ -39,6 +39,8 @@ const messages = defineMessages({
const StageSelector = props => { const StageSelector = props => {
const { const {
backdropCount, backdropCount,
containerRef,
dragOver,
fileInputRef, fileInputRef,
intl, intl,
selected, selected,
...@@ -59,9 +61,10 @@ const StageSelector = props => { ...@@ -59,9 +61,10 @@ const StageSelector = props => {
<Box <Box
className={classNames(styles.stageSelector, { className={classNames(styles.stageSelector, {
[styles.isSelected]: selected, [styles.isSelected]: selected,
[styles.raised]: raised, [styles.raised]: raised || dragOver,
[styles.receivedBlocks]: receivedBlocks [styles.receivedBlocks]: receivedBlocks
})} })}
componentRef={containerRef}
onClick={onClick} onClick={onClick}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
...@@ -125,6 +128,8 @@ const StageSelector = props => { ...@@ -125,6 +128,8 @@ const StageSelector = props => {
StageSelector.propTypes = { StageSelector.propTypes = {
backdropCount: PropTypes.number.isRequired, backdropCount: PropTypes.number.isRequired,
containerRef: PropTypes.func,
dragOver: PropTypes.bool,
fileInputRef: PropTypes.func, fileInputRef: PropTypes.func,
intl: intlShape.isRequired, intl: intlShape.isRequired,
onBackdropFileUpload: PropTypes.func, onBackdropFileUpload: PropTypes.func,
......
...@@ -11,11 +11,15 @@ import { ...@@ -11,11 +11,15 @@ import {
spritePayload spritePayload
} from '../lib/backpack-api'; } from '../lib/backpack-api';
import DragConstants from '../lib/drag-constants'; import DragConstants from '../lib/drag-constants';
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import storage from '../lib/storage'; import storage from '../lib/storage';
import VM from 'scratch-vm'; import VM from 'scratch-vm';
const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND, DragConstants.SPRITE];
const DroppableBackpack = DropAreaHOC(dragTypes)(BackpackComponent);
class Backpack extends React.Component { class Backpack extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
...@@ -23,8 +27,7 @@ class Backpack extends React.Component { ...@@ -23,8 +27,7 @@ class Backpack extends React.Component {
'handleDrop', 'handleDrop',
'handleToggle', 'handleToggle',
'handleDelete', 'handleDelete',
'refreshContents', 'refreshContents'
'setRef'
]); ]);
this.state = { this.state = {
dragOver: false, dragOver: false,
...@@ -46,29 +49,6 @@ class Backpack extends React.Component { ...@@ -46,29 +49,6 @@ class Backpack extends React.Component {
storage._hasAddedBackpackSource = true; storage._hasAddedBackpackSource = true;
} }
} }
componentWillReceiveProps (newProps) {
const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND, DragConstants.SPRITE];
// 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.handleDrop(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});
}
}
}
handleToggle () { handleToggle () {
const newState = !this.state.expanded; const newState = !this.state.expanded;
this.setState({expanded: newState, offset: 0}); this.setState({expanded: newState, offset: 0});
...@@ -126,19 +106,15 @@ class Backpack extends React.Component { ...@@ -126,19 +106,15 @@ class Backpack extends React.Component {
}); });
} }
} }
setRef (ref) {
this.ref = ref;
}
render () { render () {
return ( return (
<BackpackComponent <DroppableBackpack
contents={this.state.contents} contents={this.state.contents}
dragOver={this.state.dragOver}
dropAreaRef={this.setRef}
error={this.state.error} error={this.state.error}
expanded={this.state.expanded} expanded={this.state.expanded}
loading={this.state.loading} loading={this.state.loading}
onDelete={this.handleDelete} onDelete={this.handleDelete}
onDrop={this.handleDrop}
onToggle={this.props.host ? this.handleToggle : null} onToggle={this.props.host ? this.handleToggle : null}
/> />
); );
...@@ -146,15 +122,6 @@ class Backpack extends React.Component { ...@@ -146,15 +122,6 @@ class Backpack extends React.Component {
} }
Backpack.propTypes = { Backpack.propTypes = {
dragInfo: PropTypes.shape({
currentOffset: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
}),
dragType: PropTypes.string,
dragging: PropTypes.bool,
index: PropTypes.number
}),
host: PropTypes.string, host: PropTypes.string,
token: PropTypes.string, token: PropTypes.string,
username: PropTypes.string, username: PropTypes.string,
...@@ -181,7 +148,6 @@ const getTokenAndUsername = state => { ...@@ -181,7 +148,6 @@ const getTokenAndUsername = state => {
const mapStateToProps = state => Object.assign( const mapStateToProps = state => Object.assign(
{ {
dragInfo: state.scratchGui.assetDrag,
vm: state.scratchGui.vm vm: state.scratchGui.vm
}, },
getTokenAndUsername(state) getTokenAndUsername(state)
......
...@@ -7,6 +7,8 @@ import {connect} from 'react-redux'; ...@@ -7,6 +7,8 @@ import {connect} from 'react-redux';
import {openBackdropLibrary} from '../reducers/modals'; import {openBackdropLibrary} from '../reducers/modals';
import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab'; import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab';
import {setHoveredSprite} from '../reducers/hovered-target'; import {setHoveredSprite} from '../reducers/hovered-target';
import DragConstants from '../lib/drag-constants';
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx'; import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx';
...@@ -14,6 +16,9 @@ import backdropLibraryContent from '../lib/libraries/backdrops.json'; ...@@ -14,6 +16,9 @@ import backdropLibraryContent from '../lib/libraries/backdrops.json';
import costumeLibraryContent from '../lib/libraries/costumes.json'; import costumeLibraryContent from '../lib/libraries/costumes.json';
import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js'; import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js';
const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND];
const DroppableStage = DropAreaHOC(dragTypes)(StageSelectorComponent);
class StageSelector extends React.Component { class StageSelector extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
...@@ -27,6 +32,7 @@ class StageSelector extends React.Component { ...@@ -27,6 +32,7 @@ class StageSelector extends React.Component {
'handleBackdropUpload', 'handleBackdropUpload',
'handleMouseEnter', 'handleMouseEnter',
'handleMouseLeave', 'handleMouseLeave',
'handleDrop',
'setFileInput' 'setFileInput'
]); ]);
} }
...@@ -75,6 +81,13 @@ class StageSelector extends React.Component { ...@@ -75,6 +81,13 @@ class StageSelector extends React.Component {
handleMouseLeave () { handleMouseLeave () {
this.props.dispatchSetHoveredSprite(null); this.props.dispatchSetHoveredSprite(null);
} }
handleDrop (dragInfo) {
if (dragInfo.dragType === DragConstants.COSTUME) {
this.props.vm.shareCostumeToTarget(dragInfo.index, this.props.id);
} else if (dragInfo.dragType === DragConstants.SOUND) {
this.props.vm.shareSoundToTarget(dragInfo.index, this.props.id);
}
}
setFileInput (input) { setFileInput (input) {
this.fileInput = input; this.fileInput = input;
} }
...@@ -82,16 +95,16 @@ class StageSelector extends React.Component { ...@@ -82,16 +95,16 @@ class StageSelector extends React.Component {
const componentProps = omit(this.props, [ const componentProps = omit(this.props, [
'assetId', 'dispatchSetHoveredSprite', 'id', 'onActivateTab', 'onSelect']); 'assetId', 'dispatchSetHoveredSprite', 'id', 'onActivateTab', 'onSelect']);
return ( return (
<StageSelectorComponent <DroppableStage
fileInputRef={this.setFileInput} fileInputRef={this.setFileInput}
onBackdropFileUpload={this.handleBackdropUpload} onBackdropFileUpload={this.handleBackdropUpload}
onBackdropFileUploadClick={this.handleFileUploadClick} onBackdropFileUploadClick={this.handleFileUploadClick}
onClick={this.handleClick} onClick={this.handleClick}
onDrop={this.handleDrop}
onEmptyBackdropClick={this.handleEmptyBackdrop} onEmptyBackdropClick={this.handleEmptyBackdrop}
onMouseEnter={this.handleMouseEnter} onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave} onMouseLeave={this.handleMouseLeave}
onSurpriseBackdropClick={this.handleSurpriseBackdrop} onSurpriseBackdropClick={this.handleSurpriseBackdrop}
{...componentProps} {...componentProps}
/> />
); );
......
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import omit from 'lodash.omit';
import {connect} from 'react-redux';
const DropAreaHOC = function (dragTypes) {
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;
}
render () {
const componentProps = omit(this.props, ['onDrop', 'dragInfo']);
return (
<WrappedComponent
containerRef={this.setRef}
dragOver={this.state.dragOver}
{...componentProps}
/>
);
}
}
DropAreaWrapper.propTypes = {
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;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment