subscriber management is somewhat functional
This commit is contained in:
+48
-11
@@ -183,7 +183,7 @@ textarea {
|
|||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
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.1s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
@@ -581,6 +581,22 @@ ul.menu > li.separator:hover {
|
|||||||
.contact-box .offline {
|
.contact-box .offline {
|
||||||
background-color: #C0C0C0;
|
background-color: #C0C0C0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-box .checkmark.material-icons {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #0097A7;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.05rem;
|
||||||
|
bottom: 0.05rem;
|
||||||
|
background-color: white;
|
||||||
|
overflow: visible;
|
||||||
|
border: 0.0px solid transparent;
|
||||||
|
}
|
||||||
/* END of avatar box */
|
/* END of avatar box */
|
||||||
|
|
||||||
#add-topic {
|
#add-topic {
|
||||||
@@ -756,10 +772,11 @@ ul.menu > li.separator:hover {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0.5rem;
|
padding: 0.4rem;
|
||||||
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;
|
||||||
|
height: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A static panel shown when no topic is selected */
|
/* A static panel shown when no topic is selected */
|
||||||
@@ -962,6 +979,24 @@ ul.menu > li.separator:hover {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#group-manager {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#group-manager-buttons {
|
||||||
|
display: flex;
|
||||||
|
background-color: #eceff1;
|
||||||
|
box-shadow: 0 -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
|
||||||
|
z-index: 2;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 0.4rem;
|
||||||
|
height: 4rem;
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
/* END of InfoView */
|
/* END of InfoView */
|
||||||
|
|
||||||
/* Modal dialog */
|
/* Modal dialog */
|
||||||
@@ -1043,7 +1078,7 @@ ul.tabbar > li > a {
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: rgba(33,150,243,0.6);
|
color: rgba(33,150,243,0.6);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: inset 0 -2px 2.5px -1px rgba(0,0,0,0.5);
|
box-shadow: inset 0 -2px 2.5px -1px rgba(0,0,0,0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.tabbar li.active > a {
|
ul.tabbar li.active > a {
|
||||||
@@ -1083,10 +1118,10 @@ ul.tabbar li.active > a:after {
|
|||||||
background-color: #0097A7;
|
background-color: #0097A7;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 0.25rem;
|
width: 1.35rem;
|
||||||
min-width: 1.4rem;
|
height: 1.35rem;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
line-height: 1;
|
line-height: 1.35rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
@@ -1245,10 +1280,12 @@ ul.tabbar li.active > a:after {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
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.1s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0.25rem 0;
|
max-height: 8rem;
|
||||||
padding: 0 0.125rem 0.35rem 0.125rem;
|
overflow-y: scroll;
|
||||||
|
margin: 0.25rem 0rem 0.25rem 0rem;
|
||||||
|
padding: 0 0 0.5rem 0.5rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: #666;
|
color: #666;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1256,8 +1293,7 @@ ul.tabbar li.active > a:after {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chip-input.focused,
|
.chip-input.focused {
|
||||||
.chip-input:focus {
|
|
||||||
border-bottom: 1px solid #2196f3;
|
border-bottom: 1px solid #2196f3;
|
||||||
box-shadow: 0 1px 0 rgba(33, 150, 243, 0.25);
|
box-shadow: 0 1px 0 rgba(33, 150, 243, 0.25);
|
||||||
}
|
}
|
||||||
@@ -1284,6 +1320,7 @@ ul.tabbar li.active > a:after {
|
|||||||
margin: 0.125rem;
|
margin: 0.125rem;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chip > a {
|
.chip > a {
|
||||||
|
|||||||
+257
-81
@@ -8,7 +8,7 @@
|
|||||||
<!-- Browser styling for Chrome, FF, Opera -->
|
<!-- Browser styling for Chrome, FF, Opera -->
|
||||||
<meta name="theme-color" content="#3949AB">
|
<meta name="theme-color" content="#3949AB">
|
||||||
<!-- Google's Roboto font -->
|
<!-- Google's Roboto font -->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,400italic,700&subset=latin,cyrillic"
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,400italic,500,700&subset=latin,cyrillic"
|
||||||
rel="stylesheet" type="text/css" />
|
rel="stylesheet" type="text/css" />
|
||||||
<!-- Google's material design icons -->
|
<!-- Google's material design icons -->
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
@@ -263,7 +263,6 @@
|
|||||||
this.setState({active: false});
|
this.setState({active: false});
|
||||||
var text = this.state.text.trim();
|
var text = this.state.text.trim();
|
||||||
if ((text || this.props.text) && (text !== this.props.text)) {
|
if ((text || this.props.text) && (text !== this.props.text)) {
|
||||||
console.log("'" + text + "' != '" + this.props.text + "'");
|
|
||||||
this.props.onFinished(text);
|
this.props.onFinished(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -347,20 +346,31 @@
|
|||||||
var ChipInput = React.createClass({
|
var ChipInput = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
chips: this.props.chips,
|
|
||||||
placeholder: this.props.chips ? "" : this.props.prompt,
|
placeholder: this.props.chips ? "" : this.props.prompt,
|
||||||
input: '',
|
input: '',
|
||||||
focused: false
|
focused: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
componentWillReceiveProps: function(newProps) {
|
||||||
handleTextInput: function(e) {
|
this.setState({
|
||||||
console.log(e.target.value);
|
input: ''
|
||||||
this.setState({input: e.target.value});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleChipCancel: function(item) {
|
handleTextInput: function(e) {
|
||||||
console.log("cancel " + item);
|
this.setState({input: e.target.value});
|
||||||
|
if (this.props.filter) {
|
||||||
|
this.props.filter(e.target.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeChipAt: function(idx) {
|
||||||
|
var removed = this.props.chips[idx];
|
||||||
|
this.props.onChipRemoved(removed.user, idx);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChipCancel: function(item, idx) {
|
||||||
|
this.removeChipAt(idx);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFocusGained: function() {
|
handleFocusGained: function() {
|
||||||
@@ -373,10 +383,8 @@
|
|||||||
|
|
||||||
handleKeyDown: function(e) {
|
handleKeyDown: function(e) {
|
||||||
if (e.key === 'Backspace') {
|
if (e.key === 'Backspace') {
|
||||||
if (this.state.input.length == 0 && this.state.chips.length > 0) {
|
if (this.state.input.length == 0 && this.props.chips.length > 0) {
|
||||||
var updated = this.state.chips.slice();
|
this.removeChipAt(this.props.chips.length-1);
|
||||||
updated.splice(-1);
|
|
||||||
this.setState({chips: updated});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -384,28 +392,31 @@
|
|||||||
render: function() {
|
render: function() {
|
||||||
var chips = [];
|
var chips = [];
|
||||||
var instance = this;
|
var instance = this;
|
||||||
this.state.chips.map(function(item) {
|
var count = 0;
|
||||||
|
this.props.chips.map(function(item) {
|
||||||
chips.push(
|
chips.push(
|
||||||
<ChipInput.Chip
|
<ChipInput.Chip
|
||||||
onCancel={instance.handleChipCancel}
|
onCancel={instance.handleChipCancel}
|
||||||
avatar={makeImageUrl(item.public ? item.public.photo : null)}
|
avatar={makeImageUrl(item.public ? item.public.photo : null)}
|
||||||
title={item.public ? item.public.fn : undefined}
|
title={item.public ? item.public.fn : undefined}
|
||||||
topic={item.user}
|
topic={item.user}
|
||||||
key={item.user} />
|
index={count}
|
||||||
|
key={count} />
|
||||||
);
|
);
|
||||||
|
count++;
|
||||||
});
|
});
|
||||||
var className = "chip-input" + (this.state.focused ? " focused" : "");
|
var className = "chip-input" + (this.state.focused ? " focused" : "");
|
||||||
return (
|
return (
|
||||||
<div className={className} tabIndex="0">
|
<div className={className}>
|
||||||
{chips}
|
{chips}
|
||||||
<input type="text"
|
<input type="text"
|
||||||
placeholder={this.state.placeholder}
|
placeholder={this.state.placeholder}
|
||||||
onChange={this.handleTextInput}
|
onChange={this.handleTextInput}
|
||||||
onFocus={this.handleFocusGained}
|
onFocus={this.handleFocusGained}
|
||||||
onBlur={this.handleFocusLost}
|
onBlur={this.handleFocusLost}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
value={this.state.input}
|
value={this.state.input}
|
||||||
autoFocus />
|
autoFocus />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -413,7 +424,7 @@
|
|||||||
|
|
||||||
ChipInput.Chip = React.createClass({
|
ChipInput.Chip = React.createClass({
|
||||||
handleCancel: function() {
|
handleCancel: function() {
|
||||||
this.props.onCancel(this.props.title);
|
this.props.onCancel(this.props.topic, this.props.index);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
@@ -800,7 +811,9 @@
|
|||||||
|
|
||||||
this.props.state === 'new-topic' ?
|
this.props.state === 'new-topic' ?
|
||||||
<NewTopicView
|
<NewTopicView
|
||||||
data={this.props.subscriptions}
|
contactsSearchQuery={this.props.contactsSearchQuery}
|
||||||
|
foundContacts={this.props.foundContacts}
|
||||||
|
onInitFind={this.props.onInitFind}
|
||||||
onSearchContacts={this.props.onSearchContacts}
|
onSearchContacts={this.props.onSearchContacts}
|
||||||
onCreateTopic={this.props.onCreateTopic} /> :
|
onCreateTopic={this.props.onCreateTopic} /> :
|
||||||
|
|
||||||
@@ -922,7 +935,7 @@
|
|||||||
handleClick: function(e) {
|
handleClick: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.onSelected(this.props.topic);
|
this.props.onSelected(this.props.topic, this.props.index);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContextClick: function(e) {
|
handleContextClick: function(e) {
|
||||||
@@ -941,11 +954,16 @@
|
|||||||
var online = this.props.now ? "online" : "offline";
|
var online = this.props.now ? "online" : "offline";
|
||||||
var avatar = this.props.avatar ? this.props.avatar : true;
|
var avatar = this.props.avatar ? this.props.avatar : true;
|
||||||
return (
|
return (
|
||||||
<li className={this.props.selected ? "selected" : null} onClick={this.handleClick}>
|
<li className={!this.props.showCheckmark && this.props.selected ? "selected" : null}
|
||||||
|
onClick={this.handleClick}>
|
||||||
<div className="avatar-box">
|
<div className="avatar-box">
|
||||||
<LetterTile
|
<LetterTile
|
||||||
avatar={avatar} title={this.props.title} topic={this.props.topic} />
|
avatar={avatar} title={this.props.title} topic={this.props.topic} />
|
||||||
{this.props.showOnline ? <span className={online} /> : null}
|
|
||||||
|
{this.props.showOnline ? <span className={online} /> :
|
||||||
|
(this.props.showCheckmark && this.props.selected ?
|
||||||
|
<i className="checkmark material-icons">check_circle</i>
|
||||||
|
: null)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-box">
|
<div className="text-box">
|
||||||
<div><span className="contact-title">{title}</span>
|
<div><span className="contact-title">{title}</span>
|
||||||
@@ -1075,16 +1093,18 @@
|
|||||||
/* END Contact list */
|
/* END Contact list */
|
||||||
|
|
||||||
/* BEGIN ContactList: component for showing a list of contacts,
|
/* BEGIN ContactList: component for showing a list of contacts,
|
||||||
* such as a list of group members group chat */
|
* such as a list of group members in a group chat */
|
||||||
var ContactList = React.createClass({
|
var ContactList = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var contactNodes = [];
|
var contactNodes = [];
|
||||||
var instance = this;
|
var instance = this;
|
||||||
|
var showCheckmark = Array.isArray(this.props.topicSelected);
|
||||||
|
|
||||||
if (this.props.contacts && this.props.contacts.length > 0) {
|
if (this.props.contacts && this.props.contacts.length > 0) {
|
||||||
this.props.contacts.map(function(c) {
|
this.props.contacts.map(function(c) {
|
||||||
var key = c.topic ? c.topic : c.user;
|
var key = c.topic ? c.topic : c.user;
|
||||||
if (!key) {
|
if (!key) {
|
||||||
console.log("empty key!");
|
console.error("empty key!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,17 +1123,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var selected = showCheckmark ?
|
||||||
|
(this.props.topicSelected.indexOf(key) > -1) :
|
||||||
|
(this.props.topicSelected === key);
|
||||||
|
|
||||||
contactNodes.push(
|
contactNodes.push(
|
||||||
<Contact
|
<Contact
|
||||||
title={c.public ? c.public.fn : null}
|
title={c.public ? c.public.fn : null}
|
||||||
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
||||||
comment={c.private}
|
comment={c.private}
|
||||||
unread={c.unread} now={c.online}
|
unread={c.unread} now={c.online}
|
||||||
selected={this.props.topicSelected === key}
|
showCheckmark={showCheckmark}
|
||||||
|
selected={selected}
|
||||||
showOnline={this.props.showOnline}
|
showOnline={this.props.showOnline}
|
||||||
onSelected={this.props.onTopicSelected}
|
onSelected={this.props.onTopicSelected}
|
||||||
showContextMenu={this.props.showContextMenu}
|
showContextMenu={this.props.showContextMenu}
|
||||||
topic={key}
|
topic={key}
|
||||||
|
index={contactNodes.length}
|
||||||
key={key} />
|
key={key} />
|
||||||
);
|
);
|
||||||
}, this);
|
}, this);
|
||||||
@@ -1139,24 +1165,118 @@
|
|||||||
var GroupManager = React.createClass({
|
var GroupManager = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
members: this.props.members || [],
|
members: this.props.members,
|
||||||
contacts: this.props.contacts || [],
|
contacts: this.props.contacts,
|
||||||
|
index: this.indexMembers(this.props.members),
|
||||||
|
selectedContacts: this.selectedContacts(this.props.members)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactSelected: function(contact) {
|
indexMembers: function(members) {
|
||||||
|
var index = {};
|
||||||
|
members.map(function(m) {
|
||||||
|
index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed
|
||||||
|
});
|
||||||
|
return index;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedContacts: function(members) {
|
||||||
|
var sel = [];
|
||||||
|
members.map(function(m) {
|
||||||
|
sel.push(m.user);
|
||||||
|
});
|
||||||
|
return sel;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleContactSelected: function(userId, index) {
|
||||||
|
var status = this.state.index[userId];
|
||||||
|
if (status) {
|
||||||
|
if (status.present) {
|
||||||
|
// Prevent duplicate members
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status.delta += 1;
|
||||||
|
status.present = true;
|
||||||
|
} else {
|
||||||
|
status = {delta: 1, present: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = this.state.members.slice();
|
||||||
|
m.push(this.props.contacts[index]);
|
||||||
|
|
||||||
|
var sel = this.selectedContacts(m);
|
||||||
|
|
||||||
|
var i = this.state.index;
|
||||||
|
i[userId] = status;
|
||||||
|
|
||||||
|
this.setState({members: m, index: i, selectedContacts: sel});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleMemberRemoved: function(userId, index) {
|
||||||
|
var status = this.state.index[userId];
|
||||||
|
if (!status || !status.present) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status.present = false;
|
||||||
|
status.delta -= 1;
|
||||||
|
|
||||||
|
var m = this.state.members.slice();
|
||||||
|
m.splice(index, 1);
|
||||||
|
|
||||||
|
var sel = this.selectedContacts(m);
|
||||||
|
|
||||||
|
var i = this.state.index;
|
||||||
|
i[userId] = status;
|
||||||
|
|
||||||
|
this.setState({members: m, index: i, selectedContacts: sel});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmit: function() {
|
||||||
|
var instance = this;
|
||||||
|
var members = [];
|
||||||
|
var added = [];
|
||||||
|
var removed = [];
|
||||||
|
|
||||||
|
var keys = Object.keys(this.state.index);
|
||||||
|
keys.map(function(k) {
|
||||||
|
if (instance.state.index[k].present) {
|
||||||
|
members.push(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance.state.index[k].delta > 0) {
|
||||||
|
added.push(k);
|
||||||
|
} else if (instance.state.index[k].delta < 0) {
|
||||||
|
removed.push(k);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.props.onSubmit(members, added, removed);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancel: function() {
|
||||||
|
this.props.onCancel();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="panel-form-column">
|
<div id="group-manager">
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<label className="small">Group members</label>
|
||||||
|
</div>
|
||||||
<ChipInput
|
<ChipInput
|
||||||
chips={this.state.members}
|
chips={this.state.members}
|
||||||
prompt="add members" />
|
prompt="add members"
|
||||||
|
onChipRemoved={this.handleMemberRemoved} />
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<label className="small">All contacts</label>
|
||||||
|
</div>
|
||||||
<ContactList
|
<ContactList
|
||||||
contacts={this.props.contacts}
|
contacts={this.props.contacts}
|
||||||
onTopicSelected={this.handleContactSelected}/>
|
topicSelected={this.state.selectedContacts}
|
||||||
|
onTopicSelected={this.handleContactSelected} />
|
||||||
|
<div id="group-manager-buttons" className="panel-form-row">
|
||||||
|
<button className="blue" onClick={this.handleSubmit}>OK</button>
|
||||||
|
<button className="white" onClick={this.handleCancel}>Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1168,39 +1288,19 @@
|
|||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
tabSelected: "p2p",
|
tabSelected: "p2p",
|
||||||
searchQuery: '',
|
searchQuery: this.props.contactsSearchQuery,
|
||||||
contactList: [],
|
contactList: this.props.foundContacts,
|
||||||
contactSelected: null
|
contactSelected: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
var fnd = Tinode.getFndTopic();
|
this.props.onInitFind();
|
||||||
fnd.onSubsUpdated = this.fndSubsUpdated;
|
|
||||||
fnd.onMetaDesc = this.fndMetaDesc;
|
|
||||||
if (fnd.isSubscribed()) {
|
|
||||||
this.setState({searchQuery: fnd.private ? fnd.private.toString() : ''});
|
|
||||||
this.fndSubsUpdated();
|
|
||||||
} else {
|
|
||||||
fnd.subscribe({get: {sub: {}, desc: {}}});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillReceiveProps: function(nextProps) {
|
||||||
var fnd = Tinode.getFndTopic();
|
this.setState({
|
||||||
fnd.onSubsUpdated = undefined;
|
searchQuery: nextProps.contactsSearchQuery,
|
||||||
fnd.onMetaDesc = undefined;
|
contactList: nextProps.foundContacts
|
||||||
},
|
|
||||||
fndMetaDesc: function(desc) {
|
|
||||||
if (desc.private) {
|
|
||||||
this.setState({searchQuery: desc.private.toString()});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fndSubsUpdated: function() {
|
|
||||||
var contacts = [];
|
|
||||||
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
|
|
||||||
Tinode.getFndTopic().subscribers(function(s) {
|
|
||||||
contacts.push(s);
|
|
||||||
});
|
});
|
||||||
this.setState({contactList: contacts});
|
|
||||||
},
|
},
|
||||||
handleTabClick: function(e) {
|
handleTabClick: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1370,6 +1470,8 @@
|
|||||||
topic.onSubsUpdated = this.onSubsUpdated;
|
topic.onSubsUpdated = this.onSubsUpdated;
|
||||||
this.resetDesc(topic);
|
this.resetDesc(topic);
|
||||||
this.resetSubs(topic);
|
this.resetSubs(topic);
|
||||||
|
|
||||||
|
this.props.onInitFind();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
@@ -1457,11 +1559,21 @@
|
|||||||
this.props.onChangePermissions(this.props.topic, checked ? "-P" : "+P");
|
this.props.onChangePermissions(this.props.topic, checked ? "-P" : "+P");
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAddMembers: function() {
|
handleShowAddMembers: function() {
|
||||||
// this.props.onAddMember(this.props.topic);
|
// this.props.onAddMember(this.props.topic);
|
||||||
this.setState({showMemberPanel: true});
|
this.setState({showMemberPanel: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleHideAddMembers: function() {
|
||||||
|
// this.props.onAddMember(this.props.topic);
|
||||||
|
this.setState({showMemberPanel: false});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleMemberUpdateRequest: function(members, added, removed) {
|
||||||
|
this.props.onMemberUpdateRequest(this.props.topic, added, removed);
|
||||||
|
this.setState({showMemberPanel: false});
|
||||||
|
},
|
||||||
|
|
||||||
handleLeave: function() {
|
handleLeave: function() {
|
||||||
this.props.onLeaveTopic(this.props.topic);
|
this.props.onLeaveTopic(this.props.topic);
|
||||||
},
|
},
|
||||||
@@ -1485,7 +1597,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{this.state.showMemberPanel ?
|
{this.state.showMemberPanel ?
|
||||||
<GroupManager
|
<GroupManager
|
||||||
members={this.state.contactList} />
|
members={this.state.contactList}
|
||||||
|
contacts={this.props.foundContacts}
|
||||||
|
onCancel={this.handleHideAddMembers}
|
||||||
|
onSubmit={this.handleMemberUpdateRequest} />
|
||||||
:
|
:
|
||||||
<div id="info-view-content" className="scrollable-panel">
|
<div id="info-view-content" className="scrollable-panel">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
@@ -1542,7 +1657,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
{this.state.sharer ?
|
{this.state.sharer ?
|
||||||
<a href="#" className="flat-button" onClick={this.handleAddMembers}>
|
<a href="#" className="flat-button" onClick={this.handleShowAddMembers}>
|
||||||
<i className="material-icons">person_add</i> Add members
|
<i className="material-icons">person_add</i> Add members
|
||||||
</a>
|
</a>
|
||||||
: null}
|
: null}
|
||||||
@@ -1992,7 +2107,9 @@
|
|||||||
contextMenuClickAt: null,
|
contextMenuClickAt: null,
|
||||||
contextMenuTopic: '',
|
contextMenuTopic: '',
|
||||||
contextMenuItems: [],
|
contextMenuItems: [],
|
||||||
serverVersion: 'no connection'
|
serverVersion: 'no connection',
|
||||||
|
contactsSearchQuery: '',
|
||||||
|
foundContacts: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
@@ -2106,6 +2223,44 @@
|
|||||||
Tinode.getTopic(data.topic).noteRecv(data.seq);
|
Tinode.getTopic(data.topic).noteRecv(data.seq);
|
||||||
}, RECEIVED_DELAY);
|
}, RECEIVED_DELAY);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Fnd topic: find contacts by tokens */
|
||||||
|
tnInitFind: function() {
|
||||||
|
var fnd = Tinode.getFndTopic();
|
||||||
|
fnd.onSubsUpdated = this.tnFndSubsUpdated;
|
||||||
|
fnd.onMetaDesc = this.tnFndMetaDesc;
|
||||||
|
if (fnd.isSubscribed()) {
|
||||||
|
this.setState({contactsSearchQuery: fnd.private ? fnd.private.toString() : ''});
|
||||||
|
this.tnFndSubsUpdated();
|
||||||
|
} else {
|
||||||
|
fnd.subscribe({get: {sub: {}, desc: {}}});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tnFndMetaDesc: function(desc) {
|
||||||
|
if (desc.private) {
|
||||||
|
this.setState({contactsSearchQuery: desc.private.toString()});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tnFndSubsUpdated: function() {
|
||||||
|
var contacts = [];
|
||||||
|
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
|
||||||
|
Tinode.getFndTopic().subscribers(function(s) {
|
||||||
|
contacts.push(s);
|
||||||
|
});
|
||||||
|
this.setState({foundContacts: contacts});
|
||||||
|
},
|
||||||
|
/** 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
|
||||||
|
*/
|
||||||
|
handleSearchContacts: function(query) {
|
||||||
|
var find = Tinode.getFndTopic();
|
||||||
|
find.setMeta({desc: {private: query}}).then(function(ctrl) {
|
||||||
|
find.getMeta({sub: {}, desc: {}});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// User clicked on a contact in the side panel or deleted a contact.
|
// User clicked on a contact in the side panel or deleted a contact.
|
||||||
handleTopicSelected: function(topicName) {
|
handleTopicSelected: function(topicName) {
|
||||||
if (topicName) {
|
if (topicName) {
|
||||||
@@ -2250,15 +2405,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 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
|
|
||||||
*/
|
|
||||||
handleSearchContacts: function(query) {
|
|
||||||
var find = Tinode.getFndTopic();
|
|
||||||
find.setMeta({desc: {private: query}}).then(function(ctrl) {
|
|
||||||
find.getMeta({sub: {}, desc: {}});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleDialogCancel: function() {
|
handleDialogCancel: function() {
|
||||||
this.setState({dialogSelected: null});
|
this.setState({dialogSelected: null});
|
||||||
},
|
},
|
||||||
@@ -2344,6 +2490,29 @@
|
|||||||
handleHideInfoView: function() {
|
handleHideInfoView: function() {
|
||||||
this.setState({showInfoPanel: false});
|
this.setState({showInfoPanel: false});
|
||||||
},
|
},
|
||||||
|
handleMemberUpdateRequest: function(topicName, added, removed) {
|
||||||
|
if (!topicName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic = Tinode.getTopic(topicName);
|
||||||
|
if (!topic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added && added.length > 0) {
|
||||||
|
added.map(function(uid) {
|
||||||
|
topic.invite(uid, null, "Join my group!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed && removed.length > 0) {
|
||||||
|
removed.map(function(uid) {
|
||||||
|
topic.delSubscription(uid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div id="app-container">
|
<div id="app-container">
|
||||||
@@ -2383,11 +2552,15 @@
|
|||||||
onCreateAccount={this.handleNewAccountRequest}
|
onCreateAccount={this.handleNewAccountRequest}
|
||||||
onUpdateAccount={this.handleUpdateAccountRequest}
|
onUpdateAccount={this.handleUpdateAccountRequest}
|
||||||
onTopicSelected={this.handleTopicSelected}
|
onTopicSelected={this.handleTopicSelected}
|
||||||
onSearchContacts={this.handleSearchContacts}
|
|
||||||
onCreateTopic={this.handleNewTopicRequest}
|
onCreateTopic={this.handleNewTopicRequest}
|
||||||
onNewTopic={this.handleNewTopic}
|
onNewTopic={this.handleNewTopic}
|
||||||
onCancel={this.handleSidepanelCancel}
|
onCancel={this.handleSidepanelCancel}
|
||||||
|
|
||||||
|
onInitFind={this.tnInitFind}
|
||||||
|
contactsSearchQuery={this.state.contactsSearchQuery}
|
||||||
|
foundContacts={this.state.foundContacts}
|
||||||
|
onSearchContacts={this.handleSearchContacts}
|
||||||
|
|
||||||
showContextMenu={this.handleShowContextMenu} />
|
showContextMenu={this.handleShowContextMenu} />
|
||||||
|
|
||||||
<MessagesView
|
<MessagesView
|
||||||
@@ -2406,11 +2579,14 @@
|
|||||||
{this.state.showInfoPanel ?
|
{this.state.showInfoPanel ?
|
||||||
<InfoView
|
<InfoView
|
||||||
topic={this.state.topicSelected}
|
topic={this.state.topicSelected}
|
||||||
|
foundContacts={this.state.foundContacts}
|
||||||
onSubmit={this.handleTopicUpdateRequest}
|
onSubmit={this.handleTopicUpdateRequest}
|
||||||
onCancel={this.handleHideInfoView}
|
onCancel={this.handleHideInfoView}
|
||||||
onChangePermissions={this.handleChangePermissions}
|
onChangePermissions={this.handleChangePermissions}
|
||||||
|
onMemberUpdateRequest={this.handleMemberUpdateRequest}
|
||||||
onLeaveTopic={this.handleLeaveUnsubRequest}
|
onLeaveTopic={this.handleLeaveUnsubRequest}
|
||||||
onAddMember={this.handleManageGroupMembers}
|
onAddMember={this.handleManageGroupMembers}
|
||||||
|
onInitFind={this.tnInitFind}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|||||||
Reference in New Issue
Block a user