diff --git a/src/components/line-mode.jsx b/src/components/line-mode.jsx
new file mode 100644
index 00000000..17affefa
--- /dev/null
+++ b/src/components/line-mode.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+
+const LineModeComponent = props => (
+
+);
+
+LineModeComponent.propTypes = {
+ onMouseDown: PropTypes.func.isRequired
+};
+
+export default LineModeComponent;
diff --git a/src/components/paint-editor.jsx b/src/components/paint-editor.jsx
index 46dd614a..d95db51a 100644
--- a/src/components/paint-editor.jsx
+++ b/src/components/paint-editor.jsx
@@ -3,6 +3,8 @@ import React from 'react';
import PaperCanvas from '../containers/paper-canvas.jsx';
import BrushMode from '../containers/brush-mode.jsx';
import EraserMode from '../containers/eraser-mode.jsx';
+import PropTypes from 'prop-types';
+import LineMode from '../containers/line-mode.jsx';
import styles from './paint-editor.css';
@@ -26,7 +28,7 @@ class PaintEditorComponent extends React.Component {
{/* Todo use Label and BufferedInput from Gui */}
@@ -103,14 +105,29 @@ class PaintEditorComponent extends React.Component {
{/* Modes */}
{this.state.canvas ? (
-
-
+
+
+
) : null}
{/* Canvas */}
@@ -118,4 +135,11 @@ class PaintEditorComponent extends React.Component {
}
}
+PaintEditorComponent.propTypes = {
+ onUpdateSvg: PropTypes.func.isRequired,
+ rotationCenterX: PropTypes.number,
+ rotationCenterY: PropTypes.number,
+ svg: PropTypes.string
+};
+
export default PaintEditorComponent;
diff --git a/src/containers/blob/blob.js b/src/containers/blob/blob.js
index b06199b8..22f4cda5 100644
--- a/src/containers/blob/blob.js
+++ b/src/containers/blob/blob.js
@@ -24,9 +24,13 @@ class Blobbiness {
return 9;
}
- constructor () {
+ /**
+ * @param {function} updateCallback call when the drawing has changed to let listeners know
+ */
+ constructor (updateCallback) {
this.broadBrushHelper = new BroadBrushHelper();
this.segmentBrushHelper = new SegmentBrushHelper();
+ this.updateCallback = updateCallback;
}
/**
@@ -64,7 +68,7 @@ class Blobbiness {
this.tool.onMouseDown = function (event) {
blob.resizeCursorIfNeeded(event.point);
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
if (blob.options.brushSize < Blobbiness.THRESHOLD) {
blob.brush = Blobbiness.BROAD;
@@ -80,7 +84,7 @@ class Blobbiness {
this.tool.onMouseDrag = function (event) {
blob.resizeCursorIfNeeded(event.point);
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
if (blob.brush === Blobbiness.BROAD) {
blob.broadBrushHelper.onBroadMouseDrag(event, blob.tool, blob.options);
} else if (blob.brush === Blobbiness.SEGMENT) {
@@ -96,7 +100,7 @@ class Blobbiness {
this.tool.onMouseUp = function (event) {
blob.resizeCursorIfNeeded(event.point);
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
let lastPath;
if (blob.brush === Blobbiness.BROAD) {
@@ -113,6 +117,9 @@ class Blobbiness {
blob.mergeBrush(lastPath);
}
+ blob.cursorPreview.visible = false;
+ blob.updateCallback();
+ blob.cursorPreview.visible = true;
blob.cursorPreview.bringToFront();
blob.cursorPreview.position = event.point;
diff --git a/src/containers/blob/broad-brush-helper.js b/src/containers/blob/broad-brush-helper.js
index b3bc1ca3..c9cfee25 100644
--- a/src/containers/blob/broad-brush-helper.js
+++ b/src/containers/blob/broad-brush-helper.js
@@ -22,7 +22,7 @@ class BroadBrushHelper {
onBroadMouseDown (event, tool, options) {
tool.minDistance = options.brushSize / 2;
tool.maxDistance = options.brushSize;
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
this.finalPath = new paper.Path();
stylePath(this.finalPath, options.isEraser);
diff --git a/src/containers/blob/segment-brush-helper.js b/src/containers/blob/segment-brush-helper.js
index 88f7debd..c745b9d8 100644
--- a/src/containers/blob/segment-brush-helper.js
+++ b/src/containers/blob/segment-brush-helper.js
@@ -22,7 +22,7 @@ class SegmentBrushHelper {
}
onSegmentMouseDown (event, tool, options) {
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
tool.minDistance = 1;
tool.maxDistance = options.brushSize;
@@ -37,7 +37,7 @@ class SegmentBrushHelper {
}
onSegmentMouseDrag (event, tool, options) {
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
const step = (event.delta).normalize(options.brushSize / 2);
const handleVec = step.clone();
@@ -75,7 +75,7 @@ class SegmentBrushHelper {
}
onSegmentMouseUp (event) {
- if (event.event.button > 0) return; // only first mouse button
+ if (event.event.button > 0) return; // only first mouse button
// TODO: This smoothing tends to cut off large portions of the path! Would like to eventually
// add back smoothing, maybe a custom implementation that only applies to a subset of the line?
diff --git a/src/containers/brush-mode.jsx b/src/containers/brush-mode.jsx
index e751030f..c89e08f9 100644
--- a/src/containers/brush-mode.jsx
+++ b/src/containers/brush-mode.jsx
@@ -16,7 +16,7 @@ class BrushMode extends React.Component {
'deactivateTool',
'onScroll'
]);
- this.blob = new Blobbiness();
+ this.blob = new Blobbiness(this.props.onUpdateSvg);
}
componentDidMount () {
if (this.props.isBrushModeActive) {
@@ -70,7 +70,8 @@ BrushMode.propTypes = {
canvas: PropTypes.instanceOf(Element).isRequired,
changeBrushSize: PropTypes.func.isRequired,
handleMouseDown: PropTypes.func.isRequired,
- isBrushModeActive: PropTypes.bool.isRequired
+ isBrushModeActive: PropTypes.bool.isRequired,
+ onUpdateSvg: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
diff --git a/src/containers/eraser-mode.jsx b/src/containers/eraser-mode.jsx
index af932329..0972ed36 100644
--- a/src/containers/eraser-mode.jsx
+++ b/src/containers/eraser-mode.jsx
@@ -16,7 +16,7 @@ class EraserMode extends React.Component {
'deactivateTool',
'onScroll'
]);
- this.blob = new Blobbiness();
+ this.blob = new Blobbiness(this.props.onUpdateSvg);
}
componentDidMount () {
if (this.props.isEraserModeActive) {
@@ -66,7 +66,8 @@ EraserMode.propTypes = {
brushSize: PropTypes.number.isRequired
}),
handleMouseDown: PropTypes.func.isRequired,
- isEraserModeActive: PropTypes.bool.isRequired
+ isEraserModeActive: PropTypes.bool.isRequired,
+ onUpdateSvg: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
diff --git a/src/containers/line-mode.jsx b/src/containers/line-mode.jsx
new file mode 100644
index 00000000..7648c045
--- /dev/null
+++ b/src/containers/line-mode.jsx
@@ -0,0 +1,304 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+import bindAll from 'lodash.bindall';
+import Modes from '../modes/modes';
+import {changeLineWidth} from '../reducers/line-mode';
+import LineModeComponent from '../components/line-mode.jsx';
+import {changeMode} from '../reducers/modes';
+import paper from 'paper';
+
+class LineMode extends React.Component {
+ static get SNAP_TOLERANCE () {
+ return 6;
+ }
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'activateTool',
+ 'deactivateTool',
+ 'onMouseDown',
+ 'onMouseMove',
+ 'onMouseDrag',
+ 'onMouseUp',
+ 'toleranceSquared',
+ 'findLineEnd',
+ 'onScroll'
+ ]);
+ }
+ componentDidMount () {
+ if (this.props.isLineModeActive) {
+ this.activateTool();
+ }
+ }
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.isLineModeActive && !this.props.isLineModeActive) {
+ this.activateTool();
+ } else if (!nextProps.isLineModeActive && this.props.isLineModeActive) {
+ this.deactivateTool();
+ }
+ }
+ shouldComponentUpdate () {
+ return false; // Static component, for now
+ }
+ activateTool () {
+ // TODO add back selection
+ // pg.selection.clearSelection();
+ this.props.canvas.addEventListener('mousewheel', this.onScroll);
+ this.tool = new paper.Tool();
+
+ this.path = null;
+ this.hitResult = null;
+
+ // TODO add back colors
+ // Make sure a stroke color is set on the line tool
+ // if(!pg.stylebar.getStrokeColor()) {
+ // pg.stylebar.setStrokeColor(pg.stylebar.getFillColor());
+ // pg.stylebar.setFillColor(null);
+ // }
+
+ const lineMode = this;
+ this.tool.onMouseDown = function (event) {
+ if (event.event.button > 0) return; // only first mouse button
+ lineMode.onMouseDown(event);
+ };
+ this.tool.onMouseMove = function (event) {
+ lineMode.onMouseMove(event);
+ };
+ this.tool.onMouseDrag = function (event) {
+ if (event.event.button > 0) return; // only first mouse button
+ lineMode.onMouseDrag(event);
+ };
+ this.tool.onMouseUp = function (event) {
+ if (event.event.button > 0) return; // only first mouse button
+ lineMode.onMouseUp(event);
+ };
+
+ this.tool.activate();
+ }
+ onMouseDown (event) {
+ // Deselect old path
+ if (this.path) {
+ this.path.setSelected(false);
+ this.path = null;
+ }
+
+ // If you click near a point, continue that line instead of making a new line
+ this.hitResult = this.findLineEnd(event.point);
+ if (this.hitResult) {
+ this.path = this.hitResult.path;
+ if (this.hitResult.isFirst) {
+ this.path.reverse();
+ }
+ this.path.lastSegment.setSelected(true);
+ this.path.add(this.hitResult.segment); // Add second point, which is what will move when dragged
+ this.path.lastSegment.handleOut = null; // Make sure line isn't curvy
+ this.path.lastSegment.handleIn = null;
+ }
+
+ // If not near other path, start a new path
+ if (!this.path) {
+ this.path = new paper.Path();
+
+ // TODO add back style
+ // this.path = pg.stylebar.applyActiveToolbarStyle(path);
+ this.path.setStrokeColor('black');
+ this.path.setStrokeWidth(this.props.lineModeState.lineWidth);
+
+ this.path.setSelected(true);
+ this.path.add(event.point);
+ this.path.add(event.point); // Add second point, which is what will move when dragged
+ paper.view.draw();
+ }
+ }
+ onMouseMove (event) {
+ // If near another path's endpoint, or this path's beginpoint, clip to it to suggest
+ // joining/closing the paths.
+ if (this.hitResult) {
+ this.hitResult.path.setSelected(false);
+ this.hitResult = null;
+ }
+
+ if (this.path &&
+ !this.path.closed &&
+ this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared()) {
+ this.hitResult = {
+ path: this.path,
+ segment: this.path.firstSegment,
+ isFirst: true
+ };
+ } else {
+ this.hitResult = this.findLineEnd(event.point);
+ }
+
+ if (this.hitResult) {
+ const hitPath = this.hitResult.path;
+ hitPath.setSelected(true);
+ if (this.hitResult.isFirst) {
+ hitPath.firstSegment.setSelected(true);
+ } else {
+ hitPath.lastSegment.setSelected(true);
+ }
+ }
+ }
+ onMouseDrag (event) {
+ // If near another path's endpoint, or this path's beginpoint, clip to it to suggest
+ // joining/closing the paths.
+ if (this.hitResult && this.hitResult.path !== this.path) this.hitResult.path.setSelected(false);
+ this.hitResult = null;
+
+ if (this.path &&
+ this.path.segments.length > 3 &&
+ this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared()) {
+ this.hitResult = {
+ path: this.path,
+ segment: this.path.firstSegment,
+ isFirst: true
+ };
+ } else {
+ this.hitResult = this.findLineEnd(event.point, this.path);
+ if (this.hitResult) {
+ const hitPath = this.hitResult.path;
+ hitPath.setSelected(true);
+ if (this.hitResult.isFirst) {
+ hitPath.firstSegment.setSelected(true);
+ } else {
+ hitPath.lastSegment.setSelected(true);
+ }
+ }
+ }
+
+ // snapping
+ if (this.path) {
+ if (this.hitResult) {
+ this.path.lastSegment.point = this.hitResult.segment.point;
+ } else {
+ this.path.lastSegment.point = event.point;
+ }
+ }
+ }
+ onMouseUp (event) {
+ // If I single clicked, don't do anything
+ if (this.path.segments.length < 2 ||
+ (this.path.segments.length === 2 &&
+ this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared())) {
+ this.path.remove();
+ this.path = null;
+ // TODO don't erase the line if both ends are snapped to different points
+ return;
+ } else if (
+ this.path.lastSegment.point.getDistance(this.path.segments[this.path.segments.length - 2].point, true) <
+ this.toleranceSquared()) {
+ this.path.removeSegment(this.path.segments.length - 1);
+ return;
+ }
+
+ // If I intersect other line end points, join or close
+ if (this.hitResult) {
+ this.path.removeSegment(this.path.segments.length - 1);
+ if (this.path.firstSegment === this.hitResult.segment) {
+ // close path
+ this.path.closed = true;
+ this.path.setSelected(false);
+ } else {
+ // joining two paths
+ if (!this.hitResult.isFirst) {
+ this.hitResult.path.reverse();
+ }
+ this.path.join(this.hitResult.path);
+ }
+ this.hitResult = null;
+ }
+ this.props.onUpdateSvg();
+
+ // TODO add back undo
+ // if (this.path) {
+ // pg.undo.snapshot('line');
+ // }
+ }
+ toleranceSquared () {
+ return Math.pow(LineMode.SNAP_TOLERANCE / paper.view.zoom, 2);
+ }
+ findLineEnd (point, excludePath) {
+ const lines = paper.project.getItems({
+ class: paper.Path
+ });
+ // Prefer more recent lines
+ for (let i = lines.length - 1; i >= 0; i--) {
+ if (lines[i].closed) {
+ continue;
+ }
+ if (excludePath && lines[i] === excludePath) {
+ continue;
+ }
+ if (lines[i].firstSegment &&
+ lines[i].firstSegment.point.getDistance(point, true) < this.toleranceSquared()) {
+ return {
+ path: lines[i],
+ segment: lines[i].firstSegment,
+ isFirst: true
+ };
+ }
+ if (lines[i].lastSegment && lines[i].lastSegment.point.getDistance(point, true) < this.toleranceSquared()) {
+ return {
+ path: lines[i],
+ segment: lines[i].lastSegment,
+ isFirst: false
+ };
+ }
+ }
+ return null;
+ }
+ deactivateTool () {
+ this.props.canvas.removeEventListener('mousewheel', this.onScroll);
+ this.tool.remove();
+ this.tool = null;
+ this.hitResult = null;
+ if (this.path) {
+ this.path.setSelected(false);
+ this.path = null;
+ }
+ }
+ onScroll (event) {
+ if (event.deltaY < 0) {
+ this.props.changeLineWidth(this.props.lineModeState.lineWidth + 1);
+ } else if (event.deltaY > 0 && this.props.lineModeState.lineWidth > 1) {
+ this.props.changeLineWidth(this.props.lineModeState.lineWidth - 1);
+ }
+ return true;
+ }
+ render () {
+ return (
+
+ );
+ }
+}
+
+LineMode.propTypes = {
+ canvas: PropTypes.instanceOf(Element).isRequired,
+ changeLineWidth: PropTypes.func.isRequired,
+ handleMouseDown: PropTypes.func.isRequired,
+ isLineModeActive: PropTypes.bool.isRequired,
+ lineModeState: PropTypes.shape({
+ lineWidth: PropTypes.number.isRequired
+ }),
+ onUpdateSvg: PropTypes.func.isRequired
+};
+
+const mapStateToProps = state => ({
+ lineModeState: state.scratchPaint.lineMode,
+ isLineModeActive: state.scratchPaint.mode === Modes.LINE
+});
+const mapDispatchToProps = dispatch => ({
+ changeLineWidth: lineWidth => {
+ dispatch(changeLineWidth(lineWidth));
+ },
+ handleMouseDown: () => {
+ dispatch(changeMode(Modes.LINE));
+ }
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(LineMode);
diff --git a/src/containers/paint-editor.jsx b/src/containers/paint-editor.jsx
index 303546ba..ba260313 100644
--- a/src/containers/paint-editor.jsx
+++ b/src/containers/paint-editor.jsx
@@ -4,23 +4,50 @@ import PaintEditorComponent from '../components/paint-editor.jsx';
import {changeMode} from '../reducers/modes';
import Modes from '../modes/modes';
import {connect} from 'react-redux';
+import bindAll from 'lodash.bindall';
+import paper from 'paper';
class PaintEditor extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleUpdateSvg'
+ ]);
+ }
componentDidMount () {
document.addEventListener('keydown', this.props.onKeyPress);
}
componentWillUnmount () {
document.removeEventListener('keydown', this.props.onKeyPress);
}
+ handleUpdateSvg () {
+ const bounds = paper.project.activeLayer.bounds;
+ this.props.onUpdateSvg(
+ paper.project.exportSVG({
+ asString: true,
+ matrix: new paper.Matrix().translate(-bounds.x, -bounds.y)
+ }),
+ paper.project.view.center.x - bounds.x,
+ paper.project.view.center.y - bounds.y);
+ }
render () {
return (
-
+
);
}
}
PaintEditor.propTypes = {
- onKeyPress: PropTypes.func.isRequired
+ onKeyPress: PropTypes.func.isRequired,
+ onUpdateSvg: PropTypes.func.isRequired,
+ rotationCenterX: PropTypes.number,
+ rotationCenterY: PropTypes.number,
+ svg: PropTypes.string
};
const mapDispatchToProps = dispatch => ({
@@ -29,6 +56,8 @@ const mapDispatchToProps = dispatch => ({
dispatch(changeMode(Modes.ERASER));
} else if (event.key === 'b') {
dispatch(changeMode(Modes.BRUSH));
+ } else if (event.key === 'l') {
+ dispatch(changeMode(Modes.LINE));
}
}
});
diff --git a/src/containers/paper-canvas.jsx b/src/containers/paper-canvas.jsx
index 4ccb0b58..fdd6e44d 100644
--- a/src/containers/paper-canvas.jsx
+++ b/src/containers/paper-canvas.jsx
@@ -7,27 +7,50 @@ class PaperCanvas extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
- 'setCanvas'
+ 'setCanvas',
+ 'importSvg'
]);
}
componentDidMount () {
paper.setup(this.canvas);
- // Create a Paper.js Path to draw a line into it:
- const path = new paper.Path();
- // Give the stroke a color
- path.strokeColor = 'black';
- const start = new paper.Point(100, 100);
- // Move to start and draw a line from there
- path.moveTo(start);
- // Note that the plus operator on Point objects does not work
- // in JavaScript. Instead, we need to call the add() function:
- path.lineTo(start.add([200, -50]));
- // Draw the view now:
- paper.view.draw();
+ if (this.props.svg) {
+ this.importSvg(this.props.svg, this.props.rotationCenterX, this.props.rotationCenterY);
+ }
+ }
+ componentWillReceiveProps (newProps) {
+ paper.project.activeLayer.removeChildren();
+ this.importSvg(newProps.svg, newProps.rotationCenterX, newProps.rotationCenterY);
}
componentWillUnmount () {
paper.remove();
}
+ importSvg (svg, rotationCenterX, rotationCenterY) {
+ const imported = paper.project.importSVG(svg,
+ {
+ expandShapes: true,
+ onLoad: function (item) {
+ // Remove viewbox
+ if (item.clipped) {
+ item.clipped = false;
+ // Consider removing clip mask here?
+ }
+ while (item.reduce() !== item) {
+ item = item.reduce();
+ }
+ }
+ });
+ if (typeof rotationCenterX !== 'undefined' && typeof rotationCenterY !== 'undefined') {
+ imported.position =
+ paper.project.view.center
+ .add(imported.bounds.width / 2, imported.bounds.height / 2)
+ .subtract(rotationCenterX, rotationCenterY);
+ } else {
+ // Center
+ imported.position = paper.project.view.center;
+ }
+
+ paper.project.view.update();
+ }
setCanvas (canvas) {
this.canvas = canvas;
if (this.props.canvasRef) {
@@ -44,7 +67,10 @@ class PaperCanvas extends React.Component {
}
PaperCanvas.propTypes = {
- canvasRef: PropTypes.func
+ canvasRef: PropTypes.func,
+ rotationCenterX: PropTypes.number,
+ rotationCenterY: PropTypes.number,
+ svg: PropTypes.string
};
export default PaperCanvas;
diff --git a/src/modes/modes.js b/src/modes/modes.js
index c485f0fe..c1b64738 100644
--- a/src/modes/modes.js
+++ b/src/modes/modes.js
@@ -2,7 +2,8 @@ import keyMirror from 'keymirror';
const Modes = keyMirror({
BRUSH: null,
- ERASER: null
+ ERASER: null,
+ LINE: null
});
export default Modes;
diff --git a/src/playground/playground.jsx b/src/playground/playground.jsx
index 9f68704a..f59ef2b0 100644
--- a/src/playground/playground.jsx
+++ b/src/playground/playground.jsx
@@ -13,10 +13,28 @@ const store = createStore(
intlInitialState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
+const svgString =
+ '';
+const onUpdateSvg = function (newSvgString, rotationCenterX, rotationCenterY) {
+ console.log(newSvgString);
+ console.log(`rotationCenterX: ${rotationCenterX} rotationCenterY: ${rotationCenterY}`);
+};
ReactDOM.render((
-
+
), appTarget);
diff --git a/src/reducers/line-mode.js b/src/reducers/line-mode.js
new file mode 100644
index 00000000..8845ace4
--- /dev/null
+++ b/src/reducers/line-mode.js
@@ -0,0 +1,31 @@
+import log from '../log/log';
+
+const CHANGE_LINE_WIDTH = 'scratch-paint/line-mode/CHANGE_LINE_WIDTH';
+const initialState = {lineWidth: 2};
+
+const reducer = function (state, action) {
+ if (typeof state === 'undefined') state = initialState;
+ switch (action.type) {
+ case CHANGE_LINE_WIDTH:
+ if (isNaN(action.lineWidth)) {
+ log.warn(`Invalid line width: ${action.lineWidth}`);
+ return state;
+ }
+ return {lineWidth: Math.max(1, action.lineWidth)};
+ default:
+ return state;
+ }
+};
+
+// Action creators ==================================
+const changeLineWidth = function (lineWidth) {
+ return {
+ type: CHANGE_LINE_WIDTH,
+ lineWidth: lineWidth
+ };
+};
+
+export {
+ reducer as default,
+ changeLineWidth
+};
diff --git a/src/reducers/scratch-paint-reducer.js b/src/reducers/scratch-paint-reducer.js
index 31cfccc0..4f06e1bd 100644
--- a/src/reducers/scratch-paint-reducer.js
+++ b/src/reducers/scratch-paint-reducer.js
@@ -2,9 +2,11 @@ import {combineReducers} from 'redux';
import modeReducer from './modes';
import brushModeReducer from './brush-mode';
import eraserModeReducer from './eraser-mode';
+import lineModeReducer from './line-mode';
export default combineReducers({
mode: modeReducer,
brushMode: brushModeReducer,
- eraserMode: eraserModeReducer
+ eraserMode: eraserModeReducer,
+ lineMode: lineModeReducer
});
diff --git a/test/unit/components/line-mode.test.jsx b/test/unit/components/line-mode.test.jsx
new file mode 100644
index 00000000..7be2ff14
--- /dev/null
+++ b/test/unit/components/line-mode.test.jsx
@@ -0,0 +1,15 @@
+/* eslint-env jest */
+import React from 'react'; // eslint-disable-line no-unused-vars
+import {shallow} from 'enzyme';
+import LineModeComponent from '../../../src/components/line-mode.jsx'; // eslint-disable-line no-unused-vars
+
+describe('LineModeComponent', () => {
+ test('triggers callback when clicked', () => {
+ const onClick = jest.fn();
+ const componentShallowWrapper = shallow(
+
+ );
+ componentShallowWrapper.simulate('click');
+ expect(onClick).toHaveBeenCalled();
+ });
+});
diff --git a/test/unit/line-mode-reducer.test.js b/test/unit/line-mode-reducer.test.js
new file mode 100644
index 00000000..cd24cb99
--- /dev/null
+++ b/test/unit/line-mode-reducer.test.js
@@ -0,0 +1,31 @@
+/* eslint-env jest */
+import lineReducer from '../../src/reducers/line-mode';
+import {changeLineWidth} from '../../src/reducers/line-mode';
+
+test('initialState', () => {
+ let defaultState;
+
+ expect(lineReducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeDefined();
+ expect(lineReducer(defaultState /* state */, {type: 'anything'} /* action */).lineWidth).toBeGreaterThan(0);
+});
+
+test('changeLineWidth', () => {
+ let defaultState;
+ const newLineWidth = 8078;
+
+ expect(lineReducer(defaultState /* state */, changeLineWidth(newLineWidth) /* action */))
+ .toEqual({lineWidth: newLineWidth});
+ expect(lineReducer(2 /* state */, changeLineWidth(newLineWidth) /* action */))
+ .toEqual({lineWidth: newLineWidth});
+ expect(lineReducer(2 /* state */, changeLineWidth(-1) /* action */))
+ .toEqual({lineWidth: 1});
+});
+
+test('invalidChangeLineWidth', () => {
+ const origState = {lineWidth: 2};
+
+ expect(lineReducer(origState /* state */, changeLineWidth('invalid argument') /* action */))
+ .toBe(origState);
+ expect(lineReducer(origState /* state */, changeLineWidth() /* action */))
+ .toBe(origState);
+});
diff --git a/test/unit/modes-reducer.test.js b/test/unit/modes-reducer.test.js
index 8f08ffc9..df81f64c 100644
--- a/test/unit/modes-reducer.test.js
+++ b/test/unit/modes-reducer.test.js
@@ -19,6 +19,6 @@ test('changeMode', () => {
test('invalidChangeMode', () => {
expect(reducer(Modes.BRUSH /* state */, changeMode('non-existant mode') /* action */))
- .toBe(Modes.BRUSH);
+ .toBe(Modes.BRUSH);
expect(reducer(Modes.BRUSH /* state */, changeMode() /* action */)).toBe(Modes.BRUSH);
});
diff --git a/webpack.config.js b/webpack.config.js
index 2b093a84..fd5935d6 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,4 +1,3 @@
-
const defaultsDeep = require('lodash.defaultsdeep');
const path = require('path');
const webpack = require('webpack');
@@ -53,12 +52,12 @@ const base = {
}]
},
plugins: []
- .concat(process.env.NODE_ENV === 'production' ? [
- new webpack.optimize.UglifyJsPlugin({
- include: /\.min\.js$/,
- minimize: true
- })
- ] : [])
+ .concat(process.env.NODE_ENV === 'production' ? [
+ new webpack.optimize.UglifyJsPlugin({
+ include: /\.min\.js$/,
+ minimize: true
+ })
+ ] : [])
};
module.exports = [
@@ -96,7 +95,7 @@ module.exports = [
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
- libraryTarget: 'commonjs2',
+ libraryTarget: 'commonjs2'
}
})
];