diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index a9f69134ba43a1c3e79c1467f11875acbb8b535a..88d609723afc6765576eeb4433786778fffee51d 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -13,6 +13,7 @@ import styles from './selector.css'; const Selector = props => { const { buttons, + containerRef, dragType, items, selectedItemIndex, @@ -46,7 +47,10 @@ const Selector = props => { } return ( - <Box className={styles.wrapper}> + <Box + className={styles.wrapper} + componentRef={containerRef} + > <Box className={styles.listArea}> {items.map((item, index) => ( <SortableAsset @@ -87,6 +91,7 @@ Selector.propTypes = { img: PropTypes.string.isRequired, onClick: PropTypes.func })), + containerRef: PropTypes.func, dragType: PropTypes.oneOf(Object.keys(DragConstants)), draggingIndex: PropTypes.number, draggingType: PropTypes.oneOf(Object.keys(DragConstants)), diff --git a/src/components/sprite-selector-item/sprite-selector-item.css b/src/components/sprite-selector-item/sprite-selector-item.css index b54e8f720743d4559b1b4e0bb4604233835a7dc4..32a5339a75c27c6157a9cbfb1885e299b09d77cc 100644 --- a/src/components/sprite-selector-item/sprite-selector-item.css +++ b/src/components/sprite-selector-item/sprite-selector-item.css @@ -21,6 +21,7 @@ transition: 0.25s ease-out; user-select: none; + touch-action: none; } .sprite-selector-item.is-selected { diff --git a/src/components/sprite-selector/sprite-list.jsx b/src/components/sprite-selector/sprite-list.jsx index f36a2f1f5ac07a0f5b30cf65d9338281b389faa0..6195b0c9c8cb706589f73268d4e300432cfc0d98 100644 --- a/src/components/sprite-selector/sprite-list.jsx +++ b/src/components/sprite-selector/sprite-list.jsx @@ -13,6 +13,7 @@ import styles from './sprite-selector.css'; const SpriteList = function (props) { const { + containerRef, editingTarget, draggingIndex, draggingType, @@ -31,7 +32,10 @@ const SpriteList = function (props) { const isSpriteDrag = draggingType === DragConstants.SPRITE; return ( - <Box className={styles.itemsWrapper}> + <Box + className={styles.itemsWrapper} + componentRef={containerRef} + > {items.map((sprite, index) => { // If the sprite has just received a block drop, used for green highlight @@ -42,7 +46,14 @@ const SpriteList = function (props) { ); // If the sprite is indicating it can receive block dropping, used for blue highlight - const isRaised = !receivedBlocks && raised && sprite.id !== editingTarget; + let isRaised = !receivedBlocks && raised && sprite.id !== editingTarget; + + // A sprite is also raised if a costume or sound is being dragged. + // Note the absence of the self-sharing check: a sprite can share assets with itself. + // This is a quirk of 2.0, but seems worth leaving possible, it + // allows quick (albeit unusual) duplication of assets. + isRaised = isRaised || draggingType === DragConstants.COSTUME || + draggingType === DragConstants.SOUND; return ( <SortableAsset @@ -77,6 +88,7 @@ const SpriteList = function (props) { }; SpriteList.propTypes = { + containerRef: PropTypes.func, draggingIndex: PropTypes.number, draggingType: PropTypes.oneOf(Object.keys(DragConstants)), editingTarget: PropTypes.string, diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 994f923bb4c5b7af34153f6232d02da2d04373d7..e3c7688ddc3e4257d45ae661254b5a50521440ee 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -107,14 +107,25 @@ class TargetPane extends React.Component { this.props.onReceivedBlocks(true); } } - handleDrop (dragInfo) { + const {sprite: targetId} = this.props.hoveredTarget; if (dragInfo.dragType === DragConstants.SPRITE) { // Add one to both new and target index because we are not counting/moving the stage this.props.vm.reorderTarget(dragInfo.index + 1, dragInfo.newIndex + 1); + } else if (targetId) { + // Something is being dragged over one of the sprite tiles or the backdrop. + // Dropping assets like sounds and costumes duplicate the asset on the + // hovered target. Shared costumes also become the current costume on that target. + // However, dropping does not switch the editing target or activate that editor tab. + // This is based on 2.0 behavior, but seems like it keeps confusing switching to a minimum. + // it allows the user to share multiple things without switching back and forth. + if (dragInfo.dragType === DragConstants.COSTUME) { + this.props.vm.shareCostumeToTarget(dragInfo.index, targetId); + } else if (targetId && dragInfo.dragType === DragConstants.SOUND) { + this.props.vm.shareSoundToTarget(dragInfo.index, targetId); + } } } - render () { const { onActivateTab, // eslint-disable-line no-unused-vars diff --git a/src/lib/sortable-hoc.jsx b/src/lib/sortable-hoc.jsx index 665c5bc43a64fb055217786e4e96551de3c496ba..1d26d6749afe752cf49e693c1fd73cfd62da1705 100644 --- a/src/lib/sortable-hoc.jsx +++ b/src/lib/sortable-hoc.jsx @@ -9,12 +9,15 @@ const SortableHOC = function (WrappedComponent) { constructor (props) { super(props); bindAll(this, [ + 'setRef', 'handleAddSortable', 'handleRemoveSortable' ]); this.sortableRefs = []; this.boxes = null; + this.ref = null; + this.containerBox = null; } componentWillReceiveProps (newProps) { @@ -25,6 +28,10 @@ const SortableHOC = function (WrappedComponent) { if (a.top === b.top) return a.left - b.left; return a.top - b.top; }); + if (!this.ref) { + throw new Error('The containerRef must be assigned to the sortable area'); + } + this.containerBox = this.ref.getBoundingClientRect(); } else if (!newProps.dragInfo.dragging && this.props.dragInfo.dragging) { this.props.onDrop(Object.assign({}, this.props.dragInfo, {newIndex: this.getMouseOverIndex()})); @@ -64,17 +71,25 @@ const SortableHOC = function (WrappedComponent) { // the dragging object. Obviously only exists if there is a drag (i.e. currentOffset). let mouseOverIndex = null; if (this.props.dragInfo.currentOffset) { - mouseOverIndex = indexForPositionOnList( - this.props.dragInfo.currentOffset, this.boxes); + const {x, y} = this.props.dragInfo.currentOffset; + const {top, left, bottom, right} = this.containerBox; + if (x >= left && x <= right && y >= top && y <= bottom) { + mouseOverIndex = indexForPositionOnList( + this.props.dragInfo.currentOffset, this.boxes); + } } return mouseOverIndex; } + setRef (el) { + this.ref = el; + } render () { const {dragInfo: {index: dragIndex, dragType}, items} = this.props; const mouseOverIndex = this.getMouseOverIndex(); const ordering = this.getOrdering(items, dragIndex, mouseOverIndex); return ( <WrappedComponent + containerRef={this.setRef} draggingIndex={dragIndex} draggingType={dragType} mouseOverIndex={mouseOverIndex}