Browse Source

first react components; use babel; use strict

romanmatiasko 4 years ago
parent
commit
11eb779e77

+ 13 - 12
app.js

@@ -1,15 +1,16 @@
-var express = require('express');
-var path = require('path');
-var winston = require('winston');
-var bodyParser = require('body-parser');
-var cookieParser = require('cookie-parser');
-var favicon = require('serve-favicon');
-var logger = require('morgan');
-var routes = require('./routes/index');
-var staticPath =  path.join(__dirname,
-  process.env.NODE_ENV === 'development' ? 'build' : 'dist');
+'use strict';
 
-var app = express();
+const express = require('express');
+const path = require('path');
+const winston = require('winston');
+const bodyParser = require('body-parser');
+const cookieParser = require('cookie-parser');
+const favicon = require('serve-favicon');
+const logger = require('morgan');
+const routes = require('./routes/routes');
+const staticPath =  path.join(__dirname,
+  process.env.NODE_ENV === 'development' ? 'build' : 'dist');
+let app = express();
 
 app.set('views', path.join(__dirname, 'views'));
 app.set('view engine', 'jade');
@@ -22,7 +23,7 @@ app.use(express.static(staticPath));
 app.use('/', routes);
 
 app.use(function(req, res, next) {
-  var err = new Error('Not Found');
+  let err = new Error('Not Found');
   err.status = 404;
   next(err);
 });

+ 1 - 0
bin/www

@@ -1,5 +1,6 @@
 #!/usr/bin/env node
 
+require('babel/register');
 var app = require('../app');
 var debug = require('debug')('reti-chess:server');
 var io = require('../io');

+ 5 - 8
gulpfile.js

@@ -10,7 +10,7 @@ var gulp = require('gulp');
 var source = require('vinyl-source-stream');
 var browserify = require('browserify');
 var watchify = require('watchify');
-var reactify = require('reactify');
+var babelify = require('babelify');
 var gulpif = require('gulp-if');
 var uglify = require('gulp-uglify');
 var streamify = require('gulp-streamify');
