Start moving webapp to Redux (#6140)
* Start moving webapp to Redux * Fix localforage import * Updates per feedback * Feedback udpates and a few fixes * Minor updates * Fix statuses, config not loading properly, getMe sanitizing too much * Fix preferences * Fix user autocomplete * Fix sessions and audits * Fix error handling for all redux actions * Use new directory structure for components and containers * Refresh immediately on logout instead of after timeout * Add fetch polyfill
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
cc07c00507
Коммит
6c4c706313
25
webapp/components/more_direct_channels/index.js
Обычный файл
25
webapp/components/more_direct_channels/index.js
Обычный файл
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getProfiles, getProfilesInTeam} from 'mattermost-redux/actions/users';
|
||||
|
||||
import MoreDirectChannels from './more_direct_channels.jsx';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getProfiles,
|
||||
getProfilesInTeam
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MoreDirectChannels);
|
||||
328
webapp/components/more_direct_channels/more_direct_channels.jsx
Обычный файл
328
webapp/components/more_direct_channels/more_direct_channels.jsx
Обычный файл
@@ -0,0 +1,328 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import MultiSelect from 'components/multiselect/multiselect.jsx';
|
||||
import ProfilePicture from 'components/profile_picture.jsx';
|
||||
|
||||
import {searchUsers} from 'actions/user_actions.jsx';
|
||||
import {openDirectChannelToUser, openGroupChannelToUsers} from 'actions/channel_actions.jsx';
|
||||
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import {displayUsernameForUser} from 'utils/utils.jsx';
|
||||
import Client from 'client/web_client.jsx';
|
||||
|
||||
import React from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {browserHistory} from 'react-router/es6';
|
||||
|
||||
import store from 'stores/redux_store.jsx';
|
||||
import {searchProfiles, searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
const USERS_PER_PAGE = 50;
|
||||
const MAX_SELECTABLE_VALUES = Constants.MAX_USERS_IN_GM - 1;
|
||||
|
||||
export default class MoreDirectChannels extends React.Component {
|
||||
static propTypes = {
|
||||
startingUsers: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
onModalDismissed: React.PropTypes.func,
|
||||
actions: React.PropTypes.shape({
|
||||
getProfiles: React.PropTypes.func.isRequired,
|
||||
getProfilesInTeam: React.PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleHide = this.handleHide.bind(this);
|
||||
this.handleExit = this.handleExit.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleDelete = this.handleDelete.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
this.addValue = this.addValue.bind(this);
|
||||
|
||||
this.searchTimeoutId = 0;
|
||||
this.term = '';
|
||||
this.listType = global.window.mm_config.RestrictDirectMessage;
|
||||
|
||||
const values = [];
|
||||
if (props.startingUsers) {
|
||||
for (let i = 0; i < props.startingUsers.length; i++) {
|
||||
const user = Object.assign({}, props.startingUsers[i]);
|
||||
user.value = user.id;
|
||||
user.label = '@' + user.username;
|
||||
values.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
users: null,
|
||||
values,
|
||||
show: true,
|
||||
search: false,
|
||||
loadingChannel: -1
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.addChangeListener(this.onChange);
|
||||
UserStore.addInTeamChangeListener(this.onChange);
|
||||
UserStore.addStatusesChangeListener(this.onChange);
|
||||
|
||||
if (this.listType === 'any') {
|
||||
this.props.actions.getProfiles(0, USERS_PER_PAGE * 2);
|
||||
} else {
|
||||
this.props.actions.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
UserStore.removeChangeListener(this.onChange);
|
||||
UserStore.removeInTeamChangeListener(this.onChange);
|
||||
UserStore.removeStatusesChangeListener(this.onChange);
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
this.setState({show: false});
|
||||
}
|
||||
|
||||
handleExit() {
|
||||
if (this.exitToChannel) {
|
||||
browserHistory.push(this.exitToChannel);
|
||||
}
|
||||
|
||||
if (this.props.onModalDismissed) {
|
||||
this.props.onModalDismissed();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (this.state.loadingChannel !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userIds = this.state.values.map((v) => v.id);
|
||||
if (userIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({loadingChannel: 1});
|
||||
|
||||
const success = (channel) => {
|
||||
// Due to how react-overlays Modal handles focus, we delay pushing
|
||||
// the new channel information until the modal is fully exited.
|
||||
// The channel information will be pushed in `handleExit`
|
||||
this.exitToChannel = TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name;
|
||||
this.setState({loadingChannel: -1});
|
||||
this.handleHide();
|
||||
};
|
||||
|
||||
const error = () => {
|
||||
this.setState({loadingChannel: -1});
|
||||
};
|
||||
|
||||
if (userIds.length === 1) {
|
||||
openDirectChannelToUser(userIds[0], success, error);
|
||||
} else {
|
||||
openGroupChannelToUsers(userIds, success, error);
|
||||
}
|
||||
}
|
||||
|
||||
addValue(value) {
|
||||
const values = Object.assign([], this.state.values);
|
||||
if (values.indexOf(value) === -1) {
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
this.setState({values});
|
||||
}
|
||||
|
||||
onChange() {
|
||||
let users;
|
||||
if (this.term) {
|
||||
if (this.listType === 'any') {
|
||||
users = Object.assign([], searchProfiles(store.getState(), this.term, true));
|
||||
} else {
|
||||
users = Object.assign([], searchProfilesInCurrentTeam(store.getState(), this.term, true));
|
||||
}
|
||||
} else if (this.listType === 'any') {
|
||||
users = Object.assign([], UserStore.getProfileList(true));
|
||||
} else {
|
||||
users = Object.assign([], UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true));
|
||||
}
|
||||
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const user = Object.assign({}, users[i]);
|
||||
user.value = user.id;
|
||||
user.label = '@' + user.username;
|
||||
users[i] = user;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
users
|
||||
});
|
||||
}
|
||||
|
||||
handlePageChange(page, prevPage) {
|
||||
if (page > prevPage) {
|
||||
if (this.listType === 'any') {
|
||||
this.props.actions.getProfiles(page + 1, USERS_PER_PAGE);
|
||||
} else {
|
||||
this.props.actions.getProfilesInTeam(page + 1, USERS_PER_PAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search(term) {
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
this.term = term;
|
||||
|
||||
if (term === '') {
|
||||
this.onChange();
|
||||
return;
|
||||
}
|
||||
|
||||
let teamId;
|
||||
if (this.listType === 'any') {
|
||||
teamId = '';
|
||||
} else {
|
||||
teamId = TeamStore.getCurrentId();
|
||||
}
|
||||
|
||||
this.searchTimeoutId = setTimeout(
|
||||
() => {
|
||||
searchUsers(term, teamId);
|
||||
},
|
||||
Constants.SEARCH_TIMEOUT_MILLISECONDS
|
||||
);
|
||||
}
|
||||
|
||||
handleDelete(values) {
|
||||
this.setState({values});
|
||||
}
|
||||
|
||||
renderOption(option, isSelected, onAdd) {
|
||||
var rowSelected = '';
|
||||
if (isSelected) {
|
||||
rowSelected = 'more-modal__row--selected';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={option.id}
|
||||
ref={isSelected ? 'selected' : option.id}
|
||||
className={'more-modal__row clickable ' + rowSelected}
|
||||
onClick={() => onAdd(option)}
|
||||
>
|
||||
<ProfilePicture
|
||||
src={`${Client.getUsersRoute()}/${option.id}/image?time=${option.last_picture_update}`}
|
||||
width='32'
|
||||
height='32'
|
||||
/>
|
||||
<div
|
||||
className='more-modal__details'
|
||||
>
|
||||
<div className='more-modal__name'>
|
||||
{displayUsernameForUser(option)}
|
||||
</div>
|
||||
<div className='more-modal__description'>
|
||||
{option.email}
|
||||
</div>
|
||||
</div>
|
||||
<div className='more-modal__actions'>
|
||||
<div className='more-modal__actions--round'>
|
||||
<i className='fa fa-plus'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue(user) {
|
||||
return user.username;
|
||||
}
|
||||
|
||||
render() {
|
||||
let note;
|
||||
if (this.props.startingUsers) {
|
||||
if (this.state.values && this.state.values.length >= MAX_SELECTABLE_VALUES) {
|
||||
note = (
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.new_convo_note.full'
|
||||
defaultMessage='You’ve reached the maximum number of people for this conversation. Consider creating a private channel instead.'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
note = (
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.new_convo_note'
|
||||
defaultMessage='This will start a new conversation. If you’re adding a lot of people, consider creating a private channel instead.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const buttonSubmitText = (
|
||||
<FormattedMessage
|
||||
id='multiselect.go'
|
||||
defaultMessage='Go'
|
||||
/>
|
||||
);
|
||||
|
||||
const numRemainingText = (
|
||||
<FormattedMessage
|
||||
id='multiselect.numPeopleRemaining'
|
||||
defaultMessage='You can add {num, number} more {num, plural, =0 {people} one {person} other {people}}. '
|
||||
values={{
|
||||
num: MAX_SELECTABLE_VALUES - this.state.values.length
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName={'more-modal more-direct-channels'}
|
||||
show={this.state.show}
|
||||
onHide={this.handleHide}
|
||||
onExited={this.handleExit}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.title'
|
||||
defaultMessage='Direct Messages'
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<MultiSelect
|
||||
key='moreDirectChannelsList'
|
||||
options={this.state.users}
|
||||
optionRenderer={this.renderOption}
|
||||
values={this.state.values}
|
||||
valueRenderer={this.renderValue}
|
||||
perPage={USERS_PER_PAGE}
|
||||
handlePageChange={this.handlePageChange}
|
||||
handleInput={this.search}
|
||||
handleDelete={this.handleDelete}
|
||||
handleAdd={this.addValue}
|
||||
handleSubmit={this.handleSubmit}
|
||||
noteText={note}
|
||||
maxValues={MAX_SELECTABLE_VALUES}
|
||||
numRemainingText={numRemainingText}
|
||||
buttonSubmitText={buttonSubmitText}
|
||||
/>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user