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

Share costumes and sounds to stage
This commit is contained in:
Paul Kaplan authored and GitHub committed 2018-08-03 13:02:41 -04:00
commit f50cf118d9
5 files changed
+119 -47

No files matched your search

+3 -3
View File
@@ -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,
+7 -41
View File
@@ -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)
+15 -2
View File
@@ -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}
/>
);
+88
View File
@@ -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;