blob: 25b461cd20c6fa4c7ba61660814f0aa892527691 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var _ = require('lodash');
var genColor = require('color-generator');
var moment = require('moment');
var React = require('react');
var Channel = require('./channel');
var h = require('./react_h');
// Navigation bar.
var NavBar = React.createClass({
render: function() {
return h('div.navbar', [
h('span.title', document.title)
]);
}
});
var userColors = {};
// Shows a single message.
var Message = React.createClass({
render: function() {
var m = this.props.message;
userColors[m.sender] = (
userColors[m.sender] || genColor(0.5, 0.75).hexString());
return h('div.message', [
h('span.timestamp', [
moment(m.timestamp.toISOString()).format('MMM D [at] h:mma')
]),
h('span.sender', {
style: {color: userColors[m.sender]},
}, m.sender),
h('span.text', m.text)
]);
}
});
// Shows received messages.
var Messages = React.createClass({
componentDidUpdate: function() {
var el = this.getDOMNode();
el.scrollTop = el.scrollHeight;
},
render: function() {
return h('div.messages', _.map(this.props.messages, function(v, i) {
return new Message({key: i, message: v});
}));
}
});
// Shows a list of channel members.
var Members = React.createClass({
render: function() {
return h('div.members', [
h('span.title', 'Members'),
h('ul', _.map(this.props.members, function(member) {
return h('li', {key: member}, [
// http://graphemica.com/%E2%97%8F
h('span.status-active', {key: 'status'}, '\u25CF'),
h('span', {key: 'member'}, member)
]);
}))
]);
}
});
// Message entry box.
var Compose = React.createClass({
getInput: function() {
return this.getDOMNode().querySelector('input');
},
componentDidMount: function() {
this.getInput().focus();
},
render: function() {
var that = this;
return h('div.compose', h('form', {
onSubmit: function() {
try {
var input = that.getInput();
that.props.broadcastMessage(input.value);
// TODO(sadovsky): Maybe wait to reset input until the message is
// received by the sender?
input.value = '';
} catch (e) {
// TODO(sadovsky): For some reason, "throw e" doesn't work.
console.error(e);
} finally {
return false;
}
}
}, [
h('input', {type: 'text', size: 128, placeholder: 'Enter message'}),
// Invisible button used by the tests to submit the form.
h('button', {type: 'submit', hidden: true})
]));
}
});
// The splash screen is part of the page HTML, not rendered through React. This
// helper function removes it.
function removeSplash() {
// Remove splash screen.
var splash = document.querySelector('#splash');
if (splash) splash.remove();
}
var Page = React.createClass({
getInitialState: function() {
return {
chan: null,
members: null,
messages: null
};
},
componentWillReceiveProps: function(nextProps) {
var that = this;
var rt = nextProps.rt;
if (!rt) return;
var chan = null;
// Note: Even if we somehow fail to call chan.leave(), our server will get
// removed from the mounttable after one minute (due to ttl).
window.addEventListener('beforeunload', function() {
if (chan) chan.leave();
});
chan = new Channel(rt, 'public');
chan.on('ready', removeSplash);
chan.on('members', function(members) {
that.setState({members: members});
}).on('message', function(message) {
that.setState({messages: that.state.messages.concat([message])});
});
this.setState({
chan: chan,
members: [],
messages: []
});
// Join the channel.
chan.join();
},
render: function() {
var that = this;
if (this.props.err) {
removeSplash();
return h('div', [
h('span.alert-error', '' + this.props.err),
h('div.instructions', [
'Follow ',
h('a', {href: 'README.html'}, 'these instructions'),
' to install and run Vanadium Chat.'
])
]);
} else if (!this.props.rt) {
return h('span.alert-info', 'Loading...');
}
return h('div.page', [
new NavBar(),
h('div.messages-members', [
new Messages({messages: this.state.messages}),
new Members({members: this.state.members})
]),
new Compose({
broadcastMessage: function(messageText) {
that.state.chan.broadcastMessage(messageText, function(err) {
if (err) throw err;
});
}
})
]);
}
});
////////////////////////////////////////
// Exports
module.exports = {
Page: Page,
};