diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx index 3711cafd412d0e52e17cf61c1abce4ee6e6c80ae..563ec84a3dc7bcb7d85bd9fdf1a105451a17ee9c 100644 --- a/src/components/backpack/backpack.jsx +++ b/src/components/backpack/backpack.jsx @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {FormattedMessage} from 'react-intl'; +import DragConstants from '../../lib/drag-constants'; import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx'; import styles from './backpack.css'; @@ -8,6 +9,13 @@ import styles from './backpack.css'; // TODO make sprite selector item not require onClick const noop = () => {}; +const dragTypeMap = { + costume: DragConstants.BACKPACK_COSTUME, + sound: DragConstants.BACKPACK_SOUND, + code: DragConstants.BACKPACK_CODE, + sprite: DragConstants.BACKPACK_SPRITE +}; + const Backpack = ({contents, error, expanded, loading, onToggle}) => ( <div className={styles.backpackContainer}> <div @@ -60,6 +68,8 @@ const Backpack = ({contents, error, expanded, loading, onToggle}) => ( className={styles.backpackItem} costumeURL={item.thumbnailUrl} details={item.name} + dragPayload={item} + dragType={dragTypeMap[item.type]} key={item.id} name={item.type} selected={false} diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx index 878a6cd5f26acd9e8d0669d9777635280f74d362..a489279b28c24ac60e335d86961dc9cfd5223679 100644 --- a/src/containers/backpack.jsx +++ b/src/containers/backpack.jsx @@ -4,6 +4,7 @@ import bindAll from 'lodash.bindall'; import BackpackComponent from '../components/backpack/backpack.jsx'; import {getBackpackContents} from '../lib/backpack-api'; import {connect} from 'react-redux'; +import storage from '../lib/storage'; class Backpack extends React.Component { constructor (props) { @@ -20,6 +21,14 @@ class Backpack extends React.Component { expanded: false, contents: [] }; + + // If a host is given, add it as a web source to the storage module + if (props.host) { + storage.addWebSource( + [storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound], + asset => `${props.host}/${asset.assetId}.${asset.dataFormat}` + ); + } } handleToggle () { const newState = !this.state.expanded; diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 965f81abef7b99417f7f521d932cd6460dbd80c9..9cf6051a252abe1f9a7baa9ce30cb62d41a8e321 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -19,6 +19,11 @@ import { openBackdropLibrary } from '../reducers/modals'; +import { + activateTab, + SOUNDS_TAB_INDEX +} from '../reducers/editor-tab'; + import addLibraryBackdropIcon from '../components/asset-panel/icon--add-backdrop-lib.svg'; import addLibraryCostumeIcon from '../components/asset-panel/icon--add-costume-lib.svg'; import fileUploadIcon from '../components/action-menu/icon--file-upload.svg'; @@ -191,14 +196,22 @@ class CostumeTab extends React.Component { this.fileInput.click(); } handleDrop (dropInfo) { - // Eventually will handle other kinds of drop events, right now just - // the reordering events. if (dropInfo.dragType === DragConstants.COSTUME) { const sprite = this.props.vm.editingTarget.sprite; const activeCostume = sprite.costumes[this.state.selectedCostumeIndex]; this.props.vm.reorderCostume(this.props.vm.editingTarget.id, dropInfo.index, dropInfo.newIndex); this.setState({selectedCostumeIndex: sprite.costumes.indexOf(activeCostume)}); + } else if (dropInfo.dragType === DragConstants.BACKPACK_COSTUME) { + this.props.vm.addCostume(dropInfo.payload.body, { + name: dropInfo.payload.name + }); + } else if (dropInfo.dragType === DragConstants.BACKPACK_SOUND) { + this.props.onActivateSoundsTab(); + this.props.vm.addSound({ + md5: dropInfo.payload.body, + name: dropInfo.payload.name + }); } } setFileInput (input) { @@ -303,6 +316,7 @@ CostumeTab.propTypes = { cameraModalVisible: PropTypes.bool, editingTarget: PropTypes.string, intl: intlShape, + onActivateSoundsTab: PropTypes.func.isRequired, onNewCostumeFromCameraClick: PropTypes.func.isRequired, onNewLibraryBackdropClick: PropTypes.func.isRequired, onNewLibraryCostumeClick: PropTypes.func.isRequired, @@ -333,6 +347,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ + onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onNewLibraryBackdropClick: e => { e.preventDefault(); dispatch(openBackdropLibrary()); diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index 097457d8a45a0e0b28ce5eaf26712f2da1a8becb..74b1eb5d406e16a43efd6c88ba227e220096d241 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -28,6 +28,11 @@ import { openSoundRecorder } from '../reducers/modals'; +import { + activateTab, + COSTUMES_TAB_INDEX +} from '../reducers/editor-tab'; + class SoundTab extends React.Component { constructor (props) { super(props); @@ -120,8 +125,6 @@ class SoundTab extends React.Component { } handleDrop (dropInfo) { - // Eventually will handle other kinds of drop events, right now just - // the reordering events. if (dropInfo.dragType === DragConstants.SOUND) { const sprite = this.props.vm.editingTarget.sprite; const activeSound = sprite.sounds[this.state.selectedSoundIndex]; @@ -130,6 +133,16 @@ class SoundTab extends React.Component { dropInfo.index, dropInfo.newIndex); this.setState({selectedSoundIndex: sprite.sounds.indexOf(activeSound)}); + } else if (dropInfo.dragType === DragConstants.BACKPACK_COSTUME) { + this.props.onActivateCostumesTab(); + this.props.vm.addCostume(dropInfo.payload.body, { + name: dropInfo.payload.name + }); + } else if (dropInfo.dragType === DragConstants.BACKPACK_SOUND) { + this.props.vm.addSound({ + md5: dropInfo.payload.body, + name: dropInfo.payload.name + }).then(this.handleNewSound); } } @@ -235,6 +248,7 @@ class SoundTab extends React.Component { SoundTab.propTypes = { editingTarget: PropTypes.string, intl: intlShape, + onActivateCostumesTab: PropTypes.func.isRequired, onNewSoundFromLibraryClick: PropTypes.func.isRequired, onNewSoundFromRecordingClick: PropTypes.func.isRequired, onRequestCloseSoundLibrary: PropTypes.func.isRequired, @@ -264,6 +278,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ + onActivateCostumesTab: () => dispatch(activateTab(COSTUMES_TAB_INDEX)), onNewSoundFromLibraryClick: e => { e.preventDefault(); dispatch(openSoundLibrary()); diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index 5efe494280f1fa004b55a4502f5027a37634e238..4c968f82731222227f9c5cbf6cdb2b15beed4287 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -52,7 +52,8 @@ class SpriteSelectorItem extends React.Component { currentOffset: currentOffset, dragging: true, dragType: this.props.dragType, - index: this.props.index + index: this.props.index, + payload: this.props.dragPayload }); this.noClick = true; } @@ -98,6 +99,7 @@ class SpriteSelectorItem extends React.Component { onClick, onDeleteButtonClick, onDuplicateButtonClick, + dragPayload, receivedBlocks, /* eslint-enable no-unused-vars */ ...props @@ -120,6 +122,10 @@ SpriteSelectorItem.propTypes = { assetId: PropTypes.string, costumeURL: PropTypes.string, dispatchSetHoveredSprite: PropTypes.func.isRequired, + dragPayload: PropTypes.shape({ + name: PropTypes.string, + body: PropTypes.string + }), dragType: PropTypes.string, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), index: PropTypes.number, diff --git a/src/lib/drag-constants.js b/src/lib/drag-constants.js index 86f064da90e556b50b5653be17aa8d9f17708811..ce4da5af2a8d362feb8ddc81ff171331e3487e63 100644 --- a/src/lib/drag-constants.js +++ b/src/lib/drag-constants.js @@ -1,5 +1,10 @@ export default { SOUND: 'SOUND', COSTUME: 'COSTUME', - SPRITE: 'SPRITE' + SPRITE: 'SPRITE', + + BACKPACK_SOUND: 'BACKPACK_SOUND', + BACKPACK_COSTUME: 'BACKPACK_COSTUME', + BACKPACK_SPRITE: 'BACKPACK_SPRITE', + BACKPACK_CODE: 'BACKPACK_CODE' };