From 41c1765242f6146371e0f25b8df898a4370b3a4c Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Wed, 19 Oct 2016 18:03:28 -0400 Subject: [PATCH 01/11] Initial rough sprite/costume/backdrop libraries --- package.json | 2 + src/components/costume-canvas.js | 104 +++++++++++++++++++++++++++++ src/components/library-item.js | 49 ++++++++++++++ src/components/library.js | 66 ++++++++++++++++++ src/components/modal.js | 66 ++++++++++++++++++ src/components/sprite-selector.js | 10 ++- src/containers/backdrop-library.js | 50 ++++++++++++++ src/containers/costume-library.js | 50 ++++++++++++++ src/containers/gui.js | 36 +++++++++- src/containers/sprite-library.js | 61 +++++++++++++++++ src/containers/sprite-selector.js | 8 ++- src/lib/media-library.js | 80 ++++++++++++++++++++++ 12 files changed, 579 insertions(+), 3 deletions(-) create mode 100644 src/components/costume-canvas.js create mode 100644 src/components/library-item.js create mode 100644 src/components/library.js create mode 100644 src/components/modal.js create mode 100644 src/containers/backdrop-library.js create mode 100644 src/containers/costume-library.js create mode 100644 src/containers/sprite-library.js create mode 100644 src/lib/media-library.js diff --git a/package.json b/package.json index 6f3b38449..6a2158e4d 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,11 @@ "opt-cli": "1.5.1", "react": "15.x.x", "react-dom": "15.x.x", + "react-modal": "1.5.2", "scratch-blocks": "^0.1.0-prerelease", "scratch-render": "^0.1.0-prerelease", "scratch-vm": "^0.1.0-prerelease", + "svg-to-image": "1.1.3", "travis-after-all": "jamesarosen/travis-after-all#override-api-urls", "webpack": "1.13.2", "webpack-dev-server": "1.15.2", diff --git a/src/components/costume-canvas.js b/src/components/costume-canvas.js new file mode 100644 index 000000000..f657e847d --- /dev/null +++ b/src/components/costume-canvas.js @@ -0,0 +1,104 @@ +const React = require('react'); +const svgToImage = require('svg-to-image'); +const xhr = require('xhr'); + +/** + * @fileoverview + * A component for rendering Scratch costume URLs to canvases. + * Use for sprite library, costume library, sprite selector, etc. + * Props include width, height, and direction (direction in Scratch value). + */ + +class CostumeCanvas extends React.Component { + render () { + return ; + } + componentDidMount () { + this.load(); + } + componentDidUpdate (prevProps) { + if (prevProps.url !== this.props.url) { + this.load(); + } else { + if (prevProps.width !== this.props.width || + prevProps.height !== this.props.height || + prevProps.direction !== this.props.direction) { + this.draw(); + } + } + } + draw () { + const img = this.img; + const context = this.refs.costumeCanvas.getContext('2d'); + // Scale to fit. + let scale; + if (img.width > img.height) { + scale = this.refs.costumeCanvas.width / img.width; + } else { + scale = this.refs.costumeCanvas.height / img.height; + } + const angle = (-90 + this.props.direction) * Math.PI / 180; + const contextTranslateX = this.refs.costumeCanvas.width / 2; + const contextTranslateY = this.refs.costumeCanvas.height / 2; + context.clearRect(0, 0, + this.refs.costumeCanvas.width, this.refs.costumeCanvas.height); + context.translate(contextTranslateX, contextTranslateY); + context.rotate(angle); + context.drawImage(img, + 0, 0, img.width, img.height, + -(scale * img.width / 2), -(scale * img.height / 2), + scale * img.width, + scale * img.height); + context.rotate(-angle); + context.translate(-contextTranslateX, -contextTranslateY); + } + load () { + // Draw the icon on our canvas. + const url = this.props.url; + if (url.indexOf('.svg') > -1) { + // Vector graphics: need to download with XDR and rasterize. + // Queue request asynchronously. + setTimeout(() => { + xhr.get({ + useXDR: true, + url: url + }, (err, response, body) => { + if (!err) { + svgToImage(body, (err, img) => { + if (!err) { + this.img = img; + this.draw(); + } + }); + } + }); + }, 0); + + } else { + // Raster graphics: create Image and draw it. + let img = new Image(); + img.src = url; + img.onload = () => { + this.img = img; + this.draw(); + }; + } + } +} + +CostumeCanvas.defaultProps = { + width: 100, + height: 100, + direction: 90 +}; + +CostumeCanvas.propTypes = { + url: React.PropTypes.string, + width: React.PropTypes.number, + height: React.PropTypes.number, + direction: React.PropTypes.number +}; + +module.exports = CostumeCanvas; diff --git a/src/components/library-item.js b/src/components/library-item.js new file mode 100644 index 000000000..39f65596a --- /dev/null +++ b/src/components/library-item.js @@ -0,0 +1,49 @@ +const React = require('react'); + +const CostumeCanvas = require('./costume-canvas'); + +class LibraryItem extends React.Component { + render () { + let style = (this.props.selected) ? + this.props.selectedGridTileStyle : this.props.gridTileStyle; + return ( +
this.props.onSelect(this.props.id)}> + +

{this.props.name}

+
+ ); + } +} + +LibraryItem.defaultProps = { + gridTileStyle: { + float: 'left', + width: '140px', + marginLeft: '5px', + marginRight: '5px', + textAlign: 'center', + cursor: 'pointer' + }, + selectedGridTileStyle: { + float: 'left', + width: '140px', + marginLeft: '5px', + marginRight: '5px', + textAlign: 'center', + cursor: 'pointer', + background: '#aaa', + borderRadius: '6px' + } +}; + +LibraryItem.propTypes = { + name: React.PropTypes.string, + iconURL: React.PropTypes.string, + gridTileStyle: React.PropTypes.object, + selectedGridTileStyle: React.PropTypes.object, + selected: React.PropTypes.bool, + onSelect: React.PropTypes.func, + id: React.PropTypes.number +}; + +module.exports = LibraryItem; diff --git a/src/components/library.js b/src/components/library.js new file mode 100644 index 000000000..43923dc40 --- /dev/null +++ b/src/components/library.js @@ -0,0 +1,66 @@ +const React = require('react'); + +const LibraryItem = require('./library-item'); +const ModalComponent = require('./modal'); + +class LibraryComponent extends React.Component { + constructor (props) { + super(props); + this.state = {selectedItem: null}; + } + onSelect (id) { + if (this.state.selectedItem == id) { + // Double select: select as the library's value. + this.props.onRequestClose(); + this.props.onItemSelected(this.props.data[id]); + } + this.setState({selectedItem: id}); + } + render () { + let gridItems = []; + let itemId = 0; + for (let dataItem of this.props.data) { + const scratchURL = (dataItem.md5) ? 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + + dataItem.md5 + '/get/' : dataItem.rawURL; + gridItems.push( + + ); + itemId++; + } + + const scrollGridStyle = { + overflow: 'scroll', + position: 'absolute', + top: '70px', + bottom: '20px', + left: '30px', + right: '30px' + }; + + return ( + +

{this.props.title}

+
+ {gridItems} +
+
+ ); + } +} + +LibraryComponent.propTypes = { + title: React.PropTypes.string, + data: React.PropTypes.array, + visible: React.PropTypes.bool, + onRequestClose: React.PropTypes.func, + onItemSelected: React.PropTypes.func +}; + +module.exports = LibraryComponent; diff --git a/src/components/modal.js b/src/components/modal.js new file mode 100644 index 000000000..791814424 --- /dev/null +++ b/src/components/modal.js @@ -0,0 +1,66 @@ +const React = require('react'); +const ReactModal = require('react-modal'); + +class ModalComponent extends React.Component { + render () { + return ( + +
x
+ {this.props.children} +
+ ); + } +} + +const modalStyle = { + overlay: { + zIndex: 1000, + backgroundColor: 'rgba(0, 0, 0, .75)' + }, + content: { + position: 'absolute', + overflow: 'visible', + borderRadius: '6px', + padding: 0, + top: '5%', + bottom: '5%', + left: '5%', + right: '5%', + background: '#fcfcfc' + } +}; + +const closeButtonStyle = { + color: 'rgb(255, 255, 255)', + background: 'rgb(50, 50, 50)', + borderRadius: '15px', + width: '30px', + height: '25px', + textAlign: 'center', + paddingTop: '5px', + position: 'absolute', + right: '3px', + top: '3px', + cursor: 'pointer' +}; + +ModalComponent.defaultProps = { + modalStyle: modalStyle, + closeButtonStyle: closeButtonStyle +}; + +ModalComponent.propTypes = { + children: React.PropTypes.node, + modalStyle: React.PropTypes.object, + closeButtonStyle: React.PropTypes.object, + onRequestClose: React.PropTypes.func, + visible: React.PropTypes.bool +}; + +module.exports = ModalComponent; diff --git a/src/components/sprite-selector.js b/src/components/sprite-selector.js index 93ddcb389..1d448e109 100644 --- a/src/components/sprite-selector.js +++ b/src/components/sprite-selector.js @@ -21,6 +21,11 @@ class SpriteSelectorComponent extends React.Component { ))} +

