Merge branch 'addTool' into addEraser

This commit is contained in:
DD Liu 2017-07-27 11:45:41 -04:00
commit c7471d26ab
19 changed files with 136 additions and 123 deletions

4
.babelrc Normal file
View file

@ -0,0 +1,4 @@
{
"plugins": ["transform-object-rest-spread"],
"presets": ["es2015", "react"]
}

View file

@ -9,8 +9,8 @@
"deploy": "touch playground/.nojekyll && gh-pages -t -d playground -m \"Build for $(git log --pretty=format:%H -n1)\"", "deploy": "touch playground/.nojekyll && gh-pages -t -d playground -m \"Build for $(git log --pretty=format:%H -n1)\"",
"lint": "eslint . --ext .js,.jsx", "lint": "eslint . --ext .js,.jsx",
"start": "webpack-dev-server", "start": "webpack-dev-server",
"tap": "./node_modules/.bin/tap ./test/*.js", "test": "npm run lint && npm run build && npm run unit",
"test": "npm run lint && npm run build && npm run tap", "unit": "jest",
"watch": "webpack --progress --colors --watch" "watch": "webpack --progress --colors --watch"
}, },
"author": "Massachusetts Institute of Technology", "author": "Massachusetts Institute of Technology",
@ -28,35 +28,51 @@
"autoprefixer": "7.1.1", "autoprefixer": "7.1.1",
"babel-core": "^6.23.1", "babel-core": "^6.23.1",
"babel-eslint": "^7.1.1", "babel-eslint": "^7.1.1",
"babel-jest": "^20.0.3",
"babel-loader": "^7.0.0", "babel-loader": "^7.0.0",
"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",
"classnames": "2.2.5", "classnames": "2.2.5",
"css-loader": "0.28.3", "css-loader": "0.28.3",
"enzyme": "^2.8.2",
"eslint": "^3.16.1", "eslint": "^3.16.1",
"eslint-config-import": "^0.13.0",
"eslint-config-scratch": "^3.0.0", "eslint-config-scratch": "^3.0.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-react": "^7.0.1", "eslint-plugin-react": "^7.0.1",
"gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder", "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
"html-webpack-plugin": "2.28.0", "html-webpack-plugin": "2.28.0",
"jest": "^20.0.4",
"keymirror": "0.1.1",
"lodash.bindall": "4.4.0",
"lodash.defaultsdeep": "4.6.0", "lodash.defaultsdeep": "4.6.0",
"minilog": "3.1.0", "minilog": "3.1.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"paper": "^0.11.4", "paper": "0.11.4",
"postcss-import": "^10.0.0", "postcss-import": "^10.0.0",
"postcss-loader": "^2.0.5", "postcss-loader": "^2.0.5",
"postcss-simple-vars": "^4.0.0", "postcss-simple-vars": "^4.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "15.5.4", "react": "15.6.1",
"react-dom": "15.5.4", "react-dom": "15.5.4",
"react-intl": "2.3.0", "react-intl": "2.3.0",
"react-redux": "5.0.5", "react-redux": "5.0.5",
"react-test-renderer": "^15.5.4",
"redux": "3.6.0", "redux": "3.6.0",
"redux-mock-store": "^1.2.3",
"redux-throttle": "0.1.1", "redux-throttle": "0.1.1",
"regenerator-runtime": "^0.10.5",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
"style-loader": "^0.18.0", "style-loader": "^0.18.0",
"tap": "^10.2.0", "tap": "^10.2.0",
"webpack": "^2.4.1", "webpack": "^2.4.1",
"webpack-dev-server": "^2.4.1" "webpack-dev-server": "^2.4.1"
},
"jest": {
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
}
} }
} }

View file

@ -1,7 +1,13 @@
module.exports = { module.exports = {
root: true, root: true,
extends: ['scratch', 'scratch/es6', 'scratch/react'], extends: ['scratch', 'scratch/es6', 'scratch/react', 'import'],
env: { env: {
browser: true browser: true
},
rules: {
'import/no-mutable-exports': 'error',
'import/no-commonjs': 'error',
'import/no-amd': 'error',
'import/no-nodejs-modules': 'error'
} }
}; };

View file

