Skip to content

Commit

Permalink
MM 18527 - sort teams in LHS (mattermost#4042)
Browse files Browse the repository at this point in the history
* MM-18527: added feature to sort teams on sidebar

* handle undefined teamsOrder if not passed

* followed style-guide

* removed unused params

* Update teams sidebar on changes to order

* rebase with master

* add null checks, add prop types

* fixed missing hover state

* Resolve PR comments

* Add package-lock

* Fix lint error

* please linter

* Fix merge error

Co-authored-by: Brad Coughlin <[email protected]>
Co-authored-by: Nevyana <[email protected]>
Co-authored-by: Nev Angelova <[email protected]>
Co-authored-by: Nevyana Angelova <[email protected]>
  • Loading branch information
5 people committed Mar 17, 2020
1 parent 010fb75 commit 92ef828
Show file tree
Hide file tree
Showing 11 changed files with 3,190 additions and 4,243 deletions.
17 changes: 17 additions & 0 deletions actions/team_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import {viewChannel, getChannelStats} from 'mattermost-redux/actions/channels';
import * as TeamActions from 'mattermost-redux/actions/teams';
import {getCurrentChannelId, isManuallyUnread} from 'mattermost-redux/selectors/entities/channels';
import {getUser} from 'mattermost-redux/actions/users';
import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';

import {browserHistory} from 'utils/browser_history';
import {Preferences} from 'utils/constants';

export function removeUserFromTeamAndGetStats(teamId, userId) {
return async (dispatch, getState) => {
Expand Down Expand Up @@ -88,3 +91,17 @@ export function switchTeam(url) {
browserHistory.push(url);
};
}

export function updateTeamsOrderForUser(teamIds) {
return async (dispatch, getState) => {
const state = getState();
const currentUserId = getCurrentUserId(state);
const teamOrderPreferences = [{
user_id: currentUserId,
name: '',
category: Preferences.TEAMS_ORDER,
value: teamIds.join(','),
}];
dispatch(savePreferences(currentUserId, teamOrderPreferences));
};
}
41 changes: 34 additions & 7 deletions components/team_sidebar/components/team_button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React from 'react';
import {Tooltip} from 'react-bootstrap';
import {injectIntl} from 'react-intl';
import {Link} from 'react-router-dom';
import {Draggable} from 'react-beautiful-dnd';

import {mark, trackEvent} from 'actions/diagnostics_actions.jsx';
import Constants from 'utils/constants';
Expand Down Expand Up @@ -34,6 +35,9 @@ class TeamButton extends React.Component {
teamIconUrl: PropTypes.string,
switchTeam: PropTypes.func.isRequired,
intl: intlShape.isRequired,
isDraggable: PropTypes.bool,
teamIndex: PropTypes.number,
teamId: PropTypes.string,
};

static defaultProps = {
Expand All @@ -59,11 +63,12 @@ class TeamButton extends React.Component {
}

render() {
const {teamIconUrl, displayName, btnClass, mentions, unread} = this.props;
const {teamIconUrl, displayName, btnClass, mentions, unread, isDraggable = false, teamIndex, teamId} = this.props;
const {formatMessage} = this.props.intl;

let teamClass = this.props.active ? 'active' : '';
const disabled = this.props.disabled ? 'team-disabled' : '';
const isNotCreateTeamButton = !this.props.url.endsWith('create_team') && !this.props.url.endsWith('select_team');
const handleClick = (this.props.active || this.props.disabled) ? this.handleDisabled : this.handleSwitch;
let badge;

Expand All @@ -76,7 +81,13 @@ class TeamButton extends React.Component {
});

if (!teamClass) {
teamClass = unread ? 'unread' : '';
if (unread) {
teamClass = 'unread';
} else if (isNotCreateTeamButton) {
teamClass = '';
} else {
teamClass = 'special';
}
ariaLabel = formatMessage({
id: 'team.button.unread.ariaLabel',
defaultMessage: '{teamName} team unread',
Expand Down Expand Up @@ -178,7 +189,7 @@ class TeamButton extends React.Component {
);

// if this is not a "special" team button, give it a context menu
if (!this.props.url.endsWith('create_team') && !this.props.url.endsWith('select_team')) {
if (isNotCreateTeamButton) {
teamButton = (
<CopyUrlContextMenu
link={this.props.url}
Expand All @@ -202,15 +213,31 @@ class TeamButton extends React.Component {
);
}

return (
<div
className={`team-container ${teamClass}`}
return isDraggable ? (
<Draggable
draggableId={teamId}
index={teamIndex}
>
{(provided) => {
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`team-container ${teamClass}`}
>
{teamButton}
</div>
);
}}
</Draggable>
) : (
<div className={`team-container ${teamClass}`}>
{teamButton}
{orderIndicator}
</div>
);
}
}

export default injectIntl(TeamButton);
export default injectIntl(TeamButton);
6 changes: 5 additions & 1 deletion components/team_sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {withRouter} from 'react-router-dom';

import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getMyTeams, getJoinableTeamIds, getTeamMemberships, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {get} from 'mattermost-redux/selectors/entities/preferences';

import {getCurrentLocale} from 'selectors/i18n';
import {getIsLhsOpen} from 'selectors/lhs';
import {switchTeam} from 'actions/team_actions.jsx';
import {switchTeam, updateTeamsOrderForUser} from 'actions/team_actions.jsx';
import {Preferences} from 'utils/constants.jsx';

import TeamSidebar from './team_sidebar_controller.jsx';

Expand All @@ -30,6 +32,7 @@ function mapStateToProps(state) {
experimentalPrimaryTeam,
locale: getCurrentLocale(state),
moreTeamsToJoin,
userTeamsOrderPreference: get(state, Preferences.TEAMS_ORDER, '', ''),
};
}

Expand All @@ -38,6 +41,7 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
getTeams,
switchTeam,
updateTeamsOrderForUser,
}, dispatch),
};
}
Expand Down
109 changes: 86 additions & 23 deletions components/team_sidebar/team_sidebar_controller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Scrollbars from 'react-custom-scrollbars';
import {FormattedMessage} from 'react-intl';
import Permissions from 'mattermost-redux/constants/permissions';
import classNames from 'classnames';
import {DragDropContext, Droppable} from 'react-beautiful-dnd';

