Merge pull request #2727 from paulkaplan/drop-area-for-stage
Share costumes and sounds to stage
This commit is contained in:
5 files changed
+119
-47
No files matched your search
@@ -17,7 +17,7 @@ const dragTypeMap = {
|
||||
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.backpackHeader}
|
||||
@@ -45,7 +45,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on
|
||||
{expanded ? (
|
||||
<div
|
||||
className={styles.backpackList}
|
||||
ref={dropAreaRef}
|
||||
ref={containerRef}
|
||||
>
|
||||
{error ? (
|
||||
<div className={styles.statusMessage}>
|
||||
@@ -104,6 +104,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on
|
||||
);
|
||||
|
||||
Backpack.propTypes = {
|
||||
containerRef: PropTypes.func,
|
||||
contents: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
thumbnailUrl: PropTypes.string,
|
||||
@@ -111,7 +112,6 @@ Backpack.propTypes = {
|
||||
name: PropTypes.string
|
||||
})),
|
||||
dragOver: PropTypes.bool,
|
||||
dropAreaRef: PropTypes.func,
|
||||
error: PropTypes.bool,
|
||||
expanded: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
|
||||
@@ -39,6 +39,8 @@ const messages = defineMessages({
|
||||
const StageSelector = props => {
|
||||
const {
|
||||
backdropCount,
|
||||
containerRef,
|
||||
dragOver,
|
||||
fileInputRef,
|
||||
intl,
|
||||
selected,
|
||||
@@ -59,9 +61,10 @@ const StageSelector = props => {
|
||||
<Box
|
||||
className={classNames(styles.stageSelector, {
|
||||
[styles.isSelected]: selected,
|
||||
[styles.raised]: raised,
|
||||
[styles.raised]: raised || dragOver,
|
||||
[styles.receivedBlocks]: receivedBlocks
|
||||
})}
|
||||
componentRef={containerRef}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
@@ -125,6 +128,8 @@ const StageSelector = props => {
|
||||
|
||||
StageSelector.propTypes = {
|
||||
backdropCount: PropTypes.number.isRequired,
|
||||
containerRef: PropTypes.func,
|
||||
dragOver: PropTypes.bool,
|
||||
fileInputRef: PropTypes.func,
|
||||
intl: intlShape.isRequired,
|
||||
onBackdropFileUpload: PropTypes.func,
|
||||
|
||||
@@ -11,11 +11,15 @@ import {
|
||||
spritePayload
|
||||
} from '../lib/backpack-api';
|
||||
import DragConstants from '../lib/drag-constants';
|
||||
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import storage from '../lib/storage';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND, DragConstants.SPRITE];
|
||||
const DroppableBackpack = DropAreaHOC(dragTypes)(BackpackComponent);
|
||||
|
||||
class Backpack extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -23,8 +27,7 @@ class Backpack extends React.Component {
|
||||
'handleDrop',
|
||||
'handleToggle',
|
||||
'handleDelete',
|
||||
'refreshContents',
|
||||
'setRef'
|
||||
'refreshContents'
|
||||
]);
|
||||
this.state = {
|
||||
dragOver: false,
|
||||
@@ -46,29 +49,6 @@ class Backpack extends React.Component {
|
||||
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 () {
|
||||
const newState = !this.state.expanded;
|
||||
this.setState({expanded: newState, offset: 0});
|
||||
@@ -126,19 +106,15 @@ class Backpack extends React.Component {
|
||||
});
|
||||
}
|
||||
}
|
||||
setRef (ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<BackpackComponent
|
||||
<DroppableBackpack
|
||||
contents={this.state.contents}
|
||||
dragOver={this.state.dragOver}
|
||||
dropAreaRef={this.setRef}
|
||||
error={this.state.error}
|
||||
expanded={this.state.expanded}
|
||||
loading={this.state.loading}
|
||||
onDelete={this.handleDelete}
|
||||
onDrop={this.handleDrop}
|
||||
onToggle={this.props.host ? this.handleToggle : null}
|
||||
/>
|
||||
);
|
||||
@@ -146,15 +122,6 @@ class Backpack extends React.Component {
|
||||
}
|
||||
|
||||
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,
|
||||
token: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
@@ -181,7 +148,6 @@ const getTokenAndUsername = state => {
|
||||
|
||||
const mapStateToProps = state => Object.assign(
|
||||
{
|
||||
dragInfo: state.scratchGui.assetDrag,
|
||||
vm: state.scratchGui.vm
|
||||
},
|
||||
getTokenAndUsername(state)
|
||||
|
||||
@@ -7,6 +7,8 @@ import {connect} from 'react-redux';
|
||||
import {openBackdropLibrary} from '../reducers/modals';
|
||||
import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab';
|
||||
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';
|
||||
|
||||
@@ -14,6 +16,9 @@ import backdropLibraryContent from '../lib/libraries/backdrops.json';
|
||||
import costumeLibraryContent from '../lib/libraries/costumes.json';
|
||||
import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js';
|
||||
|
||||
const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND];
|
||||
const DroppableStage = DropAreaHOC(dragTypes)(StageSelectorComponent);
|
||||
|
||||
class StageSelector extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -27,6 +32,7 @@ class StageSelector extends React.Component {
|
||||
'handleBackdropUpload',
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handleDrop',
|
||||
'setFileInput'
|
||||
]);
|
||||
}
|
||||
@@ -75,6 +81,13 @@ class StageSelector extends React.Component {
|
||||
handleMouseLeave () {
|
||||
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) {
|
||||
this.fileInput = input;
|
||||
}
|
||||
@@ -82,16 +95,16 @@ class StageSelector extends React.Component {
|
||||
const componentProps = omit(this.props, [
|
||||
'assetId', 'dispatchSetHoveredSprite', 'id', 'onActivateTab', 'onSelect']);
|
||||
return (
|
||||
<StageSelectorComponent
|
||||
<DroppableStage
|
||||
fileInputRef={this.setFileInput}
|
||||
onBackdropFileUpload={this.handleBackdropUpload}
|
||||
onBackdropFileUploadClick={this.handleFileUploadClick}
|
||||
onClick={this.handleClick}
|
||||
onDrop={this.handleDrop}
|
||||
onEmptyBackdropClick={this.handleEmptyBackdrop}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSurpriseBackdropClick={this.handleSurpriseBackdrop}
|
||||
|
||||
{...componentProps}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
Reference in new issue
Block a user