@ -3,24 +3,27 @@ import React from 'react';
import PaperCanvas from '../containers/paper-canvas.jsx'; import PaperCanvas from '../containers/paper-canvas.jsx';
import BrushTool from '../containers/tools/brush-tool.jsx'; import BrushTool from '../containers/tools/brush-tool.jsx';
import EraserTool from '../containers/tools/eraser-tool.jsx'; import EraserTool from '../containers/tools/eraser-tool.jsx';
import ToolTypes from '../tools/tool-types.js';
const PaintEditorComponent = props => ( class PaintEditorComponent extends React.Component {
render () {
return (
<div> <div>
<PaperCanvas <PaperCanvas
canvasId={props.canvasId} ref={canvas => {
tool={props.tool} this.canvas = canvas;
}}
tool={this.props.tool}
/> />
<BrushTool canvasId={props.canvasId} /> <BrushTool canvas={this.canvas} />
<EraserTool canvasId={props.canvasId} /> <EraserTool canvas={this.canvas} />
</div> </div>
); );
}
}
PaintEditorComponent.propTypes = { PaintEditorComponent.propTypes = {
canvasId: PropTypes.string.isRequired, tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
tool: PropTypes.shape({
name: PropTypes.string.isRequired
})
}; };
export default PaintEditorComponent;
module.exports = PaintEditorComponent;

View file

@ -7,27 +7,25 @@ import {connect} from 'react-redux';
class PaintEditor extends React.Component { class PaintEditor extends React.Component {
componentDidMount () { componentDidMount () {
const onKeyPress = this.props.onKeyPress; document.addEventListener('keydown', this.props.onKeyPress);
document.onkeydown = function (e) { }
e = e || window.event; componentWillUnmount () {
onKeyPress(e); document.removeEventListener('keydown', this.props.onKeyPress);
};
} }
render () { render () {
const {
onKeyPress, // eslint-disable-line no-unused-vars
...props
} = this.props;
return ( return (
<PaintEditorComponent <PaintEditorComponent {...props} />
canvasId="paper-canvas"
tool={this.props.tool}
/>
); );
} }
} }
PaintEditor.propTypes = { PaintEditor.propTypes = {
onKeyPress: PropTypes.func.isRequired, onKeyPress: PropTypes.func.isRequired,
tool: PropTypes.shape({ tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
name: PropTypes.string.isRequired
})
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
@ -43,7 +41,7 @@ const mapDispatchToProps = dispatch => ({
} }
}); });
module.exports = connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(PaintEditor); )(PaintEditor);

View file

