Browse Source

add modal component, chess clock countdown

romanmatiasko 9 years ago
parent
commit
f6c5e3a399

+ 1 - 0
app.js

@@ -30,6 +30,7 @@ app.use(function(req, res, next) {
 
 
 app.use(function(err, req, res, next) {
 app.use(function(err, req, res, next) {
   res.status(err.status || 500);
   res.status(err.status || 500);
+  console.log(err);
   res.render('error', {
   res.render('error', {
     message: err.message,
     message: err.message,
     error: app.get('env') === 'development' ? err : {}
     error: app.get('env') === 'development' ? err : {}

+ 5 - 5
io.js

@@ -70,8 +70,8 @@ io.sockets.on('connection', function (socket) {
       'id': socket.id,
       'id': socket.id,
       'socket': socket,
       'socket': socket,
       'color': color,
       'color': color,
-      'time': data.time - data.increment + 1,
-      'increment': data.increment
+      'time': data.time - data.inc + 1,
+      'inc': data.inc
     });
     });
 
 
     game.creator.emit('ready', {});
     game.creator.emit('ready', {});
@@ -145,8 +145,8 @@ io.sockets.on('connection', function (socket) {
     if (data.token in games) {
     if (data.token in games) {
 
 
       for(var j in games[data.token].players) {
       for(var j in games[data.token].players) {
-        games[data.token].players[j].time = data.time - data.increment + 1;
-        games[data.token].players[j].increment = data.increment;
+        games[data.token].players[j].time = data.time - data.inc + 1;
+        games[data.token].players[j].inc = data.inc;
         games[data.token].players[j].color = games[data.token].players[j].color === 'black' ? 'white' : 'black';
         games[data.token].players[j].color = games[data.token].players[j].color === 'black' ? 'white' : 'black';
       }
       }
 
 
@@ -198,7 +198,7 @@ function runTimer(color, token, socket) {
     if (player.socket === socket && player.color === color) {
     if (player.socket === socket && player.color === color) {
 
 
       clearInterval(games[token].interval);
       clearInterval(games[token].interval);
-      games[token].players[i].time += games[token].players[i].increment;
+      games[token].players[i].time += games[token].players[i].inc;
 
 
       return games[token].interval = setInterval(function() {
       return games[token].interval = setInterval(function() {
         games[token].players[i].time -= 1;
         games[token].players[i].time -= 1;

+ 5 - 1
routes/routes.js

@@ -19,7 +19,11 @@ router.get('/about', (req, res) => {
 });
 });
 
 
 router.get('/play/:token/:time/:inc', (req, res) => {
 router.get('/play/:token/:time/:inc', (req, res) => {
-  let params = [req.params.token, req.params.time, req.params.inc];
+  let params = [
+    req.params.token,
+    req.params.time,
+    req.params.inc
+  ];
 
 
   res.render('play', {
   res.render('play', {
     content: React.renderToString(<GameInterface params={params} />)
     content: React.renderToString(<GameInterface params={params} />)

+ 1 - 0
src/css/_base.scss

@@ -82,6 +82,7 @@ body, html {
   font-size: 16px;
   font-size: 16px;
   line-height: 1.5;
   line-height: 1.5;
   font-weight: 400;
   font-weight: 400;
+  height: 100%;
 }
 }
 
 
 a { 
 a { 

+ 15 - 11
src/css/_layout.scss

@@ -52,7 +52,7 @@ input, button {
   }
   }
 }
 }
 
 
-a.btn {
+a.btn, .btn.ok {
   width: 120px;
   width: 120px;
   line-height: 46px;
   line-height: 46px;
   text-align: center;
   text-align: center;
@@ -100,15 +100,22 @@ footer {
 }
 }
 
 
 .modal-mask {
 .modal-mask {
-  display: none;
+  opacity: 1;
+  visibility: visible;
   cursor: pointer;
   cursor: pointer;
   position: fixed;
   position: fixed;
   top: 0;
   top: 0;
+  left: 0;
   height: 100%;
   height: 100%;
   width: 100%;
   width: 100%;
-  background: #444;
   background: rgba(0, 0, 0, .7);
   background: rgba(0, 0, 0, .7);
   z-index: 10;
   z-index: 10;
+  transition: all 0.2s ease-in-out;
+
+  &.hidden {
+    opacity: 0;
+    visibility: hidden;
+  }
 
 
   > p {
   > p {
     color: #eee;
     color: #eee;
@@ -142,17 +149,14 @@ footer {
     position: absolute;
     position: absolute;
     bottom: 1em;
     bottom: 1em;
 
 
+    &.ok {
+      left: 50%;
+      margin-left: -60px;
+    }
+
     &:active {
     &:active {
       top: auto;
       top: auto;
       bottom: calc(1em + 1px);
       bottom: calc(1em + 1px);
     }
     }
   }
   }
-}
-
-#offer-accept {
-  right: 4em;
-}
-
-#offer-decline {
-  left: 4em;
 }
 }

+ 21 - 7
src/js/components/Clock.js

@@ -17,17 +17,20 @@ const Clock = React.createClass({
     return {
     return {
       white: time * 60,
       white: time * 60,
       black: time * 60,
       black: time * 60,
-      inc: inc
+      inc: inc,
+      countdown: null
     };
     };
   },
   },
   componentDidMount() {
   componentDidMount() {
     let io = this.props.io;
     let io = this.props.io;
 
 
     io.on('countdown', data => this.setState({
     io.on('countdown', data => this.setState({
-      [data.color]: data.time
+      [data.color]: data.time,
+      countdown: data.color
     }));
     }));
 
 
     io.on('countdown-gameover', data => {
     io.on('countdown-gameover', data => {
+      this.setState({countdown: null});
       // GameStore.gameOver({
       // GameStore.gameOver({
       //   timeout: true,
       //   timeout: true,
       //   winner: data.color === 'black' ? 'White' : 'Black'
       //   winner: data.color === 'black' ? 'White' : 'Black'
@@ -37,8 +40,14 @@ const Clock = React.createClass({
   render() {
   render() {
     return (
     return (
       <ul id="clock">
       <ul id="clock">
-        <Timer color="white" time={this.state.white} />
-        <Timer color="black" time={this.state.black} />
+        <Timer
+          color="white"
+          time={this.state.white}
+          countdown={this.state.countdown} />
+        <Timer
+          color="black"
+          time={this.state.black}
+          countdown={this.state.countdown} />
       </ul>
       </ul>
     );
     );
   }
   }
@@ -49,11 +58,16 @@ const Timer = React.createClass({
   mixins: [PureRenderMixin],
   mixins: [PureRenderMixin],
 
 
   render() {
   render() {
-    let min = Math.floor(this.props.time / 60);
-    let sec = this.props.time % 60;
+    let {time, color, countdown} = this.props;
+    let min = Math.floor(time / 60);
+    let sec = time % 60;
     let timeLeft = `${min}:${sec < 10 ? '0' + sec : sec}`;
     let timeLeft = `${min}:${sec < 10 ? '0' + sec : sec}`;
 
 
-    return <li className={this.props.color}>{timeLeft}</li>;
+    return (
+      <li className={color + (color === countdown ? ' ticking' : '')}>
+        {timeLeft}
+      </li>
+    );
   }
   }
 });
 });
 
 

+ 3 - 3
src/js/components/GameHeader.js

@@ -11,7 +11,7 @@ const GameHeader = React.createClass({
     io: React.PropTypes.object,
     io: React.PropTypes.object,
     params: React.PropTypes.array.isRequired,
     params: React.PropTypes.array.isRequired,
     color: React.PropTypes.string,
     color: React.PropTypes.string,
-    toggleModal: React.PropTypes.func.isRequired
+    openModal: React.PropTypes.func.isRequired
   },
   },
   mixins: [React.addons.PureRenderMixin],
   mixins: [React.addons.PureRenderMixin],
 
 
@@ -96,12 +96,12 @@ const GameHeader = React.createClass({
   },
   },
   _onRematch(e) {
   _onRematch(e) {
     e.preventDefault();
     e.preventDefault();
-    let {io, params, toggleModal} = this.props;
+    let {io, params, openModal} = this.props;
 
 
     io.emit('rematch-offer', {
     io.emit('rematch-offer', {
       token: params[0]
       token: params[0]
     });
     });
-    toggleModal(true, 'Your offer has been sent.');
+    openModal('info', 'Your offer has been sent.');
   }
   }
 });
 });
 
 

+ 40 - 5
src/js/components/GameInterface.js

@@ -3,6 +3,7 @@
 const React = require('react/addons');
 const React = require('react/addons');
 const GameHeader = require('./GameHeader');
 const GameHeader = require('./GameHeader');
 const Chat = require('./Chat');
 const Chat = require('./Chat');
+const Modal = require('./Modal');
 const Immutable = require('immutable');
 const Immutable = require('immutable');
 const {Map} = Immutable;
 const {Map} = Immutable;
 
 
@@ -16,7 +17,16 @@ const GameInterface = React.createClass({
   getInitialState() {
   getInitialState() {
     return {
     return {
       color: 'white',
       color: 'white',
-      modal: Map({open: false, message: ''}),
+      modal: Map({
+        open: false,
+        message: '',
+        type: 'info',
+        callbacks: {
+          hide: this._hideModal,
+          accept: this._acceptRematch,
+          decline: this._declineRematch
+        }
+      }),
       soundsEnabled: false
       soundsEnabled: false
     };
     };
   },
   },
@@ -33,6 +43,7 @@ const GameInterface = React.createClass({
       modal: this.state.modal
       modal: this.state.modal
         .set('open', true)
         .set('open', true)
         .set('message', 'Game link is invalid or has expired')
         .set('message', 'Game link is invalid or has expired')
+        .set('type', 'info')
     }));
     }));
 
 
     io.on('joined', data => {
     io.on('joined', data => {
@@ -55,7 +66,7 @@ const GameInterface = React.createClass({
           io={io}
           io={io}
           params={params}
           params={params}
           color={color}
           color={color}
-          toggleModal={this._toggleModal} />
+          openModal={this._openModal} />
 
 
         <audio preload="auto" ref="moveSnd">
         <audio preload="auto" ref="moveSnd">
           <source src="/snd/move.mp3" />
           <source src="/snd/move.mp3" />
@@ -73,19 +84,43 @@ const GameInterface = React.createClass({
           token={params[0]}
           token={params[0]}
           color={color}
           color={color}
           soundsEnabled={soundsEnabled} />
           soundsEnabled={soundsEnabled} />
+
+        <Modal data={this.state.modal} />
       </div>
       </div>
     );
     );
   },
   },
-  _toggleModal(open, message) {
+  _openModal(type, message) {
     this.setState({
     this.setState({
-      modal: Map({open: open, message: message})
+      modal: this.state.modal
+        .set('open', true)
+        .set('message', message)
+        .set('type', type)
+    });
+  },
+  _hideModal() {
+    this.setState({modal: this.state.modal.set('open', false)});
+  },
+  _acceptRematch() {
+    let {io, params} = this.props;
+
+    io.emit('rematch-confirm', {
+      token: params[0],
+      time: params[1] * 60,
+      inc: params[2]
+    });
+  },
+  _declineRematch() {
+    let {io, params} = this.props;
+
+    io.emit('rematch-decline', {
+      token: params[0]
     });
     });
   },
   },
   _toggleSounds() {
   _toggleSounds() {
     this.setState({
     this.setState({
       soundsEnabled: !this.state.soundsEnabled
       soundsEnabled: !this.state.soundsEnabled
     });
     });
-  }
+  },
 });
 });
 
 
 module.exports = GameInterface;
 module.exports = GameInterface;

+ 89 - 0
src/js/components/Modal.js

@@ -0,0 +1,89 @@
+'use strict';
+
+const React = require('react/addons');
+
+const Modal = React.createClass({
+  
+  propTypes: {
+    data: React.PropTypes.object.isRequired
+  },
+  mixins: [React.addons.PureRenderMixin],
+
+  componentDidMount() {
+    document.addEventListener('keydown', this._onKeydown);
+  },
+  componentWillUnmount() {
+    document.removeEventListener('keydown', this._onKeydown);
+  },
+  render() {
+    let data = this.props.data;
+    let type = data.get('type');
+    let callbacks = data.get('callbacks');
+
+    return (
+      <div className={'modal-mask' + (data.get('open') ? '' : ' hidden')}>
+        <p>
+          <strong>Esc: </strong>
+          <span>{type === 'info' ? 'OK' : 'Decline'}</span>
+          <br />
+          <strong>Enter: </strong>
+          <span>{type === 'info' ? 'OK' : 'Accept'}</span>
+        </p>
+
+        <div className="modal">
+          <p>{data.get('message')}</p>
+
+          {type === 'info' ? 
+            <button type="button"
+                    className="btn ok"
+                    onClick={e => {
+                      e.preventDefault();
+                      callbacks.hide();
+                    }}>
+              OK
+            </button> : [
+
+            <button key="a"
+                    type="button"
+                    className="btn btn--red"
+                    style={{left: '4em'}}
+                    onClick={e => {
+                      e.preventDefault();
+                      callbacks.decline();
+                    }}>
+              Decline
+            </button>,
+            <button key="b"
+                    type="button"
+                    className="btn"
+                    style={{right: '4em'}}
+                    onClick={e => {
+                      e.preventDefault();
+                      callbacks.accept();
+                    }}>
+              Accept
+            </button>
+          ]}
+        </div>
+      </div>
+    );
+  },
+  _onKeydown(e) {
+    let type = this.props.data.get('type');
+    let callbacks = this.props.data.get('callbacks');
+
+    if (type === 'info') {
+      if (e.which === 13 || e.which === 27) {
+        callbacks.hide();
+      }
+    } else if (type === 'offer') {
+      if (e.which === 13) {
+        callbacks.accept();
+      } else if (e.which === 27) {
+        callbacks.decline();
+      }
+    }
+  }
+});
+
+module.exports = Modal;

+ 3 - 1
src/js/play.js

@@ -4,7 +4,9 @@ require('es6-shim');
 const React = require('react');
 const React = require('react');
 const io = require('./io');
 const io = require('./io');
 const GameInterface = require('./components/GameInterface');
 const GameInterface = require('./components/GameInterface');
-const params = window.location.pathname.replace('/play/', '').split('/');
+let params = window.location.pathname.replace('/play/', '').split('/');
+params[1] = parseInt(params[1], 10);
+params[2] = parseInt(params[2], 10);
 
 
 React.render(
 React.render(
   <GameInterface io={io} params={params} />,
   <GameInterface io={io} params={params} />,

+ 0 - 22
views/layout.jade

@@ -15,28 +15,6 @@ html(lang='en')
     link(href='http://fonts.googleapis.com/css?family=Cherry+Swash|Open+Sans:400,600', rel='stylesheet', type='text/css')
     link(href='http://fonts.googleapis.com/css?family=Cherry+Swash|Open+Sans:400,600', rel='stylesheet', type='text/css')
 
 
   body
   body
-    #modal-mask.modal-mask
-      p
-        strong Esc: 
-        |  OK
-        br
-        strong Enter: 
-        |  OK
-      #modal-window.modal
-        p#modal-message Message.
-        a#modal-ok.btn(href='#', style='left: 50%; margin-left: -60px;') OK
-    #offer-mask.modal-mask
-      p
-        strong Esc: 
-        |  Decline
-        br
-        strong Enter: 
-        |  Accept
-      #offer-window.modal
-        p#offer-message Offer.
-        a#offer-accept.btn(href='#') Accept
-        a#offer-decline.btn.btn--red(href='#') Decline
-
     #container-wrapper.clearfix
     #container-wrapper.clearfix
         block content
         block content
         
         

+ 1 - 1
views/play.jade

@@ -1,7 +1,7 @@
 extends layout
 extends layout
 
 
 block content
 block content
-  #container.clearfix!=content
+  #container!=content
 
 
 block scripts
 block scripts
   script(src='/js/play.js')
   script(src='/js/play.js')