diff options
Diffstat (limited to 'webapp/components')
-rw-r--r-- | webapp/components/add_users_to_team.jsx | 270 | ||||
-rw-r--r-- | webapp/components/sidebar_header_dropdown.jsx | 185 | ||||
-rw-r--r-- | webapp/components/sidebar_right_menu.jsx | 70 |
3 files changed, 474 insertions, 51 deletions
diff --git a/webapp/components/add_users_to_team.jsx b/webapp/components/add_users_to_team.jsx new file mode 100644 index 000000000..fce651d9f --- /dev/null +++ b/webapp/components/add_users_to_team.jsx @@ -0,0 +1,270 @@ +// Copyright (c) 2017 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 {addUsersToTeam} from 'actions/team_actions.jsx'; +import {searchUsersNotInTeam} from 'actions/user_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 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'; + +const USERS_PER_PAGE = 50; +const MAX_SELECTABLE_VALUES = 20; + +export default class AddUsersToTeam extends React.Component { + 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.state = { + users: null, + values: [], + show: true, + search: false + }; + } + + componentDidMount() { + UserStore.addChangeListener(this.onChange); + UserStore.addNotInTeamChangeListener(this.onChange); + UserStore.addStatusesChangeListener(this.onChange); + + AsyncClient.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2); + } + + componentWillUnmount() { + UserStore.removeChangeListener(this.onChange); + UserStore.removeNotInTeamChangeListener(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(); + } + + const userIds = this.state.values.map((v) => v.id); + if (userIds.length === 0) { + return; + } + + addUsersToTeam(TeamStore.getCurrentId(), userIds); + + this.handleHide(); + } + + addValue(value) { + const values = Object.assign([], this.state.values); + if (values.indexOf(value) === -1) { + values.push(value); + } + + this.setState({values}); + } + + onChange(force) { + if (this.state.search && !force) { + return; + } + + const users = Object.assign([], UserStore.getProfileListNotInTeam(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) { + AsyncClient.getProfilesNotInTeam((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; + } + + const teamId = TeamStore.getCurrentId(); + + const searchTimeoutId = setTimeout( + () => { + searchUsersNotInTeam( + 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()) { + 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}); + } + ); + }, + Constants.SEARCH_TIMEOUT_MILLISECONDS + ); + + this.searchTimeoutId = searchTimeoutId; + } + + 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() { + 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='add_users_to_team.title' + defaultMessage='Add New Members To {teamName} Team' + values={{ + teamName: ( + <strong>{TeamStore.getCurrent().display_name}</strong> + ) + }} + /> + </Modal.Title> + </Modal.Header> + <Modal.Body> + <MultiSelect + key='addUsersToTeamKey' + 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} + maxValues={MAX_SELECTABLE_VALUES} + numRemainingText={numRemainingText} + /> + </Modal.Body> + </Modal> + ); + } +} + +AddUsersToTeam.propTypes = { + onModalDismissed: React.PropTypes.func +}; diff --git a/webapp/components/sidebar_header_dropdown.jsx b/webapp/components/sidebar_header_dropdown.jsx index a0a0205be..baa8aae7b 100644 --- a/webapp/components/sidebar_header_dropdown.jsx +++ b/webapp/components/sidebar_header_dropdown.jsx @@ -14,6 +14,7 @@ import AboutBuildModal from './about_build_modal.jsx'; import SidebarHeaderDropdownButton from './sidebar_header_dropdown_button.jsx'; import TeamMembersModal from './team_members_modal.jsx'; import UserSettingsModal from './user_settings/user_settings_modal.jsx'; +import AddUsersToTeam from './add_users_to_team.jsx'; import {Constants, WebrtcActionTypes} from 'utils/constants.jsx'; @@ -43,6 +44,8 @@ export default class SidebarHeaderDropdown extends React.Component { this.handleAboutModal = this.handleAboutModal.bind(this); this.aboutModalDismissed = this.aboutModalDismissed.bind(this); this.toggleAccountSettingsModal = this.toggleAccountSettingsModal.bind(this); + this.showAddUsersToTeamModal = this.showAddUsersToTeamModal.bind(this); + this.hideAddUsersToTeamModal = this.hideAddUsersToTeamModal.bind(this); this.showInviteMemberModal = this.showInviteMemberModal.bind(this); this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this); this.showTeamMembersModal = this.showTeamMembersModal.bind(this); @@ -61,7 +64,8 @@ export default class SidebarHeaderDropdown extends React.Component { showAboutModal: false, showDropdown: false, showTeamMembersModal: false, - showUserSettingsModal: false + showUserSettingsModal: false, + showAddUsersToTeamModal: false }; } @@ -102,6 +106,21 @@ export default class SidebarHeaderDropdown extends React.Component { }); } + showAddUsersToTeamModal(e) { + e.preventDefault(); + + this.setState({ + showAddUsersToTeamModal: true, + showDropdown: false + }); + } + + hideAddUsersToTeamModal() { + this.setState({ + showAddUsersToTeamModal: false + }); + } + showInviteMemberModal(e) { e.preventDefault(); @@ -181,6 +200,7 @@ export default class SidebarHeaderDropdown extends React.Component { const currentUser = this.props.currentUser; let teamLink = ''; let inviteLink = ''; + let addMemberToTeam = ''; let manageLink = ''; let sysAdminLink = ''; let isAdmin = false; @@ -204,7 +224,22 @@ export default class SidebarHeaderDropdown extends React.Component { > <FormattedMessage id='navbar_dropdown.inviteMember' - defaultMessage='Invite New Member' + defaultMessage='Send Email Invite' + /> + </a> + </li> + ); + + addMemberToTeam = ( + <li> + <a + id='addUsersToTeam' + href='#' + onClick={this.showAddUsersToTeamModal} + > + <FormattedMessage + id='navbar_dropdown.addMemberToTeam' + defaultMessage='Add Members to Team' /> </a> </li> @@ -230,9 +265,11 @@ export default class SidebarHeaderDropdown extends React.Component { if (config.RestrictTeamInvite === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { teamLink = null; inviteLink = null; + addMemberToTeam = null; } else if (config.RestrictTeamInvite === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { teamLink = null; inviteLink = null; + addMemberToTeam = null; } } } @@ -416,10 +453,8 @@ export default class SidebarHeaderDropdown extends React.Component { ); } - let nativeAppDivider = null; let nativeAppLink = null; if (global.window.mm_config.AppDownloadLink && !UserAgent.isMobileApp()) { - nativeAppDivider = <li className='divider'/>; nativeAppLink = ( <li> <Link @@ -447,6 +482,92 @@ export default class SidebarHeaderDropdown extends React.Component { ); } + let addUsersToTeamModal; + if (this.state.showAddUsersToTeamModal) { + addUsersToTeamModal = ( + <AddUsersToTeam + onModalDismissed={this.hideAddUsersToTeamModal} + /> + ); + } + + const accountSettings = ( + <li> + <a + id='accountSettings' + href='#' + onClick={this.toggleAccountSettingsModal} + > + <FormattedMessage + id='navbar_dropdown.accountSettings' + defaultMessage='Account Settings' + /> + </a> + </li> + ); + + const about = ( + <li> + <a + href='#' + onClick={this.handleAboutModal} + > + <FormattedMessage + id='navbar_dropdown.about' + defaultMessage='About Mattermost' + /> + </a> + </li> + ); + + const logout = ( + <li> + <a + id='logout' + href='#' + onClick={() => GlobalActions.emitUserLoggedOutEvent()} + > + <FormattedMessage + id='navbar_dropdown.logout' + defaultMessage='Logout' + /> + </a> + </li> + ); + + const customEmoji = this.renderCustomEmojiLink(); + + // Dividers. + let inviteDivider = null; + if (inviteLink || teamLink || addMemberToTeam) { + inviteDivider = <li className='divider'/>; + } + + let teamDivider = null; + if (teamSettings || manageLink || teams) { + teamDivider = <li className='divider'/>; + } + + let backstageDivider = null; + if (integrationsLink || customEmoji) { + backstageDivider = <li className='divider'/>; + } + + let sysAdminDivider = null; + if (sysAdminLink) { + sysAdminDivider = <li className='divider'/>; + } + + let helpDivider = null; + if (helpLink || reportLink || nativeAppLink || about) { + helpDivider = <li className='divider'/>; + } + + let logoutDivider = null; + if (logout) { + logoutDivider = <li className='divider'/>; + } + return ( <Dropdown id='sidebar-header-dropdown' @@ -460,56 +581,27 @@ export default class SidebarHeaderDropdown extends React.Component { onClick={this.toggleDropdown} /> <Dropdown.Menu> - <li> - <a - id='accountSettings' - href='#' - onClick={this.toggleAccountSettingsModal} - > - <FormattedMessage - id='navbar_dropdown.accountSettings' - defaultMessage='Account Settings' - /> - </a> - </li> + {accountSettings} + {inviteDivider} {inviteLink} {teamLink} - <li> - <a - id='logout' - href='#' - onClick={() => GlobalActions.emitUserLoggedOutEvent()} - > - <FormattedMessage - id='navbar_dropdown.logout' - defaultMessage='Logout' - /> - </a> - </li> - <li className='divider'/> - {integrationsLink} - {this.renderCustomEmojiLink()} - <li className='divider'/> + {addMemberToTeam} + {teamDivider} {teamSettings} {manageLink} - {sysAdminLink} {teams} - <li className='divider'/> + {backstageDivider} + {integrationsLink} + {customEmoji} + {sysAdminDivider} + {sysAdminLink} + {helpDivider} {helpLink} {reportLink} - <li> - <a - href='#' - onClick={this.handleAboutModal} - > - <FormattedMessage - id='navbar_dropdown.about' - defaultMessage='About Mattermost' - /> - </a> - </li> - {nativeAppDivider} {nativeAppLink} + {about} + {logoutDivider} + {logout} <UserSettingsModal show={this.state.showUserSettingsModal} onModalDismissed={() => this.setState({showUserSettingsModal: false})} @@ -519,6 +611,7 @@ export default class SidebarHeaderDropdown extends React.Component { show={this.state.showAboutModal} onModalDismissed={this.aboutModalDismissed} /> + {addUsersToTeamModal} </Dropdown.Menu> </Dropdown> ); diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx index c3c6d0f8f..7e689d95e 100644 --- a/webapp/components/sidebar_right_menu.jsx +++ b/webapp/components/sidebar_right_menu.jsx @@ -6,6 +6,7 @@ import TeamMembersModal from './team_members_modal.jsx'; import ToggleModalButton from './toggle_modal_button.jsx'; import UserSettingsModal from './user_settings/user_settings_modal.jsx'; import AboutBuildModal from './about_build_modal.jsx'; +import AddUsersToTeam from './add_users_to_team.jsx'; import UserStore from 'stores/user_store.jsx'; import TeamStore from 'stores/team_store.jsx'; @@ -36,6 +37,8 @@ export default class SidebarRightMenu extends React.Component { this.onChange = this.onChange.bind(this); this.handleClick = this.handleClick.bind(this); this.handleAboutModal = this.handleAboutModal.bind(this); + this.showAddUsersToTeamModal = this.showAddUsersToTeamModal.bind(this); + this.hideAddUsersToTeamModal = this.hideAddUsersToTeamModal.bind(this); this.searchMentions = this.searchMentions.bind(this); this.aboutModalDismissed = this.aboutModalDismissed.bind(this); this.getFlagged = this.getFlagged.bind(this); @@ -43,6 +46,7 @@ export default class SidebarRightMenu extends React.Component { const state = this.getStateFromStores(); state.showUserSettingsModal = false; state.showAboutModal = false; + state.showAddUsersToTeamModal = false; this.state = state; } @@ -62,6 +66,21 @@ export default class SidebarRightMenu extends React.Component { this.setState({showAboutModal: false}); } + showAddUsersToTeamModal(e) { + e.preventDefault(); + + this.setState({ + showAddUsersToTeamModal: true, + showDropdown: false + }); + } + + hideAddUsersToTeamModal() { + this.setState({ + showAddUsersToTeamModal: false + }); + } + getFlagged(e) { e.preventDefault(); getFlaggedPosts(); @@ -145,6 +164,7 @@ export default class SidebarRightMenu extends React.Component { const currentUser = UserStore.getCurrentUser(); let teamLink; let inviteLink; + let addUserToTeamLink; let teamSettingsLink; let manageLink; let consoleLink; @@ -165,7 +185,23 @@ export default class SidebarRightMenu extends React.Component { <i className='icon fa fa-user-plus'/> <FormattedMessage id='sidebar_right_menu.inviteNew' - defaultMessage='Invite New Member' + defaultMessage='Send Email Invite' + /> + </a> + </li> + ); + + addUserToTeamLink = ( + <li> + <a + id='addUsersToTeam' + href='#' + onClick={this.showAddUsersToTeamModal} + > + <i className='icon fa fa-user-plus'/> + <FormattedMessage + id='sidebar_right_menu.addMemberToTeam' + defaultMessage='Add Members to Team' /> </a> </li> @@ -192,9 +228,11 @@ export default class SidebarRightMenu extends React.Component { if (global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { teamLink = null; inviteLink = null; + addUserToTeamLink = null; } else if (global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { teamLink = null; inviteLink = null; + addUserToTeamLink = null; } } @@ -360,6 +398,25 @@ export default class SidebarRightMenu extends React.Component { ); } + let addUsersToTeamModal; + if (this.state.showAddUsersToTeamModal) { + addUsersToTeamModal = ( + <AddUsersToTeam + onModalDismissed={this.hideAddUsersToTeamModal} + /> + ); + } + + let teamDivider = null; + if (teamSettingsLink || manageLink || joinAnotherTeamLink) { + teamDivider = <li className='divider'/>; + } + + let consoleDivider = null; + if (consoleLink) { + consoleDivider = <li className='divider'/>; + } + return ( <div className='sidebar--menu' @@ -414,16 +471,20 @@ export default class SidebarRightMenu extends React.Component { /> </a> </li> + <li className='divider'/> {inviteLink} {teamLink} - {joinAnotherTeamLink} - <li className='divider'/> + {addUserToTeamLink} + {teamDivider} {teamSettingsLink} {manageLink} + {joinAnotherTeamLink} + {consoleDivider} {consoleLink} <li className='divider'/> {helpLink} {reportLink} + {nativeAppLink} <li> <a href='#' @@ -437,8 +498,6 @@ export default class SidebarRightMenu extends React.Component { </a> </li> <li className='divider'/> - {nativeAppLink} - <li className='divider'/> <li> <a href='#' @@ -461,6 +520,7 @@ export default class SidebarRightMenu extends React.Component { show={this.state.showAboutModal} onModalDismissed={this.aboutModalDismissed} /> + {addUsersToTeamModal} </div> ); } |