better handling of avatars

This commit is contained in:
or-else
2017-06-16 17:44:17 -07:00
parent 895a81799f
commit 67cd659712
2 changed files with 123 additions and 23 deletions
+74 -2
View File
@@ -40,6 +40,13 @@ form {
form label { form label {
font-size: 1rem; font-size: 1rem;
} }
img.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
}
/* Input fields and buttons */ /* Input fields and buttons */
button { button {
@@ -872,6 +879,71 @@ ul.tabbar li.active > a:after {
color: #2196f3; /* blue 500 */ color: #2196f3; /* blue 500 */
} }
/* Letter tile base style */
.lettertile {
display: flex;
color: rgba(250, 250, 250, 0.8); /* #FAFAFA, gray 50 */
font-size: 1.8rem;
line-height: 1;
width: 3rem;
height: 3rem;
border-radius: 50%;
text-transform: uppercase;
align-items: center;
justify-content: center;
text-align: center;
}
/* Colors of letter tiles */
.lettertile.color0 {
background-color: #c5e1a5;
}
.lettertile.color1 {
background-color: #ef9a9a;
}
.lettertile.color2 {
background-color: #90caf9;
}
.lettertile.color3 {
background-color: #fff59d;
}
.lettertile.color4 {
background-color: #b0bec5;
}
.lettertile.color5 {
background-color: #f48fb1;
}
.lettertile.color6 {
background-color: #b39ddb;
}
.lettertile.color7 {
background-color: #9fa8da;
}
.lettertile.color8 {
background-color: #ffab91;
}
.lettertile.color9 {
background-color: #ffe082;
}
.lettertile.color10 {
background-color: #a5d6a7;
}
.lettertile.color11 {
background-color: #bcaaa4;
}
.lettertile.color12 {
background-color: #eeeeee;
}
.lettertile.color13 {
background-color: #80deea;
}
.lettertile.color14 {
background-color: #e6ee9c;
}
.lettertile.color15 {
background-color: #ce93d8;
}
/* Material icons resized from the default 24pt */ /* Material icons resized from the default 24pt */
.material-icons { .material-icons {
font-size: 1.4rem; font-size: 1.4rem;
@@ -882,10 +954,10 @@ ul.tabbar li.active > a:after {
} }
#self-avatar.material-icons, .avatar-box .material-icons { #self-avatar.material-icons, .avatar-box .material-icons {
font-size: 2.8rem; font-size: 3rem;
} }
.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: #757575; background-color: #c0c0c0;
border-radius: 50%; border-radius: 50%;
} }
+49 -21
View File
@@ -70,6 +70,18 @@
'data:image/' + photo.type + ';base64,' + photo.data : null; 'data:image/' + photo.type + ';base64,' + photo.data : null;
} }
// Get 32 bit integer hash value for a string. Ideally it should produce the same value
// as Java's String#hash().
function stringHash(value) {
var hash = 0;
value = "" + value;
for (var i = 0; i < value.length; i++) {
hash = ((hash<<5)-hash) + value.charCodeAt(i);
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
// Detect server address from the URL // Detect server address from the URL
function detectServerAddress() { function detectServerAddress() {
var host = DEFAULT_HOST; var host = DEFAULT_HOST;
@@ -203,6 +215,28 @@
}; };
/* END Popup/dropdown menu */ /* END Popup/dropdown menu */
/* BEGIN Lettertile: Avatar box: either a bitmap or a letter tile or a stock icon. */
var LetterTile = React.createClass({
render: function() {
var avatar = null;
if (this.props.avatar === true) {
if (this.props.topic && this.props.title && this.props.title.trim()) {
var letter = this.props.title.trim().charAt(0);
var color = "lettertile color" + (Math.abs(stringHash(this.props.topic)) % 16);
avatar = <div className={color}>{letter}</div>
} else {
avatar = (Tinode.getTopicType(this.props.topic) === "grp") ?
<i className="material-icons">group</i> :
<i className="material-icons">person</i>;
}
} else if (this.props.avatar) {
avatar = <img className="avatar" alt="avatar" src={this.props.avatar} />;
}
return (<div>{avatar}</div>);
}
});
/* END Lettertile */
/* BEGIN Combobox for selecting host name */ /* BEGIN Combobox for selecting host name */
var HostSelector = React.createClass({ var HostSelector = React.createClass({
getInitialState: function() { getInitialState: function() {
@@ -307,7 +341,6 @@
/* END Login */ /* END Login */
/* BEGIN Account registration */ /* BEGIN Account registration */
var CreateAccountView = React.createClass({ var CreateAccountView = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
@@ -493,15 +526,10 @@
/* BEGIN Manage side panel - handle Login, Account Registration, Contacts, NewTopic views */ /* BEGIN Manage side panel - handle Login, Account Registration, Contacts, NewTopic views */
var SideNavbar = React.createClass({ var SideNavbar = React.createClass({
render: function() { render: function() {
var avatar = null;
if (this.props.avatar === true) {
avatar = <i id="self-avatar" className="material-icons">account_box</i>;
} else if (this.props.avatar) {
avatar = <img id="self-avatar" alt="avatar" src={this.props.avatar} />;
}
return ( return (
<div id="side-caption-panel"> <div id="side-caption-panel">
{avatar} <div id="self-avatar"><LetterTile
avatar={this.props.avatar} topic={this.props.myUserId} title={this.props.title} /></div>
<div id="sidepanel-title" className="panel-title">{this.props.title}</div> <div id="sidepanel-title" className="panel-title">{this.props.title}</div>
{this.props.state === 'login' ? {this.props.state === 'login' ?
<SideNavbar.MenuStart onSignUp={this.props.onSignUp} onSettings={this.props.onSettings} /> : <SideNavbar.MenuStart onSignUp={this.props.onSignUp} onSettings={this.props.onSettings} /> :
@@ -591,6 +619,7 @@
<div id="sidepanel" className={this.props.hideSelf ? 'nodisplay' : null}> <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}
myUserId={this.props.myUserId}
onSignUp={this.props.onSignUp} onSignUp={this.props.onSignUp}
onSettings={this.props.onSettings} onSettings={this.props.onSettings}
onNewTopic={this.props.onNewTopic} onNewTopic={this.props.onNewTopic}
@@ -687,15 +716,12 @@
} }
var selected = (this.props.selected === this.props.topic) ? "selected" : null; var selected = (this.props.selected === this.props.topic) ? "selected" : null;
var online = this.props.now ? "online" : "offline"; var online = this.props.now ? "online" : "offline";
var avatar = this.props.avatar ? var avatar = this.props.avatar ? this.props.avatar : true;
<img src={this.props.avatar} className="avatar" /> :
(Tinode.getTopicType(this.props.topic) === "grp") ?
<i className="material-icons">group</i> :
<i className="material-icons">person</i>;
return ( return (
<li className={selected} onClick={this.handleClick}> <li className={selected} onClick={this.handleClick}>
<div className="avatar-box"> <div className="avatar-box">
{avatar} <LetterTile
avatar={avatar} title={this.props.title} topic={this.props.topic} />
<span className={online} /> <span className={online} />
</div> </div>
<div className="text-box"> <div className="text-box">
@@ -1212,7 +1238,6 @@
var component = null; var component = null;
if (this.state.topic) { if (this.state.topic) {
var messageNodes = []; var messageNodes = [];
var myUid = Tinode.getCurrentUserID();
var topic = Tinode.getTopic(this.state.topic); var topic = Tinode.getTopic(this.state.topic);
var previousFrom = null; var previousFrom = null;
for (var i=0; i<this.state.messages.length; i++) { for (var i=0; i<this.state.messages.length; i++) {
@@ -1230,7 +1255,7 @@
} }
previousFrom = msg.from; previousFrom = msg.from;
var isReply = !(msg.from === myUid); var isReply = !(msg.from === this.props.myUserId);
var msgReceived = Received.UNDEF; var msgReceived = Received.UNDEF;
if (!isReply) { if (!isReply) {
if (topic.msgReadCount(msg.seq) > 0) { if (topic.msgReadCount(msg.seq) > 0) {
@@ -1415,7 +1440,7 @@
contextMenuVisible: false, contextMenuVisible: false,
login: '', login: '',
password: '', password: '',
loggedIn: false, myUserId: null,
errorText: '', errorText: '',
topicSelected: '', topicSelected: '',
loginDisabled: false, loginDisabled: false,
@@ -1496,7 +1521,7 @@
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view. // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
var me = Tinode.getMeTopic(); var me = Tinode.getMeTopic();
me.onMetaDesc = instance.tnMeMetaDesc; me.onMetaDesc = instance.tnMeMetaDesc;
instance.setState({sidePanelSelected: "contacts", loggedIn: true}); instance.setState({sidePanelSelected: "contacts", myUserId: Tinode.getCurrentUserID()});
// Subscribe, fetch topic desc, the list of subscriptions, and messages (invites). // Subscribe, fetch topic desc, the list of subscriptions, and messages (invites).
me.subscribe({get: {desc: {}, sub: {}, data: {}}}); me.subscribe({get: {desc: {}, sub: {}, data: {}}});
}, },
@@ -1507,7 +1532,7 @@
sidePanelTitle: null, sidePanelTitle: null,
sidePanelAvatar: null, sidePanelAvatar: null,
dialogSelected: null, dialogSelected: null,
loggedIn: false, myUserId: null,
errorText: err && err.message ? err.message : 'disconnected', errorText: err && err.message ? err.message : 'disconnected',
topicSelected: '', topicSelected: '',
loginDisabled: false, loginDisabled: false,
@@ -1574,7 +1599,7 @@
}, },
// User chose Settings menu item. // User chose Settings menu item.
handleSettings: function() { handleSettings: function() {
this.setState({sidePanelSelected: this.state.loggedIn ? 'edit-account' : 'settings'}); this.setState({sidePanelSelected: this.state.myUserId ? 'edit-account' : 'settings'});
}, },
// User updated connection parameters - transport and server address // User updated connection parameters - transport and server address
handleConnectionSettings: function(serverAddress, transport) { handleConnectionSettings: function(serverAddress, transport) {
@@ -1585,7 +1610,7 @@
}, },
// User clicked Cancel button in Setting or Sign Up panel. // User clicked Cancel button in Setting or Sign Up panel.
handleSidepanelCancel: function() { handleSidepanelCancel: function() {
if (this.state.loggedIn) { if (this.state.myUserId) {
this.setState({sidePanelSelected: 'contacts'}); this.setState({sidePanelSelected: 'contacts'});
} else { } else {
this.setState({sidePanelSelected: 'login'}); this.setState({sidePanelSelected: 'login'});
@@ -1686,6 +1711,7 @@
avatar={this.state.sidePanelAvatar} avatar={this.state.sidePanelAvatar}
subscriptions={this.state.subscriptions} subscriptions={this.state.subscriptions}
login={this.state.login} login={this.state.login}
myUserId={this.state.myUserId}
loginDisabled={this.state.loginDisabled} loginDisabled={this.state.loginDisabled}
errorText={this.state.errorText} errorText={this.state.errorText}
topicSelected={this.state.topicSelected} topicSelected={this.state.topicSelected}
@@ -1702,6 +1728,7 @@
onCreateTopic={this.handleNewTopicRequest} onCreateTopic={this.handleNewTopicRequest}
onNewTopic={this.handleNewTopic} onNewTopic={this.handleNewTopic}
onCancel={this.handleSidepanelCancel} onCancel={this.handleSidepanelCancel}
showContextMenu={this.handleShowContextMenu} /> showContextMenu={this.handleShowContextMenu} />
<MessagesView <MessagesView
@@ -1709,6 +1736,7 @@
displayMobile={this.state.displayMobile} displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'topic-view')} hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'topic-view')}
topic={this.state.topicSelected} topic={this.state.topicSelected}
myUserId={this.state.myUserId}
serverVersion={this.state.serverVersion} serverVersion={this.state.serverVersion}
serverAddress={this.state.serverAddress} serverAddress={this.state.serverAddress}
onData={this.tnData} onData={this.tnData}