Merge pull request #2 from fsih/addTool

Add PaperCanvas component
This commit is contained in:
DD Liu 2017-07-27 17:14:02 -04:00 committed by GitHub
commit 91ebc3c046
24 changed files with 287 additions and 74406 deletions

4
.babelrc Normal file
View file

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

View file

@ -1,3 +1,3 @@
node_modules/* node_modules/*
build/* dist/*
playground/ playground/

View file

@ -1,3 +1,3 @@
module.exports = { module.exports = {
extends: ['scratch', 'scratch/node'] extends: ['scratch', 'scratch/es6', 'scratch/node']
}; };

6
.gitignore vendored
View file

@ -7,4 +7,8 @@ npm-*
# Build # Build
playground/* playground/*
build/* dist/*
# Editors
/#*
*~

View file

@ -2,14 +2,15 @@
"name": "scratch-blobs", "name": "scratch-blobs",
"version": "0.1.0", "version": "0.1.0",
"description": "Graphical User Interface for the Scratch 3.0 paint editor, which is used to make and edit sprites for use in projects.", "description": "Graphical User Interface for the Scratch 3.0 paint editor, which is used to make and edit sprites for use in projects.",
"main": "./src/index.js", "main": "./dist/scratch-paint.js",
"scripts": { "scripts": {
"build": "npm run clean && webpack --progress --colors --bail", "build": "npm run clean && webpack --progress --colors --bail",
"clean": "rimraf ./build && mkdirp build", "clean": "rimraf ./dist && mkdirp dist && rimraf ./playground && mkdirp playground",
"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",
"test": "npm run lint && npm run build", "test": "npm run lint && npm run build && npm run unit",
"unit": "jest",
"watch": "webpack --progress --colors --watch" "watch": "webpack --progress --colors --watch"
}, },
"author": "Massachusetts Institute of Technology", "author": "Massachusetts Institute of Technology",
@ -27,31 +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",
"minilog": "3.1.0", "minilog": "3.1.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"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-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",
"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,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Scratch 3.0 Paint Editor</title>
</head>
<body>
<script type="text/javascript" src="lib.min.js"></script><script type="text/javascript" src="playground.js"></script></body>
</html>

22175
playground/lib.min.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

13
src/.eslintrc.js Normal file
View file

@ -0,0 +1,13 @@
module.exports = {
root: true,
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'
}
};

View file

@ -1,14 +1,16 @@
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import PaperCanvas from '../containers/paper-canvas.jsx';
import ToolTypes from '../tools/tool-types.js';
export default class PaintEditorComponent extends React.Component { const PaintEditorComponent = props => (
render () { <PaperCanvas
return ( tool={props.tool}
<div className="paint-editor"> />
BANANAS );
</div>
);
}
}
PaintEditorComponent.defaultProps = { PaintEditorComponent.propTypes = {
tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
}; };
export default PaintEditorComponent;

View file

@ -0,0 +1,47 @@
import PropTypes from 'prop-types';
import React from 'react';
import PaintEditorComponent from '../components/paint-editor.jsx';
import tools from '../reducers/tools';
import ToolTypes from '../tools/tool-types.js';
import {connect} from 'react-redux';
class PaintEditor extends React.Component {
componentDidMount () {
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 {...props} />
);
}
}
PaintEditor.propTypes = {
onKeyPress: PropTypes.func.isRequired,
tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
};
const mapStateToProps = state => ({
tool: state.tool
});
const mapDispatchToProps = dispatch => ({
onKeyPress: e => {
if (e.key === 'e') {
dispatch(tools.changeTool(ToolTypes.ERASER));
} else if (e.key === 'b') {
dispatch(tools.changeTool(ToolTypes.BRUSH));
}
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(PaintEditor);

View file

@ -0,0 +1,45 @@
import PropTypes from 'prop-types';
import React from 'react';
import paper from 'paper';
import ToolTypes from '../tools/tool-types.js';
class PaperCanvas extends React.Component {
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();
}
componentWillReceiveProps (nextProps) {
if (nextProps.tool !== this.props.tool) {
// TODO switch tool
}
}
componentWillUnmount () {
paper.remove();
}
render () {
return (
<canvas
ref={canvas => {
this.canvas = canvas;
}}
/>
);
}
}
PaperCanvas.propTypes = {
tool: PropTypes.oneOf(Object.keys(ToolTypes)).isRequired
};
export default PaperCanvas;

View file

@ -1,3 +1,3 @@
import PaintEditorComponent from './components/paint-editor.jsx'; import PaintEditor from './containers/paint-editor.jsx';
export default PaintEditorComponent; export default PaintEditor;

4
src/log/log.js Normal file
View file

@ -0,0 +1,4 @@
import minilog from 'minilog';
minilog.enable();
export default minilog('scratch-paint');

View file

@ -1,9 +1,18 @@
import React from 'react'; 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 {createStore} from 'redux';
import reducer from '../reducers/combine-reducers';
const appTarget = document.createElement('div'); const appTarget = document.createElement('div');
document.body.appendChild(appTarget); document.body.appendChild(appTarget);
ReactDOM.render( const store = createStore(
<PaintEditor />, reducer,
appTarget); window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
ReactDOM.render((
<Provider store={store}>
<PaintEditor />
</Provider>
), appTarget);

View file

@ -0,0 +1,6 @@
import {combineReducers} from 'redux';
import toolReducer from './tools';
export default combineReducers({
tool: toolReducer
});

29
src/reducers/tools.js Normal file
View file

@ -0,0 +1,29 @@
import ToolTypes from '../tools/tool-types';
import log from '../log/log';
const CHANGE_TOOL = 'scratch-paint/tools/CHANGE_TOOL';
const initialState = ToolTypes.BRUSH;
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case CHANGE_TOOL:
if (action.tool in ToolTypes) {
return action.tool;
}
log.warn(`Tool type does not exist: ${action.tool}`);
/* falls through */
default:
return state;
}
};
// Action creators ==================================
reducer.changeTool = function (tool) {
return {
type: CHANGE_TOOL,
tool: tool
};
};
export default reducer;

