diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-05-31 16:51:42 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-31 16:51:42 -0400 |
commit | 5aaedb9663b987caf1fb11ea6062bcc44e6bafca (patch) | |
tree | bd77c10168f9fb1b0f998b08a3b2a3761512a451 /webapp/components/quick_switch_modal | |
parent | 8ce72aedc3a5b4f783fb6ebab38aac8bf5f413ae (diff) | |
download | chat-5aaedb9663b987caf1fb11ea6062bcc44e6bafca.tar.gz chat-5aaedb9663b987caf1fb11ea6062bcc44e6bafca.tar.bz2 chat-5aaedb9663b987caf1fb11ea6062bcc44e6bafca.zip |
PLT-5699 Improvements to channel switcher (#6486)
* Refactor channel switcher to not wait on server results
* Change channel switcher to quick switcher and include team switching
* Add sections, update ordering and add discoverability button
* Fix styling error
* Use CMD in text if on mac
* Clean yarn cache on every install
* Various UX updates per feedback
* Add shortcut help text for team switcher
* Couple more updates per feedback
* Some minor fixes for GM and autocomplete race
* Updating UI for channel switcher (#6504)
* Updating channel switcher button (#6506)
* Updating switcher modal on mobile (#6507)
* Removed jQuery usage
* Rename function to toggleQuickSwitchModal
Diffstat (limited to 'webapp/components/quick_switch_modal')
-rw-r--r-- | webapp/components/quick_switch_modal/index.js | 16 | ||||
-rw-r--r-- | webapp/components/quick_switch_modal/quick_switch_modal.jsx | 322 |
2 files changed, 338 insertions, 0 deletions
diff --git a/webapp/components/quick_switch_modal/index.js b/webapp/components/quick_switch_modal/index.js new file mode 100644 index 000000000..7826fd8f5 --- /dev/null +++ b/webapp/components/quick_switch_modal/index.js @@ -0,0 +1,16 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {getMyTeams} from 'mattermost-redux/selectors/entities/teams'; + +import QuickSwitchModal from './quick_switch_modal.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + showTeamSwitcher: getMyTeams(state).length > 1 + }; +} + +export default connect(mapStateToProps)(QuickSwitchModal); diff --git a/webapp/components/quick_switch_modal/quick_switch_modal.jsx b/webapp/components/quick_switch_modal/quick_switch_modal.jsx new file mode 100644 index 000000000..c3095caf9 --- /dev/null +++ b/webapp/components/quick_switch_modal/quick_switch_modal.jsx @@ -0,0 +1,322 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import SuggestionList from 'components/suggestion/suggestion_list.jsx'; +import SuggestionBox from 'components/suggestion/suggestion_box.jsx'; +import SwitchChannelProvider from 'components/suggestion/switch_channel_provider.jsx'; +import SwitchTeamProvider from 'components/suggestion/switch_team_provider.jsx'; + +import {goToChannel, openDirectChannelToUser} from 'actions/channel_actions.jsx'; + +import Constants from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import React from 'react'; +import PropTypes from 'prop-types'; +import {browserHistory} from 'react-router/es6'; +import {Modal} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; + +// Redux actions +import store from 'stores/redux_store.jsx'; +const getState = store.getState; + +import {getChannel} from 'mattermost-redux/selectors/entities/channels'; +import {getUserByUsername} from 'mattermost-redux/selectors/entities/users'; + +const CHANNEL_MODE = 'channel'; +const TEAM_MODE = 'team'; + +export default class QuickSwitchModal extends React.PureComponent { + static propTypes = { + + /** + * The mode to start in when showing the modal, either 'channel' or 'team' + */ + initialMode: PropTypes.string.isRequired, + + /** + * Set to show the modal + */ + show: PropTypes.bool.isRequired, + + /** + * The function called to hide the modal + */ + onHide: PropTypes.func.isRequired, + + /** + * Set to show team switcher + */ + showTeamSwitcher: PropTypes.bool + } + + static defaultProps = { + initialMode: CHANNEL_MODE + } + + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + this.onShow = this.onShow.bind(this); + this.onHide = this.onHide.bind(this); + this.onExited = this.onExited.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.switchToChannel = this.switchToChannel.bind(this); + this.switchMode = this.switchMode.bind(this); + this.focusTextbox = this.focusTextbox.bind(this); + + this.enableChannelProvider = this.enableChannelProvider.bind(this); + this.enableTeamProvider = this.enableTeamProvider.bind(this); + this.channelProviders = [new SwitchChannelProvider()]; + this.teamProviders = [new SwitchTeamProvider()]; + + this.state = { + text: '', + mode: props.initialMode + }; + } + + componentDidUpdate(prevProps) { + if (this.props.show && !prevProps.show) { + this.focusTextbox(); + } + } + + componentWillReceiveProps(nextProps) { + if (!this.props.show && nextProps.show) { + this.setState({mode: nextProps.initialMode, text: ''}); + } + } + + focusTextbox() { + if (this.refs.switchbox == null) { + return; + } + + const textbox = this.refs.switchbox.getTextbox(); + textbox.focus(); + Utils.placeCaretAtEnd(textbox); + } + + onShow() { + this.setState({ + text: '' + }); + } + + onHide() { + this.setState({ + text: '' + }); + this.props.onHide(); + } + + onExited() { + setTimeout(() => { + document.querySelector('#post_textbox').focus(); + }); + } + + onChange(e) { + this.setState({text: e.target.value}); + } + + handleKeyDown(e) { + if (e.keyCode === Constants.KeyCodes.TAB) { + e.preventDefault(); + this.switchMode(); + } + } + + handleSubmit(selected) { + let channel = null; + + if (!selected) { + return; + } + + if (this.state.mode === CHANNEL_MODE) { + const selectedChannel = selected.channel; + if (selectedChannel.type === Constants.DM_CHANNEL) { + const user = getUserByUsername(getState(), selectedChannel.name); + + if (user) { + openDirectChannelToUser( + user.id, + (ch) => { + channel = ch; + this.switchToChannel(channel); + }, + () => { + channel = null; + this.switchToChannel(channel); + } + ); + } + } else { + channel = getChannel(getState(), selectedChannel.id); + this.switchToChannel(channel); + } + } else { + browserHistory.push('/' + selected.name); + this.onHide(); + } + } + + switchToChannel(channel) { + if (channel != null) { + goToChannel(channel); + this.onHide(); + } + } + + enableChannelProvider() { + this.channelProviders[0].disableDispatches = false; + this.teamProviders[0].disableDispatches = true; + } + + enableTeamProvider() { + this.teamProviders[0].disableDispatches = false; + this.channelProviders[0].disableDispatches = true; + } + + switchMode() { + if (this.state.mode === CHANNEL_MODE && this.props.showTeamSwitcher) { + this.enableTeamProvider(); + this.setState({mode: TEAM_MODE}); + } else if (this.state.mode === TEAM_MODE) { + this.enableChannelProvider(); + this.setState({mode: CHANNEL_MODE}); + } + } + + render() { + let providers = this.channelProviders; + let header; + let renderDividers = true; + + let channelShortcut = 'quick_switch_modal.channelsShortcut.windows'; + if (Utils.isMac()) { + channelShortcut = 'quick_switch_modal.channelsShortcut.mac'; + } + + let teamShortcut = 'quick_switch_modal.teamsShortcut.windows'; + if (Utils.isMac()) { + teamShortcut = 'quick_switch_modal.teamsShortcut.mac'; + } + + if (this.props.showTeamSwitcher) { + let channelsActiveClass = ''; + let teamsActiveClass = ''; + if (this.state.mode === TEAM_MODE) { + providers = this.teamProviders; + renderDividers = false; + teamsActiveClass = 'active'; + } else { + channelsActiveClass = 'active'; + } + + header = ( + <div className='nav nav-tabs'> + <li className={channelsActiveClass}> + <a + href='#' + onClick={(e) => { + e.preventDefault(); + this.enableChannelProvider(); + this.setState({mode: 'channel'}); + this.focusTextbox(); + }} + > + <FormattedMessage + id='quick_switch_modal.channels' + defaultMessage='Channels' + /> + <span className='small'> + <FormattedMessage + id={channelShortcut} + defaultMessage='CTRL+K' + /> + </span> + </a> + </li> + <li className={teamsActiveClass}> + <a + href='#' + onClick={(e) => { + e.preventDefault(); + this.enableTeamProvider(); + this.setState({mode: 'team'}); + this.focusTextbox(); + }} + > + <FormattedMessage + id='quick_switch_modal.teams' + defaultMessage='Teams' + /> + <span className='small'> + <FormattedMessage + id={teamShortcut} + defaultMessage='CTRL+ALT+K' + /> + </span> + </a> + </li> + </div> + ); + } + + let help; + if (this.props.showTeamSwitcher) { + help = ( + <FormattedMessage + id='quick_switch_modal.help' + defaultMessage='Use TAB to toggle between teams/channels, ↑↓ to browse, ↵ to confirm, ESC to dismiss' + /> + ); + } else { + help = ( + <FormattedMessage + id='quick_switch_modal.help_no_team' + defaultMessage='Type a channel name. Use ↑↓ to browse, ↵ to confirm, ESC to dismiss' + /> + ); + } + + return ( + <Modal + dialogClassName='channel-switch-modal modal--overflow' + ref='modal' + show={this.props.show} + onHide={this.onHide} + onExited={this.onExited} + > + <Modal.Header closeButton={true}/> + <Modal.Body> + {header} + <div className='modal__hint'> + {help} + </div> + <SuggestionBox + ref='switchbox' + className='form-control focused' + type='input' + onChange={this.onChange} + value={this.state.text} + onKeyDown={this.handleKeyDown} + onItemSelected={this.handleSubmit} + listComponent={SuggestionList} + maxLength='64' + providers={providers} + listStyle='bottom' + completeOnTab={false} + renderDividers={renderDividers} + /> + </Modal.Body> + </Modal> + ); + } +} |