IRC output gets queued until RPL_WELCOME, added tcp timeouts and error handling, store selected tab in localStorage, more design work, upgraded to lodash 3.0.0

This commit is contained in:
khlieng 2015-01-30 00:38:51 +01:00
parent 5c6c43e017
commit 3c02b00303
18 changed files with 268 additions and 53 deletions

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>IRC</title>
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<link href="//fonts.googleapis.com/css?family=Montserrat|Roboto" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body>

View file

@ -8,11 +8,12 @@ var serverActions = Reflux.createActions([
'load'
]);
serverActions.connect.preEmit = function(server, nick, username) {
serverActions.connect.preEmit = function(server, nick, username, tls) {
socket.send('connect', {
server: server,
nick: nick,
username: username
username: username,
tls: tls || false
});
};

View file

@ -14,8 +14,14 @@ socket.on('connect', function() {
socket.send('uuid', uuid);
serverActions.connect('irc.freenode.net', nick, 'username');
serverActions.connect('irc.quakenet.org', nick, 'username');
channelActions.join(['#stuff'], 'irc.freenode.net');
tabActions.select('irc.freenode.net');
channelActions.join(['#herp'], 'irc.quakenet.org');
});
socket.on('error', function(error) {
console.log(error.server + ': ' + error.message);
});
React.render(<App />, document.body);

View file

@ -20,18 +20,15 @@ var ChatTitle = React.createClass({
render: function() {
var tab = this.state.selectedTab;
var title;
var topic;
var usercount;
if (tab.channel) {
if (tab.channel && this.state.channels[tab.server]) {
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;
}
usercount = channel.users.length;
topic = channel.topic || '';
}
} else {
title = tab.server;
@ -39,7 +36,11 @@ var ChatTitle = React.createClass({
return (
<div className="chat-title-bar">
<span className="chat-title" title={title}>{title}</span>
<div>
<span className="chat-title">{title}</span>
<span className="chat-topic" title={topic}>{topic}</span>
</div>
<span className="chat-usercount">{usercount}</span>
</div>
);
}

View file

@ -2,6 +2,7 @@ var React = require('react');
var Reflux = require('reflux');
var _ = require('lodash');
var util = require('../util');
var messageStore = require('../stores/message');
var selectedTabStore = require('../stores/selectedTab');
@ -38,12 +39,20 @@ var MessageBox = React.createClass({
if (this.state.messages[tab.server] && dest) {
messages = _.map(this.state.messages[tab.server][dest], function(message) {
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 (
<p className={messageClass}>
<span className="message-time">{util.timestamp(message.time)}</span>
{ message.from ? <span className="message-sender">{message.from}</span> : null }
{message.message}
</p>
);
});
}

View file

@ -21,16 +21,41 @@ var TabList = React.createClass({
render: function() {
var self = this;
var tabClass;
var selected = this.state.selectedTab;
var tabs = _.map(this.state.channels, function(server, address) {
var channels = _.map(server, function(channel, name) {
return <p onClick={tabActions.select.bind(null, address, name)}>{name}</p>;
var channels = _.map(server, function(channel, name) {
if (address === selected.server &&
name === selected.channel) {
tabClass = 'selected';
} else {
tabClass = '';
}
return <p className={tabClass} onClick={tabActions.select.bind(null, address, name)}>{name}</p>;
});
channels.unshift(<p onClick={tabActions.select.bind(null, address, null)}>{address}</p>);
if (address === selected.server &&
selected.channel === null) {
tabClass = 'tab-server selected';
} else {
tabClass = 'tab-server';
}
channels.unshift(<p className={tabClass} onClick={tabActions.select.bind(null, address, null)}>{address}</p>);
return channels;
});
return (
<div className="tablist">{tabs}</div>
<div className="tablist">
<button className="button-connect">Add Network</button>
{tabs}
<div className="side-buttons">
<button>Settings</button>
</div>
</div>
);
}
});

View file

@ -22,7 +22,7 @@ var UserList = React.createClass({
var users = null;
var tab = this.state.selectedTab;
if (tab.channel) {
if (tab.channel && this.state.channels[tab.server]) {
var channel = this.state.channels[tab.server][tab.channel];
if (channel) {
users = _.map(channel.users, function(user) {

View file

@ -2,6 +2,7 @@ var Reflux = require('reflux');
var _ = require('lodash');
var actions = require('../actions/channel');
var serverActions = require('../actions/server');
var channels = {};
@ -80,6 +81,7 @@ function sortUsers(server, channel) {
var channelStore = Reflux.createStore({
init: function() {
this.listenToMany(actions);
this.listenTo(serverActions.connect, 'addServer');
},
part: function(partChannels, server) {
@ -161,6 +163,13 @@ var channelStore = Reflux.createStore({
this.trigger(channels);
},
addServer: function(server) {
if (!(server in channels)) {
channels[server] = {};
this.trigger(channels);
}
},
getState: function() {
return channels;
}

View file

@ -1,6 +1,6 @@
var Reflux = require('reflux');
var serverStore = require('../stores/server');
var serverStore = require('./server');
var actions = require('../actions/message');
var messages = {};
@ -26,7 +26,8 @@ var messageStore = Reflux.createStore({
server: server,
from: serverStore.getNick(server),
to: to,
message: message
message: message,
time: new Date()
}, to);
this.trigger(messages);
@ -38,6 +39,8 @@ var messageStore = Reflux.createStore({
dest = message.server;
}
message.time = new Date();
addMessage(message, dest);
this.trigger(messages);
},

View file

@ -5,6 +5,11 @@ var actions = require('../actions/tab');
var channelActions = require('../actions/channel');
var selectedTab = {};
var stored = localStorage.selectedTab;
if (stored) {
selectedTab = JSON.parse(stored);
}
var selectedTabStore = Reflux.createStore({
init: function() {
@ -36,4 +41,8 @@ var selectedTabStore = Reflux.createStore({
}
});
selectedTabStore.listen(function(selected) {
localStorage.selectedTab = JSON.stringify(selected);
});
module.exports = selectedTabStore;

View file

@ -1,6 +1,17 @@
var _ = require('lodash');
exports.UUID = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
};
exports.timestamp = function(date) {
date = date || new Date();
var h = _.padLeft(date.getHours(), 2, '0')
var m = _.padLeft(date.getMinutes(), 2, '0');
return h + ':' + m;
};

View file

@ -5,17 +5,24 @@
}
body {
font-family: Inconsolata, sans-serif;
font-family: Roboto, sans-serif;
background: #f0f0f0;
}
input {
font: 16px Inconsolata, sans-serif;
font: 16px Roboto, sans-serif;
border: none;
border-top: 1px solid #DDD;
outline: none;
}
button {
font: 16px Montserrat, sans-serif;
border: none;
outline: none;
cursor: pointer;
}
p {
line-height: 1.5;
}
@ -26,20 +33,64 @@ p {
top: 0;
bottom: 0;
width: 200px;
padding: 15px;
overflow: auto;
background: #272626;
background: #222;
color: #FFF;
font-family: Montserrat, sans-serif;
}
.tablist p {
padding: 3px 15px;
cursor: pointer;
}
.tablist p:hover {
background: #111;
}
.tablist p.selected {
padding-left: 10px;
border-left: 5px solid #6BB758;
}
.tab-server {
color: #AAA;
margin-top: 15px !important;
}
.button-connect {
width: 100%;
height: 50px;
background: #6BB758;
color: #FFF;
}
.button-connect:hover {
background: #7BBF6A;
}
.button-connect:active {
background: #6BB758;
}
.side-buttons {
position: fixed;
bottom: 0;
height: 50px;
width: 200px;
text-align: center;
}
.side-buttons button {
background: #333;
color: #FFF;
margin: 5px;
height: 40px;
width: 100px;
}
.chat-title-bar {
font-family: Montserrat, sans-serif;
position: fixed;
left: 200px;
top: 0;
@ -47,14 +98,34 @@ p {
height: 50px;
padding: 0 15px;
line-height: 50px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-bottom: 1px solid #DDD;
}
.chat-title-bar div {
position: absolute;
left: 0;
right: 100px;
white-space: nowrap;
overflow: hidden;
}
.chat-title {
font-size: 24px;
margin-left: 15px;
font-size: 28px;
}
.chat-topic {
display: none;
font: 16px Roboto, sans-serif;
line-height: 50px;
vertical-align: top;
color: #222;
margin-left: 15px;
}
.chat-usercount {
font-size: 20px;
float: right;
}
.messagebox {
@ -69,11 +140,25 @@ p {
}
.message {
padding-left: 50px;
text-indent: -50px;
}
.message span {
margin-right: 10px;
}
.message-info {
color: #666;
color: #999;
}
.message-time {
color: #999;
}
.message-sender {
color: #6BB758;
font: 16px Montserrat, sans-serif;
}
.message-input-wrap {
@ -100,4 +185,5 @@ p {
padding: 15px;
overflow: auto;
border-left: 1px solid #DDD;
overflow-x: hidden;
}