mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 13:32:28 -05:00
Merge branch 'addTool' into addEraser
This commit is contained in:
commit
c7471d26ab
19 changed files with 136 additions and 123 deletions
4
.babelrc
Normal file
4
.babelrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"plugins": ["transform-object-rest-spread"],
|
||||
"presets": ["es2015", "react"]
|
||||
}
|
24
package.json
24
package.json
|
@ -9,8 +9,8 @@
|
|||
"deploy": "touch playground/.nojekyll && gh-pages -t -d playground -m \"Build for $(git log --pretty=format:%H -n1)\"",
|
||||
"lint": "eslint . --ext .js,.jsx",
|
||||
"start": "webpack-dev-server",
|
||||
"tap": "./node_modules/.bin/tap ./test/*.js",
|
||||
"test": "npm run lint && npm run build && npm run tap",
|
||||
"test": "npm run lint && npm run build && npm run unit",
|
||||
"unit": "jest",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
},
|
||||
"author": "Massachusetts Institute of Technology",
|
||||
|
@ -28,35 +28,51 @@
|
|||
"autoprefixer": "7.1.1",
|
||||
"babel-core": "^6.23.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-jest": "^20.0.3",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.22.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babel-preset-react": "^6.22.0",
|
||||
"classnames": "2.2.5",
|
||||
"css-loader": "0.28.3",
|
||||
"enzyme": "^2.8.2",
|
||||
"eslint": "^3.16.1",
|
||||
"eslint-config-import": "^0.13.0",
|
||||
"eslint-config-scratch": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-react": "^7.0.1",
|
||||
"gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
|
||||
"html-webpack-plugin": "2.28.0",
|
||||
"jest": "^20.0.4",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.bindall": "4.4.0",
|
||||
"lodash.defaultsdeep": "4.6.0",
|
||||
"minilog": "3.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"paper": "^0.11.4",
|
||||
"paper": "0.11.4",
|
||||
"postcss-import": "^10.0.0",
|
||||
"postcss-loader": "^2.0.5",
|
||||
"postcss-simple-vars": "^4.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "15.5.4",
|
||||
"react": "15.6.1",
|
||||
"react-dom": "15.5.4",
|
||||
"react-intl": "2.3.0",
|
||||
"react-redux": "5.0.5",
|
||||
"react-test-renderer": "^15.5.4",
|
||||
"redux": "3.6.0",
|
||||
"redux-mock-store": "^1.2.3",
|
||||
"redux-throttle": "0.1.1",
|
||||
"regenerator-runtime": "^0.10.5",
|
||||
"rimraf": "^2.6.1",
|
||||
"style-loader": "^0.18.0",
|
||||
"tap": "^10.2.0",
|
||||
"webpack": "^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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: ['scratch', 'scratch/es6', 'scratch/react'],
|
||||
extends: ['scratch', 'scratch/es6', 'scratch/react', 'import'],
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
rules: {
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-amd': 'error',
|
||||
'import/no-nodejs-modules': 'error'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,24 +3,27 @@ import React from 'react';
|
|||
import PaperCanvas from '../containers/paper-canvas.jsx';
|
||||
import BrushTool from '../containers/tools/brush-tool.jsx';
|
||||
import EraserTool from '../containers/tools/eraser-tool.jsx';
|
||||
import ToolTypes from '../tools/tool-types.js';
|
||||
|
||||
const PaintEditorComponent = props => (
|
||||
<div>
|
||||
<PaperCanvas
|
||||
canvasId={props.canvasId}
|
||||
tool={props.tool}
|
||||
/>
|
||||
<BrushTool canvasId={props.canvasId} />
|
||||
<EraserTool canvasId={props.canvasId} />
|
||||
</div>
|
||||
);
|
||||
class PaintEditorComponent extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<PaperCanvas
|
||||
ref={canvas => {
|
||||
this.canvas = canvas;
|
||||
}}
|
||||
tool={this.props.tool}
|
||||
/>
|
||||
<BrushTool canvas={this.canvas} />
|
||||
<EraserTool canvas={this.canvas} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PaintEditorComponent.propTypes = {
|
||||
canvasId: PropTypes.string.isRequired,
|
||||
tool: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
})
|
||||
tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
|
||||
};
|
||||
|
||||
|
||||
module.exports = PaintEditorComponent;
|
||||
export default PaintEditorComponent;
|
||||
|
|
|
@ -7,27 +7,25 @@ import {connect} from 'react-redux';
|
|||
|
||||
class PaintEditor extends React.Component {
|
||||
componentDidMount () {
|
||||
const onKeyPress = this.props.onKeyPress;
|
||||
document.onkeydown = function (e) {
|
||||
e = e || window.event;
|
||||
onKeyPress(e);
|
||||
};
|
||||
document.addEventListener('keydown', this.props.onKeyPress);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('keydown', this.props.onKeyPress);
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
onKeyPress, // eslint-disable-line no-unused-vars
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
<PaintEditorComponent
|
||||
canvasId="paper-canvas"
|
||||
tool={this.props.tool}
|
||||
/>
|
||||
<PaintEditorComponent {...props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PaintEditor.propTypes = {
|
||||
onKeyPress: PropTypes.func.isRequired,
|
||||
tool: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
})
|
||||
tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -43,7 +41,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
}
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PaintEditor);
|
||||
|
|
|
@ -4,13 +4,8 @@ import paper from 'paper';
|
|||
import ToolTypes from '../tools/tool-types.js';
|
||||
|
||||
class PaperCanvas extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
paper.setup('paper-canvas');
|
||||
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
|
||||
|
@ -24,25 +19,22 @@ class PaperCanvas extends React.Component {
|
|||
// Draw the view now:
|
||||
paper.view.draw();
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.tool !== this.props.tool && nextProps.tool instanceof ToolTypes) {
|
||||
// TODO switch tool
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
paper.remove();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<canvas id={this.props.canvasId} />
|
||||
<canvas
|
||||
ref={canvas => {
|
||||
this.canvas = canvas;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PaperCanvas.propTypes = {
|
||||
canvasId: PropTypes.string.isRequired,
|
||||
tool: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
})
|
||||
tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
|
||||
};
|
||||
|
||||
module.exports = PaperCanvas;
|
||||
export default PaperCanvas;
|
||||
|
|
|
@ -22,6 +22,7 @@ class BrushTool extends React.Component {
|
|||
}
|
||||
componentDidMount () {
|
||||
if (this.props.tool === BrushTool.TOOL_TYPE) {
|
||||
debugger;
|
||||
this.activateTool();
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +39,7 @@ class BrushTool extends React.Component {
|
|||
return false; // Logic only component
|
||||
}
|
||||
activateTool () {
|
||||
document.getElementById(this.props.canvasId)
|
||||
.addEventListener('mousewheel', this.onScroll);
|
||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||
|
||||
this.tool = new paper.Tool();
|
||||
this.blob.activateTool(false /* isEraser */, this.tool, this.props.brushToolState);
|
||||
|
@ -56,8 +56,7 @@ class BrushTool extends React.Component {
|
|||
this.tool.activate();
|
||||
}
|
||||
deactivateTool () {
|
||||
document.getElementById(this.props.canvasId)
|
||||
.removeEventListener('mousewheel', this.onScroll);
|
||||
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
||||
}
|
||||
onScroll (event) {
|
||||
if (event.deltaY < 0) {
|
||||
|
@ -69,7 +68,7 @@ class BrushTool extends React.Component {
|
|||
}
|
||||
render () {
|
||||
return (
|
||||
<div />
|
||||
<div>Brush Tool </div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +77,7 @@ BrushTool.propTypes = {
|
|||
brushToolState: PropTypes.shape({
|
||||
brushSize: PropTypes.number.isRequired
|
||||
}),
|
||||
canvasId: PropTypes.string.isRequired,
|
||||
canvas: PropTypes.element,
|
||||
changeBrushSize: PropTypes.func.isRequired,
|
||||
tool: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
|
@ -95,7 +94,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
}
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BrushTool);
|
||||
|
|
|
@ -38,16 +38,14 @@ class EraserTool extends React.Component {
|
|||
return false; // Logic only component
|
||||
}
|
||||
activateTool () {
|
||||
document.getElementById(this.props.canvasId)
|
||||
.addEventListener('mousewheel', this.onScroll);
|
||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||
|
||||
this.tool = new paper.Tool();
|
||||
this.blob.activateTool(true /* isEraser */, this.tool, this.props.eraserToolState);
|
||||
this.tool.activate();
|
||||
}
|
||||
deactivateTool () {
|
||||
document.getElementById(this.props.canvasId)
|
||||
.removeEventListener('mousewheel', this.onScroll);
|
||||
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
||||
this.blob.deactivateTool();
|
||||
}
|
||||
onScroll (event) {
|
||||
|
@ -60,13 +58,13 @@ class EraserTool extends React.Component {
|
|||
}
|
||||
render () {
|
||||
return (
|
||||
<div />
|
||||
<div>Eraser Tool </div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EraserTool.propTypes = {
|
||||
canvasId: PropTypes.string.isRequired,
|
||||
canvas: PropTypes.element,
|
||||
changeBrushSize: PropTypes.func.isRequired,
|
||||
eraserToolState: PropTypes.shape({
|
||||
brushSize: PropTypes.number.isRequired
|
||||
|
@ -86,7 +84,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
}
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(EraserTool);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const minilog = require('minilog');
|
||||
import minilog from 'minilog';
|
||||
minilog.enable();
|
||||
|
||||
module.exports = minilog('paint-editor');
|
||||
export default minilog('scratch-paint');
|
||||
|
|
|
@ -2,15 +2,12 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import PaintEditor from '..';
|
||||
import {Provider} from 'react-redux';
|
||||
import {createStore, applyMiddleware} from 'redux';
|
||||
import throttle from 'redux-throttle';
|
||||
import {createStore} from 'redux';
|
||||
import reducer from '../reducers/combine-reducers';
|
||||
|
||||
const appTarget = document.createElement('div');
|
||||
document.body.appendChild(appTarget);
|
||||
const store = applyMiddleware(
|
||||
throttle(300, {leading: true, trailing: true})
|
||||
)(createStore)(
|
||||
const store = createStore(
|
||||
reducer,
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||
);
|
||||
|
|
|
@ -22,4 +22,4 @@ reducer.changeBrushSize = function (brushSize) {
|
|||
};
|
||||
};
|
||||
|
||||
module.exports = reducer;
|
||||
export default reducer;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import toolReducer from './tools';
|
||||
import brushToolReducer from './brush-tool';
|
||||
import eraserToolReducer from './eraser-tool';
|
||||
|
||||
module.exports = combineReducers({
|
||||
tool: require('./tools'),
|
||||
brushTool: require('./brush-tool'),
|
||||
eraserTool: require('./eraser-tool')
|
||||
export default combineReducers({
|
||||
tool: toolReducer,
|
||||
brushTool: brushToolReducer,
|
||||
eraserTool: eraserToolReducer
|
||||
});
|
||||
|
|
|
@ -22,4 +22,4 @@ reducer.changeBrushSize = function (brushSize) {
|
|||
};
|
||||
};
|
||||
|
||||
module.exports = reducer;
|
||||
export default reducer;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const ToolTypes = require('../tools/tool-types');
|
||||
const log = require('../log/log');
|
||||
import ToolTypes from '../tools/tool-types';
|
||||
import log from '../log/log';
|
||||
|
||||
const CHANGE_TOOL = 'scratch-paint/tools/CHANGE_TOOL';
|
||||
const initialState = ToolTypes.BRUSH;
|
||||
|
@ -8,10 +8,10 @@ const reducer = function (state, action) {
|
|||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_TOOL:
|
||||
if (action.tool instanceof ToolTypes) {
|
||||
if (action.tool in ToolTypes) {
|
||||
return action.tool;
|
||||
}
|
||||
log.warn(`Warning: Tool type does not exist: ${action.tool}`);
|
||||
log.warn(`Tool type does not exist: ${action.tool}`);
|
||||
/* falls through */
|
||||
default:
|
||||
return state;
|
||||
|
@ -22,11 +22,8 @@ const reducer = function (state, action) {
|
|||
reducer.changeTool = function (tool) {
|
||||
return {
|
||||
type: CHANGE_TOOL,
|
||||
tool: tool,
|
||||
meta: {
|
||||
throttle: 30
|
||||
}
|
||||
tool: tool
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = reducer;
|
||||
export default reducer;
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
class ToolTypes {
|
||||
constructor (name) {
|
||||
this.name = name;
|
||||
}
|
||||
toString () {
|
||||
return `ToolTypes.${this.name}`;
|
||||
}
|
||||
}
|
||||
ToolTypes.BRUSH = new ToolTypes('BRUSH');
|
||||
ToolTypes.ERASER = new ToolTypes('ERASER');
|
||||
import keyMirror from 'keymirror';
|
||||
|
||||
module.exports = ToolTypes;
|
||||
const ToolTypes = keyMirror({
|
||||
BRUSH: null,
|
||||
ERASER: null
|
||||
});
|
||||
|
||||
export default ToolTypes;
|
||||
|
|
3
test/__mocks__/fileMock.js
Normal file
3
test/__mocks__/fileMock.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
// __mocks__/fileMock.js
|
||||
|
||||
module.exports = 'test-file-stub';
|
3
test/__mocks__/styleMock.js
Normal file
3
test/__mocks__/styleMock.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
// __mocks__/styleMock.js
|
||||
|
||||
module.exports = {};
|
|
@ -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();
|
||||
});
|
23
test/unit/tools-reducer.test.js
Normal file
23
test/unit/tools-reducer.test.js
Normal 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);
|
||||
});
|
Loading…
Reference in a new issue