diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..763a6ee --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +js/build/* +bower_components/ +node_modules/ + diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..bc54157 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,216 @@ +module.exports = { + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true, + "mocha": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": [ + "react" + ], + "settings": { + "react": { + "pragma": "React", // Pragma to use, default to "React" + "version": "15.0" // React version, default to the latest React stable release + } + }, + "rules": { + "accessor-pairs": "error", + "array-bracket-spacing": ["warn", "never"], + "array-callback-return": "error", + "block-scoped-var": "error", + "block-spacing": ["error", "always"], + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "camelcase": ["off", { "properties": "always" }], + "comma-dangle": "error", + "comma-spacing": ["error", { + "before": false, + "after": true + }], + "comma-style": ["error", "last", { + "exceptions": {} + }], + "complexity": ["warn", 3], + "computed-property-spacing": ["error", "never"], + "consistent-this": "off", + "consistent-return": "error", + "curly": "error", + "default-case": "error", + "dot-location": ["error", "property"], + "dot-notation": "off", + "eol-last": "error", + "eqeqeq": ["error", "smart"], + "func-names": "off", + "func-style": ["off", "expression"], + "guard-for-in": "error", + "indent": ["error", 2], + "init-declarations": "off", + "key-spacing": ["error", { + "beforeColon": false, + "afterColon": true + }], + "keyword-spacing": ["error", { + "before": false, + "after": true, + "overrides": { + "else": { "before": true }, + "throw": { "before": true } + } + }], + "linebreak-style": ["error", "unix"], + "lines-around-comment": "error", + "max-depth": ["warn", 4], + "max-len": ["error", 80, 4, { "ignoreUrls": true }], + "max-nested-callbacks": ["warn", 3], + "max-statements-per-line": ["warn", { + "max": 3 + }], + "new-cap": "error", + "new-parens": "error", + "newline-after-var": ["off", "always"], + "newline-before-return": "warn", + "newline-per-chained-call": ["error", { + "ignoreChainWithDepth": 3 + }], + "no-alert": "warn", + "no-array-constructor": "error", + "no-bitwise": "warn", + "no-catch-shadow": "error", + "no-continue": "warn", + "no-underscore-dangle": ["error", { + "allowAfterThis": true + }], + "no-inline-comments": "warn", + "no-extra-semi": "error", + "no-extra-boolean-cast": "error", + "no-extra-parens": "error", + "no-ex-assign": "error", + "no-fallthrough": "warn", + "no-floating-decimal": "error", + "no-func-assign": "error", + "no-inner-declarations": "warn", + "no-implicit-coercion": "error", + "no-implicit-globals": "warn", + "no-implied-eval": "error", + "no-invalid-this": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": ["warn", { + "ignoreArrayIndexes": true, + "detectObjects": true + }], + "no-mixed-spaces-and-tabs": "error", + "no-multiple-empty-lines": ["error", { + "max": 2 + }], + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-negated-condition": "warn", + "no-negated-in-lhs": "warn", + "no-native-reassign": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-param-reassign": "error", + "no-plusplus": ["warn", { + "allowForLoopAfterthoughts": true + }], + "no-proto": "error", + "no-labels": "error", + "no-obj-calls": "error", + "no-redeclare": "error", + "no-restricted-globals": "off", + "no-return-assign": "error", + "no-script-url": "error", + "no-self-assign": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": ["error", { + "builtinGlobals": false, + "hoist": "functions", + "allow": [] + }], + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-spaced-func": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef": "error", + "no-undef-init": "error", + "no-undefined": "error", + "no-unexpected-multiline": "error", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": ["error", { + "allowTernary": true, + "allowShortCircuit": true + }], + "no-unreachable": "error", + "no-unused-labels": "error", + "no-unused-vars": "warn", + "no-use-before-define": ["error", { + "functions": false, + "classes": false + }], + "no-useless-call": "error", + "no-useless-concat": "error", + "no-useless-escape": "error", + "no-var": "warn", + "no-void": "error", + "no-warning-comments": "off", + "no-whitespace-before-property": "error", + "no-with": "error", + "object-curly-spacing": ["error", "always"], + "one-var": "off", + "one-var-declaration-per-line": ["warn", "always"], + "operator-assignment": ["warn", "always"], + "operator-linebreak": ["error", "after"], + "padded-blocks": ["off", "never"], + "quote-props": ["error", "consistent-as-needed", { + "keywords": true + }], + "quotes": ["error", "double"], + "radix": ["error", "as-needed"], + "react/jsx-uses-vars": 1, + "spaced-comment": ["error", "always"], + "semi": ["error", "always"], + "semi-spacing": ["warn", { + "before": false, "after": true + }], + "sort-vars": ["off", { + "ignoreCase": true + }], + "space-before-blocks": ["error", { + "functions": "always", + "keywords": "always" + }], + "space-infix-ops": "error", + "space-in-parens": ["error", "never"], + "space-unary-ops": "error", + "use-isnan": "warn", + "wrap-iife": ["error", "any"], + "wrap-regex": "error", + "valid-typeof": "warn", + "vars-on-top": "error", + "yoda": ["error", "never", { + "exceptRange": true + }] + } +}; + diff --git a/.gitignore b/.gitignore index 93be39e..8ec8e19 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,4 @@ sftp-config.json ### others js/build/ - +bower_components/ diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..edf6fc4 --- /dev/null +++ b/bower.json @@ -0,0 +1,20 @@ +{ + "name": "sphero-maze-editor", + "description": "", + "main": "index.js", + "authors": [ + "comozilla" + ], + "license": "MIT", + "homepage": "https://github.com/comozilla/SpheroMazeEditor", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "font-awesome": "^4.6.2" + } +} diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..00c3b07 --- /dev/null +++ b/css/style.css @@ -0,0 +1,44 @@ +body { + margin: 0; + overflow: hidden; +} + +#block-field { + width: 100vw; + height: 100vh; + text-align: center; + margin: 0; + padding: 10px; +} +#block-field li { + float: left; + width: 80px; + height: 80px; + list-style-type: none; + text-align: center; + line-height: 80px; + text-align: center; + background-color: #ecf0f1; + position: relative; + margin-left: 40px; + cursor: pointer; + border-radius: 20px; +} + + +#block-field li:after { + content: ""; + position: absolute; + margin-top: -50%; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-left: 20px solid #ecf0f1; + right: -30px; + top: 70px; +} +#block-field li:first-child { + margin-left: 0; +} +#block-field li:last-child:after { + display: none; +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..4c46d25 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,50 @@ +"use strict"; + +const gulp = require("gulp"); +const gutil = require("gulp-util"); +const webpack = require("webpack"); +const minimist = require("minimist"); +const browserSync = require("browser-sync"); + +const config = require("./webpack.config.js"); + +gulp.task("webpack", function() { + const env = minimist(process.argv.slice(2)); + let options = Object.create(config); + + if (env["min"]) { + options.output.filename = "./js/build/bundle.min.js"; + options.plugins.push(new webpack.optimize.UglifyJsPlugin()); + } + + if (env["watch"] || env["browser-sync"]) { + options.watch = true; + } + + if (env["browser-sync"]) { + browserSync({ + server: { + baseDir: "./", + index: "index.html" + } + }); + } + + webpack(options, function(err, stats) { + if (err) { + throw new gutil.PluginError("webpack", err); + } + gutil.log("[webpack]", stats.toString()); + if (env["browser-sync"]) { + browserSync.reload(); + } + }); +}); + +// Gulp コマンド +// readme.mdに書いてもいいが、開発時はgulpをグローバルにインストールしたくないため、 +// npm run から叩くので、ここに書いておく。 +// gulp webpack -> 普通に1回ビルド +// 引数: +// --min : UglifyJsPluginをかける。出力はbundle.min.jsなので注意 +// --browser-sync : browser-syncで監視する。--watchもされる。 diff --git a/js/action-creators/BlockActions.js b/js/action-creators/BlockActions.js new file mode 100644 index 0000000..aaef79d --- /dev/null +++ b/js/action-creators/BlockActions.js @@ -0,0 +1,14 @@ +import AppDispatcher from "../dispatcher/AppDispatcher"; +import BlockConstants from "../constants/BlockConstants"; + +const BlockActions = { + append: function(blockDetails) { + AppDispatcher.handleBlockAction({ + actionType: BlockConstants.BLOCK_APPEND, + block: blockDetails + }); + } +}; + +export default BlockActions; + diff --git a/js/components/Appender.react.jsx b/js/components/Appender.react.jsx new file mode 100644 index 0000000..02fbb90 --- /dev/null +++ b/js/components/Appender.react.jsx @@ -0,0 +1,20 @@ +import React from "react"; + +import BlockActions from "../action-creators/BlockActions"; + +export default class Appender extends React.Component { + render() { + return ( +
  • + +
  • + ); + } + _onClick() { + BlockActions.append({ + blockName: "move", + icon: "fa-arrows", + color: "#8e44ad" + }); + } +} diff --git a/js/components/Block.react.jsx b/js/components/Block.react.jsx new file mode 100644 index 0000000..8d0ed3b --- /dev/null +++ b/js/components/Block.react.jsx @@ -0,0 +1,21 @@ +import React from "react"; + +export default class Block extends React.Component { + constructor(props) { + super(props); + } + static get propTypes() { + return { + itemColor: React.PropTypes.string.isRequired, + icon: React.PropTypes.string.isRequired, + attributes: React.PropTypes.arrayOf(React.PropTypes.object) + }; + } + render() { + return ( +
  • + +
  • + ); + } +} diff --git a/js/components/BlockField.react.jsx b/js/components/BlockField.react.jsx new file mode 100644 index 0000000..8079287 --- /dev/null +++ b/js/components/BlockField.react.jsx @@ -0,0 +1,44 @@ +import React from "react"; + +import BlockStore from "../stores/BlockStore"; +import Block from "./Block.react.jsx"; +import Appender from "./Appender.react.jsx"; + +function getBlockState() { + return { + blocks: BlockStore.getAll() + }; +} +export default class BlockField extends React.Component { + constructor(prop) { + super(prop); + this.state = getBlockState(); + } + componentDidMount() { + BlockStore.addChangeListener(this._onChange.bind(this)); + } + componentWillUnmount() { + BlockStore.removeChangeListener(this._onChange.bind(this)); + } + render() { + let blocks = []; + this.state.blocks.forEach(blockDetail => { + blocks.push( + + ); + }); + + return ( + + ); + } + _onChange() { + this.setState(getBlockState()); + } +} diff --git a/js/constants/BlockConstants.js b/js/constants/BlockConstants.js new file mode 100644 index 0000000..d4e4e95 --- /dev/null +++ b/js/constants/BlockConstants.js @@ -0,0 +1,6 @@ +const keyMirror = require("keymirror"); + +export default keyMirror({ + BLOCK_APPEND: null +}); + diff --git a/js/dispatcher/AppDispatcher.js b/js/dispatcher/AppDispatcher.js new file mode 100644 index 0000000..2ee4ba8 --- /dev/null +++ b/js/dispatcher/AppDispatcher.js @@ -0,0 +1,14 @@ +const Dispatcher = require("flux").Dispatcher; +const assign = require("object-assign"); + +const AppDispatcher = assign(new Dispatcher(), { + handleBlockAction: function(action) { + this.dispatch({ + source: "BLOCK_ACTION", + action: action + }); + } +}); + +export default AppDispatcher; + diff --git a/js/main.js b/js/main.jsx similarity index 54% rename from js/main.js rename to js/main.jsx index 8760b1f..068dcb9 100644 --- a/js/main.js +++ b/js/main.jsx @@ -1,17 +1,14 @@ -import React from "react"; -import ReactDOM from "react-dom"; - -class HelloWorld extends React.Component { - render() { - return ( - Hello, World! - ); - } -} - -document.addEventListener("DOMContentLoaded", function() { - ReactDOM.render( - , - document.getElementById("app") - ); -}); +import React from "react"; +import ReactDOM from "react-dom"; + +import "../css/style.css"; +import "font-awesome"; + +import BlockField from "./components/BlockField.react.jsx"; + +document.addEventListener("DOMContentLoaded", function() { + ReactDOM.render( + , + document.getElementById("app") + ); +}); diff --git a/js/stores/BlockStore.js b/js/stores/BlockStore.js new file mode 100644 index 0000000..024c687 --- /dev/null +++ b/js/stores/BlockStore.js @@ -0,0 +1,50 @@ +const assign = require("object-assign"); +const EventEmitter = require("events").EventEmitter; + +import AppDispatcher from "../dispatcher/AppDispatcher"; +import BlockConstants from "../constants/BlockConstants"; + +const CHANGE_EVENT = "change"; + +let BlockStore; +let blockInterface; + +function BlockInterface() { + this.blocks = []; +} +BlockInterface.prototype.append = function (blockDetails) { + this.blocks.push(blockDetails); +}; + +blockInterface = new BlockInterface(); +BlockStore = assign({}, EventEmitter.prototype, { + getAll: function() { + return blockInterface.blocks; + }, + emitChange: function() { + this.emit(CHANGE_EVENT); + }, + addChangeListener: function(callback) { + this.on(CHANGE_EVENT, callback); + }, + removeChangeListener: function(callback) { + this.removeListener(CHANGE_EVENT, callback); + }, + dispatcherIndex: AppDispatcher.register(function(payload) { + let action = payload.action; + + switch (action.actionType) { + case BlockConstants.BLOCK_APPEND: + blockInterface.append(action.block); + BlockStore.emitChange(); + break; + default: + break; + } + + return true; + }) +}); + +export default BlockStore; + diff --git a/package.json b/package.json index aadf8c3..12cbabb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,11 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "webpack" + "build": "gulp webpack", + "build:min": "gulp webpack --min", + "build:watch": "gulp webpack --watch", + "build:browser-sync": "gulp webpack --browser-sync", + "lint": "eslint --fix webpack.config.js gulpfile.js js/**/*.jsx js/**/*.js" }, "repository": { "type": "git", @@ -18,6 +22,8 @@ }, "homepage": "https://github.com/comozilla/SpheroMazeEditor#readme", "dependencies": { + "flux": "^2.1.1", + "keymirror": "^0.1.1", "react": "^15.0.2", "react-dom": "^15.0.2" }, @@ -26,6 +32,16 @@ "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", + "browser-sync": "^2.12.5", + "css-loader": "^0.23.1", + "eslint": "^2.9.0", + "eslint-plugin-react": "^5.0.1", + "file-loader": "^0.8.5", + "flux": "^2.1.1", + "gulp": "^3.9.1", + "gulp-util": "^3.0.7", + "minimist": "^1.2.0", + "style-loader": "^0.13.1", "webpack": "^1.13.0" } } diff --git a/webpack.config.js b/webpack.config.js index 69c55e8..44bf7bd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,23 +1,40 @@ -const webpack = require("webpack"); - -module.exports = { - cache: true, - entry: "./js/main.js", - output: { - path: __dirname, - filename: "./js/build/bundle.js" - }, - module: { - loaders: [ - { - test: /\.js$/, - exclude: /(node_modules|bower_components)/, - loader: "babel", - query: { - cacheDirectory: true, - presets: ["react", "es2015"] - } - } - ] - } -}; +const webpack = require("webpack"); + +module.exports = { + cache: true, + entry: "./js/main.jsx", + output: { + path: __dirname, + filename: "./js/build/bundle.js" + }, + module: { + loaders: [ + { + test: /\.(js|jsx)$/, + exclude: /(node_modules|bower_components)/, + loader: "babel", + query: { + cacheDirectory: true, + presets: ["react", "es2015"] + } + }, + { test: /\.css$/, loader: "style-loader!css-loader" }, + // 下のものは、url-loaderでやると1ファイルにまとまっていいが、 + // font-awesomeが特別な種類のフォントを使っている問題でまとめられないからfile-loaderでやっている + { + test: /\.(ttf|eot|svg|woff2|woff)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: "file?name=js/build/[path][name].[ext]" + } + ] + }, + resolve: { + modulesDirectories: ["web_modules", "node_modules", "bower_components"], + alias: { + "font-awesome": "font-awesome/css/font-awesome.css" + } + }, + plugins: [new webpack.ResolverPlugin( + new webpack.ResolverPlugin + .DirectoryDescriptionFilePlugin("bower.json", ["main"]) + )] +};