From d7d1ad9ea1089928ae4dfb370d6975d366a2595f Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 4 May 2017 13:09:17 -0400 Subject: [PATCH 01/11] Move VM into a reducer Otherwise it's very annoying to get access to the storage engine to load asset data. --- src/lib/vm-listener-hoc.jsx | 11 ++++------- src/reducers/gui.js | 3 ++- src/reducers/vm.js | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 src/reducers/vm.js diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index 8c80ae5d9..58ea1ca71 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -3,8 +3,6 @@ const PropTypes = require('prop-types'); const React = require('react'); const VM = require('scratch-vm'); -const Storage = require('./storage'); - const {connect} = require('react-redux'); const targets = require('../reducers/targets'); @@ -92,13 +90,12 @@ const vmListenerHOC = function (WrappedComponent) { onTargetsUpdate: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired }; - const defaultVM = new VM('vm-listener-hoc'); - defaultVM.attachStorage(new Storage()); VMListener.defaultProps = { - attachKeyboardEvents: true, - vm: defaultVM + attachKeyboardEvents: true }; - const mapStateToProps = () => ({}); + const mapStateToProps = state => ({ + vm: state.vm + }); const mapDispatchToProps = dispatch => ({ onTargetsUpdate: data => { dispatch(targets.updateEditingTarget(data.editingTarget)); diff --git a/src/reducers/gui.js b/src/reducers/gui.js index bde1a2f3f..9b1cf6067 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -2,6 +2,7 @@ const {combineReducers} = require('redux'); module.exports = combineReducers({ modals: require('./modals'), + monitors: require('./monitors'), targets: require('./targets'), - monitors: require('./monitors') + vm: require('./vm') }); diff --git a/src/reducers/vm.js b/src/reducers/vm.js new file mode 100644 index 000000000..1fd0eb4a2 --- /dev/null +++ b/src/reducers/vm.js @@ -0,0 +1,24 @@ +const VM = require('scratch-vm'); +const Storage = require('../lib/storage'); + +const SET_VM = 'scratch-gui/vm/SET_VM'; +const defaultVM = new VM(); +defaultVM.attachStorage(new Storage()); +const initialState = defaultVM; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + switch (action.type) { + case SET_VM: + return action.vm; + default: + return state; + } +}; +reducer.setVM = function (vm) { + return { + type: SET_VM, + vm: vm + }; +}; +module.exports = reducer; From 84901ebb98222373a8b8e3e0ec8e9a4fd3b7ddc6 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 4 May 2017 13:10:32 -0400 Subject: [PATCH 02/11] Use storage to retrieve costume data urls --- src/components/asset-panel/selector.jsx | 2 +- .../sprite-selector/sprite-selector.jsx | 16 +++++++--------- src/containers/sprite-selector-item.jsx | 11 ++++++++++- src/containers/stage-selector.jsx | 13 ++++++++++++- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index d0d076bab..b2c278bbe 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -27,8 +27,8 @@ const Selector = props => { {items.map((item, index) => ( sprites[id1].order - sprites[id2].order) - .map(id => ( + .map(id => sprites[id]) + .map(sprite => ( diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index a9182996e..dacac38e1 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -2,6 +2,8 @@ const bindAll = require('lodash.bindall'); const PropTypes = require('prop-types'); const React = require('react'); +const {connect} = require('react-redux'); + const SpriteSelectorItemComponent = require('../components/sprite-selector-item/sprite-selector-item.jsx'); class SpriteSelectorItem extends React.Component { @@ -40,6 +42,7 @@ class SpriteSelectorItem extends React.Component { } SpriteSelectorItem.propTypes = { + assetId: PropTypes.string, costumeURL: PropTypes.string, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), name: PropTypes.string, @@ -48,4 +51,10 @@ SpriteSelectorItem.propTypes = { selected: PropTypes.bool }; -module.exports = SpriteSelectorItem; +const mapStateToProps = (state, {assetId}) => ({ + costumeURL: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI() +}); + +module.exports = connect( + mapStateToProps +)(SpriteSelectorItem); diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx index d37a4437c..9b4b83d0d 100644 --- a/src/containers/stage-selector.jsx +++ b/src/containers/stage-selector.jsx @@ -2,6 +2,8 @@ const bindAll = require('lodash.bindall'); const PropTypes = require('prop-types'); const React = require('react'); +const {connect} = require('react-redux'); + const StageSelectorComponent = require('../components/stage-selector/stage-selector.jsx'); class StageSelector extends React.Component { @@ -18,6 +20,7 @@ class StageSelector extends React.Component { render () { const { /* eslint-disable no-unused-vars */ + assetId, id, onSelect, /* eslint-enable no-unused-vars */ @@ -36,4 +39,12 @@ StageSelector.propTypes = { id: PropTypes.string, onSelect: PropTypes.func }; -module.exports = StageSelector; + +const mapStateToProps = (state, {assetId}) => ({ + url: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI() +}); + +module.exports = connect( + mapStateToProps, + () => ({}) // omit dispatch prop +)(StageSelector); From 45a52b77875061e2f658e489fa58ba600415e707 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 4 May 2017 13:12:40 -0400 Subject: [PATCH 03/11] Remove shouldComponentUpdate from TargetPane It doesn't help anything. Doing a deep compare is unnecessary because the underlying components will do the necessary comparison themselves. Simply seeing if the sprite list has changed at all is enough. --- src/components/target-pane/target-pane.jsx | 209 ++++++++++----------- 1 file changed, 97 insertions(+), 112 deletions(-) diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx index 5d8fc1915..c779f2747 100644 --- a/src/components/target-pane/target-pane.jsx +++ b/src/components/target-pane/target-pane.jsx @@ -1,5 +1,3 @@ -const isEqual = require('lodash.isequal'); -const omit = require('lodash.omit'); const classNames = require('classnames'); const PropTypes = require('prop-types'); const React = require('react'); @@ -23,119 +21,106 @@ const addIcon = require('./icon--add.svg'); * @param {object} props Props for the component * @returns {React.Component} rendered component */ -class TargetPane extends React.Component { - shouldComponentUpdate (nextProps) { - return ( - // Do a normal shallow compare on all props except sprites - Object.keys(omit(nextProps, ['sprites'])) - .reduce((all, k) => all || nextProps[k] !== this.props[k], false) || - // Deep compare on sprites object - !isEqual(this.props.sprites, nextProps.sprites) - ); - } - render () { - const { - editingTarget, - backdropLibraryVisible, - costumeLibraryVisible, - soundLibraryVisible, - spriteLibraryVisible, - onChangeSpriteDirection, - onChangeSpriteName, - onChangeSpriteRotationStyle, - onChangeSpriteVisibility, - onChangeSpriteX, - onChangeSpriteY, - onDeleteSprite, - onNewSpriteClick, - onNewBackdropClick, - onRequestCloseBackdropLibrary, - onRequestCloseCostumeLibrary, - onRequestCloseSoundLibrary, - onRequestCloseSpriteLibrary, - onSelectSprite, - stage, - sprites, - vm, - ...componentProps - } = this.props; - return ( - +const TargetPane = ({ + editingTarget, + backdropLibraryVisible, + costumeLibraryVisible, + soundLibraryVisible, + spriteLibraryVisible, + onChangeSpriteDirection, + onChangeSpriteName, + onChangeSpriteRotationStyle, + onChangeSpriteVisibility, + onChangeSpriteX, + onChangeSpriteY, + onDeleteSprite, + onNewSpriteClick, + onNewBackdropClick, + onRequestCloseBackdropLibrary, + onRequestCloseCostumeLibrary, + onRequestCloseSoundLibrary, + onRequestCloseSpriteLibrary, + onSelectSprite, + stage, + sprites, + vm, + ...componentProps +}) => ( + - + + {stage.id && } + + + + + + + + + + - - {stage.id && } - - - - - - - - - - - - - ); - } -} + + +); + const spriteShape = PropTypes.shape({ costume: PropTypes.shape({ url: PropTypes.string, From 32f4355455c5e8ce0a23f050149a5577696f6b0f Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 4 May 2017 15:42:15 -0400 Subject: [PATCH 04/11] Fix sound tab images --- src/components/asset-panel/selector.jsx | 1 + src/containers/sprite-selector-item.jsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index b2c278bbe..0ddc2bb51 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -29,6 +29,7 @@ const Selector = props => { ({ - costumeURL: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI() +const mapStateToProps = (state, {assetId, costumeURL}) => ({ + costumeURL: costumeURL || (assetId && state.vm.runtime.storage.get(assetId).encodeDataURI()) }); module.exports = connect( From 520e11c0cf630a3b43368a209e86a75e448a9b18 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 4 May 2017 17:41:35 -0400 Subject: [PATCH 05/11] Add Save and Load buttons Use the de/serialization features of the VM to allow downloading and loading project JSON --- src/components/button/button.css | 3 ++ src/components/button/button.jsx | 30 +++++++++++ src/components/load-button/load-button.css | 3 ++ src/components/load-button/load-button.jsx | 36 +++++++++++++ src/components/menu-bar/menu-bar.jsx | 6 ++- src/containers/load-button.jsx | 55 +++++++++++++++++++ src/containers/save-button.jsx | 62 ++++++++++++++++++++++ 7 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/components/button/button.css create mode 100644 src/components/button/button.jsx create mode 100644 src/components/load-button/load-button.css create mode 100644 src/components/load-button/load-button.jsx create mode 100644 src/containers/load-button.jsx create mode 100644 src/containers/save-button.jsx diff --git a/src/components/button/button.css b/src/components/button/button.css new file mode 100644 index 000000000..44243650f --- /dev/null +++ b/src/components/button/button.css @@ -0,0 +1,3 @@ +.button { + cursor: pointer; +} diff --git a/src/components/button/button.jsx b/src/components/button/button.jsx new file mode 100644 index 000000000..34b485154 --- /dev/null +++ b/src/components/button/button.jsx @@ -0,0 +1,30 @@ +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); + +const styles = require('./button.css'); + +const ButtonComponent = ({ + className, + onClick, + children, + ...props +}) => ( + + {children} + +); + +ButtonComponent.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + onClick: PropTypes.func.isRequired +}; +module.exports = ButtonComponent; diff --git a/src/components/load-button/load-button.css b/src/components/load-button/load-button.css new file mode 100644 index 000000000..527718028 --- /dev/null +++ b/src/components/load-button/load-button.css @@ -0,0 +1,3 @@ +.file-input { + display: none; +} diff --git a/src/components/load-button/load-button.jsx b/src/components/load-button/load-button.jsx new file mode 100644 index 000000000..29b2962e5 --- /dev/null +++ b/src/components/load-button/load-button.jsx @@ -0,0 +1,36 @@ +const PropTypes = require('prop-types'); +const React = require('react'); + +const ButtonComponent = require('../button/button.jsx'); + +const styles = require('./load-button.css'); + +const LoadButtonComponent = ({ + inputRef, + onChange, + onClick, + title, + ...props +}) => ( + + {title} + + +); + +LoadButtonComponent.propTypes = { + className: PropTypes.string, + inputRef: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + title: PropTypes.string +}; +LoadButtonComponent.defaultProps = { + title: 'Load' +}; +module.exports = LoadButtonComponent; diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index b34a2e190..4397b5002 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -2,6 +2,9 @@ const classNames = require('classnames'); const React = require('react'); const Box = require('../box/box.jsx'); +const LoadButton = require('../../containers/load-button.jsx'); +const SaveButton = require('../../containers/save-button.jsx'); + const styles = require('./menu-bar.css'); const scratchLogo = require('./scratch-logo.svg'); @@ -18,7 +21,8 @@ const MenuBar = function MenuBar () { src={scratchLogo} /> -
Animation Playtest Prototype
+ +
); }; diff --git a/src/containers/load-button.jsx b/src/containers/load-button.jsx new file mode 100644 index 000000000..dcbd4fe01 --- /dev/null +++ b/src/containers/load-button.jsx @@ -0,0 +1,55 @@ +const bindAll = require('lodash.bindall'); +const PropTypes = require('prop-types'); +const React = require('react'); +const {connect} = require('react-redux'); + +const LoadButtonComponent = require('../components/load-button/load-button.jsx'); + +class LoadButton extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'setFileInput', + 'handleChange', + 'handleClick' + ]); + } + handleChange (e) { + const reader = new FileReader(); + reader.onload = () => this.props.loadProject(reader.result); + reader.readAsText(e.target.files[0]); + } + handleClick () { + this.fileInput.click(); + } + setFileInput (input) { + this.fileInput = input; + } + render () { + const { + loadProject, // eslint-disable-line no-unused-vars + ...props + } = this.props; + return ( + + ); + } +} + +LoadButton.propTypes = { + loadProject: PropTypes.func.isRequired +}; + +const mapStateToProps = state => ({ + loadProject: state.vm.fromJSON.bind(state.vm) +}); + +module.exports = connect( + mapStateToProps, + () => ({}) // omit dispatch prop +)(LoadButton); diff --git a/src/containers/save-button.jsx b/src/containers/save-button.jsx new file mode 100644 index 000000000..f5e7ab018 --- /dev/null +++ b/src/containers/save-button.jsx @@ -0,0 +1,62 @@ +const bindAll = require('lodash.bindall'); +const PropTypes = require('prop-types'); +const React = require('react'); +const {connect} = require('react-redux'); + +const ButtonComponent = require('../components/button/button.jsx'); + +class SaveButton extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleClick' + ]); + } + handleClick () { + const json = this.props.saveProjectSb3(); + + // Download project data into a file - create link element, + // simulate click on it, and then remove it. + const saveLink = document.createElement('a'); + document.body.appendChild(saveLink); + + const data = new Blob([json], {type: 'text'}); + const url = window.URL.createObjectURL(data); + saveLink.href = url; + + // File name: project-DATE-TIME + const date = new Date(); + const timestamp = `${date.toLocaleDateString()}-${date.toLocaleTimeString()}`; + saveLink.download = `project-${timestamp}.json`; + saveLink.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(saveLink); + } + render () { + const { + saveProjectSb3, // eslint-disable-line no-unused-vars + ...props + } = this.props; + return ( + + Save + + ); + } +} + +SaveButton.propTypes = { + saveProjectSb3: PropTypes.func.isRequired +}; + +const mapStateToProps = state => ({ + saveProjectSb3: state.vm.saveProjectSb3.bind(state.vm) +}); + +module.exports = connect( + mapStateToProps, + () => ({}) // omit dispatch prop +)(SaveButton); From 62f6887ec483583910c7dbd09e644dffc14c0ede Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Fri, 5 May 2017 16:45:51 -0400 Subject: [PATCH 06/11] Allow pointer events to fall through the overlay --- src/components/stage/stage.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css index 0b22fa3c6..4cc5d3092 100644 --- a/src/components/stage/stage.css +++ b/src/components/stage/stage.css @@ -23,4 +23,5 @@ left: 0; width: 100%; height: 100%; + pointer-events: none; } From ca73379a09e956b102217718e8a86856c5492732 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 11 May 2017 15:41:10 -0400 Subject: [PATCH 07/11] Add button role to button component /ht @paulkaplan --- src/components/button/button.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/button/button.jsx b/src/components/button/button.jsx index 34b485154..dbc852395 100644 --- a/src/components/button/button.jsx +++ b/src/components/button/button.jsx @@ -15,6 +15,7 @@ const ButtonComponent = ({ styles.button, className )} + role="button" onClick={onClick} {...props} > From a0971dd8c013c0feb6fa636c85cfd71c5fb51eca Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 11 May 2017 16:10:31 -0400 Subject: [PATCH 08/11] Use appropriate minimum version of scratch-storage --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7765b8dd7..7d221cc7c 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "scratch-audio": "^0.1.0-prerelease.0", "scratch-blocks": "^0.1.0-prerelease.0", "scratch-render": "^0.1.0-prerelease.0", - "scratch-storage": "^0.0.1-prerelease.0", + "scratch-storage": "^0.1.0", "scratch-vm": "^0.1.0-prerelease.0", "style-loader": "0.16.1", "svg-to-image": "1.1.3", From ae529ea20d6ccfbc1295f62cbc8d41ef4cee4f58 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Fri, 12 May 2017 11:45:06 -0400 Subject: [PATCH 09/11] Only listen to targets update This reduces the complexity of the reducer, as well as how frequently it's run. The VM will emit a targets update after a step if any target info has changed. --- src/containers/costume-tab.jsx | 2 +- src/lib/vm-listener-hoc.jsx | 4 ---- src/reducers/targets.js | 36 ++-------------------------------- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 2c6c04a7e..24f54bc78 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -38,7 +38,7 @@ class CostumeTab extends React.Component { editingTarget.sprite.costumes = editingTarget.sprite.costumes .slice(0, costumeIndex) .concat(editingTarget.sprite.costumes.slice(costumeIndex + 1)); - this.props.vm.runtime.spriteInfoReport(editingTarget); + this.props.vm.runtime.requestTargetsUpdate(editingTarget); // @todo not sure if this is getting redrawn correctly this.props.vm.runtime.requestRedraw(); diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index 58ea1ca71..d2a7b0ca5 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -27,7 +27,6 @@ const vmListenerHOC = function (WrappedComponent) { // If the wrapped component uses the vm in componentDidMount, then // we need to start listening before mounting the wrapped component. this.props.vm.on('targetsUpdate', this.props.onTargetsUpdate); - this.props.vm.on('SPRITE_INFO_REPORT', this.props.onSpriteInfoReport); } componentDidMount () { if (this.props.attachKeyboardEvents) { @@ -100,9 +99,6 @@ const vmListenerHOC = function (WrappedComponent) { onTargetsUpdate: data => { dispatch(targets.updateEditingTarget(data.editingTarget)); dispatch(targets.updateTargets(data.targetList)); - }, - onSpriteInfoReport: spriteInfo => { - dispatch(targets.updateTarget(spriteInfo)); } }); return connect( diff --git a/src/reducers/targets.js b/src/reducers/targets.js index c2f6219ac..c7a4f4857 100644 --- a/src/reducers/targets.js +++ b/src/reducers/targets.js @@ -1,8 +1,5 @@ -const defaultsDeep = require('lodash.defaultsdeep'); - const UPDATE_EDITING_TARGET = 'scratch-gui/targets/UPDATE_EDITING_TARGET'; const UPDATE_TARGET_LIST = 'scratch-gui/targets/UPDATE_TARGET_LIST'; -const UPDATE_TARGET = 'scratch/targets/UPDATE_TARGET'; const initialState = { sprites: {}, @@ -12,39 +9,19 @@ const initialState = { const reducer = function (state, action) { if (typeof state === 'undefined') state = initialState; switch (action.type) { - case UPDATE_TARGET: - if (action.target.id === state.stage.id) { - return Object.assign({}, state, { - stage: Object.assign({}, state.stage, action.target) - }); - } - return Object.assign({}, state, { - sprites: defaultsDeep( - {[action.target.id]: action.target}, - state.sprites - ) - }); case UPDATE_TARGET_LIST: return Object.assign({}, state, { sprites: action.targets .filter(target => !target.isStage) .reduce( - (targets, target, listId) => defaultsDeep( + (targets, target, listId) => Object.assign( {[target.id]: {order: listId, ...target}}, - {[target.id]: state.sprites[target.id]}, targets ), {} ), stage: action.targets - .filter(target => target.isStage) - .reduce( - (stage, target) => { - if (target.id !== stage.id) return target; - return defaultsDeep(target, stage); - }, - state.stage - ) + .filter(target => target.isStage)[0] || {} }); case UPDATE_EDITING_TARGET: return Object.assign({}, state, {editingTarget: action.target}); @@ -52,15 +29,6 @@ const reducer = function (state, action) { return state; } }; -reducer.updateTarget = function (target) { - return { - type: UPDATE_TARGET, - target: target, - meta: { - throttle: 30 - } - }; -}; reducer.updateTargets = function (targetList) { return { type: UPDATE_TARGET_LIST, From f5ee7a4f2329e9dfbb4fedf9cc43fd16718cc5b9 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Fri, 12 May 2017 17:42:37 -0400 Subject: [PATCH 10/11] Fix argument order for Object.assign Thanks @paulkaplan --- src/reducers/targets.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reducers/targets.js b/src/reducers/targets.js index c7a4f4857..0ed67cf2d 100644 --- a/src/reducers/targets.js +++ b/src/reducers/targets.js @@ -15,8 +15,8 @@ const reducer = function (state, action) { .filter(target => !target.isStage) .reduce( (targets, target, listId) => Object.assign( - {[target.id]: {order: listId, ...target}}, - targets + targets, + {[target.id]: {order: listId, ...target}} ), {} ), From 0c42c1fc8e22445f83cb41e9df82dab33026cdab Mon Sep 17 00:00:00 2001 From: DD Liu Date: Wed, 17 May 2017 11:50:02 -0400 Subject: [PATCH 11/11] fix squashed blocks in flyout when adding a sprite while on another tab --- src/components/gui/gui.jsx | 14 ++++++-------- src/containers/blocks.jsx | 22 +++++++++++++++++++--- src/containers/gui.jsx | 13 +++++++++++++ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 91ed77ec2..9bf21c40d 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -19,6 +19,8 @@ const GUIComponent = props => { basePath, children, vm, + onTabSelect, + tabIndex, ...componentProps } = props; if (children) { @@ -29,13 +31,6 @@ const GUIComponent = props => { ); } - // @todo hack to resize blockly manually in case resize happened while hidden - const handleTabSelect = tabIndex => { - if (tabIndex === 0) { - setTimeout(() => window.dispatchEvent(new Event('resize'))); - } - }; - return ( { Scripts @@ -59,6 +54,7 @@ const GUIComponent = props => { { GUIComponent.propTypes = { basePath: PropTypes.string, children: PropTypes.node, + onTabSelect: PropTypes.func, + tabIndex: PropTypes.number, vm: PropTypes.instanceOf(VM).isRequired }; GUIComponent.defaultProps = { diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index ecd816204..58d5cb2e1 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -52,7 +52,21 @@ class Blocks extends React.Component { this.attachVM(); } shouldComponentUpdate (nextProps, nextState) { - return this.state.prompt !== nextState.prompt; + return this.state.prompt !== nextState.prompt || this.props.isVisible !== nextProps.isVisible; + } + componentDidUpdate (prevProps) { + if (this.props.isVisible === prevProps.isVisible) { + return; + } + + // @todo hack to resize blockly manually in case resize happened while hidden + if (this.props.isVisible) { // Scripts tab + window.dispatchEvent(new Event('resize')); + this.workspace.setVisible(true); + this.workspace.toolbox_.refreshSelection(); + } else { + this.workspace.setVisible(false); + } } componentWillUnmount () { this.detachVM(); @@ -146,6 +160,7 @@ class Blocks extends React.Component { const { options, // eslint-disable-line no-unused-vars vm, // eslint-disable-line no-unused-vars + isVisible, // eslint-disable-line no-unused-vars ...props } = this.props; return ( @@ -169,11 +184,12 @@ class Blocks extends React.Component { } Blocks.propTypes = { + isVisible: PropTypes.bool.isRequired, options: PropTypes.shape({ media: PropTypes.string, zoom: PropTypes.shape({ - controls: PropTypes.boolean, - wheel: PropTypes.boolean, + controls: PropTypes.bool, + wheel: PropTypes.bool, startScale: PropTypes.number }), colours: PropTypes.shape({ diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index dec5c6c6d..fbc11d7da 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -1,12 +1,20 @@ const PropTypes = require('prop-types'); const React = require('react'); const VM = require('scratch-vm'); +const bindAll = require('lodash.bindall'); const vmListenerHOC = require('../lib/vm-listener-hoc.jsx'); const GUIComponent = require('../components/gui/gui.jsx'); class GUI extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleTabSelect' + ]); + this.state = {tabIndex: 0}; + } componentDidMount () { this.props.vm.loadProject(this.props.projectData); this.props.vm.setCompatibilityMode(true); @@ -20,6 +28,9 @@ class GUI extends React.Component { componentWillUnmount () { this.props.vm.stopAll(); } + handleTabSelect (tabIndex) { + this.setState({tabIndex}); + } render () { const { projectData, // eslint-disable-line no-unused-vars @@ -28,7 +39,9 @@ class GUI extends React.Component { } = this.props; return ( );