From 972a33e916fc21ad754e9378b87e246609c6dce2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 22 Aug 2018 17:19:45 +0000 Subject: [PATCH 1/6] chore(package): update scratch-paint to version 0.2.0-prerelease.20180822171824 Closes #2926 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d4a31020b..2e19dd389 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "scratch-audio": "0.1.0-prerelease.20180625202813", "scratch-blocks": "0.1.0-prerelease.1534513944", "scratch-l10n": "3.0.20180817135616", - "scratch-paint": "0.2.0-prerelease.20180816205442", + "scratch-paint": "0.2.0-prerelease.20180822171824", "scratch-render": "0.1.0-prerelease.20180811013828", "scratch-storage": "0.5.1", "scratch-svg-renderer": "0.2.0-prerelease.20180817005452", From 98100cfa56394fc9e39e2e0f9e130fafada6759a Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 23 Aug 2018 10:12:37 -0400 Subject: [PATCH 2/6] Remove confirmation dialog when deleting a sprite, costume, or sound. --- src/containers/sprite-selector-item.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index a0197ad6d..a0049076e 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -84,10 +84,7 @@ class SpriteSelectorItem extends React.Component { } handleDelete (e) { e.stopPropagation(); // To prevent from bubbling back to handleClick - // eslint-disable-next-line no-alert - if (window.confirm(this.props.intl.formatMessage(messages.deleteSpriteConfirmation))) { - this.props.onDeleteButtonClick(this.props.id); - } + this.props.onDeleteButtonClick(this.props.id); } handleDuplicate (e) { e.stopPropagation(); // To prevent from bubbling back to handleClick From 2eb70564faf9dd74da7703451cb7b6fed1537a73 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 23 Aug 2018 10:18:41 -0400 Subject: [PATCH 3/6] Remove localized confirmation dialog message since confirmation doesn't exist anymore. --- src/containers/sprite-selector-item.jsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index a0049076e..b2fb8f877 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -2,7 +2,6 @@ import bindAll from 'lodash.bindall'; import PropTypes from 'prop-types'; import React from 'react'; import {connect} from 'react-redux'; -import {defineMessages, injectIntl, intlShape} from 'react-intl'; import {setHoveredSprite} from '../reducers/hovered-target'; import {updateAssetDrag} from '../reducers/asset-drag'; @@ -12,14 +11,6 @@ import SpriteSelectorItemComponent from '../components/sprite-selector-item/spri const dragThreshold = 3; // Same as the block drag threshold -const messages = defineMessages({ - deleteSpriteConfirmation: { - defaultMessage: 'Are you sure you want to delete this?', - description: 'Confirmation for deleting sprites', - id: 'gui.spriteSelectorItem.deleteSpriteConfirmation' - } -}); - class SpriteSelectorItem extends React.Component { constructor (props) { super(props); @@ -141,7 +132,6 @@ SpriteSelectorItem.propTypes = { dragType: PropTypes.string, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), index: PropTypes.number, - intl: intlShape.isRequired, name: PropTypes.string, onClick: PropTypes.func, onDeleteButtonClick: PropTypes.func, @@ -169,4 +159,4 @@ const mapDispatchToProps = dispatch => ({ export default connect( mapStateToProps, mapDispatchToProps -)(injectIntl(SpriteSelectorItem)); +)(SpriteSelectorItem); From 718fb7255d1f20e20660da122832f56f369af8d9 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 23 Aug 2018 11:03:29 -0400 Subject: [PATCH 4/6] Fix up tests. --- test/integration/sounds.test.js | 2 -- test/integration/sprites.test.js | 2 -- test/unit/containers/sprite-selector-item.test.jsx | 13 +------------ 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/test/integration/sounds.test.js b/test/integration/sounds.test.js index 7c306a5ee..ca1a3d275 100644 --- a/test/integration/sounds.test.js +++ b/test/integration/sounds.test.js @@ -33,8 +33,6 @@ describe('Working with sounds', () => { // Delete the sound await rightClickText('Meow', scope.soundsTab); await clickText('delete', scope.soundsTab); - await driver.switchTo().alert() - .accept(); // Add it back await clickXpath('//button[@aria-label="Choose a Sound"]'); diff --git a/test/integration/sprites.test.js b/test/integration/sprites.test.js index 41f2eec3a..5b99efcb6 100644 --- a/test/integration/sprites.test.js +++ b/test/integration/sprites.test.js @@ -55,8 +55,6 @@ describe('Working with sprites', () => { await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation await rightClickText('Sprite1', scope.spriteTile); await clickText('delete', scope.spriteTile); - await driver.switchTo().alert() - .accept(); // Confirm that the stage has been switched to await findByText('Stage selected: no motion blocks'); const logs = await getLogs(); diff --git a/test/unit/containers/sprite-selector-item.test.jsx b/test/unit/containers/sprite-selector-item.test.jsx index 85032603d..f133c02a2 100644 --- a/test/unit/containers/sprite-selector-item.test.jsx +++ b/test/unit/containers/sprite-selector-item.test.jsx @@ -48,22 +48,11 @@ describe('SpriteSelectorItem Container', () => { onDeleteButtonClick = jest.fn(); dispatchSetHoveredSprite = jest.fn(); selected = true; - // Mock window.confirm() which is called when the close button is clicked. - global.confirm = jest.fn(() => true); }); - test('should confirm if the user really wants to delete the sprite', () => { + test('should delete the sprite', () => { const wrapper = mountWithIntl(getContainer()); wrapper.find(CloseButton).simulate('click'); - expect(global.confirm).toHaveBeenCalled(); expect(onDeleteButtonClick).toHaveBeenCalledWith(1337); }); - - test('should not delete the sprite if the user cancels', () => { - global.confirm = jest.fn(() => false); - const wrapper = mountWithIntl(getContainer()); - wrapper.find(CloseButton).simulate('click'); - expect(global.confirm).toHaveBeenCalled(); - expect(onDeleteButtonClick).not.toHaveBeenCalled(); - }); }); From 2fb6a4849d3032985892e088efba13e831100513 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Thu, 23 Aug 2018 11:08:38 -0400 Subject: [PATCH 5/6] Fix warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - empty MapDispatchToProps (dispatch) - remove `on…` VM listeners from props passed to wrapped component. --- src/containers/controls.jsx | 4 +++- src/containers/error-boundary.jsx | 2 +- src/lib/vm-listener-hoc.jsx | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/containers/controls.jsx b/src/containers/controls.jsx index 7cc52870f..2869a047a 100644 --- a/src/containers/controls.jsx +++ b/src/containers/controls.jsx @@ -64,5 +64,7 @@ const mapStateToProps = state => ({ projectRunning: state.scratchGui.vmStatus.running, turbo: state.scratchGui.vmStatus.turbo }); +// no-op function to prevent dispatch prop being passed to component +const mapDispatchToProps = () => ({}); -export default connect(mapStateToProps)(Controls); +export default connect(mapStateToProps, mapDispatchToProps)(Controls); diff --git a/src/containers/error-boundary.jsx b/src/containers/error-boundary.jsx index fbb15f3b9..6485a41b8 100644 --- a/src/containers/error-boundary.jsx +++ b/src/containers/error-boundary.jsx @@ -78,6 +78,6 @@ const mapStateToProps = state => ({ }); // no-op function to prevent dispatch prop being passed to component -const mapDispatchToProps = () => {}; +const mapDispatchToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(ErrorBoundary); diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index b2256b82a..67e70f900 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -89,6 +89,10 @@ const vmListenerHOC = function (WrappedComponent) { onKeyUp, onMonitorsUpdate, onTargetsUpdate, + onProjectRunStart, + onProjectRunStop, + onTurboModeOff, + onTurboModeOn, /* eslint-enable no-unused-vars */ ...props } = this.props; From 8bfaf69d6e0802917a94121770d9e37debd07996 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Thu, 23 Aug 2018 14:57:08 -0400 Subject: [PATCH 6/6] Inject fonts thumbnails (#2948) --- src/containers/sprite-selector-item.jsx | 55 ++++++++++++++++--- .../containers/sprite-selector-item.test.jsx | 9 +++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index b2fb8f877..01e617c8e 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -6,15 +6,20 @@ import {connect} from 'react-redux'; import {setHoveredSprite} from '../reducers/hovered-target'; import {updateAssetDrag} from '../reducers/asset-drag'; import {getEventXY} from '../lib/touch-utils'; +import VM from 'scratch-vm'; +import {SVGRenderer} from 'scratch-svg-renderer'; import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx'; const dragThreshold = 3; // Same as the block drag threshold +// Contains 'font-family', but doesn't only contain 'font-family="none"' +const HAS_FONT_REGEXP = 'font-family(?!="none")'; class SpriteSelectorItem extends React.Component { constructor (props) { super(props); bindAll(this, [ + 'getCostumeUrl', 'handleClick', 'handleDelete', 'handleDuplicate', @@ -25,6 +30,34 @@ class SpriteSelectorItem extends React.Component { 'handleMouseMove', 'handleMouseUp' ]); + this.svgRenderer = new SVGRenderer(); + // Asset ID of the SVG currently in SVGRenderer + this.svgRendererAssetId = null; + } + getCostumeUrl () { + if (this.props.costumeURL) return this.props.costumeURL; + if (!this.props.assetId) return null; + + const storage = this.props.vm.runtime.storage; + const asset = storage.get(this.props.assetId); + // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag. + // Avoid parsing the SVG when possible, since it's expensive. + if (asset.assetType === storage.AssetType.ImageVector) { + // If the asset ID has not changed, no need to re-parse + if (this.svgRendererAssetId === this.props.assetId) { + return this.cachedUrl; + } + + const svgString = this.props.vm.runtime.storage.get(this.props.assetId).decodeText(); + if (svgString.match(HAS_FONT_REGEXP)) { + this.svgRendererAssetId = this.props.assetId; + this.svgRenderer.loadString(svgString); + const svgText = this.svgRenderer.toString(true /* shouldInjectFonts */); + this.cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`; + return this.cachedUrl; + } + } + return this.props.vm.runtime.storage.get(this.props.assetId).encodeDataURI(); } handleMouseUp () { this.initialOffset = null; @@ -49,7 +82,7 @@ class SpriteSelectorItem extends React.Component { const dy = currentOffset.y - this.initialOffset.y; if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) { this.props.onDrag({ - img: this.props.costumeURL, + img: this.getCostumeUrl(), currentOffset: currentOffset, dragging: true, dragType: this.props.dragType, @@ -103,11 +136,14 @@ class SpriteSelectorItem extends React.Component { onExportButtonClick, dragPayload, receivedBlocks, + costumeURL, + vm, /* eslint-enable no-unused-vars */ ...props } = this.props; return ( ({ - costumeURL: costumeURL || (assetId && state.scratchGui.vm.runtime.storage.get(assetId).encodeDataURI()), +const mapStateToProps = (state, {id}) => ({ dragging: state.scratchGui.assetDrag.dragging, receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks && - state.scratchGui.hoveredTarget.sprite === id + state.scratchGui.hoveredTarget.sprite === id, + vm: state.scratchGui.vm }); const mapDispatchToProps = dispatch => ({ dispatchSetHoveredSprite: spriteId => { @@ -155,8 +192,12 @@ const mapDispatchToProps = dispatch => ({ onDrag: data => dispatch(updateAssetDrag(data)) }); - -export default connect( +const ConnectedComponent = connect( mapStateToProps, mapDispatchToProps )(SpriteSelectorItem); + +export { + ConnectedComponent as default, + HAS_FONT_REGEXP // Exposed for testing +}; diff --git a/test/unit/containers/sprite-selector-item.test.jsx b/test/unit/containers/sprite-selector-item.test.jsx index f133c02a2..25200a1fb 100644 --- a/test/unit/containers/sprite-selector-item.test.jsx +++ b/test/unit/containers/sprite-selector-item.test.jsx @@ -4,6 +4,7 @@ import configureStore from 'redux-mock-store'; import {Provider} from 'react-redux'; import SpriteSelectorItem from '../../../src/containers/sprite-selector-item'; +import {HAS_FONT_REGEXP} from '../../../src/containers/sprite-selector-item'; import CloseButton from '../../../src/components/close-button/close-button'; describe('SpriteSelectorItem Container', () => { @@ -55,4 +56,12 @@ describe('SpriteSelectorItem Container', () => { wrapper.find(CloseButton).simulate('click'); expect(onDeleteButtonClick).toHaveBeenCalledWith(1337); }); + + test('Has font regexp works', () => { + expect('font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy(); + expect('font-family="none" font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy(); + expect('font-family = "Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy(); + + expect('font-family="none"'.match(HAS_FONT_REGEXP)).toBeFalsy(); + }); });