From a2e7396e566de4d67b5cbe12b7e2c3200d5dfeb8 Mon Sep 17 00:00:00 2001 From: notnull Date: Thu, 25 Jul 2019 14:13:23 -0400 Subject: [PATCH 01/11] added findUnreadMessages and readMessages --- src/App.js | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index dccae52..e4d4bc0 100644 --- a/src/App.js +++ b/src/App.js @@ -23,6 +23,7 @@ class App extends React.Component { this.handleChange = this.handleChange.bind(this) this.handleSubmit = this.handleSubmit.bind(this) this.join = this.join.bind(this) + this.readMessages = this.readMessages.bind(this) } async fetchData() { @@ -56,6 +57,7 @@ class App extends React.Component { }) this.socket.on('message', msg => { + if (msg.namespace === this.state.namespace) msg.read = true const messages = this.state.messages.concat(msg) this.setState({ messages }) }) @@ -83,6 +85,9 @@ class App extends React.Component { socketId: this.socket.id, text: this.state.message, namespace: this.state.namespace, + from: this.socket.id, + to: this.state.namespace, + read: false, } this.sendMessage(message) @@ -104,6 +109,24 @@ class App extends React.Component { sendMessage(msg) { this.socket.emit('message', msg) } + + findUnreadMessages(nsp) { + return this.state.messages.filter( + m => m.read === false && m.namespace === nsp + ).length + } + + readMessages(nsp) { + const messages = this.state.messages + messages.map(m => { + if (m.namespace === nsp && m.read === false) { + m.read = true + } + return m + }) + this.setState({ messages }) + } + renderApp() { const title = this.state.namespace[0] === '#' @@ -111,6 +134,7 @@ class App extends React.Component { : this.state.namespace[0] === '/' ? 'server messages' : 'User' + return (
{/*Navbar*/} @@ -178,9 +202,22 @@ class App extends React.Component { ))} -- 2.49.0 From 1e62608188de22c9e8ca8a2b270aa700ac29f312 Mon Sep 17 00:00:00 2001 From: data Date: Thu, 25 Jul 2019 13:46:17 +0100 Subject: [PATCH 02/11] color chat on unread messages --- src/App.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/App.js b/src/App.js index e4d4bc0..c2db308 100644 --- a/src/App.js +++ b/src/App.js @@ -33,7 +33,7 @@ class App extends React.Component { componentDidMount() { this.fetchData() this.socket.on('connect', () => { - console.log('connected!') + console.log(`I am ${socket.id}`) const user = { socketId: this.socket.id } this.setState({ user }) }) @@ -127,6 +127,10 @@ class App extends React.Component { this.setState({ messages }) } + activateChat(namespace) { + this.readMessages(namespace) + this.setState({ namespace }) + } renderApp() { const title = this.state.namespace[0] === '#' @@ -182,32 +186,34 @@ class App extends React.Component { className="d-flex flex-column col-sm-2 bg-dark text-light p-2" id="sidebar2" > -
User Channels
- +
Chats
{this.state.namespaces .filter( - r => - r.namespace[0] === '#' && - r.sockets.includes(this.state.user.socketId) + r => r.sockets && r.sockets.includes(this.state.user.socketId) ) .map(r => ( {this.state.namespaces .filter( - r => r.sockets && r.sockets.includes(this.state.user.socketId) + r => + r.sockets && + r.sockets.includes(this.state.user.socketId) && + r.namespace !== this.state.user.socketId ) .map(r => ( -- 2.49.0 From 0630f346b5776ef5b7e46660081be38b44e4f4bd Mon Sep 17 00:00:00 2001 From: data Date: Fri, 26 Jul 2019 20:14:46 +0100 Subject: [PATCH 05/11] add part and nick commands, store users as objects --- server/socket/index.js | 31 ++++++++++++++++--------- src/App.js | 51 ++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/server/socket/index.js b/server/socket/index.js index a1aa3e8..7b14546 100644 --- a/server/socket/index.js +++ b/server/socket/index.js @@ -1,8 +1,12 @@ +const users = {} + module.exports = io => { io.on('connection', async socket => { console.log(`A socket connection to the server has been made: ${socket.id}`) + users[socket.id] = { socketId: socket.id, nick: socket.id } socket.join('#projex', () => { - sendUsers() + sendUsers() // back to socket + sendUser(users[socket.id]) // announced to all sendNamespaces() }) @@ -17,23 +21,30 @@ module.exports = io => { sendNamespaces() }) }) - socket.on('chat', id => { - const target = io.sockets.sockets[id] - if (!target || socket.id === target.id) { - console.log('Not starting chat.', socket, target) + socket.on('chat', user => { + const target = io.sockets.sockets[user.socketId] + if (!target) return + if (socket.id === target.id) { + console.log('Not allowing chat with itself', socket.id, target.id) return } - console.log(`${socket.id} wants to chat with ${target.id}`) // create a shared namespace const namespace = `${socket.id.substr(0, 5)}.${target.id.substr(0, 5)}` + console.log(`${socket.id} starts chat with ${target.id} in ${namespace}`) socket.join(namespace) target.join(namespace) sendNamespaces() }) - socket.on('part', () => { - console.log('part') + socket.on('part', namespace => { + console.log('part', socket.id, namespace) + socket.leave(namespace, () => { + sendNamespaces() + }) + }) + socket.on('change nick', nick => { + io.emit('nick changed', { socketId: socket.id, nick: nick }) }) socket.on('get namespaces', () => sendNamespaces()) @@ -46,14 +57,14 @@ module.exports = io => { io.emit('got namespaces', namespaces) } + const sendUser = user => socket.broadcast.emit('user connected', user) const sendUsers = () => - io.emit('got users', Object.keys(io.sockets.sockets)) + socket.emit('got users', Object.keys(users).map(u => users[u])) socket.on('disconnect', async () => { io.emit('user disconnected', { socketId: socket.id }) console.log(`${socket.id} has disconnected.`) sendNamespaces() - sendUsers() }) }) } diff --git a/src/App.js b/src/App.js index 59f9674..2fc184c 100644 --- a/src/App.js +++ b/src/App.js @@ -34,16 +34,30 @@ class App extends React.Component { this.fetchData() this.socket.on('connect', () => { console.log(`I am ${socket.id}`) - const user = { socketId: this.socket.id } - this.setState({ user, nick: this.socket.id, namespace: this.socket.id }) + const user = { socketId: this.socket.id, nick: this.socket.id } + this.setState({ user, namespace: this.socket.id }) }) - this.socket.on('user connected', payload => { - console.log('a new user connected!', payload) + this.socket.on('user connected', user => { + console.log('a new user connected!', user.nick) + const allUsers = this.state.allUsers.concat(user) + this.setState({ allUsers }) }) - this.socket.on('user disconnected', payload => { - console.log('a user disconnected.', payload) + this.socket.on('nick changed', user => { + const allUsers = this.state.allUsers + allUsers.map(u => { + if (u.socketId === user.socketId) u.nick = user.nick + return u + }) + console.log('new nick', user) + this.setState({ allUsers }) + }) + this.socket.on('user disconnected', socketId => { + console.log('a user disconnected.', socketId) + const oldUsers = this.state.allUsers + const allUsers = oldUsers.filter(u => u.socketId !== socketId) + this.setState({ allUsers }) }) this.socket.on('got namespaces', namespaces => { @@ -61,6 +75,10 @@ class App extends React.Component { const messages = this.state.messages.concat(msg) this.setState({ messages }) }) + + this.socket.on('nick change', user => { + console.log('new nick', user) + }) } handleChange(e) { @@ -72,7 +90,8 @@ class App extends React.Component { const cmd = args[0].slice(1) console.log('command:', cmd) if (cmd === 'join') this.join(args[1]) - else if (cmd === 'nick') this.setState({ nick: args[1] }) + else if (cmd === 'part') this.part(args[1]) + else if (cmd === 'nick') this.nick(args[1]) this.setState({ message: '' }) } handleSubmit(e) { @@ -107,10 +126,18 @@ class App extends React.Component { this.socket.emit('join', namespace) this.setState({ namespace }) } + part(namespace) { + console.log(`leaving ${namespace}`) + socket.emit('part', namespace) + } + nick(nick) { + console.log(`Changing nick to ${nick}`) + this.socket.emit('change nick', nick) + } query(user) { - console.log(`starting query with ${user}`) + console.log(`starting query with ${user.nick}`) this.socket.emit('chat', user) - this.setState({ namespace: user }) + this.setState({ namespace: user.socketId }) } sendMessage(msg) { this.socket.emit('message', msg) @@ -159,14 +186,14 @@ class App extends React.Component { {this.state.allUsers - .filter(u => u !== socket.id) + .filter(u => u.nick !== this.state.user.nick) .map(u => ( this.query(u)} > - {u} + {u.nick} ))} -- 2.49.0 From 6da52f277022b8a234bec1203633fa7a0cf6372f Mon Sep 17 00:00:00 2001 From: notnull Date: Fri, 26 Jul 2019 14:56:24 -0400 Subject: [PATCH 06/11] lots of refactoring backend - join channels on frontend - broadcast 'user connected' - emit 'user joined', 'user left', 'changed nick', 'user disconnected' frontend - handleSubmit > submitMessage - formatMessage() - chats on state with channels and private chats - announce 'user connected', 'user joined', 'user left', 'changed nick' - no longer emit 'chat' - show nick in message window instead of socketId --- server/socket/index.js | 65 ++++---- src/App.js | 354 ++++++++++++++++++++++++++++++----------- 2 files changed, 296 insertions(+), 123 deletions(-) diff --git a/server/socket/index.js b/server/socket/index.js index 7b14546..5dd4e98 100644 --- a/server/socket/index.js +++ b/server/socket/index.js @@ -4,47 +4,41 @@ module.exports = io => { io.on('connection', async socket => { console.log(`A socket connection to the server has been made: ${socket.id}`) users[socket.id] = { socketId: socket.id, nick: socket.id } - socket.join('#projex', () => { - sendUsers() // back to socket - sendUser(users[socket.id]) // announced to all - sendNamespaces() - }) + + // announce to all + socket.broadcast.emit('user connected', users[socket.id]) + // send all users TODO remove + socket.emit( + 'got users', + Object.keys(io.sockets.sockets).map(socketId => users[socketId]) + ) socket.on('message', message => { - console.log('sending message to namespace', message.namespace, message) - io.to(message.namespace).emit('message', message) + console.log('sending message to ', message.to, message) + io.to(message.to).emit('message', message) }) socket.on('join', namespace => { - console.log('join:', namespace) + const socketId = socket.id + console.log(`${socketId} joins ${namespace}`) socket.join(namespace, () => { - sendNamespaces() + io.to(namespace).emit('user joined', { namespace, socketId }) + sendNamespace(namespace) }) }) - socket.on('chat', user => { - const target = io.sockets.sockets[user.socketId] - if (!target) return - if (socket.id === target.id) { - console.log('Not allowing chat with itself', socket.id, target.id) - return - } - - // create a shared namespace - const namespace = `${socket.id.substr(0, 5)}.${target.id.substr(0, 5)}` - console.log(`${socket.id} starts chat with ${target.id} in ${namespace}`) - socket.join(namespace) - target.join(namespace) - sendNamespaces() - }) socket.on('part', namespace => { - console.log('part', socket.id, namespace) + const socketId = socket.id + console.log('part', socketId, namespace) socket.leave(namespace, () => { - sendNamespaces() + io.to(namespace).emit('user left', { namespace, socketId }) + sendNamespace(namespace) }) }) + socket.on('change nick', nick => { - io.emit('nick changed', { socketId: socket.id, nick: nick }) + io.emit('changed nick', { socketId: socket.id, nick: nick }) + users[socket.id]['nick'] = nick }) socket.on('get namespaces', () => sendNamespaces()) @@ -57,14 +51,19 @@ module.exports = io => { io.emit('got namespaces', namespaces) } - const sendUser = user => socket.broadcast.emit('user connected', user) - const sendUsers = () => - socket.emit('got users', Object.keys(users).map(u => users[u])) - socket.on('disconnect', async () => { - io.emit('user disconnected', { socketId: socket.id }) + io.emit('user disconnected', socket.id) console.log(`${socket.id} has disconnected.`) - sendNamespaces() + //sendNamespaces() }) + + const sendNamespace = namespace => { + if (!io.sockets.adapter.rooms[namespace]) return + const newNamespace = { + namespace, + sockets: Object.keys(io.sockets.adapter.rooms[namespace].sockets), + } + io.emit('got namespace', newNamespace) + } }) } diff --git a/src/App.js b/src/App.js index 2fc184c..bdfd691 100644 --- a/src/App.js +++ b/src/App.js @@ -9,7 +9,8 @@ const initialState = { loading: true, messages: [], message: '', - namespaces: [], + allNamespaces: [], + chats: [], namespace: '/', allUsers: [], user: {}, @@ -21,7 +22,7 @@ class App extends React.Component { this.state = initialState this.socket = socket this.handleChange = this.handleChange.bind(this) - this.handleSubmit = this.handleSubmit.bind(this) + this.submitMessage = this.submitMessage.bind(this) this.join = this.join.bind(this) this.readMessages = this.readMessages.bind(this) } @@ -34,35 +35,145 @@ class App extends React.Component { this.fetchData() this.socket.on('connect', () => { console.log(`I am ${socket.id}`) + this.join('#projex') const user = { socketId: this.socket.id, nick: this.socket.id } - this.setState({ user, namespace: this.socket.id }) + this.setState({ user }) }) this.socket.on('user connected', user => { - console.log('a new user connected!', user.nick) + console.log(`${user.nick} connected.`) const allUsers = this.state.allUsers.concat(user) this.setState({ allUsers }) }) - this.socket.on('nick changed', user => { + this.socket.on('user disconnected', socketId => { + console.log(`${socketId} disconnected.`) + const allUsers = this.state.allUsers.map(u => { + if (u.socketId === socketId) u.offline = true + return u + }) + const oldNick = allUsers.find(u => u.socketId === socketId).nick + this.setState({ allUsers }) + // inform channels user was part of + this.state.chats.map(c => { + if (c === socketId) { + const messages = this.state.messages.concat( + this.formatMessage( + `${oldNick} disconnected`, + socketId, + '[server]', + c, + c === this.state.namespace ? true : false + ) + ) + this.setState({ messages }) + } + this.state.allNamespaces.map(n => { + if (n.namespace === c && n.sockets.find(s => s === socketId)) { + const messages = this.state.messages.concat( + this.formatMessage( + `${oldNick} disconnected`, + socketId, + '[server]', + c, + c === this.state.namespace ? true : false + ) + ) + this.setState({ messages }) + } + return n + }) + return c + }) + }) + + this.socket.on('user joined', data => { + const { namespace, socketId } = data + const user = this.state.allUsers.find(u => u.socketId === socketId) + console.log(`${user.nick} joined ${namespace}`) + const messages = this.state.messages.concat( + this.formatMessage( + `${user.nick} joined ${namespace}`, + socketId, + '[server]', + namespace, + namespace === this.state.namespace ? true : false + ) + ) + this.setState({ messages }) + }) + + this.socket.on('user left', data => { + const { namespace, socketId } = data + const user = this.state.allUsers.find(u => u.socketId === socketId) + console.log(`${user.nick} left ${namespace}`) + const messages = this.state.messages.concat( + this.formatMessage( + `${user.nick} left ${namespace}`, + socketId, + '[server]', + namespace, + namespace === this.state.namespace ? true : false + ) + ) + this.setState({ messages }) + }) + + this.socket.on('changed nick', user => { + if (user.socketId === this.state.user.socketId) return + console.log(`${user.socketId} is now known as ${user.nick}.`) const allUsers = this.state.allUsers + const oldNick = allUsers.find(u => u.socketId === user.socketId).nick + // inform channels user is part of + this.state.chats.map(c => { + if (c === user.socketId) { + const messages = this.state.messages.concat( + this.formatMessage( + `${oldNick} is now known as ${user.nick}`, + user.socketId, + '[server]', + c, + c === this.state.namespace ? true : false + ) + ) + this.setState({ messages }) + } + this.state.allNamespaces.map(n => { + if (n.namespace === c && n.sockets.find(s => s === user.socketId)) { + const messages = this.state.messages.concat( + this.formatMessage( + `${oldNick} is now known as ${user.nick}`, + user.socketId, + '[server]', + c, + c === this.state.namespace ? true : false + ) + ) + this.setState({ messages }) + } + return n + }) + return c + }) + // update nick allUsers.map(u => { if (u.socketId === user.socketId) u.nick = user.nick return u }) - console.log('new nick', user) - this.setState({ allUsers }) - }) - this.socket.on('user disconnected', socketId => { - console.log('a user disconnected.', socketId) - const oldUsers = this.state.allUsers - const allUsers = oldUsers.filter(u => u.socketId !== socketId) this.setState({ allUsers }) }) - this.socket.on('got namespaces', namespaces => { - console.log('got namespaces:', namespaces) - this.setState({ namespaces }) + this.socket.on('got namespace', namespace => { + console.log('got namespace:', namespace) + const allNamespaces = this.state.allNamespaces + .filter(n => n.namespace !== namespace.namespace) + .concat(namespace) + this.setState({ allNamespaces }) + }) + + this.socket.on('got namespaces', allNamespaces => { + console.log('got namespaces:', allNamespaces) + this.setState({ allNamespaces }) }) this.socket.on('got users', allUsers => { @@ -71,14 +182,23 @@ class App extends React.Component { }) this.socket.on('message', msg => { - if (msg.namespace === this.state.namespace) msg.read = true + if ( + msg.to === this.state.user.socketId && + !this.state.chats.find(c => c === msg.from) + ) { + console.log(`${msg.from} wants to chat with me.`) + const chats = this.state.chats.concat(msg.from) + this.setState({ chats }) + } + if ( + (msg.to[0] === '#' && msg.to === this.state.namespace) || + (msg.to[0] !== '#' && msg.from === this.state.namespace) + ) + msg.read = true const messages = this.state.messages.concat(msg) + console.log(msg) this.setState({ messages }) }) - - this.socket.on('nick change', user => { - console.log('new nick', user) - }) } handleChange(e) { @@ -88,29 +208,51 @@ class App extends React.Component { parseCommand() { const args = this.state.message.split(' ') const cmd = args[0].slice(1) - console.log('command:', cmd) - if (cmd === 'join') this.join(args[1]) - else if (cmd === 'part') this.part(args[1]) - else if (cmd === 'nick') this.nick(args[1]) + if (cmd === 'join') { + if (!args[1]) { + alert('did you forget a channel to join?') + return + } + this.join(args[1]) + } else if (cmd === 'part' || cmd === 'leave') { + this.part(args[1] ? args[1] : this.state.namespace) + } else if (cmd === 'nick') this.nick(args[1]) + else { + alert(`unknown command: ${cmd}`) + } this.setState({ message: '' }) } - handleSubmit(e) { - e.preventDefault() - - if (this.state.message[0] === '/') return this.parseCommand() - + formatMessage(text, from, nick, to, read) { + if (!text || !from || !to || !nick) { + console.log( + '[formatMessage] Dropping malformed message (requiring text, from, nick and to).' + ) + } const message = { id: uuid(), date: moment().format('MMMM D YYYY, h:mm a'), - socketId: this.socket.id, - text: this.state.message, - namespace: this.state.namespace, - from: this.state.nick, - to: this.state.namespace, - read: false, + text: text, + from: from, + nick: nick, + to: to, + read: read, } + return message + } + submitMessage(e) { + e.preventDefault() - this.sendMessage(message) + if (this.state.message[0] === '/') return this.parseCommand() + if (!this.state.message.length > 0) return + + this.sendMessage( + this.formatMessage( + this.state.message, + this.state.user.socketId, + this.state.user.nick, + this.state.namespace + ) + ) this.setState({ message: '' }) } renderLoading() { @@ -122,37 +264,59 @@ class App extends React.Component { } join(namespace) { if (namespace.slice(0, 1) !== '#') return alert('Use a leading #, silly!') + if (this.state.chats.find(c => c === namespace)) return console.log('joining', namespace) + const chats = this.state.chats.concat(namespace) this.socket.emit('join', namespace) - this.setState({ namespace }) + this.setState({ namespace, chats }) } part(namespace) { console.log(`leaving ${namespace}`) socket.emit('part', namespace) + const chats = this.state.chats.filter(c => c !== namespace) + this.setState({ chats, namespace: '/' }) } nick(nick) { + if (nick[0] === '#') { + alert('nick cannot start with #, sorry!') + return + } console.log(`Changing nick to ${nick}`) + const user = this.state.user + user.nick = nick + this.setState({ user }) this.socket.emit('change nick', nick) } query(user) { - console.log(`starting query with ${user.nick}`) - this.socket.emit('chat', user) - this.setState({ namespace: user.socketId }) + if (this.state.chats.find(c => c === user.socketId)) { + this.setState({ namespace: user.socketId }) + return + } + console.log(`starting chat with ${user.nick}`) + const chats = this.state.chats.concat(user.socketId) + this.setState({ namespace: user.socketId, chats }) } sendMessage(msg) { this.socket.emit('message', msg) + if (msg.to[0] !== '#') { + if (msg.to === this.state.namespace) msg.read = true + const messages = this.state.messages.concat(msg) + this.setState({ messages }) + } } - findUnreadMessages(nsp) { + countUnreadMessages(nsp) { return this.state.messages.filter( - m => m.read === false && m.namespace === nsp + m => + !m.read && + (m.to === nsp || (m.to === this.state.user.socketId && m.from === nsp)) ).length } readMessages(nsp) { const messages = this.state.messages messages.map(m => { - if (m.namespace === nsp && m.read === false) { + if (!m.read && (m.to === nsp || m.from === nsp)) { m.read = true } return m @@ -169,8 +333,8 @@ class App extends React.Component { this.state.namespace[0] === '#' ? 'Channel' : this.state.namespace === this.state.user.socketId - ? 'Me' - : 'User' + ? 'Namespace' + : 'Chat with' return (
@@ -186,7 +350,9 @@ class App extends React.Component { {this.state.allUsers - .filter(u => u.nick !== this.state.user.nick) + .filter( + u => u.socketId !== this.socket.id && u.offline !== true + ) .map(u => ( - {this.state.namespaces + {this.state.allNamespaces .filter(n => n.namespace[0] === '#') .map(r => (
Chats
- {this.state.namespaces - .filter( - r => - r.sockets && - r.sockets.includes(this.state.user.socketId) && - r.namespace !== this.state.user.socketId - ) - .map(r => ( - - ))} + ) : null} + {/* user count for channels */} + {c[0] === '#' && + this.state.allNamespaces && + this.state.allNamespaces.find(n => n.namespace === c) ? ( +
+ { + this.state.allNamespaces.find(n => n.namespace === c) + .sockets.length + } +
+ ) : null} + + ))} {/*Main Section*/} @@ -278,23 +443,32 @@ class App extends React.Component { {/*Main Section Header*/}

- {title}: {this.state.namespace} + {title} {this.state.namespace}

{/*Main Section Content*/}
{this.state.messages - .filter(m => m.namespace === this.state.namespace) + .filter( + m => + (m.to[0] === '#' && m.to === this.state.namespace) || + (m.to[0] !== '#' && m.from === this.state.namespace) || + (m.from === this.state.user.socketId && + m.to === this.state.namespace) + ) .map(m => ( -
-
{m.date}
-
{m.from}
+
+
{m.date}
+
{m.nick}
{m.text}
))}
-
+ - submit as {this.state.nick} + submit as {this.state.user.nick}
-- 2.49.0 From 774fe6e2629da7cb60cc957a3d47707c2141d85e Mon Sep 17 00:00:00 2001 From: data Date: Sat, 27 Jul 2019 19:52:55 +0100 Subject: [PATCH 07/11] messages flexbox design --- src/App.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index bdfd691..ebb8d60 100644 --- a/src/App.js +++ b/src/App.js @@ -458,13 +458,10 @@ class App extends React.Component { m.to === this.state.namespace) ) .map(m => ( -
-
{m.date}
-
{m.nick}
-
{m.text}
+
+
{m.date}
+
{m.nick}
+
{m.text}
))}
-- 2.49.0 From abcce5c839882040ced4ae594e109b15a3cf1f1c Mon Sep 17 00:00:00 2001 From: data Date: Sun, 28 Jul 2019 08:49:54 +0100 Subject: [PATCH 08/11] addNotification() for nick, join, part and disconnect --- src/App.js | 58 +++++++++++++++--------------------------------------- 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/src/App.js b/src/App.js index ebb8d60..a712d2d 100644 --- a/src/App.js +++ b/src/App.js @@ -58,26 +58,14 @@ class App extends React.Component { this.state.chats.map(c => { if (c === socketId) { const messages = this.state.messages.concat( - this.formatMessage( - `${oldNick} disconnected`, - socketId, - '[server]', - c, - c === this.state.namespace ? true : false - ) + this.addNotification(`${oldNick} disconnected`, c) ) this.setState({ messages }) } this.state.allNamespaces.map(n => { if (n.namespace === c && n.sockets.find(s => s === socketId)) { const messages = this.state.messages.concat( - this.formatMessage( - `${oldNick} disconnected`, - socketId, - '[server]', - c, - c === this.state.namespace ? true : false - ) + this.addNotification(`${oldNick} disconnected`, c) ) this.setState({ messages }) } @@ -92,13 +80,7 @@ class App extends React.Component { const user = this.state.allUsers.find(u => u.socketId === socketId) console.log(`${user.nick} joined ${namespace}`) const messages = this.state.messages.concat( - this.formatMessage( - `${user.nick} joined ${namespace}`, - socketId, - '[server]', - namespace, - namespace === this.state.namespace ? true : false - ) + this.addNotification(`${user.nick} joined ${namespace}`, namespace) ) this.setState({ messages }) }) @@ -108,13 +90,7 @@ class App extends React.Component { const user = this.state.allUsers.find(u => u.socketId === socketId) console.log(`${user.nick} left ${namespace}`) const messages = this.state.messages.concat( - this.formatMessage( - `${user.nick} left ${namespace}`, - socketId, - '[server]', - namespace, - namespace === this.state.namespace ? true : false - ) + this.addNotification(`${user.nick} left ${namespace}`, namespace) ) this.setState({ messages }) }) @@ -128,26 +104,14 @@ class App extends React.Component { this.state.chats.map(c => { if (c === user.socketId) { const messages = this.state.messages.concat( - this.formatMessage( - `${oldNick} is now known as ${user.nick}`, - user.socketId, - '[server]', - c, - c === this.state.namespace ? true : false - ) + this.addNotification(`${oldNick} is now known as ${user.nick}`, c) ) this.setState({ messages }) } this.state.allNamespaces.map(n => { if (n.namespace === c && n.sockets.find(s => s === user.socketId)) { const messages = this.state.messages.concat( - this.formatMessage( - `${oldNick} is now known as ${user.nick}`, - user.socketId, - '[server]', - c, - c === this.state.namespace ? true : false - ) + this.addNotification(`${oldNick} is now known as ${user.nick}`, c) ) this.setState({ messages }) } @@ -222,6 +186,16 @@ class App extends React.Component { } this.setState({ message: '' }) } + + addNotification(text, namespace) { + return this.formatMessage( + text, + 'server', + 'server', + namespace, + namespace === this.state.namespace ? true : false + ) + } formatMessage(text, from, nick, to, read) { if (!text || !from || !to || !nick) { console.log( -- 2.49.0 From 5bf5c2b9837a3e9b4e6070b280d69be1afbe8955 Mon Sep 17 00:00:00 2001 From: data Date: Sun, 28 Jul 2019 14:18:37 +0100 Subject: [PATCH 09/11] grey out notifications --- src/App.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index a712d2d..b894753 100644 --- a/src/App.js +++ b/src/App.js @@ -431,13 +431,18 @@ class App extends React.Component { (m.from === this.state.user.socketId && m.to === this.state.namespace) ) - .map(m => ( -
-
{m.date}
-
{m.nick}
-
{m.text}
-
- ))} + .map(m => { + const rowColor = m.from === 'server' ? 'text-secondary' : '' + return ( +
+
{m.date}
+ {m.from === 'server' ? null : ( +
{m.nick}:
+ )} +
{m.text}
+
+ ) + })}
Date: Sat, 27 Jul 2019 18:20:42 -0400 Subject: [PATCH 10/11] create component + diverse refactoring backend - namespace => room - add listener 'get all clients' frontend - remove variables from initalState - create components Sidebar, TopBar, Messages, MessageEntryForm --- server/socket/index.js | 39 +++--- src/App.js | 214 +++++------------------------ src/components/MessageEntryForm.js | 42 ++++++ src/components/Messages.js | 37 +++++ src/components/Sidebar.js | 60 ++++++++ src/components/Topbar.js | 56 ++++++++ src/components/index.js | 3 + 7 files changed, 250 insertions(+), 201 deletions(-) create mode 100644 src/components/MessageEntryForm.js create mode 100644 src/components/Messages.js create mode 100644 src/components/Sidebar.js create mode 100644 src/components/Topbar.js create mode 100644 src/components/index.js diff --git a/server/socket/index.js b/server/socket/index.js index 5dd4e98..129bb89 100644 --- a/server/socket/index.js +++ b/server/socket/index.js @@ -18,23 +18,27 @@ module.exports = io => { io.to(message.to).emit('message', message) }) - socket.on('join', namespace => { - const socketId = socket.id - console.log(`${socketId} joins ${namespace}`) - socket.join(namespace, () => { - io.to(namespace).emit('user joined', { namespace, socketId }) - sendNamespace(namespace) + socket.on('join', room => { + console.log(`${socket.id} joins ${room}`) + socket.join(room, () => { + io.to(room).emit('user joined', { room, socketId: socket.id }) + io.of(room).clients((error, clients) => { + if (error) throw error + socket.emit('got client list', clients) + }) }) }) - - socket.on('part', namespace => { - const socketId = socket.id - console.log('part', socketId, namespace) - socket.leave(namespace, () => { - io.to(namespace).emit('user left', { namespace, socketId }) - sendNamespace(namespace) + socket.on('get all clients', () => { + io.clients((e, c) => { + if (e) console.log(e) + console.log('all clients:', c) }) }) + socket.on('part', namespace => { + console.log('part', socket.id, namespace) + io.to(namespace).emit('user left', { namespace, socketId: socket.id }) + socket.leave(namespace) + }) socket.on('change nick', nick => { io.emit('changed nick', { socketId: socket.id, nick: nick }) @@ -56,14 +60,5 @@ module.exports = io => { console.log(`${socket.id} has disconnected.`) //sendNamespaces() }) - - const sendNamespace = namespace => { - if (!io.sockets.adapter.rooms[namespace]) return - const newNamespace = { - namespace, - sockets: Object.keys(io.sockets.adapter.rooms[namespace].sockets), - } - io.emit('got namespace', newNamespace) - } }) } diff --git a/src/App.js b/src/App.js index b894753..e542c72 100644 --- a/src/App.js +++ b/src/App.js @@ -1,19 +1,16 @@ import React from 'react' import socket from './socket' +import { Topbar, Sidebar, Messages } from './components' -import { Navbar, Nav, Dropdown, Button } from 'react-bootstrap' import uuid from 'uuid' import moment from 'moment' const initialState = { loading: true, - messages: [], - message: '', - allNamespaces: [], - chats: [], - namespace: '/', - allUsers: [], - user: {}, + allSockets: [], + allMessages: [], + buffers: [], + buffer: '/', } class App extends React.Component { @@ -21,10 +18,13 @@ class App extends React.Component { super() this.state = initialState this.socket = socket - this.handleChange = this.handleChange.bind(this) this.submitMessage = this.submitMessage.bind(this) this.join = this.join.bind(this) this.readMessages = this.readMessages.bind(this) + this.countUnreadMessages = this.countUnreadMessages.bind(this) + this.readMessages = this.readMessages.bind(this) + this.activateChat = this.activateChat.bind(this) + this.query = this.query.bind(this) } async fetchData() { @@ -34,10 +34,9 @@ class App extends React.Component { componentDidMount() { this.fetchData() this.socket.on('connect', () => { - console.log(`I am ${socket.id}`) + console.log(`I am ${this.socket.id}`) this.join('#projex') - const user = { socketId: this.socket.id, nick: this.socket.id } - this.setState({ user }) + this.socket.nick = this.socket.id }) this.socket.on('user connected', user => { @@ -165,10 +164,6 @@ class App extends React.Component { }) } - handleChange(e) { - this.setState({ [e.target.name]: e.target.value }) - } - parseCommand() { const args = this.state.message.split(' ') const cmd = args[0].slice(1) @@ -213,15 +208,14 @@ class App extends React.Component { } return message } - submitMessage(e) { - e.preventDefault() - - if (this.state.message[0] === '/') return this.parseCommand() - if (!this.state.message.length > 0) return + submitMessage(msg) { + console.log(msg) + if (msg[0] === '/') return this.parseCommand() + if (!msg.length > 0) return this.sendMessage( this.formatMessage( - this.state.message, + msg, this.state.user.socketId, this.state.user.nick, this.state.namespace @@ -236,13 +230,13 @@ class App extends React.Component { renderError() { return
something went wrong.
} - join(namespace) { - if (namespace.slice(0, 1) !== '#') return alert('Use a leading #, silly!') - if (this.state.chats.find(c => c === namespace)) return - console.log('joining', namespace) - const chats = this.state.chats.concat(namespace) - this.socket.emit('join', namespace) - this.setState({ namespace, chats }) + join(room) { + if (room[0] !== '#') return alert('Use a leading #, silly!') + if (this.state.chats.find(c => c === room)) return + console.log('joining', room) + const chats = this.state.chats.concat(room) + this.socket.emit('join', room, ack => console.log('ack', ack)) + this.setState({ room, chats }) } part(namespace) { console.log(`leaving ${namespace}`) @@ -309,159 +303,21 @@ class App extends React.Component { : this.state.namespace === this.state.user.socketId ? 'Namespace' : 'Chat with' - + console.log(this.socket) return (
- {/*Navbar*/} - - - - - - - +
- {/*Side Bar 2*/} -
-
Chats
- - {this.state.chats.map(c => ( - - ))} -
- - {/*Main Section*/} -
- {/*Main Section Header*/} -
-

- {title} {this.state.namespace} -

-
- {/*Main Section Content*/} -
-
- {this.state.messages - .filter( - m => - (m.to[0] === '#' && m.to === this.state.namespace) || - (m.to[0] !== '#' && m.from === this.state.namespace) || - (m.from === this.state.user.socketId && - m.to === this.state.namespace) - ) - .map(m => { - const rowColor = m.from === 'server' ? 'text-secondary' : '' - return ( -
-
{m.date}
- {m.from === 'server' ? null : ( -
{m.nick}:
- )} -
{m.text}
-
- ) - })} -
- - - - -
-
+ +
) diff --git a/src/components/MessageEntryForm.js b/src/components/MessageEntryForm.js new file mode 100644 index 0000000..af420cf --- /dev/null +++ b/src/components/MessageEntryForm.js @@ -0,0 +1,42 @@ +import React from 'react' +import { Button } from 'react-bootstrap' + +class MessageEntryForm extends React.Component { + constructor() { + super() + this.state = { message: '' } + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + + handleChange(e) { + this.setState({ [e.target.name]: e.target.value }) + } + + handleSubmit(e) { + e.preventDefault() + this.props.submitMessage(this.state.message) + } + render() { + return ( +
+ + +
+ ) + } +} + +export default MessageEntryForm diff --git a/src/components/Messages.js b/src/components/Messages.js new file mode 100644 index 0000000..03f08d1 --- /dev/null +++ b/src/components/Messages.js @@ -0,0 +1,37 @@ +import React from 'react' +import MessageEntryForm from './MessageEntryForm' +const Messages = props => { + const { title, namespace, messages, user, submitMessage } = props + return ( +
+ {/*Main Section Header*/} +
+

+ {title} {namespace} +

+
+ {/*Main Section Content*/} +
+
+ {messages + .filter( + m => + (m.to[0] === '#' && m.to === namespace) || + (m.to[0] !== '#' && m.from === namespace) || + (m.from === user.socketId && m.to === namespace) + ) + .map(m => ( +
+
{m.date}
+
{m.nick}
+
{m.text}
+
+ ))} +
+ +
+
+ ) +} + +export default Messages diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js new file mode 100644 index 0000000..3f5bf32 --- /dev/null +++ b/src/components/Sidebar.js @@ -0,0 +1,60 @@ +import React from 'react' +import { Button } from 'react-bootstrap' + +const Sidebar = props => { + const { + activateChat, + chats, + allUsers, + countUnreadMessages, + allNamespaces, + } = props + return ( +
+
Chats
+ + {chats.map(c => ( + + ))} +
+ ) +} + +export default Sidebar diff --git a/src/components/Topbar.js b/src/components/Topbar.js new file mode 100644 index 0000000..7600b54 --- /dev/null +++ b/src/components/Topbar.js @@ -0,0 +1,56 @@ +import React from 'react' +import { Navbar, Nav, Dropdown } from 'react-bootstrap' + +const Topbar = props => { + const { allUsers, user, allNamespaces, query, join } = props + + return ( + + + + + + + ) +} + +export default Topbar diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..83c2698 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,3 @@ +export { default as Messages } from './Messages' +export { default as Sidebar } from './Sidebar' +export { default as Topbar } from './Topbar' -- 2.49.0 From a09d9a8b9da615cf9d07b805531e79ebc18c7b3d Mon Sep 17 00:00:00 2001 From: data Date: Sun, 28 Jul 2019 20:50:06 +0100 Subject: [PATCH 11/11] fix components backend - sendNamespaces => sendNamespace frontend - namespace => buffer - activateChate takes optional chats object to set it on state - improved message/error handling --- server/socket/index.js | 52 ++++-- src/App.js | 244 ++++++++++++----------------- src/components/MessageEntryForm.js | 73 ++++++++- src/components/Messages.js | 51 ++++-- src/components/Topbar.js | 4 +- 5 files changed, 246 insertions(+), 178 deletions(-) diff --git a/server/socket/index.js b/server/socket/index.js index 129bb89..65a1b38 100644 --- a/server/socket/index.js +++ b/server/socket/index.js @@ -14,6 +14,10 @@ module.exports = io => { ) socket.on('message', message => { + if (!message) { + console.log('Weird. Received empty message.') + return + } console.log('sending message to ', message.to, message) io.to(message.to).emit('message', message) }) @@ -21,6 +25,7 @@ module.exports = io => { socket.on('join', room => { console.log(`${socket.id} joins ${room}`) socket.join(room, () => { + sendNamespace(room) io.to(room).emit('user joined', { room, socketId: socket.id }) io.of(room).clients((error, clients) => { if (error) throw error @@ -34,31 +39,48 @@ module.exports = io => { console.log('all clients:', c) }) }) - socket.on('part', namespace => { - console.log('part', socket.id, namespace) - io.to(namespace).emit('user left', { namespace, socketId: socket.id }) - socket.leave(namespace) + socket.on('part', room => { + console.log('part', socket.id, room) + socket.leave(room, () => { + console.log(Object.keys(io.sockets.adapter.rooms)) + if (io.sockets.adapter.rooms[room]) { + Object.keys(io.sockets.adapter.rooms[room]['sockets']).map(s => { + sendNamespace(room, s) + return s + }) + } else { + console.log(`room ${room} disappeared`) + } + io.to(room).emit('user left', { room, socketId: socket.id }) + }) }) socket.on('change nick', nick => { - io.emit('changed nick', { socketId: socket.id, nick: nick }) + console.log(`${users[socket.id]['nick']} changed nick to ${nick}`) + socket.broadcast.emit('changed nick', { socketId: socket.id, nick: nick }) users[socket.id]['nick'] = nick }) - socket.on('get namespaces', () => sendNamespaces()) - - const sendNamespaces = () => { - const namespaces = Object.keys(io.sockets.adapter.rooms).map(k => ({ - namespace: k, - sockets: Object.keys(io.sockets.adapter.rooms[k]['sockets']), - })) - io.emit('got namespaces', namespaces) + const sendNamespace = (room, socketId) => { + if (io.sockets.adapter.rooms[room]) { + const namespace = { + namespace: room, + sockets: Object.keys(io.sockets.adapter.rooms[room]['sockets']), + } + if (socketId) { + socket.emit('got namespace', namespace) + } else { + io.emit('got namespace', namespace) + } + } } socket.on('disconnect', async () => { - io.emit('user disconnected', socket.id) console.log(`${socket.id} has disconnected.`) - //sendNamespaces() + io.emit('user disconnected', socket.id) + // OPTIMIZE if there's an easy way to inform rooms the socket was part of + // they should be informed here + //socket.rooms.map(r => r.sockets.map(s => sendNamespace(r, s))) }) }) } diff --git a/src/App.js b/src/App.js index e542c72..edf24b2 100644 --- a/src/App.js +++ b/src/App.js @@ -7,6 +7,11 @@ import moment from 'moment' const initialState = { loading: true, + messages: [], + allNamespaces: [], + chats: [], + room: '/', + allUsers: [], allSockets: [], allMessages: [], buffers: [], @@ -18,9 +23,8 @@ class App extends React.Component { super() this.state = initialState this.socket = socket - this.submitMessage = this.submitMessage.bind(this) this.join = this.join.bind(this) - this.readMessages = this.readMessages.bind(this) + this.formatMessage = this.formatMessage.bind(this) this.countUnreadMessages = this.countUnreadMessages.bind(this) this.readMessages = this.readMessages.bind(this) this.activateChat = this.activateChat.bind(this) @@ -33,6 +37,9 @@ class App extends React.Component { componentDidMount() { this.fetchData() + + // EVENTS + this.socket.on('connect', () => { console.log(`I am ${this.socket.id}`) this.join('#projex') @@ -46,73 +53,64 @@ class App extends React.Component { }) this.socket.on('user disconnected', socketId => { - console.log(`${socketId} disconnected.`) + // mark user as offline + const oldNick = this.state.allUsers.find(u => u.socketId === socketId) + .nick + console.log(`${oldNick} disconnected.`) const allUsers = this.state.allUsers.map(u => { + // TODO find a better way to preserve existing chat windows if (u.socketId === socketId) u.offline = true return u }) - const oldNick = allUsers.find(u => u.socketId === socketId).nick this.setState({ allUsers }) - // inform channels user was part of - this.state.chats.map(c => { - if (c === socketId) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} disconnected`, c) - ) - this.setState({ messages }) - } - this.state.allNamespaces.map(n => { - if (n.namespace === c && n.sockets.find(s => s === socketId)) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} disconnected`, c) - ) - this.setState({ messages }) - } - return n + // inform sockets the user was chatting with + this.state.chats + .filter(c => c === socketId) + .map(c => { + this.addNotification(`${oldNick} disconnected`, this.socket.id, c) + return c }) - return c + // inform channels the user was part of and update their socket list + const allNamespaces = this.state.allNamespaces.map(n => { + if (n.sockets.find(s => s === socketId)) { + n.sockets = n.sockets.filter(s => s === socketId) + this.addNotification(`${oldNick} disconnected`, n.namespace) + } + return n }) + this.setState({ allNamespaces }) }) this.socket.on('user joined', data => { - const { namespace, socketId } = data + const { room, socketId } = data const user = this.state.allUsers.find(u => u.socketId === socketId) - console.log(`${user.nick} joined ${namespace}`) - const messages = this.state.messages.concat( - this.addNotification(`${user.nick} joined ${namespace}`, namespace) - ) - this.setState({ messages }) + console.log(`${user.nick} joined ${room}`) + this.addNotification(`${user.nick} joined ${room}`, room) }) this.socket.on('user left', data => { - const { namespace, socketId } = data + const { room, socketId } = data const user = this.state.allUsers.find(u => u.socketId === socketId) - console.log(`${user.nick} left ${namespace}`) - const messages = this.state.messages.concat( - this.addNotification(`${user.nick} left ${namespace}`, namespace) - ) - this.setState({ messages }) + console.log(`${user.nick} left ${room}`) + this.addNotification(`${user.nick} left ${room}`, room) }) this.socket.on('changed nick', user => { - if (user.socketId === this.state.user.socketId) return console.log(`${user.socketId} is now known as ${user.nick}.`) const allUsers = this.state.allUsers const oldNick = allUsers.find(u => u.socketId === user.socketId).nick // inform channels user is part of this.state.chats.map(c => { if (c === user.socketId) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} is now known as ${user.nick}`, c) + this.addNotification( + `${oldNick} is now known as ${user.nick}`, + this.socket.id, + c ) - this.setState({ messages }) } this.state.allNamespaces.map(n => { if (n.namespace === c && n.sockets.find(s => s === user.socketId)) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} is now known as ${user.nick}`, c) - ) - this.setState({ messages }) + this.addNotification(`${oldNick} is now known as ${user.nick}`, c) } return n }) @@ -120,7 +118,7 @@ class App extends React.Component { }) // update nick allUsers.map(u => { - if (u.socketId === user.socketId) u.nick = user.nick + if (u.socketId === user.socketId) u.nick = this.socket.nick return u }) this.setState({ allUsers }) @@ -134,11 +132,6 @@ class App extends React.Component { this.setState({ allNamespaces }) }) - this.socket.on('got namespaces', allNamespaces => { - console.log('got namespaces:', allNamespaces) - this.setState({ allNamespaces }) - }) - this.socket.on('got users', allUsers => { console.log('got users:', allUsers) this.setState({ allUsers }) @@ -146,7 +139,7 @@ class App extends React.Component { this.socket.on('message', msg => { if ( - msg.to === this.state.user.socketId && + msg.to === this.socket.id && !this.state.chats.find(c => c === msg.from) ) { console.log(`${msg.from} wants to chat with me.`) @@ -154,50 +147,65 @@ class App extends React.Component { this.setState({ chats }) } if ( - (msg.to[0] === '#' && msg.to === this.state.namespace) || - (msg.to[0] !== '#' && msg.from === this.state.namespace) + (msg.to[0] === '#' && msg.to === this.state.buffer) || + (msg.to[0] !== '#' && msg.from === this.state.buffer) ) msg.read = true const messages = this.state.messages.concat(msg) - console.log(msg) this.setState({ messages }) }) } - parseCommand() { - const args = this.state.message.split(' ') - const cmd = args[0].slice(1) - if (cmd === 'join') { - if (!args[1]) { - alert('did you forget a channel to join?') - return - } - this.join(args[1]) - } else if (cmd === 'part' || cmd === 'leave') { - this.part(args[1] ? args[1] : this.state.namespace) - } else if (cmd === 'nick') this.nick(args[1]) - else { - alert(`unknown command: ${cmd}`) + // FUNCTIONS + + join(room) { + if (room[0] !== '#') return alert('Use a leading #, silly!') + if (this.state.chats.find(c => c === room)) return + console.log('joining', room) + const chats = this.state.chats.concat(room) + this.socket.emit('join', room, ack => console.log('ack', ack)) + this.activateChat(room, chats) + } + query(user) { + if (this.state.chats.find(c => c === user.socketId)) { + this.activateChat(user.socketId) + return } this.setState({ message: '' }) + console.log(`starting chat with ${user.nick}`) + const chats = this.state.chats.concat(user.socketId) + this.activateChat(user.socketId, chats) } - addNotification(text, namespace) { - return this.formatMessage( - text, - 'server', - 'server', - namespace, - namespace === this.state.namespace ? true : false + addNotification(text, to, from) { + if (!text || !to) { + console.log('[addNotification] Missing parameter(s)', text, to) + return + } + const messages = this.state.messages.concat( + this.formatMessage( + text, + from ? from : 'server', + 'server', + to, + to === this.state.buffer ? true : false + ) ) + this.setState({ messages }) } formatMessage(text, from, nick, to, read) { if (!text || !from || !to || !nick) { console.log( - '[formatMessage] Dropping malformed message (requiring text, from, nick and to).' + '[formatMessage] Dropping malformed message (requiring text, from, nick and to).', + text, + from, + nick, + to, + read ) + return } - const message = { + return { id: uuid(), date: moment().format('MMMM D YYYY, h:mm a'), text: text, @@ -206,22 +214,6 @@ class App extends React.Component { to: to, read: read, } - return message - } - submitMessage(msg) { - console.log(msg) - if (msg[0] === '/') return this.parseCommand() - if (!msg.length > 0) return - - this.sendMessage( - this.formatMessage( - msg, - this.state.user.socketId, - this.state.user.nick, - this.state.namespace - ) - ) - this.setState({ message: '' }) } renderLoading() { return
loading...
@@ -230,54 +222,10 @@ class App extends React.Component { renderError() { return
something went wrong.
} - join(room) { - if (room[0] !== '#') return alert('Use a leading #, silly!') - if (this.state.chats.find(c => c === room)) return - console.log('joining', room) - const chats = this.state.chats.concat(room) - this.socket.emit('join', room, ack => console.log('ack', ack)) - this.setState({ room, chats }) - } - part(namespace) { - console.log(`leaving ${namespace}`) - socket.emit('part', namespace) - const chats = this.state.chats.filter(c => c !== namespace) - this.setState({ chats, namespace: '/' }) - } - nick(nick) { - if (nick[0] === '#') { - alert('nick cannot start with #, sorry!') - return - } - console.log(`Changing nick to ${nick}`) - const user = this.state.user - user.nick = nick - this.setState({ user }) - this.socket.emit('change nick', nick) - } - query(user) { - if (this.state.chats.find(c => c === user.socketId)) { - this.setState({ namespace: user.socketId }) - return - } - console.log(`starting chat with ${user.nick}`) - const chats = this.state.chats.concat(user.socketId) - this.setState({ namespace: user.socketId, chats }) - } - sendMessage(msg) { - this.socket.emit('message', msg) - if (msg.to[0] !== '#') { - if (msg.to === this.state.namespace) msg.read = true - const messages = this.state.messages.concat(msg) - this.setState({ messages }) - } - } - countUnreadMessages(nsp) { return this.state.messages.filter( m => - !m.read && - (m.to === nsp || (m.to === this.state.user.socketId && m.from === nsp)) + !m.read && (m.to === nsp || (m.to === this.socket.id && m.from === nsp)) ).length } @@ -292,21 +240,26 @@ class App extends React.Component { this.setState({ messages }) } - activateChat(namespace) { - this.readMessages(namespace) - this.setState({ namespace }) + activateChat(buffer, chats) { + this.readMessages(buffer) + this.setState({ buffer }) + if (chats) this.setState({ chats }) } renderApp() { const title = - this.state.namespace[0] === '#' + this.state.buffer[0] === '#' ? 'Channel' - : this.state.namespace === this.state.user.socketId - ? 'Namespace' + : this.state.buffer === this.socket.id + ? 'buffer' : 'Chat with' - console.log(this.socket) return (
- +
diff --git a/src/components/MessageEntryForm.js b/src/components/MessageEntryForm.js index af420cf..52d1b59 100644 --- a/src/components/MessageEntryForm.js +++ b/src/components/MessageEntryForm.js @@ -12,13 +12,76 @@ class MessageEntryForm extends React.Component { handleChange(e) { this.setState({ [e.target.name]: e.target.value }) } - handleSubmit(e) { e.preventDefault() - this.props.submitMessage(this.state.message) + this.submitMessage(this.state.message) } + submitMessage(msg) { + if (msg[0] === '/') return this.parseCommand() + if (!msg.length > 0) return + + this.sendMessage( + this.props.formatMessage( + msg, + this.props.socket.id, + this.props.socket.nick, + this.props.buffer + ) + ) + this.setState({ message: '' }) + } + sendMessage(msg) { + if (!msg) { + console.log('[sendMessage] Got no message. Fix your code!') + return + } + this.props.socket.emit('message', msg) + if (msg.to[0] !== '#') { + if (msg.to === this.props.buffer) msg.read = true + const messages = this.props.messages.concat(msg) + this.setState({ messages }) + } + } + parseCommand() { + const args = this.state.message.split(' ') + const cmd = args[0].slice(1) + if (cmd === 'join') { + if (!args[1]) { + alert('did you forget a channel to join?') + return + } + this.props.join(args[1]) + } else if (cmd === 'part' || cmd === 'leave') { + this.part(args[1] ? args[1] : this.props.buffer) + } else if (cmd === 'nick') { + if (!args[1]) { + alert('A new nick could be useful.') + return + } + this.nick(args[1]) + } else { + alert(`unknown command: ${cmd}`) + } + this.setState({ message: '' }) + } + part(room) { + console.log(`leaving ${room}`) + this.props.socket.emit('part', room) + const chats = this.props.chats.filter(c => c !== room) + this.props.activateChat('/', chats) + } + nick(nick) { + if (nick[0] === '#') { + alert('nick cannot start with #, sorry!') + return + } + console.log(`Changing nick to ${nick}`) + this.props.socket.nick = nick + this.props.socket.emit('change nick', nick) + } + render() { - return ( + return this.props.socket.id ? (
- submit as {this.props.user.nick} + submit as {this.props.socket.nick}
+ ) : ( +
Disconnected
) } } diff --git a/src/components/Messages.js b/src/components/Messages.js index 03f08d1..fc822c5 100644 --- a/src/components/Messages.js +++ b/src/components/Messages.js @@ -1,13 +1,23 @@ import React from 'react' import MessageEntryForm from './MessageEntryForm' const Messages = props => { - const { title, namespace, messages, user, submitMessage } = props + const { + title, + buffer, + chats, + messages, + socket, + activateChat, + join, + formatMessage, + submitMessage, + } = props return (
{/*Main Section Header*/}

- {title} {namespace} + {title} {buffer}

{/*Main Section Content*/} @@ -16,19 +26,34 @@ const Messages = props => { {messages .filter( m => - (m.to[0] === '#' && m.to === namespace) || - (m.to[0] !== '#' && m.from === namespace) || - (m.from === user.socketId && m.to === namespace) + (m.to[0] === '#' && m.to === buffer) || + (m.to[0] !== '#' && m.from === buffer) || + (m.from === socket.socketId && m.to === buffer) ) - .map(m => ( -
-
{m.date}
-
{m.nick}
-
{m.text}
-
- ))} + .map(m => { + console.log(m) + const rowColor = m.nick === 'server' ? 'text-secondary' : '' + return ( +
+
{m.date}
+ {m.nick === 'server' ? null : ( +
{m.nick}:
+ )} +
{m.text}
+
+ ) + })}
- + ) diff --git a/src/components/Topbar.js b/src/components/Topbar.js index 7600b54..0651ad0 100644 --- a/src/components/Topbar.js +++ b/src/components/Topbar.js @@ -2,7 +2,7 @@ import React from 'react' import { Navbar, Nav, Dropdown } from 'react-bootstrap' const Topbar = props => { - const { allUsers, user, allNamespaces, query, join } = props + const { allUsers, socket, allNamespaces, query, join } = props return ( @@ -16,7 +16,7 @@ const Topbar = props => { {allUsers - .filter(u => u.socketId !== user.id && u.offline !== true) + .filter(u => u.socketId !== socket.id && u.offline !== true) .map(u => (