summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorenahum <nahumhbl@gmail.com>2016-09-15 09:53:21 -0300
committerJoram Wilander <jwawilander@gmail.com>2016-09-15 08:53:21 -0400
commit2659d19d05c033d84a22d916e2b8eb709dcf23dc (patch)
treee91bb2058801f2507fea8c1dc645a94bf858ae58 /webapp
parentb180bb46e3034d0ce75c9961a8ccea3eefbc855c (diff)
downloadchat-2659d19d05c033d84a22d916e2b8eb709dcf23dc.tar.gz
chat-2659d19d05c033d84a22d916e2b8eb709dcf23dc.tar.bz2
chat-2659d19d05c033d84a22d916e2b8eb709dcf23dc.zip
PLT-3455 More channels filter (#4022)
Diffstat (limited to 'webapp')
-rw-r--r--webapp/components/filtered_channel_list.jsx180
-rw-r--r--webapp/components/more_channels.jsx102
-rw-r--r--webapp/i18n/en.json3
-rw-r--r--webapp/sass/components/_modal.scss9
-rw-r--r--webapp/sass/responsive/_mobile.scss3
5 files changed, 229 insertions, 68 deletions
diff --git a/webapp/components/filtered_channel_list.jsx b/webapp/components/filtered_channel_list.jsx
new file mode 100644
index 000000000..259c5cbf2
--- /dev/null
+++ b/webapp/components/filtered_channel_list.jsx
@@ -0,0 +1,180 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import $ from 'jquery';
+import ReactDOM from 'react-dom';
+import * as UserAgent from 'utils/user_agent.jsx';
+
+import {localizeMessage} from 'utils/utils.jsx';
+import {FormattedMessage} from 'react-intl';
+
+import React from 'react';
+import loadingGif from 'images/load.gif';
+
+export default class FilteredChannelList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleFilterChange = this.handleFilterChange.bind(this);
+ this.createChannelRow = this.createChannelRow.bind(this);
+ this.filterChannels = this.filterChannels.bind(this);
+
+ this.state = {
+ filter: '',
+ joiningChannel: '',
+ channels: this.filterChannels(props.channels)
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ // assume the channel list is immutable
+ if (this.props.channels !== nextProps.channels) {
+ this.setState({
+ channels: this.filterChannels(nextProps.channels)
+ });
+ }
+ }
+
+ componentDidMount() {
+ // only focus the search box on desktop so that we don't cause the keyboard to open on mobile
+ if (!UserAgent.isMobileApp()) {
+ ReactDOM.findDOMNode(this.refs.filter).focus();
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevState.filter !== this.state.filter) {
+ $(ReactDOM.findDOMNode(this.refs.channelList)).scrollTop(0);
+ }
+ }
+
+ handleJoin(channel) {
+ this.setState({joiningChannel: channel.id});
+ this.props.handleJoin(
+ channel,
+ () => {
+ this.setState({joiningChannel: ''});
+ });
+ }
+
+ createChannelRow(channel) {
+ let joinButton;
+ if (this.state.joiningChannel === channel.id) {
+ joinButton = (
+ <img
+ className='join-channel-loading-gif'
+ src={loadingGif}
+ />
+ );
+ } else {
+ joinButton = (
+ <button
+ onClick={this.handleJoin.bind(this, channel)}
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='more_channels.join'
+ defaultMessage='Join'
+ />
+ </button>
+ );
+ }
+
+ return (
+ <div
+ className='more-modal__row'
+ key={channel.id}
+ >
+ <div className='more-modal__details'>
+ <p className='more-modal__name'>{channel.display_name}</p>
+ <p className='more-modal__description'>{channel.purpose}</p>
+ </div>
+ <div className='more-modal__actions'>
+ {joinButton}
+ </div>
+ </div>
+ );
+ }
+
+ filterChannels(channels) {
+ if (!this.state || !this.state.filter) {
+ return channels;
+ }
+
+ return channels.filter((chan) => {
+ const filter = this.state.filter.toLowerCase();
+ return !!((chan.name.toLowerCase().indexOf(filter) !== -1 || chan.display_name.toLowerCase().indexOf(filter) !== -1) && chan.delete_at === 0);
+ });
+ }
+
+ handleFilterChange(e) {
+ this.setState({
+ filter: e.target.value
+ });
+ }
+
+ render() {
+ let channels = this.state.channels;
+
+ if (this.state.filter && this.state.filter.length > 0) {
+ channels = this.filterChannels(channels);
+ }
+
+ let count;
+ if (channels.length === this.props.channels.length) {
+ count = (
+ <FormattedMessage
+ id='filtered_channels_list.count'
+ defaultMessage='{count} {count, plural, =0 {0 channels} one {channel} other {channels}}'
+ values={{
+ count: channels.length
+ }}
+ />
+ );
+ } else {
+ count = (
+ <FormattedMessage
+ id='filtered_channels_list.countTotal'
+ defaultMessage='{count} {count, plural, =0 {0 channels} one {channel} other {channels}} of {total} Total'
+ values={{
+ count: channels.length,
+ total: this.props.channels.length
+ }}
+ />
+ );
+ }
+
+ return (
+ <div className='filtered-user-list'>
+ <div className='filter-row'>
+ <div className='col-sm-6'>
+ <input
+ ref='filter'
+ className='form-control filter-textbox'
+ placeholder={localizeMessage('filtered_channels_list.search', 'Search channels')}
+ onInput={this.handleFilterChange}
+ />
+ </div>
+ <div className='col-sm-12'>
+ <span className='channel-count pull-left'>{count}</span>
+ </div>
+ </div>
+ <div
+ ref='channelList'
+ className='more-modal__list'
+ >
+ {channels.map(this.createChannelRow)}
+ </div>
+ </div>
+ );
+ }
+}
+
+FilteredChannelList.defaultProps = {
+ channels: []
+};
+
+FilteredChannelList.propTypes = {
+ channels: React.PropTypes.arrayOf(React.PropTypes.object),
+ handleJoin: React.PropTypes.func.isRequired
+};
diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx
index 724cd2f60..00e7ecf78 100644
--- a/webapp/components/more_channels.jsx
+++ b/webapp/components/more_channels.jsx
@@ -4,6 +4,7 @@
import $ from 'jquery';
import LoadingScreen from './loading_screen.jsx';
import NewChannelFlow from './new_channel_flow.jsx';
+import FilteredChannelList from './filtered_channel_list.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -18,18 +19,8 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import React from 'react';
-import ReactDOM from 'react-dom';
import PureRenderMixin from 'react-addons-pure-render-mixin';
-import loadingGif from 'images/load.gif';
-
-function getStateFromStores() {
- return {
- channels: ChannelStore.getMoreAll(),
- serverError: null
- };
-}
-
export default class MoreChannels extends React.Component {
constructor(props) {
super(props);
@@ -37,100 +28,78 @@ export default class MoreChannels extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.handleJoin = this.handleJoin.bind(this);
this.handleNewChannel = this.handleNewChannel.bind(this);
- this.createChannelRow = this.createChannelRow.bind(this);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
- var initState = getStateFromStores();
+ const initState = this.getStateFromStores();
initState.channelType = '';
- initState.joiningChannel = '';
initState.showNewChannelModal = false;
this.state = initState;
}
+
componentDidMount() {
+ const self = this;
ChannelStore.addMoreChangeListener(this.onListenerChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => {
+
+ $(this.refs.modal).on('shown.bs.modal', () => {
AsyncClient.getMoreChannels(true);
});
- var self = this;
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => {
- var button = e.relatedTarget;
+ $(this.refs.modal).on('show.bs.modal', (e) => {
+ const button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')});
});
}
+
componentWillUnmount() {
ChannelStore.removeMoreChangeListener(this.onListenerChange);
}
+
+ getStateFromStores() {
+ return {
+ channels: ChannelStore.getMoreAll(),
+ serverError: null
+ };
+ }
+
onListenerChange() {
- var newState = getStateFromStores();
+ const newState = this.getStateFromStores();
if (!Utils.areObjectsEqual(newState.channels, this.state.channels)) {
this.setState(newState);
}
}
- handleJoin(channel) {
- this.setState({joiningChannel: channel.id});
+
+ handleJoin(channel, done) {
GlobalActions.emitJoinChannelEvent(
channel,
() => {
- $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
+ $(this.refs.modal).modal('hide');
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
- this.setState({joiningChannel: ''});
+ if (done) {
+ done();
+ }
},
(err) => {
- this.setState({joiningChannel: '', serverError: err.message});
+ this.setState({serverError: err.message});
+ if (done) {
+ done();
+ }
}
);
}
+
handleNewChannel() {
- $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
+ $(this.refs.modal).modal('hide');
this.setState({showNewChannelModal: true});
}
- createChannelRow(channel) {
- let joinButton;
- if (this.state.joiningChannel === channel.id) {
- joinButton = (
- <img
- className='join-channel-loading-gif'
- src={loadingGif}
- />
- );
- } else {
- joinButton = (
- <button
- onClick={this.handleJoin.bind(self, channel)}
- className='btn btn-primary'
- >
- <FormattedMessage
- id='more_channels.join'
- defaultMessage='Join'
- />
- </button>
- );
- }
- return (
- <div
- className='more-modal__row'
- key={channel.id}
- >
- <div className='more-modal__details'>
- <p className='more-modal__name'>{channel.display_name}</p>
- <p className='more-modal__description'>{channel.purpose}</p>
- </div>
- <div className='more-modal__actions'>
- {joinButton}
- </div>
- </div>
- );
- }
render() {
let maxHeight = 1000;
if (Utils.windowHeight() <= 1200) {
maxHeight = Utils.windowHeight() - 300;
}
- var serverError;
+ let serverError;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
@@ -170,7 +139,7 @@ export default class MoreChannels extends React.Component {
}
}
- var moreChannels;
+ let moreChannels;
if (this.state.channels != null) {
var channels = this.state.channels;
@@ -178,9 +147,10 @@ export default class MoreChannels extends React.Component {
moreChannels = <LoadingScreen/>;
} else if (channels.length) {
moreChannels = (
- <div className='more-modal__list'>
- {channels.map(this.createChannelRow)}
- </div>
+ <FilteredChannelList
+ channels={channels}
+ handleJoin={this.handleJoin}
+ />
);
} else {
moreChannels = (
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 4a6293b2d..f9b6149f0 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -1182,6 +1182,9 @@
"file_upload.filesAbove": "Files above {max}MB cannot be uploaded: {filenames}",
"file_upload.limited": "Uploads limited to {count} files maximum. Please use additional posts for more files.",
"file_upload.pasted": "Image Pasted at ",
+ "filtered_channels_list.count": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}}",
+ "filtered_channels_list.countTotal": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}} of {total} Total",
+ "filtered_channels_list.search": "Search channels",
"filtered_user_list.any_team": "All Users",
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
"filtered_user_list.countTotal": "{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} Total",
diff --git a/webapp/sass/components/_modal.scss b/webapp/sass/components/_modal.scss
index 837db2f44..737a8f98f 100644
--- a/webapp/sass/components/_modal.scss
+++ b/webapp/sass/components/_modal.scss
@@ -227,7 +227,14 @@
&.more-channel__modal {
.modal-body {
- padding: 0;
+ overflow-x: hidden;
+ padding: 10px 0 20px;
+ }
+
+ .channel-count {
+ @include opacity(.8);
+ float: right;
+ margin-top: 5px;
}
}
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index 32f3206a3..e00b818e7 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -307,7 +307,8 @@
max-width: calc(100% - 10px);
}
- .modal-direct-channels {
+ .modal-direct-channels,
+ .more-channel__modal {
.member-count {
display: block;
float: none;