From 820008853f487d4945ff8916ce5e7d9b83a87760 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 3 May 2018 09:53:51 -0400 Subject: [PATCH 1/8] Enable sprite upload. --- .../sprite-selector/sprite-selector.jsx | 16 +++++- src/components/target-pane/target-pane.jsx | 11 +++- src/containers/target-pane.jsx | 25 +++++++- src/lib/file-uploader.js | 57 ++++++++++++++++++- 4 files changed, 102 insertions(+), 7 deletions(-) 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/target-pane.jsx b/src/containers/target-pane.jsx index 97d6dffaa..535e4b270 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 () { @@ -80,6 +85,21 @@ class TargetPane extends React.Component { }); } } + handleNewSprite (spriteJSONString) { + this.props.vm.addSprite(spriteJSONString); // TODO change all instances of addSprite2 to addSprite? + } + 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..cfe54784b 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 }; }; @@ -171,8 +173,59 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun handleSound(vmSound); }; +const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite) { + switch (fileType) { + case '': { // We think this is a .sprite2 or .sprite3 file + handleSprite(new Uint8Array(fileData)); + // TODO VM addSprite function should handle + // buffers directly and get unpacked in scratch-parser + return; + } + case 'image/svg+xml': + case 'image/png': + case 'imag/jpeg': { + // Make a sprite from an image by making it a costume first + + // let costume = null; + costumeUpload(fileData, fileType, `${spriteName}-costume1`, storage, (vmCostume => { + const newSprite = { + targets: [{ + name: spriteName, + isStage: false, + x: 0, // what should we put here... + y: 0, + visible: true, + size: 100, + rotationStyle: 'all around', + direction: 90, + draggable: true, + currentCostume: 0, + blocks: {}, + variables: {}, + lists: {}, + broadcasts: {}, + 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; + } + // case 'archive/zip': { // sprite2 / sprite3 + // handleSprite(JSON.stringify(fileData.toString('utf-8'))); + // return; + // } + default: { + return; + } + } +}; + export { handleFileUpload, costumeUpload, - soundUpload + soundUpload, + spriteUpload }; From 0bbf92f90bf455ea8f5b49668eca959071acf56e Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 3 May 2018 16:30:44 -0400 Subject: [PATCH 2/8] Update calls to addSprite2 to use new API for adding sprites. Cleanup sprite3 representation for uploading a new sprite from an image. --- src/containers/sprite-library.jsx | 2 +- src/containers/target-pane.jsx | 6 +++--- src/lib/file-uploader.js | 32 ++++++++++++++----------------- 3 files changed, 18 insertions(+), 22 deletions(-) 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 535e4b270..1178ed64e 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -72,13 +72,13 @@ 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); }); @@ -86,7 +86,7 @@ class TargetPane extends React.Component { } } handleNewSprite (spriteJSONString) { - this.props.vm.addSprite(spriteJSONString); // TODO change all instances of addSprite2 to addSprite? + this.props.vm.addSprite(spriteJSONString); } handleFileUploadClick () { this.fileInput.click(); diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index cfe54784b..a431774c5 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -189,24 +189,20 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp // let costume = null; costumeUpload(fileData, fileType, `${spriteName}-costume1`, storage, (vmCostume => { const newSprite = { - targets: [{ - name: spriteName, - isStage: false, - x: 0, // what should we put here... - y: 0, - visible: true, - size: 100, - rotationStyle: 'all around', - direction: 90, - draggable: true, - currentCostume: 0, - blocks: {}, - variables: {}, - lists: {}, - broadcasts: {}, - costumes: [vmCostume], - sounds: [] // TODO are all of these necessary? - }] + 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)); From f1e2d1b2e3fb48e68ae3d6041494f5fc1034dcdd Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 3 May 2018 18:28:00 -0400 Subject: [PATCH 3/8] Fix typo. --- src/lib/file-uploader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index a431774c5..12f67f8a4 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -183,7 +183,7 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp } case 'image/svg+xml': case 'image/png': - case 'imag/jpeg': { + case 'image/jpeg': { // Make a sprite from an image by making it a costume first // let costume = null; From 81927ecfcd5d6cbabae1d8a5de75dbaca47b1d05 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 4 May 2018 15:16:16 -0400 Subject: [PATCH 4/8] Update scratch-vm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d6662bba..4764b0044 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", From 579c61aef3d906399ccc0de84fadff08472f52c5 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 4 May 2018 15:54:42 -0400 Subject: [PATCH 5/8] Treat zips as sprite files as well. --- src/lib/file-uploader.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index 12f67f8a4..c6bd45bad 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -175,10 +175,9 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite) { switch (fileType) { - case '': { // We think this is a .sprite2 or .sprite3 file + case '': + case 'archive/zip': { // We think this is a .sprite2 or .sprite3 file handleSprite(new Uint8Array(fileData)); - // TODO VM addSprite function should handle - // buffers directly and get unpacked in scratch-parser return; } case 'image/svg+xml': @@ -209,10 +208,6 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp })); return; } - // case 'archive/zip': { // sprite2 / sprite3 - // handleSprite(JSON.stringify(fileData.toString('utf-8'))); - // return; - // } default: { return; } From ed07535a04ee689de153b1d7f35f589fec0b3c9f Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 4 May 2018 15:56:02 -0400 Subject: [PATCH 6/8] Log unexpected file type. --- src/lib/file-uploader.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index c6bd45bad..901147770 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -108,6 +108,7 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle break; } default: + log.warn(`Encountered unexpected file type: ${fileType}`); return; } @@ -160,6 +161,7 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun break; } default: + log.warn(`Encountered unexpected file type: ${fileType}`); return; } @@ -209,6 +211,7 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp return; } default: { + log.warn(`Encountered unexpected file type: ${fileType}`); return; } } From ab6e2d15e23c6c10b9022289a426ea987950e340 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 4 May 2018 15:58:25 -0400 Subject: [PATCH 7/8] Fix MIME type for zips. --- src/lib/file-uploader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index 901147770..35a4cf02b 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -178,7 +178,7 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite) { switch (fileType) { case '': - case 'archive/zip': { // We think this is a .sprite2 or .sprite3 file + case 'application/zip': { // We think this is a .sprite2 or .sprite3 file handleSprite(new Uint8Array(fileData)); return; } From 148eed03eefdffb7d3a45e3aa126a12e7930c5c5 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Mon, 7 May 2018 10:06:59 -0400 Subject: [PATCH 8/8] Cleanup --- src/lib/file-uploader.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index 35a4cf02b..d32f5cc1c 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -186,8 +186,6 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp case 'image/png': case 'image/jpeg': { // Make a sprite from an image by making it a costume first - - // let costume = null; costumeUpload(fileData, fileType, `${spriteName}-costume1`, storage, (vmCostume => { const newSprite = { name: spriteName,