making the app adaptive - now works on mobile

This commit is contained in:
or-else
2016-01-31 20:04:37 -08:00
parent f5e91a6366
commit 1483740189
3 changed files with 207 additions and 102 deletions
+6 -6
View File
@@ -1,13 +1,13 @@
# Single-page chat with Tinode # Single-page chat with Tinode
Example using Tinode to build a single-page chat application. The app uses Example using [Tinode](https://github.com/tinode/chat/) to build a single-page chat application. The app uses
[React](https://facebook.github.io/react/), Google's [material design fonts](https://www.google.com/fonts/) [React](https://facebook.github.io/react/), Google's material design [fonts](https://www.google.com/fonts/)
and [icons](https://google.github.io/material-design-icons/#icon-font-for-the-web). The [Tinode javascript library](https://github.com/tinode/tinode-js/) has no and [icons](https://google.github.io/material-design-icons/#icon-font-for-the-web). The [Tinode javascript library](https://github.com/tinode/tinode-js/) has no external dependencies.
external dependencies.
This is work in progress. Bugs are expected. The work was tested in the latest Chrome & Firefox only. This is work in progress. Bugs are expected. The app was tested in the latest Chrome & Firefox only.
Try it at http://api.tinode.co/x/example-react-js/ Try it at http://api.tinode.co/x/example-react-js/. Login as one of `alice`, `bob`, `carol`, `dave`, `frank`.
Password is `<login>123`, e.g. login for `alice` is `alice123`.
License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+88 -36
View File
@@ -14,7 +14,7 @@ html, body {
color: #666; color: #666;
overflow: hidden; overflow: hidden;
background-color: silver; background-color: silver;
box-shadow: inset 0 10em #3949AB; /* the deep-plue stripe accross the top */ box-shadow: inset 0 10rem #3949AB; /* the deep-plue stripe accross the top */
} }
a { a {
@@ -36,6 +36,9 @@ form {
font-size: 1.2rem; font-size: 1.2rem;
} }
form label {
font-size: 1rem;
}
/* Input fields and buttons */ /* Input fields and buttons */
button { button {
@@ -123,27 +126,31 @@ textarea, input {
font-size: 1.05rem; font-size: 1.05rem;
background: transparent; background: transparent;
border: none; border: none;
border-bottom: 1px solid #ccc;
border-radius: 0.2rem;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
width: 100%;
margin: 0.25rem 0; margin: 0.25rem 0;
padding: 0 0.125rem; padding: 0 0.125rem;
outline: none; outline: none;
transition: all 0.2s ease-in-out;
box-sizing: border-box; box-sizing: border-box;
color: #666; color: #666;
} }
textarea:focus, input:focus {
border-bottom: 1px solid #2196f3;
box-shadow: 0 1px 0 rgba(33, 150, 243, 0.25);
}
input {
height: 2.5rem;
}
textarea { textarea {
resize: none; resize: none;
} }
input[type="text"],
input[type="password"],
textarea {
height: 2.5rem;
border-bottom: 1px solid #ccc;
border-radius: 0.2rem;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
width: 100%;
}
textarea:focus,
input[type="text"]:focus,
input[type="password"]:focus {
border-bottom: 1px solid #2196f3;
box-shadow: 0 1px 0 rgba(33, 150, 243, 0.25);
}
/* Scrollbar styling for Chrome. Firefox does not support scrollbar styling. */ /* Scrollbar styling for Chrome. Firefox does not support scrollbar styling. */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.6rem; width: 0.6rem;
@@ -161,7 +168,7 @@ textarea {
/* END of input and buttons */ /* END of input and buttons */
form { form {
padding: 0.5em 0.75em; padding: 0.5rem 0.75rem;
line-height: 1.75; line-height: 1.75;
font-size: 1.2rem; font-size: 1.2rem;
} }
@@ -170,8 +177,8 @@ form {
#mountPoint { #mountPoint {
height: 100%; height: 100%;
padding-top: 1em; padding-top: 1rem;
padding-bottom: 3em; padding-bottom: 3rem;
} }
/* Top-level application layout style: sidepanel + main view */ /* Top-level application layout style: sidepanel + main view */
@@ -179,12 +186,12 @@ form {
position: relative; position: relative;
display: flex; display: flex;
height: 100%; height: 100%;
width: 80em; max-width: 80rem;
overflow: hidden; overflow: hidden;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
border-radius: 0.25em; border-radius: 0.25rem;
box-shadow: 1px 2px 0.3em gray; box-shadow: 1px 2px 0.3rem gray;
background-color: white; background-color: white;
} }
@@ -192,7 +199,8 @@ form {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 30em; width: 24rem;
min-width: 24rem;
height: 100%; height: 100%;
} }
@@ -202,6 +210,28 @@ form {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@media (max-width: 960px) {
#mountPoint {
padding: 0;
}
}
@media (max-width: 640px) {
#app-container {
width: 100%;
}
#sidepanel {
width: 100%;
}
}
/* Class to alternatively hide sidepanel/topic-view */
.nodisplay {
display: none!important;
}
/* END of top-level layout */ /* END of top-level layout */
/* Sidepanel styles */ /* Sidepanel styles */
@@ -251,6 +281,20 @@ form {
border-radius: 33%; border-radius: 33%;
} }
/* Application settings */
#settings-form ul {
list-style: none;
}
#settings-form li {
vertical-align: middle;
}
#settings-form label {
display: inline-block;
padding-left: 0.5rem;
}
/* Area which displays an error message */ /* Area which displays an error message */
#alert-box { #alert-box {
padding: 0.5em; padding: 0.5em;
@@ -302,7 +346,7 @@ form {
#contact-box .avatar { #contact-box .avatar {
width: 3rem; width: 3rem;
height: auto; height: auto;
border-radius: 33%; border-radius: 40%;
} }
/* Online-offline indicator positioned inside avatar-box */ /* Online-offline indicator positioned inside avatar-box */
@@ -347,7 +391,7 @@ form {
width: 4.5rem; width: 4.5rem;
height: auto; height: auto;
box-sizing: border-box; box-sizing: border-box;
margin: 0 0.5rem; margin-left: 0.5rem;
padding: 0; padding: 0;
} }
@@ -416,26 +460,28 @@ form {
/* Topic/messages view */ /* Topic/messages view */
#topic-caption-panel { #topic-caption-panel {
display: flex; display: flex;
align-items: baseline; align-items:center;;
justify-content: space-between; justify-content: flex-start;
flex: none; flex: none;
padding: 0.5em; padding: 0.5rem;
height: 4em; height: 4rem;
background-color: #b0bec5; background-color: #b0bec5;
box-shadow: 0 1px 2px gray; box-shadow: 0 1px 2px gray;
z-index: 1; /* otherwise the shadow becomes invisible */ z-index: 1; /* otherwise the shadow becomes invisible */
} }
/* #hide-message-view {
#topic-title { display: block;
margin-right: 0.5rem;
} }
#topic-last-seen {
#topic-users {
margin-left: auto;
} }
*/
/* Scrollable panel where the messages are shown */ /* Scrollable panel where the messages are shown */
#messages-panel { #messages-panel {
padding: 0.75em; padding: 0.75rem;
overflow-y: scroll; overflow-y: scroll;
background-color: #eceff1; background-color: #eceff1;
height: 100%; height: 100%;
@@ -447,7 +493,7 @@ form {
display: flex; display: flex;
align-items: center; align-items: center;
flex: none; flex: none;
padding: 0.5em; padding: 0.5rem;
background-color: #eceff1; background-color: #eceff1;
box-shadow: 0 -1px rgba(0,0,0,0.1); /* Faint shadow above panel */ box-shadow: 0 -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
z-index: 2; z-index: 2;
@@ -462,12 +508,18 @@ form {
justify-content: center; justify-content: center;
background-color: #e0f7fa; background-color: #e0f7fa;
} }
#dummy-view a {
text-decoration: none;
display: block;
text-align: center;
vertical-align: middle;
}
/* Talk bubbles */ /* Talk bubbles */
.chat-box { .chat-box {
list-style: none; list-style: none;
padding: 0 0 3.5em 0; padding: 0 0 3.5rem 0;
margin: 0 1em 0 1em; margin: 0 0.5rem;
} }
.chat-box li { .chat-box li {
@@ -683,6 +735,6 @@ form {
} }
.avatar-box .material-icons { .avatar-box .material-icons {
color: rgba(250, 250, 250, 0.8); /* #fafafa, gray 50 */ color: rgba(250, 250, 250, 0.8); /* #fafafa, gray 50 */
background-color: #bdbdbd; background-color: #e0e0e0;
border-radius: 2pt; border-radius: 40%;
} }
+113 -60
View File
@@ -29,6 +29,7 @@
<script type="text/babel"> <script type="text/babel">
// Name of this application, used in the User-Agent // Name of this application, used in the User-Agent
var APP_NAME = "TinodeReact/1.0"; var APP_NAME = "TinodeReact/1.0";
// Host name and posrt to connect to // Host name and posrt to connect to
var HOST = "api.tinode.co"; var HOST = "api.tinode.co";
// var HOST = "localhost:6060"; // var HOST = "localhost:6060";
@@ -45,6 +46,10 @@
// Delay before sending a read notification, milliseconds. // Delay before sending a read notification, milliseconds.
var READ_DELAY = 3*1000; var READ_DELAY = 3*1000;
// Mediaquery breakpoint between desktop and mobile, in px. Should match the value
// in @meadia (max-size: 640px) in base.css
var MEDIA_BREAKPOINT = 640;
// Short representation of time in the past. // Short representation of time in the past.
function shortDateFormat(then) { function shortDateFormat(then) {
var now = new Date(); var now = new Date();
@@ -94,9 +99,9 @@
this.props.onLogin(this.state.login.trim(), this.state.password.trim()); this.props.onLogin(this.state.login.trim(), this.state.password.trim());
}, },
render: function() { render: function() {
var submitClassList = "blue"; var submitClasses = "blue";
if (this.props.disabled) { if (this.props.disabled) {
submitClassList += " disabled"; submitClasses += " disabled";
} }
return ( return (
<div> <div>
@@ -112,7 +117,7 @@
onChange={this.handlePasswordChange} onChange={this.handlePasswordChange}
required /> required />
<div className="dialog-buttons"> <div className="dialog-buttons">
<button className={submitClassList} type="submit">Sign in</button> <button className={submitClasses} type="submit">Sign in</button>
</div> </div>
</form> </form>
</div> </div>
@@ -172,9 +177,9 @@
} }
}, },
render: function() { render: function() {
var submitClassList = "blue"; var submitClasses = "blue";
if (this.props.disabled) { if (this.props.disabled) {
submitClassList += " disabled"; submitClasses += " disabled";
} }
return ( return (
<div> <div>
@@ -191,9 +196,9 @@
<CreateAccountView.AvatarUpload onImageChanged={this.handleImageChanged} /> <CreateAccountView.AvatarUpload onImageChanged={this.handleImageChanged} />
</div> </div>
<input type="text" placeholder="Full name, e.g. John Doe" <input type="text" placeholder="Full name, e.g. John Doe"
value={this.state.fn} onChange={this.handleFnChange} required /> value={this.state.fn} onChange={this.handleFnChange} />
<div className="dialog-buttons"> <div className="dialog-buttons">
<button className={submitClassList} type="submit">Sign up</button> <button className={submitClasses} type="submit">Sign up</button>
</div> </div>
</form> </form>
</div> </div>
@@ -208,7 +213,6 @@
}; };
}, },
handleFileUpload(e) { handleFileUpload(e) {
console.log("handleFileUpload");
var instance = this; var instance = this;
var img = new Image(); var img = new Image();
img.crossOrigin = 'Anonymous'; img.crossOrigin = 'Anonymous';
@@ -228,9 +232,7 @@
ctx.drawImage(this, sx, sy, size, size, 0, 0, 64, 64); ctx.drawImage(this, sx, sy, size, size, 0, 0, 64, 64);
instance.setState({dataUrl: canvas.toDataURL("image/jpg")}); instance.setState({dataUrl: canvas.toDataURL("image/jpg")});
canvas = null; canvas = null;
console.log("handleFileUpload.onLoad: done");
}; };
console.log(e);
img.src = URL.createObjectURL(e.target.files[0]); img.src = URL.createObjectURL(e.target.files[0]);
}, },
render: function() { render: function() {
@@ -238,7 +240,7 @@
<div className="avatar-upload"> <div className="avatar-upload">
{this.state.dataUrl ? {this.state.dataUrl ?
<img src={this.state.dataUrl} className="preview" /> : <img src={this.state.dataUrl} className="preview" /> :
<div className="blank">48&times;48</div>} <div className="blank">64&times;64</div>}
<input type="file" id="file-input-avatar" className="inputfile hidden" <input type="file" id="file-input-avatar" className="inputfile hidden"
accept="image/*" onChange={this.handleFileUpload} /> accept="image/*" onChange={this.handleFileUpload} />
<label htmlFor="file-input-avatar" className="round"> <label htmlFor="file-input-avatar" className="round">
@@ -252,11 +254,43 @@
/* BEGIN Tinode config panel */ /* BEGIN Tinode config panel */
var SettingsView = React.createClass({ var SettingsView = React.createClass({
getInitialState: function() {
return {
hostName: HOST,
};
},
handleSubmit: function(e) {
e.preventDefault();
},
handleHostNameChange: function(e) {
this.setState({hostName: e.target.value});
},
render: function() { render: function() {
return ( return (
<div id="settings-form"> <form id="settings-form" onSubmit={this.handleSubmit}>
<big>Not implemented</big> <p>This form does not do anything yet</p>
</div> <label htmlFor="host-name">Host:</label>
<input type="text" id="host-name" placeholder="api.tinode.co"
value={this.state.hostName} onChange={this.handleHostNameChange} required autofocus />
<p>Wire transport:</p>
<ul>
<li>
<input type="radio" id="transport-default" name="transport-select" defaultChecked />
<label htmlFor="transport-default">default</label>
</li>
<li>
<input type="radio" id="transport-ws" name="transport-select" />
<label htmlFor="transport-ws">websocket</label>
</li>
<li>
<input type="radio" id="transport-lp" name="transport-select" />
<label htmlFor="transport-lp">long polling</label>
</li>
</ul>
<div className="dialog-buttons">
<button type="submit" className="blue">Update</button>
</div>
</form>
); );
} }
}); });
@@ -279,6 +313,8 @@
<SideNavbar.MenuStart onSignUp={this.props.onSignUp} onSettings={this.props.onSettings} /> : <SideNavbar.MenuStart onSignUp={this.props.onSignUp} onSettings={this.props.onSettings} /> :
this.props.state === 'settings' ? this.props.state === 'settings' ?
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> : <SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
this.props.state === 'edit-account' ?
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
this.props.state === 'register' ? this.props.state === 'register' ?
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> : <SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
this.props.state === 'contacts' ? this.props.state === 'contacts' ?
@@ -339,13 +375,15 @@
title = "Create Account"; break; title = "Create Account"; break;
case 'settings': case 'settings':
title = "Settings"; break; title = "Settings"; break;
case 'edit-account':
title = "Edit Account"; break;
case 'contacts': case 'contacts':
title = this.props.title; title = this.props.title;
avatar = this.props.avatar; avatar = this.props.avatar;
default:; default:;
}; };
return ( return (
<div id="sidepanel"> <div id="sidepanel" className={this.props.hideSelf ? 'nodisplay' : null}>
<SideNavbar state={this.props.state} <SideNavbar state={this.props.state}
title={title} avatar={avatar} title={title} avatar={avatar}
onSignUp={this.props.onSignUp} onSignUp={this.props.onSignUp}
@@ -369,6 +407,9 @@
this.props.state === 'settings' ? this.props.state === 'settings' ?
<SettingsView onCancel={this.props.onCancel} /> : <SettingsView onCancel={this.props.onCancel} /> :
this.props.state === 'edit-account' ?
<EditAccountView onCancel={this.props.onCancel} /> :
this.props.state === 'contacts' ? this.props.state === 'contacts' ?
<ContactsView <ContactsView
data={this.props.subscriptions} data={this.props.subscriptions}
@@ -396,6 +437,18 @@
/* END Side panel */ /* END Side panel */
/* EditAccount parameters */
var EditAccountView = React.createClass({
render: function() {
return (
<form id="edit-account">
<p>Not implemented</p>
</form>
);
}
});
/* END EditAccount */
/* BEGIN Contact list (list of topics) */ /* BEGIN Contact list (list of topics) */
/* A form for initiating a new topic /* A form for initiating a new topic
@@ -532,35 +585,6 @@
/* END Contact list */ /* END Contact list */
/* BEGIN Conversation panel */ /* 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 = { var Received = {
UNDEF: -2, UNDEF: -2,
SENDING: -1, SENDING: -1,
@@ -751,13 +775,18 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
} }
component = ( component = (
<div id="topic-view"> <div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}>
<div id="topic-caption-panel"> <div id="topic-caption-panel">
<div> {this.props.displayMobile ?
<a href="#" id="hide-message-view" onClick={this.props.onHideMessageView}>
<i className="material-icons big">arrow_back</i>
</a> :
null}
<div id="topic-title-group">
<div id="topic-title" className="panel-title">{this.state.title}</div> <div id="topic-title" className="panel-title">{this.state.title}</div>
<div id="topic-last-seen">{lastSeen}</div> <div id="topic-last-seen">{lastSeen}</div>
</div> </div>
<div>users online and menu buttons</div> <div id="topic-users">users online and menu buttons</div>
</div> </div>
<div id="messages-panel" ref="messagesPanel"> <div id="messages-panel" ref="messagesPanel">
<ul className="chat-box"> <ul className="chat-box">
@@ -769,7 +798,7 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
); );
} else { } else {
component = ( component = (
<MessagesView.Dummy /> <MessagesView.Dummy hideSelf={this.props.hideSelf} />
); );
} }
return component; return component;
@@ -827,13 +856,13 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
/* This is just a static page to display when no conversation is selected. */ /* This is just a static page to display when no conversation is selected. */
MessagesView.Dummy = React.createClass({ MessagesView.Dummy = React.createClass({
render: function() { render: function() {
var version = document.lastModified;
return ( return (
<div id="dummy-view"> <div id="dummy-view" className={this.props.hideSelf ? 'nodisplay' : null}>
<a href="https://github.com/tinode/chat/"> <a href="https://github.com/tinode/chat/">
<div> <img id="logo" alt="logo" src="img/logo.svg" />
<img id="logo" alt="logo" src="img/logo.svg" /> <h2>Tinode Demo Chat</h2>
<h2>Tinode Demo Chat</h2> <p>Version: {version}</p>
</div>
</a> </a>
</div> </div>
); );
@@ -891,16 +920,30 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
errorText: '', errorText: '',
topicSelected: '', topicSelected: '',
loginDisabled: false, loginDisabled: false,
subscriptions: {} subscriptions: {},
displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),
mobilePanel: 'sidepanel'
}; };
}, },
componentDidMount: function() { componentDidMount: function() {
window.addEventListener('resize', this.handleResize);
Tinode.enableLogging(true); Tinode.enableLogging(true);
Tinode.onDisconnect = this.handleDicsonnect; Tinode.onDisconnect = this.handleDisconnect;
// Setup with the default transport, usually websocket. // Setup with the default transport, usually websocket.
Tinode.setup(APP_NAME, HOST, API_KEY); Tinode.setup(APP_NAME, HOST, API_KEY);
}, },
handleDicsonnect: function() { componentWillUnmount: function(){
window.removeEventListener('resize', this.handleResize);
},
handleResize: function(){
var mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;
if (this.state.displayMobile != mobile) {
this.setState({displayMobile: mobile});
}
console.log("viewport (mob=" + mobile + "): " + document.documentElement.clientWidth +
"x" + document.documentElement.clientHeight);
},
handleDisconnect: function() {
this.setState({ this.setState({
sidePanelSelected: 'login', sidePanelSelected: 'login',
sidePanelTitle: null, sidePanelTitle: null,
@@ -910,7 +953,8 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
errorText: 'disconnected', errorText: 'disconnected',
topicSelected: '', topicSelected: '',
loginDisabled: false, loginDisabled: false,
subscriptions: {} subscriptions: {},
mobilePanel: 'sidepanel'
}); });
}, },
// User clicked Login button in the side panel. // User clicked Login button in the side panel.
@@ -949,7 +993,7 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
// Registration of a new account. // Registration of a new account.
handleNewAccount: function(login_, password_, public_, private_) { handleNewAccount: function(login_, password_, public_, private_) {
var instance = this; var instance = this;
Tinode.connect().then(function(){ Tinode.connect().then(function() {
return Tinode.createUserBasic(login_, password_, return Tinode.createUserBasic(login_, password_,
{login: "basic", public: public_, private: private_}); {login: "basic", public: public_, private: private_});
}).catch(function(err) { }).catch(function(err) {
@@ -958,7 +1002,11 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
}, },
// User clicked on a contact in the side panel. // User clicked on a contact in the side panel.
handleTopicSelected: function(topicName) { handleTopicSelected: function(topicName) {
this.setState({topicSelected: topicName}); this.setState({topicSelected: topicName, mobilePanel: 'topic-view'});
},
// In mobile view user requested to show sidepanel
handleHideMessageView: function() {
this.setState({mobilePanel: 'sidepanel'});
}, },
// User clicked on a message send button. // User clicked on a message send button.
handleSendMessage: function(msg) { handleSendMessage: function(msg) {
@@ -971,7 +1019,7 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
}, },
// User chose Settings menu item. // User chose Settings menu item.
handleSettings: function() { handleSettings: function() {
this.setState({sidePanelSelected: 'settings'}); this.setState({sidePanelSelected: this.state.loggedIn ? 'edit-account' : 'settings'});
}, },
// User clicked Cancel button in Setting or Sign Up panel. // User clicked Cancel button in Setting or Sign Up panel.
handleSidepanelCancel: function() { handleSidepanelCancel: function() {
@@ -996,6 +1044,8 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
onCancel={this.handleDialogCancel} /> onCancel={this.handleDialogCancel} />
<SidepanelView <SidepanelView
displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'sidepanel')}
state={this.state.sidePanelSelected} state={this.state.sidePanelSelected}
title={this.state.sidePanelTitle} title={this.state.sidePanelTitle}
avatar={this.state.sidePanelAvatar} avatar={this.state.sidePanelAvatar}
@@ -1011,6 +1061,9 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
onCancel={this.handleSidepanelCancel} /> onCancel={this.handleSidepanelCancel} />
<MessagesView <MessagesView
onHideMessageView={this.handleHideMessageView}
displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'topic-view')}
topicSelected={this.state.topicSelected} topicSelected={this.state.topicSelected}
onData={this.tnData} onData={this.tnData}
sendMessage={this.handleSendMessage} /> sendMessage={this.handleSendMessage} />