From 7b177c1680d411d0060ac491e3005f327d0a375b Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 31 Oct 2017 16:57:50 -0400 Subject: [PATCH 01/28] set button images to aria-hidden --- src/components/close-button/close-button.jsx | 1 + src/components/icon-button/icon-button.jsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/close-button/close-button.jsx b/src/components/close-button/close-button.jsx index 416adec8e..6d6fe6e26 100644 --- a/src/components/close-button/close-button.jsx +++ b/src/components/close-button/close-button.jsx @@ -20,6 +20,7 @@ const CloseButton = props => ( ); diff --git a/src/components/icon-button/icon-button.jsx b/src/components/icon-button/icon-button.jsx index 50d5557e5..9c78c3fc6 100644 --- a/src/components/icon-button/icon-button.jsx +++ b/src/components/icon-button/icon-button.jsx @@ -16,6 +16,7 @@ const IconButton = ({
{title} From abea389a6333b14224e2b113e1488c82ee7183a0 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 31 Oct 2017 16:34:13 -0400 Subject: [PATCH 02/28] show focus on language selector --- src/components/language-selector/language-selector.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css index 5e1a41aee..a061ae258 100644 --- a/src/components/language-selector/language-selector.css +++ b/src/components/language-selector/language-selector.css @@ -22,3 +22,8 @@ color: #3373cc; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } + +.language-select:focus { + border-color: black; + box-shadow: inset 0 0 0 -2px rgba(0, 0, 0, 0.1); +} From 5f362305b62f49ecbda56300c80d0fc508bd6cd6 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Thu, 2 Nov 2017 15:03:18 -0400 Subject: [PATCH 03/28] show white ring when language selector focused --- src/components/language-selector/language-selector.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css index a061ae258..2fa6caa41 100644 --- a/src/components/language-selector/language-selector.css +++ b/src/components/language-selector/language-selector.css @@ -24,6 +24,6 @@ } .language-select:focus { - border-color: black; - box-shadow: inset 0 0 0 -2px rgba(0, 0, 0, 0.1); + border-color: white; + box-shadow: 0 0 0 -2px white; } From 9b42c2885678c9a2209799083a7a4ce215e1f221 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 31 Oct 2017 14:28:47 -0400 Subject: [PATCH 04/28] set x and y icons to aria hidden --- src/components/sprite-info/sprite-info.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx index f9bbb64e3..da4e9c1ce 100644 --- a/src/components/sprite-info/sprite-info.jsx +++ b/src/components/sprite-info/sprite-info.jsx @@ -56,6 +56,7 @@ class SpriteInfo extends React.Component {
@@ -78,6 +79,7 @@ class SpriteInfo extends React.Component { From 3f75f83b1ffd3059bca9e27b09635051de351add Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 31 Oct 2017 15:56:39 -0400 Subject: [PATCH 05/28] enable selection of sprite visibility via keyboard control --- src/components/sprite-info/sprite-info.jsx | 12 ++++++++++-- src/containers/sprite-info.jsx | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx index f9bbb64e3..2cddd2b2c 100644 --- a/src/components/sprite-info/sprite-info.jsx +++ b/src/components/sprite-info/sprite-info.jsx @@ -116,6 +116,9 @@ class SpriteInfo extends React.Component { )} tabIndex="4" onClick={this.props.onClickVisible} + onKeyPress={this.props.onPressVisible} + aria-label="Show sprite" + role="button" > ); } From ef2cb656c575ba922b75b849823de3c5f837db95 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 31 Oct 2017 16:21:50 -0400 Subject: [PATCH 06/28] show focus on sprite visibility buttons --- src/components/sprite-info/sprite-info.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/sprite-info/sprite-info.css b/src/components/sprite-info/sprite-info.css index 200aedaf7..f018abe8e 100644 --- a/src/components/sprite-info/sprite-info.css +++ b/src/components/sprite-info/sprite-info.css @@ -62,6 +62,11 @@ border-bottom-left-radius: $form-radius; } +.radio-left:focus { + border-color: #4c97ff; + box-shadow: inset 0 0 0 -2px rgba(0, 0, 0, 0.1); +} + .radio-right { border-bottom: 1px solid $form-border; border-top: 1px solid $form-border; @@ -70,6 +75,11 @@ border-bottom-right-radius: $form-radius; } +.radio-right:focus { + border-color: #4c97ff; + box-shadow: inset 0 0 0 -2px rgba(0, 0, 0, 0.1); +} + .icon { width: 100%; height: 100%; From d14311e758b9e777af1566614e3221dcdad921dd Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Thu, 2 Nov 2017 16:27:27 -0400 Subject: [PATCH 07/28] remove aria label and button role to narrow scope of PR --- src/components/sprite-info/sprite-info.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx index 2cddd2b2c..6db049a7a 100644 --- a/src/components/sprite-info/sprite-info.jsx +++ b/src/components/sprite-info/sprite-info.jsx @@ -117,8 +117,6 @@ class SpriteInfo extends React.Component { tabIndex="4" onClick={this.props.onClickVisible} onKeyPress={this.props.onPressVisible} - aria-label="Show sprite" - role="button" > Date: Thu, 2 Nov 2017 17:03:14 -0400 Subject: [PATCH 08/28] nit single quotes --- src/containers/sprite-info.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/sprite-info.jsx b/src/containers/sprite-info.jsx index baea1f818..f4b5ca696 100644 --- a/src/containers/sprite-info.jsx +++ b/src/containers/sprite-info.jsx @@ -27,13 +27,13 @@ class SpriteInfo extends React.Component { this.props.onChangeVisibility(false); } handlePressVisible (e) { - if (e.key === " " || e.key === "Enter") { + if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this.props.onChangeVisibility(true); } } handlePressNotVisible (e) { - if (e.key === " " || e.key === "Enter") { + if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this.props.onChangeVisibility(false); } From 64e705d1d0897bd3f1f9ca8e917e572029b63ecc Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Thu, 2 Nov 2017 17:20:30 -0400 Subject: [PATCH 09/28] nit prop ordering --- src/components/close-button/close-button.jsx | 2 +- src/components/icon-button/icon-button.jsx | 2 +- src/components/sprite-info/sprite-info.jsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/close-button/close-button.jsx b/src/components/close-button/close-button.jsx index 6d6fe6e26..2bc75ed24 100644 --- a/src/components/close-button/close-button.jsx +++ b/src/components/close-button/close-button.jsx @@ -18,9 +18,9 @@ const CloseButton = props => ( onClick={props.onClick} > ); diff --git a/src/components/icon-button/icon-button.jsx b/src/components/icon-button/icon-button.jsx index 9c78c3fc6..799ab991f 100644 --- a/src/components/icon-button/icon-button.jsx +++ b/src/components/icon-button/icon-button.jsx @@ -14,9 +14,9 @@ const IconButton = ({ onClick={onClick} >
{title} diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx index da4e9c1ce..5fcfd440f 100644 --- a/src/components/sprite-info/sprite-info.jsx +++ b/src/components/sprite-info/sprite-info.jsx @@ -54,9 +54,9 @@ class SpriteInfo extends React.Component {
@@ -77,9 +77,9 @@ class SpriteInfo extends React.Component {
From 875b4dab78c4ccedb34370b742a7257dc0f3d946 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Thu, 9 Nov 2017 20:49:49 -0500 Subject: [PATCH 10/28] remove box shadow --- src/components/language-selector/language-selector.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css index 2fa6caa41..a060131a8 100644 --- a/src/components/language-selector/language-selector.css +++ b/src/components/language-selector/language-selector.css @@ -24,6 +24,5 @@ } .language-select:focus { - border-color: white; - box-shadow: 0 0 0 -2px white; + border: 1px solid white; } From c09a295d659ac85caca8be6ed398a21f7c422f90 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Thu, 9 Nov 2017 21:28:29 -0500 Subject: [PATCH 11/28] remove aria-hidden and add button aria role --- src/components/close-button/close-button.jsx | 3 ++- src/components/icon-button/icon-button.jsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/close-button/close-button.jsx b/src/components/close-button/close-button.jsx index 2bc75ed24..e90bd1d64 100644 --- a/src/components/close-button/close-button.jsx +++ b/src/components/close-button/close-button.jsx @@ -7,6 +7,7 @@ import closeIcon from './icon--close.svg'; const CloseButton = props => (
( [styles.large]: props.size === CloseButton.SIZE_LARGE } )} + role="button" onClick={props.onClick} > diff --git a/src/components/icon-button/icon-button.jsx b/src/components/icon-button/icon-button.jsx index 799ab991f..c6f9ae3d0 100644 --- a/src/components/icon-button/icon-button.jsx +++ b/src/components/icon-button/icon-button.jsx @@ -11,10 +11,10 @@ const IconButton = ({ }) => (
From c696d61f3dcea6672b2be355a9120f5ef8c009a8 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 13 Nov 2017 18:59:08 +0000 Subject: [PATCH 12/28] chore(package): update react to version 16.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a2e71f91..027d02d85 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "prop-types": "^15.5.10", "raf": "^3.4.0", "raw-loader": "0.5.1", - "react": "16.1.0", + "react": "16.1.1", "react-contextmenu": "2.8.0", "react-dom": "16.1.0", "react-draggable": "3.0.3", From 52e74c218b917194665293e6fed7cf6361206d1e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 13 Nov 2017 19:52:33 +0000 Subject: [PATCH 13/28] chore(package): update react-dom to version 16.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a2e71f91..850f27df5 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "raw-loader": "0.5.1", "react": "16.1.0", "react-contextmenu": "2.8.0", - "react-dom": "16.1.0", + "react-dom": "16.1.1", "react-draggable": "3.0.3", "react-intl": "2.4.0", "react-intl-redux": "0.7.0", From bc3447a9fef17fd62a38aa1c7b83993211c2eb10 Mon Sep 17 00:00:00 2001 From: Matthew Taylor Date: Mon, 13 Nov 2017 15:15:18 -0500 Subject: [PATCH 14/28] Make selector column responsive to paint editor width This is for https://github.com/LLK/scratch-paint/pull/194, making the selector area smaller on tablet so as to create more space for the paint editor --- src/components/asset-panel/selector.css | 12 ++++++++++++ src/css/units.css | 4 ++++ src/lib/layout-constants.js | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css index 2a4de1a25..63e686832 100644 --- a/src/components/asset-panel/selector.css +++ b/src/components/asset-panel/selector.css @@ -1,4 +1,5 @@ @import "../../css/colors.css"; +@import "../../css/units.css"; .wrapper { width: 150px; @@ -51,3 +52,14 @@ min-height: 5rem; margin: 1rem auto; } + +@media only screen and (max-width: $full-size-paint) { + .wrapper { + width: 80px; + } + + .list-item { + width: 4rem; + min-height: 4rem; + } +} diff --git a/src/css/units.css b/src/css/units.css index 3de6174a2..3e7f73f36 100644 --- a/src/css/units.css +++ b/src/css/units.css @@ -9,3 +9,7 @@ $stage-menu-height: 2.75rem; $library-header-height: 4.375rem; $form-radius: calc($space / 2); + +/* layout contants from `layout-constants.js` */ +$full-size: 1095px; +$full-size-paint: 1249px; diff --git a/src/lib/layout-constants.js b/src/lib/layout-constants.js index 672c8013f..67c2ae44c 100644 --- a/src/lib/layout-constants.js +++ b/src/lib/layout-constants.js @@ -3,5 +3,6 @@ export default { fullStageHeight: 360, smallerStageWidth: 480 * 0.85, smallerStageHeight: 360 * 0.85, - fullSizeMinWidth: 1096 + fullSizeMinWidth: 1096, + fullSizePaintMinWidth: 1250 }; From dd7d9e9e226c4e5013b9653b5a7ac8a876a61bcb Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 14 Nov 2017 15:02:24 -0500 Subject: [PATCH 15/28] edit snapshots --- .../components/__snapshots__/icon-button.test.jsx.snap | 1 + .../components/__snapshots__/sound-editor.test.jsx.snap | 7 +++++++ .../__snapshots__/sprite-selector-item.test.jsx.snap | 2 ++ 3 files changed, 10 insertions(+) diff --git a/test/unit/components/__snapshots__/icon-button.test.jsx.snap b/test/unit/components/__snapshots__/icon-button.test.jsx.snap index 3a26f0acf..b5f3e7a10 100644 --- a/test/unit/components/__snapshots__/icon-button.test.jsx.snap +++ b/test/unit/components/__snapshots__/icon-button.test.jsx.snap @@ -4,6 +4,7 @@ exports[`IconButtonComponent matches snapshot 1`] = `
Date: Wed, 15 Nov 2017 09:07:40 +0000 Subject: [PATCH 16/28] chore(package): update enzyme-adapter-react-16 to version 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a2e71f91..817fb17f3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.7", "enzyme": "^3.1.0", - "enzyme-adapter-react-16": "1.0.4", + "enzyme-adapter-react-16": "1.1.0", "eslint": "^4.7.1", "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.7.0", From 0a76f1c7f5e459b84fa799c585661905744ac5c5 Mon Sep 17 00:00:00 2001 From: DD Date: Thu, 16 Nov 2017 11:27:49 -0500 Subject: [PATCH 17/28] remake toolbox xml when target changes --- src/containers/blocks.jsx | 19 ++++++++++++------- src/lib/make-toolbox-xml.js | 17 ++++++++++------- src/reducers/toolbox.js | 4 +--- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 471dc6cd0..6f8ce8177 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -172,6 +172,11 @@ class Blocks extends React.Component { this.workspace.reportValue(data.id, data.value); } onWorkspaceUpdate (data) { + // When we change sprites, update the toolbox to have the new sprite's blocks + if (this.props.vm.editingTarget) { + this.props.updateToolboxState(makeToolboxXML(this.props.vm.editingTarget.id)); + } + if (this.props.vm.editingTarget && !this.state.workspaceMetrics[this.props.vm.editingTarget.id]) { this.onWorkspaceMetricsChange(); } @@ -193,8 +198,8 @@ class Blocks extends React.Component { handleExtensionAdded (blocksInfo) { this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json)); const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML(); - const toolboxXML = makeToolboxXML(dynamicBlocksXML); - this.props.onExtensionAdded(toolboxXML); + const toolboxXML = makeToolboxXML(this.props.vm.editingTarget.id, dynamicBlocksXML); + this.props.updateToolboxState(toolboxXML); } handleCategorySelected (categoryName) { this.workspace.toolbox_.setSelectedCategoryByName(categoryName); @@ -220,7 +225,7 @@ class Blocks extends React.Component { vm, isVisible, onActivateColorPicker, - onExtensionAdded, + updateToolboxState, onRequestCloseExtensionLibrary, toolboxXML, ...props @@ -257,7 +262,6 @@ Blocks.propTypes = { extensionLibraryVisible: PropTypes.bool, isVisible: PropTypes.bool, onActivateColorPicker: PropTypes.func, - onExtensionAdded: PropTypes.func, onRequestCloseExtensionLibrary: PropTypes.func, options: PropTypes.shape({ media: PropTypes.string, @@ -281,6 +285,7 @@ Blocks.propTypes = { comments: PropTypes.bool }), toolboxXML: PropTypes.string, + updateToolboxState: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired }; @@ -322,11 +327,11 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ onActivateColorPicker: callback => dispatch(activateColorPicker(callback)), - onExtensionAdded: toolboxXML => { - dispatch(updateToolbox(toolboxXML)); - }, onRequestCloseExtensionLibrary: () => { dispatch(closeExtensionLibrary()); + }, + updateToolboxState: toolboxXML => { + dispatch(updateToolbox(toolboxXML)); } }); diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 315e1388c..94ffc44ab 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -120,9 +120,9 @@ const motion = ` ${blockSeparator} ${blockSeparator} - - - + + + ${categorySeparator} `; @@ -229,10 +229,10 @@ const looks = ` ${blockSeparator} - + - + ${categorySeparator} `; @@ -613,10 +613,11 @@ const xmlOpen = ''; const xmlClose = ''; /** + * @param {!string} targetId - The current editing target * @param {string?} categoriesXML - null for default toolbox, or an XML string with elements. * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox. */ -const makeToolboxXML = function (categoriesXML) { +const makeToolboxXML = function (targetId, categoriesXML) { const gap = [categorySeparator]; const everything = [ @@ -636,7 +637,9 @@ const makeToolboxXML = function (categoriesXML) { } everything.push(xmlClose); - return everything.join('\n'); + return everything.join('\n').split('') + .join(targetId); // targetIds are designed to not break XML. + // @todo consider something less hacky? }; export default makeToolboxXML; diff --git a/src/reducers/toolbox.js b/src/reducers/toolbox.js index 4b06a807c..0d3257df1 100644 --- a/src/reducers/toolbox.js +++ b/src/reducers/toolbox.js @@ -1,9 +1,7 @@ const UPDATE_TOOLBOX = 'scratch-gui/toolbox/UPDATE_TOOLBOX'; -import makeToolboxXML from '../lib/make-toolbox-xml'; - const initialState = { - toolboxXML: makeToolboxXML() + toolboxXML: '' }; const reducer = function (state, action) { From abb5d5b7ae7c339e690feaa322c447e4f2e6a86d Mon Sep 17 00:00:00 2001 From: DD Date: Thu, 16 Nov 2017 11:57:05 -0500 Subject: [PATCH 18/28] Change makexml constants to functions that take targetID --- src/lib/make-toolbox-xml.js | 78 ++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 94ffc44ab..608cf92b3 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -2,7 +2,8 @@ const categorySeparator = ''; const blockSeparator = ''; // At default scale, about 28px -const motion = ` +const motion = function (targetId) { + return ` @@ -120,14 +121,16 @@ const motion = ` ${blockSeparator} ${blockSeparator} - - - + + + ${categorySeparator} -`; + `; +}; -const looks = ` +const looks = function (targetId) { + return ` @@ -229,15 +232,17 @@ const looks = ` ${blockSeparator} - + - + ${categorySeparator} -`; + `; +}; -const sound = ` +const sound = function () { + return ` @@ -284,9 +289,11 @@ const sound = ` ${categorySeparator} -`; + `; +}; -const events = ` +const events = function () { + return ` @@ -317,9 +324,11 @@ const events = ` ${categorySeparator} -`; + `; +}; -const control = ` +const control = function () { + return ` @@ -354,9 +363,11 @@ const control = ` ${categorySeparator} -`; + `; +}; -const sensing = ` +const sensing = function () { + return ` @@ -422,9 +433,11 @@ const sensing = ` ${categorySeparator} -`; + `; +}; -const operators = ` +const operators = function () { + return ` @@ -602,12 +615,15 @@ const operators = ` ${categorySeparator} -`; + `; +}; -const data = ` +const data = function () { + return ` -`; + `; +}; const xmlOpen = ''; const xmlClose = ''; @@ -622,14 +638,14 @@ const makeToolboxXML = function (targetId, categoriesXML) { const everything = [ xmlOpen, - motion, gap, - looks, gap, - sound, gap, - events, gap, - control, gap, - sensing, gap, - operators, gap, - data + motion(targetId), gap, + looks(targetId), gap, + sound(targetId), gap, + events(targetId), gap, + control(targetId), gap, + sensing(targetId), gap, + operators(targetId), gap, + data(targetId) ]; if (categoriesXML) { @@ -637,9 +653,7 @@ const makeToolboxXML = function (targetId, categoriesXML) { } everything.push(xmlClose); - return everything.join('\n').split('') - .join(targetId); // targetIds are designed to not break XML. - // @todo consider something less hacky? + return everything.join('\n'); }; export default makeToolboxXML; From bbbbcf4369aa535afefd8b2b41366afb254460c1 Mon Sep 17 00:00:00 2001 From: DD Date: Thu, 16 Nov 2017 17:07:05 -0500 Subject: [PATCH 19/28] add comment --- src/containers/blocks.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 6f8ce8177..f3e46dfaf 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -184,6 +184,8 @@ class Blocks extends React.Component { // 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); + // @todo This line rerenders toolbox, and the change in the toolbox XML also rerenders the toolbox. + // We should only rerender the toolbox once. See https://github.com/LLK/scratch-gui/issues/901 this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace); this.workspace.addChangeListener(this.props.vm.blockListener); From 2dfa862fab2a3e5fb27ab380bd95537a220ab300 Mon Sep 17 00:00:00 2001 From: DD Date: Thu, 16 Nov 2017 17:15:40 -0500 Subject: [PATCH 20/28] Add back in initialized toolbox XML --- src/reducers/toolbox.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/reducers/toolbox.js b/src/reducers/toolbox.js index 0d3257df1..3f559884f 100644 --- a/src/reducers/toolbox.js +++ b/src/reducers/toolbox.js @@ -1,7 +1,9 @@ const UPDATE_TOOLBOX = 'scratch-gui/toolbox/UPDATE_TOOLBOX'; +import makeToolboxXML from '../lib/make-toolbox-xml'; const initialState = { - toolboxXML: '' + // @todo switch this to make the stage's XML + toolboxXML: makeToolboxXML('') }; const reducer = function (state, action) { From e29184e1ce9c74676e391e43ed8b4dded412e0d9 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Fri, 17 Nov 2017 15:09:57 -0500 Subject: [PATCH 21/28] Warn user before navigating away/unloading --- src/index.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/index.jsx b/src/index.jsx index 6d301f516..e55fc0ef4 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -7,6 +7,11 @@ import ProjectLoaderHOC from './lib/project-loader-hoc.jsx'; import styles from './index.css'; +if (process.env.NODE_ENV === 'production' && typeof window === 'object') { + // Warn before navigating away + window.onbeforeunload = () => true; +} + const App = AppStateHOC(ProjectLoaderHOC(GUI)); const appTarget = document.createElement('div'); From 145ade6ffc98ba1ce378002b04cd197651134ded Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Fri, 17 Nov 2017 15:44:44 -0500 Subject: [PATCH 22/28] Fix integration tests by clearing reload warning --- test/integration/test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/integration/test.js b/test/integration/test.js index 86729cf27..8cb23329d 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -37,6 +37,7 @@ describe('costumes, sounds and variables', () => { test('Blocks report when clicked in the toolbox', async () => { await driver.get(`file://${uri}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await clickText('Blocks'); await clickText('Operators', blocksTabScope); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation @@ -48,6 +49,7 @@ describe('costumes, sounds and variables', () => { test('Switching sprites updates the block menus', async () => { await driver.get(`file://${uri}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await clickText('Sound', blocksTabScope); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation // "meow" sound block should be visible @@ -61,6 +63,7 @@ describe('costumes, sounds and variables', () => { test('Adding a costume', async () => { await driver.get(`file://${uri}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await clickText('Costumes'); await clickText('Add Costume'); const el = await findByXpath("//input[@placeholder='what are you looking for?']"); @@ -74,6 +77,7 @@ describe('costumes, sounds and variables', () => { test('Adding a sound', async () => { await driver.get(`file://${uri}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await clickText('Sounds'); await clickText('Add Sound'); const el = await findByXpath("//input[@placeholder='what are you looking for?']"); @@ -100,6 +104,7 @@ describe('costumes, sounds and variables', () => { test('Load a project by ID', async () => { const projectId = '96708228'; await driver.get(`file://${uri}#${projectId}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await new Promise(resolve => setTimeout(resolve, 2000)); await clickXpath('//img[@title="Go"]'); await new Promise(resolve => setTimeout(resolve, 2000)); @@ -110,6 +115,7 @@ describe('costumes, sounds and variables', () => { test('Creating variables', async () => { await driver.get(`file://${uri}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await clickText('Blocks'); await clickText('Data', blocksTabScope); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation @@ -134,6 +140,7 @@ describe('costumes, sounds and variables', () => { test('Importing extensions', async () => { await driver.get(`file://${uri}`); + await driver.executeScript('window.onbeforeunload = undefined;'); await clickText('Blocks'); await clickText('Extensions'); await clickText('Pen', modalScope); // Modal closes From c2d5cfe7f98539492687cf9e07492c8371a52e8f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 17 Nov 2017 21:12:40 +0000 Subject: [PATCH 23/28] chore(package): update react-tabs to version 2.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a2e71f91..9729c28d1 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "react-redux": "5.0.6", "react-responsive": "3.0.0", "react-style-proptype": "3.1.0", - "react-tabs": "2.1.0", + "react-tabs": "2.1.1", "react-test-renderer": "16.1.0", "redux": "3.7.0", "redux-mock-store": "^1.2.3", From f3e76acd53e3a49f60a6b5938ba41f66a520cc44 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Fri, 17 Nov 2017 16:18:16 -0500 Subject: [PATCH 24/28] Capture onbeforeunload unsetter in a helper --- test/helpers/selenium-helper.js | 11 ++++++++++- test/integration/examples.test.js | 9 +++++---- test/integration/test.js | 24 +++++++++--------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js index c4f36ca05..1021b48ec 100644 --- a/test/helpers/selenium-helper.js +++ b/test/helpers/selenium-helper.js @@ -15,7 +15,8 @@ class SeleniumHelper { 'findByText', 'findByXpath', 'getDriver', - 'getLogs' + 'getLogs', + 'loadUri' ]); } @@ -34,6 +35,14 @@ class SeleniumHelper { return this.findByXpath(`//body//${scope || '*'}//*[contains(text(), '${text}')]`); } + loadUri (uri) { + return this.driver + .get(`file://${uri}`) + .then(() => ( + this.driver.executeScript('window.onbeforeunload = undefined;') + )); + } + clickXpath (xpath) { return this.findByXpath(xpath).then(el => el.click()); } diff --git a/test/integration/examples.test.js b/test/integration/examples.test.js index 7fffa903a..908dbfd36 100644 --- a/test/integration/examples.test.js +++ b/test/integration/examples.test.js @@ -9,7 +9,8 @@ const { clickXpath, findByXpath, getDriver, - getLogs + getLogs, + loadUri } = new SeleniumHelper(); const errorWhitelist = [ @@ -31,7 +32,7 @@ describe('player example', () => { test('Load a project by ID', async () => { const projectId = '96708228'; - await driver.get(`file://${uri}#${projectId}`); + await loadUri(`${uri}#${projectId}`); await new Promise(resolve => setTimeout(resolve, 2000)); await clickXpath('//img[@title="Go"]'); await new Promise(resolve => setTimeout(resolve, 2000)); @@ -54,7 +55,7 @@ describe('blocks example', () => { test('Load a project by ID', async () => { const projectId = '96708228'; - await driver.get(`file://${uri}#${projectId}`); + await loadUri(`${uri}#${projectId}`); await new Promise(resolve => setTimeout(resolve, 2000)); await clickXpath('//img[@title="Go"]'); await new Promise(resolve => setTimeout(resolve, 2000)); @@ -64,7 +65,7 @@ describe('blocks example', () => { }); test('Change categories', async () => { - await driver.get(`file://${uri}`); + await loadUri(`${uri}`); await clickText('Looks'); await clickText('Sound'); await clickText('Events'); diff --git a/test/integration/test.js b/test/integration/test.js index 8cb23329d..c792e80b0 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -8,7 +8,8 @@ const { findByText, findByXpath, getDriver, - getLogs + getLogs, + loadUri } = new SeleniumHelper(); const uri = path.resolve(__dirname, '../../build/index.html'); @@ -36,8 +37,7 @@ describe('costumes, sounds and variables', () => { test('Blocks report when clicked in the toolbox', async () => { - await driver.get(`file://${uri}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(uri); await clickText('Blocks'); await clickText('Operators', blocksTabScope); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation @@ -48,8 +48,7 @@ describe('costumes, sounds and variables', () => { }); test('Switching sprites updates the block menus', async () => { - await driver.get(`file://${uri}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(uri); await clickText('Sound', blocksTabScope); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation // "meow" sound block should be visible @@ -62,8 +61,7 @@ describe('costumes, sounds and variables', () => { }); test('Adding a costume', async () => { - await driver.get(`file://${uri}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(uri); await clickText('Costumes'); await clickText('Add Costume'); const el = await findByXpath("//input[@placeholder='what are you looking for?']"); @@ -76,8 +74,7 @@ describe('costumes, sounds and variables', () => { }); test('Adding a sound', async () => { - await driver.get(`file://${uri}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(uri); await clickText('Sounds'); await clickText('Add Sound'); const el = await findByXpath("//input[@placeholder='what are you looking for?']"); @@ -103,8 +100,7 @@ describe('costumes, sounds and variables', () => { test('Load a project by ID', async () => { const projectId = '96708228'; - await driver.get(`file://${uri}#${projectId}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(`${uri}#${projectId}`); await new Promise(resolve => setTimeout(resolve, 2000)); await clickXpath('//img[@title="Go"]'); await new Promise(resolve => setTimeout(resolve, 2000)); @@ -114,8 +110,7 @@ describe('costumes, sounds and variables', () => { }); test('Creating variables', async () => { - await driver.get(`file://${uri}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(uri); await clickText('Blocks'); await clickText('Data', blocksTabScope); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation @@ -139,8 +134,7 @@ describe('costumes, sounds and variables', () => { }); test('Importing extensions', async () => { - await driver.get(`file://${uri}`); - await driver.executeScript('window.onbeforeunload = undefined;'); + await loadUri(uri); await clickText('Blocks'); await clickText('Extensions'); await clickText('Pen', modalScope); // Modal closes From 609da8b571dfe6a89fe8ca38b355b9cc9e7f60b7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 19 Nov 2017 17:27:30 +0000 Subject: [PATCH 25/28] chore(package): update eslint-plugin-react to version 7.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a2e71f91..545a8d008 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "eslint": "^4.7.1", "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.7.0", - "eslint-plugin-react": "^7.2.1", + "eslint-plugin-react": "^7.5.1", "file-loader": "1.1.5", "get-float-time-domain-data": "0.1.0", "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder", From a8ea0ff56c85599e11133b05564ad4b0e445197c Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 16 Nov 2017 11:38:47 -0500 Subject: [PATCH 26/28] Use the vm editing target to make sure sound reference is up-to-date. --- src/containers/sound-tab.jsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index 1984923d5..eb3e8c269 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -52,20 +52,18 @@ class SoundTab extends React.Component { render () { const { - editingTarget, - sprites, - stage, + vm, onNewSoundFromLibraryClick, onNewSoundFromRecordingClick } = this.props; - const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage; + const sprite = vm.editingTarget.sprite; - if (!target) { + if (!sprite) { return null; } - const sounds = target.sounds ? target.sounds.map(sound => ( + const sounds = sprite.sounds ? sprite.sounds.map(sound => ( { url: soundIcon, name: sound.name @@ -106,7 +104,7 @@ class SoundTab extends React.Component { onDeleteClick={this.handleDeleteSound} onItemClick={this.handleSelectSound} > - {editingTarget && target.sounds && target.sounds.length > 0 ? ( + {sprite.sounds && sprite.sounds.length > 0 ? ( ) : null} {this.props.soundRecorderVisible ? ( From 2389c09324df81f77e1ddd09bfbf6fd116afe64a Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 20 Nov 2017 10:23:53 -0500 Subject: [PATCH 27/28] Add integration test for deleting sound --- test/helpers/selenium-helper.js | 11 +++++++++-- test/integration/test.js | 15 ++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js index c4f36ca05..07002a94a 100644 --- a/test/helpers/selenium-helper.js +++ b/test/helpers/selenium-helper.js @@ -4,7 +4,7 @@ import bindAll from 'lodash.bindall'; import 'chromedriver'; // register path import webdriver from 'selenium-webdriver'; -const {By, until} = webdriver; +const {By, until, Button} = webdriver; class SeleniumHelper { constructor () { @@ -15,7 +15,8 @@ class SeleniumHelper { 'findByText', 'findByXpath', 'getDriver', - 'getLogs' + 'getLogs', + 'rightClickText' ]); } @@ -42,6 +43,12 @@ class SeleniumHelper { return this.findByText(text, scope).then(el => el.click()); } + rightClickText (text, scope) { + return this.findByText(text, scope).then(el => this.driver.actions() + .click(el, Button.RIGHT) + .perform()); + } + clickButton (text) { return this.clickXpath(`//button[contains(text(), '${text}')]`); } diff --git a/test/integration/test.js b/test/integration/test.js index 86729cf27..c2b5f2945 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -8,7 +8,8 @@ const { findByText, findByXpath, getDriver, - getLogs + getLogs, + rightClickText } = new SeleniumHelper(); const uri = path.resolve(__dirname, '../../build/index.html'); @@ -34,7 +35,6 @@ describe('costumes, sounds and variables', () => { await driver.quit(); }); - test('Blocks report when clicked in the toolbox', async () => { await driver.get(`file://${uri}`); await clickText('Blocks'); @@ -75,15 +75,20 @@ describe('costumes, sounds and variables', () => { test('Adding a sound', async () => { await driver.get(`file://${uri}`); await clickText('Sounds'); + + // Delete the sound + await rightClickText('meow', soundsTabScope); + await clickText('delete', soundsTabScope); + await driver.switchTo().alert() + .accept(); + + // Add a sound await clickText('Add Sound'); const el = await findByXpath("//input[@placeholder='what are you looking for?']"); await el.sendKeys('chom'); await clickText('chomp'); // Should close the modal, then click the sounds in the selector - await clickText('meow', soundsTabScope); await clickText('chomp', soundsTabScope); await clickXpath('//button[@title="Play"]'); - await clickText('meow', soundsTabScope); - await clickXpath('//button[@title="Play"]'); await clickText('Louder'); await clickText('Softer'); From 022bec3e138fc8afe4291a281cb3a3f70bf253a0 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 20 Nov 2017 10:44:30 -0500 Subject: [PATCH 28/28] Fix if no editing target --- src/containers/sound-tab.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index eb3e8c269..6143175dd 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -57,12 +57,12 @@ class SoundTab extends React.Component { onNewSoundFromRecordingClick } = this.props; - const sprite = vm.editingTarget.sprite; - - if (!sprite) { + if (!vm.editingTarget) { return null; } + const sprite = vm.editingTarget.sprite; + const sounds = sprite.sounds ? sprite.sounds.map(sound => ( { url: soundIcon,