diff --git a/package.json b/package.json index 3b7c7e967..5ce6baf4f 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "react-redux": "5.0.2", "react-style-proptype": "2.0.1", "redux": "3.6.0", + "redux-throttle": "0.1.1", "scratch-audio": "latest", "scratch-blocks": "latest", "scratch-render": "latest", diff --git a/src/components/sprite-info/sprite-info.css b/src/components/sprite-info/sprite-info.css index 810ab542c..3a8c2121e 100644 --- a/src/components/sprite-info/sprite-info.css +++ b/src/components/sprite-info/sprite-info.css @@ -36,29 +36,27 @@ } /* TODO: refactor from descendant selector, to a regular class */ -.group img { +.group img { display: inline-block; - width: 1rem; + width: 1rem; height: 1rem; margin: 0 0.1rem; } +.icon { + opacity: 0.4; +} + +.icon.is-active { + opacity: 1; +} + .draggable-icon { - width: 0.55rem !important; - opacity: 1 !important; + width: 0.55rem; } .not-draggable-icon { - width: 0.75rem !important; - opacity: 0.4 !important; -} - -.show-icon { - opacity: 1 !important; -} - -.hide-icon { - opacity: 0.4 !important; + width: 0.75rem; } .input-label { @@ -87,19 +85,19 @@ font-size: 0.625rem; font-weight: bold; border-width: 1px; - border-style: solid; - border-color: #e9eef2; - border-radius: 2rem; + border-style: solid; + border-color: #e9eef2; + border-radius: 2rem; cursor: text; - transition: 0.25s ease-out; /* TODO: standardize with var */ + transition: 0.25s ease-out; /* TODO: standardize with var */ box-shadow: none; - /* + /* For truncating overflowing text gracefully Min-width is for a bug: https://css-tricks.com/flexbox-truncated-text TODO: move this out into a mixin or a helper component */ - overflow: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; @@ -122,5 +120,9 @@ border: 1px solid #e9eef2; border-radius: 0.25rem; cursor: pointer; - transition: 0.25s ease-out; /* TODO: standardize with var */ + transition: 0.25s ease-out; /* TODO: standardize with var */ +} + +.radio-box.is-disabled { + cursor: default; } diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx index f7604696b..bb88b7abb 100644 --- a/src/components/sprite-info/sprite-info.jsx +++ b/src/components/sprite-info/sprite-info.jsx @@ -11,101 +11,175 @@ const hideIcon = require('./icon--hide.svg'); const draggableIcon = require('./icon--draggable.svg'); const notDraggableIcon = require('./icon--not-draggable.svg'); -const SpriteInfo = props => ( - -
-
- - {props.name} - - -
+const ROTATION_STYLES = ['left-right', 'don\'t rotate', 'all around']; -
- - x - -
- -
- - y - -
-
+class SpriteInfo extends React.Component { + shouldComponentUpdate (nextProps) { + return ( + this.props.disabled !== nextProps.disabled || + this.props.draggable !== nextProps.draggable || + this.props.name !== nextProps.name || + this.props.rotationStyle !== nextProps.rotationStyle || + this.props.visible !== nextProps.visible || + this.props.x !== nextProps.x || + this.props.y !== nextProps.y + ); + } + render () { + return ( + +
+
+ Sprite + +
+
+ + x + +
-
-
- - Show - -
- - +
+ + y + +
-
-
- - Draggable - -
- - + +
+
+ + Show + +
+ + +
+
+ +
+ + Draggable + +
+ + +
+
+ +
+ + Rotation + + +
-
- -
- - Rotation - - -
-
- -); + + ); + } +} SpriteInfo.propTypes = { - name: React.PropTypes.string + disabled: React.PropTypes.bool, + draggable: React.PropTypes.bool, + name: React.PropTypes.string, + onChangeName: React.PropTypes.func, + onChangeRotationStyle: React.PropTypes.func, + onChangeX: React.PropTypes.func, + onChangeY: React.PropTypes.func, + onClickDraggable: React.PropTypes.func, + onClickNotDraggable: React.PropTypes.func, + onClickNotVisible: React.PropTypes.func, + onClickVisible: React.PropTypes.func, + rotationStyle: React.PropTypes.oneOf(ROTATION_STYLES), + visible: React.PropTypes.bool, + x: React.PropTypes.number, + y: React.PropTypes.number }; module.exports = SpriteInfo; diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index 89d29da10..e33ccaf29 100644 --- a/src/components/sprite-selector/sprite-selector.jsx +++ b/src/components/sprite-selector/sprite-selector.jsx @@ -1,23 +1,49 @@ const React = require('react'); const Box = require('../box/box.jsx'); +const SpriteInfo = require('../../containers/sprite-info.jsx'); const SpriteSelectorItem = require('../../containers/sprite-selector-item.jsx'); -const SpriteInfo = require('../sprite-info/sprite-info.jsx'); const styles = require('./sprite-selector.css'); const SpriteSelectorComponent = function (props) { const { + onChangeSpriteDraggability, + onChangeSpriteName, + onChangeSpriteRotationStyle, + onChangeSpriteVisibility, + onChangeSpriteX, + onChangeSpriteY, onSelectSprite, selectedId, sprites, ...componentProps } = props; + let selectedSprite = sprites[selectedId]; + let spriteInfoDisabled = false; + if (typeof selectedSprite === 'undefined') { + selectedSprite = {}; + spriteInfoDisabled = true; + } return ( - + @@ -43,6 +69,12 @@ const SpriteSelectorComponent = function (props) { }; SpriteSelectorComponent.propTypes = { + onChangeSpriteDraggability: React.PropTypes.func, + onChangeSpriteName: React.PropTypes.func, + onChangeSpriteRotationStyle: React.PropTypes.func, + onChangeSpriteVisibility: React.PropTypes.func, + onChangeSpriteX: React.PropTypes.func, + onChangeSpriteY: React.PropTypes.func, onSelectSprite: React.PropTypes.func, selectedId: React.PropTypes.string, sprites: React.PropTypes.shape({ diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx index 241448035..c918b034e 100644 --- a/src/components/target-pane/target-pane.jsx +++ b/src/components/target-pane/target-pane.jsx @@ -37,6 +37,12 @@ class TargetPane extends React.Component { backdropLibraryVisible, costumeLibraryVisible, spriteLibraryVisible, + onChangeSpriteDraggability, + onChangeSpriteName, + onChangeSpriteRotationStyle, + onChangeSpriteVisibility, + onChangeSpriteX, + onChangeSpriteY, onNewSpriteClick, onNewBackdropClick, onRequestCloseBackdropLibrary, @@ -53,10 +59,16 @@ class TargetPane extends React.Component { className={styles.targetPane} {...componentProps} > - + @@ -118,15 +130,26 @@ const spriteShape = React.PropTypes.shape({ rotationCenterX: React.PropTypes.number, rotationCenterY: React.PropTypes.number }), + draggable: React.PropTypes.bool, id: React.PropTypes.string, name: React.PropTypes.string, - order: React.PropTypes.number + order: React.PropTypes.number, + rotationStyle: React.PropTypes.string, + visibility: React.PropTypes.bool, + x: React.PropTypes.number, + y: React.PropTypes.number }); TargetPane.propTypes = { backdropLibraryVisible: React.PropTypes.bool, costumeLibraryVisible: React.PropTypes.bool, editingTarget: React.PropTypes.string, + onChangeSpriteDraggability: React.PropTypes.func, + onChangeSpriteName: React.PropTypes.func, + onChangeSpriteRotationStyle: React.PropTypes.func, + onChangeSpriteVisibility: React.PropTypes.func, + onChangeSpriteX: React.PropTypes.func, + onChangeSpriteY: React.PropTypes.func, onNewBackdropClick: React.PropTypes.func, onNewSpriteClick: React.PropTypes.func, onRequestCloseBackdropLibrary: React.PropTypes.func, diff --git a/src/containers/sprite-info.jsx b/src/containers/sprite-info.jsx new file mode 100644 index 000000000..1d7cbcadd --- /dev/null +++ b/src/containers/sprite-info.jsx @@ -0,0 +1,80 @@ +const bindAll = require('lodash.bindall'); +const React = require('react'); + +const SpriteInfoComponent = require('../components/sprite-info/sprite-info.jsx'); + +class SpriteInfo extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChangeName', + 'handleChangeRotationStyle', + 'handleChangeX', + 'handleChangeY', + 'handleClickVisible', + 'handleClickNotVisible', + 'handleClickDraggable', + 'handleClickNotDraggable' + ]); + } + handleChangeName (e) { + this.props.onChangeName(e.target.value); + } + handleChangeRotationStyle (e) { + this.props.onChangeRotationStyle(e.target.value); + } + handleChangeX (e) { + let x = e.target.value; + if (x === '-') x = -1; + if (isNaN(x)) return; + this.props.onChangeX(x); + } + handleChangeY (e) { + let y = e.target.value; + if (y === '-') y = -1; + if (isNaN(y)) return; + this.props.onChangeY(y); + } + handleClickVisible (e) { + e.preventDefault(); + this.props.onChangeVisibility(true); + } + handleClickNotVisible (e) { + e.preventDefault(); + this.props.onChangeVisibility(false); + } + handleClickDraggable (e) { + e.preventDefault(); + this.props.onChangeDraggability(true); + } + handleClickNotDraggable (e) { + e.preventDefault(); + this.props.onChangeDraggability(false); + } + render () { + return ( + + ); + } +} + +SpriteInfo.propTypes = { + onChangeDraggability: React.PropTypes.func, + onChangeName: React.PropTypes.func, + onChangeRotationStyle: React.PropTypes.func, + onChangeVisibility: React.PropTypes.func, + onChangeX: React.PropTypes.func, + onChangeY: React.PropTypes.func +}; + +module.exports = SpriteInfo; diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 2911f7889..45033a72f 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -1,5 +1,4 @@ const bindAll = require('lodash.bindall'); -const pick = require('lodash.pick'); const React = require('react'); const {connect} = require('react-redux'); @@ -18,9 +17,33 @@ class TargetPane extends React.Component { constructor (props) { super(props); bindAll(this, [ + 'handleChangeSpriteDraggability', + 'handleChangeSpriteName', + 'handleChangeSpriteRotationStyle', + 'handleChangeSpriteVisibility', + 'handleChangeSpriteX', + 'handleChangeSpriteY', 'handleSelectSprite' ]); } + handleChangeSpriteDraggability (draggable) { + this.props.vm.postSpriteInfo({draggable}); + } + handleChangeSpriteName (name) { + this.props.vm.renameSprite(this.props.editingTarget, name); + } + handleChangeSpriteRotationStyle (rotationStyle) { + this.props.vm.postSpriteInfo({rotationStyle}); + } + handleChangeSpriteVisibility (visible) { + this.props.vm.postSpriteInfo({visible}); + } + handleChangeSpriteX (x) { + this.props.vm.postSpriteInfo({x}); + } + handleChangeSpriteY (y) { + this.props.vm.postSpriteInfo({y}); + } handleSelectSprite (id) { this.props.vm.setEditingTarget(id); } @@ -28,6 +51,12 @@ class TargetPane extends React.Component { return ( ); @@ -46,7 +75,10 @@ TargetPane.propTypes = { const mapStateToProps = state => ({ editingTarget: state.targets.editingTarget, sprites: Object.keys(state.targets.sprites).reduce((sprites, k) => { - sprites[k] = pick(state.targets.sprites[k], ['costume', 'name', 'order']); + let {x, y, ...sprite} = state.targets.sprites[k]; + if (typeof x !== 'undefined') x = Math.round(x); + if (typeof y !== 'undefined') y = Math.round(y); + sprites[k] = {...sprite, x, y}; return sprites; }, {}), stage: state.targets.stage, diff --git a/src/index.jsx b/src/index.jsx index a91c27b1d..a7ba6255e 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,7 +1,8 @@ const React = require('react'); const ReactDOM = require('react-dom'); const {Provider} = require('react-redux'); -const {createStore} = require('redux'); +const {createStore, applyMiddleware} = require('redux'); +const throttle = require('redux-throttle').default; const GUI = require('./containers/gui.jsx'); const log = require('./lib/log'); @@ -63,7 +64,9 @@ App.propTypes = { const appTarget = document.createElement('div'); appTarget.className = styles.app; document.body.appendChild(appTarget); -const store = createStore( +const store = applyMiddleware( + throttle(300, {leading: true, trailing: true}) +)(createStore)( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); diff --git a/src/reducers/targets.js b/src/reducers/targets.js index 61c01ebb3..c2f6219ac 100644 --- a/src/reducers/targets.js +++ b/src/reducers/targets.js @@ -30,7 +30,7 @@ const reducer = function (state, action) { .filter(target => !target.isStage) .reduce( (targets, target, listId) => defaultsDeep( - {[target.id]: {order: listId}}, + {[target.id]: {order: listId, ...target}}, {[target.id]: state.sprites[target.id]}, targets ), @@ -55,19 +55,28 @@ const reducer = function (state, action) { reducer.updateTarget = function (target) { return { type: UPDATE_TARGET, - target: target + target: target, + meta: { + throttle: 30 + } }; }; reducer.updateTargets = function (targetList) { return { type: UPDATE_TARGET_LIST, - targets: targetList + targets: targetList, + meta: { + throttle: 30 + } }; }; reducer.updateEditingTarget = function (editingTarget) { return { type: UPDATE_EDITING_TARGET, - target: editingTarget + target: editingTarget, + meta: { + throttle: 30 + } }; }; module.exports = reducer;