]> git.wincent.com - hextrapolate.git/commitdiff
More setup, blinking light demo
authorGreg Hurrell <greg@hurrell.net>
Fri, 31 Jul 2015 18:23:37 +0000 (11:23 -0700)
committerGreg Hurrell <greg@hurrell.net>
Fri, 31 Jul 2015 18:25:28 +0000 (11:25 -0700)
This is a mess of things that should be committed separately, but rather
than do that I think I am just going to squash the history before
publishing. Or not. Possibly can't be bothered.

13 files changed:
.eslintrc
LICENSE
gulpfile.babel.js
index.html
package.json
src/App.js
src/Field.react.js [new file with mode: 0644]
src/__mocks__/.eslintrc [new file with mode: 0644]
src/__tests__/.eslintrc [new file with mode: 0644]
src/__tests__/stub-test.js [deleted file]
src/add.js
src/index.js
webpack.config.js

index 329eec0b297759a83d90c020122e076c073aedec..167d121d8b5dd34190d168432a17d532a1c4f0fb 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,21 +1,18 @@
 {
-  "ecmaFeatures": {
-    "jsx": true,
-    "modules": true
-  },
   "env": {
-    "browser": true,
-    "node": true
+    "browser": true
   },
   "parser": "babel-eslint",
-  "rules": {
-    "quotes": [2, "single"],
-    "strict": [2, "never"],
-    "react/jsx-uses-react": 2,
-    "react/jsx-uses-vars": 2,
-    "react/react-in-jsx-scope": 2
-  },
   "plugins": [
     "react"
-  ]
+  ],
+  "rules": {
+    // key: 0 = allow, 1 = warn, 2 = error
+    "comma-dangle": [0, "always-multiline"],
+    "no-process-exit": [0],
+    "no-underscore-dangle": [0],
+    "no-use-before-define": [0],
+    "quotes": [1, "single", "avoid-escape"],
+    "strict": [2, "global"]
+  }
 }
diff --git a/LICENSE b/LICENSE
index 44a03c28193599e7c6f6d6a99d464001267268fb..6efb4226013a9da988b85c56a4b7cac188b2118a 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2014 Dan Abramov
+Copyright (c) 2015-present Greg Hurrell
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
index 45503e4d8c125e2bcef681ab49ae8b4b403ba754..e1c31496e0a17c3608a057dcd5b62a656f614a51 100644 (file)
@@ -1,13 +1,86 @@
+// Copyright 2015-present Greg Hurrell. All rights reserved.
+// Licensed under the terms of the MIT license.
+
 import WebpackDevServer from 'webpack-dev-server';
 import config from './webpack.config.js';
+import eslint from 'gulp-eslint';
+import flow from 'gulp-flowtype';
 import gulp from 'gulp';
 import gutil from 'gulp-util';
+import mocha from 'gulp-spawn-mocha';
 import webpack from 'webpack';
 
+let watching = false;
+
+/**
+ * Ring the terminal bell.
+ */
+function ringBell() {
+  process.stderr.write('\x07');
+}
+
+/**
+ * Wrap a stream in an error-handler (until Gulp 4, needed to prevent "watch"
+ * task from dying on error).
+ */
+function wrap(stream) {
+  stream.on('error', error => {
+    gutil.log(gutil.colors.red(error.message));
+    gutil.log(error.stack);
+    if (watching) {
+      gutil.log(gutil.colors.yellow('[aborting]'));
+      stream.end();
+    } else {
+      gutil.log(gutil.colors.yellow('[exiting]'));
+      process.exit(1);
+    }
+    ringBell();
+  });
+  return stream;
+}
+
 gulp.task('default', ['webpack-dev-server']);
 
 gulp.task('build', ['webpack:build']);
 
