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 => {
@@ -159,14 +163,17 @@ const GUIComponent = props => {
);
};
GUIComponent.propTypes = {
+ activeTabIndex: PropTypes.number,
basePath: PropTypes.string,
+ blocksTabVisible: PropTypes.bool,
children: PropTypes.node,
+ costumesTabVisible: PropTypes.bool,
feedbackFormVisible: PropTypes.bool,
intl: intlShape.isRequired,
+ onActivateTab: PropTypes.func,
onExtensionButtonClick: PropTypes.func,
- onTabSelect: PropTypes.func,
previewInfoVisible: PropTypes.bool,
- tabIndex: PropTypes.number,
+ soundsTabVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
GUIComponent.defaultProps = {
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index d986e0d28..65e560993 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -2,23 +2,21 @@ import AudioEngine from 'scratch-audio';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
-import bindAll from 'lodash.bindall';
import {connect} from 'react-redux';
-import {openExtensionLibrary} from '../reducers/modals.js';
+import {openExtensionLibrary} from '../reducers/modals';
+import {
+ activateTab,
+ BLOCKS_TAB_INDEX,
+ COSTUMES_TAB_INDEX,
+ SOUNDS_TAB_INDEX
+} from '../reducers/navigation';
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
import GUIComponent from '../components/gui/gui.jsx';
class GUI extends React.Component {
- constructor (props) {
- super(props);
- bindAll(this, [
- 'handleTabSelect'
- ]);
- this.state = {tabIndex: 0};
- }
componentDidMount () {
this.audioEngine = new AudioEngine();
this.props.vm.attachAudioEngine(this.audioEngine);
@@ -34,9 +32,6 @@ class GUI extends React.Component {
componentWillUnmount () {
this.props.vm.stopAll();
}
- handleTabSelect (tabIndex) {
- this.setState({tabIndex});
- }
render () {
const {
children,
@@ -46,9 +41,7 @@ class GUI extends React.Component {
} = this.props;
return (
{children}
@@ -68,12 +61,17 @@ GUI.propTypes = {
GUI.defaultProps = GUIComponent.defaultProps;
const mapStateToProps = state => ({
+ activeTabIndex: state.navigation.activeTabIndex,
+ blocksTabVisible: state.navigation.activeTabIndex === BLOCKS_TAB_INDEX,
+ costumesTabVisible: state.navigation.activeTabIndex === COSTUMES_TAB_INDEX,
feedbackFormVisible: state.modals.feedbackForm,
- previewInfoVisible: state.modals.previewInfo
+ previewInfoVisible: state.modals.previewInfo,
+ soundsTabVisible: state.navigation.activeTabIndex === SOUNDS_TAB_INDEX
});
const mapDispatchToProps = dispatch => ({
- onExtensionButtonClick: () => dispatch(openExtensionLibrary())
+ onExtensionButtonClick: () => dispatch(openExtensionLibrary()),
+ onActivateTab: tab => dispatch(activateTab(tab))
});
const ConnectedGUI = connect(
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index d49144b4f..6eb4271e8 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -9,6 +9,7 @@ import targetReducer from './targets';
import toolboxReducer from './toolbox';
import vmReducer from './vm';
import stageSizeReducer from './stage-size';
+import navigationReducer from './navigation';
import {ScratchPaintReducer} from 'scratch-paint';
export default combineReducers({
@@ -19,6 +20,7 @@ export default combineReducers({
modals: modalReducer,
monitors: monitorReducer,
monitorLayout: monitorLayoutReducer,
+ navigation: navigationReducer,
targets: targetReducer,
toolbox: toolboxReducer,
vm: vmReducer,
diff --git a/src/reducers/navigation.js b/src/reducers/navigation.js
new file mode 100644
index 000000000..21b3534e2
--- /dev/null
+++ b/src/reducers/navigation.js
@@ -0,0 +1,37 @@
+const ACTIVATE_TAB = 'scratch-gui/navigation/ACTIVATE_TAB';
+
+// Constants use numbers to make it easier to work with react-tabs
+const BLOCKS_TAB_INDEX = 0;
+const COSTUMES_TAB_INDEX = 1;
+const SOUNDS_TAB_INDEX = 2;
+
+const initialState = {
+ activeTabIndex: BLOCKS_TAB_INDEX
+};
+
+const reducer = function (state, action) {
+ if (typeof state === 'undefined') state = initialState;
+ switch (action.type) {
+ case ACTIVATE_TAB:
+ return Object.assign({}, state, {
+ activeTabIndex: action.activeTabIndex
+ });
+ default:
+ return state;
+ }
+};
+
+const activateTab = function (tab) {
+ return {
+ type: ACTIVATE_TAB,
+ activeTabIndex: tab
+ };
+};
+
+export {
+ reducer as default,
+ activateTab,
+ BLOCKS_TAB_INDEX,
+ COSTUMES_TAB_INDEX,
+ SOUNDS_TAB_INDEX
+};
From 64d6c379ebd77546ab9fe88a83626008187919de Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Thu, 22 Feb 2018 14:40:01 -0500
Subject: [PATCH 21/65] Change navigation => editor tab reducer, fix
alphabetization
---
src/components/gui/gui.jsx | 8 ++++----
src/containers/gui.jsx | 10 +++++-----
src/reducers/{navigation.js => editor-tab.js} | 0
src/reducers/gui.js | 4 ++--
4 files changed, 11 insertions(+), 11 deletions(-)
rename src/reducers/{navigation.js => editor-tab.js} (100%)
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 4950128f2..488e705e8 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -40,13 +40,13 @@ const GUIComponent = props => {
blocksTabVisible,
children,
costumesTabVisible,
- intl,
feedbackFormVisible,
- vm,
- previewInfoVisible,
- soundsTabVisible,
+ intl,
onExtensionButtonClick,
onActivateTab,
+ previewInfoVisible,
+ soundsTabVisible,
+ vm,
...componentProps
} = props;
if (children) {
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 65e560993..bae6f9684 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -10,7 +10,7 @@ import {
BLOCKS_TAB_INDEX,
COSTUMES_TAB_INDEX,
SOUNDS_TAB_INDEX
-} from '../reducers/navigation';
+} from '../reducers/editor-tab';
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
@@ -61,12 +61,12 @@ GUI.propTypes = {
GUI.defaultProps = GUIComponent.defaultProps;
const mapStateToProps = state => ({
- activeTabIndex: state.navigation.activeTabIndex,
- blocksTabVisible: state.navigation.activeTabIndex === BLOCKS_TAB_INDEX,
- costumesTabVisible: state.navigation.activeTabIndex === COSTUMES_TAB_INDEX,
+ activeTabIndex: state.editorTab.activeTabIndex,
+ blocksTabVisible: state.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
+ costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
feedbackFormVisible: state.modals.feedbackForm,
previewInfoVisible: state.modals.previewInfo,
- soundsTabVisible: state.navigation.activeTabIndex === SOUNDS_TAB_INDEX
+ soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX
});
const mapDispatchToProps = dispatch => ({
diff --git a/src/reducers/navigation.js b/src/reducers/editor-tab.js
similarity index 100%
rename from src/reducers/navigation.js
rename to src/reducers/editor-tab.js
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index 6eb4271e8..d19966837 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -9,18 +9,18 @@ import targetReducer from './targets';
import toolboxReducer from './toolbox';
import vmReducer from './vm';
import stageSizeReducer from './stage-size';
-import navigationReducer from './navigation';
+import editorTabReducer from './editor-tab';
import {ScratchPaintReducer} from 'scratch-paint';
export default combineReducers({
colorPicker: colorPickerReducer,
customProcedures: customProceduresReducer,
+ editorTab: editorTabReducer,
intl: intlReducer,
stageSize: stageSizeReducer,
modals: modalReducer,
monitors: monitorReducer,
monitorLayout: monitorLayoutReducer,
- navigation: navigationReducer,
targets: targetReducer,
toolbox: toolboxReducer,
vm: vmReducer,
From a48f87c2cdcc28917f537c969ee9ceb6a14406df Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Thu, 22 Feb 2018 16:41:32 -0500
Subject: [PATCH 22/65] Fix deleting in sprite selector item event bubbling
---
src/containers/sprite-selector-item.jsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index 9879d8003..240a1bcb4 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -19,7 +19,8 @@ class SpriteSelectorItem extends React.Component {
e.preventDefault();
this.props.onClick(this.props.id);
}
- handleDelete () {
+ handleDelete (e) {
+ e.stopPropagation(); // To prevent from bubbling back to handleClick
// @todo add i18n here
// eslint-disable-next-line no-alert
if (window.confirm('Are you sure you want to delete this?')) {
From 954e74be050f3f41eb89afb67eae05b314574af4 Mon Sep 17 00:00:00 2001
From: Karishma Chadha
Date: Tue, 20 Feb 2018 16:30:56 -0500
Subject: [PATCH 23/65] Initial SB2 Import UI (button in preview modal + import
modal that can load projects via url). Preliminary validation on url, still
needs error handling.
---
src/components/gui/gui.jsx | 7 +
src/components/import-modal/import-modal.css | 200 ++++++++++++++++++
src/components/import-modal/import-modal.jsx | 132 ++++++++++++
.../preview-modal/preview-modal.css | 9 +-
.../preview-modal/preview-modal.jsx | 11 +
src/containers/gui.jsx | 2 +
src/containers/import-modal.jsx | 112 ++++++++++
src/containers/preview-modal.jsx | 17 +-
src/css/colors.css | 2 +
src/reducers/modals.js | 19 +-
10 files changed, 503 insertions(+), 8 deletions(-)
create mode 100644 src/components/import-modal/import-modal.css
create mode 100644 src/components/import-modal/import-modal.jsx
create mode 100644 src/containers/import-modal.jsx
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 488e705e8..5c7a378dc 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -19,6 +19,7 @@ import Box from '../box/box.jsx';
import FeedbackForm from '../feedback-form/feedback-form.jsx';
import MenuBar from '../menu-bar/menu-bar.jsx';
import PreviewModal from '../../containers/preview-modal.jsx';
+import ImportModal from '../../containers/import-modal.jsx';
import WebGlModal from '../../containers/webgl-modal.jsx';
import layout from '../../lib/layout-constants.js';
@@ -41,6 +42,7 @@ const GUIComponent = props => {
children,
costumesTabVisible,
feedbackFormVisible,
+ importInfoVisible,
intl,
onExtensionButtonClick,
onActivateTab,
@@ -76,6 +78,9 @@ const GUIComponent = props => {
{previewInfoVisible ? (
) : null}
+ {importInfoVisible ? (
+
+ ) : null}
{feedbackFormVisible ? (
) : null}
@@ -169,9 +174,11 @@ GUIComponent.propTypes = {
children: PropTypes.node,
costumesTabVisible: PropTypes.bool,
feedbackFormVisible: PropTypes.bool,
+ importInfoVisible: PropTypes.bool,
intl: intlShape.isRequired,
onActivateTab: PropTypes.func,
onExtensionButtonClick: PropTypes.func,
+ onTabSelect: PropTypes.func,
previewInfoVisible: PropTypes.bool,
soundsTabVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
diff --git a/src/components/import-modal/import-modal.css b/src/components/import-modal/import-modal.css
new file mode 100644
index 000000000..80a2de576
--- /dev/null
+++ b/src/components/import-modal/import-modal.css
@@ -0,0 +1,200 @@
+@import "../../css/colors.css";
+@import "../../css/units.css";
+@import "../../css/typography.css";
+
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ background-color: hsla(215, 100%, 65%, .9);
+}
+
+.modal-content {
+ margin: 100px auto;
+ outline: none;
+ border: .25rem solid hsla(0, 100%, 100%, .25);
+ padding: 0;
+ border-radius: $space;
+ user-select: none;
+ width: 500px;
+
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ color: $text-primary;
+ overflow: hidden;
+}
+
+/* .illustration {
+ width: 100%;
+ height: 208px;
+ background-color: $motion-primary;
+ background-image: url('./welcome.png');
+ background-size: cover;
+} */
+
+/* .input {
+ margin-bottom: 1.5rem;
+ width: 100%;
+ border: 1px solid rgba(0,0,0,.1);
+ border-radius: 5px;
+ padding: 0 1rem;
+ height: 3rem;
+ color: #6b6b6b;
+ font-size: .875rem;
+} */
+
+/*
+ Modal header has 3 items:
+ |filter title x|
+
+ Use the same width for both side item containers,
+ so that title remains centered
+*/
+$sides: 20rem;
+
+.header {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ height: $library-header-height;
+
+ box-sizing: border-box;
+ width: 100%;
+ background-color: $import-primary;
+
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 1rem;
+ font-weight: normal;
+}
+
+.header-item {
+ display: flex;
+ align-items: center;
+ padding: 1rem;
+ text-decoration: none;
+ color: white;
+ user-select: none;
+}
+
+.header-item-filter {
+ display: flex;
+ flex-basis: $sides;
+ justify-content: flex-end;
+}
+
+.header-item-title {
+ flex-grow: 1;
+ flex-shrink: 0;
+ justify-content: center;
+ user-select: none;
+ letter-spacing: 0.4px;
+ cursor: default;
+}
+
+.header-item-close {
+ display: flex;
+ flex-basis: $sides;
+ justify-content: flex-start;
+}
+
+.body {
+ background: $ui-pane-gray;
+ padding: 1.5rem 2.25rem;
+ text-align: center;
+}
+
+.input-row {
+ margin: 1.5rem 0;
+ font-weight: bolder;
+ text-align: right;
+ display: flex;
+ justify-content: center;
+ /*border: 1px solid $import-primary;
+ border-radius: 0.25rem;*/
+}
+
+.input-row input {
+ width: 100%;
+ padding: 0 1rem;
+ height: 3rem;
+ color: #6b6b6b;
+ font-size: .875rem;
+}
+
+.input-row input::placeholder {
+ font-style: italic;
+ color: rgba(87,94,117,0.5);
+}
+
+.input-row input:focus {
+ outline: none;
+ border: 1px solid $motion-primary;
+ border-radius: 0.25rem
+}
+
+.input-row button {
+ padding: 0.5rem 2rem;
+ font-weight: bold;
+ font-size: .875rem;
+ cursor: pointer;
+ border: 1px solid $import-primary;
+ border-radius: 0.25rem;
+}
+
+.input-row button.ok-button {
+ background: $import-primary;
+ color: white;
+}
+
+/* .buttonRow input + button {
+ border: 1px solid $motion-primary; /* rgba(0,0,0,.1);
+ border-radius: 0.25rem;
+} */
+
+/* Confirmation buttons at the bottom of the modal */
+.button-row {
+ margin: 1.5rem 0;
+ font-weight: bolder;
+ text-align: right;
+ display: flex;
+ justify-content: center;
+}
+
+.button-row button {
+ padding: 0.5rem 2rem;
+ background: white;
+ font-weight: bold;
+ font-size: .875rem;
+ cursor: pointer;
+}
+
+.button-row button.no-button {
+ border: 1px solid $motion-primary;
+ border-radius: 0.25rem;
+ color: $motion-primary;
+}
+.button-row button + button {
+ margin-left: 0.5rem;
+}
+
+/* .cat-icon {
+ margin-left: .125rem;
+ width: 1.5rem;
+ height: 1.5rem;
+ vertical-align: middle;
+} */
+
+.faq-link-text {
+ margin: 2rem 0 .5rem 0;
+ font-size: .875rem;
+ color: $text-primary;
+}
+
+.faq-link {
+ color: $motion-primary;
+ text-decoration: none;
+}
diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx
new file mode 100644
index 000000000..9a9ad872d
--- /dev/null
+++ b/src/components/import-modal/import-modal.jsx
@@ -0,0 +1,132 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactModal from 'react-modal';
+import Box from '../box/box.jsx';
+import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
+import classNames from 'classnames';
+
+import CloseButton from '../close-button/close-button.jsx';
+
+import styles from './import-modal.css';
+
+const messages = defineMessages({
+ title: {
+ id: 'gui.importInfo.title',
+ defaultMessage: 'View a Scratch 2.0 Project',
+ description: 'Scratch 2.0 import modal label - for accessibility'
+ }
+});
+
+const ImportModal = ({intl, ...props}) => (
+
+
+
+
+
+
+
+
+ {intl.formatMessage({...messages.title})}
+
+
+
+ {null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+
+
+
+);
+
+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}) => (
-
:
Date: Wed, 21 Feb 2018 15:59:31 -0500
Subject: [PATCH 25/65] Fixing lint errors.
---
src/components/import-error/import-error.jsx | 19 +++++---
src/components/import-input/import-input.jsx | 46 +++++++++----------
src/components/import-modal/import-modal.jsx | 29 ++++++------
.../preview-modal/preview-modal.jsx | 3 +-
src/containers/import-modal.jsx | 27 +++++------
5 files changed, 67 insertions(+), 57 deletions(-)
diff --git a/src/components/import-error/import-error.jsx b/src/components/import-error/import-error.jsx
index 5147138a6..bc8f287ea 100644
--- a/src/components/import-error/import-error.jsx
+++ b/src/components/import-error/import-error.jsx
@@ -1,12 +1,15 @@
import bindAll from 'lodash.bindall';
import classNames from 'classnames';
-import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
+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';
+// TODO store different error messages depending on the situation (?) and
+// needs to use intl lib for localization support
+
class ImportErrorContent extends React.Component {
constructor (props) {
super(props);
@@ -56,10 +59,11 @@ class ImportErrorContent extends React.Component {
ImportErrorContent.propTypes = {
className: PropTypes.string,
- intl: intlShape,
+ errorMessage: PropTypes.string.isRequired,
+ // intl: intlShape,
place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
- tooltipId: PropTypes.string.isRequired,
- errorMessage: PropTypes.string.isRequired
+ tooltipId: PropTypes.string.isRequired
+
};
ImportErrorContent.defaultProps = {
@@ -82,9 +86,9 @@ const ImportErrorTooltip = props => (
);
@@ -94,10 +98,11 @@ ImportErrorTooltip.propTypes = {
className: PropTypes.string,
delayHide: PropTypes.number,
delayShow: PropTypes.number,
+ errorMessage: PropTypes.string.isRequired,
place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
tooltipClassName: PropTypes.string,
- tooltipId: PropTypes.string.isRequired,
- errorMessage: PropTypes.string.isRequired
+ tooltipId: PropTypes.string.isRequired
+
};
ImportErrorTooltip.defaultProps = {
diff --git a/src/components/import-input/import-input.jsx b/src/components/import-input/import-input.jsx
index 997de86af..bc4d7c0c2 100644
--- a/src/components/import-input/import-input.jsx
+++ b/src/components/import-input/import-input.jsx
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
-import classNames from 'classnames';
+// import classNames from 'classnames';
import {ImportErrorTooltip} from '../import-error/import-error.jsx';
@@ -8,50 +8,50 @@ import {ImportErrorTooltip} from '../import-error/import-error.jsx';
class ImportInput extends React.Component {
render () {
let input = null;
- if (!this.props.hasValidationError) {
- input = (
-
- );
- } else {
+ if (this.props.hasValidationError) {
input = (
);
+ } else {
+ input = (
+
+ );
}
return input;
}
}
ImportInput.propTypes = {
- onChange: PropTypes.func.isRequired,
- onKeyPress: PropTypes.func.isRequired,
- placeholder: PropTypes.string,
+ badClassName: PropTypes.string,
+ errorDivClassName: PropTypes.string,
errorMessage: PropTypes.string.isRequired,
hasValidationError: PropTypes.bool.isRequired,
inputValue: PropTypes.string.isRequired,
okClassName: PropTypes.string,
- badClassName: PropTypes.string,
- errorDivClassName: PropTypes.string
+ onChange: PropTypes.func.isRequired,
+ onKeyPress: PropTypes.func.isRequired,
+ placeholder: PropTypes.string
};
export default ImportInput;
diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx
index 9df475f12..03a1aef8f 100644
--- a/src/components/import-modal/import-modal.jsx
+++ b/src/components/import-modal/import-modal.jsx
@@ -15,6 +15,12 @@ const messages = defineMessages({
id: 'gui.importInfo.title',
defaultMessage: 'View a Scratch 2.0 Project',
description: 'Scratch 2.0 import modal label - for accessibility'
+ },
+ formDescription: {
+ defaultMessage:
+ 'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Preview will not be saved.',
+ description: 'Import project message',
+ id: 'gui.importInfo.message'
}
});
@@ -57,24 +63,20 @@ const ImportModal = ({intl, ...props}) => (
-
+ {intl.formatMessage({...messages.formDescription})}
+
+ {props.hasValidationError ?
+
+
+ {/* intl.formatMessage({...messages.invalidLink})*/}
+
+
+
: null}
+
@@ -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}
>
{
if (buttons.length > 0) {
const {img, title, onClick} = buttons[0];
- const moreButtons = buttons.slice(0);
+ const moreButtons = buttons.slice(1);
newButtonSection = (
Date: Mon, 26 Feb 2018 10:42:28 -0500
Subject: [PATCH 52/65] Remove duplicate backdrop library
---
src/containers/costume-tab.jsx | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx
index 9621cfba0..e3d754be4 100644
--- a/src/containers/costume-tab.jsx
+++ b/src/containers/costume-tab.jsx
@@ -7,12 +7,10 @@ import VM from 'scratch-vm';
import AssetPanel from '../components/asset-panel/asset-panel.jsx';
import PaintEditorWrapper from './paint-editor-wrapper.jsx';
import CostumeLibrary from './costume-library.jsx';
-import BackdropLibrary from './backdrop-library.jsx';
import {connect} from 'react-redux';
import {
closeCostumeLibrary,
- closeBackdropLibrary,
openCostumeLibrary,
openBackdropLibrary
} from '../reducers/modals';
@@ -163,9 +161,7 @@ class CostumeTab extends React.Component {
onNewLibraryBackdropClick,
onNewLibraryCostumeClick,
costumeLibraryVisible,
- backdropLibraryVisible,
onRequestCloseCostumeLibrary,
- onRequestCloseBackdropLibrary,
...props
} = this.props;
@@ -234,13 +230,6 @@ class CostumeTab extends React.Component {
onRequestClose={onRequestCloseCostumeLibrary}
/>
) : null}
- {backdropLibraryVisible ? (
-
- ) : null}
);
}
@@ -289,9 +278,6 @@ const mapDispatchToProps = dispatch => ({
e.preventDefault();
dispatch(openCostumeLibrary());
},
- onRequestCloseBackdropLibrary: () => {
- dispatch(closeBackdropLibrary());
- },
onRequestCloseCostumeLibrary: () => {
dispatch(closeCostumeLibrary());
}
From 7dd4e1b8703c6e5186959ed0654a9490e88e76e9 Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Mon, 26 Feb 2018 10:58:10 -0500
Subject: [PATCH 53/65] Update and fix the default message for libraries
---
src/components/sprite-selector/sprite-selector.jsx | 2 +-
src/components/stage-selector/stage-selector.jsx | 2 +-
src/containers/costume-tab.jsx | 8 ++++----
src/containers/sound-tab.jsx | 4 ++--
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index c1ae71075..7590f286e 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -19,7 +19,7 @@ const messages = defineMessages({
addSpriteFromLibrary: {
id: 'gui.spriteSelector.addSpriteFromLibrary',
description: 'Button to add a sprite in the target pane from library',
- defaultMessage: 'Library'
+ defaultMessage: 'Sprite Library'
},
addSpriteFromPaint: {
id: 'gui.spriteSelector.addSpriteFromPaint',
diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx
index 8c7a00180..073ca257c 100644
--- a/src/components/stage-selector/stage-selector.jsx
+++ b/src/components/stage-selector/stage-selector.jsx
@@ -18,7 +18,7 @@ const messages = defineMessages({
addBackdropFromLibrary: {
id: 'gui.spriteSelector.addBackdropFromLibrary',
description: 'Button to add a stage in the target pane from library',
- defaultMessage: 'Library'
+ defaultMessage: 'Backdrop Library'
},
addBackdropFromPaint: {
id: 'gui.stageSelector.addBackdropFromPaint',
diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx
index e3d754be4..2e897c451 100644
--- a/src/containers/costume-tab.jsx
+++ b/src/containers/costume-tab.jsx
@@ -27,14 +27,14 @@ import backdropLibraryContent from '../lib/libraries/backdrops.json';
const messages = defineMessages({
addLibraryBackdropMsg: {
- defaultMessage: 'Library',
+ defaultMessage: 'Backdrop Library',
description: 'Button to add a backdrop in the editor tab',
- id: 'gui.costumeTab.addBackdrop'
+ id: 'gui.costumeTab.addBackdropFromLibrary'
},
addLibraryCostumeMsg: {
- defaultMessage: 'Library',
+ defaultMessage: 'Costume Library',
description: 'Button to add a costume in the editor tab',
- id: 'gui.costumeTab.addCostume'
+ id: 'gui.costumeTab.addCostumeFromLibrary'
},
addBlankCostumeMsg: {
defaultMessage: 'Paint',
diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx
index 400370dd2..471ebe99e 100644
--- a/src/containers/sound-tab.jsx
+++ b/src/containers/sound-tab.jsx
@@ -130,9 +130,9 @@ class SoundTab extends React.Component {
id: 'gui.soundTab.recordSound'
},
addSound: {
- defaultMessage: 'Library',
+ defaultMessage: 'Sound Library',
description: 'Button to add a sound in the editor tab',
- id: 'gui.soundTab.addSound'
+ id: 'gui.soundTab.addSoundFromLibrary'
}
});
From e56d69ccc3f6ce50c30e0e211baeec3430505fe9 Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Mon, 26 Feb 2018 11:00:41 -0500
Subject: [PATCH 54/65] Fix tests
---
test/integration/costumes.test.js | 4 ++--
test/integration/sounds.test.js | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/test/integration/costumes.test.js b/test/integration/costumes.test.js
index f07c3d0f3..00700cba2 100644
--- a/test/integration/costumes.test.js
+++ b/test/integration/costumes.test.js
@@ -29,7 +29,7 @@ describe('Working with costumes', () => {
await loadUri(uri);
await clickXpath('//button[@title="tryit"]');
await clickText('Costumes');
- await clickXpath('//button[@title="Add Costume"]');
+ await clickXpath('//button[@aria-label="Costume Library"]');
const el = await findByXpath("//input[@placeholder='what are you looking for?']");
await el.sendKeys('abb');
await clickText('Abby-a'); // Should close the modal, then click the costumes in the selector
@@ -57,7 +57,7 @@ describe('Working with costumes', () => {
test('Adding a backdrop', async () => {
await loadUri(uri);
await clickXpath('//button[@title="tryit"]');
- await clickXpath('//button[@title="Add Backdrop"]');
+ await clickXpath('//button[@aria-label="Backdrop Library"]');
const el = await findByXpath("//input[@placeholder='what are you looking for?']");
await el.sendKeys('blue');
await clickText('Blue Sky'); // Should close the modal
diff --git a/test/integration/sounds.test.js b/test/integration/sounds.test.js
index e31548684..fc36c55a5 100644
--- a/test/integration/sounds.test.js
+++ b/test/integration/sounds.test.js
@@ -37,13 +37,13 @@ describe('Working with sounds', () => {
.accept();
// Add it back
- await clickXpath('//button[@title="Add Sound"]');
+ await clickXpath('//button[@aria-label="Sound Library"]');
let el = await findByXpath("//input[@placeholder='what are you looking for?']");
await el.sendKeys('meow');
await clickText('Meow', scope.modal); // Should close the modal
// Add a new sound
- await clickXpath('//button[@title="Add Sound"]');
+ await clickXpath('//button[@aria-label="Sound Library"]');
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
@@ -86,11 +86,11 @@ describe('Working with sounds', () => {
// Add a sound so this sprite has 2 sounds.
await clickText('Sounds');
- await clickXpath('//button[@title="Add Sound"]');
+ await clickXpath('//button[@aria-label="Sound Library"]');
await clickText('A Bass'); // Closes the modal
// Now add a sprite with only one sound.
- await clickXpath('//button[@title="Add Sprite"]');
+ await clickXpath('//button[@aria-label="Sprite Library"]');
await clickText('Abby'); // Doing this used to crash the editor.
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for error
From 77972be35b0b7b8eb66d378ef135564b03bdd4ac Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Mon, 26 Feb 2018 11:19:47 -0500
Subject: [PATCH 55/65] Only update state if menu is not already open.
This makes it possible to use on iOS/touch
---
src/components/action-menu/action-menu.jsx | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx
index dfefb5c51..d67c95d85 100644
--- a/src/components/action-menu/action-menu.jsx
+++ b/src/components/action-menu/action-menu.jsx
@@ -27,16 +27,12 @@ class ActionMenu extends React.Component {
this.closeTimeoutId = null;
}, CLOSE_DELAY);
}
- handleToggleOpenState (e) {
- if (!this.state.isOpen) {
- e.stopPropagation(); // For touch start, to prevent clicking primary button
- }
-
+ handleToggleOpenState () {
// Mouse enter back in after timeout was started prevents it from closing.
if (this.closeTimeoutId) {
clearTimeout(this.closeTimeoutId);
this.closeTimeoutId = null;
- } else {
+ } else if (!this.state.isOpen) {
this.setState({
isOpen: true,
forceHide: false
From 47a2f4383a7f5e9b07cdf9800ce66d3dd88866b6 Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Mon, 26 Feb 2018 12:23:33 -0500
Subject: [PATCH 56/65] Make the fade out on costume selector not block clicks
---
src/components/asset-panel/selector.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css
index 24a5c2ec6..db9bca069 100644
--- a/src/components/asset-panel/selector.css
+++ b/src/components/asset-panel/selector.css
@@ -35,6 +35,7 @@ $fade-out-distance: 100px;
background: linear-gradient(rgba(232,237,241, 0),rgba(232,237,241, 1));
height: $fade-out-distance;
width: 100%;
+ pointer-events: none;
}
.new-buttons > button + button {
From 635a131745259eadf887dcab90b92cccfc8122d3 Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Mon, 26 Feb 2018 13:43:55 -0500
Subject: [PATCH 57/65] Fix prop type warnings
---
src/containers/stage-selector.jsx | 1 +
src/containers/target-pane.jsx | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx
index 3d280bd16..27075cef8 100644
--- a/src/containers/stage-selector.jsx
+++ b/src/containers/stage-selector.jsx
@@ -54,6 +54,7 @@ class StageSelector extends React.Component {
/* eslint-disable no-unused-vars */
assetId,
id,
+ onActivateTab,
onSelect,
/* eslint-enable no-unused-vars */
...componentProps
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index a1665f808..8928d9fe8 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -72,9 +72,13 @@ class TargetPane extends React.Component {
}
}
render () {
+ const {
+ onActivateTab, // eslint-disable-line no-unused-vars
+ ...componentProps
+ } = this.props;
return (
Date: Mon, 26 Feb 2018 13:45:20 -0500
Subject: [PATCH 58/65] Fix flashing tooltips while project is playing.
I also suspect this will fix some of the times when the menu
mysteriously stayed open, because it was being re-rendered for by
mistake.
---
src/components/action-menu/action-menu.jsx | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx
index d67c95d85..4e7891ec6 100644
--- a/src/components/action-menu/action-menu.jsx
+++ b/src/components/action-menu/action-menu.jsx
@@ -21,6 +21,16 @@ class ActionMenu extends React.Component {
forceHide: false
};
}
+ shouldComponentUpdate (newProps, newState) {
+ // This check prevents re-rendering while the project is updating.
+ // @todo check only the state and the title because it is enough to know
+ // if anything substantial has changed
+ // This is needed because of the sloppy way the props are passed as a new object,
+ // which should be refactored.
+ return newState.isOpen !== this.state.isOpen ||
+ newState.forceHide !== this.state.forceHide ||
+ newProps.title !== this.props.title;
+ }
handleClosePopover () {
this.closeTimeoutId = setTimeout(() => {
this.setState({isOpen: false});
From a970d6685534b2e7d36d3724196624ee2385f221 Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Mon, 26 Feb 2018 14:30:48 -0500
Subject: [PATCH 59/65] Fix asset menus on touch
---
src/components/action-menu/action-menu.jsx | 37 ++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx
index 4e7891ec6..910d1ab6a 100644
--- a/src/components/action-menu/action-menu.jsx
+++ b/src/components/action-menu/action-menu.jsx
@@ -12,15 +12,25 @@ class ActionMenu extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
+ 'clickDelayer',
'handleClosePopover',
'handleToggleOpenState',
- 'clickDelayer'
+ 'handleTouchStart',
+ 'handleTouchOutside',
+ 'setButtonRef',
+ 'setContainerRef'
]);
this.state = {
isOpen: false,
forceHide: false
};
}
+ componentDidMount () {
+ // Touch start on the main button is caught to trigger open and not click
+ this.buttonRef.addEventListener('touchstart', this.handleTouchStart);
+ // Touch start on document is used to trigger close if it is outside
+ document.addEventListener('touchstart', this.handleTouchOutside);
+ }
shouldComponentUpdate (newProps, newState) {
// This check prevents re-rendering while the project is updating.
// @todo check only the state and the title because it is enough to know
@@ -31,6 +41,10 @@ class ActionMenu extends React.Component {
newState.forceHide !== this.state.forceHide ||
newProps.title !== this.props.title;
}
+ componentWillUnmount () {
+ this.buttonRef.removeEventListener('touchstart', this.handleTouchStart);
+ document.removeEventListener('touchstart', this.handleTouchOutside);
+ }
handleClosePopover () {
this.closeTimeoutId = setTimeout(() => {
this.setState({isOpen: false});
@@ -49,6 +63,11 @@ class ActionMenu extends React.Component {
});
}
}
+ handleTouchOutside (e) {
+ if (this.state.isOpen && !this.containerRef.contains(e.target)) {
+ this.setState({isOpen: false});
+ }
+ }
clickDelayer (fn) {
// Return a wrapped action that manages the menu closing.
// @todo we may be able to use react-transition for this in the future
@@ -61,6 +80,19 @@ class ActionMenu extends React.Component {
});
};
}
+ handleTouchStart (e) {
+ // Prevent this touch from becoming a click if menu is closed
+ if (!this.state.isOpen) {
+ e.preventDefault();
+ this.handleToggleOpenState();
+ }
+ }
+ setButtonRef (ref) {
+ this.buttonRef = ref;
+ }
+ setContainerRef (ref) {
+ this.containerRef = ref;
+ }
render () {
const {
className,
@@ -78,15 +110,16 @@ class ActionMenu extends React.Component {
[styles.expanded]: this.state.isOpen,
[styles.forceHidden]: this.state.forceHide
})}
+ ref={this.setContainerRef}
onMouseEnter={this.handleToggleOpenState}
onMouseLeave={this.handleClosePopover}
- onTouchStart={this.handleToggleOpenState}
>
Date: Mon, 26 Feb 2018 16:56:18 -0500
Subject: [PATCH 60/65] Switch to mouse enter and mouse leave
---
.../sprite-selector-item.jsx | 8 ++---
.../sprite-selector/sprite-selector.css | 34 +++++++++----------
src/containers/sprite-selector-item.jsx | 12 +++----
.../sprite-selector-item.test.jsx.snap | 3 +-
4 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/src/components/sprite-selector-item/sprite-selector-item.jsx b/src/components/sprite-selector-item/sprite-selector-item.jsx
index c1b288a8c..abb47bb54 100644
--- a/src/components/sprite-selector-item/sprite-selector-item.jsx
+++ b/src/components/sprite-selector-item/sprite-selector-item.jsx
@@ -19,8 +19,8 @@ const SpriteSelectorItem = props => (
[styles.isSelected]: props.selected
}),
onClick: props.onClick,
- onMouseOver: props.onMouseOver,
- onMouseOut: props.onMouseOut
+ onMouseEnter: props.onMouseEnter,
+ onMouseLeave: props.onMouseLeave
}}
id={`${props.name}-${contextMenuId}`}
>
@@ -72,8 +72,8 @@ SpriteSelectorItem.propTypes = {
onClick: PropTypes.func,
onDeleteButtonClick: PropTypes.func,
onDuplicateButtonClick: PropTypes.func,
- onMouseOut: PropTypes.func,
- onMouseOver: PropTypes.func,
+ onMouseEnter: PropTypes.func,
+ onMouseLeave: PropTypes.func,
selected: PropTypes.bool.isRequired
};
diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css
index d46e05086..ec5cfee2b 100644
--- a/src/components/sprite-selector/sprite-selector.css
+++ b/src/components/sprite-selector/sprite-selector.css
@@ -115,32 +115,32 @@
}
.receivedBlocks {
- -webkit-animation: glowing 250ms;
- -moz-animation: glowing 250ms;
- -o-animation: glowing 250ms;
- animation: glowing 250ms;
+ -webkit-animation: glowing 250ms;
+ -moz-animation: glowing 250ms;
+ -o-animation: glowing 250ms;
+ animation: glowing 250ms;
}
@-webkit-keyframes glowing {
- 10% { -webkit-box-shadow: 0 0 10px #7fff1e; }
- 90% { -webkit-box-shadow: 0 0 10px #7fff1e; }
- 100% { -webkit-box-shadow: none; }
+ 10% { -webkit-box-shadow: 0 0 10px #7fff1e; }
+ 90% { -webkit-box-shadow: 0 0 10px #7fff1e; }
+ 100% { -webkit-box-shadow: none; }
}
@-moz-keyframes glowing {
- 10% { -moz-box-shadow: 0 0 10px #7fff1e; }
- 90% { -moz-box-shadow: 0 0 10px #7fff1e; }
- 100% { -moz-box-shadow: none; }
+ 10% { -moz-box-shadow: 0 0 10px #7fff1e; }
+ 90% { -moz-box-shadow: 0 0 10px #7fff1e; }
+ 100% { -moz-box-shadow: none; }
}
@-o-keyframes glowing {
- 10% { box-shadow: 0 0 10px #7fff1e; }
- 90% { box-shadow: 0 0 10px #7fff1e; }
- 100% { box-shadow: none; }
+ 10% { box-shadow: 0 0 10px #7fff1e; }
+ 90% { box-shadow: 0 0 10px #7fff1e; }
+ 100% { box-shadow: none; }
}
@keyframes glowing {
- 10% { box-shadow: 0 0 10px #7fff1e; }
- 90% { box-shadow: 0 0 10px #7fff1e; }
- 100% { box-shadow: none; }
-}
\ No newline at end of file
+ 10% { box-shadow: 0 0 10px #7fff1e; }
+ 90% { box-shadow: 0 0 10px #7fff1e; }
+ 100% { box-shadow: none; }
+}
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index 44253e6cf..edb124b2c 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -14,8 +14,8 @@ class SpriteSelectorItem extends React.Component {
'handleClick',
'handleDelete',
'handleDuplicate',
- 'handleMouseOut',
- 'handleMouseOver'
+ 'handleMouseEnter',
+ 'handleMouseLeave'
]);
}
handleClick (e) {
@@ -34,10 +34,10 @@ class SpriteSelectorItem extends React.Component {
e.stopPropagation(); // To prevent from bubbling back to handleClick
this.props.onDuplicateButtonClick(this.props.id);
}
- handleMouseOut () {
+ handleMouseLeave () {
this.props.dispatchSetHoveredSprite(null);
}
- handleMouseOver () {
+ handleMouseEnter () {
this.props.dispatchSetHoveredSprite(this.props.id);
}
render () {
@@ -57,8 +57,8 @@ class SpriteSelectorItem extends React.Component {
onClick={this.handleClick}
onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
- onMouseOut={this.handleMouseOut}
- onMouseOver={this.handleMouseOver}
+ onMouseEnter={this.handleMouseEnter}
+ onMouseLeave={this.handleMouseLeave}
{...props}
/>
);
diff --git a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap
index 0b61da9d0..4ecf19565 100644
--- a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap
+++ b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap
@@ -6,8 +6,9 @@ exports[`SpriteSelectorItemComponent matches snapshot when selected 1`] = `
onClick={[Function]}
onContextMenu={[Function]}
onMouseDown={[Function]}
+ onMouseEnter={undefined}
+ onMouseLeave={undefined}
onMouseOut={[Function]}
- onMouseOver={undefined}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
From 0de87a86727452515bd49694f10a7eed7a2ca130 Mon Sep 17 00:00:00 2001
From: DD
Date: Mon, 26 Feb 2018 17:11:45 -0500
Subject: [PATCH 61/65] Update vm
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index e44c89a02..685aae913 100644
--- a/package.json
+++ b/package.json
@@ -94,7 +94,7 @@
"scratch-paint": "0.2.0-prerelease.20180222192821",
"scratch-render": "0.1.0-prerelease.1516837442",
"scratch-storage": "0.4.0",
- "scratch-vm": "0.1.0-prerelease.1519653784-prerelease.1519653801",
+ "scratch-vm": "0.1.0-prerelease.1519681201-prerelease.1519681262",
"selenium-webdriver": "3.6.0",
"startaudiocontext": "1.2.1",
"style-loader": "^0.20.0",
From 0a741b1083cd7d1eaa482cab9284e421e03c061d Mon Sep 17 00:00:00 2001
From: Moran T
Date: Tue, 27 Feb 2018 15:11:52 +0200
Subject: [PATCH 62/65] Adding constraint on min height for large stage
Should help with smaller screen sizes, where the ratio is different (like some chromebooks)
---
src/components/gui/gui.jsx | 25 +++++++++++++---------
src/components/sprite-info/sprite-info.jsx | 15 ++++++++++---
src/lib/layout-constants.js | 1 +
3 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 5c7a378dc..c2d025dd7 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -144,16 +144,21 @@ const GUIComponent = props => {
{/* eslint-disable arrow-body-style */}
- {isFullSize => {
- return isRendererSupported ? (
-
- ) : null;
- }}
+
+ {isFullSize => {
+ return isRendererSupported ? (
+
+ ) : null;
+ }}
+
{/* eslint-enable arrow-body-style */}
diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx
index 7d0acf952..32044e59d 100644
--- a/src/components/sprite-info/sprite-info.jsx
+++ b/src/components/sprite-info/sprite-info.jsx
@@ -51,7 +51,10 @@ class SpriteInfo extends React.Component {
-
+
-
+
-
+
-
+
-
+
-
+