From 6b88a9b4aa79089996c3f2c7c98599c64bab0a90 Mon Sep 17 00:00:00 2001 From: Florrie Date: Fri, 26 Jan 2018 17:16:24 -0400 Subject: [PATCH 01/65] Detect wheel event on canvas and post data to VM This, along with the related VM pull request, fixes LLK/scratch-vm#865. This adds the GUI code which posts wheel scroll data to the VM's mouseWheel IO (which, in turn, activates relevant "when key pressed" blocks). --- src/containers/stage.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index 9f76aa7ab..888897e44 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -30,6 +30,7 @@ class Stage extends React.Component { 'onMouseDown', 'onStartDrag', 'onStopDrag', + 'onWheel', 'updateRect', 'questionListener', 'setCanvas' @@ -97,6 +98,7 @@ class Stage extends React.Component { document.addEventListener('touchend', this.onMouseUp); canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('touchstart', this.onMouseDown); + canvas.addEventListener('wheel', this.onWheel); } detachMouseEvents (canvas) { document.removeEventListener('mousemove', this.onMouseMove); @@ -105,6 +107,7 @@ class Stage extends React.Component { document.removeEventListener('touchend', this.onMouseUp); canvas.removeEventListener('mousedown', this.onMouseDown); canvas.removeEventListener('touchstart', this.onMouseDown); + canvas.removeEventListener('wheel', this.onWheel); } attachRectEvents () { window.addEventListener('resize', this.updateRect); @@ -227,6 +230,13 @@ class Stage extends React.Component { this.setState({colorInfo: null}); } } + onWheel (e) { + const data = { + deltaX: e.deltaX, + deltaY: e.deltaY + }; + this.props.vm.postIOData('mouseWheel', data); + } cancelMouseDownTimeout () { if (this.state.mouseDownTimeoutId !== null) { clearTimeout(this.state.mouseDownTimeoutId); From b2f048b05fac9b6d0331662cc11d5a962d733f89 Mon Sep 17 00:00:00 2001 From: ivanixgames Date: Sun, 11 Feb 2018 07:28:23 +0800 Subject: [PATCH 02/65] Fix issue 1212 - Call scrollToCategoryByName('My Blocks') after custom block is created - Cannot assume custom blocks section will be last category - May pose issue during localization efforts --- src/containers/blocks.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index a53b48760..bedbbdbad 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -252,7 +252,10 @@ class Blocks extends React.Component { } handleCustomProceduresClose (data) { this.props.onRequestCloseCustomProcedures(data); - this.workspace.refreshToolboxSelection_(); + const ws = this.workspace; + ws.refreshToolboxSelection_(); + // @todo ensure this does not break on localization + ws.toolbox_.scrollToCategoryByName('My Blocks'); } render () { /* eslint-disable no-unused-vars */ From ecdb9525332b030d5f096140e08c9bb54921920d Mon Sep 17 00:00:00 2001 From: Jake Bartles Date: Tue, 13 Feb 2018 12:54:27 -0500 Subject: [PATCH 03/65] fixed issue 1409 --- src/components/monitor/monitor.jsx | 1 + src/lib/opcode-labels.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index 17b562120..593899823 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -10,6 +10,7 @@ const categories = { sound: '#CF63CF', looks: '#9966FF', motion: '#4C97FF' + list: '#FC662C' }; const MonitorComponent = props => ( diff --git a/src/lib/opcode-labels.js b/src/lib/opcode-labels.js index c4fa07bcf..e877a2a51 100644 --- a/src/lib/opcode-labels.js +++ b/src/lib/opcode-labels.js @@ -37,10 +37,10 @@ const opcodeMap = { labelFn: params => params.VARIABLE }, data_listcontents: { - category: 'data', + category: 'list', labelFn: params => params.LIST }, - + // Sound sound_volume: { category: 'sound', From 88279c7095e29dec4b4ab64dd41f43140b43f276 Mon Sep 17 00:00:00 2001 From: Jake Bartles Date: Tue, 13 Feb 2018 13:03:00 -0500 Subject: [PATCH 04/65] accidentally deleted this comma before commit --- src/components/monitor/monitor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index 593899823..5fb521e9c 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -9,7 +9,7 @@ const categories = { sensing: '#5CB1D6', sound: '#CF63CF', looks: '#9966FF', - motion: '#4C97FF' + motion: '#4C97FF', list: '#FC662C' }; From acdc8242c86eaee4373f4c03ba470da175107f98 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 16 Feb 2018 00:59:53 +0000 Subject: [PATCH 05/65] chore(package): update scratch-paint to version 0.2.0-prerelease.20180215230736 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7840f355b..34081f7bd 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "scratch-audio": "0.1.0-prerelease.1516198804", "scratch-blocks": "0.1.0-prerelease.1518210548", "scratch-l10n": "2.0.20180108132626", - "scratch-paint": "0.1.0-prerelease.20180209163938", + "scratch-paint": "0.2.0-prerelease.20180215230736", "scratch-render": "0.1.0-prerelease.1516837442", "scratch-storage": "0.4.0", "scratch-vm": "0.1.0-prerelease.1518098842-prerelease.1518098861", From fff7375b583a56b828b92806325429da424262c5 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Fri, 16 Feb 2018 10:41:20 -0500 Subject: [PATCH 06/65] Remove enableExtensions prop --- src/components/gui/gui.jsx | 6 +----- src/containers/gui.jsx | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index fe58243ba..5cbd88cbc 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -37,7 +37,6 @@ const GUIComponent = props => { const { basePath, children, - enableExtensions, intl, feedbackFormVisible, vm, @@ -109,9 +108,7 @@ const GUIComponent = props => { + + + + + + + + + ) + }} + /> + + + +); + +ImportModal.propTypes = { + intl: intlShape.isRequired, + onCancel: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onKeyPress: PropTypes.func.isRequired, + onViewProject: PropTypes.func.isRequired, + placeholder: PropTypes.string, +}; + +export default injectIntl(ImportModal); diff --git a/src/components/preview-modal/preview-modal.css b/src/components/preview-modal/preview-modal.css index 858beb5ba..c6e5037e8 100644 --- a/src/components/preview-modal/preview-modal.css +++ b/src/components/preview-modal/preview-modal.css @@ -51,7 +51,7 @@ .button-row button { border: 1px solid $motion-primary; border-radius: 0.25rem; - padding: 0.5rem 2rem; + padding: 0.5rem 1.5rem; background: white; font-weight: bold; font-size: .875rem; @@ -66,6 +66,13 @@ .button-row button.no-button { color: $motion-primary; } + +.button-row button.view-project-button { + background: $import-primary; + border-color: $import-primary; + color: white; +} + .button-row button + button { margin-left: 0.5rem; } diff --git a/src/components/preview-modal/preview-modal.jsx b/src/components/preview-modal/preview-modal.jsx index 71a3b029c..2b9d14407 100644 --- a/src/components/preview-modal/preview-modal.jsx +++ b/src/components/preview-modal/preview-modal.jsx @@ -71,6 +71,17 @@ const PreviewModal = ({intl, ...props}) => ( }} /> + ({ blocksTabVisible: state.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, feedbackFormVisible: state.modals.feedbackForm, + importInfoVisible: state.modals.importInfo, previewInfoVisible: state.modals.previewInfo, soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX }); diff --git a/src/containers/import-modal.jsx b/src/containers/import-modal.jsx new file mode 100644 index 000000000..9d808b6e3 --- /dev/null +++ b/src/containers/import-modal.jsx @@ -0,0 +1,112 @@ +import bindAll from 'lodash.bindall'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {connect} from 'react-redux'; +import platform from 'platform'; +// import RegExp from 'regex'; + +import ImportModalComponent from '../components/import-modal/import-modal.jsx'; +import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; + +import { + closeImportInfo +} from '../reducers/modals'; + +class ImportModal extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleViewProject', + 'handleCancel', + 'handleKeyPress', + 'handleChange' + ]); + + this.state = { + inputValue: '' + }; + } + handleKeyPress (event) { + if (event.key === 'Enter') this.handleViewProject(); + } + handleViewProject () { + const inputValue = this.state.inputValue; + const projectId = this.validate(inputValue); + + if (projectId) { + const projectLink = document.createElement('a'); + document.body.appendChild(projectLink); + projectLink.href = `#${projectId}`; + projectLink.click(); + document.body.removeChild(projectLink); + this.handleCancel(); + } else { + console.log("Error") + } + + // window. + // this.setState({previewing: true}); + // this.props.onViewProject(); + } + handleChange (e) { + this.setState({inputValue: e.target.value}); + } + validate (input) { + //const regex1 = RegExp( + //const regex2 = RegExp('^scratch.mit.edu/projects/'); + const matches = input.match(/^(https:\/\/)?scratch\.mit\.edu\/projects\/(\d+)(\/?)$/); + if (matches != null && matches.length > 0) { + console.log("Project ID: " + matches[2]); + return matches[2]; + } + return null; + } + handleCancel () { + // this.setState({previewing: false}); + this.props.onCancel(); + } + handleGoBack () { + window.location.replace('https://scratch.mit.edu'); + } + supportedBrowser () { + if (platform.name === 'IE') { + return false; + } + return true; + } + render () { + return (this.supportedBrowser() ? + : + + ); + } +} + +ImportModal.propTypes = { + onViewProject: PropTypes.func, + onCancel: PropTypes.func.isRequired, +}; + +const mapStateToProps = () => ({}); + +const mapDispatchToProps = dispatch => ({ + onViewProject: () => { + dispatch(closeImportInfo()); + }, + onCancel: () => { + dispatch(closeImportInfo()); + } +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ImportModal); diff --git a/src/containers/preview-modal.jsx b/src/containers/preview-modal.jsx index 8d50dc83c..1209f251f 100644 --- a/src/containers/preview-modal.jsx +++ b/src/containers/preview-modal.jsx @@ -8,7 +8,8 @@ import PreviewModalComponent from '../components/preview-modal/preview-modal.jsx import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; import { - closePreviewInfo + closePreviewInfo, + openImportInfo } from '../reducers/modals'; class PreviewModal extends React.Component { @@ -16,7 +17,8 @@ class PreviewModal extends React.Component { super(props); bindAll(this, [ 'handleTryIt', - 'handleCancel' + 'handleCancel', + 'handleViewProject' ]); this.state = { @@ -30,6 +32,9 @@ class PreviewModal extends React.Component { handleCancel () { window.location.replace('https://scratch.mit.edu'); } + handleViewProject () { + this.props.onViewProject(); + } supportedBrowser () { if (platform.name === 'IE') { return false; @@ -42,6 +47,7 @@ class PreviewModal extends React.Component { previewing={this.state.previewing} onCancel={this.handleCancel} onTryIt={this.handleTryIt} + onViewProject={this.handleViewProject} /> : ({}); @@ -59,6 +66,10 @@ const mapStateToProps = () => ({}); const mapDispatchToProps = dispatch => ({ onTryIt: () => { dispatch(closePreviewInfo()); + }, + onViewProject: () => { + dispatch(closePreviewInfo()); + dispatch(openImportInfo()); } }); diff --git a/src/css/colors.css b/src/css/colors.css index e270217f9..5dd2f325c 100644 --- a/src/css/colors.css +++ b/src/css/colors.css @@ -19,3 +19,5 @@ $control-primary: #FFAB19; $data-primary: #FF8C1A; $form-border: #E9EEF2; + +$import-primary: #0FBD8C; diff --git a/src/reducers/modals.js b/src/reducers/modals.js index ffbca3f8c..ba038ff34 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -7,6 +7,7 @@ const MODAL_BACKDROP_LIBRARY = 'backdropLibrary'; const MODAL_COSTUME_LIBRARY = 'costumeLibrary'; const MODAL_EXTENSION_LIBRARY = 'extensionLibrary'; const MODAL_FEEDBACK_FORM = 'feedbackForm'; +const MODAL_IMPORT_INFO = 'importInfo'; const MODAL_PREVIEW_INFO = 'previewInfo'; const MODAL_SOUND_LIBRARY = 'soundLibrary'; const MODAL_SPRITE_LIBRARY = 'spriteLibrary'; @@ -18,6 +19,7 @@ const initialState = { [MODAL_COSTUME_LIBRARY]: false, [MODAL_EXTENSION_LIBRARY]: false, [MODAL_FEEDBACK_FORM]: false, + [MODAL_IMPORT_INFO]: false, [MODAL_PREVIEW_INFO]: true, [MODAL_SOUND_LIBRARY]: false, [MODAL_SPRITE_LIBRARY]: false, @@ -67,6 +69,14 @@ const openFeedbackForm = function () { analytics.pageview('/modals/feedback'); return openModal(MODAL_FEEDBACK_FORM); }; +const openImportInfo = function () { + analytics.pageview('modals/import'); + return openModal(MODAL_IMPORT_INFO); +}; +const openPreviewInfo = function () { + analytics.pageview('/modals/preview'); + return openModal(MODAL_PREVIEW_INFO); +}; const openSoundLibrary = function () { analytics.pageview('/libraries/sounds'); return openModal(MODAL_SOUND_LIBRARY); @@ -79,10 +89,6 @@ const openSoundRecorder = function () { analytics.pageview('/modals/microphone'); return openModal(MODAL_SOUND_RECORDER); }; -const openPreviewInfo = function () { - analytics.pageview('/modals/preview'); - return openModal(MODAL_PREVIEW_INFO); -}; const closeBackdropLibrary = function () { return closeModal(MODAL_BACKDROP_LIBRARY); }; @@ -95,6 +101,9 @@ const closeExtensionLibrary = function () { const closeFeedbackForm = function () { return closeModal(MODAL_FEEDBACK_FORM); }; +const closeImportInfo = function () { + return closeModal(MODAL_IMPORT_INFO); +}; const closePreviewInfo = function () { return closeModal(MODAL_PREVIEW_INFO); }; @@ -113,6 +122,7 @@ export { openCostumeLibrary, openExtensionLibrary, openFeedbackForm, + openImportInfo, openPreviewInfo, openSoundLibrary, openSpriteLibrary, @@ -121,6 +131,7 @@ export { closeCostumeLibrary, closeExtensionLibrary, closeFeedbackForm, + closeImportInfo, closePreviewInfo, closeSpriteLibrary, closeSoundLibrary, From 6d626d1b5f8eedbc851e62856a6bf53ef4656c4c Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Wed, 21 Feb 2018 14:45:26 -0500 Subject: [PATCH 24/65] Import input now shows error state w/orange border around input and tooltip with static error message. --- src/components/import-error/import-error.css | 61 ++++++++++ src/components/import-error/import-error.jsx | 111 +++++++++++++++++++ src/components/import-input/import-input.jsx | 57 ++++++++++ src/components/import-modal/import-modal.css | 16 ++- src/components/import-modal/import-modal.jsx | 13 ++- src/containers/import-modal.jsx | 15 ++- 6 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 src/components/import-error/import-error.css create mode 100644 src/components/import-error/import-error.jsx create mode 100644 src/components/import-input/import-input.jsx diff --git a/src/components/import-error/import-error.css b/src/components/import-error/import-error.css new file mode 100644 index 000000000..4ffac015f --- /dev/null +++ b/src/components/import-error/import-error.css @@ -0,0 +1,61 @@ +/* + * NOTE: the copious use of `important` is needed to overwrite + * the default tooltip styling, and is required by the 3rd party + * library being used, `react-tooltip` + */ + +@import "../../css/colors.css"; + +.import-error { + background-color: $data-primary !important; + border: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: .25rem !important; + box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important; + padding: .75rem 1rem !important; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-size: 1rem !important; + line-height: 1.25rem !important; + z-index: 100 !important; +} + +.import-error:after { + content: ""; + border-top: 1px solid hsla(0, 0%, 0%, .1) !important; + border-left: 0 !important; + border-bottom: 0 !important; + border-right: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: .25rem; + background-color: $data-primary !important; + height: 1rem !important; + width: 1rem !important; +} + +.show, +.show:before, +.show:after { + opacity: 1 !important; +} + +.left:after { + margin-top: -.5rem !important; + right: -.5rem !important; + transform: rotate(45deg) !important; +} + +.right:after { + margin-top: -.5rem !important; + left: -.5rem !important; + transform: rotate(-135deg) !important; +} + +.top:after { + margin-right: -.5rem !important; + bottom: -.5rem !important; + transform: rotate(135deg) !important; +} + +.bottom:after { + margin-left: -.5rem !important; + top: -.5rem !important; + transform: rotate(-45deg) !important; +} diff --git a/src/components/import-error/import-error.jsx b/src/components/import-error/import-error.jsx new file mode 100644 index 000000000..5147138a6 --- /dev/null +++ b/src/components/import-error/import-error.jsx @@ -0,0 +1,111 @@ +import bindAll from 'lodash.bindall'; +import classNames from 'classnames'; +import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ReactTooltip from 'react-tooltip'; + +import styles from './import-error.css'; + +class ImportErrorContent extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'setHide', + 'setShow', + 'getContent' + ]); + this.state = { + isShowing: true + }; + } + setShow () { + // needed to set the opacity to 1, since the default is .9 on show + this.setState({isShowing: true}); + } + setHide () { + this.setState({isShowing: false}); + } + getContent () { + return ( +