@ -4,13 +4,8 @@ import paper from 'paper';
import ToolTypes from '../tools/tool-types.js'; import ToolTypes from '../tools/tool-types.js';
class PaperCanvas extends React.Component { class PaperCanvas extends React.Component {
constructor (props) {
super(props);
this.state = {
};
}
componentDidMount () { componentDidMount () {
paper.setup('paper-canvas'); paper.setup(this.canvas);
// Create a Paper.js Path to draw a line into it: // Create a Paper.js Path to draw a line into it:
const path = new paper.Path(); const path = new paper.Path();
// Give the stroke a color // Give the stroke a color
@ -24,25 +19,22 @@ class PaperCanvas extends React.Component {
// Draw the view now: // Draw the view now:
paper.view.draw(); paper.view.draw();
} }
componentWillReceiveProps (nextProps) {
if (nextProps.tool !== this.props.tool && nextProps.tool instanceof ToolTypes) {
// TODO switch tool
}
}
componentWillUnmount () { componentWillUnmount () {
paper.remove();
} }
render () { render () {
return ( return (
<canvas id={this.props.canvasId} /> <canvas
ref={canvas => {
this.canvas = canvas;
}}
/>
); );
} }
} }
PaperCanvas.propTypes = { PaperCanvas.propTypes = {
canvasId: PropTypes.string.isRequired, tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
tool: PropTypes.shape({
name: PropTypes.string.isRequired
})
}; };
module.exports = PaperCanvas; export default PaperCanvas;

View file

@ -22,6 +22,7 @@ class BrushTool extends React.Component {
} }
componentDidMount () { componentDidMount () {
if (this.props.tool === BrushTool.TOOL_TYPE) { if (this.props.tool === BrushTool.TOOL_TYPE) {
debugger;
this.activateTool(); this.activateTool();
} }
} }
@ -38,8 +39,7 @@ class BrushTool extends React.Component {
return false; // Logic only component return false; // Logic only component
} }
activateTool () { activateTool () {
document.getElementById(this.props.canvasId) this.props.canvas.addEventListener('mousewheel', this.onScroll);
.addEventListener('mousewheel', this.onScroll);
this.tool = new paper.Tool(); this.tool = new paper.Tool();
this.blob.activateTool(false /* isEraser */, this.tool, this.props.brushToolState); this.blob.activateTool(false /* isEraser */, this.tool, this.props.brushToolState);
@ -56,8 +56,7 @@ class BrushTool extends React.Component {
this.tool.activate(); this.tool.activate();
} }
deactivateTool () { deactivateTool () {
document.getElementById(this.props.canvasId) this.props.canvas.removeEventListener('mousewheel', this.onScroll);
.removeEventListener('mousewheel', this.onScroll);
} }
onScroll (event) { onScroll (event) {
if (event.deltaY < 0) { if (event.deltaY < 0) {
@ -69,7 +68,7 @@ class BrushTool extends React.Component {
} }
render () { render () {
return ( return (
<div /> <div>Brush Tool </div>
); );
} }
} }
@ -78,7 +77,7 @@ BrushTool.propTypes = {
brushToolState: PropTypes.shape({ brushToolState: PropTypes.shape({
brushSize: PropTypes.number.isRequired brushSize: PropTypes.number.isRequired
}), }),
canvasId: PropTypes.string.isRequired, canvas: PropTypes.element,
changeBrushSize: PropTypes.func.isRequired, changeBrushSize: PropTypes.func.isRequired,
tool: PropTypes.shape({ tool: PropTypes.shape({
name: PropTypes.string.isRequired name: PropTypes.string.isRequired
@ -95,7 +94,7 @@ const mapDispatchToProps = dispatch => ({
} }
}); });
module.exports = connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(BrushTool); )(BrushTool);

View file

@ -38,16 +38,14 @@ class EraserTool extends React.Component {
return false; // Logic only component return false; // Logic only component
} }
activateTool () { activateTool () {
document.getElementById(this.props.canvasId) this.props.canvas.addEventListener('mousewheel', this.onScroll);
.addEventListener('mousewheel', this.onScroll);
this.tool = new paper.Tool(); this.tool = new paper.Tool();
this.blob.activateTool(true /* isEraser */, this.tool, this.props.eraserToolState); this.blob.activateTool(true /* isEraser */, this.tool, this.props.eraserToolState);
this.tool.activate(); this.tool.activate();
} }
deactivateTool () { deactivateTool () {
document.getElementById(this.props.canvasId) this.props.canvas.removeEventListener('mousewheel', this.onScroll);
.removeEventListener('mousewheel', this.onScroll);
this.blob.deactivateTool(); this.blob.deactivateTool();
} }
onScroll (event) { onScroll (event) {
@ -60,13 +58,13 @@ class EraserTool extends React.Component {
} }
render () { render () {
return ( return (
<div /> <div>Eraser Tool </div>
); );
} }
} }
EraserTool.propTypes = { EraserTool.propTypes = {
canvasId: PropTypes.string.isRequired, canvas: PropTypes.element,
changeBrushSize: PropTypes.func.isRequired, changeBrushSize: PropTypes.func.isRequired,
eraserToolState: PropTypes.shape({ eraserToolState: PropTypes.shape({
brushSize: PropTypes.number.isRequired brushSize: PropTypes.number.isRequired
@ -86,7 +84,7 @@ const mapDispatchToProps = dispatch => ({
} }
}); });
module.exports = connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(EraserTool); )(EraserTool);

View file

@ -1,4 +1,4 @@
const minilog = require('minilog'); import minilog from 'minilog';
minilog.enable(); minilog.enable();
module.exports = minilog('paint-editor'); export default minilog('scratch-paint');

View file

@ -2,15 +2,12 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import PaintEditor from '..'; import PaintEditor from '..';
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import {createStore, applyMiddleware} from 'redux'; import {createStore} from 'redux';
import throttle from 'redux-throttle';
import reducer from '../reducers/combine-reducers'; import reducer from '../reducers/combine-reducers';
const appTarget = document.createElement('div'); const appTarget = document.createElement('div');
document.body.appendChild(appTarget); document.body.appendChild(appTarget);
const store = applyMiddleware( const store = createStore(
throttle(300, {leading: true, trailing: true})
)(createStore)(
reducer, reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); );

View file

@ -22,4 +22,4 @@ reducer.changeBrushSize = function (brushSize) {
}; };
}; };
module.exports = reducer; export default reducer;

View file

@ -1,7 +1,10 @@
import {combineReducers} from 'redux'; import {combineReducers} from 'redux';
import toolReducer from './tools';
import brushToolReducer from './brush-tool';
import eraserToolReducer from './eraser-tool';
module.exports = combineReducers({ export default combineReducers({
tool: require('./tools'), tool: toolReducer,
brushTool: require('./brush-tool'), brushTool: brushToolReducer,
eraserTool: require('./eraser-tool') eraserTool: eraserToolReducer
}); });