+ + + +

); } @@ -34,7 +39,10 @@ SpriteSelectorComponent.propTypes = { name: React.PropTypes.string }) ), - value: React.PropTypes.arrayOf(React.PropTypes.string) + value: React.PropTypes.arrayOf(React.PropTypes.string), + openNewSprite: React.PropTypes.func, + openNewCostume: React.PropTypes.func, + openNewBackdrop: React.PropTypes.func }; module.exports = SpriteSelectorComponent; diff --git a/src/containers/backdrop-library.js b/src/containers/backdrop-library.js new file mode 100644 index 000000000..c57d0a367 --- /dev/null +++ b/src/containers/backdrop-library.js @@ -0,0 +1,50 @@ +const React = require('react'); +const VM = require('scratch-vm'); +const MediaLibrary = require('../lib/media-library'); + +const LibaryComponent = require('../components/library'); + + +class BackdropLibrary extends React.Component { + constructor (props) { + super(props); + this.state = {backdropData: []}; + } + componentWillReceiveProps (nextProps) { + if (nextProps.visible && this.state.backdropData.length === 0) { + this.props.mediaLibrary.getMediaLibrary('backdrop', this.setData.bind(this)); + } + } + setData (data) { + this.setState({backdropData: data}); + } + selectItem (item) { + var vmBackdrop = { + skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + item.md5 + '/get/', + name: item.name, + rotationCenterX: item.info[0], + rotationCenterY: item.info[1] + }; + if (item.info.length > 2) { + vmBackdrop.bitmapResolution = item.info[2]; + } + this.props.vm.addBackdrop(vmBackdrop); + } + render () { + return ; + } +} + +BackdropLibrary.propTypes = { + vm: React.PropTypes.instanceOf(VM).isRequired, + mediaLibrary: React.PropTypes.instanceOf(MediaLibrary), + visible: React.PropTypes.bool, + onRequestClose: React.PropTypes.func +}; + +module.exports = BackdropLibrary; diff --git a/src/containers/costume-library.js b/src/containers/costume-library.js new file mode 100644 index 000000000..1e1d849ab --- /dev/null +++ b/src/containers/costume-library.js @@ -0,0 +1,50 @@ +const React = require('react'); +const VM = require('scratch-vm'); +const MediaLibrary = require('../lib/media-library'); + +const LibaryComponent = require('../components/library'); + + +class CostumeLibrary extends React.Component { + constructor (props) { + super(props); + this.state = {costumeData: []}; + } + componentWillReceiveProps (nextProps) { + if (nextProps.visible && this.state.costumeData.length === 0) { + this.props.mediaLibrary.getMediaLibrary('costume', this.setData.bind(this)); + } + } + setData (data) { + this.setState({costumeData: data}); + } + selectItem (item) { + var vmCostume = { + skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + item.md5 + '/get/', + name: item.name, + rotationCenterX: item.info[0], + rotationCenterY: item.info[1] + }; + if (item.info.length > 2) { + vmCostume.bitmapResolution = item.info[2]; + } + this.props.vm.addCostume(vmCostume); + } + render () { + return ; + } +} + +CostumeLibrary.propTypes = { + vm: React.PropTypes.instanceOf(VM).isRequired, + mediaLibrary: React.PropTypes.instanceOf(MediaLibrary), + visible: React.PropTypes.bool, + onRequestClose: React.PropTypes.func +}; + +module.exports = CostumeLibrary; diff --git a/src/containers/gui.js b/src/containers/gui.js index 9940c5868..9525c49e3 100644 --- a/src/containers/gui.js +++ b/src/containers/gui.js @@ -2,6 +2,7 @@ const React = require('react'); const VM = require('scratch-vm'); const VMManager = require('../lib/vm-manager'); +const MediaLibrary = require('../lib/media-library'); const Blocks = require('./blocks'); const GUIComponent = require('../components/gui'); @@ -10,10 +11,16 @@ const SpriteSelector = require('./sprite-selector'); const Stage = require('./stage'); const StopAll = require('./stop-all'); +const SpriteLibrary = require('./sprite-library'); +const CostumeLibrary = require('./costume-library'); +const BackdropLibrary = require('./backdrop-library'); + class GUI extends React.Component { constructor (props) { super(props); this.vmManager = new VMManager(this.props.vm); + this.mediaLibrary = new MediaLibrary(); + this.state = {currentModal: null}; } componentDidMount () { this.vmManager.attachKeyboardEvents(); @@ -29,19 +36,46 @@ class GUI extends React.Component { this.props.vm.loadProject(nextProps.projectData); } } + openModal (modalName) { + this.setState({currentModal: modalName}); + } + closeModal () { + this.setState({currentModal: null}); + } render () { return ( - + this.openModal('sprite-library')} + openNewCostume={() => this.openModal('costume-library')} + openNewBackdrop={() => this.openModal('backdrop-library')} /> + + + ); } diff --git a/src/containers/sprite-library.js b/src/containers/sprite-library.js new file mode 100644 index 000000000..99efc2386 --- /dev/null +++ b/src/containers/sprite-library.js @@ -0,0 +1,61 @@ +const React = require('react'); +const VM = require('scratch-vm'); +const MediaLibrary = require('../lib/media-library'); + +const LibaryComponent = require('../components/library'); + +class SpriteLibrary extends React.Component { + constructor (props) { + super(props); + this.state = {data: [], spriteData: {}}; + } + componentWillReceiveProps (nextProps) { + if (nextProps.visible && this.state.data.length === 0) { + this.props.mediaLibrary.getMediaLibrary('sprite', this.setData.bind(this)); + } + } + setData (data) { + this.setState({data: data}); + for (let sprite of data) { + this.props.mediaLibrary.getSprite(sprite.md5, this.setSpriteData.bind(this)); + } + } + setSpriteData (md5, data) { + let spriteData = this.state.spriteData; + spriteData[md5] = data; + this.setState({spriteData: spriteData}); + } + selectItem (item) { + var spriteData = JSON.stringify(this.state.spriteData[item.json]); + this.props.vm.addSprite2(spriteData); + } + render () { + let libraryData = []; + Object.keys(this.state.spriteData).map((libraryKey) => { + let libraryItem = this.state.spriteData[libraryKey]; + libraryData.push( + { + name: libraryItem.objName, + md5: libraryItem.costumes[0].baseLayerMD5, + json: libraryKey + } + ); + }); + return ; + } +} + +SpriteLibrary.propTypes = { + vm: React.PropTypes.instanceOf(VM).isRequired, + mediaLibrary: React.PropTypes.instanceOf(MediaLibrary), + visible: React.PropTypes.bool, + onRequestClose: React.PropTypes.func +}; + +module.exports = SpriteLibrary; diff --git a/src/containers/sprite-selector.js b/src/containers/sprite-selector.js index b3c8ee744..613c0b296 100644 --- a/src/containers/sprite-selector.js +++ b/src/containers/sprite-selector.js @@ -27,6 +27,9 @@ class SpriteSelector extends React.Component { ( { id: target[0], @@ -39,7 +42,10 @@ class SpriteSelector extends React.Component { } SpriteSelector.propTypes = { - vm: React.PropTypes.object.isRequired + vm: React.PropTypes.object.isRequired, + openNewSprite: React.PropTypes.func, + openNewCostume: React.PropTypes.func, + openNewBackdrop: React.PropTypes.func }; module.exports = SpriteSelector; diff --git a/src/lib/media-library.js b/src/lib/media-library.js new file mode 100644 index 000000000..6f33b25c9 --- /dev/null +++ b/src/lib/media-library.js @@ -0,0 +1,80 @@ +const xhr = require('xhr'); + +const LIBRARY_PREFIX = 'https://cdn.scratch.mit.edu/scratchr2/static/' + + '__8d9c95eb5aa1272a311775ca32568417__/medialibraries/'; +const LIBRARY_URL = { + sprite: LIBRARY_PREFIX + 'spriteLibrary.json', + costume: LIBRARY_PREFIX + 'costumeLibrary.json', + backdrop: LIBRARY_PREFIX + 'backdropLibrary.json' +}; +const SPRITE_OBJECT_PREFIX = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/'; +const SPRITE_OBJECT_SUFFIX = '/get/'; + +class MediaLibrary { + constructor () { + /* + * Cached library data, from JSON. + * @type {Object} + */ + this._libraryData = {}; + + /** + * Cached sprite data, from JSON. + * @type {Object.} + */ + this._spriteData = {}; + } + + /** + * Get the media library data for a particular scratchr2 library. + * In the future, load this from `scratch-storage` asset manager, + * e.g., for offline support. + * @param {string} libraryType Type of library, i.e., sprite, costume, sound, backdrop. + * @param {!Function} callback Callback, called with list of data. + */ + getMediaLibrary (libraryType, callback) { + if (!this._libraryData.hasOwnProperty(libraryType)) { + this._libraryData[libraryType] = null; + } + if (this._libraryData[libraryType]) { + callback(this._libraryData[libraryType]); + } else { + xhr.get({ + useXDR: true, + url: LIBRARY_URL[libraryType] + }, (err, response, body) => { + if (!err) { + let data = JSON.parse(body); + this._libraryData[libraryType] = data; + callback(this._libraryData[libraryType]); + } + }); + } + } + + /** + * Get media library info for a specific scratchr2 sprite. + * In the future, load this from `scratch-storage` asset manager, + * e.g., for offline support. + * @param {string} url URL to sprite (md5.json). + * @param {!Function} callback Callback, called with sprite data. + */ + getSprite (url, callback) { + if (this._spriteData.hasOwnProperty(url)) { + callback(url, this._spriteData[url]); + } else { + xhr.get({ + useXDR: true, + url: SPRITE_OBJECT_PREFIX + url + SPRITE_OBJECT_SUFFIX + }, (err, response, body) => { + if (!err) { + let data = JSON.parse(body); + this._spriteData[url] = data; + callback(url, data); + } + }); + } + } +} + +module.exports = MediaLibrary; From 93645ea7e667ac088cc31db8d0434d4df47c02a9 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:24:27 -0400 Subject: [PATCH 02/11] CostumeCanvas nits --- src/components/costume-canvas.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/costume-canvas.js b/src/components/costume-canvas.js index f657e847d..73c6002b6 100644 --- a/src/components/costume-canvas.js +++ b/src/components/costume-canvas.js @@ -10,11 +10,6 @@ const xhr = require('xhr'); */ class CostumeCanvas extends React.Component { - render () { - return ; - } componentDidMount () { this.load(); } @@ -30,20 +25,27 @@ class CostumeCanvas extends React.Component { } } draw () { + // Draw the costume to the rendered canvas. const img = this.img; const context = this.refs.costumeCanvas.getContext('2d'); // Scale to fit. let scale; + // Choose the larger dimension to scale by. if (img.width > img.height) { scale = this.refs.costumeCanvas.width / img.width; } else { scale = this.refs.costumeCanvas.height / img.height; } + // Rotate by the Scratch-value direction. const angle = (-90 + this.props.direction) * Math.PI / 180; + // Rotation origin point will be center of the canvas. const contextTranslateX = this.refs.costumeCanvas.width / 2; const contextTranslateY = this.refs.costumeCanvas.height / 2; + // First, clear the canvas. context.clearRect(0, 0, this.refs.costumeCanvas.width, this.refs.costumeCanvas.height); + // Translate the context to the center of the canvas, + // then rotate canvas drawing by `angle`. context.translate(contextTranslateX, contextTranslateY); context.rotate(angle); context.drawImage(img, @@ -51,6 +53,7 @@ class CostumeCanvas extends React.Component { -(scale * img.width / 2), -(scale * img.height / 2), scale * img.width, scale * img.height); + // Reset the canvas rotation and translation to 0, (0, 0). context.rotate(-angle); context.translate(-contextTranslateX, -contextTranslateY); } @@ -86,6 +89,11 @@ class CostumeCanvas extends React.Component { }; } } + render () { + return ; + } } CostumeCanvas.defaultProps = { From ee72f23f7b5dd20e3db091e4ea107660d4b1759f Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:26:09 -0400 Subject: [PATCH 03/11] Idiomatic "map" --- src/components/library.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/library.js b/src/components/library.js index 43923dc40..39dec362a 100644 --- a/src/components/library.js +++ b/src/components/library.js @@ -17,22 +17,20 @@ class LibraryComponent extends React.Component { this.setState({selectedItem: id}); } render () { - let gridItems = []; let itemId = 0; - for (let dataItem of this.props.data) { + let gridItems = this.props.data.map(function (dataItem) { + let id = itemId; + itemId++; const scratchURL = (dataItem.md5) ? 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + dataItem.md5 + '/get/' : dataItem.rawURL; - gridItems.push( - - ); - itemId++; - } + return ; + }); const scrollGridStyle = { overflow: 'scroll', From a6bdf09ba6c101e2a7393c906b11290cd689719d Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:29:00 -0400 Subject: [PATCH 04/11] Various nits --- src/components/costume-canvas.js | 6 ++-- src/components/library.js | 6 ++-- src/components/modal.js | 8 ++++-- src/components/sprite-selector.js | 9 ++++-- src/containers/backdrop-library.js | 3 +- src/containers/costume-library.js | 3 +- src/containers/gui.js | 44 ++++++++++++++++++------------ src/containers/sprite-library.js | 3 +- 8 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/components/costume-canvas.js b/src/components/costume-canvas.js index 73c6002b6..ac0646756 100644 --- a/src/components/costume-canvas.js +++ b/src/components/costume-canvas.js @@ -90,9 +90,11 @@ class CostumeCanvas extends React.Component { } } render () { - return ; + height={this.props.height} + />; } } diff --git a/src/components/library.js b/src/components/library.js index 39dec362a..cd0dd8769 100644 --- a/src/components/library.js +++ b/src/components/library.js @@ -42,8 +42,10 @@ class LibraryComponent extends React.Component { }; return ( - +

{this.props.title}

{gridItems} diff --git a/src/components/modal.js b/src/components/modal.js index 791814424..a2c257149 100644 --- a/src/components/modal.js +++ b/src/components/modal.js @@ -8,10 +8,14 @@ class ModalComponent extends React.Component { ref="modal" style={this.props.modalStyle} isOpen={this.props.visible} - onRequestClose={this.props.onRequestClose}> + onRequestClose={this.props.onRequestClose} + >
x
+ style={this.props.closeButtonStyle} + > + x +
{this.props.children} ); diff --git a/src/components/sprite-selector.js b/src/components/sprite-selector.js index f6d0e7159..da00622bf 100644 --- a/src/components/sprite-selector.js +++ b/src/components/sprite-selector.js @@ -6,6 +6,9 @@ class SpriteSelectorComponent extends React.Component { onChange, sprites, value, + openNewSprite, + openNewCostume, + openNewBackdrop, ...props } = this.props; return ( @@ -29,9 +32,9 @@ class SpriteSelectorComponent extends React.Component { ))}

- - - + + +

); diff --git a/src/containers/backdrop-library.js b/src/containers/backdrop-library.js index c57d0a367..2c36868ad 100644 --- a/src/containers/backdrop-library.js +++ b/src/containers/backdrop-library.js @@ -36,7 +36,8 @@ class BackdropLibrary extends React.Component { visible={this.props.visible} data={this.state.backdropData} onRequestClose={this.props.onRequestClose} - onItemSelected={this.selectItem.bind(this)} />; + onItemSelected={this.selectItem.bind(this)} + />; } } diff --git a/src/containers/costume-library.js b/src/containers/costume-library.js index 1e1d849ab..f33b97b0d 100644 --- a/src/containers/costume-library.js +++ b/src/containers/costume-library.js @@ -36,7 +36,8 @@ class CostumeLibrary extends React.Component { visible={this.props.visible} data={this.state.costumeData} onRequestClose={this.props.onRequestClose} - onItemSelected={this.selectItem.bind(this)} />; + onItemSelected={this.selectItem.bind(this)} + />; } } diff --git a/src/containers/gui.js b/src/containers/gui.js index 0ae3e8483..2a3c56bb2 100644 --- a/src/containers/gui.js +++ b/src/containers/gui.js @@ -1,3 +1,4 @@ +const bindAll = require('lodash.bindall'); const defaultsDeep = require('lodash.defaultsdeep'); const React = require('react'); const VM = require('scratch-vm'); @@ -19,6 +20,7 @@ const BackdropLibrary = require('./backdrop-library'); class GUI extends React.Component { constructor (props) { super(props); + bindAll(this, ['closeModal']); this.vmManager = new VMManager(this.props.vm); this.mediaLibrary = new MediaLibrary(); this.state = {currentModal: null}; @@ -45,10 +47,13 @@ class GUI extends React.Component { } render () { let { + backdropLibraryProps, basePath, blocksProps, + costumeLibraryProps, greenFlagProps, projectData, // eslint-disable-line no-unused-vars + spriteLibraryProps, spriteSelectorProps, stageProps, stopAllProps, @@ -60,6 +65,21 @@ class GUI extends React.Component { media: basePath + 'static/blocks-media/' } }); + spriteLibraryProps = { + mediaLibrary: this.mediaLibrary, + onRequestClose: this.closeModal, + visible: this.state.currentModal == 'sprite-library' + }; + costumeLibraryProps = { + mediaLibrary: this.mediaLibrary, + onRequestClose: this.closeModal, + visible: this.state.currentModal == 'costume-library' + }; + backdropLibraryProps = { + mediaLibrary: this.mediaLibrary, + onRequestClose: this.closeModal, + visible: this.state.currentModal == 'backdrop-library' + }; if (this.props.children) { return ( @@ -74,24 +94,9 @@ class GUI extends React.Component { - - - + + + ); } @@ -99,11 +104,14 @@ class GUI extends React.Component { } GUI.propTypes = { + backdropLibraryProps: React.PropTypes.object, basePath: React.PropTypes.string, blocksProps: React.PropTypes.object, + costumeLibraryProps: React.PropTypes.object, children: React.PropTypes.node, greenFlagProps: React.PropTypes.object, projectData: React.PropTypes.string, + spriteLibraryProps: React.PropTypes.object, spriteSelectorProps: React.PropTypes.object, stageProps: React.PropTypes.object, stopAllProps: React.PropTypes.object, diff --git a/src/containers/sprite-library.js b/src/containers/sprite-library.js index 99efc2386..9de69a367 100644 --- a/src/containers/sprite-library.js +++ b/src/containers/sprite-library.js @@ -47,7 +47,8 @@ class SpriteLibrary extends React.Component { data={libraryData} mediaLibrary={this.props.mediaLibrary} onRequestClose={this.props.onRequestClose} - onItemSelected={this.selectItem.bind(this)} />; + onItemSelected={this.selectItem.bind(this)} + />; } } From 14afd85197acebc7c2b4fe172b75c304c7111408 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:31:48 -0400 Subject: [PATCH 05/11] bindAll throughout --- src/components/library.js | 4 +++- src/containers/backdrop-library.js | 6 ++++-- src/containers/costume-library.js | 6 ++++-- src/containers/sprite-library.js | 8 +++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/library.js b/src/components/library.js index cd0dd8769..78f499947 100644 --- a/src/components/library.js +++ b/src/components/library.js @@ -1,3 +1,4 @@ +const bindAll = require('lodash.bindall'); const React = require('react'); const LibraryItem = require('./library-item'); @@ -6,6 +7,7 @@ const ModalComponent = require('./modal'); class LibraryComponent extends React.Component { constructor (props) { super(props); + bindAll(this, ['onSelect']); this.state = {selectedItem: null}; } onSelect (id) { @@ -28,7 +30,7 @@ class LibraryComponent extends React.Component { iconURL={scratchURL} key={'item_' + itemId} selected={this.state.selectedItem == itemId} - onSelect={this.onSelect.bind(this)} + onSelect={this.onSelect} id={id} />; }); diff --git a/src/containers/backdrop-library.js b/src/containers/backdrop-library.js index 2c36868ad..bcac13b52 100644 --- a/src/containers/backdrop-library.js +++ b/src/containers/backdrop-library.js @@ -1,3 +1,4 @@ +const bindAll = require('lodash.bindall'); const React = require('react'); const VM = require('scratch-vm'); const MediaLibrary = require('../lib/media-library'); @@ -8,11 +9,12 @@ const LibaryComponent = require('../components/library'); class BackdropLibrary extends React.Component { constructor (props) { super(props); + bindAll(this, ['setData', 'selectItem']); this.state = {backdropData: []}; } componentWillReceiveProps (nextProps) { if (nextProps.visible && this.state.backdropData.length === 0) { - this.props.mediaLibrary.getMediaLibrary('backdrop', this.setData.bind(this)); + this.props.mediaLibrary.getMediaLibrary('backdrop', this.setData); } } setData (data) { @@ -36,7 +38,7 @@ class BackdropLibrary extends React.Component { visible={this.props.visible} data={this.state.backdropData} onRequestClose={this.props.onRequestClose} - onItemSelected={this.selectItem.bind(this)} + onItemSelected={this.selectItem} />; } } diff --git a/src/containers/costume-library.js b/src/containers/costume-library.js index f33b97b0d..091a736ff 100644 --- a/src/containers/costume-library.js +++ b/src/containers/costume-library.js @@ -1,3 +1,4 @@ +const bindAll = require('lodash.bindall'); const React = require('react'); const VM = require('scratch-vm'); const MediaLibrary = require('../lib/media-library'); @@ -8,11 +9,12 @@ const LibaryComponent = require('../components/library'); class CostumeLibrary extends React.Component { constructor (props) { super(props); + bindAll(this, ['setData', 'selectItem']); this.state = {costumeData: []}; } componentWillReceiveProps (nextProps) { if (nextProps.visible && this.state.costumeData.length === 0) { - this.props.mediaLibrary.getMediaLibrary('costume', this.setData.bind(this)); + this.props.mediaLibrary.getMediaLibrary('costume', this.setData); } } setData (data) { @@ -36,7 +38,7 @@ class CostumeLibrary extends React.Component { visible={this.props.visible} data={this.state.costumeData} onRequestClose={this.props.onRequestClose} - onItemSelected={this.selectItem.bind(this)} + onItemSelected={this.selectItem} />; } } diff --git a/src/containers/sprite-library.js b/src/containers/sprite-library.js index 9de69a367..a11a709e0 100644 --- a/src/containers/sprite-library.js +++ b/src/containers/sprite-library.js @@ -1,3 +1,4 @@ +const bindAll = require('lodash.bindall'); const React = require('react'); const VM = require('scratch-vm'); const MediaLibrary = require('../lib/media-library'); @@ -7,17 +8,18 @@ const LibaryComponent = require('../components/library'); class SpriteLibrary extends React.Component { constructor (props) { super(props); + bindAll(this, ['setData', 'selectItem', 'setSpriteData']); this.state = {data: [], spriteData: {}}; } componentWillReceiveProps (nextProps) { if (nextProps.visible && this.state.data.length === 0) { - this.props.mediaLibrary.getMediaLibrary('sprite', this.setData.bind(this)); + this.props.mediaLibrary.getMediaLibrary('sprite', this.setData); } } setData (data) { this.setState({data: data}); for (let sprite of data) { - this.props.mediaLibrary.getSprite(sprite.md5, this.setSpriteData.bind(this)); + this.props.mediaLibrary.getSprite(sprite.md5, this.setSpriteData); } } setSpriteData (md5, data) { @@ -47,7 +49,7 @@ class SpriteLibrary extends React.Component { data={libraryData} mediaLibrary={this.props.mediaLibrary} onRequestClose={this.props.onRequestClose} - onItemSelected={this.selectItem.bind(this)} + onItemSelected={this.selectItem} />; } } From 7c5cecb67fe95e94a16bab16985e8c75a2ec2bd1 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:32:37 -0400 Subject: [PATCH 06/11] Return the library item object --- src/containers/sprite-library.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/containers/sprite-library.js b/src/containers/sprite-library.js index a11a709e0..602fb1776 100644 --- a/src/containers/sprite-library.js +++ b/src/containers/sprite-library.js @@ -32,16 +32,13 @@ class SpriteLibrary extends React.Component { this.props.vm.addSprite2(spriteData); } render () { - let libraryData = []; - Object.keys(this.state.spriteData).map((libraryKey) => { + let libraryData = Object.keys(this.state.spriteData).map((libraryKey) => { let libraryItem = this.state.spriteData[libraryKey]; - libraryData.push( - { - name: libraryItem.objName, - md5: libraryItem.costumes[0].baseLayerMD5, - json: libraryKey - } - ); + return { + name: libraryItem.objName, + md5: libraryItem.costumes[0].baseLayerMD5, + json: libraryKey + }; }); return Date: Thu, 20 Oct 2016 13:34:06 -0400 Subject: [PATCH 07/11] Add defaults for library props --- src/containers/gui.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/containers/gui.js b/src/containers/gui.js index 2a3c56bb2..1f350785c 100644 --- a/src/containers/gui.js +++ b/src/containers/gui.js @@ -119,10 +119,13 @@ GUI.propTypes = { }; GUI.defaultProps = { + backdropLibraryProps: {}, basePath: '/', blocksProps: {}, + costumeLibraryProps: {}, greenFlagProps: {}, spriteSelectorProps: {}, + spriteLibraryProps: {}, stageProps: {}, stopAllProps: {}, vm: new VM() From bd22602aa7aadaa545605597f142a537a96ca743 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:41:14 -0400 Subject: [PATCH 08/11] Fix the props situation --- src/components/library.js | 2 +- src/containers/gui.js | 17 +++++++++++------ src/containers/sprite-selector.js | 9 ++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/library.js b/src/components/library.js index 78f499947..203c7aa5f 100644 --- a/src/components/library.js +++ b/src/components/library.js @@ -20,7 +20,7 @@ class LibraryComponent extends React.Component { } render () { let itemId = 0; - let gridItems = this.props.data.map(function (dataItem) { + let gridItems = this.props.data.map((dataItem) => { let id = itemId; itemId++; const scratchURL = (dataItem.md5) ? 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + diff --git a/src/containers/gui.js b/src/containers/gui.js index 1f350785c..0ab742034 100644 --- a/src/containers/gui.js +++ b/src/containers/gui.js @@ -65,21 +65,26 @@ class GUI extends React.Component { media: basePath + 'static/blocks-media/' } }); - spriteLibraryProps = { + spriteSelectorProps = defaultsDeep({}, spriteSelectorProps, { + openNewBackdrop: () => this.openModal('backdrop-library'), + openNewCostume: () => this.openModal('costume-library'), + openNewSprite: () => this.openModal('sprite-library') + }); + spriteLibraryProps = defaultsDeep({}, spriteLibraryProps, { mediaLibrary: this.mediaLibrary, onRequestClose: this.closeModal, visible: this.state.currentModal == 'sprite-library' - }; - costumeLibraryProps = { + }); + costumeLibraryProps = defaultsDeep({}, costumeLibraryProps, { mediaLibrary: this.mediaLibrary, onRequestClose: this.closeModal, visible: this.state.currentModal == 'costume-library' - }; - backdropLibraryProps = { + }); + backdropLibraryProps = defaultsDeep({}, backdropLibraryProps, { mediaLibrary: this.mediaLibrary, onRequestClose: this.closeModal, visible: this.state.currentModal == 'backdrop-library' - }; + }); if (this.props.children) { return ( diff --git a/src/containers/sprite-selector.js b/src/containers/sprite-selector.js index add75b707..c30ac7137 100644 --- a/src/containers/sprite-selector.js +++ b/src/containers/sprite-selector.js @@ -25,15 +25,18 @@ class SpriteSelector extends React.Component { render () { const { vm, // eslint-disable-line no-unused-vars + openNewSprite, + openNewCostume, + openNewBackdrop, ...props } = this.props; return ( ( { id: target[0], From 9cc4dcf48fb2f5e2ca6f3fc159e9c8017012d2b1 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:44:16 -0400 Subject: [PATCH 09/11] Fix id for selected item, return when CostumeCanvas draw fails --- src/components/costume-canvas.js | 3 +++ src/components/library.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/costume-canvas.js b/src/components/costume-canvas.js index ac0646756..368d52a9b 100644 --- a/src/components/costume-canvas.js +++ b/src/components/costume-canvas.js @@ -25,6 +25,9 @@ class CostumeCanvas extends React.Component { } } draw () { + if (!this.refs.costumeCanvas) { + return; + } // Draw the costume to the rendered canvas. const img = this.img; const context = this.refs.costumeCanvas.getContext('2d'); diff --git a/src/components/library.js b/src/components/library.js index 203c7aa5f..f301329ce 100644 --- a/src/components/library.js +++ b/src/components/library.js @@ -28,8 +28,8 @@ class LibraryComponent extends React.Component { return ; }); From bd900a937c7029afeb00e392d1092b9a65a34ae0 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:46:52 -0400 Subject: [PATCH 10/11] Fix merge duplication of package.json --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 3259968b5..e892dc02d 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,6 @@ "opt-cli": "1.5.1", "react": "15.x.x", "react-dom": "15.x.x", - "scratch-blocks": "^0.1.0-prerelease", - "scratch-render": "^0.1.0-prerelease", - "scratch-vm": "^0.1.0-prerelease", "react-modal": "1.5.2", "scratch-blocks": "latest", "scratch-render": "latest", From 7fcf71ddeab27d0223c120e9d92d61eba4871029 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 20 Oct 2016 13:47:43 -0400 Subject: [PATCH 11/11] NL for library item --- src/components/library.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/library.js b/src/components/library.js index f301329ce..f23f4b826 100644 --- a/src/components/library.js +++ b/src/components/library.js @@ -31,7 +31,8 @@ class LibraryComponent extends React.Component { key={'item_' + id} selected={this.state.selectedItem == id} onSelect={this.onSelect} - id={id} />; + id={id} + />; }); const scrollGridStyle = {