bug fixes and visual clean up

This commit is contained in:
or-else
2017-06-25 15:26:19 +03:00
parent ac97f1d61d
commit e3909c96bd
9 changed files with 121 additions and 84 deletions
+14 -4
View File
@@ -14,17 +14,27 @@ Photos from https://www.pexels.com/ under [CC0 license](https://www.pexels.com/p
Background pattern from http://subtlepatterns.com/, commercial and non-commercial use allowed with attribution. Background pattern from http://subtlepatterns.com/, commercial and non-commercial use allowed with attribution.
## Missing functionality
* Cannot manage group members.
* Editing chat parameters is incomplete and buggy.
* Cannot send images or other media.
* No typing indicators.
* No indicators of chat status (muted, blocked, etc.).
* Long polling as a wire transport does not work.
* Browser push notifications are missing.
* No encryption.
## Responsive design ## Responsive design
### Desktop ### Desktop
<p align="center"> <p align="center">
<img src="web-desktop.png" alt="Desktop web: full app" width=540 /> <img src="web-desktop.png" alt="Desktop web: full app" width=866 />
</p> </p>
### Mobile ### Mobile
<p align="center" nowrap> <p align="center">
<img src="web-mobile-contacts.png" alt="Mobile web: contacts" width=401 /> <img src="web-mob-contacts.png" alt="Mobile web: contacts" width=323 /> <img src="web-mob-chat.png" alt="Mobile web: chat" width=323 /> <img src="web-mob-topicinfo.png" alt="Mobile web: topic info" width=323 />
<img src="web-mobile-chat.png" alt="Mobile web: chat" width=485 />
</p> </p>
+62 -38
View File
@@ -22,11 +22,6 @@ tt {
font-family: Roboto Mono, monospace; font-family: Roboto Mono, monospace;
} }
hr {
height: 1px;
color: #666;
}
a { a {
color: #2196f3; color: #2196f3;
text-decoration: none; text-decoration: none;
@@ -78,8 +73,8 @@ button {
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
white-space: nowrap; white-space: nowrap;
padding-left: 2em; padding-left: 2rem;
padding-right: 2em; padding-right: 2rem;
border-radius: 2px; border-radius: 2px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.25); box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.25);
text-transform: uppercase; text-transform: uppercase;
@@ -256,8 +251,10 @@ ul.menu > li.separator:hover {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 0.3;
flex-shrink: 0.3;
width: 24rem; width: 24rem;
min-width: 24rem; min-width: 18rem;
height: 100%; height: 100%;
} }
@@ -271,8 +268,10 @@ ul.menu > li.separator:hover {
#info-view { #info-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 0.3;
flex-shrink: 0.3;
width: 24rem; width: 24rem;
min-width: 24rem; min-width: 18rem;
height: 100%; height: 100%;
} }
@@ -285,6 +284,8 @@ ul.menu > li.separator:hover {
@media (max-width: 640px) { @media (max-width: 640px) {
#app-container { #app-container {
width: 100%; width: 100%;
border-radius: 0;
box-shadow: none;
} }
#sidepanel { #sidepanel {
@@ -312,6 +313,11 @@ ul.menu > li.separator:hover {
display: none!important; display: none!important;
} }
/* Quoted div */
.quoted {
padding-left: 1.5rem;
}
/* BEGIN InPlaceEdit styles */ /* BEGIN InPlaceEdit styles */
.in-place-edit { .in-place-edit {
display: inline-block; display: inline-block;
@@ -320,12 +326,10 @@ ul.menu > li.separator:hover {
height: 2.5rem; height: 2.5rem;
line-height: 1.5; line-height: 1.5;
margin: 0.25rem 0.125rem; margin: 0.25rem 0.125rem;
-x-padding: 0 0.125rem;
width: 100%; width: 100%;
vertical-align: baseline; vertical-align: baseline;
border-bottom: 1px dashed #ddd; border-bottom: 1px dashed #ddd;
cursor: pointer; cursor: pointer;
overflow: hidden;
} }
.in-place-edit.disabled { .in-place-edit.disabled {
@@ -337,6 +341,12 @@ ul.menu > li.separator:hover {
color: #bbb; color: #bbb;
} }
.in-place-edit .content {
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden
}
/* END of InPlaceEdit styles */ /* END of InPlaceEdit styles */
/* END of top-level layout */ /* END of top-level layout */
@@ -350,8 +360,8 @@ ul.menu > li.separator:hover {
display: flex; display: flex;
align-items: center; align-items: center;
flex: none; flex: none;
padding: 0.5em; padding: 0.5rem;
height: 4em; height: 4rem;
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 */
@@ -359,7 +369,7 @@ ul.menu > li.separator:hover {
.dialog-buttons { .dialog-buttons {
display: flex; display: flex;
padding: 0.5em; padding: 0.5rem;
justify-content: flex-end; justify-content: flex-end;
} }
@@ -385,7 +395,7 @@ ul.menu > li.separator:hover {
#sidepanel-title { #sidepanel-title {
margin-right: auto; margin-right: auto;
margin-left: 0.5em; margin-left: 0.5rem;
} }
#self-avatar { #self-avatar {
@@ -421,7 +431,7 @@ ul.menu > li.separator:hover {
overflow: hidden; overflow: hidden;
} }
#alert-box.error-alert { #alert-box.error-alert {
padding: 0.5em; padding: 0.5rem;
background-color: #ffcdd2; background-color: #ffcdd2;
border-color: #d32f2f; border-color: #d32f2f;
color: #b71c1c; color: #b71c1c;
@@ -484,6 +494,7 @@ ul.menu > li.separator:hover {
width: 3rem; width: 3rem;
min-width: 3rem; min-width: 3rem;
height: 3rem; height: 3rem;
min-height: 3rem;
font-size: 1.8rem; font-size: 1.8rem;
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;
@@ -503,7 +514,10 @@ ul.menu > li.separator:hover {
.contact-box .online, .offline { .contact-box .online, .offline {
display: inline-block; display: inline-block;
width: 0.75rem; width: 0.75rem;
min-width: 0.75rem;
height: 0.75rem; height: 0.75rem;
min-height: 0.75rem;
flex-shrink: 0;
border-radius: 50%; border-radius: 50%;
position: absolute; position: absolute;
right: 0; right: 0;
@@ -519,8 +533,8 @@ ul.menu > li.separator:hover {
#add-topic { #add-topic {
position: absolute; position: absolute;
right: 1em; right: 1rem;
bottom: 1em; bottom: 1rem;
} }
/* Various forms inside a panel (signup form, new topic form) */ /* Various forms inside a panel (signup form, new topic form) */
@@ -544,15 +558,21 @@ ul.menu > li.separator:hover {
/* Styling of avatar upload area in the New Account panel */ /* Styling of avatar upload area in the New Account panel */
.avatar-upload { .avatar-upload {
position: relative; display: flex;
text-align: center; flex-direction: column;
vertical-align: middle;
flex-shrink: 0; flex-shrink: 0;
width: 7rem; width: 7rem;
height: 7rem; height: 9rem;
margin-left: 0.5rem; margin-left: 0.5rem;
padding: 0; padding: 0;
border-radius: 50%; }
/* Clearfix */
.avatar-upload:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
} }
.avatar-upload .blank { .avatar-upload .blank {
@@ -560,6 +580,7 @@ ul.menu > li.separator:hover {
vertical-align: middle; vertical-align: middle;
border: 1px dashed #999; border: 1px dashed #999;
font-size: 1rem; font-size: 1rem;
flex-shrink: 0;
width: 7rem; width: 7rem;
height: 7rem; height: 7rem;
overflow: hidden; overflow: hidden;
@@ -584,18 +605,19 @@ ul.menu > li.separator:hover {
} }
.avatar-upload .inputfile + label.round { .avatar-upload .inputfile + label.round {
display: inline-block; display: flex;
vertical-align: middle; align-items: center;
text-align: center; justify-content: center;
cursor: pointer; cursor: pointer;
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
line-height: 4rem; line-height: 3rem;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 1px 4px rgba(0,0,0,0.4); box-shadow: 0 1px 4px rgba(0,0,0,0.4);
position: absolute; margin-left: auto;
top: 6rem; margin-right: auto;
left: 2rem; margin-top: -1rem;
bottom: 0;
background-color: #fafafa; background-color: #fafafa;
} }
@@ -724,7 +746,7 @@ ul.menu > li.separator:hover {
.chat-box li.single { .chat-box li.single {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 0.25rem; margin-bottom: 0.75rem;
} }
.chat-box li.first { .chat-box li.first {
@@ -739,7 +761,7 @@ ul.menu > li.separator:hover {
.chat-box li.last { .chat-box li.last {
margin-top: 0.15rem; margin-top: 0.15rem;
margin-bottom: 0.25rem; margin-bottom: 0.75rem;
} }
.chat-box .left .bubble { .chat-box .left .bubble {
@@ -819,7 +841,7 @@ ul.menu > li.separator:hover {
font-size: 80%; font-size: 80%;
color: #777; color: #777;
padding-left: 1rem; padding-left: 1rem;
top: 0.5em; top: 0.5rem;
float: right; float: right;
white-space: nowrap; white-space: nowrap;
} }
@@ -980,15 +1002,17 @@ ul.tabbar li.active > a:after {
/* Badge with the number of unread messages */ /* Badge with the number of unread messages */
.badge { .badge {
display: inline-block; display: inline-block;
font-size: 0.8rem; font-size: 0.75rem;
font-weight: bold; font-weight: bolder;
background-color: #0097A7; background-color: #0097A7;
color: white; color: white;
border-radius: 1em; border-radius: 50%;
padding: 0.25em; padding: 0.25rem;
min-width: 1.75em; min-width: 1.4rem;
margin-left: 0.5rem; margin-left: 0.5rem;
line-height: 1;
text-align: center; text-align: center;
vertical-align: middle;
} }
/* Color of buttons */ /* Color of buttons */
+45 -42
View File
@@ -225,6 +225,8 @@
return avatar; return avatar;
} }
} }
/* END Lettertile */ /* END Lettertile */
/* BEGIN In-place text editor. Shows text with an icon /* BEGIN In-place text editor. Shows text with an icon
@@ -269,6 +271,9 @@
spanText = this.props.placeholder; spanText = this.props.placeholder;
spanClass += " placeholder"; spanClass += " placeholder";
} }
if (spanText.length > 16) {
spanText = spanText.substring(0, 16) + "...";
}
return ( return (
this.state.active ? this.state.active ?
<input type={this.props.type || "text"} <input type={this.props.type || "text"}
@@ -279,8 +284,9 @@
onBlur={this.handleEditingFinished} onBlur={this.handleEditingFinished}
autoFocus /> autoFocus />
: :
<span className={spanClass} <span className={spanClass} onClick={this.handleStartEditing}>
onClick={this.handleStartEditing}>{spanText}</span> <span className="content">{spanText}</span>
</span>
); );
} }
}); });
@@ -518,16 +524,12 @@
var SettingsView = React.createClass({ var SettingsView = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
hostName: this.props.serverAddress,
transport: "def" transport: "def"
}; };
}, },
handleSubmit: function(e) { handleSubmit: function(e) {
e.preventDefault(); e.preventDefault();
this.props.onUpdate(this.state.hostName, this.state.transport); this.props.onUpdate(null, this.state.transport);
},
handleHostNameChange: function(name) {
this.setState({hostName: name});
}, },
handleTransportSelected: function(e) { handleTransportSelected: function(e) {
this.setState({transport: e.currentTarget.value}); this.setState({transport: e.currentTarget.value});
@@ -550,12 +552,8 @@
}); });
return ( return (
<form id="settings-form" onSubmit={this.handleSubmit}> <form id="settings-form" onSubmit={this.handleSubmit}>
<p>This form does not do anything yet</p> <label className="small">Wire transport:</label>
<HostSelector <ul className="quoted">
serverAddress={this.props.serverAddress}
onHostNameChange={this.handleHostNameChange} />
<p>Wire transport:</p>
<ul>
{transportOptions} {transportOptions}
</ul> </ul>
<div className="dialog-buttons"> <div className="dialog-buttons">
@@ -680,7 +678,6 @@
this.props.state === 'settings' ? this.props.state === 'settings' ?
<SettingsView onCancel={this.props.onCancel} <SettingsView onCancel={this.props.onCancel}
serverAddress={this.props.serverAddress}
onUpdate={this.props.onConnectionSettings} onUpdate={this.props.onConnectionSettings}
/> : /> :
@@ -729,7 +726,8 @@
var EditAccountView = React.createClass({ var EditAccountView = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
updated: false, updatedDesc: false,
updatedPasswd: false,
address: null, address: null,
fullName: undefined, fullName: undefined,
avatar: null, avatar: null,
@@ -760,20 +758,21 @@
}, },
handleFullNameUpdate: function(fn) { handleFullNameUpdate: function(fn) {
this.setState({updated: true, fullName: fn}); this.setState({updatedDesc: true, fullName: fn});
}, },
handlePasswordUpdate: function(pwd) { handlePasswordUpdate: function(pwd) {
this.setState({updated: true, password: pwd}); this.setState({updatedPasswd: true, password: pwd});
}, },
handleImageChanged: function(img) { handleImageChanged: function(img) {
this.setState({updated: true, avatar: img}); this.setState({updatedDesc: true, avatar: img});
}, },
handleSubmit: function(e) { handleSubmit: function(e) {
e.preventDefault(); e.preventDefault();
if (this.state.updated) { if (this.state.updatedDesc || this.state.updatedPasswd) {
this.props.onSubmit(this.state.password, this.props.onSubmit(
vcard(this.state.fullName, this.state.avatar)); this.state.updatedPasswd ? this.state.password : null,
this.state.updatedDesc ? vcard(this.state.fullName, this.state.avatar) : null);
} }
}, },
@@ -797,15 +796,15 @@
avatar={this.state.avatar} avatar={this.state.avatar}
onImageChanged={this.handleImageChanged} /> onImageChanged={this.handleImageChanged} />
</div> </div>
<br />
<hr />
<br />
<div> <div>
<div>Login: <tt>{this.state.login}</tt></div> <div>Login: <tt>{this.state.login}</tt></div>
<div>Address: <tt>{this.state.address}</tt></div> <div>Address: <tt>{this.state.address}</tt></div>
<div>Default access mode:</div>
<div>&nbsp;&nbsp;Auth: <tt>{this.state.auth}</tt></div> <div><label className="small">Default access mode:</label></div>
<div>&nbsp;&nbsp;Anon: <tt>{this.state.anon}</tt></div> <div className="quoted">
<div>Auth: <tt>{this.state.auth}</tt></div>
<div>Anon: <tt>{this.state.anon}</tt></div>
</div>
</div> </div>
<div className="dialog-buttons"> <div className="dialog-buttons">
<button className="blue" type="submit">Update</button> <button className="blue" type="submit">Update</button>
@@ -1172,6 +1171,7 @@
var InfoView = React.createClass({ var InfoView = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
owner: false,
updated: false, updated: false,
address: null, address: null,
fullName: undefined, fullName: undefined,
@@ -1187,7 +1187,9 @@
topic.onMetaDesc = this.onMetaDesc; topic.onMetaDesc = this.onMetaDesc;
var defacs = topic.getDefaultAccess() || {}; var defacs = topic.getDefaultAccess() || {};
var acs = topic.getAccessMode();
this.setState({ this.setState({
owner: acs && acs.isOwner(),
fullName: topic.public ? topic.public.fn : undefined, fullName: topic.public ? topic.public.fn : undefined,
avatar: makeImageUrl(topic.public ? topic.public.photo : null), avatar: makeImageUrl(topic.public ? topic.public.photo : null),
private: topic.private, private: topic.private,
@@ -1242,6 +1244,7 @@
<div><label className="small">Name</label></div> <div><label className="small">Name</label></div>
<div><InPlaceEdit <div><InPlaceEdit
placeholder="Group name" placeholder="Group name"
readOnly={!this.state.owner}
text={this.state.fullName} text={this.state.fullName}
onFinished={this.handleFullNameUpdate} /></div> onFinished={this.handleFullNameUpdate} /></div>
<div><label className="small">Private comment</label></div> <div><label className="small">Private comment</label></div>
@@ -1254,15 +1257,15 @@
avatar={this.state.avatar} avatar={this.state.avatar}
onImageChanged={this.handleImageChanged} /> onImageChanged={this.handleImageChanged} />
</div> </div>
<hr />
<br />
<div> <div>
<div>Address: <tt>{this.state.address}</tt></div> <div>Address: <tt>{this.state.address}</tt></div>
{this.state.auth || this.state.anon ? {this.state.auth || this.state.anon ?
<div> <div>
<div>Default access mode:</div> <div><label className="small">Default access mode:</label></div>
<div>&nbsp;&nbsp;Auth: <tt>{this.state.auth}</tt></div> <div className="quoted">
<div>&nbsp;&nbsp;Anon: <tt>{this.state.anon}</tt></div> <div>Auth: <tt>{this.state.auth}</tt></div>
<div>Anon: <tt>{this.state.anon}</tt></div>
</div>
</div> </div>
: :
null null
@@ -1526,13 +1529,11 @@
</a> </a>
: :
null} null}
{this.props.displayMobile ? <div className="avatar-box">
<div className="avatar-box"> <LetterTile avatar={avatar}
<LetterTile avatar={avatar} topic={this.state.topic} title={this.state.title} />
topic={this.state.topic} title={this.state.title} /> <span className="online" />
<span className="online" /> </div>
</div>
: null}
<div id="topic-title-group"> <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>
@@ -1858,8 +1859,9 @@
handleUpdateAccountRequest: function(password, pub, priv) { handleUpdateAccountRequest: function(password, pub, priv) {
this.handleSidepanelCancel(); this.handleSidepanelCancel();
var me = Tinode.getMeTopic(); if (pub || priv) {
me.setMeta({desc: {public: pub, private: priv}}); Tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}});
}
if (password) { if (password) {
Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password); Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password);
} }
@@ -2066,7 +2068,8 @@
<MessagesView <MessagesView
onHideMessagesView={this.handleHideMessagesView} onHideMessagesView={this.handleHideMessagesView}
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' || this.state.showInfoPanel))}
topic={this.state.topicSelected} topic={this.state.topicSelected}
myUserId={this.state.myUserId} myUserId={this.state.myUserId}
serverVersion={this.state.serverVersion} serverVersion={this.state.serverVersion}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 541 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB