Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
PLT-5049 (Webapp) New Channel Members UI. (#5036)
Browse files Browse the repository at this point in the history
This replaces the existing Channel Members UI with one based on the Team
Members UI, so that either a button, a role or a role with a menu can be
displayed.

Basic logic for which actions and roles are displayed is implemented,
although this doesn't change behaviour or functionality at all, as that
will come in later PRs. It does, however, add code to fetch the
ChannelMember objects as that is necessary to provide the full set of
actions and roles as intended.
  • Loading branch information
grundleborg authored and jwilander committed Jan 15, 2017
1 parent 892a6b9 commit def9295
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 150 deletions.
3 changes: 3 additions & 0 deletions actions/channel_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ export function removeUserFromChannel(channelId, userId, success, error) {
}
UserStore.emitInChannelChange();

ChannelStore.removeMemberInChannel(channelId, userId);
ChannelStore.emitChange();

if (success) {
success(data);
}
Expand Down
108 changes: 108 additions & 0 deletions actions/user_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,34 @@ export function loadProfilesAndTeamMembers(offset, limit, teamId = TeamStore.get
);
}

export function loadProfilesAndTeamMembersAndChannelMembers(offset, limit, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
Client.getProfilesInChannel(
channelId,
offset,
limit,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
profiles: data,
channel_id: channelId,
offset,
count: Object.keys(data).length
});

loadTeamMembersForProfilesMap(
data,
teamId,
() => {
loadChannelMembersForProfilesMap(data, channelId, success, error);
loadStatusesForProfilesMap(data);
});
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesInChannel');
}
);
}

export function loadTeamMembersForProfilesMap(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (const pid in profiles) {
Expand Down Expand Up @@ -132,6 +160,86 @@ function loadTeamMembersForProfiles(userIds, teamId, success, error) {
);
}

export function loadChannelMembersForProfilesMap(profiles, channelId = ChannelStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (const pid in profiles) {
if (!profiles.hasOwnProperty(pid)) {
continue;
}

if (!ChannelStore.hasActiveMemberInChannel(channelId, pid)) {
membersToLoad[pid] = true;
}
}

const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}

loadChannelMembersForProfiles(list, channelId, success, error);
}

export function loadTeamMembersAndChannelMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
loadTeamMembersForProfilesList(profiles, teamId, () => {
loadChannelMembersForProfilesList(profiles, channelId, success, error);
}, error);
}

export function loadChannelMembersForProfilesList(profiles, channelId = ChannelStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (let i = 0; i < profiles.length; i++) {
const pid = profiles[i].id;

if (!ChannelStore.hasActiveMemberInChannel(channelId, pid)) {
membersToLoad[pid] = true;
}
}

const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}

loadChannelMembersForProfiles(list, channelId, success, error);
}

function loadChannelMembersForProfiles(userIds, channelId, success, error) {
Client.getChannelMembersByIds(
channelId,
userIds,
(data) => {
const memberMap = {};
for (let i = 0; i < data.length; i++) {
memberMap[data[i].user_id] = data[i];
}

AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MEMBERS_IN_CHANNEL,
channel_id: channelId,
channel_members: memberMap
});

if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'getChannelMembersByIds');

if (error) {
error(err);
}
}
);
}

function populateDMChannelsWithProfiles(userIds) {
const currentUserId = UserStore.getCurrentId();

Expand Down
10 changes: 10 additions & 0 deletions client/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,16 @@ export default class Client {
end(this.handleResponse.bind(this, 'getChannelMember', success, error));
}

getChannelMembersByIds(channelId, userIds, success, error) {
request.
post(`${this.getChannelNeededRoute(channelId)}/members/ids`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send(userIds).
end(this.handleResponse.bind(this, 'getChannelMembersByIds', success, error));
}

addChannelMember(channelId, userId, success, error) {
request.
post(`${this.getChannelNeededRoute(channelId)}/add`).
Expand Down
180 changes: 180 additions & 0 deletions components/channel_members_dropdown.jsx
Original file line number Diff line number Diff line change
@@ -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
};
Loading

0 comments on commit def9295

Please sign in to comment.