diff --git a/src/components/paint-editor.jsx b/src/components/paint-editor.jsx
index da5fcd3f..46f9cc72 100644
--- a/src/components/paint-editor.jsx
+++ b/src/components/paint-editor.jsx
@@ -58,11 +58,13 @@ class PaintEditorComponent extends React.Component {
@@ -154,6 +156,8 @@ class PaintEditorComponent extends React.Component {
PaintEditorComponent.propTypes = {
intl: intlShape,
+ onRedo: PropTypes.func.isRequired,
+ onUndo: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
diff --git a/src/containers/blob/blob.js b/src/containers/blob/blob.js
index 3025be8d..3a24597f 100644
--- a/src/containers/blob/blob.js
+++ b/src/containers/blob/blob.js
@@ -4,6 +4,7 @@ import BroadBrushHelper from './broad-brush-helper';
import SegmentBrushHelper from './segment-brush-helper';
import {MIXED, styleCursorPreview} from '../../helper/style-path';
import {clearSelection} from '../../helper/selection';
+import {performSnapshot} from '../../helper/undo';
/**
* Shared code for the brush and eraser mode. Adds functions on the paper tool object
@@ -29,11 +30,12 @@ class Blobbiness {
* @param {function} updateCallback call when the drawing has changed to let listeners know
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
*/
- constructor (updateCallback, clearSelectedItems) {
+ constructor (updateCallback, clearSelectedItems, undoSnapshot) {
this.broadBrushHelper = new BroadBrushHelper();
this.segmentBrushHelper = new SegmentBrushHelper();
this.updateCallback = updateCallback;
this.clearSelectedItems = clearSelectedItems;
+ this.undoSnapshot = undoSnapshot;
// The following are stored to check whether these have changed and the cursor preview needs to be redrawn.
this.strokeColor = null;
@@ -144,6 +146,7 @@ class Blobbiness {
blob.cursorPreview.visible = false;
blob.updateCallback();
+ performSnapshot(blob.undoSnapshot);
blob.cursorPreview.visible = true;
blob.cursorPreview.bringToFront();
blob.cursorPreview.position = event.point;
@@ -234,8 +237,6 @@ class Blobbiness {
paths.splice(i, 1);
}
}
- // TODO: Add back undo
- // pg.undo.snapshot('broadbrush');
}
mergeEraser (lastPath) {
@@ -284,8 +285,6 @@ class Blobbiness {
}
}
lastPath.remove();
- // TODO add back undo
- // pg.undo.snapshot('eraser');
continue;
}
// Erase
@@ -358,8 +357,6 @@ class Blobbiness {
items[i].remove();
}
lastPath.remove();
- // TODO: Add back undo handling
- // pg.undo.snapshot('eraser');
}
colorMatch (existingPath, addedPath) {
diff --git a/src/containers/brush-mode.jsx b/src/containers/brush-mode.jsx
index b7027c14..6c4a0b7c 100644
--- a/src/containers/brush-mode.jsx
+++ b/src/containers/brush-mode.jsx
@@ -4,10 +4,13 @@ import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Modes from '../modes/modes';
import Blobbiness from './blob/blob';
+
import {changeBrushSize} from '../reducers/brush-mode';
import {changeMode} from '../reducers/modes';
import {clearSelectedItems} from '../reducers/selected-items';
+import {undoSnapshot} from '../reducers/undo';
import {clearSelection} from '../helper/selection';
+
import BrushModeComponent from '../components/brush-mode.jsx';
class BrushMode extends React.Component {
@@ -18,7 +21,8 @@ class BrushMode extends React.Component {
'deactivateTool',
'onScroll'
]);
- this.blob = new Blobbiness(this.props.onUpdateSvg, this.props.clearSelectedItems);
+ this.blob = new Blobbiness(
+ this.props.onUpdateSvg, this.props.clearSelectedItems, this.props.undoSnapshot);
}
componentDidMount () {
if (this.props.isBrushModeActive) {
@@ -87,7 +91,8 @@ BrushMode.propTypes = {
}).isRequired,
handleMouseDown: PropTypes.func.isRequired,
isBrushModeActive: PropTypes.bool.isRequired,
- onUpdateSvg: PropTypes.func.isRequired
+ onUpdateSvg: PropTypes.func.isRequired,
+ undoSnapshot: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
@@ -104,6 +109,9 @@ const mapDispatchToProps = dispatch => ({
},
handleMouseDown: () => {
dispatch(changeMode(Modes.BRUSH));
+ },
+ undoSnapshot: snapshot => {
+ dispatch(undoSnapshot(snapshot));
}
});
diff --git a/src/containers/eraser-mode.jsx b/src/containers/eraser-mode.jsx
index 423058c2..5d5f6baa 100644
--- a/src/containers/eraser-mode.jsx
+++ b/src/containers/eraser-mode.jsx
@@ -6,6 +6,7 @@ import Modes from '../modes/modes';
import Blobbiness from './blob/blob';
import {changeBrushSize} from '../reducers/eraser-mode';
import {clearSelectedItems} from '../reducers/selected-items';
+import {undoSnapshot} from '../reducers/undo';
import EraserModeComponent from '../components/eraser-mode.jsx';
import {changeMode} from '../reducers/modes';
@@ -17,7 +18,8 @@ class EraserMode extends React.Component {
'deactivateTool',
'onScroll'
]);
- this.blob = new Blobbiness(this.props.onUpdateSvg, this.props.clearSelectedItems);
+ this.blob = new Blobbiness(
+ this.props.onUpdateSvg, this.props.clearSelectedItems, this.props.undoSnapshot);
}
componentDidMount () {
if (this.props.isEraserModeActive) {
@@ -72,7 +74,8 @@ EraserMode.propTypes = {
}),
handleMouseDown: PropTypes.func.isRequired,
isEraserModeActive: PropTypes.bool.isRequired,
- onUpdateSvg: PropTypes.func.isRequired
+ onUpdateSvg: PropTypes.func.isRequired,
+ undoSnapshot: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
@@ -88,6 +91,9 @@ const mapDispatchToProps = dispatch => ({
},
handleMouseDown: () => {
dispatch(changeMode(Modes.ERASER));
+ },
+ undoSnapshot: snapshot => {
+ dispatch(undoSnapshot(snapshot));
}
});
diff --git a/src/containers/fill-color-indicator.jsx b/src/containers/fill-color-indicator.jsx
index 35611f45..8c3cb5aa 100644
--- a/src/containers/fill-color-indicator.jsx
+++ b/src/containers/fill-color-indicator.jsx
@@ -2,13 +2,16 @@ import {connect} from 'react-redux';
import {changeFillColor} from '../reducers/fill-color';
import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
import {applyFillColorToSelection} from '../helper/style-path';
+import {performSnapshot} from '../helper/undo';
+import {undoSnapshot} from '../reducers/undo';
const mapStateToProps = state => ({
fillColor: state.scratchPaint.color.fillColor
});
const mapDispatchToProps = dispatch => ({
onChangeFillColor: fillColor => {
- applyFillColorToSelection(fillColor);
+ applyFillColorToSelection(fillColor, undoSnapshot);
+ performSnapshot(snapshot => dispatch(undoSnapshot(snapshot)));
dispatch(changeFillColor(fillColor));
}
});
diff --git a/src/containers/line-mode.jsx b/src/containers/line-mode.jsx
index 69356d62..0e11e3d4 100644
--- a/src/containers/line-mode.jsx
+++ b/src/containers/line-mode.jsx
@@ -1,3 +1,4 @@
+import paper from 'paper';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
@@ -6,10 +7,13 @@ import Modes from '../modes/modes';
import {changeStrokeWidth} from '../reducers/stroke-width';
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
import {MIXED} from '../helper/style-path';
-import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
-import LineModeComponent from '../components/line-mode.jsx';
import {changeMode} from '../reducers/modes';
-import paper from 'paper';
+import {changeStrokeWidth} from '../reducers/stroke-width';
+import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
+import {performSnapshot} from '../helper/undo';
+import {undoSnapshot} from '../reducers/undo';
+
+import LineModeComponent from '../components/line-mode.jsx';
class LineMode extends React.Component {
static get SNAP_TOLERANCE () {
@@ -209,10 +213,9 @@ class LineMode extends React.Component {
this.props.onUpdateSvg();
this.props.setSelectedItems();
- // TODO add back undo
- // if (this.path) {
- // pg.undo.snapshot('line');
- // }
+ if (this.path) {
+ performSnapshot(this.props.undoSnapshot);
+ }
}
toleranceSquared () {
return Math.pow(LineMode.SNAP_TOLERANCE / paper.view.zoom, 2);
@@ -284,7 +287,8 @@ LineMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
isLineModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
- setSelectedItems: PropTypes.func.isRequired
+ setSelectedItems: PropTypes.func.isRequired,
+ undoSnapshot: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
@@ -303,6 +307,9 @@ const mapDispatchToProps = dispatch => ({
},
handleMouseDown: () => {
dispatch(changeMode(Modes.LINE));
+ },
+ undoSnapshot: snapshot => {
+ dispatch(undoSnapshot(snapshot));
}
});
diff --git a/src/containers/paint-editor.jsx b/src/containers/paint-editor.jsx
index 9d48fa2d..232a2b43 100644
--- a/src/containers/paint-editor.jsx
+++ b/src/containers/paint-editor.jsx
@@ -1,8 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import PaintEditorComponent from '../components/paint-editor.jsx';
+
import {changeMode} from '../reducers/modes';
+import {undo, redo} from '../reducers/undo';
+
import {getGuideLayer} from '../helper/layer';
+import {performUndo, performRedo} from '../helper/undo';
+
import Modes from '../modes/modes';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
@@ -12,7 +17,9 @@ class PaintEditor extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
- 'handleUpdateSvg'
+ 'handleUpdateSvg',
+ 'handleUndo',
+ 'handleRedo'
]);
}
componentDidMount () {
@@ -34,12 +41,20 @@ class PaintEditor extends React.Component {
paper.project.view.center.y - bounds.y);
getGuideLayer().visible = true;
}
+ handleUndo () {
+ performUndo(this.props.undoState, this.props.onUndo);
+ }
+ handleRedo () {
+ performRedo(this.props.undoState, this.props.onRedo);
+ }
render () {
return (
);
@@ -48,12 +63,21 @@ class PaintEditor extends React.Component {
PaintEditor.propTypes = {
onKeyPress: PropTypes.func.isRequired,
+ onRedo: PropTypes.func.isRequired,
+ onUndo: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
- svg: PropTypes.string
+ svg: PropTypes.string,
+ undoState: PropTypes.shape({
+ stack: PropTypes.arrayOf(PropTypes.object).isRequired,
+ pointer: PropTypes.number.isRequired
+ })
};
+const mapStateToProps = state => ({
+ undoState: state.scratchPaint.undo
+});
const mapDispatchToProps = dispatch => ({
onKeyPress: event => {
if (event.key === 'e') {
@@ -65,10 +89,16 @@ const mapDispatchToProps = dispatch => ({
} else if (event.key === 's') {
dispatch(changeMode(Modes.SELECT));
}
+ },
+ onUndo: () => {
+ dispatch(undo());
+ },
+ onRedo: () => {
+ dispatch(redo());
}
});
export default connect(
- null,
+ mapStateToProps,
mapDispatchToProps
)(PaintEditor);
diff --git a/src/containers/paper-canvas.jsx b/src/containers/paper-canvas.jsx
index ae4eda24..030eaec4 100644
--- a/src/containers/paper-canvas.jsx
+++ b/src/containers/paper-canvas.jsx
@@ -1,8 +1,12 @@
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
+import {connect} from 'react-redux';
import paper from 'paper';
+import {performSnapshot} from '../helper/undo';
+import {undoSnapshot} from '../reducers/undo';
+
import styles from './paper-canvas.css';
class PaperCanvas extends React.Component {
@@ -20,6 +24,7 @@ class PaperCanvas extends React.Component {
if (this.props.svg) {
this.importSvg(this.props.svg, this.props.rotationCenterX, this.props.rotationCenterY);
}
+ performSnapshot(this.props.undoSnapshot);
}
componentWillReceiveProps (newProps) {
paper.project.activeLayer.removeChildren();
@@ -85,7 +90,16 @@ PaperCanvas.propTypes = {
canvasRef: PropTypes.func,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
- svg: PropTypes.string
+ svg: PropTypes.string,
+ undoSnapshot: PropTypes.func.isRequired
};
+const mapDispatchToProps = dispatch => ({
+ undoSnapshot: snapshot => {
+ dispatch(undoSnapshot(snapshot));
+ }
+});
-export default PaperCanvas;
+export default connect(
+ null,
+ mapDispatchToProps
+)(PaperCanvas);
diff --git a/src/containers/reshape-mode.jsx b/src/containers/reshape-mode.jsx
index 1683874c..f192b454 100644
--- a/src/containers/reshape-mode.jsx
+++ b/src/containers/reshape-mode.jsx
@@ -8,6 +8,7 @@ import {changeMode} from '../reducers/modes';
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
import {getSelectedLeafItems} from '../helper/selection';
+import {undoSnapshot} from '../reducers/undo';
import ReshapeTool from '../helper/selection-tools/reshape-tool';
import ReshapeModeComponent from '../components/reshape-mode.jsx';
@@ -45,7 +46,9 @@ class ReshapeMode extends React.Component {
this.props.clearHoveredItem,
this.props.setSelectedItems,
this.props.clearSelectedItems,
- this.props.onUpdateSvg);
+ this.props.onUpdateSvg,
+ this.props.undoSnapshot
+ );
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
this.tool.activate();
}
@@ -70,7 +73,8 @@ ReshapeMode.propTypes = {
isReshapeModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
setHoveredItem: PropTypes.func.isRequired,
- setSelectedItems: PropTypes.func.isRequired
+ setSelectedItems: PropTypes.func.isRequired,
+ undoSnapshot: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
@@ -92,6 +96,9 @@ const mapDispatchToProps = dispatch => ({
},
handleMouseDown: () => {
dispatch(changeMode(Modes.RESHAPE));
+ },
+ undoSnapshot: snapshot => {
+ dispatch(undoSnapshot(snapshot));
}
});
diff --git a/src/containers/select-mode.jsx b/src/containers/select-mode.jsx
index 505bf412..0d946f60 100644
--- a/src/containers/select-mode.jsx
+++ b/src/containers/select-mode.jsx
@@ -7,6 +7,7 @@ import Modes from '../modes/modes';
import {changeMode} from '../reducers/modes';
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
+import {undoSnapshot} from '../reducers/undo';
import {getSelectedLeafItems} from '../helper/selection';
import SelectTool from '../helper/selection-tools/select-tool';
@@ -45,7 +46,9 @@ class SelectMode extends React.Component {
this.props.clearHoveredItem,
this.props.setSelectedItems,
this.props.clearSelectedItems,
- this.props.onUpdateSvg);
+ this.props.onUpdateSvg,
+ this.props.undoSnapshot
+ );
this.tool.activate();
}
deactivateTool () {
@@ -68,7 +71,8 @@ SelectMode.propTypes = {
isSelectModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
setHoveredItem: PropTypes.func.isRequired,
- setSelectedItems: PropTypes.func.isRequired
+ setSelectedItems: PropTypes.func.isRequired,
+ undoSnapshot: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
@@ -90,6 +94,9 @@ const mapDispatchToProps = dispatch => ({
},
handleMouseDown: () => {
dispatch(changeMode(Modes.SELECT));
+ },
+ undoSnapshot: snapshot => {
+ dispatch(undoSnapshot(snapshot));
}
});
diff --git a/src/containers/stroke-color-indicator.jsx b/src/containers/stroke-color-indicator.jsx
index f7ffcbab..20bd9fee 100644
--- a/src/containers/stroke-color-indicator.jsx
+++ b/src/containers/stroke-color-indicator.jsx
@@ -2,13 +2,16 @@ import {connect} from 'react-redux';
import {changeStrokeColor} from '../reducers/stroke-color';
import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
import {applyStrokeColorToSelection} from '../helper/style-path';
+import {performSnapshot} from '../helper/undo';
+import {undoSnapshot} from '../reducers/undo';
const mapStateToProps = state => ({
strokeColor: state.scratchPaint.color.strokeColor
});
const mapDispatchToProps = dispatch => ({
onChangeStrokeColor: strokeColor => {
- applyStrokeColorToSelection(strokeColor);
+ applyStrokeColorToSelection(strokeColor, undoSnapshot);
+ performSnapshot(snapshot => dispatch(undoSnapshot(snapshot)));
dispatch(changeStrokeColor(strokeColor));
}
});
diff --git a/src/containers/stroke-width-indicator.jsx b/src/containers/stroke-width-indicator.jsx
index d1b0def3..105263c9 100644
--- a/src/containers/stroke-width-indicator.jsx
+++ b/src/containers/stroke-width-indicator.jsx
@@ -2,13 +2,16 @@ import {connect} from 'react-redux';
import {changeStrokeWidth} from '../reducers/stroke-width';
import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx';
import {applyStrokeWidthToSelection} from '../helper/style-path';
+import {performSnapshot} from '../helper/undo';
+import {undoSnapshot} from '../reducers/undo';
const mapStateToProps = state => ({
strokeWidth: state.scratchPaint.color.strokeWidth
});
const mapDispatchToProps = dispatch => ({
onChangeStrokeWidth: strokeWidth => {
- applyStrokeWidthToSelection(strokeWidth);
+ applyStrokeWidthToSelection(strokeWidth, undoSnapshot);
+ performSnapshot(snapshot => dispatch(undoSnapshot(snapshot)));
dispatch(changeStrokeWidth(strokeWidth));
}
});
diff --git a/src/helper/selection-tools/bounding-box-tool.js b/src/helper/selection-tools/bounding-box-tool.js
index 1826f365..46d94aa7 100644
--- a/src/helper/selection-tools/bounding-box-tool.js
+++ b/src/helper/selection-tools/bounding-box-tool.js
@@ -36,16 +36,16 @@ class BoundingBoxTool {
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ constructor (setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot) {
this.onUpdateSvg = onUpdateSvg;
this.mode = null;
this.boundsPath = null;
this.boundsScaleHandles = [];
this.boundsRotHandles = [];
this._modeMap = {};
- this._modeMap[Modes.SCALE] = new ScaleTool(onUpdateSvg);
- this._modeMap[Modes.ROTATE] = new RotateTool(onUpdateSvg);
- this._modeMap[Modes.MOVE] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
+ this._modeMap[Modes.SCALE] = new ScaleTool(onUpdateSvg, undoSnapshot);
+ this._modeMap[Modes.ROTATE] = new RotateTool(onUpdateSvg, undoSnapshot);
+ this._modeMap[Modes.MOVE] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot);
}
/**
diff --git a/src/helper/selection-tools/handle-tool.js b/src/helper/selection-tools/handle-tool.js
index c3d26a1d..fd55b0f7 100644
--- a/src/helper/selection-tools/handle-tool.js
+++ b/src/helper/selection-tools/handle-tool.js
@@ -1,4 +1,5 @@
import {clearSelection, getSelectedLeafItems} from '../selection';
+import {performSnapshot} from '../undo';
/** Sub tool of the Reshape tool for moving handles, which adjust bezier curves. */
class HandleTool {
@@ -7,11 +8,13 @@ class HandleTool {
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ constructor (setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot) {
this.hitType = null;
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
+ this.undoSnapshot = undoSnapshot;
+ this.selectedItems = [];
}
/**
* @param {!object} hitProperties Describes the mouse event
@@ -28,9 +31,9 @@ class HandleTool {
this.hitType = hitProperties.hitResult.type;
}
onMouseDrag (event) {
- const selectedItems = getSelectedLeafItems();
+ this.selectedItems = getSelectedLeafItems();
- for (const item of selectedItems) {
+ for (const item of this.selectedItems) {
for (const seg of item.segments) {
// add the point of the segment before the drag started
// for later use in the snap calculation
@@ -66,8 +69,24 @@ class HandleTool {
}
}
onMouseUp () {
- // @todo add back undo
- this.onUpdateSvg();
+ // resetting the items and segments origin points for the next usage
+ let moved = false;
+ for (const item of this.selectedItems) {
+ if (!item.segments) {
+ return;
+ }
+ for (const seg of item.segments) {
+ if (seg.origPoint && !seg.equals(seg.origPoint)) {
+ moved = true;
+ }
+ seg.origPoint = null;
+ }
+ }
+ if (moved) {
+ performSnapshot(this.undoSnapshot);
+ this.onUpdateSvg();
+ }
+ this.selectedItems = [];
}
}
diff --git a/src/helper/selection-tools/move-tool.js b/src/helper/selection-tools/move-tool.js
index 0a16277b..c55cf6db 100644
--- a/src/helper/selection-tools/move-tool.js
+++ b/src/helper/selection-tools/move-tool.js
@@ -2,6 +2,7 @@ import {isGroup} from '../group';
import {isCompoundPathItem, getRootItem} from '../item';
import {snapDeltaToAngle} from '../math';
import {clearSelection, cloneSelection, getSelectedLeafItems, setItemSelection} from '../selection';
+import {performSnapshot} from '../undo';
/**
* Tool to handle dragging an item to reposition it in a selection mode.
@@ -12,11 +13,12 @@ class MoveTool {
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ constructor (setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot) {
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.selectedItems = null;
this.onUpdateSvg = onUpdateSvg;
+ this.undoSnapshot = undoSnapshot;
}
/**
@@ -51,7 +53,7 @@ class MoveTool {
}
this._select(item, true, hitProperties.subselect);
}
- if (hitProperties.clone) cloneSelection(hitProperties.subselect);
+ if (hitProperties.clone) cloneSelection(hitProperties.subselect, this.undoSnapshot);
this.selectedItems = getSelectedLeafItems();
}
/**
@@ -94,15 +96,20 @@ class MoveTool {
}
}
onMouseUp () {
+ let moved = false;
// resetting the items origin point for the next usage
for (const item of this.selectedItems) {
+ if (item.data.origPos && !item.position.equals(item.data.origPos)) {
+ moved = true;
+ }
item.data.origPos = null;
}
this.selectedItems = null;
- // @todo add back undo
- // pg.undo.snapshot('moveSelection');
- this.onUpdateSvg();
+ if (moved) {
+ performSnapshot(this.undoSnapshot);
+ this.onUpdateSvg();
+ }
}
}
diff --git a/src/helper/selection-tools/point-tool.js b/src/helper/selection-tools/point-tool.js
index 9b54cb7e..73bcbd27 100644
--- a/src/helper/selection-tools/point-tool.js
+++ b/src/helper/selection-tools/point-tool.js
@@ -1,6 +1,7 @@
import paper from 'paper';
import {snapDeltaToAngle} from '../math';
import {clearSelection, getSelectedLeafItems} from '../selection';
+import {performSnapshot} from '../undo';
/** Subtool of ReshapeTool for moving control points. */
class PointTool {
@@ -9,7 +10,7 @@ class PointTool {
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ constructor (setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot) {
/**
* Deselection often does not happen until mouse up. If the mouse is dragged before
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
@@ -29,6 +30,7 @@ class PointTool {
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
+ this.undoSnapshot = undoSnapshot;
}
/**
@@ -166,11 +168,15 @@ class PointTool {
}
onMouseUp () {
// resetting the items and segments origin points for the next usage
+ let moved = false;
for (const item of this.selectedItems) {
if (!item.segments) {
return;
}
for (const seg of item.segments) {
+ if (seg.origPoint && !seg.equals(seg.origPoint)) {
+ moved = true;
+ }
seg.origPoint = null;
}
}
@@ -193,8 +199,10 @@ class PointTool {
}
this.selectedItems = null;
this.setSelectedItems();
- // @todo add back undo
- this.onUpdateSvg();
+ if (moved) {
+ performSnapshot(this.undoSnapshot);
+ this.onUpdateSvg();
+ }
}
}
diff --git a/src/helper/selection-tools/reshape-tool.js b/src/helper/selection-tools/reshape-tool.js
index efa75158..b7e428a7 100644
--- a/src/helper/selection-tools/reshape-tool.js
+++ b/src/helper/selection-tools/reshape-tool.js
@@ -45,18 +45,19 @@ class ReshapeTool extends paper.Tool {
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
+ this.undoSnapshot = undoSnapshot;
this.prevHoveredItemId = null;
this.lastEvent = null;
this.mode = ReshapeModes.SELECTION_BOX;
this._modeMap = {};
- this._modeMap[ReshapeModes.FILL] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
- this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
- this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
+ this._modeMap[ReshapeModes.FILL] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot);
+ this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot);
+ this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot);
this._modeMap[ReshapeModes.SELECTION_BOX] =
new SelectionBoxTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems);
@@ -221,7 +222,7 @@ class ReshapeTool extends paper.Tool {
handleKeyUp (event) {
// Backspace, delete
if (event.key === 'delete' || event.key === 'backspace') {
- deleteSelection(Modes.RESHAPE);
+ deleteSelection(Modes.RESHAPE, this.undoSnapshot);
this.onUpdateSvg();
}
}
diff --git a/src/helper/selection-tools/rotate-tool.js b/src/helper/selection-tools/rotate-tool.js
index 2006cebf..0b197c7a 100644
--- a/src/helper/selection-tools/rotate-tool.js
+++ b/src/helper/selection-tools/rotate-tool.js
@@ -1,4 +1,5 @@
import paper from 'paper';
+import {performSnapshot} from '../undo';
/**
* Tool to handle rotation when dragging the rotation handle in the bounding box tool.
@@ -7,11 +8,12 @@ class RotateTool {
/**
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (onUpdateSvg) {
+ constructor (onUpdateSvg, undoSnapshot) {
this.rotItems = [];
this.rotGroupPivot = null;
this.prevRot = [];
this.onUpdateSvg = onUpdateSvg;
+ this.undoSnapshot = undoSnapshot;
}
/**
@@ -63,7 +65,7 @@ class RotateTool {
this.rotGroupPivot = null;
this.prevRot = [];
- // @todo add back undo
+ performSnapshot(this.undoSnapshot);
this.onUpdateSvg();
}
}
diff --git a/src/helper/selection-tools/scale-tool.js b/src/helper/selection-tools/scale-tool.js
index 8744ab72..7518ca9b 100644
--- a/src/helper/selection-tools/scale-tool.js
+++ b/src/helper/selection-tools/scale-tool.js
@@ -1,4 +1,5 @@
import paper from 'paper';
+import {performSnapshot} from '../undo';
/**
* Tool to handle scaling items by pulling on the handles around the edges of the bounding
@@ -8,7 +9,7 @@ class ScaleTool {
/**
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (onUpdateSvg) {
+ constructor (onUpdateSvg, undoSnapshot) {
this.pivot = null;
this.origPivot = null;
this.corner = null;
@@ -22,6 +23,7 @@ class ScaleTool {
this.boundsScaleHandles = [];
this.boundsRotHandles = [];
this.onUpdateSvg = onUpdateSvg;
+ this.undoSnapshot = undoSnapshot;
}
/**
@@ -157,7 +159,7 @@ class ScaleTool {
}
this.itemGroup.remove();
- // @todo add back undo
+ performSnapshot(this.undoSnapshot);
this.onUpdateSvg();
}
_getRectCornerNameByIndex (index) {
diff --git a/src/helper/selection-tools/select-tool.js b/src/helper/selection-tools/select-tool.js
index 93fc31cf..f0592cd1 100644
--- a/src/helper/selection-tools/select-tool.js
+++ b/src/helper/selection-tools/select-tool.js
@@ -25,12 +25,12 @@ class SelectTool extends paper.Tool {
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
- constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
- this.boundingBoxTool = new BoundingBoxTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
+ this.boundingBoxTool = new BoundingBoxTool(setSelectedItems, clearSelectedItems, onUpdateSvg, undoSnapshot);
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
this.selectionBoxMode = false;
this.prevHoveredItemId = null;
@@ -126,7 +126,7 @@ class SelectTool extends paper.Tool {
handleKeyUp (event) {
// Backspace, delete
if (event.key === 'delete' || event.key === 'backspace') {
- deleteSelection(Modes.SELECT);
+ deleteSelection(Modes.SELECT, this.undoSnapshot);
this.clearHoveredItem();
this.boundingBoxTool.removeBoundsPath();
this.onUpdateSvg();
diff --git a/src/helper/selection.js b/src/helper/selection.js
index 2c12b161..b2d4f7dc 100644
--- a/src/helper/selection.js
+++ b/src/helper/selection.js
@@ -4,6 +4,7 @@ import Modes from '../modes/modes';
import {getItemsGroup, isGroup} from './group';
import {getRootItem, isCompoundPathItem, isBoundsItem, isPathItem, isPGTextItem} from './item';
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
+import {performSnapshot} from './undo';
/**
* @param {boolean} includeGuides True if guide layer items like the bounding box should
@@ -165,20 +166,18 @@ const getSelectedLeafItems = function () {
return items;
};
-const deleteItemSelection = function (items) {
+const deleteItemSelection = function (items, undoSnapshot) {
for (let i = 0; i < items.length; i++) {
items[i].remove();
}
// @todo: Update toolbar state on change
paper.project.view.update();
- // @todo add back undo
- // pg.undo.snapshot('deleteItemSelection');
+ performSnapshot(undoSnapshot);
};
-const removeSelectedSegments = function (items) {
- // @todo add back undo
- // pg.undo.snapshot('removeSelectedSegments');
+const removeSelectedSegments = function (items, undoSnapshot) {
+ performSnapshot(undoSnapshot);
const segmentsToRemove = [];
@@ -201,16 +200,16 @@ const removeSelectedSegments = function (items) {
return removedSegments;
};
-const deleteSelection = function (mode) {
+const deleteSelection = function (mode, undoSnapshot) {
if (mode === Modes.RESHAPE) {
const selectedItems = getSelectedLeafItems();
// If there are points selected remove them. If not delete the item selected.
- if (!removeSelectedSegments(selectedItems)) {
- deleteItemSelection(selectedItems);
+ if (!removeSelectedSegments(selectedItems, undoSnapshot)) {
+ deleteItemSelection(selectedItems, undoSnapshot);
}
} else {
const selectedItems = getSelectedRootItems();
- deleteItemSelection(selectedItems);
+ deleteItemSelection(selectedItems, undoSnapshot);
}
};
@@ -281,11 +280,11 @@ const splitPathAtSelectedSegments = function () {
}
};
-const deleteSegments = function (item) {
+const deleteSegments = function (item, undoSnapshot) {
if (item.children) {
for (let i = 0; i < item.children.length; i++) {
const child = item.children[i];
- deleteSegments(child);
+ deleteSegments(child, undoSnapshot);
}
} else {
const segments = item.segments;
@@ -299,7 +298,7 @@ const deleteSegments = function (item) {
!segment.previous.selected)) {
splitPathRetainSelection(item, j);
- deleteSelection();
+ deleteSelection(Modes.RESHAPE, undoSnapshot);
return;
} else if (!item.closed) {
@@ -316,26 +315,24 @@ const deleteSegments = function (item) {
}
};
-const deleteSegmentSelection = function (items) {
+const deleteSegmentSelection = function (items, undoSnapshot) {
for (let i = 0; i < items.length; i++) {
- deleteSegments(items[i]);
+ deleteSegments(items[i], undoSnapshot);
}
// @todo: Update toolbar state on change
paper.project.view.update();
- // @todo add back undo
- // pg.undo.snapshot('deleteSegmentSelection');
+ performSnapshot(undoSnapshot);
};
-const cloneSelection = function (recursive) {
+const cloneSelection = function (recursive, undoSnapshot) {
const selectedItems = recursive ? getSelectedLeafItems() : getSelectedRootItems();
for (let i = 0; i < selectedItems.length; i++) {
const item = selectedItems[i];
item.clone();
item.selected = false;
}
- // @todo add back undo
- // pg.undo.snapshot('cloneSelection');
+ performSnapshot(undoSnapshot);
};
// Only returns paths, no compound paths, groups or any other stuff
diff --git a/src/helper/style-path.js b/src/helper/style-path.js
index 6e85b9f6..92c79d67 100644
--- a/src/helper/style-path.js
+++ b/src/helper/style-path.js
@@ -1,6 +1,7 @@
import {getSelectedLeafItems} from './selection';
import {isPGTextItem, isPointTextItem} from './item';
import {isGroup} from './group';
+import {performSnapshot} from './undo';
const MIXED = 'scratch-paint/style-path/mixed';
@@ -8,7 +9,7 @@ const MIXED = 'scratch-paint/style-path/mixed';
* Called when setting fill color
* @param {string} colorString New color, css format
*/
-const applyFillColorToSelection = function (colorString) {
+const applyFillColorToSelection = function (colorString, undoSnapshot) {
const items = getSelectedLeafItems();
for (const item of items) {
if (isPGTextItem(item)) {
@@ -30,14 +31,14 @@ const applyFillColorToSelection = function (colorString) {
item.fillColor = colorString;
}
}
- // @todo add back undo
+ performSnapshot(undoSnapshot);
};
/**
* Called when setting stroke color
* @param {string} colorString New color, css format
*/
-const applyStrokeColorToSelection = function (colorString) {
+const applyStrokeColorToSelection = function (colorString, undoSnapshot) {
const items = getSelectedLeafItems();
for (const item of items) {
@@ -61,14 +62,14 @@ const applyStrokeColorToSelection = function (colorString) {
item.strokeColor = colorString;
}
}
- // @todo add back undo
+ performSnapshot(undoSnapshot);
};
/**
* Called when setting stroke width
* @param {number} value New stroke width
*/
-const applyStrokeWidthToSelection = function (value) {
+const applyStrokeWidthToSelection = function (value, undoSnapshot) {
const items = getSelectedLeafItems();
for (const item of items) {
if (isGroup(item)) {
@@ -77,7 +78,7 @@ const applyStrokeWidthToSelection = function (value) {
item.strokeWidth = value;
}
}
- // @todo add back undo
+ performSnapshot(undoSnapshot);
};
/**
@@ -168,7 +169,12 @@ const stylePath = function (path, options) {
if (options.isEraser) {
path.fillColor = 'white';
} else {
- path.fillColor = options.fillColor;
+ if (options.fillColor) {
+ path.fillColor = options.fillColor;
+ } else {
+ // Make sure something visible is drawn
+ path.fillColor = 'black';
+ }
}
};
@@ -178,7 +184,12 @@ const styleCursorPreview = function (path, options) {
path.strokeColor = 'cornflowerblue';
path.strokeWidth = 1;
} else {
- path.fillColor = options.fillColor;
+ if (options.fillColor) {
+ path.fillColor = options.fillColor;
+ } else {
+ // Make sure something visible is drawn
+ path.fillColor = 'black';
+ }
}
};
diff --git a/src/reducers/scratch-paint-reducer.js b/src/reducers/scratch-paint-reducer.js
index 2ac4a2ee..12fb6d7f 100644
--- a/src/reducers/scratch-paint-reducer.js
+++ b/src/reducers/scratch-paint-reducer.js
@@ -5,6 +5,7 @@ import eraserModeReducer from './eraser-mode';
import colorReducer from './color';
import hoverReducer from './hover';
import selectedItemReducer from './selected-items';
+import undoReducer from './undo';
export default combineReducers({
mode: modeReducer,
@@ -12,5 +13,6 @@ export default combineReducers({
eraserMode: eraserModeReducer,
color: colorReducer,
hoveredItemId: hoverReducer,
- selectedItems: selectedItemReducer
+ selectedItems: selectedItemReducer,
+ undo: undoReducer
});
diff --git a/src/reducers/undo.js b/src/reducers/undo.js
index e950df8b..2a669383 100644
--- a/src/reducers/undo.js
+++ b/src/reducers/undo.js
@@ -13,7 +13,7 @@ const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case UNDO:
- if (state.pointer === -1) {
+ if (state.pointer <= 0) {
log.warn(`Can't undo, undo stack is empty`);
return state;
}
@@ -22,7 +22,7 @@ const reducer = function (state, action) {
pointer: state.pointer - 1
};
case REDO:
- if (state.pointer === state.stack.length - 1) {
+ if (state.pointer <= -1 || state.pointer === state.stack.length - 1) {
log.warn(`Can't redo, redo stack is empty`);
return state;
}
diff --git a/test/unit/undo-reducer.test.js b/test/unit/undo-reducer.test.js
index 4f20299c..6201d9e3 100644
--- a/test/unit/undo-reducer.test.js
+++ b/test/unit/undo-reducer.test.js
@@ -52,12 +52,20 @@ test('clearUndoState', () => {
test('cantUndo', () => {
let defaultState;
+ const state1 = {state: 1};
// Undo when there's no undo stack
- const reduxState = undoReducer(defaultState /* state */, undo() /* action */);
+ let reduxState = undoReducer(defaultState /* state */, undo() /* action */);
expect(reduxState.pointer).toEqual(-1);
expect(reduxState.stack).toHaveLength(0);
+
+ // Undo when there's only one state
+ reduxState = undoReducer(reduxState /* state */, undoSnapshot([state1]) /* action */);
+ reduxState = undoReducer(reduxState /* state */, undo() /* action */);
+
+ expect(reduxState.pointer).toEqual(0);
+ expect(reduxState.stack).toHaveLength(1);
});
test('cantRedo', () => {
@@ -111,23 +119,23 @@ test('undoSnapshotCantRedo', () => {
let defaultState;
const state1 = {state: 1};
const state2 = {state: 2};
+ const state3 = {state: 3};
- // Push 2 states then undo twice
+ // Push 2 states then undo
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
- reduxState = undoReducer(reduxState /* state */, undo() /* action */);
- expect(reduxState.pointer).toEqual(-1);
+ expect(reduxState.pointer).toEqual(0);
expect(reduxState.stack).toHaveLength(2);
// Snapshot
- reduxState = undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
+ reduxState = undoReducer(reduxState /* state */, undoSnapshot([state3]) /* action */);
// Redo should do nothing
const newReduxState = undoReducer(reduxState /* state */, redo() /* action */);
expect(newReduxState.pointer).toEqual(reduxState.pointer);
expect(newReduxState.stack).toHaveLength(reduxState.stack.length);
expect(newReduxState.stack[0]).toEqual(reduxState.stack[0]);
- expect(newReduxState.stack[0]).toEqual(state2);
+ expect(newReduxState.stack[1]).toEqual(state3);
});