mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Switch state to track hover item ID instead of item itself
This commit is contained in:
parent
05ad64fd01
commit
e5303784a0
7 changed files with 58 additions and 45 deletions
|
@ -37,7 +37,6 @@
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.22.0",
|
"babel-plugin-transform-object-rest-spread": "^6.22.0",
|
||||||
"babel-preset-es2015": "^6.22.0",
|
"babel-preset-es2015": "^6.22.0",
|
||||||
"babel-preset-react": "^6.22.0",
|
"babel-preset-react": "^6.22.0",
|
||||||
"canvas-prebuilt": "^1.6.5-prerelease.1",
|
|
||||||
"classnames": "2.2.5",
|
"classnames": "2.2.5",
|
||||||
"css-loader": "0.28.3",
|
"css-loader": "0.28.3",
|
||||||
"enzyme": "^2.8.2",
|
"enzyme": "^2.8.2",
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
||||||
|
|
||||||
import SelectTool from '../helper/selection-tools/select-tool';
|
import SelectTool from '../helper/selection-tools/select-tool';
|
||||||
import SelectModeComponent from '../components/select-mode.jsx';
|
import SelectModeComponent from '../components/select-mode.jsx';
|
||||||
import paper from 'paper';
|
|
||||||
|
|
||||||
class SelectMode extends React.Component {
|
class SelectMode extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
@ -25,8 +24,8 @@ class SelectMode extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (this.tool && nextProps.hoveredItem !== this.props.hoveredItem) {
|
if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) {
|
||||||
this.tool.setPrevHoveredItem(nextProps.hoveredItem);
|
this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) {
|
if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) {
|
||||||
|
@ -57,7 +56,7 @@ class SelectMode extends React.Component {
|
||||||
SelectMode.propTypes = {
|
SelectMode.propTypes = {
|
||||||
clearHoveredItem: PropTypes.func.isRequired,
|
clearHoveredItem: PropTypes.func.isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
hoveredItem: PropTypes.instanceOf(paper.Item),
|
hoveredItemId: PropTypes.number,
|
||||||
isSelectModeActive: PropTypes.bool.isRequired,
|
isSelectModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired,
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
setHoveredItem: PropTypes.func.isRequired
|
setHoveredItem: PropTypes.func.isRequired
|
||||||
|
@ -65,11 +64,11 @@ SelectMode.propTypes = {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
isSelectModeActive: state.scratchPaint.mode === Modes.SELECT,
|
isSelectModeActive: state.scratchPaint.mode === Modes.SELECT,
|
||||||
hoveredItem: state.scratchPaint.hoveredItem
|
hoveredItemId: state.scratchPaint.hoveredItemId
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
setHoveredItem: hoveredItem => {
|
setHoveredItem: hoveredItemId => {
|
||||||
dispatch(setHoveredItem(hoveredItem));
|
dispatch(setHoveredItem(hoveredItemId));
|
||||||
},
|
},
|
||||||
clearHoveredItem: () => {
|
clearHoveredItem: () => {
|
||||||
dispatch(clearHoveredItem());
|
dispatch(clearHoveredItem());
|
||||||
|
|
|
@ -1,29 +1,43 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
const SelectionHOC = function (WrappedComponent) {
|
const SelectionHOC = function (WrappedComponent) {
|
||||||
class SelectionComponent extends React.Component {
|
class SelectionComponent extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'removeItemById'
|
||||||
|
]);
|
||||||
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.hoveredItem) {
|
if (this.props.hoveredItemId) {
|
||||||
paper.view.update();
|
paper.view.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (this.props.hoveredItem && this.props.hoveredItem !== prevProps.hoveredItem) {
|
// Hovered item has changed
|
||||||
// A hover item has been added. Update the view
|
if ((this.props.hoveredItemId && this.props.hoveredItemId !== prevProps.hoveredItemId) ||
|
||||||
if (prevProps.hoveredItem) {
|
(!this.props.hoveredItemId && prevProps.hoveredItemId)) {
|
||||||
prevProps.hoveredItem.remove();
|
// Remove the old hover item if any
|
||||||
|
this.removeItemById(prevProps.hoveredItemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeItemById (itemId) {
|
||||||
|
if (itemId) {
|
||||||
|
const match = paper.project.getItem({
|
||||||
|
match: item => (item.id === itemId)
|
||||||
|
});
|
||||||
|
if (match) {
|
||||||
|
match.remove();
|
||||||
}
|
}
|
||||||
} else if (!this.props.hoveredItem && prevProps.hoveredItem) {
|
|
||||||
// Remove the hover item
|
|
||||||
prevProps.hoveredItem.remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
hoveredItem, // eslint-disable-line no-unused-vars
|
hoveredItemId, // eslint-disable-line no-unused-vars
|
||||||
...props
|
...props
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -32,11 +46,11 @@ const SelectionHOC = function (WrappedComponent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SelectionComponent.propTypes = {
|
SelectionComponent.propTypes = {
|
||||||
hoveredItem: PropTypes.instanceOf(paper.Item)
|
hoveredItemId: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
hoveredItem: state.scratchPaint.hoveredItem
|
hoveredItemId: state.scratchPaint.hoveredItemId
|
||||||
});
|
});
|
||||||
return connect(
|
return connect(
|
||||||
mapStateToProps
|
mapStateToProps
|
||||||
|
|
|
@ -47,11 +47,11 @@ class SelectTool extends paper.Tool {
|
||||||
* To be called when the hovered item changes. When the select tool hovers over a
|
* To be called when the hovered item changes. When the select tool hovers over a
|
||||||
* new item, it compares against this to see if a hover item change event needs to
|
* new item, it compares against this to see if a hover item change event needs to
|
||||||
* be fired.
|
* be fired.
|
||||||
* @param {paper.Item} prevHoveredItem The highlight that indicates the mouse is over
|
* @param {paper.Item} prevHoveredItemId ID of the highlight item that indicates the mouse is
|
||||||
* a given item currently
|
* over a given item currently
|
||||||
*/
|
*/
|
||||||
setPrevHoveredItem (prevHoveredItem) {
|
setPrevHoveredItemId (prevHoveredItemId) {
|
||||||
this.prevHoveredItem = prevHoveredItem;
|
this.prevHoveredItemId = prevHoveredItemId;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns the hit options to use when conducting hit tests.
|
* Returns the hit options to use when conducting hit tests.
|
||||||
|
@ -92,11 +92,11 @@ class SelectTool extends paper.Tool {
|
||||||
}
|
}
|
||||||
handleMouseMove (event) {
|
handleMouseMove (event) {
|
||||||
const hoveredItem = getHoveredItem(event, this.getHitOptions());
|
const hoveredItem = getHoveredItem(event, this.getHitOptions());
|
||||||
if ((!hoveredItem && this.prevHoveredItem) || // There is no longer a hovered item
|
if ((!hoveredItem && this.prevHoveredItemId) || // There is no longer a hovered item
|
||||||
(hoveredItem && !this.prevHoveredItem) || // There is now a hovered item
|
(hoveredItem && !this.prevHoveredItemId) || // There is now a hovered item
|
||||||
(hoveredItem && this.prevHoveredItem &&
|
(hoveredItem && this.prevHoveredItemId &&
|
||||||
hoveredItem.id !== this.prevHoveredItem.id)) { // hovered item changed
|
hoveredItem.id !== this.prevHoveredItemId)) { // hovered item changed
|
||||||
this.setHoveredItem(hoveredItem);
|
this.setHoveredItem(hoveredItem ? hoveredItem.id : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleMouseDrag (event) {
|
handleMouseDrag (event) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import paper from 'paper';
|
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
|
|
||||||
const CHANGE_HOVERED = 'scratch-paint/hover/CHANGE_HOVERED';
|
const CHANGE_HOVERED = 'scratch-paint/hover/CHANGE_HOVERED';
|
||||||
|
@ -8,29 +7,36 @@ const reducer = function (state, action) {
|
||||||
if (typeof state === 'undefined') state = initialState;
|
if (typeof state === 'undefined') state = initialState;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CHANGE_HOVERED:
|
case CHANGE_HOVERED:
|
||||||
if (typeof action.hoveredItem === 'undefined' ||
|
if (typeof action.hoveredItemId === 'undefined') {
|
||||||
(action.hoveredItem !== null && !(action.hoveredItem instanceof paper.Item))) {
|
|
||||||
log.warn(`Hovered item should not be set to undefined. Use null.`);
|
log.warn(`Hovered item should not be set to undefined. Use null.`);
|
||||||
return state;
|
return state;
|
||||||
|
} else if (typeof action.hoveredItemId === 'undefined' || isNaN(action.hoveredItemId)) {
|
||||||
|
log.warn(`Hovered item should be an item ID number. Got: ${action.hoveredItemId}`);
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
return action.hoveredItem;
|
return action.hoveredItemId;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Action creators ==================================
|
// Action creators ==================================
|
||||||
const setHoveredItem = function (hoveredItem) {
|
/**
|
||||||
|
* Set the hovered item state to the given item ID
|
||||||
|
* @param {number} hoveredItemId The paper.Item ID of the hover indicator item.
|
||||||
|
* @return {object} Redux action to change the hovered item.
|
||||||
|
*/
|
||||||
|
const setHoveredItem = function (hoveredItemId) {
|
||||||
return {
|
return {
|
||||||
type: CHANGE_HOVERED,
|
type: CHANGE_HOVERED,
|
||||||
hoveredItem: hoveredItem
|
hoveredItemId: hoveredItemId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearHoveredItem = function () {
|
const clearHoveredItem = function () {
|
||||||
return {
|
return {
|
||||||
type: CHANGE_HOVERED,
|
type: CHANGE_HOVERED,
|
||||||
hoveredItem: null
|
hoveredItemId: null
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,5 @@ export default combineReducers({
|
||||||
brushMode: brushModeReducer,
|
brushMode: brushModeReducer,
|
||||||
eraserMode: eraserModeReducer,
|
eraserMode: eraserModeReducer,
|
||||||
color: colorReducer,
|
color: colorReducer,
|
||||||
hoveredItem: hoverReducer
|
hoveredItemId: hoverReducer
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
import paper from 'paper';
|
|
||||||
import reducer from '../../src/reducers/hover';
|
import reducer from '../../src/reducers/hover';
|
||||||
import {clearHoveredItem, setHoveredItem} from '../../src/reducers/hover';
|
import {clearHoveredItem, setHoveredItem} from '../../src/reducers/hover';
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
paper.setup();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('initialState', () => {
|
test('initialState', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
expect(reducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeNull();
|
expect(reducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeNull();
|
||||||
|
@ -14,22 +9,22 @@ test('initialState', () => {
|
||||||
|
|
||||||
test('setHoveredItem', () => {
|
test('setHoveredItem', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
const item1 = new paper.Path();
|
const item1 = 1;
|
||||||
const item2 = new paper.Path();
|
const item2 = 2;
|
||||||
expect(reducer(defaultState /* state */, setHoveredItem(item1) /* action */)).toBe(item1);
|
expect(reducer(defaultState /* state */, setHoveredItem(item1) /* action */)).toBe(item1);
|
||||||
expect(reducer(item1 /* state */, setHoveredItem(item2) /* action */)).toBe(item2);
|
expect(reducer(item1 /* state */, setHoveredItem(item2) /* action */)).toBe(item2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('clearHoveredItem', () => {
|
test('clearHoveredItem', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
const item = new paper.Path();
|
const item = 1;
|
||||||
expect(reducer(defaultState /* state */, clearHoveredItem() /* action */)).toBeNull();
|
expect(reducer(defaultState /* state */, clearHoveredItem() /* action */)).toBeNull();
|
||||||
expect(reducer(item /* state */, clearHoveredItem() /* action */)).toBeNull();
|
expect(reducer(item /* state */, clearHoveredItem() /* action */)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('invalidSetHoveredItem', () => {
|
test('invalidSetHoveredItem', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
const item = new paper.Path();
|
const item = 1;
|
||||||
const nonItem = {random: 'object'};
|
const nonItem = {random: 'object'};
|
||||||
let undef;
|
let undef;
|
||||||
expect(reducer(defaultState /* state */, setHoveredItem(nonItem) /* action */)).toBeNull();
|
expect(reducer(defaultState /* state */, setHoveredItem(nonItem) /* action */)).toBeNull();
|
||||||
|
|
Loading…
Reference in a new issue