Added title bar and basic message and command input

This commit is contained in:
khlieng 2015-01-21 03:06:34 +01:00
parent 508a04cf4c
commit f42d6011c6
23 changed files with 399 additions and 83 deletions

View file

@ -1,4 +1,5 @@
var Reflux = require('reflux');
var sock = require('../socket.js')('/ws');
var channelActions = Reflux.createActions([
@ -6,7 +7,9 @@ var channelActions = Reflux.createActions([
'joined',
'part',
'parted',
'quit',
'setUsers',
'setTopic',
'load'
]);
@ -26,10 +29,18 @@ sock.on('part', function(data) {
channelActions.parted(data.user, data.server, data.channels[0]);
});
sock.on('quit', function(data) {
channelActions.quit(data.user, data.server);
});
sock.on('users', function(data) {
channelActions.setUsers(data.users, data.server, data.channel);
});
sock.on('topic', function(data) {
channelActions.setTopic(data.topic, data.server, data.channel);
});
sock.on('channels', function(data) {
channelActions.load(data);
});

View file

@ -1,13 +1,19 @@
var Reflux = require('reflux');
var sock = require('../socket.js')('/ws');
var messageActions = Reflux.createActions([
'send',
'add',
'selectTab'
]);
messageActions.send.preEmit = function() {
messageActions.send.preEmit = function(message, to, server) {
sock.send('chat', {
server: server,
to: to,
message: message
});
};
module.exports = messageActions;

View file

@ -1,4 +1,5 @@
var Reflux = require('reflux');
var sock = require('../socket.js')('/ws');
var serverActions = Reflux.createActions([

View file

@ -26,7 +26,7 @@ sock.on('connect', function() {
channelActions.join({
server: 'irc.freenode.net',
channels: [ '#stuff', '#go-nuts' ]
channels: [ '#stuff' ]
});
});
@ -35,7 +35,8 @@ channelActions.joined.listen(function(user, server, channel) {
server: server,
from: '',
to: channel,
message: user + ' joined the channel'
message: user + ' joined the channel',
type: 'info'
});
});
@ -44,7 +45,8 @@ channelActions.parted.listen(function(user, server, channel) {
server: server,
from: '',
to: channel,
message: user + ' left the channel'
message: user + ' left the channel',
type: 'info'
});
});
@ -56,15 +58,6 @@ sock.on('pm', function(data) {
messageActions.add(data);
});
sock.on('topic', function(data) {
messageActions.add({
server: data.server,
from: '',
to: data.channel,
message: data.topic
});
});
sock.on('motd', function(data) {
_.each(data.content.split('\n'), function(line) {
messageActions.add({

View file

@ -1,15 +1,14 @@
var React = require('react');
var TabList = require('./TabList.jsx');
var MessageBox = require('./MessageBox.jsx');
var UserList = require('./UserList.jsx');
var Chat = require('./Chat.jsx');
var App = React.createClass({
render: function() {
return (
<div>
<TabList />
<MessageBox />
<UserList />
<Chat />
</div>
);
}

View file

@ -0,0 +1,21 @@
var React = require('react');
var ChatTitle = require('./ChatTitle.jsx');
var MessageBox = require('./MessageBox.jsx');
var MessageInput = require('./MessageInput.jsx');
var UserList = require('./UserList.jsx');
var Chat = React.createClass({
render: function() {
return (
<div>
<ChatTitle />
<MessageBox />
<MessageInput />
<UserList />
</div>
)
}
});
module.exports = Chat;

View file

@ -0,0 +1,48 @@
var React = require('react');
var Reflux = require('reflux');
var channelStore = require('../stores/channel.js');
var selectedTabStore = require('../stores/selectedTab.js');
var ChatTitle = React.createClass({
mixins: [
Reflux.connect(channelStore, 'channels'),
Reflux.connect(selectedTabStore, 'selectedTab')
],
getInitialState: function() {
return {
channels: channelStore.getState(),
selectedTab: selectedTabStore.getState()
};
},
render: function() {
var tab = this.state.selectedTab;
var title;
if (tab.channel) {
var channel = this.state.channels[tab.server][tab.channel];
if (channel) {
title = tab.channel
title += ' [';
title += channel.users.length;
title += ']';
if (channel.topic) {
title += ': ' + channel.topic;
}
}
} else {
title = tab.server;
}
return (
<div className="chat-title-bar">
<span className="chat-title" title={title}>{title}</span>
</div>
);
}
});
module.exports = ChatTitle;

View file

@ -1,6 +1,7 @@
var React = require('react');
var Reflux = require('reflux');
var _ = require('lodash');
var messageStore = require('../stores/message.js');
var selectedTabStore = require('../stores/selectedTab.js');
@ -32,7 +33,13 @@ var MessageBox = React.createClass({
render: function() {
var tab = this.state.selectedTab.channel || this.state.selectedTab.server;
var messages = _.map(this.state.messages[tab], function(message) {
return <p>{message.from ? message.from + ': ' : null}{message.message}</p>;
var messageClass = 'message';
switch (message.type) {
case 'info':
messageClass += ' message-info';
break;
}
return <p className={messageClass}>{message.from ? message.from + ': ' : null}{message.message}</p>;
});
return (

View file

@ -0,0 +1,65 @@
var React = require('react');
var Reflux = require('reflux');
var selectedTabStore = require('../stores/selectedTab.js');
var messageActions = require('../actions/message.js');
var channelActions = require('../actions/channel.js');
function dispatchCommand(cmd, channel, server) {
var params = cmd.slice(1).split(' ');
switch (params[0].toLowerCase()) {
case 'join':
if (params[1]) {
channelActions.join({
server: server,
channels: [params[1]]
});
}
break;
case 'part':
if (channel) {
channelActions.part({
server: server,
channels: [channel]
});
}
break;
}
}
var MessageInput = React.createClass({
mixins: [
Reflux.connect(selectedTabStore, 'selectedTab')
],
getInitialState: function() {
return {
selectedTab: selectedTabStore.getState()
};
},
handleKey: function(e) {
if (e.which === 13 && e.target.value) {
var tab = this.state.selectedTab;
if (e.target.value.charAt(0) === '/') {
dispatchCommand(e.target.value, tab.channel, tab.server);
} else {
messageActions.send(e.target.value, tab.channel, tab.server);
}
e.target.value = '';
}
},
render: function() {
return (
<div className="message-input-wrap">
<input className="message-input" type="text" onKeyDown={this.handleKey} />
</div>
);
}
});
module.exports = MessageInput;

View file

@ -1,6 +1,7 @@
var React = require('react');
var Reflux = require('reflux');
var _ = require('lodash');
var channelStore = require('../stores/channel.js');
var selectedTabStore = require('../stores/selectedTab.js');
@ -20,11 +21,14 @@ var UserList = React.createClass({
render: function() {
var users = null;
var tab = this.state.selectedTab;
if (tab.channel) {
users = _.map(this.state.channels[tab.server][tab.channel].users, function(user) {
return <p>{user}</p>;
});
var channel = this.state.channels[tab.server][tab.channel];
if (channel) {
users = _.map(channel.users, function(user) {
return <p>{user}</p>;
});
}
}
return (

View file

@ -1,4 +1,5 @@
var EventEmitter = require('events').EventEmitter;
var _ = require('lodash');
var sockets = {};
@ -8,7 +9,7 @@ function createSocket(path) {
return sockets[path];
} else {
var ws = new WebSocket('ws://' + window.location.host + path);
var sock = {
send: function(type, data) {
ws.send(JSON.stringify({ type: type, request: data }));

View file

@ -1,5 +1,6 @@
var Reflux = require('reflux');
var _ = require('lodash');
var actions = require('../actions/channel.js');
var channels = {};
@ -36,16 +37,29 @@ var channelStore = Reflux.createStore({
this.trigger(channels);
},
quit: function(user, server) {
_.each(channels[server], function(channel) {
_.pull(channel.users, user);
});
this.trigger(channels);
},
setUsers: function(users, server, channel) {
initChannel(server, channel);
channels[server][channel].users = users;
this.trigger(channels);
},
setTopic: function(topic, server, channel) {
channels[server][channel].topic = topic;
this.trigger(channels);
},
load: function(storedChannels) {
_.each(storedChannels, function(channel) {
initChannel(channel.server, channel.name);
channels[channel.server][channel.name].users = channel.users;
channels[channel.server][channel.name].topic = channel.topic;
});
this.trigger(channels);
},

View file

@ -1,25 +1,40 @@
var Reflux = require('reflux');
var actions = require('../actions/message.js');
var messages = {};
function addMessage(message, dest) {
if (!(dest in messages)) {
messages[dest] = [message];
} else {
messages[dest].push(message);
}
}
var messageStore = Reflux.createStore({
init: function() {
this.listenToMany(actions);
},
send: function(message, to, server) {
addMessage({
server: server,
from: 'self',
to: to,
message: message
}, to);
this.trigger(messages);
},
add: function(message) {
var dest = message.to || message.from;
if (message.from.indexOf('.') !== -1) {
dest = message.server;
}
if (!(dest in messages)) {
messages[dest] = [message];
} else {
messages[dest].push(message);
}
addMessage(message, dest);
this.trigger(messages);
},

View file

@ -1,11 +1,15 @@
var Reflux = require('reflux');
var _ = require('lodash');
var actions = require('../actions/tab.js');
var channelActions = require('../actions/channel.js');
var selectedTab = {};
var selectedTabStore = Reflux.createStore({
init: function() {
this.listenToMany(actions);
this.listenTo(channelActions.part, 'part');
},
select: function(server, channel) {
@ -14,6 +18,19 @@ var selectedTabStore = Reflux.createStore({
this.trigger(selectedTab);
},
part: function(data) {
var self = this;
if (data.server === selectedTab.server) {
_.each(data.channels, function(channel) {
if (channel === selectedTab.channel) {
delete selectedTab.channel;
self.trigger(selectedTab);
return;
}
});
}
},
getState: function() {
return selectedTab;
}

View file

@ -1,4 +1,5 @@
var Reflux = require('reflux');
var actions = require('../actions/server.js');
var servers = {};

View file

@ -1,6 +1,7 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
@ -9,6 +10,14 @@ body {
color: #FFF;
}
input {
font: 16px Inconsolata, sans-serif;
background: rgba(0,0,0,0.25);
color: #FFF;
outline: none;
border: none;
}
p {
line-height: 1.5;
}
@ -19,7 +28,7 @@ p {
top: 0;
bottom: 0;
right: 200px;
padding: 20px;
padding: 15px;
overflow: auto;
}
@ -31,23 +40,64 @@ p {
color: #AAA;
}
.messagebox {
.chat-title-bar {
position: fixed;
left: 200px;
top: 0;
bottom: 0;
right: 200px;
padding: 20px;
height: 50px;
padding: 0 15px;
line-height: 50px;
background: rgba(0,0,0,0.25);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-title {
}
.messagebox {
position: fixed;
left: 200px;
top: 50px;
bottom: 50px;
right: 200px;
padding: 15px;
overflow: auto;
z-index: 1;
}
.message {
}
.message-info {
color: #666;
}
.message-input-wrap {
position: fixed;
left: 200px;
bottom: 0px;
right: 0;
height: 50px;
z-index: 1;
}
.message-input {
width: 100%;
height: 100%;
padding: 15px;
}
.userlist {
position: fixed;
top: 0;
bottom: 0;
bottom: 50px;
right: 0;
width: 200px;
padding: 20px;
padding: 15px;
overflow: auto;
}