Browse Source

add chat flux store, component and actions

romanmatiasko 9 years ago
parent
commit
28a2fd1d58

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@
 node_modules/
 logs/
 build/
+npm-debug.log

+ 2 - 0
package.json

@@ -23,6 +23,8 @@
     "babelify": "^5.0.3",
     "browserify": "^9.0.3",
     "chess.js": "^0.1.0",
+    "es6-shim": "^0.27.1",
+    "flux": "^2.0.1",
     "gulp": "^3.8.11",
     "gulp-autoprefixer": "^2.1.0",
     "gulp-cssmin": "^0.1.6",

+ 14 - 14
public/javascripts/play.js

@@ -359,23 +359,23 @@ $(function() {
     window.location = '/';
   });
 
-  $socket.on('receive-message', function (data) {
-    var chat = $('ul#chat');
-    var chat_node = $('ul#chat')[0];
-    var messageSnd = $("#messageSnd")[0];
+  // $socket.on('receive-message', function (data) {
+  //   var chat = $('ul#chat');
+  //   var chat_node = $('ul#chat')[0];
+  //   var messageSnd = $("#messageSnd")[0];
 
-    chat.append('<li class="' + data.color + ' left" >' + escapeHTML(data.message) + '</li>');
+  //   chat.append('<li class="' + data.color + ' left" >' + escapeHTML(data.message) + '</li>');
 
-    if (chat.is(':visible') && chat_node.scrollHeight > 300) {
-      setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
-    } else if (!chat.is(':visible') && !$('.new-message').is(':visible')) {
-      $('#bubble').before('<span class="new-message">You have a new message!</span>');
-    }
+  //   if (chat.is(':visible') && chat_node.scrollHeight > 300) {
+  //     setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
+  //   } else if (!chat.is(':visible') && !$('.new-message').is(':visible')) {
+  //     $('#bubble').before('<span class="new-message">You have a new message!</span>');
+  //   }
 
-    if ($('#sounds').is(':checked')) {
-      messageSnd.play();
-    }
-  });
+  //   if ($('#sounds').is(':checked')) {
+  //     messageSnd.play();
+  //   }
+  // });
 
   // $socket.on('countdown', function (data) {
   //   var color = data.color;

+ 2 - 2
src/css/_chat.scss

@@ -43,7 +43,7 @@
   }
 }
 
-ul#chat {
+#chat-list {
   position: relative;
   top: 0;
   max-height: 300px;
@@ -99,7 +99,7 @@ ul#chat {
   }
 }
 