import {Constants} from 'utils/constants.jsx';
import {filterAndSortTeamsByDisplayName} from 'utils/team_utils.jsx';
Expand Down Expand Up @@ -41,7 +42,7 @@ export function renderThumbVertical(props) {
/>);
}

export default class TeamSidebar extends React.PureComponent {
export default class TeamSidebar extends React.Component {
static propTypes = {
myTeams: PropTypes.array.isRequired,
currentTeamId: PropTypes.string.isRequired,
Expand All @@ -53,7 +54,9 @@ export default class TeamSidebar extends React.PureComponent {
actions: PropTypes.shape({
getTeams: PropTypes.func.isRequired,
switchTeam: PropTypes.func.isRequired,
updateTeamsOrderForUser: PropTypes.func.isRequired,
}).isRequired,
userTeamsOrderPreference: PropTypes.string,
}

constructor(props) {
Expand Down Expand Up @@ -145,6 +148,42 @@ export default class TeamSidebar extends React.PureComponent {
document.removeEventListener('keyup', this.handleKeyUp);
}

onDragEnd = (result) => {
const {
updateTeamsOrderForUser,
} = this.props.actions;

if (!result.destination) {
return;
}

const teams = filterAndSortTeamsByDisplayName(this.props.myTeams, this.props.locale, this.props.userTeamsOrderPreference);

const sourceIndex = result.source.index;
const destinationIndex = result.destination.index;

// Positioning the dropped Team button
const popElement = (list, idx) => {
return [...list.slice(0, idx), ...list.slice(idx + 1, list.length)];
};

const pushElement = (list, idx, itemId) => {
return [
...list.slice(0, idx),
teams.find((team) => team.id === itemId),
...list.slice(idx, list.length),
];
};

const newTeamsOrder = pushElement(
popElement(teams, sourceIndex),
destinationIndex,
result.draggableId
);
updateTeamsOrderForUser(newTeamsOrder.map((o) => o.id));
this.setState({teamsOrder: newTeamsOrder});
}

render() {
const root = document.querySelector('#root');
if (this.props.myTeams.length <= 1) {
Expand All @@ -154,28 +193,32 @@ export default class TeamSidebar extends React.PureComponent {
root.classList.add('multi-teams');

const plugins = [];
const teams = filterAndSortTeamsByDisplayName(this.props.myTeams, this.props.locale).
map((team, idx) => {
const member = this.props.myTeamMembers[team.id];
return (
<TeamButton
key={'switch_team_' + team.name}
order={idx + 1}
showOrder={this.state.showOrder}
url={`/${team.name}`}
tip={team.display_name}
active={team.id === this.props.currentTeamId}
displayName={team.display_name}
unread={member.msg_count > 0}
mentions={member.mention_count}
teamIconUrl={Utils.imageURLForTeam(team)}
switchTeam={this.props.actions.switchTeam}
/>
);
});
const sortedTeams = filterAndSortTeamsByDisplayName(this.props.myTeams, this.props.locale, this.props.userTeamsOrderPreference);

const teams = sortedTeams.map((team, index) => {
const member = this.props.myTeamMembers[team.id];
return (
<TeamButton
key={'switch_team_' + team.name}
url={`/${team.name}`}
tip={team.display_name}
active={team.id === this.props.currentTeamId}
displayName={team.display_name}
unread={member.msg_count > 0}
mentions={member.mention_count}
teamIconUrl={Utils.imageURLForTeam(team)}
switchTeam={this.props.actions.switchTeam}
isDraggable={true}
teamId={team.id}
teamIndex={index}
/>
);
});

const joinableTeams = [];

if (this.props.moreTeamsToJoin && !this.props.experimentalPrimaryTeam) {
teams.push(
joinableTeams.push(
<TeamButton
btnClass='team-btn__add'
key='more_teams'
Expand All @@ -191,7 +234,7 @@ export default class TeamSidebar extends React.PureComponent {
/>
);
} else {
teams.push(
joinableTeams.push(
<SystemPermissionGate
permissions={[Permissions.CREATE_TEAM]}
key='more_teams'
Expand Down Expand Up @@ -240,7 +283,27 @@ export default class TeamSidebar extends React.PureComponent {
renderView={renderView}
onScroll={this.handleScroll}
>
{teams}
<DragDropContext
onDragEnd={this.onDragEnd}
>
<Droppable
droppableId='my_teams'
type='TEAM_BUTTON'
>
{(provided) => {
return (
<div
ref={provided.innerRef}
{...provided.droppableProps}
>
{teams}
{provided.placeholder}
</div>
);
}}
</Droppable>
</DragDropContext>
{joinableTeams}
</Scrollbars>
</div>
{plugins}
Expand Down
Loading

0 comments on commit 92ef828

Please sign in to comment.