From 0d8d3fbb33170b3059a5ba2a677ee4dc4dbf2b59 Mon Sep 17 00:00:00 2001 From: "Michael H. Arieli" Date: Fri, 23 Nov 2018 16:55:48 -0800 Subject: [PATCH] Implemented socket chat --- .gitignore | 1 - package.json | 1 + src/client/actions/ChatActions.js | 21 + src/client/actions/SocketActions.js | 13 + src/client/components/App.js | 50 +- src/client/components/Chat.js | 55 + src/client/components/Input.js | 13 +- src/client/components/Toolbar.js | 10 + src/client/constants.js | 5 + src/client/containers/App.js | 1 + src/client/reducers/index.js | 2 + src/client/reducers/messages.js | 17 + src/scss/_alert.scss | 32 + src/scss/_chat.scss | 197 + src/scss/_notification.scss | 24 + src/scss/_toolbar.scss | 85 + src/scss/_variables.scss | 12 + src/scss/_video.scss | 46 + src/scss/style.scss | 225 +- src/server/socket.js | 22 +- yarn.lock | 7479 +++++++++++++++++++++++++++ 21 files changed, 8067 insertions(+), 244 deletions(-) create mode 100644 src/client/actions/ChatActions.js create mode 100644 src/client/components/Chat.js create mode 100644 src/client/reducers/messages.js create mode 100644 src/scss/_alert.scss create mode 100644 src/scss/_chat.scss create mode 100644 src/scss/_notification.scss create mode 100644 src/scss/_toolbar.scss create mode 100644 src/scss/_variables.scss create mode 100644 src/scss/_video.scss create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 5ce6a77..7fcbb2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .DS_Store -yarn.lock *.swp *.swo dist/ diff --git a/package.json b/package.json index 2658cfd..970820a 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "classnames": "^2.2.5", "config": "^1.26.1", "express": "^4.13.3", + "moment": "^2.22.2", "prop-types": "^15.5.10", "pug": "^2.0.0-rc.2", "react": "^15.5.4", diff --git a/src/client/actions/ChatActions.js b/src/client/actions/ChatActions.js new file mode 100644 index 0000000..799a63f --- /dev/null +++ b/src/client/actions/ChatActions.js @@ -0,0 +1,21 @@ +import * as constants from '../constants.js' +import _ from 'underscore' + +export function addMessage ({ userId, message, timestamp }) { + return { + type: constants.MESSAGE_ADD, + payload: { + id: _.uniqueId('chat'), + userId, + message, + timestamp + } + } +} + +export function loadHistory (messages) { + return { + type: constants.MESSAGES_HISTORY, + messages + } +} diff --git a/src/client/actions/SocketActions.js b/src/client/actions/SocketActions.js index 69c6429..6865bc5 100644 --- a/src/client/actions/SocketActions.js +++ b/src/client/actions/SocketActions.js @@ -1,3 +1,4 @@ +import * as ChatActions from '../actions/ChatActions.js' import * as NotifyActions from '../actions/NotifyActions.js' import * as PeerActions from '../actions/PeerActions.js' import * as constants from '../constants.js' @@ -41,6 +42,16 @@ class SocketHandler { .filter(id => !newUsersMap[id]) .forEach(id => peers[id].destroy()) } + handleMessages = ({ messages }) => { + const { dispatch } = this + debug('socket messages: %o', messages) + dispatch(ChatActions.loadHistory(messages)) + } + handleNewMessage = (payload) => { + const { dispatch } = this + debug('socket message: %o', payload) + dispatch(ChatActions.addMessage(payload)) + } } export function handshake ({ socket, roomName, stream }) { @@ -55,6 +66,8 @@ export function handshake ({ socket, roomName, stream }) { socket.on(constants.SOCKET_EVENT_SIGNAL, handler.handleSignal) socket.on(constants.SOCKET_EVENT_USERS, handler.handleUsers) + socket.on(constants.SOCKET_EVENT_MESSAGES, handler.handleMessages) + socket.on(constants.SOCKET_EVENT_NEW_MESSAGE, handler.handleNewMessage) debug('socket.id: %s', socket.id) debug('emit ready for room: %s', roomName) diff --git a/src/client/components/App.js b/src/client/components/App.js index d031dd9..730e29f 100644 --- a/src/client/components/App.js +++ b/src/client/components/App.js @@ -1,8 +1,9 @@ import Alerts, { AlertPropType } from './Alerts.js' import * as constants from '../constants.js' import Toolbar from './Toolbar.js' -import Input from './Input.js' import Notifications, { NotificationPropTypes } from './Notifications.js' +import Chat, { MessagePropTypes } from './Chat.js' +import Input from './Input.js' import PropTypes from 'prop-types' import React from 'react' import Video, { StreamPropType } from './Video.js' @@ -16,6 +17,7 @@ export default class App extends React.PureComponent { init: PropTypes.func.isRequired, notifications: PropTypes.objectOf(NotificationPropTypes).isRequired, notify: PropTypes.func.isRequired, + messages: PropTypes.arrayOf(MessagePropTypes).isRequired, peers: PropTypes.object.isRequired, sendMessage: PropTypes.func.isRequired, streams: PropTypes.objectOf(StreamPropType).isRequired, @@ -32,35 +34,41 @@ export default class App extends React.PureComponent { dismissAlert, notifications, notify, + messages, peers, sendMessage, toggleActive, streams } = this.props - return (
- - - - -
-
) + ) } } diff --git a/src/client/components/Chat.js b/src/client/components/Chat.js new file mode 100644 index 0000000..1518d38 --- /dev/null +++ b/src/client/components/Chat.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types' +import React from 'react' +import socket from '../socket.js' + +export const MessagePropTypes = PropTypes.shape({ + userId: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + timestamp: PropTypes.string.isRequired +}) + +export default class Chat extends React.PureComponent { + static propTypes = { + messages: PropTypes.arrayOf(MessagePropTypes).isRequired + } + hideChat = e => { + document.getElementById('chat').classList.remove('show') + document.querySelector('.toolbar .chat').classList.remove('on') + } + render () { + const { messages } = this.props + return ( +
+
+
+
+ arrow_back +
+
+
Chat
+
+
+ + {messages.length ? ( + messages.map((message, i) => ( +
+
+

{message.userId}

+

{message.message}

+ {message.timestamp} +
+
+
+ )) + ) : ( +
+
chat
+
No Notifications
+
+ )} + +
+
+ ) + } +} diff --git a/src/client/components/Input.js b/src/client/components/Input.js index a5eaaae..10ef6e0 100644 --- a/src/client/components/Input.js +++ b/src/client/components/Input.js @@ -1,5 +1,7 @@ import PropTypes from 'prop-types' import React from 'react' +import moment from 'moment' +import socket from '../socket.js' export default class Input extends React.PureComponent { static propTypes = { @@ -30,8 +32,15 @@ export default class Input extends React.PureComponent { submit = () => { const { notify, sendMessage } = this.props const { message } = this.state - notify('You: ' + message) - sendMessage(message) + if (message) { + notify('You: ' + message) + sendMessage(message) + + const userId = socket.id; + const timestamp = moment().format('ddd, D MMM HH:mm a'); + const payload = { userId, message, timestamp } + socket.emit('new_message', payload) + } this.setState({ message: '' }) } render () { diff --git a/src/client/components/Toolbar.js b/src/client/components/Toolbar.js index 6cf60fc..b52103a 100644 --- a/src/client/components/Toolbar.js +++ b/src/client/components/Toolbar.js @@ -5,6 +5,10 @@ export default class Toolbar extends React.PureComponent { static propTypes = { stream: StreamPropType } + handleChatClick = e => { + document.getElementById('chat').classList.toggle('show') + e.currentTarget.classList.toggle('on') + } handleMicClick = e => { const { stream } = this.props stream.mediaStream.getAudioTracks().forEach(track => { @@ -58,6 +62,12 @@ export default class Toolbar extends React.PureComponent { return (
+
+ chat +
{stream && (