Switch state to track hover item ID instead of item itself

This commit is contained in:
DD 2017-09-22 13:48:18 -04:00
parent 3bb606e16a
commit 7e1375d855
7 changed files with 58 additions and 45 deletions

View file

@ -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",

View file

@ -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());

View file

@ -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

View file

@ -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) {

View file

@ -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
}; };
}; };

View file

@ -10,5 +10,5 @@ export default combineReducers({
brushMode: brushModeReducer, brushMode: brushModeReducer,
eraserMode: eraserModeReducer, eraserMode: eraserModeReducer,
color: colorReducer, color: colorReducer,
hoveredItem: hoverReducer hoveredItemId: hoverReducer
}); });

View file

@ -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();