@@ -27,17 +27,14 @@ var imagemin = require('gulp-imagemin');
 var dependencies = [
   'react',
   'react/addons',
-  'react-router',
-  'flux',
-  'eventemitter2',
   'immutable'
 ];
 
 var browserifyTask = function() {
 
   var appBundler = browserify({
-    entries: './src/js/app.js',
-    transform: [[reactify, {harmony: true}]],
+    entries: './src/js/index.js',
+    transform: [babelify],
     debug: IS_DEVELOPMENT,
     // required by watchify
     cache: {}, packageCache: {}, fullPaths: IS_DEVELOPMENT
@@ -52,7 +49,7 @@ var browserifyTask = function() {
     console.log('Building BROWSERIFY bundle');
     appBundler.bundle()
       .on('error', gutil.log)
-      .pipe(source('app.js'))
+      .pipe(source('index.js'))
       .pipe(gulpif(!IS_DEVELOPMENT, streamify(uglify())))
       .pipe(gulp.dest(IS_DEVELOPMENT ? './build/js/' : './dist/js/'))
       .pipe(notify(function() {
@@ -137,7 +134,7 @@ var imageminTask = function() {
       progressive: true,
       svgoPlugins: [{removeViewBox: false}]
     }))
-    .pipe(gulp.dest('./dist/img/'))
+    .pipe(gulp.dest('./' + (IS_DEVELOPMENT ? 'build' : 'dist') + '/img/'))
     .pipe(notify({
       message: function() {
         gutil.log(gutil.colors.green('IMAGES/SVG optimized.'));

+ 2 - 0
io.js

@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * Socket.IO
  */

+ 0 - 7
package.json

@@ -22,8 +22,6 @@
   "devDependencies": {
     "babelify": "^5.0.3",
     "browserify": "^9.0.3",
-    "eventemitter2": "^0.4.14",
-    "flux": "^2.0.1",
     "gulp": "^3.8.11",
     "gulp-autoprefixer": "^2.1.0",
     "gulp-cssmin": "^0.1.6",
@@ -35,12 +33,7 @@
     "gulp-streamify": "0.0.5",
     "gulp-uglify": "^1.1.0",
     "gulp-util": "^3.0.4",
-    "jest-cli": "^0.4.0",
-    "jstransform": "^10.0.0",
-    "map-stream": "0.0.5",
     "react": "^0.12.2",
-    "react-router": "^0.12.4",
-    "reactify": "^1.0.0",
     "socket.io-client": "^1.3.4",
     "vinyl-source-stream": "^1.0.0",
     "watchify": "2.4.0"

+ 0 - 33
public/javascripts/start.js

@@ -1,33 +0,0 @@
-$(function () {
-  var $token, $time, $increment;
-
-  $socket.on('created', function (data) {
-    $token = data.token;
-    $('#waiting').text('Wating for opponent to connect.');
-
-    $('#game-link').val($URL + '/play/' + $token + '/' + $time + '/' + $increment); // create game link
-    $('#game-link').click(function() {
-      $(this).select(); // when clicked, link is automatically selected for convenience
-    });
-  });
-
-  $socket.on('ready', function (data) {
-    document.location = $URL + '/play/' + $token + '/' + $time + '/' + $increment;
-  });
-
-  $socket.on('token-expired', function (data) {
-    $('#waiting').text('Game link has expired, generate a new one.');
-  });
-
-  $('#play').click(function (ev) {
-    var min = parseInt($('#minutes').val());
-    var sec = parseInt($('#seconds').val());
-    if (!isNaN(min) && min > 0 && min <= 50 && !isNaN(sec) && sec >= 0 && sec <= 50) {
-      $time = min;
-      $increment = sec;
-      $socket.emit('start');
-      $('#waiting').text('Generating game link').slideDown(400);
-      ev.preventDefault();
-    }
-  });
-});

+ 0 - 32
routes/index.js

@@ -1,32 +0,0 @@
-var express = require('express');
-var path = require('path');
-var fs = require('fs');
-var router = express.Router();
-
-router.get('/', function(req, res) {
-  res.render('index');
-});
-
-router.get('/about', function(req, res) {
-  res.render('about');
-});
-
-router.get('/play/:token/:time/:increment', function(req, res) {
-  res.render('play', {
-    'token': req.params.token,
-    'time': req.params.time,
-    'increment': req.params.increment
-  });
-});
-
-router.get('/logs', function(req, res) {
-  fs.readFile(path.join(__dirname, 'logs/games.log'), function(err, data) {
-    if (err) {
-      res.redirect('/');
-    }
-    res.set('Content-Type', 'text/plain');
-    res.send(data);
-  });
-});
-
-module.exports = router;

+ 38 - 0
routes/routes.js

@@ -0,0 +1,38 @@
+'use strict';
+
+const express = require('express');
+const path = require('path');
+const fs = require('fs');
+const React = require('react');
+const Index = require('../src/js/components/Index');
+let router = express.Router();
+
+router.get('/', (req, res) => {
+  res.render('index', {
+    content: React.renderToString(<Index />)
+  });
+});
+
+router.get('/about', (req, res) => {
+  res.render('about');
+});
+
+router.get('/play/:token/:time/:increment', (req, res) => {
+  res.render('play', {
+    'token': req.params.token,
+    'time': req.params.time,
+    'increment': req.params.increment
+  });
+});
+
+router.get('/logs', (req, res) => {
+  fs.readFile(path.join(__dirname, 'logs/games.log'), (err, data) => {
+    if (err) {
+      res.redirect('/');
+    }
+    res.set('Content-Type', 'text/plain');
+    res.send(data);
+  });
+});
+
+module.exports = router;

+ 2 - 2
src/css/_base.scss

@@ -101,7 +101,7 @@ p {
   font-size: 1rem;
 }
 
-h2 {
+h1 {
   font-family: 'Cherry Swash';
   font-weight: 400;
   font-size: 2rem;
@@ -110,7 +110,7 @@ h2 {
   margin-bottom: 0.5em;
 }
 
-h3 {
+h2 {
   font-weight: 600;
   font-size: 1.5rem;
   margin: 1em 0 0.5em 0;

+ 7 - 3
src/css/_index.scss

@@ -1,3 +1,8 @@
+.knight {
+  margin: 2em auto 0.5em;
+  display: block;
+}
+
 #create-game {
   width: 100%;
   height: 200px;
@@ -64,13 +69,12 @@
   }
 }
 
-p#waiting {
+#game-status {
   font-family: 'Cherry Swash';
   font-size: 1.275rem;
   color: darken($red, 5%);
   width: 100%;
   text-align: center;
-  margin: 15px 0px 0px 0px;
-  display: none;
+  margin: 1em 0;
   float: left;
 }

+ 0 - 1
src/css/_layout.scss

@@ -82,7 +82,6 @@ footer {
   width: 100%;
   line-height: 2em;
   text-align: center;
-  background: $grey;
   border-bottom: 4px solid darken($grey, 10%);
   box-shadow: inset 0 -4px darken($grey, 10%);
 

+ 0 - 1
src/css/main.scss

@@ -5,7 +5,6 @@
 
 .alpha { font-size: 1.125rem; }
 .center { text-align: center !important; }
-.margin-fifty { margin: 50px 0; }
 .last-origin { background: lighten($blue, 25%) !important; }
 .last-target { background: lighten($blue, 20%) !important; }
 

+ 0 - 6
src/js/app.js

@@ -1,6 +0,0 @@
-var io = require('socket.io-client');
-var URL = 'http://localhost:3000';
-var WS = URL;
-
-window.$socket = io.connect(WS);
-window.$URL = URL;

+ 55 - 0
src/js/components/CreateGameForm.js

@@ -0,0 +1,55 @@
+'use strict';
+
+const React = require('react/addons');
+
+const CreateGameForm = React.createClass({
+
+  propTypes: {
+    link: React.PropTypes.string.isRequired,
+    time: React.PropTypes.string.isRequired,
+    inc: React.PropTypes.string.isRequired,
+    onChangeForm: React.PropTypes.func.isRequired,
+    createGame: React.PropTypes.func.isRequired
+  },
+  mixins: [React.addons.PureRenderMixin],
+
+  render() {
+    return (
+      <form onSubmit={this.props.createGame}>
+        <fieldset>
+          <label>
+            <span>Minutes per side: </span>
+            <input
+              type="number"
+              name="time"
+              value={this.props.time}
+              onChange={this.props.onChangeForm}
+              min="1"
+              max="50"
+              required />
+          </label>
+          <label style={{paddingLeft: '2em'}}>
+            <span>Increment in seconds: </span>
+            <input
+              type="number"
+              name="inc"
+              value={this.props.inc}
+              onChange={this.props.onChangeForm}
+              min="0"
+              max="50"
+              required />
+          </label>
+        </fieldset>
+        <input
+          id="game-link"
+          type="text"
+          value={this.props.link || 'Game link will be generated here.'}
+          onClick={e => e.target.select()}
+          readOnly />
+        <button type="submit" className="button">Play</button>
+      </form>
+    );
+  }
+});
+
+module.exports = CreateGameForm;

+ 100 - 0
src/js/components/Index.js

@@ -0,0 +1,100 @@
+'use strict';
+
+const React = require('react');
+const CreateGameForm = require('./CreateGameForm');
+const io = require('../io');
+
+const Index = React.createClass({
+  
+  propTypes: {
+    io: React.PropTypes.object
+  },
+
+  getInitialState() {
+    return {
+      link: '',
+      hasExpired: false,
+      time: '30',
+      inc: '0'
+    };
+  },
+  componentDidMount() {
+    let io = this.props.io;
+
+    /**
+     * Socket.IO events
+     */
+    io.on('created', data => {
+      let {time, inc} = this.state;
+
+      this.setState({
+        link: `${document.location.origin}/play/${data.token}/${time}/${inc}`
+      });
+    });
+    io.on('ready', () => {
+      window.location = this.state.link;
+    });
+    io.on('token-expired', () => this.setState({hasExpired: true}));
+  },
+  render() {
+    return (
+      <div>
+        <img src="/img/knight.png"
+             width="122"
+             height="122"
+             className="knight" />
+        <h1>Reti Chess</h1>
+        <p style={{margin: '50px 0'}} className="center">
+          A lightweight real-time chess app build in Node, Express, 
+          Socket.IO and React.
+        </p>
+
+        <div id="create-game">
+          <CreateGameForm
+            link={this.state.link}
+            time={this.state.time}
+            inc={this.state.inc}
+            onChangeForm={this._onChangeForm}
+            createGame={this._createGame} />
+          <p id="game-status">
+            {this.state.link ?
+              'Waiting for opponent to connect'
+            :this.state.hasExpired ?
+              'Game link has expired, generate a new one'
+            :null}
+          </p>
+        </div>
+
+        <p>
+          Click the button to create a game. Send the link to your friend.
+          Once the link is opened your friend‘s browser, game should begin 
+          shortly. Colours are picked randomly by computer.
+        </p>
+        <p>
+          <a href="/about" className="alpha">Read more about Reti Chess</a>
+        </p>
+      </div>
+    );
+  },
+
+  _onChangeForm(e) {
+    this.setState({[e.target.name]: e.target.value});
+  },
+  _createGame(e) {
+    e.preventDefault();
+    let {time, inc} = this.state;
+    let isInvalid = [time, inc].some(val => {
+      val = parseInt(val, 10);
+      return isNaN(val) || val < 0 || val > 50;
+    });
+
+    if (isInvalid) {
+      // fallback for old browsers
+      return window.alert('Form is invalid.');
+    } else {
+      this.props.io.emit('start');
+    }
+  }
+});
+
+module.exports = Index;

+ 10 - 0
src/js/index.js

@@ -0,0 +1,10 @@
+'use strict';
+
+const React = require('react');
+const io = require('./io');
+const Index = require('./components/Index');
+
+React.render(
+  <Index io={io} />,
+  document.getElementById('container')
+);

+ 7 - 0
src/js/io.js

@@ -0,0 +1,7 @@
+'use strict';
+
+const io = require('socket.io-client');
+const URL = 'http://localhost:3000';
+const WS = URL;
+
+module.exports = io.connect(WS);

+ 2 - 21
views/index.jade

@@ -1,26 +1,7 @@
 extends layout
 
 block content
-  h1.knight
-  h2 Reti Chess
-  p.margin-fifty.center A lightweight real-time chess app built in Node.js, Express framework and Socket.IO
-
-  div#create-game
-    form(action='')
-      fieldset
-        label Minutes per side: 
-          input#minutes(type='number', value='30', min='1', max='50', required)
-        label(style='padding-left: 2em;') Increment in seconds:
-          input#seconds(type='number', value='0', min='0', max='50', required)
-      input#game-link(type='text', readonly, value='Game link will be generated here.')
-      button.button#play(type='submit') Play
-  
-    p#waiting Generating game link.
-  
-  p.center Click the button play to create a game. Send the link to your friend. Once the link is opened in your friend's browser, game should begin shortly. Colours are picked randomly by computer. 
-
-  p.center
-    a(href='/about', class='alpha') Read more about Reti Chess
+  #container.clearfix!=content
 
 block scripts
-  script(type='text/javascript', src='/js/start.js')
+  script(src='/js/index.js')

+ 4 - 8
views/layout.jade

@@ -39,19 +39,15 @@ html(lang='en')
         a#offer-decline.button.button--red(href='#') Decline
 
     #container-wrapper.clearfix
-      #container.clearfix
         block content
         
     footer
       #footer.clearfix
-        p.center 2013 - 2014. Reti Chess is available under 
-          a(href='http://opensource.org/licenses/MIT')  the MIT License (MIT)/. Fork me on 
-          a(href='http://github.com/romanmatiasko/reti-chess')  Github/.
+        p.center Distributed under 
+          a(href='http://opensource.org/licenses/MIT') the MIT License
+          | &nbsp;&nbsp;&bull;&nbsp;&nbsp;
+          a(href='http://github.com/romanmatiasko/reti-chess') Github repo
 
-    script(type='text/javascript', src='/js/jquery.js')
-    script(type='text/javascript', src='/js/jquery.stickyFooter.js')
-    script(type='text/javascript', src='/js/chess.js')
-    script(type='text/javascript', src='/js/app.js')
     - if (process.env.NODE_ENV === 'development')
       script(src='/js/vendor.js')
     block scripts

+ 2 - 0
winston.js

@@ -1,3 +1,5 @@
+'use strict';
+
 /** 
  * Winston logger
  */