From 823ecc667e58116a83648342e9d879e8978e46b7 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 25 Oct 2017 09:36:32 -0400 Subject: [PATCH 01/38] Use new batched xml loading API --- src/containers/blocks.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 345d21e26..ce5dd7f0f 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -172,13 +172,11 @@ class Blocks extends React.Component { this.onWorkspaceMetricsChange(); } - this.ScratchBlocks.Events.disable(); - this.workspace.clear(); - + // Remove and reattach the workspace listener (but allow flyout events) + this.workspace.removeChangeListener(this.props.vm.blockListener); const dom = this.ScratchBlocks.Xml.textToDom(data.xml); - this.ScratchBlocks.Xml.domToWorkspace(dom, this.workspace); - this.ScratchBlocks.Events.enable(); - this.workspace.toolbox_.refreshSelection(); + this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace); + this.workspace.addChangeListener(this.props.vm.blockListener); if (this.props.vm.editingTarget && this.state.workspaceMetrics[this.props.vm.editingTarget.id]) { const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id]; From 86d305929a15afd2f05d39a35f2733d2c05052b4 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 24 Oct 2017 12:22:24 -0400 Subject: [PATCH 02/38] Remove an extra call to refreshSelection. The VM refreshWorkspace will eventually lead back to onWorkspaceUpdate, which will call refreshSelection itself. --- src/containers/blocks.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index ce5dd7f0f..d9c8c5221 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -87,7 +87,6 @@ class Blocks extends React.Component { this.workspace.setVisible(true); this.props.vm.refreshWorkspace(); window.dispatchEvent(new Event('resize')); - this.workspace.toolbox_.refreshSelection(); } else { this.workspace.setVisible(false); } From 028b85817953156b9e40e923ba2f08f94ec46768 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 25 Oct 2017 14:52:04 -0400 Subject: [PATCH 03/38] Give fixed IDs to all monitored blocks --- src/lib/make-toolbox-xml.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 257413c2a..39bcdadca 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -186,9 +186,9 @@ const motion = ` - - - + + + `; @@ -288,10 +288,10 @@ const looks = ` - - - - + + + + `; @@ -386,7 +386,7 @@ const sound = ` - + `; @@ -485,10 +485,10 @@ const sensing = ` - - + + - + @@ -496,7 +496,7 @@ const sensing = ` - + From a97b0d178bbf04891e75f828443124257cc08c73 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 25 Oct 2017 15:42:29 -0400 Subject: [PATCH 04/38] Add ID for volume block --- src/lib/make-toolbox-xml.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 39bcdadca..3cc909094 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -371,7 +371,7 @@ const sound = ` - + From c5fe55de7fddd68fb5110ace6980c24092b1d932 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 26 Oct 2017 09:26:04 -0400 Subject: [PATCH 05/38] Fix corner radius bug --- src/components/stage/stage.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css index f13554a02..9c784e956 100644 --- a/src/components/stage/stage.css +++ b/src/components/stage/stage.css @@ -1,4 +1,5 @@ @import "../../css/units.css"; +@import "../../css/colors.css"; .stage { /* @@ -7,8 +8,6 @@ */ display: block; - border-radius: $space; - /* @todo: This is for overriding the value being set somewhere. Where is it being set? */ background-color: transparent; } @@ -31,6 +30,10 @@ .stage-wrapper { position: relative; + border-radius: $space; + border: 1px solid $ui-pane-border; + /* Keep the canvas inside the border radius */ + overflow: hidden; } .monitor-wrapper, .color-picker-wrapper { From 21483d456f6a9f01b56ca25dd957e8c7337e005a Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Oct 2017 13:57:27 -0400 Subject: [PATCH 06/38] =?UTF-8?q?Remove=20=E2=80=9Ctop=E2=80=9D=20category?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/make-toolbox-xml.js | 75 ------------------------------------- 1 file changed, 75 deletions(-) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 3cc909094..8685b753c 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -1,79 +1,5 @@ const separator = ''; -const top = ` - - - - - - - - - 10 - - - - - - - 15 - - - - - - - - - - - - - Hello! - - - - - 2 - - - - - - - 10 - - - - - - - 10 - - - - - - - 1 - - - - - - - 1 - - - - - 10 - - - - -`; - const motion = ` @@ -683,7 +609,6 @@ const makeToolboxXML = function (categoriesXML) { const everything = [ xmlOpen, - top, gap, motion, gap, looks, gap, sound, gap, From 9e0c1710fbf46a03c06798e0c2384d26270355e7 Mon Sep 17 00:00:00 2001 From: KaneShao Date: Tue, 31 Oct 2017 10:38:15 +0800 Subject: [PATCH 07/38] update toolbox before ScratchBlocks inject --- src/containers/blocks.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index d9c8c5221..daeb91e85 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -53,12 +53,9 @@ class Blocks extends React.Component { componentDidMount () { this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; - const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options); + const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options, {toolbox: this.props.toolboxXML}); this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); - // Load the toolbox from the GUI (otherwise we get the scratch-blocks default toolbox) - this.workspace.updateToolbox(this.props.toolboxXML); - // @todo change this when blockly supports UI events addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange); addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange); From 6d0507802f0996e400eb6442acd6cc8832e69722 Mon Sep 17 00:00:00 2001 From: KaneShao Date: Tue, 31 Oct 2017 10:45:15 +0800 Subject: [PATCH 08/38] Give ID to some monitored shadow blocks --- src/lib/make-toolbox-xml.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 8685b753c..59787f486 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -416,15 +416,15 @@ const sensing = ` - + - + - + From c177706a121c8bd0c6c9271eca5c37a294f0a5ed Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 31 Oct 2017 13:34:44 -0400 Subject: [PATCH 09/38] Fix deleting costumes not updating the paint editor --- src/containers/paint-editor-wrapper.jsx | 2 +- webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx index ca033c061..6448f3e9e 100644 --- a/src/containers/paint-editor-wrapper.jsx +++ b/src/containers/paint-editor-wrapper.jsx @@ -55,7 +55,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => { name: costume && costume.name, rotationCenterX: costume && costume.rotationCenterX, rotationCenterY: costume && costume.rotationCenterY, - svgId: editingTarget && `${editingTarget}${selectedCostumeIndex}` + svgId: editingTarget && `${editingTarget}${costume.skinId}` }; }; diff --git a/webpack.config.js b/webpack.config.js index 6c486cc8a..f77833967 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,7 @@ module.exports = { devServer: { contentBase: path.resolve(__dirname, 'build'), host: '0.0.0.0', - port: process.env.PORT || 8601 + port: process.env.PORT || 8607 }, devtool: 'cheap-module-source-map', entry: { From c9119c5e14c6c4bc3050ce44c46d0fa918d8b76e Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 31 Oct 2017 13:39:40 -0400 Subject: [PATCH 10/38] Revert "Fix deleting costumes not updating the paint editor" This reverts commit c177706a121c8bd0c6c9271eca5c37a294f0a5ed. --- src/containers/paint-editor-wrapper.jsx | 2 +- webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx index 6448f3e9e..ca033c061 100644 --- a/src/containers/paint-editor-wrapper.jsx +++ b/src/containers/paint-editor-wrapper.jsx @@ -55,7 +55,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => { name: costume && costume.name, rotationCenterX: costume && costume.rotationCenterX, rotationCenterY: costume && costume.rotationCenterY, - svgId: editingTarget && `${editingTarget}${costume.skinId}` + svgId: editingTarget && `${editingTarget}${selectedCostumeIndex}` }; }; diff --git a/webpack.config.js b/webpack.config.js index f77833967..6c486cc8a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,7 @@ module.exports = { devServer: { contentBase: path.resolve(__dirname, 'build'), host: '0.0.0.0', - port: process.env.PORT || 8607 + port: process.env.PORT || 8601 }, devtool: 'cheap-module-source-map', entry: { From a01654fe5067f8c31ad1a0120f74274829d4b775 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 31 Oct 2017 13:41:54 -0400 Subject: [PATCH 11/38] Use unique costume ID --- src/containers/paint-editor-wrapper.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx index ca033c061..6448f3e9e 100644 --- a/src/containers/paint-editor-wrapper.jsx +++ b/src/containers/paint-editor-wrapper.jsx @@ -55,7 +55,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => { name: costume && costume.name, rotationCenterX: costume && costume.rotationCenterX, rotationCenterY: costume && costume.rotationCenterY, - svgId: editingTarget && `${editingTarget}${selectedCostumeIndex}` + svgId: editingTarget && `${editingTarget}${costume.skinId}` }; }; From 228def180baffc9f430ef1dec0d2b6e46d06a746 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 31 Oct 2017 18:29:04 -0400 Subject: [PATCH 12/38] Shrink extension button --- src/components/gui/gui.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css index bb336c40d..9f9b84743 100644 --- a/src/components/gui/gui.css +++ b/src/components/gui/gui.css @@ -150,7 +150,7 @@ } .extension-button-container { - width: 60px; + width: 3.25rem; height: 3.25rem; position: absolute; bottom: 0; From a4311fd1246179f1875d4bf7b34663a0613acfc6 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 31 Oct 2017 21:52:20 -0400 Subject: [PATCH 13/38] =?UTF-8?q?Don=E2=80=99t=20load=20duplicate=20extens?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/containers/extension-library.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 343d0747d..b0690fcad 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -19,6 +19,9 @@ class ExtensionLibrary extends React.PureComponent { // eslint-disable-next-line no-alert const url = item.extensionURL || prompt('Enter the URL of the extension'); if (url) { + if (this.props.vm.extensionManager.isExtensionLoaded(url)) { + return; + } this.props.vm.extensionManager.loadExtensionURL(url); } } From 9ee5ce497c86fe7e8f42af0f582b6a11aeff9cac Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 1 Nov 2017 12:34:04 -0400 Subject: [PATCH 14/38] Render extension library in blocks container --- src/components/target-pane/target-pane.jsx | 9 --------- src/containers/blocks.jsx | 19 ++++++++++++++++++- src/containers/target-pane.jsx | 7 +------ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx index 9ee7b5f4e..2f147447d 100644 --- a/src/components/target-pane/target-pane.jsx +++ b/src/components/target-pane/target-pane.jsx @@ -7,7 +7,6 @@ import BackdropLibrary from '../../containers/backdrop-library.jsx'; import CostumeLibrary from '../../containers/costume-library.jsx'; import SoundLibrary from '../../containers/sound-library.jsx'; import SpriteLibrary from '../../containers/sprite-library.jsx'; -import ExtensionLibrary from '../../containers/extension-library.jsx'; import SpriteSelectorComponent from '../sprite-selector/sprite-selector.jsx'; import StageSelector from '../../containers/stage-selector.jsx'; @@ -22,7 +21,6 @@ import styles from './target-pane.css'; */ const TargetPane = ({ editingTarget, - extensionLibraryVisible, backdropLibraryVisible, costumeLibraryVisible, soundLibraryVisible, @@ -40,7 +38,6 @@ const TargetPane = ({ onRequestCloseCostumeLibrary, onRequestCloseSoundLibrary, onRequestCloseSpriteLibrary, - onRequestCloseExtensionLibrary, onSelectSprite, stage, sprites, @@ -78,12 +75,6 @@ const TargetPane = ({ onSelect={onSelectSprite} />}
- {extensionLibraryVisible ? ( - - ) : null} {spriteLibraryVisible ? ( { const oldFn = object[property]; @@ -69,7 +71,8 @@ class Blocks extends React.Component { return ( this.state.prompt !== nextState.prompt || this.props.isVisible !== nextProps.isVisible || - this.props.toolboxXML !== nextProps.toolboxXML + this.props.toolboxXML !== nextProps.toolboxXML || + this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible ); } componentDidUpdate (prevProps) { @@ -209,11 +212,13 @@ class Blocks extends React.Component { render () { /* eslint-disable no-unused-vars */ const { + extensionLibraryVisible, options, vm, isVisible, onActivateColorPicker, onExtensionAdded, + onRequestCloseExtensionLibrary, toolboxXML, ...props } = this.props; @@ -233,15 +238,23 @@ class Blocks extends React.Component { onOk={this.handlePromptCallback} /> ) : null} + {extensionLibraryVisible ? ( + + ) : null}
); } } Blocks.propTypes = { + extensionLibraryVisible: PropTypes.bool, isVisible: PropTypes.bool, onActivateColorPicker: PropTypes.func, onExtensionAdded: PropTypes.func, + onRequestCloseExtensionLibrary: PropTypes.func, options: PropTypes.shape({ media: PropTypes.string, zoom: PropTypes.shape({ @@ -299,6 +312,7 @@ Blocks.defaultProps = { }; const mapStateToProps = state => ({ + extensionLibraryVisible: state.modals.extensionLibrary, toolboxXML: state.toolbox.toolboxXML }); @@ -306,6 +320,9 @@ const mapDispatchToProps = dispatch => ({ onActivateColorPicker: callback => dispatch(activateColorPicker(callback)), onExtensionAdded: toolboxXML => { dispatch(updateToolbox(toolboxXML)); + }, + onRequestCloseExtensionLibrary: () => { + dispatch(closeExtensionLibrary()); } }); diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 3a68847c6..ba0438fae 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -7,7 +7,6 @@ import { openSpriteLibrary, closeBackdropLibrary, closeCostumeLibrary, - closeExtensionLibrary, closeSoundLibrary, closeSpriteLibrary } from '../reducers/modals'; @@ -97,8 +96,7 @@ const mapStateToProps = state => ({ soundLibraryVisible: state.modals.soundLibrary, spriteLibraryVisible: state.modals.spriteLibrary, costumeLibraryVisible: state.modals.costumeLibrary, - backdropLibraryVisible: state.modals.backdropLibrary, - extensionLibraryVisible: state.modals.extensionLibrary + backdropLibraryVisible: state.modals.backdropLibrary }); const mapDispatchToProps = dispatch => ({ onNewSpriteClick: e => { @@ -111,9 +109,6 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseCostumeLibrary: () => { dispatch(closeCostumeLibrary()); }, - onRequestCloseExtensionLibrary: () => { - dispatch(closeExtensionLibrary()); - }, onRequestCloseSoundLibrary: () => { dispatch(closeSoundLibrary()); }, From 52cf504f44f77924f42b0c57e4f29f8cbea2b57e Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 1 Nov 2017 12:39:35 -0400 Subject: [PATCH 15/38] Scroll to extension category when attempting to load duplicate --- src/containers/blocks.jsx | 5 +++++ src/containers/extension-library.jsx | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 26886a293..f3f42a93f 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -31,6 +31,7 @@ class Blocks extends React.Component { bindAll(this, [ 'attachVM', 'detachVM', + 'handleCategorySelected', 'handlePromptStart', 'handlePromptCallback', 'handlePromptClose', @@ -194,6 +195,9 @@ class Blocks extends React.Component { const toolboxXML = makeToolboxXML(dynamicBlocksXML); this.props.onExtensionAdded(toolboxXML); const categoryName = blocksInfo[0].json.category; + this.handleCategorySelected(categoryName); + } + handleCategorySelected (categoryName) { this.workspace.toolbox_.setSelectedCategoryByName(categoryName); } setBlocks (blocks) { @@ -241,6 +245,7 @@ class Blocks extends React.Component { {extensionLibraryVisible ? ( ) : null} diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index b0690fcad..9f3d81bf2 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -20,9 +20,10 @@ class ExtensionLibrary extends React.PureComponent { const url = item.extensionURL || prompt('Enter the URL of the extension'); if (url) { if (this.props.vm.extensionManager.isExtensionLoaded(url)) { - return; + this.props.onCategorySelected(item.name); + } else { + this.props.vm.extensionManager.loadExtensionURL(url); } - this.props.vm.extensionManager.loadExtensionURL(url); } } render () { @@ -43,6 +44,7 @@ class ExtensionLibrary extends React.PureComponent { } ExtensionLibrary.propTypes = { + onCategorySelected: PropTypes.func, onRequestClose: PropTypes.func, visible: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired // eslint-disable-line react/no-unused-prop-types From 8fea127b32689ec0b79a552e8c8a1ed586737fac Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 1 Nov 2017 15:41:34 -0400 Subject: [PATCH 16/38] Fix linting --- src/containers/blocks.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index daeb91e85..58c0841ba 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -53,7 +53,11 @@ class Blocks extends React.Component { componentDidMount () { this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; - const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options, {toolbox: this.props.toolboxXML}); + const workspaceConfig = defaultsDeep({}, + Blocks.defaultOptions, + this.props.options, + {toolbox: this.props.toolboxXML} + ); this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); // @todo change this when blockly supports UI events From 74890bd22ba6191951e9459a7d735a6866d9f904 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 1 Nov 2017 17:17:24 -0400 Subject: [PATCH 17/38] Add block chunking --- src/lib/make-toolbox-xml.js | 48 ++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 8685b753c..ee260ceeb 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -1,4 +1,6 @@ -const separator = ''; +const categorySeparator = ''; + +const blockSeparator = ''; // At default scale, about 28px const motion = ` @@ -23,6 +25,7 @@ const motion = `
+ ${blockSeparator} @@ -36,6 +39,7 @@ const motion = ` + ${blockSeparator} @@ -82,6 +86,7 @@ const motion = ` + ${blockSeparator} @@ -110,11 +115,15 @@ const motion = ` + ${blockSeparator} + ${blockSeparator} + ${blockSeparator} + ${categorySeparator} `; @@ -158,8 +167,10 @@ const looks = ` + ${blockSeparator} + ${blockSeparator} @@ -177,6 +188,7 @@ const looks = ` + ${blockSeparator} @@ -192,6 +204,7 @@ const looks = ` + ${blockSeparator} @@ -206,6 +219,7 @@ const looks = ` + ${blockSeparator} @@ -214,10 +228,12 @@ const looks = ` + ${blockSeparator} + ${categorySeparator} `; @@ -234,6 +250,7 @@ const sound = ` + ${blockSeparator} @@ -251,6 +268,7 @@ const sound = ` + ${blockSeparator} @@ -268,6 +286,7 @@ const sound = ` + ${blockSeparator} @@ -283,6 +302,7 @@ const sound = ` + ${blockSeparator} @@ -298,6 +318,7 @@ const sound = ` + ${blockSeparator} @@ -313,6 +334,7 @@ const sound = ` + ${categorySeparator} `; @@ -324,6 +346,7 @@ const events = ` + ${blockSeparator} @@ -331,6 +354,7 @@ const events = ` + ${blockSeparator} @@ -343,6 +367,7 @@ const events = ` + ${categorySeparator} `; @@ -355,6 +380,7 @@ const control = ` + ${blockSeparator} @@ -363,11 +389,14 @@ const control = ` + ${blockSeparator} + ${blockSeparator} + ${blockSeparator} @@ -375,6 +404,7 @@ const control = ` + ${categorySeparator} `; @@ -403,6 +433,7 @@ const sensing = ` + ${blockSeparator} @@ -411,9 +442,12 @@ const sensing = ` + ${blockSeparator} + ${blockSeparator} + ${blockSeparator} @@ -422,12 +456,14 @@ const sensing = ` + ${blockSeparator} + ${categorySeparator} `; @@ -481,6 +517,7 @@ const operators = ` + ${blockSeparator} @@ -493,6 +530,7 @@ const operators = ` + ${blockSeparator} @@ -529,9 +567,11 @@ const operators = ` + ${blockSeparator} + ${blockSeparator} @@ -563,6 +603,7 @@ const operators = ` + ${blockSeparator} @@ -582,6 +623,7 @@ const operators = ` + ${blockSeparator} @@ -589,6 +631,7 @@ const operators = ` + ${categorySeparator} `; @@ -605,7 +648,7 @@ const xmlClose = ''; * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox. */ const makeToolboxXML = function (categoriesXML) { - const gap = [separator]; + const gap = [categorySeparator]; const everything = [ xmlOpen, @@ -624,7 +667,6 @@ const makeToolboxXML = function (categoriesXML) { } everything.push(xmlClose); - return everything.join('\n'); }; From 34537e4de1077d39f0cc9eb967fc82b410b87fb3 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 2 Nov 2017 04:02:34 +0000 Subject: [PATCH 18/38] chore(package): update enzyme-adapter-react-16 to version 1.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 690963a1d..19fc02387 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.7", "enzyme": "^3.1.0", - "enzyme-adapter-react-16": "1.0.2", + "enzyme-adapter-react-16": "1.0.3", "eslint": "^4.7.1", "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.7.0", From 02d35e12cdd49bb88a5dadbb58d055bf56de2270 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Nov 2017 09:54:17 -0400 Subject: [PATCH 19/38] Use file-loader for svg and png Instead of using svg-url-loader only --- package.json | 2 +- webpack.config.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 690963a1d..d3890e9e2 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.7.0", "eslint-plugin-react": "^7.2.1", + "file-loader": "^1.1.5", "get-float-time-domain-data": "0.1.0", "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder", "html-webpack-plugin": "^2.30.0", @@ -93,7 +94,6 @@ "startaudiocontext": "1.2.1", "style-loader": "^0.19.0", "svg-to-image": "1.1.3", - "svg-url-loader": "^2.1.0", "wav-encoder": "1.3.0", "web-audio-test-api": "^0.5.2", "webpack": "^3.6.0", diff --git a/webpack.config.js b/webpack.config.js index 6c486cc8a..f0490193f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -66,8 +66,8 @@ module.exports = { }] }, { - test: /\.svg$/, - loader: 'svg-url-loader?noquotes' + test: /\.(svg|png)$/, + loader: 'file-loader' }] }, plugins: [ From 51c0178c9de79581f2f320acf0bc61aabf1722c9 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Nov 2017 09:55:44 -0400 Subject: [PATCH 20/38] Load extension library content from a js file With locally loaded png image --- src/containers/extension-library.jsx | 2 +- src/lib/libraries/extensions.json | 9 --------- src/lib/libraries/extensions/index.js | 11 +++++++++++ src/lib/libraries/extensions/pen.png | Bin 0 -> 8270 bytes 4 files changed, 12 insertions(+), 10 deletions(-) delete mode 100644 src/lib/libraries/extensions.json create mode 100644 src/lib/libraries/extensions/index.js create mode 100644 src/lib/libraries/extensions/pen.png diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 343d0747d..ed8a0bda2 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import VM from 'scratch-vm'; -import extensionLibraryContent from '../lib/libraries/extensions.json'; +import extensionLibraryContent from '../lib/libraries/extensions/index'; import LibraryComponent from '../components/library/library.jsx'; import extensionIcon from '../components/sprite-selector/icon--sprite.svg'; diff --git a/src/lib/libraries/extensions.json b/src/lib/libraries/extensions.json deleted file mode 100644 index 80edbcb55..000000000 --- a/src/lib/libraries/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "name": "Pen", - "extensionURL": "pen", - "md5": "1a2ec605e73000897797f0c851134f5b.png", - "description": "Draw with your sprites.", - "featured": true - } -] diff --git a/src/lib/libraries/extensions/index.js b/src/lib/libraries/extensions/index.js new file mode 100644 index 000000000..9e3f02015 --- /dev/null +++ b/src/lib/libraries/extensions/index.js @@ -0,0 +1,11 @@ +import penImage from './pen.png'; + +export default [ + { + name: 'Pen', + extensionURL: 'pen', + iconURL: penImage, + description: 'Draw with your sprites.', + featured: true + } +]; diff --git a/src/lib/libraries/extensions/pen.png b/src/lib/libraries/extensions/pen.png new file mode 100644 index 0000000000000000000000000000000000000000..f02064c425e89cbac6962b36ee0b90e50d145e03 GIT binary patch literal 8270 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 y|H5tT|7pMZU%OiV- Date: Thu, 2 Nov 2017 10:08:10 -0400 Subject: [PATCH 21/38] Bring back the 'contains' operator --- src/lib/make-toolbox-xml.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 0c25b7095..b186ba8e4 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -603,6 +603,18 @@ const operators = ` + + + + hello + + + + + world + + + ${blockSeparator} From 17d7f655841ffc98dbc838263ec77e16be7e1b0f Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 2 Nov 2017 11:43:01 -0400 Subject: [PATCH 22/38] Ensure loading extension again scrolls back down --- test/integration/test.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/integration/test.js b/test/integration/test.js index efd0732f2..86729cf27 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -23,6 +23,7 @@ const blocksTabScope = "*[@id='react-tabs-1']"; const costumesTabScope = "*[@id='react-tabs-3']"; const soundsTabScope = "*[@id='react-tabs-5']"; const reportedValueScope = '*[@class="blocklyDropDownContent"]'; +const modalScope = '*[@class="ReactModalPortal"]'; describe('costumes, sounds and variables', () => { beforeAll(() => { @@ -135,9 +136,18 @@ describe('costumes, sounds and variables', () => { await driver.get(`file://${uri}`); await clickText('Blocks'); await clickText('Extensions'); - await clickText('Pen'); // Modal closes - await clickText('Pen', blocksTabScope); // Click the new category + await clickText('Pen', modalScope); // Modal closes + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation await clickText('stamp', blocksTabScope); // Click the "stamp" block + + // Make sure trying to load the extension again scrolls back down + await clickText('Motion', blocksTabScope); // To scroll the list back to the top + await clickText('Extensions'); + await clickText('Pen', modalScope); // Modal closes + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation + await clickText('stamp', blocksTabScope); // Would fail if didn't scroll back + + const logs = await getLogs(errorWhitelist); await expect(logs).toEqual([]); }); From 324cd84051740c10d31a2af0d4f9f724498d6244 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 2 Nov 2017 20:06:35 +0000 Subject: [PATCH 23/38] chore(package): update scratch-storage to version 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19fc02387..900812b57 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "scratch-blocks": "latest", "scratch-l10n": "^2.0.0", "scratch-render": "latest", - "scratch-storage": "^0.2.0", + "scratch-storage": "^0.3.0", "scratch-vm": "latest", "selenium-webdriver": "3.5.0", "startaudiocontext": "1.2.1", From eddc8c4345ad078b7a084ec64579d9b1eaf0df19 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 2 Nov 2017 16:47:56 -0400 Subject: [PATCH 24/38] Wait for scroll animations --- test/integration/examples.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/examples.test.js b/test/integration/examples.test.js index e2b0c8ab4..7fffa903a 100644 --- a/test/integration/examples.test.js +++ b/test/integration/examples.test.js @@ -72,6 +72,7 @@ describe('blocks example', () => { await clickText('Sensing'); await clickText('Operators'); await clickText('Data'); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation await clickText('Create variable...'); let el = await findByXpath("//input[@placeholder='']"); await el.sendKeys('score'); From ceb39f366ceeed0552c64b2c1f5a21e2fd7dcc5d Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Mon, 30 Oct 2017 09:56:49 -0400 Subject: [PATCH 25/38] Use file-loader for svg's, since it's more generic --- package.json | 2 +- webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 900812b57..fc9c43d38 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.7.0", "eslint-plugin-react": "^7.2.1", + "file-loader": "1.1.5", "get-float-time-domain-data": "0.1.0", "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder", "html-webpack-plugin": "^2.30.0", @@ -93,7 +94,6 @@ "startaudiocontext": "1.2.1", "style-loader": "^0.19.0", "svg-to-image": "1.1.3", - "svg-url-loader": "^2.1.0", "wav-encoder": "1.3.0", "web-audio-test-api": "^0.5.2", "webpack": "^3.6.0", diff --git a/webpack.config.js b/webpack.config.js index 6c486cc8a..1a874c704 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -67,7 +67,7 @@ module.exports = { }, { test: /\.svg$/, - loader: 'svg-url-loader?noquotes' + loader: 'file-loader' }] }, plugins: [ From adb31ea8d374a3bd0013b007ddc00959d3bad829 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Mon, 30 Oct 2017 09:59:19 -0400 Subject: [PATCH 26/38] Use a storage singleton This way the instance can be reused, which allows access to non-deprecated `DataFormat` and `AssetType`. --- src/lib/storage.js | 4 +++- src/reducers/vm.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/storage.js b/src/lib/storage.js index e1dc91bd6..d1c8f886d 100644 --- a/src/lib/storage.js +++ b/src/lib/storage.js @@ -26,4 +26,6 @@ class Storage extends ScratchStorage { } } -export default Storage; +const storage = new Storage(); + +export default storage; diff --git a/src/reducers/vm.js b/src/reducers/vm.js index 5d0e6f91f..dd70f3f4f 100644 --- a/src/reducers/vm.js +++ b/src/reducers/vm.js @@ -1,9 +1,9 @@ import VM from 'scratch-vm'; -import Storage from '../lib/storage'; +import storage from '../lib/storage'; const SET_VM = 'scratch-gui/vm/SET_VM'; const defaultVM = new VM(); -defaultVM.attachStorage(new Storage()); +defaultVM.attachStorage(storage); const initialState = defaultVM; const reducer = function (state, action) { From 0cb28ea9bece42b55518c9b51aa2f02b134e21b5 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Mon, 30 Oct 2017 10:00:16 -0400 Subject: [PATCH 27/38] Cache the default project json and its assets --- package.json | 4 +- .../09dc888b0b7df19f70d81588ae73420e.svg | 42 ++++++++++++++++ .../3696356a03a8d938318876a593572843.svg | 37 ++++++++++++++ .../5c81a336fab8be57adc039a8a2b33ca9.png | Bin 0 -> 1263 bytes .../739b5e2a2435f6e1ec2993791b423146.png | Bin 0 -> 1264 bytes .../83a9787d4cb6f3b7632b4ddfebf74367.wav | Bin 0 -> 560 bytes .../83c36d806dc92327b9e7049a565c6bff.wav | Bin 0 -> 37420 bytes src/lib/default-project/index.js | 45 ++++++++++++++++++ .../project.json} | 0 src/lib/storage.js | 8 ++++ webpack.config.js | 4 ++ 11 files changed, 139 insertions(+), 1 deletion(-) create mode 100755 src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg create mode 100755 src/lib/default-project/3696356a03a8d938318876a593572843.svg create mode 100755 src/lib/default-project/5c81a336fab8be57adc039a8a2b33ca9.png create mode 100755 src/lib/default-project/739b5e2a2435f6e1ec2993791b423146.png create mode 100755 src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav create mode 100755 src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav create mode 100644 src/lib/default-project/index.js rename src/lib/{empty-project.json => default-project/project.json} (100%) diff --git a/package.json b/package.json index fc9c43d38..5d7651fd7 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "babel-plugin-transform-object-rest-spread": "^6.22.0", "babel-preset-es2015": "^6.22.0", "babel-preset-react": "^6.22.0", + "buffer-loader": "0.0.1", "chromedriver": "2.33.1", "classnames": "2.2.5", "copy-webpack-plugin": "^4.0.1", @@ -67,6 +68,7 @@ "postcss-simple-vars": "^4.0.0", "prop-types": "^15.5.10", "raf": "^3.4.0", + "raw-loader": "0.5.1", "react": "16.0.0", "react-contextmenu": "2.8.0", "react-dom": "16.0.0", @@ -84,9 +86,9 @@ "redux-throttle": "0.1.1", "rimraf": "^2.6.1", "scratch-audio": "latest", - "scratch-paint": "latest", "scratch-blocks": "latest", "scratch-l10n": "^2.0.0", + "scratch-paint": "latest", "scratch-render": "latest", "scratch-storage": "^0.3.0", "scratch-vm": "latest", diff --git a/src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg b/src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg new file mode 100755 index 000000000..d449b3d15 --- /dev/null +++ b/src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/lib/default-project/3696356a03a8d938318876a593572843.svg b/src/lib/default-project/3696356a03a8d938318876a593572843.svg new file mode 100755 index 000000000..0ecb2de81 --- /dev/null +++ b/src/lib/default-project/3696356a03a8d938318876a593572843.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/lib/default-project/5c81a336fab8be57adc039a8a2b33ca9.png b/src/lib/default-project/5c81a336fab8be57adc039a8a2b33ca9.png new file mode 100755 index 0000000000000000000000000000000000000000..da373d2cf3ab7c4c617d307eec5a044ae43917ee GIT binary patch literal 1263 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|?860dtk$!Ux1qKF|T2B|pkcwMxuNv|)Fz_7S zAp5kxQmZr0ti|ReBSY~-pqK;*86<$vfd)n(Vqj(iA&A6CmV*Qe_HY>)a*zapo+coE zA1QLG3@8}_+4E2dcxD-aa`;M#SB%m(SrT^)ho4(^K_;C7O|mYhH4GN@~#xw)%HKjSQZyelF{r5}E+9OyCm$ literal 0 HcmV?d00001 diff --git a/src/lib/default-project/739b5e2a2435f6e1ec2993791b423146.png b/src/lib/default-project/739b5e2a2435f6e1ec2993791b423146.png new file mode 100755 index 0000000000000000000000000000000000000000..b395ac94c246fd4de8368a156f92010baa2abe21 GIT binary patch literal 1264 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|?860dtk$!Ux1qKF|I!_nJkcwMxuUaxPFmN0( zX#1Q$^EgYe(n9acs~H-?Co(YbNPv(*0tg*wU<4utW;PIlNQ`7TNT6U3mysa{Nf791 z0^;|PBB#oLk|B^i50!vtmJuk2uar2)@R^NsZt;VgUBCiKx5PD~Bsf2^a0 B;rIXm literal 0 HcmV?d00001 diff --git a/src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav b/src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav new file mode 100755 index 0000000000000000000000000000000000000000..fc3b2724a9c7cfef378eeb65499d44236ad2add8 GIT binary patch literal 560 zcmV-00?++YNk&E}0ssJ4K~_a(ZFC?I000010096ID*yl@RsaA301yCVVRT^x0ssKj z00RTP2XYPX6;mBNBby}RAgdO0237H2+F-ZWdO|~4V=TEqdmIH}tkx#Dm*<1_&JWyZnZx?A+P6{M(@>#`aqqdWH znfS73+M@}MHfvWYW};ubM)e=a@&n5CtcAAMIVe%_Ry0XYD+2^9+rGN& zs1B*oyo}wv20Si+OB7irQe8Q$6CCPs#jUL=r@FHn(6IKU9(q5>QOZ$kKYbtP`P0^c zz8|(#zO2=}^(q*GGxb4)KrAl|6Ib$|)w9F(zjVu-<9Y&mAc;1HKhiilB*O-h=Nrz3 zzX-q;&r#`X2!16XItW3vIF=(*1~2GV&nm*|!dlS<>PZKbBG)z0JnS_}BU%YZ?i<*5 z%MHtB)^F;u0+JasDtIueD?AstBS;^s6R!hO^Kj<6 z-`n6b>CE;d2HO+z97-RA8p#iQ0p#*E=MCY8;dXzq(>EQ4A_|yUj3rY}@5!ntm2^j);``5*s|1eXXG3W^9-1Y!OTiU1S< literal 0 HcmV?d00001 diff --git a/src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav b/src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav new file mode 100755 index 0000000000000000000000000000000000000000..45742d5ef6f09d05b0f0788cb055ffe54abfd9ad GIT binary patch literal 37420 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 `${ASSET_SERVER}/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/` ); + defaultProjectAssets.forEach(asset => this.cache( + this.AssetType[asset.assetType], + this.DataFormat[asset.dataFormat], + asset.data, + asset.id + )); } } diff --git a/webpack.config.js b/webpack.config.js index 1a874c704..90b821039 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -65,6 +65,10 @@ module.exports = { } }] }, + { + test: /\.(png|wav)$/, + loader: 'raw-loader' + }, { test: /\.svg$/, loader: 'file-loader' From 6be5ec1cf17049380f380a2225fdffa3666c3382 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Mon, 30 Oct 2017 10:00:25 -0400 Subject: [PATCH 28/38] Load projects through storage With this change we only use `xhr` in the `CostumeCanvas` component. --- src/lib/project-loader-hoc.jsx | 47 ++++++++++------------------------ 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx index 5468e7a09..1d2cf0f68 100644 --- a/src/lib/project-loader-hoc.jsx +++ b/src/lib/project-loader-hoc.jsx @@ -1,26 +1,7 @@ import React from 'react'; -import xhr from 'xhr'; import log from './log'; -import emptyProject from './empty-project.json'; - -class ProjectLoaderConstructor { - get DEFAULT_PROJECT_DATA () { - return emptyProject; - } - - load (id, callback) { - callback = callback || (err => log.error(err)); - xhr({ - uri: `https://projects.scratch.mit.edu/internalapi/project/${id}/get/` - }, (err, res, body) => { - if (err) return callback(err); - callback(null, body); - }); - } -} - -const ProjectLoader = new ProjectLoaderConstructor(); +import storage from './storage'; /* Higher Order Component to provide behavior for loading projects by id from * the window's hash (#this part in the url) @@ -35,13 +16,21 @@ const ProjectLoaderHOC = function (WrappedComponent) { this.updateProject = this.updateProject.bind(this); this.state = { projectId: null, - projectData: this.fetchProjectId().length ? null : JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA) + projectData: null }; } componentDidMount () { window.addEventListener('hashchange', this.updateProject); this.updateProject(); } + componentDidUpdate (prevProps, prevState) { + if (this.state.projectId !== prevState.projectId) { + storage + .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON) + .then(projectAsset => this.setState({projectData: JSON.stringify(projectAsset.data)})) + .catch(err => log.error(err)); + } + } componentWillUnmount () { window.removeEventListener('hashchange', this.updateProject); } @@ -49,18 +38,9 @@ const ProjectLoaderHOC = function (WrappedComponent) { return window.location.hash.substring(1); } updateProject () { - const projectId = this.fetchProjectId(); + let projectId = this.fetchProjectId(); if (projectId !== this.state.projectId) { - if (projectId.length < 1) { - return this.setState({ - projectId: projectId, - projectData: JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA) - }); - } - ProjectLoader.load(projectId, (err, body) => { - if (err) return log.error(err); - this.setState({projectData: body}); - }); + if (projectId.length < 1) projectId = 0; this.setState({projectId: projectId}); } } @@ -80,6 +60,5 @@ const ProjectLoaderHOC = function (WrappedComponent) { export { - ProjectLoaderHOC as default, - ProjectLoader + ProjectLoaderHOC as default }; From e772aa17cca6251c32764bd5762abfa96fd41132 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 2 Nov 2017 09:13:45 -0400 Subject: [PATCH 29/38] Disable loader linting eslint-plugin-import doesn't like these inline loaders. I tried unsuccessfully to configure the recommended solution with `eslint-import-resolver-webpack`, but we should come back to this. --- src/lib/default-project/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/default-project/index.js b/src/lib/default-project/index.js index f797b4e71..4050f1536 100644 --- a/src/lib/default-project/index.js +++ b/src/lib/default-project/index.js @@ -1,10 +1,13 @@ import projectJson from './project.json'; + +/* eslint-disable import/no-unresolved */ import popWav from '!buffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav'; import meowWav from '!buffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav'; import backdrop from '!buffer-loader!./739b5e2a2435f6e1ec2993791b423146.png'; import penLayer from '!buffer-loader!./5c81a336fab8be57adc039a8a2b33ca9.png'; import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg'; import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg'; +/* eslint-enable import/no-unresolved */ const encoder = new TextEncoder(); export default [{ From 009876d3d4454977ce96dc605a5791a7931bb19c Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 2 Nov 2017 09:39:00 -0400 Subject: [PATCH 30/38] Include text-encoder polyfill for tests --- package.json | 1 + src/lib/default-project/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 5d7651fd7..13a4b8c1f 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "startaudiocontext": "1.2.1", "style-loader": "^0.19.0", "svg-to-image": "1.1.3", + "text-encoding": "0.6.4", "wav-encoder": "1.3.0", "web-audio-test-api": "^0.5.2", "webpack": "^3.6.0", diff --git a/src/lib/default-project/index.js b/src/lib/default-project/index.js index 4050f1536..2551331a9 100644 --- a/src/lib/default-project/index.js +++ b/src/lib/default-project/index.js @@ -1,3 +1,4 @@ +import {TextEncoder} from 'text-encoding'; import projectJson from './project.json'; /* eslint-disable import/no-unresolved */ From 42cf12e3d7991414ab70d840563ee8121bf2ecdd Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 2 Nov 2017 09:39:44 -0400 Subject: [PATCH 31/38] Handle null responses from storage --- src/lib/project-loader-hoc.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx index 1d2cf0f68..52fe62b9d 100644 --- a/src/lib/project-loader-hoc.jsx +++ b/src/lib/project-loader-hoc.jsx @@ -27,7 +27,9 @@ const ProjectLoaderHOC = function (WrappedComponent) { if (this.state.projectId !== prevState.projectId) { storage .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON) - .then(projectAsset => this.setState({projectData: JSON.stringify(projectAsset.data)})) + .then(projectAsset => projectAsset && this.setState({ + projectData: JSON.stringify(projectAsset.data) + })) .catch(err => log.error(err)); } } From 0e0c689e2272d888e7440751d6848677a2665ceb Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 2 Nov 2017 09:41:26 -0400 Subject: [PATCH 32/38] Fix tests Previously we tested the ProjectLoader helper object, but now all loading is confined to storage. Unfortunately I wasn't able to get enzyme to wait for the storage promise to resolve within componentDidUpdate, so I can't test the actual results of storage.load, just what it was called with. --- test/unit/util/project-loader-hoc.test.jsx | 40 +++++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/test/unit/util/project-loader-hoc.test.jsx b/test/unit/util/project-loader-hoc.test.jsx index d61352d36..b1da2b7d3 100644 --- a/test/unit/util/project-loader-hoc.test.jsx +++ b/test/unit/util/project-loader-hoc.test.jsx @@ -1,5 +1,6 @@ import React from 'react'; -import ProjectLoaderHOC, {ProjectLoader} from '../../../src/lib/project-loader-hoc.jsx'; +import ProjectLoaderHOC from '../../../src/lib/project-loader-hoc.jsx'; +import storage from '../../../src/lib/storage'; import {mount} from 'enzyme'; describe('ProjectLoaderHOC', () => { @@ -7,34 +8,38 @@ describe('ProjectLoaderHOC', () => { const Component = ({projectData}) =>
{projectData}
; const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = '#winning'; - ProjectLoader.load = jest.fn((id, cb) => cb(null, null)); + storage.load = jest.fn(() => Promise.resolve(null)); const mounted = mount(); - ProjectLoader.load.mockRestore(); + storage.load.mockRestore(); window.location.hash = ''; - expect(mounted.find('div').exists()).toEqual(false); + const mountedDiv = mounted.find('div'); + expect(mountedDiv.exists()).toEqual(false); }); test('when there is no hash, it loads the default project', () => { const Component = ({projectData}) =>
{projectData}
; const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = ''; + storage.load = jest.fn((type, id) => Promise.resolve(id)); const mounted = mount(); - expect(mounted.find('div').text()).toEqual(JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA)); + expect(mounted.state().projectId).toEqual(0); + expect(storage.load).toHaveBeenCalledWith( + storage.AssetType.Project, 0, storage.DataFormat.JSON + ); + storage.load.mockRestore(); }); test('when there is a hash, it tries to load that project', () => { const Component = ({projectData}) =>
{projectData}
; const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = '#winning'; - ProjectLoader.load = jest.fn((id, cb) => cb(null, id)); + storage.load = jest.fn((type, id) => Promise.resolve({data: id})); const mounted = mount(); - mounted.update(); - ProjectLoader.load.mockRestore(); - window.location.hash = ''; - expect(mounted - .find('div') - .text() - ).toEqual('winning'); + expect(mounted.state().projectId).toEqual('winning'); + expect(storage.load).toHaveBeenLastCalledWith( + storage.AssetType.Project, 'winning', storage.DataFormat.JSON + ); + storage.load.mockRestore(); }); test('when hash change happens, the project data state is changed', () => { @@ -42,10 +47,11 @@ describe('ProjectLoaderHOC', () => { const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = ''; const mounted = mount(); - const before = mounted.find('div').text(); - ProjectLoader.load = jest.fn((id, cb) => cb(null, id)); - window.location.hash = `#winning`; + expect(mounted.state().projectId).toEqual(0); + storage.load = jest.fn((type, id) => Promise.resolve({data: id})); + window.location.hash = '#winning'; mounted.instance().updateProject(); - expect(mounted.find('div').text()).not.toEqual(before); + expect(mounted.state().projectId).toEqual('winning'); + storage.load.mockRestore(); }); }); From c3ebfa5c6d7090e4ac42ba1944338656975994dc Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Thu, 2 Nov 2017 14:48:17 -0400 Subject: [PATCH 33/38] Mock responsibly /ht @paulkaplan --- test/unit/util/project-loader-hoc.test.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/unit/util/project-loader-hoc.test.jsx b/test/unit/util/project-loader-hoc.test.jsx index b1da2b7d3..3771f3769 100644 --- a/test/unit/util/project-loader-hoc.test.jsx +++ b/test/unit/util/project-loader-hoc.test.jsx @@ -8,9 +8,10 @@ describe('ProjectLoaderHOC', () => { const Component = ({projectData}) =>
{projectData}
; const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = '#winning'; + const originalLoad = storage.load; storage.load = jest.fn(() => Promise.resolve(null)); const mounted = mount(); - storage.load.mockRestore(); + storage.load = originalLoad; window.location.hash = ''; const mountedDiv = mounted.find('div'); expect(mountedDiv.exists()).toEqual(false); @@ -20,26 +21,28 @@ describe('ProjectLoaderHOC', () => { const Component = ({projectData}) =>
{projectData}
; const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = ''; + const originalLoad = storage.load; storage.load = jest.fn((type, id) => Promise.resolve(id)); const mounted = mount(); expect(mounted.state().projectId).toEqual(0); expect(storage.load).toHaveBeenCalledWith( storage.AssetType.Project, 0, storage.DataFormat.JSON ); - storage.load.mockRestore(); + storage.load = originalLoad; }); test('when there is a hash, it tries to load that project', () => { const Component = ({projectData}) =>
{projectData}
; const WrappedComponent = ProjectLoaderHOC(Component); window.location.hash = '#winning'; + const originalLoad = storage.load; storage.load = jest.fn((type, id) => Promise.resolve({data: id})); const mounted = mount(); expect(mounted.state().projectId).toEqual('winning'); expect(storage.load).toHaveBeenLastCalledWith( storage.AssetType.Project, 'winning', storage.DataFormat.JSON ); - storage.load.mockRestore(); + storage.load = originalLoad; }); test('when hash change happens, the project data state is changed', () => { @@ -48,10 +51,11 @@ describe('ProjectLoaderHOC', () => { window.location.hash = ''; const mounted = mount(); expect(mounted.state().projectId).toEqual(0); + const originalLoad = storage.load; storage.load = jest.fn((type, id) => Promise.resolve({data: id})); window.location.hash = '#winning'; mounted.instance().updateProject(); expect(mounted.state().projectId).toEqual('winning'); - storage.load.mockRestore(); + storage.load = originalLoad; }); }); From 87ef5b46b7e4b1e61b7750848ba71f3f89858abb Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Fri, 3 Nov 2017 10:43:49 -0400 Subject: [PATCH 34/38] Fix project json loading through storage --- src/lib/default-project/index.js | 2 +- src/lib/project-loader-hoc.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/default-project/index.js b/src/lib/default-project/index.js index 2551331a9..bc1ace999 100644 --- a/src/lib/default-project/index.js +++ b/src/lib/default-project/index.js @@ -15,7 +15,7 @@ export default [{ id: 0, assetType: 'Project', dataFormat: 'JSON', - data: projectJson + data: JSON.stringify(projectJson) }, { id: '83a9787d4cb6f3b7632b4ddfebf74367', assetType: 'Sound', diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx index 52fe62b9d..23ad4694a 100644 --- a/src/lib/project-loader-hoc.jsx +++ b/src/lib/project-loader-hoc.jsx @@ -28,7 +28,7 @@ const ProjectLoaderHOC = function (WrappedComponent) { storage .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON) .then(projectAsset => projectAsset && this.setState({ - projectData: JSON.stringify(projectAsset.data) + projectData: projectAsset.data.toString() })) .catch(err => log.error(err)); } From 12896d934a0a99f1e86b5a717369b61a6e7f6675 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Fri, 3 Nov 2017 15:44:03 -0400 Subject: [PATCH 35/38] Scroll to extension category only when loading from library --- src/containers/blocks.jsx | 2 -- src/containers/extension-library.jsx | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 59dd1a539..471dc6cd0 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -195,8 +195,6 @@ class Blocks extends React.Component { const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML(); const toolboxXML = makeToolboxXML(dynamicBlocksXML); this.props.onExtensionAdded(toolboxXML); - const categoryName = blocksInfo[0].json.category; - this.handleCategorySelected(categoryName); } handleCategorySelected (categoryName) { this.workspace.toolbox_.setSelectedCategoryByName(categoryName); diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 071427409..09fd65ff3 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -22,7 +22,9 @@ class ExtensionLibrary extends React.PureComponent { if (this.props.vm.extensionManager.isExtensionLoaded(url)) { this.props.onCategorySelected(item.name); } else { - this.props.vm.extensionManager.loadExtensionURL(url); + this.props.vm.extensionManager.loadExtensionURL(url).then(() => { + this.props.onCategorySelected(item.name); + }); } } } From 67c1151c4130eec29f243c715ad0486a2eff8c68 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Fri, 3 Nov 2017 16:40:26 -0400 Subject: [PATCH 36/38] Add Lego WeDo 2.0 extension to library --- src/lib/libraries/extensions/index.js | 8 ++++++++ src/lib/libraries/extensions/wedo.png | Bin 0 -> 13930 bytes 2 files changed, 8 insertions(+) create mode 100644 src/lib/libraries/extensions/wedo.png diff --git a/src/lib/libraries/extensions/index.js b/src/lib/libraries/extensions/index.js index 9e3f02015..4a03a896b 100644 --- a/src/lib/libraries/extensions/index.js +++ b/src/lib/libraries/extensions/index.js @@ -1,4 +1,5 @@ import penImage from './pen.png'; +import wedoImage from './wedo.png'; export default [ { @@ -7,5 +8,12 @@ export default [ iconURL: penImage, description: 'Draw with your sprites.', featured: true + }, + { + name: 'Lego WeDo 2.0', + extensionURL: 'wedo2', + iconURL: wedoImage, + description: 'Build with motors and sensors.', + featured: true } ]; diff --git a/src/lib/libraries/extensions/wedo.png b/src/lib/libraries/extensions/wedo.png new file mode 100644 index 0000000000000000000000000000000000000000..e8519256752af7d5cbbbe86b74df1cce7fe5614d GIT binary patch literal 13930 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 UgDNpa505>Ol~j@_7dH<2KM8PBo&W#< literal 0 HcmV?d00001 From 058476cb29f98cc58d467e0775baf5b6c769600a Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Fri, 3 Nov 2017 12:52:15 -0400 Subject: [PATCH 37/38] Add compatibility testing example page --- src/examples/compatibility-testing.jsx | 74 ++++++++++++++++++++++++++ webpack.config.js | 7 +++ 2 files changed, 81 insertions(+) create mode 100644 src/examples/compatibility-testing.jsx diff --git a/src/examples/compatibility-testing.jsx b/src/examples/compatibility-testing.jsx new file mode 100644 index 000000000..11c9bc4bf --- /dev/null +++ b/src/examples/compatibility-testing.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import {connect} from 'react-redux'; + +import AppStateHOC from '../lib/app-state-hoc.jsx'; +import Controls from '../containers/controls.jsx'; +import Stage from '../containers/stage.jsx'; +import Box from '../components/box/box.jsx'; +import GUI from '../containers/gui.jsx'; +import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx'; + +const mapStateToProps = state => ({vm: state.vm}); + +const VMStage = connect(mapStateToProps)(Stage); +const VMControls = connect(mapStateToProps)(Controls); + +class Player extends React.Component { + constructor (props) { + super(props); + this.updateProject = this.updateProject.bind(this); + this.state = { + projectId: window.location.hash.substring(1) || '10015059' + }; + } + componentDidMount () { + window.addEventListener('hashchange', this.updateProject); + } + componentWillUnmount () { + window.addEventListener('hashchange', this.updateProject); + } + updateProject () { + this.setState({projectId: window.location.hash.substring(1)}); + } + render () { + const width = 480; + const height = 360; + return ( +
+ + + + + + +