Files
im_webapp/index.html
T

1092 lines
38 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tinode example: chat web application in react.js</title>
<link rel="shortcut icon" href="img/logo32x32.png" />
<!-- Google's Roboto font -->
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,400italic,700&subset=latin,cyrillic"
rel="stylesheet" type="text/css" />
<!-- Google's material design icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Customized Bootstrap style (material design). -->
<link rel="stylesheet" href="css/bootstrap.css" />
<!-- Styles for local elements -->
<link rel="stylesheet" href="css/base.css" />
<!-- ReactJs scripts -->
<script src="https://fb.me/react-0.14.6.js"></script>
<script src="https://fb.me/react-dom-0.14.6.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<!-- Some scripts to make bootstrap more dynamic -->
<!--script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script-->
<!--script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script-->
<script src="tinode-0.5.1.js"></script>
</head>
</head>
<body>
<div id="mountPoint"></div>
<script type="text/babel">
// Host name and posrt to connect to
var HOST = "api.tinode.co";
//var HOST = "localhost:6060";
// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own
var API_KEY = "AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K";
// Minimum time between two keypress notifications, milliseconds.
var KEYPRESS_DELAY = 5*1000;
// Delay before sending a {note} for reciving a message, milliseconds.
var RECEIVED_DELAY = 500;
// Delay before sending a read notification, milliseconds.
var READ_DELAY = 3*1000;
// Short representation of time in the past.
function shortDateFormat(then) {
var now = new Date();
if (then.getFullYear() == now.getFullYear()) {
if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {
return then.toLocaleTimeString(undefined, {hour12: false, hour: '2-digit', minute: '2-digit'});
} else {
return then.toLocaleDateString(undefined,
{hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});
}
}
return then.toLocaleDateString(undefined,
{hour12: false, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});
}
/* BEGIN Login: a login form */
var LoginView = React.createClass({
getInitialState: function() {
return {
login: '',
password: '',
errorCleared: false
};
},
handleLoginChange: function(e) {
if (!this.state.errorCleared) {
this.props.onClearError();
this.setState({errorCleared: true});
}
this.setState({login: e.target.value});
},
handlePasswordChange: function(e) {
if (!this.state.errorCleared) {
this.props.onClearError();
this.setState({errorCleared: true});
}
this.setState({password: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
this.setState({errorCleared: false});
this.props.onLogin(this.state.login.trim(), this.state.password.trim());
},
render: function() {
var submitClassList = "blue";
if (this.props.disabled) {
submitClassList += " disabled";
}
return (
<div>
<form className="form-signin" onSubmit={this.handleSubmit}>
<label htmlFor="inputLogin" className="sr-only">Login</label>
<input type="text" id="inputLogin" className="form-control"
placeholder="Login (alice, bob, carol, dave, frank)"
value={this.state.login}
onChange={this.handleLoginChange}
required autofocus />
<label htmlFor="inputPassword" className="sr-only">Password</label>
<input type="password" id="inputPassword" className="form-control"
placeholder="Password (alice123, bob123, ...)"
value={this.state.password}
onChange={this.handlePasswordChange}
required />
<br />
<button className={submitClassList} type="submit">SIGN IN</button>
</form>
</div>
);
}
})
/* END Login */
/* BEGIN Account registration */
var CreateAccountView = React.createClass({
getInitialState: function() {
return {
login: '',
password: '',
password2: '',
public: '',
private: '',
errorCleared: false
};
},
handleLoginChange: function(e) {
this.setState({login: e.target.value});
},
handlePasswordChange: function(e) {
this.setState({password: e.target.value});
},
handlePassword2Change: function(e) {
this.setState({password2: e.target.value});
},
handlePublicChange: function(e) {
this.setState({public: e.target.value});
},
handlePrivateChange: function(e) {
this.setState({private: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
if (this.state.password.trim() != this.state.password2.trim()) {
} else {
this.setState({errorCleared: false});
this.props.onCreateAccount(this.state.login.trim(), this.state.password.trim(),
this.state.public.trim(), this.state.private.trim());
}
},
render: function() {
var submitClassList = "blue";
if (this.props.disabled) {
submitClassList += " disabled";
}
return (
<div>
<form onSubmit={this.handleSubmit} onCancel={this.props.onCancel}>
<label htmlFor="signupLogin" className="sr-only">Login</label>
<input type="text" id="signupLogin" className="form-control" placeholder="Login"
value={this.state.login} onChange={this.handleLoginChange} required autofocus />
<label htmlFor="signupPassword" className="sr-only">Password</label>
<input type="password" id="signupPassword" className="form-control" placeholder="Password"
value={this.state.password} onChange={this.handlePasswordChange} required />
<label htmlFor="signupPassword2" className="sr-only">Repeat password</label>
<input type="password" id="signupPassword" className="form-control" placeholder="Repeat password"
value={this.state.password2} onChange={this.handlePassword2Change} required />
<label htmlFor="signupPublic" className="sr-only">Account public data</label>
<textarea id="signupPublic" className="form-control" rows="3" onChange={this.handlePublicChange}
value={this.state.public} placeholder="Public: text or valid JSON"></textarea>
<label htmlFor="signupPrivate" className="sr-only">Account private data</label>
<textarea id="signupPrivate" className="form-control" rows="3" onChange={this.handlePrivateChange}
value={this.state.private} placeholder="Private: text or valid JSON"></textarea>
<br />
<button className={submitClassList} type="submit">SIGN UP</button>
</form>
</div>
);
}
});
/* END Account registration */
/* BEGIN Tinode config panel */
var SettingsView = React.createClass({
render: function() {
return (
<div className="container-fluid">
<h3>Not implemented</h3>
</div>
);
}
});
/* END Tinode config panel */
/* BEGIN Manage side panel - handle Login, Account Registration, and Contacts views */
var SideNavbar = React.createClass({
render: function() {
var avatar = null;
if (this.props.avatar) {
var src = this.props.avatar === true ? "img/avatar.svg" : this.props.avatar;
avatar = <img id="self-avatar" alt="avatar" src={src} />
}
return (
<div id="side-caption-panel">
{avatar}
<div id="sidepanel-title">{this.props.title}</div>
{this.props.state === 'login' ?
<SideNavbar.MenuStart onSignUp={this.props.onSignUp} onSettings={this.props.onSettings} /> :
this.props.state === 'settings' ?
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
this.props.state === 'register' ?
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
this.props.state === 'contacts' ?
<SideNavbar.MenuSettings onSettings={this.props.onSettings} /> : null}
</div>
);
}
})
SideNavbar.MenuStart = React.createClass({
render: function() {
return (
<div>
<a href="#" onClick={this.props.onSignUp}><i className="material-icons medium">person_add</i></a>
&nbsp;
<a href="#" onClick={this.props.onSettings}><i className="material-icons medium">settings</i></a>
</div>
);
}
});
SideNavbar.MenuCancel = React.createClass({
render: function() {
return (
<a href="#" onClick={this.props.onCancel}><i className="material-icons medium">close</i></a>
);
}
})
SideNavbar.MenuSettings = React.createClass({
render: function() {
return (
<a href="#" onClick={this.props.onSettings}><i className="material-icons medium">settings</i></a>
);
}
})
var SidepanelView = React.createClass({
getInitialState: function() {
return {
showErrorText: true,
};
},
handleClearErrorText: function() {
this.setState({showErrorText: false});
},
handleLoginRequested: function(login, password) {
this.props.onLoginRequest(login, password);
this.setState({showErrorText: true});
},
render: function() {
var title = null;
var avatar = false;
switch (this.props.state) {
case 'login':
title = "Sign In"; break;
case 'register':
title = "Create Account"; break;
case 'settings':
title = "Settings"; break;
case 'contacts':
title = this.props.title;
avatar = true;
default:;
};
return (
<div id="sidepanel">
<SideNavbar state={this.props.state}
title={title} avatar={avatar}
onSignUp={this.props.onSignUp}
onSettings={this.props.onSettings}
onCancel={this.props.onCancel} />
<SidepanelView.ErrorPanel showError={this.state.showErrorText} errorText={this.props.errorText} />
{this.props.state === 'login' ?
<LoginView disabled={this.props.loginDisabled}
onLogin={this.handleLoginRequested}
onClearError={this.handleClearErrorText} /> :
this.props.state === 'register' ?
<CreateAccountView onCreateAccount={this.props.onCreateAccount} onCancel={this.props.onCancel} /> :
this.props.state === 'settings' ?
<SettingsView onCancel={this.props.onCancel} /> :
this.props.state === 'contacts' ?
<ContactsView data={this.props.subscriptions} onTopicSelected={this.props.onTopicSelected} /> :
null}
</div>
);
}
});
SidepanelView.ErrorPanel = React.createClass({
render: function() {
var errorClass = null;
var errorText = ' ';
if (this.props.errorText && this.props.showError) {
errorClass = "error-alert";
errorText = this.props.errorText;
}
return (
<div id="alert-box" className={errorClass}>{errorText}</div>
);
}
});
/* END Side panel */
/* BEGIN Contact list (list of topics) */
/* A form for initiating a new topic
var NewTopic = React.createClass({
render: function() {
return (
<div className="input-group" id="newTopicInput">
<label htmlFor="addTopic" className="sr-only">Add user or topic</label>
<input id="addTopic" className="form-control" type="text" placeholder="Add topic" />
<span className="input-group-btn"><button className="btn btn-default" type="button">
<i className="material-icons">add</i>
</button></span>
</div>
);
}
});
*/
/*
var contactsData = [
{topic: "usrBOB", public: "Bob Smith", private: "This is another comment", online: {now: true}, unread: 0},
{topic: "usrCAROL", public: "Carol Xmas", private: "No comments", online: {now: false, when: "1 hour ago"}, unread: 2},
{topic: "usrDAVE", public: "Dave Goliaphsson", private: "This is a comment", online: {now: false, when: "30 min ago"}, unread: 0},
{topic: "usrBOB2", public: "Bob Smith", private: "This is another comment", online: {now: true}, unread: 0},
{topic: "usrCAROL2", public: "Carol Xmas", private: "No comments", online: {now: false, when: "1 hour ago"}, unread: 2},
{topic: "usrDAVE2", public: "Dave Goliaphsson", private: "This is a comment", online: {now: false, when: "30 min ago"}, unread: 0},
{topic: "usrBOB3", public: "Bob Smith", private: "This is another comment", online: {now: true}, unread: 0},
{topic: "usrCAROL3", public: "Carol Xmas", private: "No comments", online: {now: false, when: "1 hour ago"}, unread: 2},
{topic: "usrDAVE3", public: "Dave Goliaphsson", private: "This is a comment", online: {now: false, when: "30 min ago"}, unread: 0}
];
*/
/* A single topic */
var Contact = React.createClass({
handleClick: function() {
this.props.onSelected(this.props.topic);
},
render: function() {
var title = this.props.public;
if (title.length > 32) {
title = title.substring(0, 30) + "...";
}
return (
<li onClick={this.handleClick}>
<div className="avatar-box">
<img src="img/avatar.svg" className="avatar" alt="" />
<span className={this.props.now ? "online" : "offline"}>&#x25cf;</span>
</div>
<div>
<strong>{title}</strong> <Contact.Unread count={this.props.unread} />
<p>{this.props.comment}</p>
</div>
</li>
);
}
});
/* The counter of unread messages in the topic */
Contact.Unread = React.createClass({
render: function() {
var showUnreadBadge = null;
if (this.props.count > 0) {
var count = this.props.count > 99 ? "99+" : this.props.count;
showUnreadBadge = <span className="badge">{count}</span>;
}
return (
<span>{showUnreadBadge}</span>
);
}
});
/* ContactsView holds all contacts-related stuff */
var ContactsView = React.createClass({
getInitialState: function() {
return {subscriptions: []};
},
componentWillMount: function() {
var me = Tinode.getMeTopic();
me.onContactUpdate = this.tnMeContactUpdate;
me.onData = this.tnInvite;
me.onSubsUpdated = this.tnMeSubsUpdated;
},
tnMeContactUpdate: function(what, cont) {
console.log("changed: " + what + " in " + cont.topic);
this.forceUpdate();
},
tnInvite: function(invite) {
console.log(invite);
},
tnMeSubsUpdated: function(names) {
/*var subs = [];
Tinode.getMeTopic().contacts(function(cont) {
subs.push(cont);
});
this.setState({subscriptions: subs});
*/
this.forceUpdate();
},
onSelectionChange: function(sel) {
this.props.onTopicSelected(sel);
},
render: function() {
var contactNodes = [];
Tinode.getMeTopic().contacts(function(c) {
var lastSeen = null;
if (c.seen) {
lastSeen = shortDateFormat(c.seen.when);
// TODO(gene): also handle user agent in c.seen.ua
}
var unread = c.seq;
if (c.read > 0) {
unread -= c.read;
}
contactNodes.push(
<Contact public={c.public} comment={c.private} unread={unread} now={c.online} when={lastSeen}
onSelected={this.onSelectionChange} topic={c.topic} key={c.topic} />
);
}, this);
return (
<div id="contacts-panel">
<ul id="contact-box">
{contactNodes}
</ul>
<button className="round blue" id="add-topic"><i className="material-icons big">add</i></button>
</div>
);
}
});
/* END Contact list */
/* BEGIN Conversation panel */
/* Don't delete it for now: maybe needed for debugging
var messagesList = [
{
id: 1, content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet tellus interdum. \
Integer sodales accumsan sem, ut malesuada velit egestas vel. Aenean consequat consectetur justo eget venenatis. Morbi \
eleifend fringilla urna euismod tristique. Nullam at magna vel mauris rutrum finibus. Duis eu dignissim magna. Donec \
lobortis suscipit vulputate. Cras luctus elit ex, vitae lobortis magna lobortis bibendum.",
timestamp: new Date(2015, 11, 31, 22, 23, 24, 567), response: false
},
{
id: 2, content: "Duis porttitor est nec diam lobortis, non aliquam risus aliquam. \
Nulla vitae est aliquam, egestas nunc dapibus, dapibus risus.\
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \
Nullam vitae condimentum lectus, quis maximus elit.\
\
Nunc ac nisi risus. Integer eget augue vel risus commodo blandit. Nulla faucibus convallis neque sed eleifend. Vestibulum\
dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac habitasse platea dictumst.",
timestamp: new Date(2016, 0, 1, 2, 3, 4, 567),
response: true
},
{id: 3, content: "Ooga chaka ooga ooga!", timestamp: new Date(2016, 0, 2, 16, 17, 18, 567), response: false},
{id: 4, content: "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \
Nullam vitae condimentum lectus, quis maximus elit.", timestamp: new Date(2016, 0, 3, 1, 2, 3, 567), response: true},
{id: 5, content: "Integer sodales accumsan sem, ut malesuada velit egestas vel. Aenean consequat consectetur justo eget venenatis.",
timestamp: new Date(2016, 0, 3, 4, 5, 6, 567), response: true},
{id: 6, content: "No way.", timestamp: new Date(2016, 0, 3, 15, 16, 17, 567), response: false},
{id: 7, content: "OK!", timestamp: new Date(2016, 0, 3, 18, 19, 20, 567), response: true}
];
*/
var Received = {
UNDEF: -2,
SENDING: -1,
SERVER: 0,
CLIENT: 1,
READ: 2
};
var Message = React.createClass({
render: function() {
var liClass = (this.props.response ? "left" : "right") + " " + this.props.sequence;
var colors = {
backgroundColor: materialColor[materialColorIndex[this.props.userIndex]]["100"],
borderColor: materialColor[materialColorIndex[this.props.userIndex]]["100"]
};
var bubbleClass = (this.props.sequence === "single" || this.props.sequence === "last") ?
"bubble tip" : "bubble";
return (
<li className={liClass}>
<div className={bubbleClass} style={colors}>
<p>{this.props.content}
<Message.ReceivedMarker response={this.props.response}
timestamp={this.props.timestamp} received={this.props.received} />
</p>
</div>
</li>
);
}
});
/* Received/read indicator */
Message.ReceivedMarker = React.createClass({
render: function() {
var timestamp = (this.props.received === Received.SENDING) ?
"sending ..." :
shortDateFormat(this.props.timestamp);
var marker = null;
if (this.props.received === Received.SENDING) {
marker = (<i className="material-icons small">access_time</i>); // watch face
} else if (this.props.received === Received.SERVER) {
marker = (<i className="material-icons small">done</i>); // checkmark
} else if (this.props.received === Received.CLIENT) {
marker = (<i className="material-icons small">done_all</i>); // double checkmark
} else if (this.props.received === Received.READ) {
marker = (<i className="material-icons small blue">done_all</i>); // open eye
}
return (
<span className="pull-right timestamp">
{timestamp}{'\u00a0'}{marker}
</span>
);
}
});
var MessagesView = React.createClass({
getInitialState: function() {
return {
messages: [],
topic: '',
title: ''
};
},
// Scroll last message into view on component update e.g. on message received.
componentDidUpdate: function() {
//var node = ReactDOM.findDOMNode(this);
//if (node) {
// node.scrollTop = node.scrollHeight;
//}
//var node = this.refs.messagesPanel;
//node.scrollTop = node.scrollHeight;
},
componentWillMount: function() {
this.propsChange(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.propsChange(nextProps);
},
propsChange: function(props) {
if (props.topicSelected != this.state.topic) {
var msgs = [];
var topic = Tinode.getTopic(props.topicSelected);
// Bind the new topic to component.
topic.onData = this.handleNewMessage;
topic.onMetaInfo = this.handleInfoChange;
topic.onSubsUpdated = this.handleSubsUpdated;
// Unbind the previous topic from this component.
if (this.state.topic) {
var oldTopic = Tinode.getTopic(this.state.topic);
oldTopic.onData = undefined;
oldTopic.onMetaInfo = undefined;
oldTopic.onSubsUpdated = undefined;
}
if (topic.isSubscribed()) {
this.handleInfoChange(topic);
topic.messages(function(msg) {
msgs = msgs.concat(msg);
});
} else {
topic.subscribe({get: "info sub data"});
}
// The user switched to the new topic before the timer for
// the previous topic has triggered, kill it.
clearTimeout(this.readTimer);
this.setState({messages: msgs, topic: props.topicSelected});
}
},
handleInfoChange: function(info) {
console.log("handleInfoChange: " + info);
this.setState({title: info.public});
},
handleSubsUpdated: function(uids) {
},
handleNewMessage: function(msg) {
this.setState({messages: this.state.messages.concat(msg)});
// Aknowledge all messages, including own messges.
clearTimeout(this.readTimer);
this.readTimer = setTimeout(function() {
Tinode.getTopic(msg.topic).noteRead(msg.seq);
}, READ_DELAY);
this.props.onData(msg);
},
render: function() {
var component = null;
if (this.state.topic) {
var messageNodes = [];
var me = Tinode.getCurrentUserID();
var topic = Tinode.getTopic(this.state.topic);
var previousFrom = null;
for (var i=0; i<this.state.messages.length; i++) {
var msg = this.state.messages[i];
var nextFrom = (i + 1 < this.state.messages.length) ? this.state.messages[i+1].from : null;
var sequence = "single";
if (msg.from === previousFrom) {
if (msg.from === nextFrom) {
sequence = "middle";
} else {
sequence = "last";
}
} else if (msg.from === nextFrom) {
sequence = "first";
}
previousFrom = msg.from;
var isReply = !(msg.from === me);
var msgReceived = Received.UNDEF;
if (!isReply) {
if (topic.msgReadCount(msg.seq) > 1) {
msgReceived = Received.READ;
} else if (topic.msgRecvCount(msg.seq) > 1) {
msgReceived = Received.CLIENT;
} else {
msgReceived = Received.SERVER;
}
}
messageNodes.push(
<Message content={msg.content} timestamp={msg.ts} response={isReply}
userIndex={msg.$userIndex} sequence={sequence} received={msgReceived} key={msg.seq} />
);
}
component = (
<div id="topic-view">
<div id="topic-caption-panel">
<h4>{this.state.title}</h4>
</div>
<div id="messages-panel" ref="messagesPanel">
<ul className="chat-box">
{messageNodes}
</ul>
</div>
<div id="send-message-panel">
<MessagesView.Send topic={this.props.topicSelected} sendMessage={this.props.sendMessage} />
</div>
</div>
);
} else {
component = (
<MessagesView.Dummy />
);
}
return component;
}
});
/* Send message form */
MessagesView.Send = React.createClass({
getInitialState:function() {
return {
message: '',
// Make initial keypress time as if it happened 5001 milliseconds in the past.
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
};
},
handleSend: function(e) {
var message = this.state.message.trim();
if (message) {
this.props.sendMessage(this.state.message.trim());
this.setState({message: ''});
}
},
handleMessageTyping: function(e) {
var newState = {message: e.target.value};
var now = new Date().getTime();
if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {
var topic = Tinode.getTopic(this.props.topic);
if (topic.isSubscribed()) {
topic.noteKeyPress();
}
newState.keypressTimestamp = now;
}
this.setState(newState);
},
render: function() {
return (
<div>
<input id="sendMessage" type="text" placeholder="New message"
value={this.state.message} onChange={this.handleMessageTyping} />
<a href="#" onClick={this.handleSend}>
<i className="material-icons">send</i>
</a>
</div>
);
}
});
/* This is just a static page to display when no conversation is selected. */
MessagesView.Dummy = React.createClass({
render: function() {
return (
<div id="dummy-view">
<a href="https://github.com/tinode/chat/">
<div>
<img id="logo" alt="logo" src="img/logo.svg" />
<h4>Tinode Demo Chat</h4>
</div>
</a>
</div>
);
}
});
/* END Conversation panel */
/* The top-level class to hold all fuinctionality together */
var AppView = React.createClass({
getInitialState: function() {
return {
sidePanelSelected: 'login',
sidePanelTitle: null,
loggedIn: false,
errorText: '',
topicSelected: '',
loginDisabled: false,
subscriptions: {}
};
},
componentDidMount: function() {
Tinode.enableLogging(true);
Tinode.onDisconnect = this.handleDicsonnect;
// Setup with the default transport, usually websocket.
Tinode.setup(HOST, API_KEY);
},
handleDicsonnect: function() {
this.setState({
sidePanelSelected: 'login',
sidePanelTitle: null,
loggedIn: false,
errorText: 'disconnected',
topicSelected: '',
loginDisabled: false,
subscriptions: {}
});
},
// User clicked Login button in the side panel.
handleLoginRequest: function(login, password) {
var instance = this;
this.setState({loginDisabled: true, errorText: ''});
Tinode.connect().then(function() {
// Login
return Tinode.loginBasic(login, password);
}).then(function() {
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
var me = Tinode.getMeTopic();
me.onMetaInfo = instance.tnMeMetaInfo;
instance.setState({sidePanelSelected: "contacts", loggedIn: true});
// Subscribe, fetch topic info and the list of subscriptions.
me.subscribe({get: "info sub data"});
}).catch(function(err) {
// Login failed, report error
instance.setState({loginDisabled: false, errorText: err.message});
});
},
tnMeMetaInfo: function(info) {
this.setState({sidePanelTitle: info.public});
},
// Sending "received" notifications
tnData: function(data) {
clearTimeout(this.receivedTimer);
this.receivedTimer = setTimeout(function() {
this.receivedTimer = undefined;
Tinode.getTopic(data.topic).noteRecv(data.seq);
}, RECEIVED_DELAY);
},
// Registration of a new account.
handleNewAccount: function(login_, password_, public_, private_) {
var instance = this;
Tinode.connect().then(function(){
return Tinode.createUserBasic(login_, password_,
{login: "basic", public: public_, private: private_});
}).catch(function(err) {
instance.setState({errorText: err.message});
});
},
// User clicked on a contact in the side panel.
handleTopicSelected: function(topicName) {
this.setState({topicSelected: topicName});
},
// User clicked on a message send button.
handleSendMessage: function(msg) {
var topic = Tinode.getTopic(this.state.topicSelected);
topic.publish(msg);
},
// User chose a Sign Up menu item.
handleSignUp: function() {
this.setState({sidePanelSelected: 'register'});
},
// User chose Settings menu item.
handleSettings: function() {
this.setState({sidePanelSelected: 'settings'});
},
// User clicked Cancel button in Setting or Sign Up panel.
handleSidepanelCancel: function() {
if (this.state.loggedIn) {
this.setState({sidePanelSelected: 'contacts'});
} else {
this.setState({sidePanelSelected: 'login'});
}
},
render: function() {
return (
<div id="app-container">
<SidepanelView
state={this.state.sidePanelSelected}
title={this.state.sidePanelTitle}
subscriptions={this.state.subscriptions}
loginDisabled={this.state.loginDisabled}
errorText={this.state.errorText}
onSignUp={this.handleSignUp}
onSettings={this.handleSettings}
onLoginRequest={this.handleLoginRequest}
onCreateAccount={this.handleNewAccount}
onTopicSelected={this.handleTopicSelected}
onCancel={this.handleSidepanelCancel} />
<MessagesView
topicSelected={this.state.topicSelected}
onData={this.tnData}
sendMessage={this.handleSendMessage} />
</div>
);
}
})
ReactDOM.render(
<AppView />,
document.getElementById('mountPoint')
);
</script>
<script type="text/javascript">
var materialColorIndex = ["red", "green", "blue", "yellow", "grey", "pink", "light-green", "indigo", "deep-purple",
"light-blue", "deep-orange", "cyan", "lime","amber", "purple", "orange", "teal", "brown", "blue-grey"];
var materialShadeIndex = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900"];
var materialColor = {
"red": {
"50": "#ffebee",
"100": "#ffcdd2",
"200": "#ef9a9a",
"300": "#e57373",
"400": "#ef5350",
"500": "#f44336",
"600": "#e53935",
"700": "#d32f2f",
"800": "#c62828",
"900": "#b71c1c"
},
"pink": {
"50": "#fce4ec",
"100": "#f8bbd0",
"200": "#f48fb1",
"300": "#f06292",
"400": "#ec407a",
"500": "#e91e63",
"600": "#d81b60",
"700": "#c2185b",
"800": "#ad1457",
"900": "#880e4f"
},
"purple": {
"50": "#f3e5f5",
"100": "#e1bee7",
"200": "#ce93d8",
"300": "#ba68c8",
"400": "#ab47bc",
"500": "#9c27b0",
"600": "#8e24aa",
"700": "#7b1fa2",
"800": "#6a1b9a",
"900": "#4a148c"
},
"deep-purple": {
"50": "#ede7f6",
"100": "#d1c4e9",
"200": "#b39ddb",
"300": "#9575cd",
"400": "#7e57c2",
"500": "#673ab7",
"600": "#5e35b1",
"700": "#512da8",
"800": "#4527a0",
"900": "#311b92"
},
"indigo": {
"50": "#e8eaf6",
"100": "#c5cae9",
"200": "#9fa8da",
"300": "#7986cb",
"400": "#5c6bc0",
"500": "#3f51b5",
"600": "#3949ab",
"700": "#303f9f",
"800": "#283593",
"900": "#1a237e"
},
"blue": {
"50": "#e3f2fd",
"100": "#bbdefb",
"200": "#90caf9",
"300": "#64b5f6",
"400": "#42a5f5",
"500": "#2196f3",
"600": "#1e88e5",
"700": "#1976d2",
"800": "#1565c0",
"900": "#0d47a1"
},
"light-blue": {
"50": "#e1f5fe",
"100": "#b3e5fc",
"200": "#81d4fa",
"300": "#4fc3f7",
"400": "#29b6f6",
"500": "#03a9f4",
"600": "#039be5",
"700": "#0288d1",
"800": "#0277bd",
"900": "#01579b"
},
"cyan": {
"50": "#e0f7fa",
"100": "#b2ebf2",
"200": "#80deea",
"300": "#4dd0e1",
"400": "#26c6da",
"500": "#00bcd4",
"600": "#00acc1",
"700": "#0097a7",
"800": "#00838f",
"900": "#006064"
},
"teal": {
"50": "#e0f2f1",
"100": "#b2dfdb",
"200": "#80cbc4",
"300": "#4db6ac",
"400": "#26a69a",
"500": "#009688",
"600": "#00897b",
"700": "#00796b",
"800": "#00695c",
"900": "#004d40"
},
"green": {
"50": "#e8f5e9",
"100": "#c8e6c9",
"200": "#a5d6a7",
"300": "#81c784",
"400": "#66bb6a",
"500": "#4caf50",
"600": "#43a047",
"700": "#388e3c",
"800": "#2e7d32",
"900": "#1b5e20"
},
"light-green": {
"50": "#f1f8e9",
"100": "#dcedc8",
"200": "#c5e1a5",
"300": "#aed581",
"400": "#9ccc65",
"500": "#8bc34a",
"600": "#7cb342",
"700": "#689f38",
"800": "#558b2f",
"900": "#33691e"
},
"lime": {
"50": "#f9fbe7",
"100": "#f0f4c3",
"200": "#e6ee9c",
"300": "#dce775",
"400": "#d4e157",
"500": "#cddc39",
"600": "#c0ca33",
"700": "#afb42b",
"800": "#9e9d24",
"900": "#827717"
},
"yellow": {
"50": "#fffde7",
"100": "#fff9c4",
"200": "#fff59d",
"300": "#fff176",
"400": "#ffee58",
"500": "#ffeb3b",
"600": "#fdd835",
"700": "#fbc02d",
"800": "#f9a825",
"900": "#f57f17"
},
"amber": {
"50": "#fff8e1",
"100": "#ffecb3",
"200": "#ffe082",
"300": "#ffd54f",
"400": "#ffca28",
"500": "#ffc107",
"600": "#ffb300",
"700": "#ffa000",
"800": "#ff8f00",
"900": "#ff6f00"
},
"orange": {
"50": "#fff3e0",
"100": "#ffe0b2",
"200": "#ffcc80",
"300": "#ffb74d",
"400": "#ffa726",
"500": "#ff9800",
"600": "#fb8c00",
"700": "#f57c00",
"800": "#ef6c00",
"900": "#e65100"
},
"deep-orange": {
"50": "#fbe9e7",
"100": "#ffccbc",
"200": "#ffab91",
"300": "#ff8a65",
"400": "#ff7043",
"500": "#ff5722",
"600": "#f4511e",
"700": "#e64a19",
"800": "#d84315",
"900": "#bf360c"
},
"brown": {
"50": "#efebe9",
"100": "#d7ccc8",
"200": "#bcaaa4",
"300": "#a1887f",
"400": "#8d6e63",
"500": "#795548",
"600": "#6d4c41",
"700": "#5d4037",
"800": "#4e342e",
"900": "#3e2723"
},
"grey": {
"50": "#fafafa",
"100": "#f5f5f5",
"200": "#eeeeee",
"300": "#e0e0e0",
"400": "#bdbdbd",
"500": "#9e9e9e",
"600": "#757575",
"700": "#616161",
"800": "#424242",
"900": "#212121"
},
"blue-grey": {
"50": "#eceff1",
"100": "#cfd8dc",
"200": "#b0bec5",
"300": "#90a4ae",
"400": "#78909c",
"500": "#607d8b",
"600": "#546e7a",
"700": "#455a64",
"800": "#37474f",
"900": "#263238"
}
};
</script>
</body>
</html>