summaryrefslogtreecommitdiffstats
path: root/webapp/components/more_direct_channels.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/more_direct_channels.jsx')
-rw-r--r--webapp/components/more_direct_channels.jsx308
1 files changed, 189 insertions, 119 deletions
diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx
index 13ee50b4d..c4a3a3526 100644
--- a/webapp/components/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels.jsx
@@ -1,19 +1,19 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import SearchableUserList from 'components/searchable_user_list.jsx';
-import SpinnerButton from 'components/spinner_button.jsx';
+import MultiSelect from 'components/multiselect/multiselect.jsx';
+import ProfilePicture from 'components/profile_picture.jsx';
import {searchUsers} from 'actions/user_actions.jsx';
-import {openDirectChannelToUser} from 'actions/channel_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 * as AsyncClient from 'utils/async_client.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import {localizeMessage} from 'utils/utils.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';
@@ -21,6 +21,7 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
const USERS_PER_PAGE = 50;
+const MAX_SELECTABLE_VALUES = Constants.MAX_USERS_IN_GM - 1;
export default class MoreDirectChannels extends React.Component {
constructor(props) {
@@ -28,21 +29,31 @@ export default class MoreDirectChannels extends React.Component {
this.handleHide = this.handleHide.bind(this);
this.handleExit = this.handleExit.bind(this);
- this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleDelete = this.handleDelete.bind(this);
this.onChange = this.onChange.bind(this);
- this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
- this.toggleList = this.toggleList.bind(this);
- this.nextPage = this.nextPage.bind(this);
this.search = this.search.bind(this);
+ this.addValue = this.addValue.bind(this);
this.searchTimeoutId = 0;
+ 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,
- loadingDMChannel: -1,
- listType: 'team',
+ values,
show: true,
- search: false
+ search: false,
+ loadingChannel: -1
};
}
@@ -50,17 +61,18 @@ export default class MoreDirectChannels extends React.Component {
UserStore.addChangeListener(this.onChange);
UserStore.addInTeamChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
- TeamStore.addChangeListener(this.onChange);
- AsyncClient.getProfiles(0, Constants.PROFILE_CHUNK_SIZE);
- AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), 0, Constants.PROFILE_CHUNK_SIZE);
+ if (this.listType === 'any') {
+ AsyncClient.getProfiles(0, USERS_PER_PAGE * 2);
+ } else {
+ AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
+ }
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onChange);
UserStore.removeInTeamChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
- TeamStore.removeChangeListener(this.onChange);
}
handleHide() {
@@ -68,8 +80,8 @@ export default class MoreDirectChannels extends React.Component {
}
handleExit() {
- if (this.exitToDirectChannel) {
- browserHistory.push(this.exitToDirectChannel);
+ if (this.exitToChannel) {
+ browserHistory.push(this.exitToChannel);
}
if (this.props.onModalDismissed) {
@@ -77,28 +89,49 @@ export default class MoreDirectChannels extends React.Component {
}
}
- handleShowDirectChannel(teammate, e) {
- e.preventDefault();
+ handleSubmit(e) {
+ if (e) {
+ e.preventDefault();
+ }
- if (this.state.loadingDMChannel !== -1) {
+ if (this.state.loadingChannel !== -1) {
return;
}
- this.setState({loadingDMChannel: teammate.id});
- openDirectChannelToUser(
- teammate,
- (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.exitToDirectChannel = TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name;
- this.setState({loadingDMChannel: -1});
- this.handleHide();
- },
- () => {
- this.setState({loadingDMChannel: -1});
- }
- );
+ 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(force) {
@@ -107,83 +140,69 @@ export default class MoreDirectChannels extends React.Component {
}
let users;
- if (this.state.listType === 'any') {
- users = UserStore.getProfileList(true);
+ if (this.listType === 'any') {
+ users = Object.assign([], UserStore.getProfileList(true));
} else {
- users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, true);
+ users = Object.assign([], UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true));
}
- this.setState({
- users
- });
- }
-
- toggleList(e) {
- const listType = e.target.value;
- let users;
- if (listType === 'any') {
- users = UserStore.getProfileList(true);
- } else {
- users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, 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,
- listType
+ users
});
}
- createJoinDirectChannelButton({user}) {
- return (
- <SpinnerButton
- className='btn btm-sm btn-primary'
- spinning={this.state.loadingDMChannel === user.id}
- onClick={this.handleShowDirectChannel.bind(this, user)}
- >
- <FormattedMessage
- id='more_direct_channels.message'
- defaultMessage='Message'
- />
- </SpinnerButton>
- );
- }
-
- nextPage(page) {
- if (this.state.listType === 'any') {
+ handlePageChange(page, prevPage) {
+ if (page > prevPage) {
AsyncClient.getProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
- } else {
- AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
}
}
search(term) {
+ clearTimeout(this.searchTimeoutId);
+
if (term === '') {
this.onChange(true);
this.setState({search: false});
+ this.searchTimeoutId = '';
return;
}
let teamId;
- if (this.state.listType === 'any') {
+ if (this.listType === 'any') {
teamId = '';
} else {
teamId = TeamStore.getCurrentId();
}
- clearTimeout(this.searchTimeoutId);
-
- this.searchTimeoutId = setTimeout(
+ const searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
teamId,
{},
(users) => {
+ if (searchTimeoutId !== this.searchTimeoutId) {
+ return;
+ }
+
+ let indexToDelete = -1;
for (let i = 0; i < users.length; i++) {
if (users[i].id === UserStore.getCurrentId()) {
- users.splice(i, 1);
- break;
+ indexToDelete = i;
}
+ users[i].value = users[i].id;
+ users[i].label = '@' + users[i].username;
+ }
+
+ if (indexToDelete !== -1) {
+ users.splice(indexToDelete, 1);
}
this.setState({search: true, users});
}
@@ -191,44 +210,88 @@ export default class MoreDirectChannels extends React.Component {
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
+
+ this.searchTimeoutId = searchTimeoutId;
}
- render() {
- let teamToggle;
- let memberClass = '';
- if (global.window.mm_config.RestrictDirectMessage === 'any') {
- memberClass = 'more-system-members';
- teamToggle = (
- <div className='member-select__container'>
- <select
- className='form-control'
- id='restrictList'
- ref='restrictList'
- defaultValue='team'
- onChange={this.toggleList}
- >
- <option value='any'>
- {localizeMessage('filtered_user_list.any_team', 'All Users')}
- </option>
- <option value='team'>
- {localizeMessage('filtered_user_list.team_only', 'Members of this Team')}
- </option>
- </select>
- <span
- className='member-show'
- >
- <FormattedMessage
- id='filtered_user_list.show'
- defaultMessage='Filter:'
- />
- </span>
+ 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 group 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 group instead.'
+ />
+ );
+ }
}
+ 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 ' + memberClass}
+ dialogClassName={'more-modal more-direct-channels'}
show={this.state.show}
onHide={this.handleHide}
onExited={this.handleExit}
@@ -242,15 +305,21 @@ export default class MoreDirectChannels extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- {teamToggle}
- <SearchableUserList
- key={'moreDirectChannelsList_' + this.state.listType}
- users={this.state.users}
- usersPerPage={USERS_PER_PAGE}
- nextPage={this.nextPage}
- search={this.search}
- actions={[this.createJoinDirectChannelButton]}
- focusOnMount={!UserAgent.isMobile()}
+ <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}
/>
</Modal.Body>
</Modal>
@@ -259,5 +328,6 @@ export default class MoreDirectChannels extends React.Component {
}
MoreDirectChannels.propTypes = {
+ startingUsers: React.PropTypes.arrayOf(React.PropTypes.object),
onModalDismissed: React.PropTypes.func
-}; \ No newline at end of file
+};