diff options
author | Joram Wilander <jwawilander@gmail.com> | 2016-11-24 09:35:09 -0500 |
---|---|---|
committer | Harrison Healey <harrisonmhealey@gmail.com> | 2016-11-24 09:35:09 -0500 |
commit | 981ea33b8e10456bc279f36235c814305d01b243 (patch) | |
tree | 00fb6119d9ef16f60d4c0dbdaad1bd6dfbc347ed /webapp/components/searchable_channel_list.jsx | |
parent | c96ecae6da31aceabf29586cde872876b81d11d9 (diff) | |
download | chat-981ea33b8e10456bc279f36235c814305d01b243.tar.gz chat-981ea33b8e10456bc279f36235c814305d01b243.tar.bz2 chat-981ea33b8e10456bc279f36235c814305d01b243.zip |
PLT-4403 Add server-based channel autocomplete, search and paging (#4585)
* Add more channel paging API
* Add channel paging support to client
* Add DB channel search functions
* Add API for searching more channels
* Add more channel search functionality to client
* Add API for autocompleting channels
* Add channel autocomplete functionality to the client
* Move to be deprecated APIs to their own file
* Final clean-up
* Fixes related to feedback
* Localization changes
* Add unit as suffix to timeout constants
Diffstat (limited to 'webapp/components/searchable_channel_list.jsx')
-rw-r--r-- | webapp/components/searchable_channel_list.jsx | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/webapp/components/searchable_channel_list.jsx b/webapp/components/searchable_channel_list.jsx new file mode 100644 index 000000000..4a7f90455 --- /dev/null +++ b/webapp/components/searchable_channel_list.jsx @@ -0,0 +1,205 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from './loading_screen.jsx'; + +import * as UserAgent from 'utils/user_agent.jsx'; + +import $ from 'jquery'; +import React from 'react'; +import {localizeMessage} from 'utils/utils.jsx'; +import {FormattedMessage} from 'react-intl'; + +import loadingGif from 'images/load.gif'; + +const NEXT_BUTTON_TIMEOUT_MILLISECONDS = 500; + +export default class SearchableChannelList extends React.Component { + constructor(props) { + super(props); + + this.createChannelRow = this.createChannelRow.bind(this); + this.nextPage = this.nextPage.bind(this); + this.previousPage = this.previousPage.bind(this); + this.doSearch = this.doSearch.bind(this); + + this.nextTimeoutId = 0; + + this.state = { + joiningChannel: '', + page: 0, + nextDisabled: false + }; + } + + componentDidMount() { + // only focus the search box on desktop so that we don't cause the keyboard to open on mobile + if (!UserAgent.isMobile()) { + this.refs.filter.focus(); + } + } + + componentDidUpdate(prevProps, prevState) { + if (prevState.page !== this.state.page) { + $(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> + ); + } + + nextPage(e) { + e.preventDefault(); + this.setState({page: this.state.page + 1, nextDisabled: true}); + this.nextTimeoutId = setTimeout(() => this.setState({nextDisabled: false}), NEXT_BUTTON_TIMEOUT_MILLISECONDS); + this.props.nextPage(this.state.page + 1); + } + + previousPage(e) { + e.preventDefault(); + this.setState({page: this.state.page - 1}); + } + + doSearch() { + const term = this.refs.filter.value; + this.props.search(term); + if (term === '') { + this.setState({page: 0}); + } + } + + render() { + const channels = this.props.channels; + let listContent; + let nextButton; + let previousButton; + + if (channels == null) { + listContent = <LoadingScreen/>; + } else if (channels.length === 0) { + listContent = ( + <div className='no-channel-message'> + <p className='primary-message'> + <FormattedMessage + id='more_channels.noMore' + defaultMessage='No more channels to join' + /> + </p> + {this.props.noResultsText} + </div> + ); + } else { + const pageStart = this.state.page * this.props.channelsPerPage; + const pageEnd = pageStart + this.props.channelsPerPage; + const channelsToDisplay = this.props.channels.slice(pageStart, pageEnd); + listContent = channelsToDisplay.map(this.createChannelRow); + + if (channelsToDisplay.length >= this.props.channelsPerPage) { + nextButton = ( + <button + className='btn btn-default filter-control filter-control__next' + onClick={this.nextPage} + disabled={this.state.nextDisabled} + > + {'Next'} + </button> + ); + } + + if (this.state.page > 0) { + previousButton = ( + <button + className='btn btn-default filter-control filter-control__prev' + onClick={this.previousPage} + > + {'Previous'} + </button> + ); + } + } + + 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.doSearch} + /> + </div> + </div> + <div + ref='channelList' + className='more-modal__list' + > + {listContent} + </div> + <div className='filter-controls'> + {previousButton} + {nextButton} + </div> + </div> + ); + } +} + +SearchableChannelList.defaultProps = { + channels: [] +}; + +SearchableChannelList.propTypes = { + channels: React.PropTypes.arrayOf(React.PropTypes.object), + channelsPerPage: React.PropTypes.number, + nextPage: React.PropTypes.func.isRequired, + search: React.PropTypes.func.isRequired, + handleJoin: React.PropTypes.func.isRequired, + noResultsText: React.PropTypes.object +}; |