Add IRC connection status indicator

This commit is contained in:
Ken-Håvard Lieng 2016-01-13 18:53:54 +01:00
parent 83aef5df7b
commit f429a528ba
11 changed files with 124 additions and 38 deletions

File diff suppressed because one or more lines are too long

View File

@ -61,6 +61,7 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
.tablist p { .tablist p {
padding: 3px 15px; padding: 3px 15px;
padding-right: 10px;
cursor: pointer; cursor: pointer;
} }
@ -77,11 +78,30 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
border-left: 5px solid #6BB758; border-left: 5px solid #6BB758;
} }
.tab-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tab-server { .tab-server {
display: flex;
align-items: center;
color: #999; color: #999;
margin-top: 10px !important; margin-top: 10px !important;
} }
.tab-server .tab-content {
flex: 1;
margin-right: 5px;
}
.tab-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
}
.button-connect { .button-connect {
width: 100%; width: 100%;
height: 50px; height: 50px;

View File

@ -26,6 +26,7 @@ export const SET_NICK = 'SET_NICK';
export const SOCKET_CERT_FAIL = 'SOCKET_CERT_FAIL'; export const SOCKET_CERT_FAIL = 'SOCKET_CERT_FAIL';
export const SOCKET_CERT_SUCCESS = 'SOCKET_CERT_SUCCESS'; export const SOCKET_CERT_SUCCESS = 'SOCKET_CERT_SUCCESS';
export const SOCKET_CHANNELS = 'SOCKET_CHANNELS'; export const SOCKET_CHANNELS = 'SOCKET_CHANNELS';
export const SOCKET_CONNECTION_UPDATE = 'SOCKET_CONNECTION_UPDATE';
export const SOCKET_JOIN = 'SOCKET_JOIN'; export const SOCKET_JOIN = 'SOCKET_JOIN';
export const SOCKET_MESSAGE = 'SOCKET_MESSAGE'; export const SOCKET_MESSAGE = 'SOCKET_MESSAGE';
export const SOCKET_MODE = 'SOCKET_MODE'; export const SOCKET_MODE = 'SOCKET_MODE';

View File

@ -35,6 +35,7 @@ export default class TabList extends Component {
selected.channel === null && selected.channel === null &&
selected.user === null selected.user === null
} }
connected={servers.getIn([address, 'connected'])}
onClick={this.handleTabClick} onClick={this.handleTabClick}
/> />
); );

View File

@ -20,8 +20,24 @@ export default class TabListItem extends Component {
classes.push('selected'); classes.push('selected');
} }
let indicator = null;
if (this.props.connected !== undefined) {
const style = {};
if (this.props.connected) {
style.background = '#6BB758';
} else {
style.background = '#F6546A';
}
indicator = <i className="tab-indicator" style={style}></i>;
}
return ( return (
<p className={classes.join(' ')} onClick={this.handleClick}>{content}</p> <p className={classes.join(' ')} onClick={this.handleClick}>
<span className="tab-content">{content}</span>
{indicator}
</p>
); );
} }
} }

View File

@ -1,10 +1,12 @@
import { Map, Record } from 'immutable'; import { Map, Record } from 'immutable';
import createReducer from '../util/createReducer'; import createReducer from '../util/createReducer';
import * as actions from '../actions'; import * as actions from '../actions';
import forEach from 'lodash/collection/forEach';
const Server = Record({ const Server = Record({
nick: null, nick: null,
name: null name: null,
connected: false
}); });
export default createReducer(Map(), { export default createReducer(Map(), {
@ -42,5 +44,13 @@ export default createReducer(Map(), {
s.set(server.address, new Server(server)); s.set(server.address, new Server(server));
}); });
}); });
},
[actions.SOCKET_CONNECTION_UPDATE](state, action) {
return state.withMutations(s => forEach(action, (connected, server) => {
if (s.has(server)) {
s.setIn([server, 'connected'], connected);
}
}));
} }
}); });

View File