+gulp.task('flow', ['typecheck']);
+
+gulp.task('js', ['build', 'lint', 'test', 'typecheck']);
+
+gulp.task('lint', () => (
+  gulp.src('src/**/*.js')
+    .pipe(eslint())
+    .pipe(eslint.format())
+));
+
+gulp.task('typecheck', () => {
+  return gulp.src('src/**/*.js', {read: false})
+    .pipe(gutil.noop());
+
+  // TODO: enable this once Flow groks ES2015/ES2016 features.
+  return gulp.src('src/**/*.js')
+    .pipe(wrap(flow()))
+});
+
+gulp.task('test', () => (
+  gulp.src(
+    [
+      'src/**/__mocks__/*.js',
+      'src/**/__tests__/*-test.js',
+    ],
+    {read: false}
+  )
+    .pipe(wrap(mocha({
+      opts: 'mocha/mocha.opts',
+      reporter: watching ? 'mocha/watch-reporter' : 'list',
+    })))
+));
+
+gulp.task('watch', () => {
+  watching = true;
+  gulp.watch('src/**/*.js', ['js']);
+});
+
 gulp.task('webpack:build', callback => {
   const myConfig = {
     ...config,
@@ -24,17 +97,23 @@ gulp.task('webpack:build', callback => {
 
   webpack(myConfig, (err, stats) => {
     if (err) {
+      ringBell();
       throw new gutil.PluginError('webpack:build', err);
     }
-    gutil.log('[webpack:build]', stats.toString({
-      colors: true
-    }));
+    if (stats.compilation.errors) {
+      ringBell();
+    }
+    if (!watching) {
+      gutil.log('[webpack:build]', stats.toString({
+        colors: true
+      }));
+    }
     callback();
   });
 });
 
 gulp.task('webpack-dev-server', callback => {
-  var myConfig = {
+  const myConfig = {
     ...config,
     debug: true,
   };
@@ -48,6 +127,7 @@ gulp.task('webpack-dev-server', callback => {
     },
   }).listen(3000, 'localhost', err => {
     if (err) {
+      ringBell();
       throw new gutil.PluginError('webpack-dev-server', err);
     }
     gutil.log('[webpack-dev-server]', 'http://localhost:3000');
index c4d9c490250137fc3ceb5e3a5ee0f7eefdf7d682..61fdb4d0de097a5310e88d532930ecf767eddf42 100644 (file)
@@ -1,6 +1,6 @@
 <html>
   <head>
-    <title>Sample App</title>
+    <title>Hextrapolate</title>
   </head>
   <body>
     <div id='root'>
index 5188b3df1e318027cb4702f4fc47ca219d140676..47dd2fa3427505a1b1332d4efa408b6a2f905762 100644 (file)
@@ -28,6 +28,9 @@
     "eslint-plugin-react": "^2.3.0",
     "expect": "^1.8.0",
     "gulp": "^3.9.0",
+    "gulp-eslint": "^0.15.0",
+    "gulp-flowtype": "^0.4.7",
+    "gulp-spawn-mocha": "^2.2.1",
     "gulp-util": "^3.0.6",
     "mocha": "^2.2.5",
     "react-hot-loader": "^1.2.7",
index 054537268bac216d83e95c61c571566e526bacb3..41aedb5eef156bddced99e76b1f09b39195273f9 100644 (file)
@@ -1,9 +1,63 @@
-import React, { Component } from 'react';
+/**
+ * Copyright 2015-present Greg Hurrell. All rights reserved.
+ * Licensed under the terms of the MIT license.
+ *
+ * @flow
+ */
+
+'use strict';
+
+import React from 'react';
+import Field from './Field.react';
+
+export default class App extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      value: '0',
+    };
+  }
+
+  _onValueChange = (value: string) => {
+    this.setState({value});
+  }
 
-export default class App extends Component {
   render() {
     return (
-      <h1>Hello, world.</h1>
+      <div>
+        <h1>Hextrapolate</h1>
+        <label>
+          Hexadecimal
+          <Field
+            base={16}
+            onValueChange={this._onValueChange}
+            value={this.state.value}
+          />
+        </label>
+        <label>
+          Decimal
+          <Field
+            onValueChange={this._onValueChange}
+            value={this.state.value}
+          />
+        </label>
+        <label>
+          Octal
+          <Field
+            base={8}
+            onValueChange={this._onValueChange}
+            value={this.state.value}
+          />
+        </label>
+        <label>
+          Binary
+          <Field
+            base={2}
+            onValueChange={this._onValueChange}
+            value={this.state.value}
+          />
+        </label>
+      </div>
     );
   }
 }
diff --git a/src/Field.react.js b/src/Field.react.js
new file mode 100644 (file)
index 0000000..77dbf0b
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright 2015-present Greg Hurrell. All rights reserved.
+ * Licensed under the terms of the MIT license.
+ *
+ * @flow
+ */
+
+'use strict';
+
+import React from 'react';
+
+const DIGITS = '0123456789abcdef';
+
+/**
+ * Convert from canonical (hexadecimal) value to `base`.
+ */
+function fromValue(value: string, base: number): string {
+  if (base === 16) {
+    return value;
+  }
+  for (let i = 0; i < value.length - 1; i++) {
+    const digit = value[value.length - 1 - i]; // eslint-disable-line no-unused-vars
+    // TODO: finish this
+  }
+  return value;
+}
+
+/**
+ * Convert from `base` value to canonical (hexadecimal) value.
+ */
+function toValue(value: string, base: number): string {
+  if (base === 16) {
+    return value;
+  }
+  return value;
+}
+
+export default class Field extends React.Component {
+  static propTypes = {
+    base: React.PropTypes.number,
+    onValueChange: React.PropTypes.func.required,
+    value: React.PropTypes.string.required,
+  };
+  static defaultProps = {
+    base: 10,
+  };
+
+  constructor(props) {
+    super(props);
+    if (props.base < 2 || props.base > DIGITS.length) {
+      throw new Error(
+        `base prop must be between 2..${DIGITS.length}`
+      );
+    }
+  }
+
+  _isValid(value: string): boolean {
+    const validator = new RegExp(
+      `^[${DIGITS.slice(0, this.props.base)}]*$`
+    );
+    return validator.test(value.trim().toLowerCase());
+  }
+
+  _onChange = event => {
+    const value = event.currentTarget.value;
+    if (this._isValid(value)) {
+      this.props.onValueChange(toValue(value, this.props.base));
+    }
+  }
+
+  render() {
+    return (
+      <input
+        onChange={this._onChange}
+        type="text"
+        value={fromValue(this.props.value, this.props.base)}
+      />
+    );
+  }
+}
diff --git a/src/__mocks__/.eslintrc b/src/__mocks__/.eslintrc
new file mode 100644 (file)
index 0000000..1a863e8
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "env": {
+    "jasmine": true,
+    "node": true
+  },
+  "globals": {
+    "sinon": false
+  }
+}
diff --git a/src/__tests__/.eslintrc b/src/__tests__/.eslintrc
new file mode 100644 (file)
index 0000000..1a863e8
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "env": {
+    "jasmine": true,
+    "node": true
+  },
+  "globals": {
+    "sinon": false
+  }
+}
diff --git a/src/__tests__/stub-test.js b/src/__tests__/stub-test.js
deleted file mode 100644 (file)
index 058ace2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-// TODO: replace with actual test files
index 43656fb2bc953927d5c4e1fecdf8cfe308721067..fe31aa673c601327901e5fc490ad133cd8a0fb0f 100644 (file)
@@ -5,6 +5,8 @@
  * @flow
  */
 
+'use strict';
+
 /**
  * Strips the leading prefix from `number` in `base` and returns the remaining
  * part of the string.
index 0b8688347b27d639b5a414e5e641b2ec3701f730..dd6823f76010bdb42eec442b9d7a645c65751f1f 100644 (file)
@@ -1,3 +1,12 @@
+/**
+ * Copyright 2015-present Greg Hurrell. All rights reserved.
+ * Licensed under the terms of the MIT license.
+ *
+ * @flow
+ */
+
+'use strict';
+
 import React from 'react';
 import App from './App';
 
index 8ed3af26e0f8b9a505d395e0eaf436bafa5a4728..b3bf729799a949325d1b9d999ebd8ca2ecc9e649 100644 (file)
@@ -1,3 +1,10 @@
+/**
+ * Copyright 2015-present Greg Hurrell. All rights reserved.
+ * Licensed under the terms of the MIT license.
+ */
+
+'use strict';
+
 var path = require('path');
 var webpack = require('webpack');