-#send-message {
+#chat-form {
   input {
     width: 100%;
     padding: 15px 10px;

+ 19 - 0
src/js/actions/ChatActions.js

@@ -0,0 +1,19 @@
+const ChatConstants = require('../constants/ChatConstants');
+let AppDispatcher = require('../dispatcher/AppDispatcher');
+
+let ChatActions = {
+  toggleChat() {
+    AppDispatcher.handleViewAction({
+      actionType: ChatConstants.TOGGLE_CHAT
+    });
+  },
+  submitMessage(message, className) {
+    AppDispatcher.handleViewAction({
+      actionType: ChatConstants.SUBMIT_MESSAGE,
+      message: message,
+      className: className
+    });
+  }
+};
+
+module.exports = ChatActions;

+ 105 - 0
src/js/components/Chat.js

@@ -0,0 +1,105 @@
+'use strict';
+
+const React = require('react/addons');
+const ChatStore = require('../stores/ChatStore');
+const ChatActions = require('../actions/ChatActions');
+
+const Chat = React.createClass({
+  
+  propTypes: {
+    io: React.PropTypes.object,
+    token: React.PropTypes.string,
+    color: React.PropTypes.string,
+    soundsEnabled: React.PropTypes.bool.isRequired
+  },
+  mixins: [React.addons.PureRenderMixin],
+
+  getInitialState() {
+    let state = ChatStore.getState();
+    return {
+      isChatHidden: state.isChatHidden,
+      messages: state.messages,
+      message: '',
+    };
+  },
+  componentDidMount() {
+    let {io, soundsEnabled} = this.props;
+
+    io.on('receive-message', data => {
+      ChatActions.submitMessage(data.message, data.color + ' left');
+
+      if (!this.state.isChatHidden) {
+        this._scrollChat();
+      }
+      if (soundsEnabled) {
+        this.refs.msgSnd.getDOMNode().play();
+      }
+    });
+    
+    ChatStore.on('change', this._onChatStoreChange);
+  },
+  componentWillUnmount() {
+    ChatStore.off('change', this._onChatStoreChange);
+  },
+  render() {
+    return (
+      <div id="chat-wrapper"
+           style={this.state.isChatHidden ? {display: 'none'} : null}>
+        <h4>Chat</h4>
+        <a className="close"
+           onClick={this._toggleChat}>
+          x
+        </a>
+        <audio preload="auto" ref="msgSnd">
+          <source src="/snd/message.mp3" />
+        </audio>
+        <ul id="chat-list" ref="chat">
+          {this.state.messages.map((message, i) => (
+            <li key={i} className={message.get('className')}>
+              {message.get('message')}
+            </li>
+          )).toArray()}
+        </ul>
+        <span>Write your message:</span>
+        <form id="chat-form"
+              onSubmit={this._submitMessage}>
+          <input type="text"
+                 className={this.props.color}
+                 required
+                 value={this.state.message}
+                 onChange={this._onChangeMessage} />
+        </form>
+      </div>
+    );
+  },
+  _onChatStoreChange() {
+    this.setState(ChatStore.getState(), this._scrollChat);
+  },
+  _toggleChat(e) {
+    e.preventDefault();
+    ChatActions.toggleChat();
+  },
+  _onChangeMessage(e) {
+    this.setState({message: e.target.value});
+  },
+  _submitMessage(e) {
+    e.preventDefault();
+    let {io, token, color} = this.props;
+    let message = this.state.message;
+
+    ChatActions.submitMessage(message, color + ' right');
+    this.setState({message: ''});
+
+    io.emit('send-message', {
+      message: message,
+      color: color,
+      token: token
+    });
+  },
+  _scrollChat() {
+    let chatNode = this.refs.chat.getDOMNode();
+    chatNode.scrollTop = chatNode.scrollHeight;
+  }
+});
+
+module.exports = Chat;

+ 37 - 8
src/js/components/GameHeader.js

@@ -2,6 +2,8 @@
 
 const React = require('react/addons');
 const Clock = require('./Clock');
+const ChatStore = require('../stores/ChatStore');
+const ChatActions = require('../actions/ChatActions');
 
 const GameHeader = React.createClass({
   
@@ -13,6 +15,25 @@ const GameHeader = React.createClass({
   },
   mixins: [React.addons.PureRenderMixin],
 
+  getInitialState() {
+    return {
+      isChatHidden: ChatStore.getState().isChatHidden,
+      newMessage: false
+    };
+  },
+  componentDidMount() {
+    let io = this.props.io;
+
+    io.on('receive-message', () => {
+      if (this.state.isChatHidden) {
+        this.setState({newMessage: true});
+      }
+    });
+    ChatStore.on('change', this._onChatStoreChange);
+  },
+  componentWillUnmount() {
+    ChatStore.off('change', this._onChatStoreChange);
+  },
   render() {
     let [_, time, inc] = this.props.params;
 
@@ -43,8 +64,10 @@ const GameHeader = React.createClass({
 
         <a id="chat-icon"
            onClick={this._toggleChat}>
-          <img id="bubble"
-               src="/img/chat.svg"
+          {this.state.newMessage ?
+            <span className="new-message">You have a new message!</span>
+          :null}
+          <img src="/img/chat.svg"
                width="50"
                height="50" />
           Chat
@@ -52,27 +75,33 @@ const GameHeader = React.createClass({
       </header>
     );
   },
+  _onChatStoreChange() {
+    this.setState({
+      isChatHidden: ChatStore.getState().isChatHidden
+    });
+  },
+  _toggleChat(e) {
+    e.preventDefault();
+    this.setState({newMessage: false});
+    ChatActions.toggleChat();
+  },
   _onResign(e) {
+    e.preventDefault();
     let {io, params, color} = this.props;
 
     io.emit('resign', {
       token: params[0],
       color: color
     });
-    e.preventDefault();
   },
   _onRematch(e) {
+    e.preventDefault();
     let {io, params, toggleModal} = this.props;
 
     io.emit('rematch-offer', {
       token: params[0]
     });
     toggleModal(true, 'Your offer has been sent.');
-    e.preventDefault();
-  },
-  _toggleChat(e) {
-    // ChatStore.toggleChat();
-    e.preventDefault();
   }
 });
 

+ 39 - 4
src/js/components/GameInterface.js

@@ -2,9 +2,9 @@
 
 const React = require('react/addons');
 const GameHeader = require('./GameHeader');
+const Chat = require('./Chat');
 const Immutable = require('immutable');
 const {Map} = Immutable;
-const Chess = require('chess.js');
 
 const GameInterface = React.createClass({
   
@@ -15,16 +15,46 @@ const GameInterface = React.createClass({
 
   getInitialState() {
     return {
+      color: 'white',
       modal: Map({open: false, message: ''}),
       soundsEnabled: false
     };
   },
+  componentDidMount() {
+    let {io, params} = this.props;
+
+    io.emit('join', {
+      token: params[0],
+      time: params[1] * 60,
+      inc: params[2]
+    });
+
+    io.on('token-invalid', () => this.setState({
+      modal: this.state.modal
+        .set('open', true)
+        .set('message', 'Game link is invalid or has expired')
+    }));
+
+    io.on('joined', data => {
+      if (data.color === 'white') {
+        io.emit('timer-white', {
+          token: params[0]
+        });
+      } else {
+        this.setState({color: 'black'});
+      }
+    });
+  },
   render() {
+    let {io, params} = this.props;
+    let {color, soundsEnabled} = this.state;
+
     return (
       <div>
         <GameHeader
-          io={this.props.io}
-          params={this.props.params}
+          io={io}
+          params={params}
+          color={color}
           toggleModal={this._toggleModal} />
 
         <audio preload="auto" ref="moveSnd">
@@ -33,11 +63,16 @@ const GameInterface = React.createClass({
         </audio>
         <label id="sounds-label">
           <input type="checkbox"
-                 checked={this.state.soundsEnabled}
+                 checked={soundsEnabled}
                  onChange={this._toggleSounds} />
           <span> Enable sounds</span>
         </label>
 
+        <Chat
+          io={io}
+          token={params[0]}
+          color={color}
+          soundsEnabled={soundsEnabled} />
       </div>
     );
   },

+ 6 - 0
src/js/constants/ChatConstants.js

@@ -0,0 +1,6 @@
+const keyMirror = require('react/lib/keyMirror');
+
+module.exports = keyMirror({
+  TOGGLE_CHAT: null,
+  SUBMIT_MESSAGE: null
+});

+ 11 - 0
src/js/dispatcher/AppDispatcher.js

@@ -0,0 +1,11 @@
+const Dispatcher = require('flux').Dispatcher;
+
+module.exports = Object.assign(new Dispatcher(), {
+  // @param {object} action The data coming from the view.
+  handleViewAction: function(action) {
+    this.dispatch({
+      source: 'VIEW_ACTION',
+      action: action
+    });
+  }
+});

+ 1 - 0
src/js/play.js

@@ -1,5 +1,6 @@
 'use strict';
 
+require('es6-shim');
 const React = require('react');
 const io = require('./io');
 const GameInterface = require('./components/GameInterface');

+ 46 - 0
src/js/stores/ChatStore.js

@@ -0,0 +1,46 @@
+'use strict';
+
+const AppDispatcher = require('../dispatcher/AppDispatcher');
+const EventEmitter = require('eventemitter2').EventEmitter2; 
+const ChatConstants = require('../constants/ChatConstants');
+const Immutable = require('immutable');
+const {List, Map} = Immutable;
+const CHANGE_EVENT = 'change';
+  
+var _messages = List();
+var _isChatHidden = false;
+
+var ChatStore = Object.assign({}, EventEmitter.prototype, {
+  getState() {
+    return {
+      messages: _messages,
+      isChatHidden: _isChatHidden
+    };
+  }
+});
+
+AppDispatcher.register(payload => {
+  var action = payload.action;
+
+  switch (action.actionType) {
+
+    case ChatConstants.TOGGLE_CHAT:
+      _isChatHidden = !_isChatHidden;
+      break;
+
+    case ChatConstants.SUBMIT_MESSAGE:
+      _messages = _messages.push(Map({
+        message: action.message,
+        className: action.className
+      }));
+      break;
+
+    default:
+      return true;
+  }
+
+  ChatStore.emit(CHANGE_EVENT);
+  return true;
+});
+
+module.exports = ChatStore;