diff --git a/src/components/sprite-selector/sprite-list.jsx b/src/components/sprite-selector/sprite-list.jsx index e8ab7f8d0cc583f25801617f53399ae0c78b10a1..a29912b79385183e8c49e0ac819ba5288ec3e761 100644 --- a/src/components/sprite-selector/sprite-list.jsx +++ b/src/components/sprite-selector/sprite-list.jsx @@ -71,6 +71,7 @@ const SpriteList = function (props) { [styles.raised]: isRaised, [styles.receivedBlocks]: receivedBlocks })} + dragPayload={sprite} dragType={DragConstants.SPRITE} id={sprite.id} index={index} diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx index 35fc11bf1655f2b40a0453ce55f12b04d9d1ad7f..aedf2f35da1c84187386aae3e5132d7fbfad9372 100644 --- a/src/containers/backpack.jsx +++ b/src/containers/backpack.jsx @@ -7,12 +7,14 @@ import { saveBackpackObject, deleteBackpackObject, soundPayload, - costumePayload + costumePayload, + spritePayload } from '../lib/backpack-api'; import DragConstants from '../lib/drag-constants'; import {connect} from 'react-redux'; import storage from '../lib/storage'; +import VM from 'scratch-vm'; class Backpack extends React.Component { constructor (props) { @@ -45,7 +47,7 @@ class Backpack extends React.Component { } } componentWillReceiveProps (newProps) { - const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND]; + 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(); @@ -83,10 +85,13 @@ class Backpack extends React.Component { case DragConstants.SOUND: payloader = soundPayload; break; + case DragConstants.SPRITE: + payloader = spritePayload; + break; } if (!payloader) return; - payloader(dragInfo.payload) + payloader(dragInfo.payload, this.props.vm) .then(payload => saveBackpackObject({ host: this.props.host, token: this.props.token, @@ -152,7 +157,8 @@ Backpack.propTypes = { }), host: PropTypes.string, token: PropTypes.string, - username: PropTypes.string + username: PropTypes.string, + vm: PropTypes.instanceOf(VM) }; const getTokenAndUsername = state => { diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 1d637330acce4a4410f1f6ecd840089c86febfc6..55f1a23eda355dde4ebf76d6c6d87c6d9bbe3d58 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -135,6 +135,12 @@ class TargetPane extends React.Component { 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 (dragInfo.dragType === DragConstants.BACKPACK_SPRITE) { + // TODO storage does not have a way of loading zips right now, and may never need it. + // So for now just grab the zip manually. + fetch(dragInfo.payload.bodyUrl) + .then(response => response.arrayBuffer()) + .then(zip => this.props.vm.addSprite(zip)); } 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 diff --git a/src/lib/backpack-api.js b/src/lib/backpack-api.js index 9a2b19e95979a57329cbcd87dac60b895fdb18bf..f0c51f14040ced7823c226521dd792b4cf639f65 100644 --- a/src/lib/backpack-api.js +++ b/src/lib/backpack-api.js @@ -1,6 +1,7 @@ import xhr from 'xhr'; import costumePayload from './backpack/costume-payload'; import soundPayload from './backpack/sound-payload'; +import spritePayload from './backpack/sprite-payload'; const getBackpackContents = ({ host, @@ -19,9 +20,13 @@ const getBackpackContents = ({ return reject(); } // Add a new property for the full thumbnail url, which includes the host. + // Also include a full body url for loading sprite zips // TODO retreiving the images through storage would allow us to remove this. return resolve(response.body.map(item => ( - Object.assign({}, item, {thumbnailUrl: `${host}/${item.thumbnail}`}) + Object.assign({}, item, { + thumbnailUrl: `${host}/${item.thumbnail}`, + bodyUrl: `${host}/${item.body}` + }) ))); }); }); @@ -72,5 +77,6 @@ export { saveBackpackObject, deleteBackpackObject, costumePayload, - soundPayload + soundPayload, + spritePayload }; diff --git a/src/lib/backpack/sprite-payload.js b/src/lib/backpack/sprite-payload.js new file mode 100644 index 0000000000000000000000000000000000000000..c9dea7f22ab4cf9933463540903dada2c4a6206c --- /dev/null +++ b/src/lib/backpack/sprite-payload.js @@ -0,0 +1,24 @@ +import jpegThumbnail from './jpeg-thumbnail'; +import storage from '../storage'; + +const spritePayload = (sprite, vm) => vm.exportSprite( + sprite.id, + 'base64' +).then(zippedSprite => { + const payload = { + type: 'sprite', + name: sprite.name, + mime: 'application/zip', + body: zippedSprite, + // Filled in below + thumbnail: '' + }; + + const costumeDataUrl = storage.get(sprite.costume.assetId).encodeDataURI(); + return jpegThumbnail(costumeDataUrl).then(thumbnail => { + payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', ''); + return payload; + }); +}); + +export default spritePayload;