summaryrefslogtreecommitdiffstats
path: root/webapp/components/more_direct_channels
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-04-25 11:46:02 -0400
committerChristopher Speller <crspeller@gmail.com>2017-04-25 11:46:02 -0400
commit6c4c706313eb765eb00c639f381646be74f27b69 (patch)
tree6068feaa9668dcd74601730ac1a5abfb366402b1 /webapp/components/more_direct_channels
parentcc07c005074348de87854f1c953a549e8772fa03 (diff)
downloadchat-6c4c706313eb765eb00c639f381646be74f27b69.tar.gz
chat-6c4c706313eb765eb00c639f381646be74f27b69.tar.bz2
chat-6c4c706313eb765eb00c639f381646be74f27b69.zip
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
Diffstat (limited to 'webapp/components/more_direct_channels')
-rw-r--r--webapp/components/more_direct_channels/index.js25
-rw-r--r--webapp/components/more_direct_channels/more_direct_channels.jsx328
2 files changed, 353 insertions, 0 deletions
diff --git a/webapp/components/more_direct_channels/index.js b/webapp/components/more_direct_channels/index.js
new file mode 100644
index 000000000..a56f45886
--- /dev/null
+++ b/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);
diff --git a/webapp/components/more_direct_channels/more_direct_channels.jsx b/webapp/components/more_direct_channels/more_direct_channels.jsx
new file mode 100644
index 000000000..50e2c4e48
--- /dev/null
+++ b/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>
+ );
+ }
+}