@ -12,14 +12,15 @@ import (
) )
type Client struct { type Client struct {
Server string Server string
Host string Host string
TLS bool TLS bool
TLSConfig *tls.Config TLSConfig *tls.Config
Password string Password string
Username string Username string
Realname string Realname string
Messages chan *Message Messages chan *Message
ConnectionChanged chan bool
nick string nick string
@ -40,13 +41,14 @@ type Client struct {
func NewClient(nick, username string) *Client { func NewClient(nick, username string) *Client {
return &Client{ return &Client{
nick: nick, nick: nick,
Username: username, Username: username,
Realname: nick, Realname: nick,
Messages: make(chan *Message, 32), Messages: make(chan *Message, 32),
out: make(chan string, 32), ConnectionChanged: make(chan bool, 16),
quit: make(chan struct{}), out: make(chan string, 32),
reconnect: make(chan struct{}), quit: make(chan struct{}),
reconnect: make(chan struct{}),
backoff: &backoff.Backoff{ backoff: &backoff.Backoff{
Jitter: true, Jitter: true,
}, },

View File

@ -10,6 +10,8 @@ import (
) )
func (c *Client) Connect(address string) { func (c *Client) Connect(address string) {
c.ConnectionChanged <- false
if idx := strings.Index(address, ":"); idx < 0 { if idx := strings.Index(address, ":"); idx < 0 {
c.Host = address c.Host = address
@ -70,6 +72,7 @@ func (c *Client) connect() error {
} }
c.connected = true c.connected = true
c.ConnectionChanged <- true
c.reader = bufio.NewReader(c.conn) c.reader = bufio.NewReader(c.conn)
c.register() c.register()
@ -154,6 +157,7 @@ func (c *Client) recv() {
return return
default: default:
c.ConnectionChanged <- false
c.lock.Lock() c.lock.Lock()
c.connected = false c.connected = false
c.lock.Unlock() c.lock.Unlock()
@ -181,6 +185,7 @@ func (c *Client) recv() {
func (c *Client) close() { func (c *Client) close() {
if c.Connected() { if c.Connected() {
c.ConnectionChanged <- false
c.lock.Lock() c.lock.Lock()
c.connected = false c.connected = false
c.lock.Unlock() c.lock.Unlock()

View File

@ -31,13 +31,21 @@ func newIRCHandler(client *irc.Client, session *Session) *ircHandler {
func (i *ircHandler) run() { func (i *ircHandler) run() {
for { for {
msg, ok := <-i.client.Messages select {
if !ok { case msg, ok := <-i.client.Messages:
i.session.deleteIRC(i.client.Host) if !ok {
return i.session.deleteIRC(i.client.Host)
} return
}
i.dispatchMessage(msg) i.dispatchMessage(msg)
case connected := <-i.client.ConnectionChanged:
i.session.sendJSON("connection_update", map[string]bool{
i.client.Host: connected,
})
i.session.setConnectionState(i.client.Host, connected)
}
} }
} }

View File

@ -8,8 +8,9 @@ import (
) )
type Session struct { type Session struct {
irc map[string]*irc.Client irc map[string]*irc.Client
ircLock sync.Mutex connectionState map[string]bool
ircLock sync.Mutex
ws map[string]*wsConn ws map[string]*wsConn
wsLock sync.Mutex wsLock sync.Mutex
@ -20,9 +21,10 @@ type Session struct {
func NewSession() *Session { func NewSession() *Session {
return &Session{ return &Session{
irc: make(map[string]*irc.Client), irc: make(map[string]*irc.Client),
ws: make(map[string]*wsConn), connectionState: make(map[string]bool),
out: make(chan WSResponse, 32), ws: make(map[string]*wsConn),
out: make(chan WSResponse, 32),
} }
} }
@ -37,12 +39,14 @@ func (s *Session) getIRC(server string) (*irc.Client, bool) {
func (s *Session) setIRC(server string, i *irc.Client) { func (s *Session) setIRC(server string, i *irc.Client) {
s.ircLock.Lock() s.ircLock.Lock()
s.irc[server] = i s.irc[server] = i
s.connectionState[server] = false
s.ircLock.Unlock() s.ircLock.Unlock()
} }
func (s *Session) deleteIRC(server string) { func (s *Session) deleteIRC(server string) {
s.ircLock.Lock() s.ircLock.Lock()
delete(s.irc, server) delete(s.irc, server)
delete(s.connectionState, server)
s.ircLock.Unlock() s.ircLock.Unlock()
} }
@ -54,6 +58,24 @@ func (s *Session) numIRC() int {
return n return n
} }
func (s *Session) getConnectionStates() map[string]bool {
s.ircLock.Lock()
state := make(map[string]bool, len(s.connectionState))
for k, v := range s.connectionState {
state[k] = v
}
s.ircLock.Unlock()
return state
}
func (s *Session) setConnectionState(server string, connected bool) {
s.ircLock.Lock()
s.connectionState[server] = connected
s.ircLock.Unlock()
}
func (s *Session) setWS(addr string, w *wsConn) { func (s *Session) setWS(addr string, w *wsConn) {
s.wsLock.Lock() s.wsLock.Lock()
s.ws[addr] = w s.ws[addr] = w

View File

@ -71,6 +71,7 @@ func (h *wsHandler) init(uuid string) {
h.session.sendJSON("channels", channels) h.session.sendJSON("channels", channels)
h.session.sendJSON("servers", h.session.user.GetServers()) h.session.sendJSON("servers", h.session.user.GetServers())
h.session.sendJSON("connection_update", h.session.getConnectionStates())
for _, channel := range channels { for _, channel := range channels {
h.session.sendJSON("users", Userlist{ h.session.sendJSON("users", Userlist{