View file

@ -22,4 +22,4 @@ reducer.changeBrushSize = function (brushSize) {
}; };
}; };
module.exports = reducer; export default reducer;

View file

@ -1,5 +1,5 @@
const ToolTypes = require('../tools/tool-types'); import ToolTypes from '../tools/tool-types';
const log = require('../log/log'); import log from '../log/log';
const CHANGE_TOOL = 'scratch-paint/tools/CHANGE_TOOL'; const CHANGE_TOOL = 'scratch-paint/tools/CHANGE_TOOL';
const initialState = ToolTypes.BRUSH; const initialState = ToolTypes.BRUSH;
@ -8,10 +8,10 @@ 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_TOOL: case CHANGE_TOOL:
if (action.tool instanceof ToolTypes) { if (action.tool in ToolTypes) {
return action.tool; return action.tool;
} }
log.warn(`Warning: Tool type does not exist: ${action.tool}`); log.warn(`Tool type does not exist: ${action.tool}`);
/* falls through */ /* falls through */
default: default:
return state; return state;
@ -22,11 +22,8 @@ const reducer = function (state, action) {
reducer.changeTool = function (tool) { reducer.changeTool = function (tool) {
return { return {
type: CHANGE_TOOL, type: CHANGE_TOOL,
tool: tool, tool: tool
meta: {
throttle: 30
}
}; };
}; };
module.exports = reducer; export default reducer;

View file

@ -1,12 +1,8 @@
class ToolTypes { import keyMirror from 'keymirror';
constructor (name) {
this.name = name;
}
toString () {
return `ToolTypes.${this.name}`;
}
}
ToolTypes.BRUSH = new ToolTypes('BRUSH');
ToolTypes.ERASER = new ToolTypes('ERASER');
module.exports = ToolTypes; const ToolTypes = keyMirror({
BRUSH: null,
ERASER: null
});
export default ToolTypes;

View file

@ -0,0 +1,3 @@
// __mocks__/fileMock.js
module.exports = 'test-file-stub';

View file

@ -0,0 +1,3 @@
// __mocks__/styleMock.js
module.exports = {};

View file

@ -1,25 +0,0 @@
const test = require('tap').test;
const ToolTypes = require('../src/tools/tool-types');
const reducer = require('../src/reducers/tools');
test('initialState', t => {
let defaultState;
t.assert(reducer(defaultState /* state */, {type: 'anything'} /* action */) instanceof ToolTypes);
t.end();
});
test('changeTool', t => {
let defaultState;
t.assert(reducer(defaultState /* state */, reducer.changeTool(ToolTypes.ERASER) /* action */), ToolTypes.ERASER);
t.assert(
reducer(ToolTypes.ERASER /* state */, reducer.changeTool(ToolTypes.ERASER) /* action */), ToolTypes.ERASER);
t.assert(reducer(ToolTypes.BRUSH /* state */, reducer.changeTool(ToolTypes.ERASER) /* action */), ToolTypes.ERASER);
t.end();
});
test('invalidChangeTool', t => {
t.assert(
reducer(ToolTypes.BRUSH /* state */, reducer.changeTool('non-existant tool') /* action */), ToolTypes.BRUSH);
t.assert(reducer(ToolTypes.BRUSH /* state */, reducer.changeTool() /* action */), ToolTypes.BRUSH);
t.end();
});

View file

@ -0,0 +1,23 @@
/* eslint-env jest */
import ToolTypes from '../../src/tools/tool-types';
import reducer from '../../src/reducers/tools';
test('initialState', () => {
let defaultState;
expect(reducer(defaultState /* state */, {type: 'anything'} /* action */) in ToolTypes).toBeTruthy();
});
test('changeTool', () => {
let defaultState;
expect(reducer(defaultState /* state */, reducer.changeTool(ToolTypes.ERASER) /* action */)).toBe(ToolTypes.ERASER);
expect(reducer(ToolTypes.ERASER /* state */, reducer.changeTool(ToolTypes.ERASER) /* action */))
.toBe(ToolTypes.ERASER);
expect(reducer(ToolTypes.BRUSH /* state */, reducer.changeTool(ToolTypes.ERASER) /* action */))
.toBe(ToolTypes.ERASER);
});
test('invalidChangeTool', () => {
expect(reducer(ToolTypes.BRUSH /* state */, reducer.changeTool('non-existant tool') /* action */))
.toBe(ToolTypes.BRUSH);
expect(reducer(ToolTypes.BRUSH /* state */, reducer.changeTool() /* action */)).toBe(ToolTypes.BRUSH);
});