diff --git a/package.json b/package.json
index 439bf3079..94658acec 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"scratch-render": "0.1.0-prerelease.20180502115145",
"scratch-svg-renderer": "0.1.0-prerelease.20180423193917",
"scratch-storage": "0.4.1",
- "scratch-vm": "0.1.0-prerelease.1525459625",
+ "scratch-vm": "0.1.0-prerelease.1525460669",
"selenium-webdriver": "3.6.0",
"startaudiocontext": "1.2.1",
"style-loader": "^0.20.0",
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index d3b2f9d44..21cef41e9 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -34,7 +34,7 @@ const messages = defineMessages({
addSpriteFromFile: {
id: 'gui.spriteSelector.addSpriteFromFile',
description: 'Button to add a sprite in the target pane from file',
- defaultMessage: 'Coming Soon'
+ defaultMessage: 'Upload'
}
});
@@ -51,12 +51,15 @@ const SpriteSelectorComponent = function (props) {
onChangeSpriteY,
onDeleteSprite,
onDuplicateSprite,
+ onFileUploadClick,
onNewSpriteClick,
- onSurpriseSpriteClick,
onPaintSpriteClick,
onSelectSprite,
+ onSpriteUpload,
+ onSurpriseSpriteClick,
raised,
selectedId,
+ spriteFileInput,
sprites,
...componentProps
} = props;
@@ -121,7 +124,11 @@ const SpriteSelectorComponent = function (props) {
moreButtons={[
{
title: intl.formatMessage(messages.addSpriteFromFile),
- img: fileUploadIcon
+ img: fileUploadIcon,
+ onClick: onFileUploadClick,
+ fileAccept: '.svg, .png, .jpg, .jpeg, .sprite2', // TODO add sprite 3
+ fileChange: onSpriteUpload,
+ fileInput: spriteFileInput
}, {
title: intl.formatMessage(messages.addSpriteFromSurprise),
img: surpriseIcon,
@@ -154,12 +161,15 @@ SpriteSelectorComponent.propTypes = {
onChangeSpriteY: PropTypes.func,
onDeleteSprite: PropTypes.func,
onDuplicateSprite: PropTypes.func,
+ onFileUploadClick: PropTypes.func,
onNewSpriteClick: PropTypes.func,
onPaintSpriteClick: PropTypes.func,
onSelectSprite: PropTypes.func,
+ onSpriteUpload: PropTypes.func,
onSurpriseSpriteClick: PropTypes.func,
raised: PropTypes.bool,
selectedId: PropTypes.string,
+ spriteFileInput: PropTypes.func,
sprites: PropTypes.shape({
id: PropTypes.shape({
costume: PropTypes.shape({
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index 4be489f69..fb08f3b41 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -17,6 +17,7 @@ import styles from './target-pane.css';
*/
const TargetPane = ({
editingTarget,
+ fileInputRef,
hoveredTarget,
spriteLibraryVisible,
onChangeSpriteDirection,
@@ -27,11 +28,13 @@ const TargetPane = ({
onChangeSpriteY,
onDeleteSprite,
onDuplicateSprite,
+ onFileUploadClick,
onNewSpriteClick,
- onSurpriseSpriteClick,
onPaintSpriteClick,
onRequestCloseSpriteLibrary,
onSelectSprite,
+ onSpriteUpload,
+ onSurpriseSpriteClick,
raiseSprites,
stage,
sprites,
@@ -48,6 +51,7 @@ const TargetPane = ({
hoveredTarget={hoveredTarget}
raised={raiseSprites}
selectedId={editingTarget}
+ spriteFileInput={fileInputRef}
sprites={sprites}
onChangeSpriteDirection={onChangeSpriteDirection}
onChangeSpriteName={onChangeSpriteName}
@@ -57,9 +61,11 @@ const TargetPane = ({
onChangeSpriteY={onChangeSpriteY}
onDeleteSprite={onDeleteSprite}
onDuplicateSprite={onDuplicateSprite}
+ onFileUploadClick={onFileUploadClick}
onNewSpriteClick={onNewSpriteClick}
onPaintSpriteClick={onPaintSpriteClick}
onSelectSprite={onSelectSprite}
+ onSpriteUpload={onSpriteUpload}
onSurpriseSpriteClick={onSurpriseSpriteClick}
/>
@@ -108,6 +114,7 @@ const spriteShape = PropTypes.shape({
TargetPane.propTypes = {
editingTarget: PropTypes.string,
extensionLibraryVisible: PropTypes.bool,
+ fileInputRef: PropTypes.func,
hoveredTarget: PropTypes.shape({
hoveredSprite: PropTypes.string,
receivedBlocks: PropTypes.bool
@@ -120,11 +127,13 @@ TargetPane.propTypes = {
onChangeSpriteY: PropTypes.func,
onDeleteSprite: PropTypes.func,
onDuplicateSprite: PropTypes.func,
+ onFileUploadClick: PropTypes.func,
onNewSpriteClick: PropTypes.func,
onPaintSpriteClick: PropTypes.func,
onRequestCloseExtensionLibrary: PropTypes.func,
onRequestCloseSpriteLibrary: PropTypes.func,
onSelectSprite: PropTypes.func,
+ onSpriteUpload: PropTypes.func,
onSurpriseSpriteClick: PropTypes.func,
raiseSprites: PropTypes.bool,
spriteLibraryVisible: PropTypes.bool,
diff --git a/src/containers/sprite-library.jsx b/src/containers/sprite-library.jsx
index 072e1714a..15d75970c 100644
--- a/src/containers/sprite-library.jsx
+++ b/src/containers/sprite-library.jsx
@@ -39,7 +39,7 @@ class SpriteLibrary extends React.PureComponent {
clearInterval(this.intervalId);
}
handleItemSelect (item) {
- this.props.vm.addSprite2(JSON.stringify(item.json));
+ this.props.vm.addSprite(JSON.stringify(item.json));
analytics.event({
category: 'library',
action: 'Select Sprite',
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 97d6dffaa..1178ed64e 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -13,6 +13,7 @@ import {setReceivedBlocks} from '../reducers/hovered-target';
import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
import spriteLibraryContent from '../lib/libraries/sprites.json';
+import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js';
class TargetPane extends React.Component {
constructor (props) {
@@ -27,9 +28,13 @@ class TargetPane extends React.Component {
'handleChangeSpriteY',
'handleDeleteSprite',
'handleDuplicateSprite',
+ 'handleNewSprite',
'handleSelectSprite',
'handleSurpriseSpriteClick',
- 'handlePaintSpriteClick'
+ 'handlePaintSpriteClick',
+ 'handleFileUploadClick',
+ 'handleSpriteUpload',
+ 'setFileInput'
]);
}
componentDidMount () {
@@ -67,19 +72,34 @@ class TargetPane extends React.Component {
}
handleSurpriseSpriteClick () {
const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
- this.props.vm.addSprite2(JSON.stringify(item.json));
+ this.props.vm.addSprite(JSON.stringify(item.json));
}
handlePaintSpriteClick () {
// @todo this is brittle, will need to be refactored for localized libraries
const emptyItem = spriteLibraryContent.find(item => item.name === 'Empty');
if (emptyItem) {
- this.props.vm.addSprite2(JSON.stringify(emptyItem.json)).then(() => {
+ this.props.vm.addSprite(JSON.stringify(emptyItem.json)).then(() => {
setTimeout(() => { // Wait for targets update to propagate before tab switching
this.props.onActivateTab(COSTUMES_TAB_INDEX);
});
});
}
}
+ handleNewSprite (spriteJSONString) {
+ this.props.vm.addSprite(spriteJSONString);
+ }
+ handleFileUploadClick () {
+ this.fileInput.click();
+ }
+ handleSpriteUpload (e) {
+ const storage = this.props.vm.runtime.storage;
+ handleFileUpload(e.target, (buffer, fileType, fileName) => {
+ spriteUpload(buffer, fileType, fileName, storage, this.handleNewSprite);
+ });
+ }
+ setFileInput (input) {
+ this.fileInput = input;
+ }
handleBlockDragEnd (blocks) {
if (this.props.hoveredTarget.sprite && this.props.hoveredTarget.sprite !== this.props.editingTarget) {
this.props.vm.shareBlocksToTarget(blocks, this.props.hoveredTarget.sprite);
@@ -95,6 +115,7 @@ class TargetPane extends React.Component {
return (
);
diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js
index ca4ce8773..d32f5cc1c 100644
--- a/src/lib/file-uploader.js
+++ b/src/lib/file-uploader.js
@@ -47,6 +47,7 @@ const handleFileUpload = function (fileInput, onload) {
* @property {string} dataFormat The data format of this asset, typically
* the extension to be used for that particular asset, e.g. 'svg' for vector images
* @property {string} md5 The md5 hash of the asset data, followed by '.'' and dataFormat
+ * @property {string} The md5 hash of the asset data // TODO remove duplication....
*/
/**
@@ -71,7 +72,8 @@ const cacheAsset = function (storage, fileName, assetType, dataFormat, data) {
return {
name: fileName,
dataFormat: dataFormat,
- md5: `${md5}.${dataFormat}`
+ md5: `${md5}.${dataFormat}`,
+ assetId: md5
};
};
@@ -106,6 +108,7 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle
break;
}
default:
+ log.warn(`Encountered unexpected file type: ${fileType}`);
return;
}
@@ -158,6 +161,7 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun
break;
}
default:
+ log.warn(`Encountered unexpected file type: ${fileType}`);
return;
}
@@ -171,8 +175,49 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun
handleSound(vmSound);
};
+const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite) {
+ switch (fileType) {
+ case '':
+ case 'application/zip': { // We think this is a .sprite2 or .sprite3 file
+ handleSprite(new Uint8Array(fileData));
+ return;
+ }
+ case 'image/svg+xml':
+ case 'image/png':
+ case 'image/jpeg': {
+ // Make a sprite from an image by making it a costume first
+ costumeUpload(fileData, fileType, `${spriteName}-costume1`, storage, (vmCostume => {
+ const newSprite = {
+ name: spriteName,
+ isStage: false,
+ x: 0,
+ y: 0,
+ visible: true,
+ size: 100,
+ rotationStyle: 'all around',
+ direction: 90,
+ draggable: true,
+ currentCostume: 0,
+ blocks: {},
+ variables: {},
+ costumes: [vmCostume],
+ sounds: [] // TODO are all of these necessary?
+ };
+ // TODO probably just want sprite upload to handle this object directly
+ handleSprite(JSON.stringify(newSprite));
+ }));
+ return;
+ }
+ default: {
+ log.warn(`Encountered unexpected file type: ${fileType}`);
+ return;
+ }
+ }
+};
+
export {
handleFileUpload,
costumeUpload,
- soundUpload
+ soundUpload,
+ spriteUpload
};