diff options
Diffstat (limited to 'webapp/components')
-rw-r--r-- | webapp/components/channel_members_dropdown.jsx | 180 | ||||
-rw-r--r-- | webapp/components/channel_members_modal.jsx | 153 | ||||
-rw-r--r-- | webapp/components/member_list_channel.jsx | 173 |
3 files changed, 360 insertions, 146 deletions
diff --git a/webapp/components/channel_members_dropdown.jsx b/webapp/components/channel_members_dropdown.jsx new file mode 100644 index 000000000..82aaf0612 --- /dev/null +++ b/webapp/components/channel_members_dropdown.jsx @@ -0,0 +1,180 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelStore from 'stores/channel_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import {removeUserFromChannel} from 'actions/channel_actions.jsx'; + +import * as AsyncClient from 'utils/async_client.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +export default class ChannelMembersDropdown extends React.Component { + constructor(props) { + super(props); + + this.handleRemoveFromChannel = this.handleRemoveFromChannel.bind(this); + + this.state = { + serverError: null, + user: null, + role: null + }; + } + + handleRemoveFromChannel() { + removeUserFromChannel( + this.props.channel.id, + this.props.user.id, + () => { + AsyncClient.getChannelStats(this.props.channel.id); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + // Checks if the user this menu is for is a channel admin or not. + isChannelAdmin() { + if (Utils.isChannelAdmin(this.props.channelMember.roles)) { + return true; + } + + return false; + } + + // Checks if the current user has the power to change the roles of this member. + canChangeMemberRoles() { + if (UserStore.isSystemAdminForCurrentUser()) { + return true; + } else if (TeamStore.isTeamAdminForCurrentTeam()) { + return true; + } else if (ChannelStore.isChannelAdminForCurrentChannel()) { + return true; + } + + return false; + } + + // Checks if the current user has the power to remove this member from the channel. + canRemoveMember() { + // TODO: This will be implemented as part of PLT-5047. + return true; + } + + render() { + let serverError = null; + if (this.state.serverError) { + serverError = ( + <div className='has-error'> + <label className='has-error control-label'>{this.state.serverError}</label> + </div> + ); + } + + if (this.props.user.id === UserStore.getCurrentId()) { + return null; + } + + if (this.canChangeMemberRoles()) { + let role = ( + <FormattedMessage + id='channel_members_dropdown.channel_member' + defaultMessage='Channel Member' + /> + ); + + if (this.isChannelAdmin()) { + role = ( + <FormattedMessage + id='channel_members_dropdown.channel_admin' + defaultMessage='Channel Admin' + /> + ); + } + + let removeFromChannel = null; + if (this.canRemoveMember()) { + removeFromChannel = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleRemoveFromChannel} + > + <FormattedMessage + id='channel_members_dropdown.remove_from_channel' + defaultMessage='Remove From Channel' + /> + </a> + </li> + ); + } + + return ( + <div className='dropdown member-drop'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + data-toggle='dropdown' + aria-expanded='true' + > + <span>{role} </span> + <span className='fa fa-chevron-down'/> + </a> + <ul + className='dropdown-menu member-menu' + role='menu' + > + {removeFromChannel} + </ul> + {serverError} + </div> + ); + } else if (this.canRemoveMember()) { + return ( + <button + type='button' + className='btn btn-danger btn-message' + onClick={this.handleRemoveFromChannel} + > + <FormattedMessage + id='channel_members_dropdown.remove_member' + defaultMessage='Remove Member' + /> + </button> + ); + } else if (this.isChannelAdmin()) { + return ( + <div> + <FormattedMessage + id='channel_members_dropdown.channel_admin' + defaultMessage='Channel Admin' + /> + </div> + ); + } + + return ( + <div> + <FormattedMessage + id='channel_members_dropdown.channel_member' + defaultMessage='Channel Member' + /> + </div> + ); + } +} + +ChannelMembersDropdown.propTypes = { + channel: React.PropTypes.object.isRequired, + user: React.PropTypes.object.isRequired, + teamMember: React.PropTypes.object.isRequired, + channelMember: React.PropTypes.object.isRequired +}; diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx index 351efed96..96d90e5cc 100644 --- a/webapp/components/channel_members_modal.jsx +++ b/webapp/components/channel_members_modal.jsx @@ -1,170 +1,29 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import SearchableUserList from './searchable_user_list.jsx'; -import LoadingScreen from './loading_screen.jsx'; - -import UserStore from 'stores/user_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; - -import {searchUsers} from 'actions/user_actions.jsx'; -import {removeUserFromChannel} from 'actions/channel_actions.jsx'; - -import * as AsyncClient from 'utils/async_client.jsx'; -import * as UserAgent from 'utils/user_agent.jsx'; -import Constants from 'utils/constants.jsx'; +import MemberListChannel from './member_list_channel.jsx'; import React from 'react'; import {Modal} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; -const USERS_PER_PAGE = 50; - export default class ChannelMembersModal extends React.Component { constructor(props) { super(props); - this.onChange = this.onChange.bind(this); - this.onStatusChange = this.onStatusChange.bind(this); this.onHide = this.onHide.bind(this); - this.handleRemove = this.handleRemove.bind(this); - this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this); - this.search = this.search.bind(this); - this.nextPage = this.nextPage.bind(this); - - this.term = ''; - this.searchTimeoutId = 0; - - const stats = ChannelStore.getStats(props.channel.id); this.state = { - users: [], - total: stats.member_count, - show: true, - search: false, - statusChange: false + channel: this.props.channel, + show: true }; } - componentDidMount() { - ChannelStore.addStatsChangeListener(this.onChange); - UserStore.addInChannelChangeListener(this.onChange); - UserStore.addStatusesChangeListener(this.onStatusChange); - - AsyncClient.getProfilesInChannel(this.props.channel.id, 0); - } - - componentWillUnmount() { - ChannelStore.removeStatsChangeListener(this.onChange); - UserStore.removeInChannelChangeListener(this.onChange); - UserStore.removeStatusesChangeListener(this.onStatusChange); - } - - onChange(force) { - if (this.state.search && !force) { - this.search(this.term); - return; - } - - const stats = ChannelStore.getStats(this.props.channel.id); - this.setState({ - users: UserStore.getProfileListInChannel(this.props.channel.id), - total: stats.member_count - }); - } - - onStatusChange() { - // Initiate a render to pick up on new statuses - this.setState({ - statusChange: !this.state.statusChange - }); - } - onHide() { this.setState({show: false}); } - handleRemove(user) { - const userId = user.id; - - removeUserFromChannel( - this.props.channel.id, - userId, - null, - (err) => { - this.setState({inviteError: err.message}); - } - ); - } - - createRemoveMemberButton({user}) { - if (user.id === UserStore.getCurrentId()) { - return null; - } - - return ( - <button - type='button' - className='btn btn-primary btn-message' - onClick={this.handleRemove.bind(this, user)} - > - <FormattedMessage - id='channel_members_modal.remove' - defaultMessage='Remove' - /> - </button> - ); - } - - nextPage(page) { - AsyncClient.getProfilesInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE); - } - - search(term) { - this.term = term; - - if (term === '') { - this.onChange(true); - this.setState({search: false}); - return; - } - - clearTimeout(this.searchTimeoutId); - - this.searchTimeoutId = setTimeout( - () => { - searchUsers( - term, - TeamStore.getCurrentId(), - {in_channel_id: this.props.channel.id}, - (users) => { - this.setState({search: true, users}); - } - ); - }, - Constants.SEARCH_TIMEOUT_MILLISECONDS - ); - } - render() { - let content; - if (this.state.loading) { - content = (<LoadingScreen/>); - } else { - content = ( - <SearchableUserList - users={this.state.users} - usersPerPage={USERS_PER_PAGE} - total={this.state.total} - nextPage={this.nextPage} - search={this.search} - actions={[this.createRemoveMemberButton]} - focusOnMount={!UserAgent.isMobile()} - /> - ); - } - return ( <div> <Modal @@ -177,7 +36,7 @@ export default class ChannelMembersModal extends React.Component { <Modal.Title> <span className='name'>{this.props.channel.display_name}</span> <FormattedMessage - id='channel_memebers_modal.members' + id='channel_members_modal.members' defaultMessage=' Members' /> </Modal.Title> @@ -198,7 +57,9 @@ export default class ChannelMembersModal extends React.Component { <Modal.Body ref='modalBody' > - {content} + <MemberListChannel + channel={this.props.channel} + /> </Modal.Body> </Modal> </div> diff --git a/webapp/components/member_list_channel.jsx b/webapp/components/member_list_channel.jsx new file mode 100644 index 000000000..75ecd8172 --- /dev/null +++ b/webapp/components/member_list_channel.jsx @@ -0,0 +1,173 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelMembersDropdown from 'components/channel_members_dropdown.jsx'; +import SearchableUserList from 'components/searchable_user_list.jsx'; + +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; + +import {searchUsers, loadProfilesAndTeamMembersAndChannelMembers, loadTeamMembersAndChannelMembersForProfilesList} from 'actions/user_actions.jsx'; +import {getChannelStats} from 'utils/async_client.jsx'; + +import Constants from 'utils/constants.jsx'; + +import * as UserAgent from 'utils/user_agent.jsx'; + +import React from 'react'; + +const USERS_PER_PAGE = 50; + +export default class MemberListChannel extends React.Component { + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + this.onStatsChange = this.onStatsChange.bind(this); + this.search = this.search.bind(this); + this.loadComplete = this.loadComplete.bind(this); + + this.searchTimeoutId = 0; + + const stats = ChannelStore.getCurrentStats(); + + this.state = { + users: UserStore.getProfileListInChannel(), + teamMembers: Object.assign({}, TeamStore.getMembersInTeam()), + channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()), + total: stats.member_count, + search: false, + term: '', + loading: true + }; + } + + componentDidMount() { + UserStore.addInTeamChangeListener(this.onChange); + UserStore.addStatusesChangeListener(this.onChange); + TeamStore.addChangeListener(this.onChange); + ChannelStore.addChangeListener(this.onChange); + ChannelStore.addStatsChangeListener(this.onStatsChange); + + loadProfilesAndTeamMembersAndChannelMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete); + getChannelStats(ChannelStore.getCurrentId()); + } + + componentWillUnmount() { + UserStore.removeInTeamChangeListener(this.onChange); + UserStore.removeStatusesChangeListener(this.onChange); + TeamStore.removeChangeListener(this.onChange); + ChannelStore.removeChangeListener(this.onChange); + ChannelStore.removeStatsChangeListener(this.onStatsChange); + } + + loadComplete() { + this.setState({loading: false}); + } + + onChange(force) { + if (this.state.search && !force) { + return; + } else if (this.state.search) { + this.search(this.state.term); + return; + } + + this.setState({ + users: UserStore.getProfileListInChannel(), + teamMembers: Object.assign({}, TeamStore.getMembersInTeam()), + channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()) + }); + } + + onStatsChange() { + const stats = ChannelStore.getCurrentStats(); + this.setState({total: stats.member_count}); + } + + nextPage(page) { + loadProfilesAndTeamMembersAndChannelMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE); + } + + search(term) { + if (term === '') { + this.setState({ + search: false, + term, + users: UserStore.getProfileListInChannel(), + teamMembers: Object.assign([], TeamStore.getMembersInTeam()), + channelMembers: Object.assign([], ChannelStore.getMembersInChannel()) + }); + return; + } + + clearTimeout(this.searchTimeoutId); + + this.searchTimeoutId = setTimeout( + () => { + searchUsers( + term, + TeamStore.getCurrentId(), + {}, + (users) => { + this.setState({ + loading: true, + search: true, + users, + term, + teamMembers: Object.assign([], TeamStore.getMembersInTeam()), + channelMembers: Object.assign([], ChannelStore.getMembersInChannel()) + }); + loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete); + } + ); + }, + Constants.SEARCH_TIMEOUT_MILLISECONDS + ); + } + + render() { + const teamMembers = this.state.teamMembers; + const channelMembers = this.state.channelMembers; + const users = this.state.users; + const actionUserProps = {}; + + let usersToDisplay; + if (this.state.loading) { + usersToDisplay = null; + } else { + usersToDisplay = []; + + for (let i = 0; i < users.length; i++) { + const user = users[i]; + + if (teamMembers[user.id] && channelMembers[user.id]) { + usersToDisplay.push(user); + actionUserProps[user.id] = { + channel: this.props.channel, + teamMember: teamMembers[user.id], + channelMember: channelMembers[user.id] + }; + } + } + } + + return ( + <SearchableUserList + users={usersToDisplay} + usersPerPage={USERS_PER_PAGE} + total={this.state.total} + nextPage={this.nextPage} + search={this.search} + actions={[ChannelMembersDropdown]} + actionUserProps={actionUserProps} + focusOnMount={!UserAgent.isMobile()} + /> + ); + } +} + +MemberListChannel.propTypes = { + channel: React.PropTypes.object.isRequired +}; |