diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index d3b2f9d4480de7260e6a15d2650077e1486dd0bd..21cef41e9a6c66b2072b2625d6ad3f1a828f7154 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 4be489f6948e718c7fe8c2cecb90c44db388726a..fb08f3b413c8da8105a40309571c4c5a95cb383b 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} /> <div className={styles.stageSelectorWrapper}> @@ -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 97d6dffaa31e4c89e9f09a02d0e7acda5bb8fe76..535e4b270a19111bdea51874511672cb28244074 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 ( <TargetPaneComponent {...componentProps} + fileInputRef={this.setFileInput} onChangeSpriteDirection={this.handleChangeSpriteDirection} onChangeSpriteName={this.handleChangeSpriteName} onChangeSpriteSize={this.handleChangeSpriteSize} @@ -103,8 +124,10 @@ class TargetPane extends React.Component { onChangeSpriteY={this.handleChangeSpriteY} onDeleteSprite={this.handleDeleteSprite} onDuplicateSprite={this.handleDuplicateSprite} + onFileUploadClick={this.handleFileUploadClick} onPaintSpriteClick={this.handlePaintSpriteClick} onSelectSprite={this.handleSelectSprite} + onSpriteUpload={this.handleSpriteUpload} onSurpriseSpriteClick={this.handleSurpriseSpriteClick} /> ); diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index ca4ce8773c113b92ef9fcfb460daeda609242482..cfe54784b329774b0e92d44b9477b3c16e808ee9 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 };