added topic and account editing
This commit is contained in:
@@ -18,9 +18,13 @@ Background pattern from http://subtlepatterns.com/, commercial and non-commercia
|
|||||||
|
|
||||||
### Desktop
|
### Desktop
|
||||||
|
|
||||||
<img src="web-desktop.png" alt="Desktop web: full app" width=540 />
|
<p align="center">
|
||||||
|
<img src="web-desktop.png" alt="Desktop web: full app" width=540 />
|
||||||
|
</p>
|
||||||
|
|
||||||
### Mobile
|
### Mobile
|
||||||
|
|
||||||
<img src="web-mobile-contacts.png" alt="Mobile web: contacts" width=401 />
|
<p align="center" nowrap>
|
||||||
<img src="web-mobile-chat.png" alt="Mobile web: chat" width=485 />
|
<img src="web-mobile-contacts.png" alt="Mobile web: contacts" width=401 />
|
||||||
|
<img src="web-mobile-chat.png" alt="Mobile web: chat" width=485 />
|
||||||
|
</p>
|
||||||
|
|||||||
+140
-57
@@ -18,6 +18,15 @@ html, body {
|
|||||||
box-shadow: inset 0 10rem #3949AB; /* the deep-plue stripe accross the top */
|
box-shadow: inset 0 10rem #3949AB; /* the deep-plue stripe accross the top */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
font-family: Roboto Mono, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 1px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #2196f3;
|
color: #2196f3;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -34,11 +43,18 @@ a:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
font-size: 1.2rem;
|
font-size: 1.1rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
line-height: 1.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
form label {
|
form label {
|
||||||
font-size: 1rem;
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form label.small {
|
||||||
|
color: #1e88e5;
|
||||||
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.avatar {
|
img.avatar {
|
||||||
@@ -131,13 +147,12 @@ button.white:focus, button.white:hover, button.blue:active {
|
|||||||
|
|
||||||
textarea, input {
|
textarea, input {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 1.05rem;
|
font-size: 1.1rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
padding: 0 0.125rem;
|
padding: 0 0.125rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-sizing: border-box;
|
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
@@ -148,7 +163,6 @@ input[type="password"],
|
|||||||
textarea {
|
textarea {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
border-radius: 0.2rem;
|
|
||||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -160,6 +174,12 @@ input[type="password"]:focus {
|
|||||||
box-shadow: 0 1px 0 rgba(33, 150, 243, 0.25);
|
box-shadow: 0 1px 0 rgba(33, 150, 243, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="text"]::placeholder,
|
||||||
|
input[type="password"]::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
/* 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;
|
||||||
@@ -176,12 +196,6 @@ input[type="password"]:focus {
|
|||||||
|
|
||||||
/* END of input and buttons */
|
/* END of input and buttons */
|
||||||
|
|
||||||
form {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
line-height: 1.75;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BEGIN Popup/dropdown menu */
|
/* BEGIN Popup/dropdown menu */
|
||||||
|
|
||||||
ul.menu {
|
ul.menu {
|
||||||
@@ -254,6 +268,14 @@ ul.menu > li.separator:hover {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#info-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 24rem;
|
||||||
|
min-width: 24rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
#mountPoint {
|
#mountPoint {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -268,6 +290,10 @@ ul.menu > li.separator:hover {
|
|||||||
#sidepanel {
|
#sidepanel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#info-view {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generic announcement text, centered vertically and horizontally */
|
/* Generic announcement text, centered vertically and horizontally */
|
||||||
@@ -281,11 +307,38 @@ ul.menu > li.separator:hover {
|
|||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Class to alternatively hide sidepanel/topic-view */
|
/* Class to alternatively hide sidepanel/topic-view/info-view */
|
||||||
.nodisplay {
|
.nodisplay {
|
||||||
display: none!important;
|
display: none!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* BEGIN InPlaceEdit styles */
|
||||||
|
.in-place-edit {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0.25rem 0.125rem;
|
||||||
|
-x-padding: 0 0.125rem;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: baseline;
|
||||||
|
border-bottom: 1px dashed #ddd;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.in-place-edit.disabled {
|
||||||
|
border-bottom: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.in-place-edit.placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END of InPlaceEdit styles */
|
||||||
|
|
||||||
/* END of top-level layout */
|
/* END of top-level layout */
|
||||||
|
|
||||||
/* Sidepanel styles */
|
/* Sidepanel styles */
|
||||||
@@ -293,30 +346,37 @@ ul.menu > li.separator:hover {
|
|||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-buttons {
|
.caption-panel {
|
||||||
display: flex;
|
|
||||||
padding: 0.5em;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-caption-panel {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
|
|
||||||
background-color: #2196f3; /* blue 500 */
|
|
||||||
color: #bbdefb; /* light-blue 100 */
|
|
||||||
|
|
||||||
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 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
padding: 0.5em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-box {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#side-caption-panel {
|
||||||
|
background-color: #2196f3; /* blue 500 */
|
||||||
|
color: #bbdefb; /* light-blue 100 */
|
||||||
|
}
|
||||||
|
|
||||||
#side-caption-panel a {
|
#side-caption-panel a {
|
||||||
color: #bbdefb; /* blue 100 */
|
color: #bbdefb; /* blue 100 */
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5rem;
|
||||||
vertical-align: -1em; /* icons are bigger than the declared font size */
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#side-caption-panel :hover {
|
#side-caption-panel :hover {
|
||||||
@@ -330,11 +390,16 @@ ul.menu > li.separator:hover {
|
|||||||
|
|
||||||
#self-avatar {
|
#self-avatar {
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
|
min-width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
flex: none;
|
flex: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#self-avatar.avatar-box {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Application settings */
|
/* Application settings */
|
||||||
#settings-form ul {
|
#settings-form ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -417,6 +482,7 @@ ul.menu > li.separator:hover {
|
|||||||
margin-right: 0.35rem;
|
margin-right: 0.35rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
|
min-width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -472,7 +538,7 @@ ul.menu > li.separator:hover {
|
|||||||
.panel-form-two {
|
.panel-form-two {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -481,36 +547,32 @@ ul.menu > li.separator:hover {
|
|||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 4.5rem;
|
flex-shrink: 0;
|
||||||
height: auto;
|
width: 7rem;
|
||||||
box-sizing: border-box;
|
height: 7rem;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-upload .blank {
|
.avatar-upload .blank {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
border: 1px dashed #999;
|
border: 1px dashed #999;
|
||||||
border-radius: 0.75rem;
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
width: 4.5rem;
|
width: 7rem;
|
||||||
height: 4.5rem;
|
height: 7rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #999;
|
color: #999;
|
||||||
vertical-align: middle;
|
line-height: 7rem;
|
||||||
line-height: 4.5rem;
|
border-radius: 50%;
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-upload .preview {
|
.avatar-upload .preview {
|
||||||
border-radius: 0.75rem;
|
width: 7rem;
|
||||||
width: 4.5rem;
|
height: 7rem;
|
||||||
height: 4.5rem;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-sizing: border-box;
|
border-radius: 50%;
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-upload .inputfile.hidden {
|
.avatar-upload .inputfile.hidden {
|
||||||
@@ -526,14 +588,14 @@ ul.menu > li.separator:hover {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 2.5rem;
|
width: 3rem;
|
||||||
height: 2.5rem;
|
height: 3rem;
|
||||||
line-height: 3rem;
|
line-height: 4rem;
|
||||||
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;
|
position: absolute;
|
||||||
top: 3.875rem;
|
top: 6rem;
|
||||||
left: 1rem;
|
left: 2rem;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,24 +614,23 @@ ul.menu > li.separator:hover {
|
|||||||
|
|
||||||
/* Topic/messages view */
|
/* Topic/messages view */
|
||||||
#topic-caption-panel {
|
#topic-caption-panel {
|
||||||
display: flex;
|
|
||||||
align-items:center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex: none;
|
|
||||||
padding: 0.5rem;
|
|
||||||
height: 4rem;
|
|
||||||
background-color: #b0bec5;
|
background-color: #b0bec5;
|
||||||
box-shadow: 0 1px 2px gray;
|
|
||||||
z-index: 1; /* otherwise the shadow becomes invisible */
|
|
||||||
}
|
}
|
||||||
#topic-caption-panel a {
|
#topic-caption-panel a {
|
||||||
color: #757575;
|
color: #757575;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#topic-caption-panel :hover {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#topic-caption-panel .avatar-box {
|
#topic-caption-panel .avatar-box {
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
|
min-width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
font-size: 1.8rem;
|
||||||
margin-right: 0.35rem;
|
margin-right: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,7 +791,9 @@ ul.menu > li.separator:hover {
|
|||||||
.chat-box .avatar-box {
|
.chat-box .avatar-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 1.4rem;
|
width: 1.4rem;
|
||||||
|
min-width: 1.4rem;
|
||||||
height: 1.4rem;
|
height: 1.4rem;
|
||||||
|
min-height: 1.4rem;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
margin-right: 0.8rem;
|
margin-right: 0.8rem;
|
||||||
@@ -784,6 +847,24 @@ ul.menu > li.separator:hover {
|
|||||||
/* END of talk bubbles */
|
/* END of talk bubbles */
|
||||||
/* END of topic/mesages view */
|
/* END of topic/mesages view */
|
||||||
|
|
||||||
|
/* InfoView styles */
|
||||||
|
#info-caption-panel {
|
||||||
|
background-color: #b0bec5;
|
||||||
|
}
|
||||||
|
#info-caption-panel a {
|
||||||
|
color: #757575;
|
||||||
|
line-height: 1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#info-caption-panel :hover {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-title {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END of InfoView */
|
||||||
|
|
||||||
/* Modal dialog */
|
/* Modal dialog */
|
||||||
.dialog-overlay {
|
.dialog-overlay {
|
||||||
@@ -915,7 +996,7 @@ ul.tabbar li.active > a:after {
|
|||||||
color: #2196f3; /* blue 500 */
|
color: #2196f3; /* blue 500 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Letter tile base style */
|
/* Letter tile base style, responsive square box */
|
||||||
.lettertile {
|
.lettertile {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
@@ -923,9 +1004,11 @@ ul.tabbar li.active > a:after {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive square box for text tiles */
|
/* Div with letter tile content */
|
||||||
.lettertile > div {
|
.lettertile > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; bottom: 0; left: 0; right: 0;
|
top: 0; bottom: 0; left: 0; right: 0;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|||||||
+376
-88
@@ -67,6 +67,8 @@
|
|||||||
|
|
||||||
// Make a data URL from public.photo
|
// Make a data URL from public.photo
|
||||||
function makeImageUrl(photo) {
|
function makeImageUrl(photo) {
|
||||||
|
//console.log("makeImageUrl");
|
||||||
|
//console.log(photo);
|
||||||
return (photo && photo.type && photo.data) ?
|
return (photo && photo.type && photo.data) ?
|
||||||
'data:image/' + photo.type + ';base64,' + photo.data : null;
|
'data:image/' + photo.type + ';base64,' + photo.data : null;
|
||||||
}
|
}
|
||||||
@@ -94,6 +96,26 @@
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create VCard which represents topic 'public' info
|
||||||
|
function vcard(fn, imageDataUrl) {
|
||||||
|
var card = null;
|
||||||
|
|
||||||
|
if ((fn && fn.trim()) || imageDataUrl) {
|
||||||
|
card = {};
|
||||||
|
if (fn) {
|
||||||
|
card.fn = fn.trim();
|
||||||
|
}
|
||||||
|
if (imageDataUrl) {
|
||||||
|
var dataStart = imageDataUrl.indexOf(",");
|
||||||
|
card.photo = {
|
||||||
|
data: imageDataUrl.substring(dataStart+1),
|
||||||
|
type: "jpg"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
/* BEGIN Popup/dropdown menu */
|
/* BEGIN Popup/dropdown menu */
|
||||||
var ContextMenu = React.createClass({
|
var ContextMenu = React.createClass({
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
@@ -172,6 +194,15 @@
|
|||||||
});
|
});
|
||||||
/* END Popup/dropdown menu */
|
/* END Popup/dropdown menu */
|
||||||
|
|
||||||
|
/* The X menu to be displayed in title bars */
|
||||||
|
var MenuCancel = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<a href="#" onClick={this.props.onCancel}><i className="material-icons">close</i></a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* BEGIN Lettertile: Avatar box: either a bitmap or a letter tile or a stock icon. */
|
/* BEGIN Lettertile: Avatar box: either a bitmap or a letter tile or a stock icon. */
|
||||||
class LetterTile extends React.PureComponent {
|
class LetterTile extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
@@ -196,6 +227,66 @@
|
|||||||
}
|
}
|
||||||
/* END Lettertile */
|
/* END Lettertile */
|
||||||
|
|
||||||
|
/* BEGIN In-place text editor. Shows text with an icon
|
||||||
|
* which toggles it into an input field */
|
||||||
|
var InPlaceEdit = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
active: this.props.state,
|
||||||
|
text: this.props.text || "",
|
||||||
|
changed: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handeTextChange: function(e) {
|
||||||
|
this.setState({text: e.target.value});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleKeyPress: function(e) {
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
this.setState({text: this.props.text, active: false});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleStartEditing: function() {
|
||||||
|
ReactDOM.findDOMNode(this).focus();
|
||||||
|
this.setState({active: !this.props.readOnly});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEditingFinished: function() {
|
||||||
|
this.setState({active: false});
|
||||||
|
if (this.state.text !== this.props.text) {
|
||||||
|
this.props.onFinished(this.state.text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var spanText = this.props.type === "password" ?
|
||||||
|
"••••••••" : this.state.text;
|
||||||
|
var spanClass = "in-place-edit" +
|
||||||
|
(this.props.readOnly ? " disabled" : "");
|
||||||
|
if (!spanText) {
|
||||||
|
spanText = this.props.placeholder;
|
||||||
|
spanClass += " placeholder";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
this.state.active ?
|
||||||
|
<input type={this.props.type || "text"}
|
||||||
|
value={this.state.text}
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
onChange={this.handeTextChange}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
onBlur={this.handleEditingFinished}
|
||||||
|
autoFocus />
|
||||||
|
:
|
||||||
|
<span className={spanClass}
|
||||||
|
onClick={this.handleStartEditing}>{spanText}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* END InPlaceEdit */
|
||||||
|
|
||||||
/* BEGIN Combobox for selecting host name */
|
/* BEGIN Combobox for selecting host name */
|
||||||
class HostSelector extends React.PureComponent {
|
class HostSelector extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -338,22 +429,10 @@
|
|||||||
// FIXME(gene): report an error here.
|
// FIXME(gene): report an error here.
|
||||||
} else {
|
} else {
|
||||||
this.setState({errorCleared: false});
|
this.setState({errorCleared: false});
|
||||||
var vcard = null;
|
this.props.onCreateAccount(
|
||||||
|
this.state.login.trim(),
|
||||||
if ((this.state.fn && this.state.fn.trim()) || this.state.imageDataUrl) {
|
this.state.password.trim(),
|
||||||
vcard = {};
|
vcard(this.state.fn, this.state.imageDataUrl));
|
||||||
if (this.state.fn) {
|
|
||||||
vcard.fn = this.state.fn.trim();
|
|
||||||
}
|
|
||||||
if (this.state.imageDataUrl) {
|
|
||||||
var dataStart = this.state.imageDataUrl.indexOf(",");
|
|
||||||
vcard.photo = {
|
|
||||||
data: this.state.imageDataUrl.substring(dataStart+1),
|
|
||||||
type: "jpg"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onCreateAccount(this.state.login.trim(), this.state.password.trim(), vcard);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
@@ -362,26 +441,24 @@
|
|||||||
submitClasses += " disabled";
|
submitClasses += " disabled";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<form className="panel-form" onSubmit={this.handleSubmit}>
|
||||||
<form className="panel-form" onSubmit={this.handleSubmit}>
|
<div className="panel-form-one">
|
||||||
<div className="panel-form-one">
|
<div className="panel-form-two">
|
||||||
<div className="panel-form-two">
|
<input type="text" placeholder="Login"
|
||||||
<input type="text" placeholder="Login"
|
value={this.state.login} onChange={this.handleLoginChange} required autoFocus />
|
||||||
value={this.state.login} onChange={this.handleLoginChange} required autoFocus />
|
<input type="password" placeholder="Password"
|
||||||
<input type="password" placeholder="Password"
|
value={this.state.password} onChange={this.handlePasswordChange} required />
|
||||||
value={this.state.password} onChange={this.handlePasswordChange} required />
|
<input type="password" placeholder="Repeat password"
|
||||||
<input type="password" placeholder="Repeat password"
|
value={this.state.password2} onChange={this.handlePassword2Change} required />
|
||||||
value={this.state.password2} onChange={this.handlePassword2Change} required />
|
|
||||||
</div>
|
|
||||||
<AvatarUpload onImageChanged={this.handleImageChanged} />
|
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placeholder="Full name, e.g. John Doe"
|
<AvatarUpload onImageChanged={this.handleImageChanged} />
|
||||||
value={this.state.fn} onChange={this.handleFnChange} />
|
</div>
|
||||||
<div className="dialog-buttons">
|
<input type="text" placeholder="Full name, e.g. John Doe"
|
||||||
<button className={submitClasses} type="submit">Sign up</button>
|
value={this.state.fn} onChange={this.handleFnChange} />
|
||||||
</div>
|
<div className="dialog-buttons">
|
||||||
</form>
|
<button className={submitClasses} type="submit">Sign up</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -389,7 +466,7 @@
|
|||||||
var AvatarUpload = React.createClass({
|
var AvatarUpload = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
dataUrl: null
|
dataUrl: this.props.avatar
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleFileUpload(e) {
|
handleFileUpload(e) {
|
||||||
@@ -398,7 +475,7 @@
|
|||||||
img.crossOrigin = 'Anonymous';
|
img.crossOrigin = 'Anonymous';
|
||||||
img.onload = function() {
|
img.onload = function() {
|
||||||
var canvas = document.createElement('CANVAS');
|
var canvas = document.createElement('CANVAS');
|
||||||
canvas.width = canvas.height = 64;
|
canvas.width = canvas.height = 128;
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d');
|
||||||
var sx = 0, sy = 0, size;
|
var sx = 0, sy = 0, size;
|
||||||
if (this.height > this.width) {
|
if (this.height > this.width) {
|
||||||
@@ -409,7 +486,7 @@
|
|||||||
sx = (this.width - size) / 2;
|
sx = (this.width - size) / 2;
|
||||||
}
|
}
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.drawImage(this, sx, sy, size, size, 0, 0, 64, 64);
|
ctx.drawImage(this, sx, sy, size, size, 0, 0, canvas.width, canvas.height);
|
||||||
var bits = canvas.toDataURL("image/jpg");
|
var bits = canvas.toDataURL("image/jpg");
|
||||||
canvas = null;
|
canvas = null;
|
||||||
|
|
||||||
@@ -423,12 +500,14 @@
|
|||||||
<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">64×64</div>}
|
<div className="blank">128×128</div>}
|
||||||
<input type="file" id="file-input-avatar" className="inputfile hidden"
|
{this.props.readOnly ? null :
|
||||||
accept="image/*" onChange={this.handleFileUpload} />
|
<input type="file" id="file-input-avatar" className="inputfile hidden"
|
||||||
|
accept="image/*" onChange={this.handleFileUpload} />}
|
||||||
|
{this.props.readOnly ? null :
|
||||||
<label htmlFor="file-input-avatar" className="round">
|
<label htmlFor="file-input-avatar" className="round">
|
||||||
<i className="material-icons">file_upload</i>
|
<i className="material-icons">file_upload</i>
|
||||||
</label>
|
</label>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -492,22 +571,22 @@
|
|||||||
var SideNavbar = React.createClass({
|
var SideNavbar = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div id="side-caption-panel">
|
<div id="side-caption-panel" className="caption-panel">
|
||||||
<div id="self-avatar"><LetterTile
|
<div id="self-avatar" className="avatar-box"><LetterTile
|
||||||
avatar={this.props.avatar} topic={this.props.myUserId} title={this.props.title} /></div>
|
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} /> :
|
||||||
this.props.state === 'settings' ?
|
this.props.state === 'settings' ?
|
||||||
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
|
<MenuCancel onCancel={this.props.onCancel} /> :
|
||||||
this.props.state === 'edit-account' ?
|
this.props.state === 'edit-account' ?
|
||||||
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
|
<MenuCancel onCancel={this.props.onCancel} /> :
|
||||||
this.props.state === 'register' ?
|
this.props.state === 'register' ?
|
||||||
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
|
<MenuCancel onCancel={this.props.onCancel} /> :
|
||||||
this.props.state === 'contacts' ?
|
this.props.state === 'contacts' ?
|
||||||
<SideNavbar.MenuContacts onNewTopic={this.props.onNewTopic} onSettings={this.props.onSettings} /> :
|
<SideNavbar.MenuContacts onNewTopic={this.props.onNewTopic} onSettings={this.props.onSettings} /> :
|
||||||
this.props.state === 'new-topic' ?
|
this.props.state === 'new-topic' ?
|
||||||
<SideNavbar.MenuCancel onCancel={this.props.onCancel} /> :
|
<MenuCancel onCancel={this.props.onCancel} /> :
|
||||||
null}
|
null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -526,14 +605,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
SideNavbar.MenuCancel = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<a href="#" onClick={this.props.onCancel}><i className="material-icons">close</i></a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
SideNavbar.MenuContacts = React.createClass({
|
SideNavbar.MenuContacts = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
@@ -614,7 +685,10 @@
|
|||||||
/> :
|
/> :
|
||||||
|
|
||||||
this.props.state === 'edit-account' ?
|
this.props.state === 'edit-account' ?
|
||||||
<EditAccountView onCancel={this.props.onCancel} /> :
|
<EditAccountView
|
||||||
|
login={this.props.login}
|
||||||
|
onSubmit={this.props.onUpdateAccount}
|
||||||
|
onCancel={this.props.onCancel} /> :
|
||||||
|
|
||||||
this.props.state === 'contacts' ?
|
this.props.state === 'contacts' ?
|
||||||
<ContactsView
|
<ContactsView
|
||||||
@@ -653,10 +727,89 @@
|
|||||||
|
|
||||||
/* EditAccount parameters */
|
/* EditAccount parameters */
|
||||||
var EditAccountView = React.createClass({
|
var EditAccountView = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
updated: false,
|
||||||
|
address: null,
|
||||||
|
fullName: undefined,
|
||||||
|
avatar: null,
|
||||||
|
login: undefined,
|
||||||
|
password: undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillMount: function() {
|
||||||
|
var me = Tinode.getMeTopic();
|
||||||
|
me.onMetaDesc = this.onMetaDesc;
|
||||||
|
var defacs = me.getDefaultAccess();
|
||||||
|
this.setState({
|
||||||
|
fullName: me.public ? me.public.fn : undefined,
|
||||||
|
avatar: makeImageUrl(me.public ? me.public.photo : null),
|
||||||
|
address: Tinode.getCurrentUserID(),
|
||||||
|
login: this.props.login,
|
||||||
|
auth: defacs.auth,
|
||||||
|
anon: defacs.anon
|
||||||
|
});
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
var me = Tinode.getMeTopic();
|
||||||
|
me.onMetaDesc = undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
onMetaDesc: function(desc) {
|
||||||
|
// Update form with changed values
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFullNameUpdate: function(fn) {
|
||||||
|
this.setState({updated: true, fullName: fn});
|
||||||
|
},
|
||||||
|
handlePasswordUpdate: function(pwd) {
|
||||||
|
this.setState({updated: true, password: pwd});
|
||||||
|
},
|
||||||
|
handleImageChanged: function(img) {
|
||||||
|
this.setState({updated: true, avatar: img});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmit: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.state.updated) {
|
||||||
|
this.props.onSubmit(this.state.password,
|
||||||
|
vcard(this.state.fullName, this.state.avatar));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<form id="edit-account">
|
<form id="edit-account" className="panel-form" onSubmit={this.handleSubmit}>
|
||||||
<p>Not implemented</p>
|
<div className="panel-form-one">
|
||||||
|
<div className="panel-form-two">
|
||||||
|
<div><label className="small">Your name</label></div>
|
||||||
|
<div><InPlaceEdit
|
||||||
|
placeholder="Full name, e.g. John Doe"
|
||||||
|
text={this.state.fullName}
|
||||||
|
onFinished={this.handleFullNameUpdate} /></div>
|
||||||
|
<div><label className="small">Password</label></div>
|
||||||
|
<div><InPlaceEdit
|
||||||
|
placeholder="Unchanged"
|
||||||
|
type="password"
|
||||||
|
onFinished={this.handlePasswordUpdate} /></div>
|
||||||
|
</div>
|
||||||
|
<AvatarUpload
|
||||||
|
avatar={this.state.avatar}
|
||||||
|
onImageChanged={this.handleImageChanged} />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<div>Login: <tt>{this.state.login}</tt></div>
|
||||||
|
<div>Address: <tt>{this.state.address}</tt></div>
|
||||||
|
<div>Default access mode:</div>
|
||||||
|
<div> Auth: <tt>{this.state.auth}</tt></div>
|
||||||
|
<div> Anon: <tt>{this.state.anon}</tt></div>
|
||||||
|
</div>
|
||||||
|
<div className="dialog-buttons">
|
||||||
|
<button className="blue" type="submit">Update</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -866,20 +1019,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleGroupSubmit: function(name, dataUrl) {
|
handleGroupSubmit: function(name, dataUrl) {
|
||||||
var vcard = null;
|
this.props.onCreateTopic(undefined, vcard(name, dataUrl));
|
||||||
if (name || dataUrl) {
|
|
||||||
vcard = {
|
|
||||||
fn: name,
|
|
||||||
};
|
|
||||||
if (dataUrl) {
|
|
||||||
var dataStart = dataUrl.indexOf(",");
|
|
||||||
vcard.photo = {
|
|
||||||
data: dataUrl.substring(dataStart+1),
|
|
||||||
type: "jpg"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onCreateTopic(undefined, vcard);
|
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
@@ -1026,9 +1166,118 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* END Create new topic and invite users or send an invite */
|
/* END Create new topic and invite users or send an invite */
|
||||||
|
|
||||||
|
/* BEGIN InfoView: panel with topic/user info */
|
||||||
|
var InfoView = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
updated: false,
|
||||||
|
address: null,
|
||||||
|
fullName: undefined,
|
||||||
|
avatar: null,
|
||||||
|
private: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillMount: function() {
|
||||||
|
var topic = Tinode.getTopic(this.props.topic);
|
||||||
|
if (!topic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
topic.onMetaDesc = this.onMetaDesc;
|
||||||
|
var defacs = topic.getDefaultAccess() || {};
|
||||||
|
this.setState({
|
||||||
|
fullName: topic.public ? topic.public.fn : undefined,
|
||||||
|
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||||
|
private: topic.private,
|
||||||
|
address: topic.name,
|
||||||
|
auth: defacs.auth,
|
||||||
|
anon: defacs.anon
|
||||||
|
});
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
var topic = Tinode.getTopic(this.props.topic);
|
||||||
|
if (!topic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topic.onMetaDesc = undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
onMetaDesc: function(desc) {
|
||||||
|
// Update form with changed values
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFullNameUpdate: function(fn) {
|
||||||
|
this.setState({updated: true, fullName: fn});
|
||||||
|
},
|
||||||
|
handlePrivateUpdate: function(priv) {
|
||||||
|
this.setState({updated: true, private: priv});
|
||||||
|
},
|
||||||
|
handleImageChanged: function(img) {
|
||||||
|
this.setState({updated: true, avatar: img});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmit: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.state.updated) {
|
||||||
|
this.props.onSubmit(this.props.topic,
|
||||||
|
vcard(this.state.fullName,this.state.avatar),
|
||||||
|
this.state.private);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div id="info-view">
|
||||||
|
<div className="caption-panel" id="info-caption-panel">
|
||||||
|
<div className="panel-title" id="info-title">Info</div>
|
||||||
|
<div>
|
||||||
|
<MenuCancel onCancel={this.props.onCancel} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="edit-account" className="panel-form" onSubmit={this.handleSubmit}>
|
||||||
|
<div className="panel-form-one">
|
||||||
|
<div className="panel-form-two">
|
||||||
|
<div><label className="small">Name</label></div>
|
||||||
|
<div><InPlaceEdit
|
||||||
|
placeholder="Group name"
|
||||||
|
text={this.state.fullName}
|
||||||
|
onFinished={this.handleFullNameUpdate} /></div>
|
||||||
|
<div><label className="small">Private comment</label></div>
|
||||||
|
<div><InPlaceEdit
|
||||||
|
placeholder="Visible to you only"
|
||||||
|
text={this.state.private}
|
||||||
|
onFinished={this.handlePrivateUpdate} /></div>
|
||||||
|
</div>
|
||||||
|
<AvatarUpload
|
||||||
|
avatar={this.state.avatar}
|
||||||
|
onImageChanged={this.handleImageChanged} />
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<div>Address: <tt>{this.state.address}</tt></div>
|
||||||
|
{this.state.auth || this.state.anon ?
|
||||||
|
<div>
|
||||||
|
<div>Default access mode:</div>
|
||||||
|
<div> Auth: <tt>{this.state.auth}</tt></div>
|
||||||
|
<div> Anon: <tt>{this.state.anon}</tt></div>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="dialog-buttons">
|
||||||
|
<button className="blue" type="submit">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* END InfoView */
|
||||||
|
|
||||||
/* BEGIN Conversation panel */
|
/* BEGIN Conversation panel */
|
||||||
var Received = {
|
var Received = {
|
||||||
UNDEF: -2,
|
UNDEF: -2,
|
||||||
@@ -1132,6 +1381,7 @@
|
|||||||
// Unbind the previous topic from this component.
|
// Unbind the previous topic from this component.
|
||||||
if (this.state.topic) {
|
if (this.state.topic) {
|
||||||
var oldTopic = Tinode.getTopic(this.state.topic);
|
var oldTopic = Tinode.getTopic(this.state.topic);
|
||||||
|
oldTopic.leave(false);
|
||||||
oldTopic.onData = undefined;
|
oldTopic.onData = undefined;
|
||||||
oldTopic.onInfo = undefined;
|
oldTopic.onInfo = undefined;
|
||||||
oldTopic.onMetaDesc = undefined;
|
oldTopic.onMetaDesc = undefined;
|
||||||
@@ -1269,9 +1519,9 @@
|
|||||||
var avatar = this.state.avatar || true;
|
var avatar = this.state.avatar || true;
|
||||||
component = (
|
component = (
|
||||||
<div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}>
|
<div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}>
|
||||||
<div id="topic-caption-panel">
|
<div id="topic-caption-panel" className="caption-panel">
|
||||||
{this.props.displayMobile ?
|
{this.props.displayMobile ?
|
||||||
<a href="#" id="hide-message-view" onClick={this.props.onHideMessageView}>
|
<a href="#" id="hide-message-view" onClick={this.props.onHideMessagesView}>
|
||||||
<i className="material-icons big">arrow_back</i>
|
<i className="material-icons big">arrow_back</i>
|
||||||
</a>
|
</a>
|
||||||
:
|
:
|
||||||
@@ -1287,7 +1537,7 @@
|
|||||||
<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 id="topic-users">users online and menu buttons</div>
|
<div id="topic-users"></div>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" onClick={this.handleContextClick}>
|
<a href="#" onClick={this.handleContextClick}>
|
||||||
<i className="material-icons big">more_vert</i>
|
<i className="material-icons big">more_vert</i>
|
||||||
@@ -1438,6 +1688,7 @@
|
|||||||
loginDisabled: false,
|
loginDisabled: false,
|
||||||
subscriptions: {},
|
subscriptions: {},
|
||||||
displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),
|
displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),
|
||||||
|
showInfoPanel: false,
|
||||||
mobilePanel: 'sidepanel',
|
mobilePanel: 'sidepanel',
|
||||||
contextMenuVisible: false,
|
contextMenuVisible: false,
|
||||||
contextMenuBounds: null,
|
contextMenuBounds: null,
|
||||||
@@ -1534,6 +1785,7 @@
|
|||||||
topicSelected: '',
|
topicSelected: '',
|
||||||
loginDisabled: false,
|
loginDisabled: false,
|
||||||
subscriptions: {},
|
subscriptions: {},
|
||||||
|
showInfoPanel: false,
|
||||||
mobilePanel: 'sidepanel',
|
mobilePanel: 'sidepanel',
|
||||||
contextMenuVisible: false,
|
contextMenuVisible: false,
|
||||||
contextMenuBounds: null,
|
contextMenuBounds: null,
|
||||||
@@ -1561,14 +1813,22 @@
|
|||||||
handleTopicSelected: function(topicName) {
|
handleTopicSelected: function(topicName) {
|
||||||
if (topicName) {
|
if (topicName) {
|
||||||
// Contact selected
|
// Contact selected
|
||||||
this.setState({topicSelected: topicName, mobilePanel: 'topic-view'});
|
this.setState({
|
||||||
|
topicSelected: topicName,
|
||||||
|
mobilePanel: 'topic-view',
|
||||||
|
showInfoPanel: false
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Currently selected contact deleted
|
// Currently selected contact deleted
|
||||||
this.setState({topicSelected: null, mobilePanel: 'sidepanel'});
|
this.setState({
|
||||||
|
topicSelected: null,
|
||||||
|
mobilePanel: 'sidepanel',
|
||||||
|
showInfoPanel: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// In mobile view user requested to show sidepanel
|
// In mobile view user requested to show sidepanel
|
||||||
handleHideMessageView: function() {
|
handleHideMessagesView: function() {
|
||||||
this.setState({mobilePanel: 'sidepanel'});
|
this.setState({mobilePanel: 'sidepanel'});
|
||||||
},
|
},
|
||||||
// User clicked on a message send button.
|
// User clicked on a message send button.
|
||||||
@@ -1585,8 +1845,8 @@
|
|||||||
var instance = this;
|
var instance = this;
|
||||||
Tinode.connect(this.state.serverAddress)
|
Tinode.connect(this.state.serverAddress)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return Tinode.createUserBasic(login_, password_,
|
return Tinode.createAccountBasic(login_, password_,
|
||||||
{login: "basic", public: public_, private: private_});
|
{login: true, public: public_, private: private_});
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
instance.handleLoginSuccessful(instance);
|
instance.handleLoginSuccessful(instance);
|
||||||
@@ -1595,6 +1855,15 @@
|
|||||||
instance.setState({errorText: err.message});
|
instance.setState({errorText: err.message});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleUpdateAccountRequest: function(password, pub, priv) {
|
||||||
|
this.handleSidepanelCancel();
|
||||||
|
var me = Tinode.getMeTopic();
|
||||||
|
me.setMeta({desc: {public: pub, private: priv}});
|
||||||
|
if (password) {
|
||||||
|
Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password);
|
||||||
|
}
|
||||||
|
},
|
||||||
// User chose Settings menu item.
|
// User chose Settings menu item.
|
||||||
handleSettings: function() {
|
handleSettings: function() {
|
||||||
this.setState({sidePanelSelected: this.state.myUserId ? 'edit-account' : 'settings'});
|
this.setState({sidePanelSelected: this.state.myUserId ? 'edit-account' : 'settings'});
|
||||||
@@ -1619,7 +1888,7 @@
|
|||||||
this.setState({sidePanelSelected: 'new-topic'});
|
this.setState({sidePanelSelected: 'new-topic'});
|
||||||
},
|
},
|
||||||
// Create of a new topic. New P2P topic requires peer's name.
|
// Create of a new topic. New P2P topic requires peer's name.
|
||||||
handleNewTopicRequest: function(peerName, vcard) {
|
handleNewTopicRequest: function(peerName, pub) {
|
||||||
var instance = this;
|
var instance = this;
|
||||||
|
|
||||||
// Check if user clicked on an existing connection.
|
// Check if user clicked on an existing connection.
|
||||||
@@ -1635,7 +1904,7 @@
|
|||||||
var params = {get: {desc: {}, data: {}}};
|
var params = {get: {desc: {}, data: {}}};
|
||||||
if (!peerName) {
|
if (!peerName) {
|
||||||
params.get.sub = {}; // Fetch subs for grp topics only. Subscriptions for a p2p topic are useless.
|
params.get.sub = {}; // Fetch subs for grp topics only. Subscriptions for a p2p topic are useless.
|
||||||
params.set = {desc: {public: vcard}};
|
params.set = {desc: {public: pub}};
|
||||||
}
|
}
|
||||||
topic.subscribe(params).then(function() {
|
topic.subscribe(params).then(function() {
|
||||||
instance.setState({
|
instance.setState({
|
||||||
@@ -1649,6 +1918,11 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleTopicUpdateRequest: function(topicName, pub, priv) {
|
||||||
|
var topic = Tinode.getTopic(topicName);
|
||||||
|
topic.setMeta({desc: {public: pub, private: priv}});
|
||||||
|
},
|
||||||
/** Called when the user enters a contact into the contact search field in the NewAccount panel
|
/** Called when the user enters a contact into the contact search field in the NewAccount panel
|
||||||
@param query {Array} is an array of contacts to search for
|
@param query {Array} is an array of contacts to search for
|
||||||
*/
|
*/
|
||||||
@@ -1710,8 +1984,7 @@
|
|||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "Info":
|
case "Info":
|
||||||
// TODO(gene): implement topic info
|
this.handleShowInfoView();
|
||||||
console.log("Topic info not implemented");
|
|
||||||
break;
|
break;
|
||||||
case "Clear messages":
|
case "Clear messages":
|
||||||
topic.delMessagesAll(false).then(function(){
|
topic.delMessagesAll(false).then(function(){
|
||||||
@@ -1738,7 +2011,12 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleShowInfoView: function() {
|
||||||
|
this.setState({showInfoPanel: true});
|
||||||
|
},
|
||||||
|
handleHideInfoView: function() {
|
||||||
|
this.setState({showInfoPanel: false});
|
||||||
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div id="app-container">
|
<div id="app-container">
|
||||||
@@ -1776,6 +2054,7 @@
|
|||||||
onSettings={this.handleSettings}
|
onSettings={this.handleSettings}
|
||||||
onLoginRequest={this.handleLoginRequest}
|
onLoginRequest={this.handleLoginRequest}
|
||||||
onCreateAccount={this.handleNewAccountRequest}
|
onCreateAccount={this.handleNewAccountRequest}
|
||||||
|
onUpdateAccount={this.handleUpdateAccountRequest}
|
||||||
onTopicSelected={this.handleTopicSelected}
|
onTopicSelected={this.handleTopicSelected}
|
||||||
onSearchContacts={this.handleSearchContacts}
|
onSearchContacts={this.handleSearchContacts}
|
||||||
onCreateTopic={this.handleNewTopicRequest}
|
onCreateTopic={this.handleNewTopicRequest}
|
||||||
@@ -1785,7 +2064,7 @@
|
|||||||
showContextMenu={this.handleShowContextMenu} />
|
showContextMenu={this.handleShowContextMenu} />
|
||||||
|
|
||||||
<MessagesView
|
<MessagesView
|
||||||
onHideMessageView={this.handleHideMessageView}
|
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')}
|
||||||
topic={this.state.topicSelected}
|
topic={this.state.topicSelected}
|
||||||
@@ -1796,6 +2075,15 @@
|
|||||||
showContextMenu={this.handleShowContextMenu}
|
showContextMenu={this.handleShowContextMenu}
|
||||||
sendMessage={this.handleSendMessage} />
|
sendMessage={this.handleSendMessage} />
|
||||||
|
|
||||||
|
{this.state.showInfoPanel ?
|
||||||
|
<InfoView
|
||||||
|
topic={this.state.topicSelected}
|
||||||
|
onSubmit={this.handleTopicUpdateRequest}
|
||||||
|
onCancel={this.handleHideInfoView}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user