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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8ce72aedc3
Коммит
5aaedb9663
16
webapp/components/quick_switch_modal/index.js
Обычный файл
16
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);
|
||||
322
webapp/components/quick_switch_modal/quick_switch_modal.jsx
Обычный файл
322
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user