{this.props.errorMessage}

+ ); + } + render () { + return ( + + ); + } +} + +ImportErrorContent.propTypes = { + className: PropTypes.string, + intl: intlShape, + place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipId: PropTypes.string.isRequired, + errorMessage: PropTypes.string.isRequired +}; + +ImportErrorContent.defaultProps = { + place: 'bottom' +}; + +const ImportError = injectIntl(ImportErrorContent); + +const ImportErrorTooltip = props => ( +
+
+ {props.children} +
+ +
+); + +ImportErrorTooltip.propTypes = { + children: PropTypes.node.isRequired, + className: PropTypes.string, + delayHide: PropTypes.number, + delayShow: PropTypes.number, + place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipClassName: PropTypes.string, + tooltipId: PropTypes.string.isRequired, + errorMessage: PropTypes.string.isRequired +}; + +ImportErrorTooltip.defaultProps = { + delayHide: 0, + delayShow: 0 +}; + +export { + ImportError as ImportErrorComponent, + ImportErrorTooltip +}; diff --git a/src/components/import-input/import-input.jsx b/src/components/import-input/import-input.jsx new file mode 100644 index 000000000..997de86af --- /dev/null +++ b/src/components/import-input/import-input.jsx @@ -0,0 +1,57 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; + +import {ImportErrorTooltip} from '../import-error/import-error.jsx'; + + +class ImportInput extends React.Component { + render () { + let input = null; + if (!this.props.hasValidationError) { + input = ( + + ); + } else { + input = ( + + + + ); + } + return input; + } +} + +ImportInput.propTypes = { + onChange: PropTypes.func.isRequired, + onKeyPress: PropTypes.func.isRequired, + placeholder: PropTypes.string, + errorMessage: PropTypes.string.isRequired, + hasValidationError: PropTypes.bool.isRequired, + inputValue: PropTypes.string.isRequired, + okClassName: PropTypes.string, + badClassName: PropTypes.string, + errorDivClassName: PropTypes.string +}; + +export default ImportInput; diff --git a/src/components/import-modal/import-modal.css b/src/components/import-modal/import-modal.css index 80a2de576..e025f8137 100644 --- a/src/components/import-modal/import-modal.css +++ b/src/components/import-modal/import-modal.css @@ -113,8 +113,12 @@ $sides: 20rem; text-align: right; display: flex; justify-content: center; - /*border: 1px solid $import-primary; - border-radius: 0.25rem;*/ +} + +.input-row div.error-div { + margin: 0; + width: 100%; + padding: 0; } .input-row input { @@ -130,12 +134,18 @@ $sides: 20rem; color: rgba(87,94,117,0.5); } -.input-row input:focus { +.input-row input.ok-input:focus { outline: none; border: 1px solid $motion-primary; border-radius: 0.25rem } +.input-row input.bad-input:focus { + outline: none; + border: 1px solid $data-primary; + border-radius: 0.25rem +} + .input-row button { padding: 0.5rem 2rem; font-weight: bold; diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx index 9a9ad872d..9df475f12 100644 --- a/src/components/import-modal/import-modal.jsx +++ b/src/components/import-modal/import-modal.jsx @@ -6,6 +6,7 @@ import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-int import classNames from 'classnames'; import CloseButton from '../close-button/close-button.jsx'; +import ImportInput from '../import-input/import-input.jsx'; import styles from './import-modal.css'; @@ -64,12 +65,16 @@ const ImportModal = ({intl, ...props}) => (

- + + {props.hasValidationError ? +
+

+ {/* intl.formatMessage({...messages.invalidLink})*/} + +

+
: null} +
- - {props.hasValidationError ? -
-

- {/* intl.formatMessage({...messages.invalidLink})*/} - -

-
: null} -
+ {props.hasValidationError ? + +

+ +

+
: null + } + +
+
+ {(moreButtons || []).map(({img, title, onClick: handleClick}) => { + const isComingSoon = !handleClick; + const tooltipId = `tooltip-${Math.random()}`; + return ( +
+ + +
+ ); + })} +
+
+ + ); + } +} + +ActionMenu.propTypes = { + className: PropTypes.string, + img: PropTypes.string, + moreButtons: PropTypes.arrayOf(PropTypes.shape({ + img: PropTypes.string, + title: PropTypes.node.isRequired, + onClick: PropTypes.func // Optional, "coming soon" if no callback provided + })), + onClick: PropTypes.func.isRequired, + title: PropTypes.node.isRequired +}; + +export default ActionMenu; diff --git a/src/components/stage-selector/icon--backdrop.svg b/src/components/action-menu/icon--backdrop.svg similarity index 100% rename from src/components/stage-selector/icon--backdrop.svg rename to src/components/action-menu/icon--backdrop.svg diff --git a/src/components/action-menu/icon--camera.svg b/src/components/action-menu/icon--camera.svg new file mode 100644 index 000000000..e8c442d8f --- /dev/null +++ b/src/components/action-menu/icon--camera.svg @@ -0,0 +1,12 @@ + + + + camera + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/components/action-menu/icon--file-upload.svg b/src/components/action-menu/icon--file-upload.svg new file mode 100644 index 000000000..57337d959 --- /dev/null +++ b/src/components/action-menu/icon--file-upload.svg @@ -0,0 +1,12 @@ + + + + file-upload + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/components/action-menu/icon--paint.svg b/src/components/action-menu/icon--paint.svg new file mode 100644 index 000000000..f79d562c3 --- /dev/null +++ b/src/components/action-menu/icon--paint.svg @@ -0,0 +1,12 @@ + + + + paint + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/components/sprite-selector/icon--sprite.svg b/src/components/action-menu/icon--sprite.svg similarity index 100% rename from src/components/sprite-selector/icon--sprite.svg rename to src/components/action-menu/icon--sprite.svg diff --git a/src/components/action-menu/icon--surprise.svg b/src/components/action-menu/icon--surprise.svg new file mode 100644 index 000000000..41655999f --- /dev/null +++ b/src/components/action-menu/icon--surprise.svg @@ -0,0 +1,12 @@ + + + + surprise + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/components/asset-button/asset-button.css b/src/components/asset-button/asset-button.css deleted file mode 100644 index b36fa76b8..000000000 --- a/src/components/asset-button/asset-button.css +++ /dev/null @@ -1,21 +0,0 @@ -@import "../../css/colors.css"; - -.container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - cursor: pointer; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - background: $motion-primary; - border-radius: 100%; - width: 2.75rem; - height: 2.75rem; - outline: none; - border: none; -} - -.icon { - width: 1.75rem; - height: 1.75rem; -} diff --git a/src/components/asset-button/asset-button.jsx b/src/components/asset-button/asset-button.jsx deleted file mode 100644 index 088838990..000000000 --- a/src/components/asset-button/asset-button.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import classNames from 'classnames'; -import styles from './asset-button.css'; - -const AssetButton = ({ - img, - className, - title, - onClick -}) => ( - -); - -AssetButton.propTypes = { - className: PropTypes.string, - img: PropTypes.string, - onClick: PropTypes.func.isRequired, - title: PropTypes.node.isRequired -}; - -export default AssetButton; diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css index 2a19296d6..24a5c2ec6 100644 --- a/src/components/asset-panel/selector.css +++ b/src/components/asset-panel/selector.css @@ -11,13 +11,30 @@ } .new-buttons { + position: absolute; + bottom: 0; + width: 100%; + display: flex; flex-direction: column; align-items: center; justify-content: space-around; - margin: 0.75rem 0; + padding: 0.75rem 0; color: $motion-primary; text-align: center; + background: none; +} + +$fade-out-distance: 100px; + +.new-buttons:before { + content: ""; + position: absolute; + bottom: 0; + left: 0; + background: linear-gradient(rgba(232,237,241, 0),rgba(232,237,241, 1)); + height: $fade-out-distance; + width: 100%; } .new-buttons > button + button { diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index b69707781..51094059f 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -4,7 +4,7 @@ import React from 'react'; import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx'; import Box from '../box/box.jsx'; -import AssetButton from '../asset-button/asset-button.jsx'; +import ActionMenu from '../action-menu/action-menu.jsx'; import styles from './selector.css'; const Selector = props => { @@ -36,14 +36,12 @@ const Selector = props => { ))}
- {buttons.map(({message, img, onClick}, index) => ( - - ))} +
); diff --git a/src/components/green-flag/green-flag.css b/src/components/green-flag/green-flag.css index 2c1452838..35ba29726 100644 --- a/src/components/green-flag/green-flag.css +++ b/src/components/green-flag/green-flag.css @@ -5,6 +5,7 @@ padding: 0.375rem; border-radius: 0.25rem; user-select: none; + user-drag: none; cursor: pointer; transition: 0.2s ease-out; } diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css index 92b83acc2..5132af685 100644 --- a/src/components/gui/gui.css +++ b/src/components/gui/gui.css @@ -140,7 +140,8 @@ For making the sprite-selector a scrollable pane @todo: Not working in Safari */ - overflow: hidden; + /* TODO this also breaks the thermometer menu */ + /* overflow: hidden; */ } .extension-button-container { @@ -156,6 +157,19 @@ box-sizing: content-box; /* To match scratch-block vertical toolbox borders */ } +$fade-out-distance: 15px; + +.extension-button-container:before { + content: ""; + position: absolute; + top: calc(calc(-1 * $fade-out-distance) - 1px); + left: -1px; + background: linear-gradient(rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15)); + height: $fade-out-distance; + width: calc(100% + 0.5px); +} + + .extension-button { background: none; border: none; diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css index 67b0532d3..4cb03ecd5 100644 --- a/src/components/sprite-selector/sprite-selector.css +++ b/src/components/sprite-selector/sprite-selector.css @@ -2,7 +2,7 @@ .sprite-selector { flex-grow: 1; - position: relative; + position: relative; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; margin-right: calc($space / 2); background-color: #f9f9f9; @@ -16,14 +16,14 @@ /* In prep for renaming sprite-selector-item to sprite */ .sprite { - /* + /* Our goal is to fit sprites evenly in a row without leftover space. - Flexbox's `space between` property gets us close, but doesn't flow + Flexbox's `space between` property gets us close, but doesn't flow well when the # of items per row > 1 and less than the max per row. - Solving by explicitly calc'ing the width of each sprite. Setting - `border-box` simplifies things, because content, padding and - border-width all are included in the width, leaving us only to subtract + Solving by explicitly calc'ing the width of each sprite. Setting + `border-box` simplifies things, because content, padding and + border-width all are included in the width, leaving us only to subtract the left + right margins. @todo: make room for the scrollbar @@ -32,7 +32,7 @@ width: calc((100% / $sprites-per-row ) - $space); min-width: 4rem; min-height: 4rem; /* @todo: calc height same as width */ - margin: calc($space / 2); + margin: calc($space / 2); } @@ -41,11 +41,11 @@ Sets the sprite-selector items as a scrollable pane @todo: Safari: pane doesn't stretch to fill height; - @todo: Adding `position: relative` still doesn't fix Safari scrolling pane, and - also introduces a new bug in Chrome when vertically resizing window down, - then back up, introduces white space in the outside the page container. + @todo: Adding `position: relative` still doesn't fix Safari scrolling pane, and + also introduces a new bug in Chrome when vertically resizing window down, + then back up, introduces white space in the outside the page container. */ - height: calc(100% - $sprite-info-height); + height: calc(100% - $sprite-info-height); overflow-y: scroll; } @@ -57,11 +57,12 @@ padding-top: calc($space / 2); padding-left: calc($space / 2); padding-right: calc($space / 2); - padding-bottom: $space; + padding-bottom: $space; } .add-button { position: absolute; bottom: 0.75rem; right: 1rem; + z-index: 1; /* TODO overlaps the stage, this doesn't work, fix! */ } diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index 41e51a316..9817386f7 100644 --- a/src/components/sprite-selector/sprite-selector.jsx +++ b/src/components/sprite-selector/sprite-selector.jsx @@ -5,16 +5,41 @@ import {defineMessages, injectIntl, intlShape} from 'react-intl'; import Box from '../box/box.jsx'; import SpriteInfo from '../../containers/sprite-info.jsx'; import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx'; -import AssetButton from '../asset-button/asset-button.jsx'; +import ActionMenu from '../action-menu/action-menu.jsx'; import styles from './sprite-selector.css'; -import spriteIcon from './icon--sprite.svg'; + +import cameraIcon from '../action-menu/icon--camera.svg'; +import fileUploadIcon from '../action-menu/icon--file-upload.svg'; +import paintIcon from '../action-menu/icon--paint.svg'; +import spriteIcon from '../action-menu/icon--sprite.svg'; +import surpriseIcon from '../action-menu/icon--surprise.svg'; const messages = defineMessages({ - addSprite: { - id: 'gui.spriteSelector.addSprite', - description: 'Button to add a sprite in the target pane', - defaultMessage: 'Add Sprite' + addSpriteFromLibrary: { + id: 'gui.spriteSelector.addSpriteFromLibrary', + description: 'Button to add a sprite in the target pane from library', + defaultMessage: 'Library' + }, + addSpriteFromPaint: { + id: 'gui.spriteSelector.addSpriteFromPaint', + description: 'Button to add a sprite in the target pane from paint', + defaultMessage: 'Paint' + }, + addSpriteFromSurprise: { + id: 'gui.spriteSelector.addSpriteFromSurprise', + description: 'Button to add a random sprite in the target pane', + defaultMessage: 'Surprise' + }, + addSpriteFromFile: { + id: 'gui.spriteSelector.addSpriteFromFile', + description: 'Button to add a sprite in the target pane from file', + defaultMessage: 'Coming Soon' + }, + addSpriteFromCamera: { + id: 'gui.spriteSelector.addSpriteFromCamera', + description: 'Button to add a sprite in the target pane from camera', + defaultMessage: 'Coming Soon' } }); @@ -30,6 +55,8 @@ const SpriteSelectorComponent = function (props) { onDeleteSprite, onDuplicateSprite, onNewSpriteClick, + onSurpriseSpriteClick, + onPaintSpriteClick, onSelectSprite, selectedId, sprites, @@ -85,10 +112,27 @@ const SpriteSelectorComponent = function (props) { } - diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx index eea811409..bb2771072 100644 --- a/src/components/stage-selector/stage-selector.jsx +++ b/src/components/stage-selector/stage-selector.jsx @@ -4,16 +4,41 @@ import React from 'react'; import {defineMessages, intlShape, injectIntl, FormattedMessage} from 'react-intl'; import Box from '../box/box.jsx'; -import AssetButton from '../asset-button/asset-button.jsx'; +import ActionMenu from '../action-menu/action-menu.jsx'; import CostumeCanvas from '../costume-canvas/costume-canvas.jsx'; import styles from './stage-selector.css'; -import backdropIcon from './icon--backdrop.svg'; + +import backdropIcon from '../action-menu/icon--backdrop.svg'; +import cameraIcon from '../action-menu/icon--camera.svg'; +import fileUploadIcon from '../action-menu/icon--file-upload.svg'; +import paintIcon from '../action-menu/icon--paint.svg'; +import surpriseIcon from '../action-menu/icon--surprise.svg'; const messages = defineMessages({ - addBackdrop: { - id: 'gui.stageSelector.targetPaneAddBackdrop', - description: 'Button to add a backdrop in the target pane', - defaultMessage: 'Add Backdrop' + addBackdropFromLibrary: { + id: 'gui.spriteSelector.addBackdropFromLibrary', + description: 'Button to add a stage in the target pane from library', + defaultMessage: 'Library' + }, + addBackdropFromPaint: { + id: 'gui.stageSelector.addBackdropFromPaint', + description: 'Button to add a stage in the target pane from paint', + defaultMessage: 'Paint' + }, + addBackdropFromSurprise: { + id: 'gui.stageSelector.addBackdropFromSurprise', + description: 'Button to add a random stage in the target pane', + defaultMessage: 'Surprise' + }, + addBackdropFromFile: { + id: 'gui.stageSelector.addBackdropFromFile', + description: 'Button to add a stage in the target pane from file', + defaultMessage: 'Coming Soon' + }, + addBackdropFromCamera: { + id: 'gui.stageSelector.addBackdropFromCamera', + description: 'Button to add a stage in the target pane from camera', + defaultMessage: 'Coming Soon' } }); @@ -25,6 +50,8 @@ const StageSelector = props => { url, onClick, onNewBackdropClick, + onSurpriseBackdropClick, + onEmptyBackdropClick, ...componentProps } = props; return ( @@ -54,10 +81,28 @@ const StageSelector = props => { />
{backdropCount}
- @@ -72,4 +117,5 @@ StageSelector.propTypes = { selected: PropTypes.bool.isRequired, url: PropTypes.string }; + export default injectIntl(StageSelector); diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx index 30267688f..da19c36fc 100644 --- a/src/components/target-pane/target-pane.jsx +++ b/src/components/target-pane/target-pane.jsx @@ -29,6 +29,8 @@ const TargetPane = ({ onDeleteSprite, onDuplicateSprite, onNewSpriteClick, + onSurpriseSpriteClick, + onPaintSpriteClick, onRequestCloseSpriteLibrary, onRequestCloseBackdropLibrary, onSelectSprite, @@ -54,7 +56,9 @@ const TargetPane = ({ onDeleteSprite={onDeleteSprite} onDuplicateSprite={onDuplicateSprite} onNewSpriteClick={onNewSpriteClick} + onSurpriseSpriteClick={onSurpriseSpriteClick} onSelectSprite={onSelectSprite} + onPaintSpriteClick={onPaintSpriteClick} />
{stage.id && 2 ? item.info[2] : 1, + skinId: null + }; + this.props.vm.addCostume(item.md5, vmCostume).then(() => { + this.handleNewCostume(); + }); + } + handleSurpriseBackdrop () { + const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)]; + const vmCostume = { + name: item.name, + rotationCenterX: item.info[0] && item.info[0] / 2, + rotationCenterY: item.info[1] && item.info[1] / 2, + bitmapResolution: item.info.length > 2 ? item.info[2] : 1, + skinId: null + }; + this.props.vm.addCostume(item.md5, vmCostume).then(() => { + this.handleNewCostume(); + }); + } render () { // For paint wrapper const { @@ -139,8 +179,7 @@ class CostumeTab extends React.Component { return null; } - const addLibraryMessage = target.isStage ? messages.addLibraryBackdropMsg : messages.addLibraryCostumeMsg; - const addBlankMessage = target.isStage ? messages.addBlankBackdropMsg : messages.addBlankCostumeMsg; + const addSurpriseFunc = target.isStage ? this.handleSurpriseBackdrop : this.handleSurpriseCostume; const addLibraryFunc = target.isStage ? onNewLibraryBackdropClick : onNewLibraryCostumeClick; const addLibraryIcon = target.isStage ? addLibraryBackdropIcon : addLibraryCostumeIcon; @@ -148,14 +187,27 @@ class CostumeTab extends React.Component { { + this.handleNewSound(); + }); + } + render () { const { intl, @@ -94,13 +114,23 @@ class SoundTab extends React.Component { )) : []; const messages = defineMessages({ + fileUploadSound: { + defaultMessage: 'Coming Soon', + description: 'Button to upload sound from file in the editor tab', + id: 'gui.soundTab.fileUploadSound' + }, + surpriseSound: { + defaultMessage: 'Surprise', + description: 'Button to get a random sound in the editor tab', + id: 'gui.soundTab.surpriseSound' + }, recordSound: { - defaultMessage: 'Record Sound', + defaultMessage: 'Record', description: 'Button to record a sound in the editor tab', id: 'gui.soundTab.recordSound' }, addSound: { - defaultMessage: 'Add Sound', + defaultMessage: 'Library', description: 'Button to add a sound in the editor tab', id: 'gui.soundTab.addSound' } @@ -109,13 +139,20 @@ class SoundTab extends React.Component { return ( ({ url: soundIcon, diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx index 83a5a1bb0..3d280bd16 100644 --- a/src/containers/stage-selector.jsx +++ b/src/containers/stage-selector.jsx @@ -4,19 +4,51 @@ import React from 'react'; import {connect} from 'react-redux'; import {openBackdropLibrary} from '../reducers/modals'; +import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab'; + import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx'; +import backdropLibraryContent from '../lib/libraries/backdrops.json'; +import costumeLibraryContent from '../lib/libraries/costumes.json'; + class StageSelector extends React.Component { constructor (props) { super(props); bindAll(this, [ - 'handleClick' + 'handleClick', + 'handleSurpriseBackdrop', + 'handleEmptyBackdrop', + 'addBackdropFromLibraryItem' ]); } + addBackdropFromLibraryItem (item) { + const vmBackdrop = { + name: item.name, + rotationCenterX: item.info[0] && item.info[0] / 2, + rotationCenterY: item.info[1] && item.info[1] / 2, + bitmapResolution: item.info.length > 2 ? item.info[2] : 1, + skinId: null + }; + return this.props.vm.addBackdrop(item.md5, vmBackdrop); + } handleClick (e) { e.preventDefault(); this.props.onSelect(this.props.id); } + handleSurpriseBackdrop () { + // @todo should this not add a backdrop you already have? + const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)]; + this.addBackdropFromLibraryItem(item); + } + handleEmptyBackdrop () { + // @todo this is brittle, will need to be refactored for localized libraries + const emptyItem = costumeLibraryContent.find(item => item.name === 'Empty'); + if (emptyItem) { + this.addBackdropFromLibraryItem(emptyItem).then(() => { + this.props.onActivateTab(COSTUMES_TAB_INDEX); + }); + } + } render () { const { /* eslint-disable no-unused-vars */ @@ -29,6 +61,8 @@ class StageSelector extends React.Component { return ( ); @@ -41,13 +75,17 @@ StageSelector.propTypes = { }; const mapStateToProps = (state, {assetId}) => ({ - url: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI() + url: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI(), + vm: state.vm }); const mapDispatchToProps = dispatch => ({ onNewBackdropClick: e => { e.preventDefault(); dispatch(openBackdropLibrary()); + }, + onActivateTab: tabIndex => { + dispatch(activateTab(tabIndex)); } }); diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 32ad448bc..a1665f808 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -9,7 +9,10 @@ import { closeSpriteLibrary } from '../reducers/modals'; +import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab'; + import TargetPaneComponent from '../components/target-pane/target-pane.jsx'; +import spriteLibraryContent from '../lib/libraries/sprites.json'; class TargetPane extends React.Component { constructor (props) { @@ -23,7 +26,9 @@ class TargetPane extends React.Component { 'handleChangeSpriteY', 'handleDeleteSprite', 'handleDuplicateSprite', - 'handleSelectSprite' + 'handleSelectSprite', + 'handleSurpriseSpriteClick', + 'handlePaintSpriteClick' ]); } handleChangeSpriteDirection (direction) { @@ -53,6 +58,19 @@ class TargetPane extends React.Component { handleSelectSprite (id) { this.props.vm.setEditingTarget(id); } + handleSurpriseSpriteClick () { + const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)]; + this.props.vm.addSprite2(JSON.stringify(item.json)); + } + handlePaintSpriteClick () { + // @todo this is brittle, will need to be refactored for localized libraries + const emptyItem = spriteLibraryContent.find(item => item.name === 'Empty'); + if (emptyItem) { + this.props.vm.addSprite2(JSON.stringify(emptyItem.json)).then(() => { + this.props.onActivateTab(COSTUMES_TAB_INDEX); + }); + } + } render () { return ( ); } @@ -105,6 +125,9 @@ const mapDispatchToProps = dispatch => ({ }, onRequestCloseBackdropLibrary: () => { dispatch(closeBackdropLibrary()); + }, + onActivateTab: tabIndex => { + dispatch(activateTab(tabIndex)); } }); diff --git a/src/css/colors.css b/src/css/colors.css index e270217f9..f4611c06e 100644 --- a/src/css/colors.css +++ b/src/css/colors.css @@ -18,4 +18,6 @@ $control-primary: #FFAB19; $data-primary: #FF8C1A; +$pen-primary: #11B581; + $form-border: #E9EEF2; From e1efbc5bc369d2a34dd10fbb6eb031325e3260d0 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 26 Feb 2018 09:43:20 -0500 Subject: [PATCH 50/65] Fix linting --- src/components/asset-panel/selector.jsx | 26 +++++++++++++------ .../sprite-selector/sprite-selector.jsx | 2 ++ .../stage-selector/stage-selector.jsx | 2 ++ src/components/target-pane/target-pane.jsx | 6 +++-- src/containers/costume-tab.jsx | 2 -- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index 51094059f..471e9d942 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -17,6 +17,23 @@ const Selector = props => { onItemClick } = props; + let newButtonSection = null; + + if (buttons.length > 0) { + const {img, title, onClick} = buttons[0]; + const moreButtons = buttons.slice(0); + newButtonSection = ( + + + + ); + } + return ( @@ -35,14 +52,7 @@ const Selector = props => { /> ))} - - - + {newButtonSection} ); }; diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index 9817386f7..c1ae71075 100644 --- a/src/components/sprite-selector/sprite-selector.jsx +++ b/src/components/sprite-selector/sprite-selector.jsx @@ -150,7 +150,9 @@ SpriteSelectorComponent.propTypes = { onDeleteSprite: PropTypes.func, onDuplicateSprite: PropTypes.func, onNewSpriteClick: PropTypes.func, + onPaintSpriteClick: PropTypes.func, onSelectSprite: PropTypes.func, + onSurpriseSpriteClick: PropTypes.func, selectedId: PropTypes.string, sprites: PropTypes.shape({ id: PropTypes.shape({ diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx index bb2771072..8c7a00180 100644 --- a/src/components/stage-selector/stage-selector.jsx +++ b/src/components/stage-selector/stage-selector.jsx @@ -113,7 +113,9 @@ StageSelector.propTypes = { backdropCount: PropTypes.number.isRequired, intl: intlShape.isRequired, onClick: PropTypes.func, + onEmptyBackdropClick: PropTypes.func, onNewBackdropClick: PropTypes.func, + onSurpriseBackdropClick: PropTypes.func, selected: PropTypes.bool.isRequired, url: PropTypes.string }; diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx index da19c36fc..7369f07fc 100644 --- a/src/components/target-pane/target-pane.jsx +++ b/src/components/target-pane/target-pane.jsx @@ -56,9 +56,9 @@ const TargetPane = ({ onDeleteSprite={onDeleteSprite} onDuplicateSprite={onDuplicateSprite} onNewSpriteClick={onNewSpriteClick} - onSurpriseSpriteClick={onSurpriseSpriteClick} - onSelectSprite={onSelectSprite} onPaintSpriteClick={onPaintSpriteClick} + onSelectSprite={onSelectSprite} + onSurpriseSpriteClick={onSurpriseSpriteClick} />
{stage.id && Date: Mon, 26 Feb 2018 10:24:04 -0500 Subject: [PATCH 51/65] Fix issues with asset panel buttons --- src/components/action-menu/action-menu.jsx | 10 +++++++++- src/components/asset-panel/selector.jsx | 2 +- src/containers/costume-tab.jsx | 8 +++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx index 750a0ea3e..dfefb5c51 100644 --- a/src/components/action-menu/action-menu.jsx +++ b/src/components/action-menu/action-menu.jsx @@ -27,7 +27,12 @@ class ActionMenu extends React.Component { this.closeTimeoutId = null; }, CLOSE_DELAY); } - handleToggleOpenState () { + handleToggleOpenState (e) { + if (!this.state.isOpen) { + e.stopPropagation(); // For touch start, to prevent clicking primary button + } + + // Mouse enter back in after timeout was started prevents it from closing. if (this.closeTimeoutId) { clearTimeout(this.closeTimeoutId); this.closeTimeoutId = null; @@ -69,8 +74,10 @@ class ActionMenu extends React.Component { })} onMouseEnter={this.handleToggleOpenState} onMouseLeave={this.handleClosePopover} + onTouchStart={this.handleToggleOpenState} >
- +
- +
- +
- +
- +
- +