member management is complete now

This commit is contained in:
or-else
2017-07-09 11:22:27 +03:00
parent b689d08e71
commit 8d5f446ccd
3 changed files with 102 additions and 22 deletions
+2 -3
View File
@@ -10,20 +10,19 @@ Try it (possibly older version) live at http://api.tinode.co/x/example-react-js/
License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
Photos from https://www.pexels.com/ under [CC0 license](https://www.pexels.com/photo-license/).
Sample avatar photos from https://www.pexels.com/ under [CC0 license](https://www.pexels.com/photo-license/).
Background pattern from http://subtlepatterns.com/, commercial and non-commercial use allowed with attribution.
## Missing functionality
* Cannot manage members of group chats.
* Cannot send images or other media.
* No typing indicators.
* No indication of chat status (muted, blocked, etc.).
* Long polling as a wire transport does not work.
* Browser push notifications are missing.
* No encryption.
* Editing chat parameters is incomplete.
* Editing chat permissions is incomplete.
## Responsive design
+20 -1
View File
@@ -257,6 +257,16 @@ ul.menu > li.separator:hover {
.red {
color: #D32F2F; /* red 700 */
}
.hr {
display: block;
width: 100%;
height: 0.6rem;
background-color: #f0f0f0;
box-shadow: inset 0 0.25rem 0.25rem -0.25rem rgba(0,0,0,0.15),
inset 0 -0.25rem 0.25rem -0.25rem rgba(0,0,0,0.15);
}
/* END generic text colors */
/* END of basic elements */
@@ -1274,6 +1284,7 @@ ul.tabbar li.active > a:after {
.chip-input {
display: flex;
flex-wrap: wrap;
flex-grow: 1;
cursor: text;
font-size: 1.1rem;
font-weight: normal;
@@ -1282,7 +1293,8 @@ ul.tabbar li.active > a:after {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
transition: all 0.1s ease-in-out;
width: 100%;
max-height: 8rem;
max-height: 10rem;
min-height: 4rem;
overflow-y: scroll;
margin: 0.25rem 0rem 0.25rem 0rem;
padding: 0 0 0.5rem 0.5rem;
@@ -1355,3 +1367,10 @@ ul.tabbar li.active > a:after {
.chip .avatar-box .material-icons {
font-size: 1.8rem;
}
.chip .spacer {
display: inline-block;
margin: 0.3rem;
width: 0.25rem;
height: 1.175rem;
}
+80 -18
View File
@@ -347,26 +347,55 @@
getInitialState: function() {
return {
placeholder: this.props.chips ? "" : this.props.prompt,
sortedChips: this.sortChips(this.props.chips, this.props.required),
chipIndex: this.indexChips(this.props.chips),
input: '',
focused: false
};
},
componentWillReceiveProps: function(newProps) {
this.setState({
input: ''
sortedChips: this.sortChips(newProps.chips, newProps.required),
chipIndex: this.indexChips(newProps.chips)
});
},
// Map chip index to user name
indexChips: function(chips) {
var index = {};
var count = 0;
chips.map(function(item) {
index[item.user] = count;
count ++;
});
return index;
},
// Have non-removable chips appear before all other chips.
sortChips: function(chips, keep) {
var required = [];
var normal = [];
chips.map(function(item) {
if (item.user === keep) {
required.push(item);
} else {
normal.push(item);
}
});
return required.concat(normal);
},
handleTextInput: function(e) {
this.setState({input: e.target.value});
if (this.props.filter) {
this.props.filter(e.target.value);
if (this.props.filterFunc) {
this.props.filterFunc(e.target.value);
}
},
removeChipAt: function(idx) {
var removed = this.props.chips[idx];
this.props.onChipRemoved(removed.user, idx);
var removed = this.state.sortedChips[idx];
this.props.onChipRemoved(removed.user, this.state.chipIndex[removed.user]);
},
handleChipCancel: function(item, idx) {
@@ -383,25 +412,30 @@
handleKeyDown: function(e) {
if (e.key === 'Backspace') {
if (this.state.input.length == 0 && this.props.chips.length > 0) {
this.removeChipAt(this.props.chips.length-1);
if (this.state.input.length == 0 && this.state.sortedChips.length > 0) {
var at = this.state.sortedChips.length - 1;
if (this.state.sortedChips[at].user !== this.props.required) {
this.removeChipAt(at);
}
}
}
},
render: function() {
var chips = [];
var instance = this;
var count = 0;
this.props.chips.map(function(item) {
this.state.sortedChips.map(function(item) {
chips.push(
<ChipInput.Chip
onCancel={instance.handleChipCancel}
avatar={makeImageUrl(item.public ? item.public.photo : null)}
title={item.public ? item.public.fn : undefined}
topic={item.user}
required={item.user === instance.props.required}
index={count}
key={count} />
key={item.user} />
);
count++;
});
@@ -437,9 +471,9 @@
title={this.props.title} />
</div>
<span>{this.props.title}</span>
{this.props.onCancel ?
{this.props.onCancel && !this.props.required ?
<a href="#" onClick={this.handleCancel} >&times;</a>
: null}
: <span className="spacer" />}
</div>
);
}
@@ -906,6 +940,7 @@
title={this.state.fullName}
onImageChanged={this.handleImageChanged} />
</div>
<div className="hr" />
<div className="panel-form-column">
<div className="panel-form-row">
<label>Login:</label>
@@ -1103,22 +1138,18 @@
if (this.props.contacts && this.props.contacts.length > 0) {
this.props.contacts.map(function(c) {
var key = c.topic ? c.topic : c.user;
if (!key) {
console.error("empty key!");
return;
}
// If filter function is provided, filter out the items
// which don't satisfy the condition.
if (instance.props.filterFunc && instance.props.filter) {
var content = [key];
if (c.private) {
content.push(c.private);
content.push(("" + c.private).toLowerCase());
}
if (c.public && c.public.fn) {
content.push(c.public.fn);
content.push(("" + c.public.fn).toLowerCase());
}
if (!instance.props.filter(instance.props.filter, content)) {
if (!instance.props.filterFunc(instance.props.filter, content)) {
return;
}
}
@@ -1168,6 +1199,8 @@
members: this.props.members,
contacts: this.props.contacts,
index: this.indexMembers(this.props.members),
contactFilter: '',
noContactsMessage: 'You have no contacts :-(',
selectedContacts: this.selectedContacts(this.props.members)
};
},
@@ -1231,6 +1264,26 @@
this.setState({members: m, index: i, selectedContacts: sel});
},
handleContactFilter: function(val) {
var msg = !val ?
"You have no contacts :-(" :
"No contacts match '" + val + "'";
this.setState({contactFilter: val, noContactsMessage: msg});
},
doContactFiltering: function(filter, values) {
if (filter) {
for (var i=0; i<values.length; i++) {
if (values[i].indexOf(filter) >= 0) {
return true;
}
}
return false;
}
return true;
},
handleSubmit: function() {
var instance = this;
var members = [];
@@ -1264,7 +1317,9 @@
</div>
<ChipInput
chips={this.state.members}
required={this.props.requiredMember}
prompt="add members"
filterFunc={this.handleContactFilter}
onChipRemoved={this.handleMemberRemoved} />
<div className="panel-form-row">
<label className="small">All contacts</label>
@@ -1272,6 +1327,9 @@
<ContactList
contacts={this.props.contacts}
topicSelected={this.state.selectedContacts}
filter={this.state.contactFilter}
filterFunc={this.doContactFiltering}
emptyListMessage={this.state.noContactsMessage}
onTopicSelected={this.handleContactSelected} />
<div id="group-manager-buttons" className="panel-form-row">
<button className="blue" onClick={this.handleSubmit}>OK</button>
@@ -1598,6 +1656,7 @@
{this.state.showMemberPanel ?
<GroupManager
members={this.state.contactList}
requiredMember={this.props.myUserId}
contacts={this.props.foundContacts}
onCancel={this.handleHideAddMembers}
onSubmit={this.handleMemberUpdateRequest} />
@@ -1624,6 +1683,7 @@
title={this.state.fullName}
onImageChanged={this.handleImageChanged} />
</div>
<div className="hr" />
<div className="panel-form-column">
<div className="panel-form-row">
<label>Address:</label>
@@ -1650,6 +1710,7 @@
null
}
</div>
<div className="hr" />
{this.state.groupTopic ?
<div className="panel-form-column">
<div className="panel-form-row">
@@ -2580,6 +2641,7 @@
<InfoView
topic={this.state.topicSelected}
foundContacts={this.state.foundContacts}
myUserId={this.state.myUserId}
onSubmit={this.handleTopicUpdateRequest}
onCancel={this.handleHideInfoView}
onChangePermissions={this.handleChangePermissions}