diff --git a/package.json b/package.json index 7fabae78..3e56fbb8 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "babel-plugin-transform-object-rest-spread": "^6.22.0", "babel-preset-es2015": "^6.22.0", "babel-preset-react": "^6.22.0", - "canvas-prebuilt": "^1.6.5-prerelease.1", "classnames": "2.2.5", "css-loader": "0.28.3", "enzyme": "^2.8.2", diff --git a/src/containers/select-mode.jsx b/src/containers/select-mode.jsx index 731ba0e6..3992d3f9 100644 --- a/src/containers/select-mode.jsx +++ b/src/containers/select-mode.jsx @@ -9,7 +9,6 @@ import {setHoveredItem, clearHoveredItem} from '../reducers/hover'; import SelectTool from '../helper/selection-tools/select-tool'; import SelectModeComponent from '../components/select-mode.jsx'; -import paper from 'paper'; class SelectMode extends React.Component { constructor (props) { @@ -25,8 +24,8 @@ class SelectMode extends React.Component { } } componentWillReceiveProps (nextProps) { - if (this.tool && nextProps.hoveredItem !== this.props.hoveredItem) { - this.tool.setPrevHoveredItem(nextProps.hoveredItem); + if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) { + this.tool.setPrevHoveredItemId(nextProps.hoveredItemId); } if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) { @@ -57,7 +56,7 @@ class SelectMode extends React.Component { SelectMode.propTypes = { clearHoveredItem: PropTypes.func.isRequired, handleMouseDown: PropTypes.func.isRequired, - hoveredItem: PropTypes.instanceOf(paper.Item), + hoveredItemId: PropTypes.number, isSelectModeActive: PropTypes.bool.isRequired, onUpdateSvg: PropTypes.func.isRequired, setHoveredItem: PropTypes.func.isRequired @@ -65,11 +64,11 @@ SelectMode.propTypes = { const mapStateToProps = state => ({ isSelectModeActive: state.scratchPaint.mode === Modes.SELECT, - hoveredItem: state.scratchPaint.hoveredItem + hoveredItemId: state.scratchPaint.hoveredItemId }); const mapDispatchToProps = dispatch => ({ - setHoveredItem: hoveredItem => { - dispatch(setHoveredItem(hoveredItem)); + setHoveredItem: hoveredItemId => { + dispatch(setHoveredItem(hoveredItemId)); }, clearHoveredItem: () => { dispatch(clearHoveredItem()); diff --git a/src/containers/selection-hoc.jsx b/src/containers/selection-hoc.jsx index ca1179ab..e468a179 100644 --- a/src/containers/selection-hoc.jsx +++ b/src/containers/selection-hoc.jsx @@ -1,29 +1,43 @@ import PropTypes from 'prop-types'; import React from 'react'; import {connect} from 'react-redux'; +import bindAll from 'lodash.bindall'; import paper from 'paper'; const SelectionHOC = function (WrappedComponent) { class SelectionComponent extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'removeItemById' + ]); + } componentDidMount () { - if (this.props.hoveredItem) { + if (this.props.hoveredItemId) { paper.view.update(); } } componentDidUpdate (prevProps) { - if (this.props.hoveredItem && this.props.hoveredItem !== prevProps.hoveredItem) { - // A hover item has been added. Update the view - if (prevProps.hoveredItem) { - prevProps.hoveredItem.remove(); + // Hovered item has changed + if ((this.props.hoveredItemId && this.props.hoveredItemId !== prevProps.hoveredItemId) || + (!this.props.hoveredItemId && prevProps.hoveredItemId)) { + // 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 () { const { - hoveredItem, // eslint-disable-line no-unused-vars + hoveredItemId, // eslint-disable-line no-unused-vars ...props } = this.props; return ( @@ -32,11 +46,11 @@ const SelectionHOC = function (WrappedComponent) { } } SelectionComponent.propTypes = { - hoveredItem: PropTypes.instanceOf(paper.Item) + hoveredItemId: PropTypes.number }; const mapStateToProps = state => ({ - hoveredItem: state.scratchPaint.hoveredItem + hoveredItemId: state.scratchPaint.hoveredItemId }); return connect( mapStateToProps diff --git a/src/helper/selection-tools/select-tool.js b/src/helper/selection-tools/select-tool.js index e2f561ed..22320a23 100644 --- a/src/helper/selection-tools/select-tool.js +++ b/src/helper/selection-tools/select-tool.js @@ -47,11 +47,11 @@ class SelectTool extends paper.Tool { * 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 * be fired. - * @param {paper.Item} prevHoveredItem The highlight that indicates the mouse is over - * a given item currently + * @param {paper.Item} prevHoveredItemId ID of the highlight item that indicates the mouse is + * over a given item currently */ - setPrevHoveredItem (prevHoveredItem) { - this.prevHoveredItem = prevHoveredItem; + setPrevHoveredItemId (prevHoveredItemId) { + this.prevHoveredItemId = prevHoveredItemId; } /** * Returns the hit options to use when conducting hit tests. @@ -92,11 +92,11 @@ class SelectTool extends paper.Tool { } handleMouseMove (event) { const hoveredItem = getHoveredItem(event, this.getHitOptions()); - if ((!hoveredItem && this.prevHoveredItem) || // There is no longer a hovered item - (hoveredItem && !this.prevHoveredItem) || // There is now a hovered item - (hoveredItem && this.prevHoveredItem && - hoveredItem.id !== this.prevHoveredItem.id)) { // hovered item changed - this.setHoveredItem(hoveredItem); + if ((!hoveredItem && this.prevHoveredItemId) || // There is no longer a hovered item + (hoveredItem && !this.prevHoveredItemId) || // There is now a hovered item + (hoveredItem && this.prevHoveredItemId && + hoveredItem.id !== this.prevHoveredItemId)) { // hovered item changed + this.setHoveredItem(hoveredItem ? hoveredItem.id : null); } } handleMouseDrag (event) { diff --git a/src/reducers/hover.js b/src/reducers/hover.js index 2a3e31ee..fe5d5cab 100644 --- a/src/reducers/hover.js +++ b/src/reducers/hover.js @@ -1,4 +1,3 @@ -import paper from 'paper'; import log from '../log/log'; const CHANGE_HOVERED = 'scratch-paint/hover/CHANGE_HOVERED'; @@ -8,29 +7,36 @@ const reducer = function (state, action) { if (typeof state === 'undefined') state = initialState; switch (action.type) { case CHANGE_HOVERED: - if (typeof action.hoveredItem === 'undefined' || - (action.hoveredItem !== null && !(action.hoveredItem instanceof paper.Item))) { + if (typeof action.hoveredItemId === 'undefined') { log.warn(`Hovered item should not be set to undefined. Use null.`); 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: return state; } }; // 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 { type: CHANGE_HOVERED, - hoveredItem: hoveredItem + hoveredItemId: hoveredItemId }; }; const clearHoveredItem = function () { return { type: CHANGE_HOVERED, - hoveredItem: null + hoveredItemId: null }; }; diff --git a/src/reducers/scratch-paint-reducer.js b/src/reducers/scratch-paint-reducer.js index 3dfabe6b..0d44903b 100644 --- a/src/reducers/scratch-paint-reducer.js +++ b/src/reducers/scratch-paint-reducer.js @@ -10,5 +10,5 @@ export default combineReducers({ brushMode: brushModeReducer, eraserMode: eraserModeReducer, color: colorReducer, - hoveredItem: hoverReducer + hoveredItemId: hoverReducer }); diff --git a/test/unit/hover-reducer.test.js b/test/unit/hover-reducer.test.js index b8c63cd0..58f469b8 100644 --- a/test/unit/hover-reducer.test.js +++ b/test/unit/hover-reducer.test.js @@ -1,12 +1,7 @@ /* eslint-env jest */ -import paper from 'paper'; import reducer from '../../src/reducers/hover'; import {clearHoveredItem, setHoveredItem} from '../../src/reducers/hover'; -beforeEach(() => { - paper.setup(); -}); - test('initialState', () => { let defaultState; expect(reducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeNull(); @@ -14,22 +9,22 @@ test('initialState', () => { test('setHoveredItem', () => { let defaultState; - const item1 = new paper.Path(); - const item2 = new paper.Path(); + const item1 = 1; + const item2 = 2; expect(reducer(defaultState /* state */, setHoveredItem(item1) /* action */)).toBe(item1); expect(reducer(item1 /* state */, setHoveredItem(item2) /* action */)).toBe(item2); }); test('clearHoveredItem', () => { let defaultState; - const item = new paper.Path(); + const item = 1; expect(reducer(defaultState /* state */, clearHoveredItem() /* action */)).toBeNull(); expect(reducer(item /* state */, clearHoveredItem() /* action */)).toBeNull(); }); test('invalidSetHoveredItem', () => { let defaultState; - const item = new paper.Path(); + const item = 1; const nonItem = {random: 'object'}; let undef; expect(reducer(defaultState /* state */, setHoveredItem(nonItem) /* action */)).toBeNull();