From 981ea33b8e10456bc279f36235c814305d01b243 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 24 Nov 2016 09:35:09 -0500 Subject: 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 --- webapp/components/searchable_channel_list.jsx | 205 ++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 webapp/components/searchable_channel_list.jsx (limited to 'webapp/components/searchable_channel_list.jsx') 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 = ( + + ); + } else { + joinButton = ( + + ); + } + + return ( +
+
+

{channel.display_name}

+

{channel.purpose}

+
+
+ {joinButton} +
+
+ ); + } + + 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 = ; + } else if (channels.length === 0) { + listContent = ( +
+

+ +

+ {this.props.noResultsText} +
+ ); + } 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 = ( + + ); + } + + if (this.state.page > 0) { + previousButton = ( + + ); + } + } + + return ( +
+
+
+ +
+
+
+ {listContent} +
+
+ {previousButton} + {nextButton} +
+
+ ); + } +} + +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 +}; -- cgit v1.2.3-1-g7c22