From 8d5f446ccd3d337b5bc126d2bb1dffe63c3df17c Mon Sep 17 00:00:00 2001 From: or-else Date: Sun, 9 Jul 2017 11:22:27 +0300 Subject: [PATCH] member management is complete now --- README.md | 5 ++- css/base.css | 21 ++++++++++- index.html | 98 ++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 0bf5baf2..b836af10 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/css/base.css b/css/base.css index af8956e2..a8229b6a 100644 --- a/css/base.css +++ b/css/base.css @@ -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; +} diff --git a/index.html b/index.html index 9f2ee3d0..6258fd7a 100644 --- a/index.html +++ b/index.html @@ -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( + key={item.user} /> ); count++; }); @@ -437,9 +471,9 @@ title={this.props.title} /> {this.props.title} - {this.props.onCancel ? + {this.props.onCancel && !this.props.required ? × - : null} + : } ); } @@ -906,6 +940,7 @@ title={this.state.fullName} onImageChanged={this.handleImageChanged} /> +
@@ -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= 0) { + return true; + } + } + return false; + } + return true; + }, + handleSubmit: function() { var instance = this; var members = []; @@ -1264,7 +1317,9 @@
@@ -1272,6 +1327,9 @@
@@ -1598,6 +1656,7 @@ {this.state.showMemberPanel ? @@ -1624,6 +1683,7 @@ title={this.state.fullName} onImageChanged={this.handleImageChanged} />
+
@@ -1650,6 +1710,7 @@ null }
+
{this.state.groupTopic ?
@@ -2580,6 +2641,7 @@