8
src/tools/tool-types.js Normal file
View file

@ -0,0 +1,8 @@
import keyMirror from 'keymirror';
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

@ -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);
});

View file

@ -1,29 +1,17 @@
var path = require('path'); const defaultsDeep = require('lodash.defaultsdeep');
var webpack = require('webpack'); const path = require('path');
const webpack = require('webpack');
// Plugins // Plugins
var HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
// PostCss // PostCss
var autoprefixer = require('autoprefixer'); const autoprefixer = require('autoprefixer');
var postcssVars = require('postcss-simple-vars'); const postcssVars = require('postcss-simple-vars');
var postcssImport = require('postcss-import'); const postcssImport = require('postcss-import');
module.exports = { const base = {
devServer: {
contentBase: path.resolve(__dirname, 'build'),
host: '0.0.0.0',
port: process.env.PORT || 8078
},
devtool: 'cheap-module-source-map', devtool: 'cheap-module-source-map',
entry: {
lib: ['react', 'react-dom'],
playground: './src/playground/playground.jsx'
},
output: {
path: path.resolve(__dirname, 'playground'),
filename: '[name].js'
},
externals: { externals: {
React: 'react', React: 'react',
ReactDOM: 'react-dom' ReactDOM: 'react-dom'
@ -71,10 +59,6 @@ module.exports = {
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'lib', name: 'lib',
filename: 'lib.min.js' filename: 'lib.min.js'
}),
new HtmlWebpackPlugin({
template: 'src/playground/index.ejs',
title: 'Scratch 3.0 Paint Editor'
}) })
].concat(process.env.NODE_ENV === 'production' ? [ ].concat(process.env.NODE_ENV === 'production' ? [
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
@ -83,3 +67,39 @@ module.exports = {
}) })
] : []) ] : [])
}; };
module.exports = [
// For the playground
defaultsDeep({}, base, {
devServer: {
contentBase: path.resolve(__dirname, 'playground'),
host: '0.0.0.0',
port: process.env.PORT || 8078
},
entry: {
lib: ['react', 'react-dom'],
playground: './src/playground/playground.jsx'
},
output: {
path: path.resolve(__dirname, 'playground'),
filename: '[name].js'
},
plugins: base.plugins.concat([
new HtmlWebpackPlugin({
template: 'src/playground/index.ejs',
title: 'Scratch 3.0 Paint Editor Playground'
})
])
}),
// For use as a library
defaultsDeep({}, base, {
entry: {
'lib': ['react', 'react-dom'],
'scratch-paint': './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
})
];