From b03189defb794a529cd21de5ef15936b5b150012 Mon Sep 17 00:00:00 2001 From: Stan Chan Date: Mon, 10 Jul 2017 05:30:51 -0700 Subject: [PATCH] Refactor Jump to Conversation (#726) * Refactor Jump to Conversation Fixed channel loader not showing when switching channels Refactored jump to conversation * Review feedback --- .../channel_drawer/channel_drawer.js | 10 +- .../filtered_list/filtered_list.js | 194 ++++++++++++------ .../channels_list/filtered_list/index.js | 16 +- .../channel_drawer/channels_list/index.js | 2 +- .../channels_list/list/index.js | 13 +- .../channel_drawer/channels_list/list/list.js | 4 - .../channel/channel_post_list/index.js | 2 +- 7 files changed, 160 insertions(+), 81 deletions(-) diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index 434553f6c..638088f4a 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -144,7 +144,7 @@ export default class ChannelDrawer extends PureComponent { this.setState({openDrawer: true}); }; - selectChannel = (id) => { + selectChannel = (channel) => { const { actions, currentChannel @@ -154,15 +154,17 @@ export default class ChannelDrawer extends PureComponent { handleSelectChannel, markChannelAsRead, setChannelLoading, + setChannelDisplayName, viewChannel } = actions; - markChannelAsRead(id, currentChannel.id); setChannelLoading(); - viewChannel(id, currentChannel.id); + markChannelAsRead(channel.id, currentChannel.id); + viewChannel(channel.id, currentChannel.id); + setChannelDisplayName(channel.display_name); this.closeChannelDrawer(); InteractionManager.runAfterInteractions(() => { - handleSelectChannel(id); + handleSelectChannel(channel.id); }); }; diff --git a/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js b/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js index 07b9b90a5..58670b4a5 100644 --- a/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js +++ b/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js @@ -42,6 +42,8 @@ class FilteredList extends Component { PropTypes.object, PropTypes.array ), + searchOrder: PropTypes.array.isRequired, + pastDirectMessages: PropTypes.array, statuses: PropTypes.object, styles: PropTypes.object.isRequired, term: PropTypes.string, @@ -50,7 +52,8 @@ class FilteredList extends Component { static defaultProps = { currentTeam: {}, - currentChannel: {} + currentChannel: {}, + pastDirectMessages: [] }; constructor(props) { @@ -116,72 +119,92 @@ class FilteredList extends Component { }); }; - buildChannelsForSearch = (props, term) => { - const data = []; - const {groupChannels, otherChannels, styles} = props; - const { - unreadChannels, - favoriteChannels, - publicChannels, - privateChannels - } = props.channels; + getSectionBuilders = () => ({ + unreads: { + builder: this.buildUnreadChannelsForSearch, + id: 'mobile.channel_list.unreads', + defaultMessage: 'UNREADS' + }, + channels: { + builder: this.buildChannelsForSearch, + id: 'sidebar.channels', + defaultMessage: 'CHANNELS' + }, + dms: { + builder: this.buildCurrentDMSForSearch, + id: 'sidebar.direct', + defaultMessage: 'DIRECT MESSAGES' + }, + members: { + builder: this.buildMembersForSearch, + id: 'mobile.channel_list.members', + defaultMessage: 'MEMBERS' + }, + nonmembers: { + builder: this.buildOtherMembersForSearch, + id: 'mobile.channel_list.not_member', + defaultMessage: 'NOT A MEMBER' + } + }); + + buildUnreadChannelsForSearch = (props, term) => { + const {unreadChannels} = props.channels; + + return this.filterChannels(unreadChannels, term); + } + + buildCurrentDMSForSearch = (props, term) => { + const {channels, teammateNameDisplay, profiles, statuses, pastDirectMessages} = props; + const {favoriteChannels} = channels; + + const favoriteDms = favoriteChannels.filter((c) => { + return c.type === General.DM_CHANNEL; + }); + + const directChannelUsers = []; + const groupChannels = []; + + channels.directAndGroupChannels.forEach((c) => { + if (c.type === General.DM_CHANNEL) { + directChannelUsers.push(profiles[c.teammate_id]); + } else { + groupChannels.push(c); + } + }); + + const pastDirectMessageUsers = pastDirectMessages.map((p) => profiles[p]).filter((p) => typeof p !== 'undefined'); + + const dms = [...directChannelUsers, ...pastDirectMessageUsers].map((u) => { + const displayName = displayUsername(u, teammateNameDisplay); - const notMemberOf = otherChannels.map((o) => { return { - ...o, + id: u.id, + status: statuses[u.id], + display_name: displayName, + username: u.username, + email: u.email, + name: displayName, + type: General.DM_CHANNEL, fake: true }; }); - const favorites = favoriteChannels.filter((c) => { - return c.type !== General.DM_CHANNEL && c.type !== General.GM_CHANNEL; + return this.filterChannels([...favoriteDms, ...dms, ...groupChannels], term).sort(sortChannelsByDisplayName.bind(null, props.intl.locale)); + } + + buildMembersForSearch = (props, term) => { + const {channels, currentUserId, teammateNameDisplay, profiles, statuses, pastDirectMessages} = props; + const {favoriteChannels, unreadChannels} = channels; + + const favoriteAndUnreadDms = [...favoriteChannels, ...unreadChannels].filter((c) => { + return c.type === General.DM_CHANNEL; }); - const unreads = this.filterChannels(unreadChannels, term); - const channels = this.filterChannels([...favorites, ...publicChannels, ...privateChannels], term). - sort(sortChannelsByDisplayName.bind(null, props.intl.locale)); + const directAndGroupChannelMembers = [...channels.directAndGroupChannels, ...favoriteAndUnreadDms].filter((c) => c.type === General.DM_CHANNEL).map((c) => c.teammate_id); - const others = this.filterChannels(notMemberOf, term); - const groups = this.filterChannels(groupChannels, term); - const fakeDms = this.filterChannels(this.buildFakeDms(props), term); - const directMessages = [...groups, ...fakeDms].sort(sortChannelsByDisplayName.bind(null, props.intl.locale)); + const userNotInDirectOrGroupChannels = Object.values(profiles).filter((u) => directAndGroupChannelMembers.indexOf(u.id) === -1 && pastDirectMessages.indexOf(u.id) === -1 && u.id !== currentUserId); - if (unreads.length) { - data.push( - this.renderTitle(styles, 'mobile.channel_list.unreads', 'UNREADS', null, false, true), - ...unreads - ); - } - - if (channels.length) { - data.push( - this.renderTitle(styles, 'sidebar.channels', 'CHANNELS', null, unreads.length > 0, true), - ...channels - ); - } - - if (others.length) { - data.push( - this.renderTitle(styles, 'mobile.channel_list.not_member', 'NOT A MEMBER', null, channels.length > 0, true), - ...others - ); - } - - if (directMessages.length) { - data.push( - this.renderTitle(styles, 'sidebar.direct', 'DIRECT MESSAGES', null, others.length > 0, true), - ...directMessages - ); - } - - return data; - }; - - buildFakeDms = (props) => { - const {currentUserId, teammateNameDisplay, profiles, statuses} = props; - const users = Object.values(profiles).filter((p) => p.id !== currentUserId); - - return users.map((u) => { + const members = userNotInDirectOrGroupChannels.map((u) => { const displayName = displayUsername(u, teammateNameDisplay); return { @@ -193,6 +216,61 @@ class FilteredList extends Component { fake: true }; }); + + const fakeDms = this.filterChannels([...members], term); + + return [...fakeDms].sort(sortChannelsByDisplayName.bind(null, props.intl.locale)); + } + + buildChannelsForSearch = (props, term) => { + const { + favoriteChannels, + publicChannels, + privateChannels + } = props.channels; + + const favorites = favoriteChannels.filter((c) => { + return c.type !== General.DM_CHANNEL && c.type !== General.GM_CHANNEL; + }); + + return this.filterChannels([...favorites, ...publicChannels, ...privateChannels], term). + sort(sortChannelsByDisplayName.bind(null, props.intl.locale)); + } + + buildOtherMembersForSearch = (props, term) => { + const {otherChannels} = props; + + const notMemberOf = otherChannels.map((o) => { + return { + ...o, + fake: true + }; + }); + + return this.filterChannels(notMemberOf, term); + } + + buildSectionsForSearch = (props, term) => { + const items = []; + const {searchOrder, styles} = props; + const sectionBuilders = this.getSectionBuilders(); + + let previousDataLength = 0; + for (const section of searchOrder) { + if (sectionBuilders.hasOwnProperty(section)) { + const sectionBuilder = sectionBuilders[section]; + const {builder, defaultMessage, id} = sectionBuilder; + const data = builder(props, term); + + if (data.length) { + const title = this.renderTitle(styles, id, defaultMessage, null, previousDataLength > 0, true); + items.push(title, ...data); + previousDataLength = data.length; + } + } + } + + return items; }; buildData = (props, term) => { @@ -200,7 +278,7 @@ class FilteredList extends Component { return null; } - return this.buildChannelsForSearch(props, term); + return this.buildSectionsForSearch(props, term); }; renderSectionAction = (styles, action) => { diff --git a/app/components/channel_drawer/channels_list/filtered_list/index.js b/app/components/channel_drawer/channels_list/filtered_list/index.js index 06b489da6..3703e4d04 100644 --- a/app/components/channel_drawer/channels_list/filtered_list/index.js +++ b/app/components/channel_drawer/channels_list/filtered_list/index.js @@ -3,6 +3,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {createSelector} from 'reselect'; import {searchChannels} from 'mattermost-redux/actions/channels'; import {searchProfiles} from 'mattermost-redux/actions/users'; @@ -11,10 +12,19 @@ import {General} from 'mattermost-redux/constants'; import {getGroupChannels, getOtherChannels} from 'mattermost-redux/selectors/entities/channels'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getProfilesInCurrentTeam, getUsers, getUserStatuses} from 'mattermost-redux/selectors/entities/users'; -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; +import {getDirectShowPreferences, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; + +import Config from 'assets/config'; import FilteredList from './filtered_list'; +const DEFAULT_SEARCH_ORDER = ['unreads', 'dms', 'channels', 'members', 'nonmembers']; + +const pastDirectMessages = createSelector( + getDirectShowPreferences, + (directChannelsFromPreferences) => directChannelsFromPreferences.filter((d) => d.value === 'false').map((d) => d.name) +); + function mapStateToProps(state, ownProps) { const {currentUserId} = state.entities.users; @@ -25,6 +35,8 @@ function mapStateToProps(state, ownProps) { profiles = getProfilesInCurrentTeam(state); } + const searchOrder = Config.DrawerSearchOrder ? Config.DrawerSearchOrder : DEFAULT_SEARCH_ORDER; + return { currentUserId, otherChannels: getOtherChannels(state), @@ -32,6 +44,8 @@ function mapStateToProps(state, ownProps) { profiles, teammateNameDisplay: getTeammateNameDisplaySetting(state), statuses: getUserStatuses(state), + searchOrder, + pastDirectMessages: pastDirectMessages(state), ...ownProps }; } diff --git a/app/components/channel_drawer/channels_list/index.js b/app/components/channel_drawer/channels_list/index.js index e5a3150e1..203c1e7a7 100644 --- a/app/components/channel_drawer/channels_list/index.js +++ b/app/components/channel_drawer/channels_list/index.js @@ -62,7 +62,7 @@ class ChannelsList extends Component { if (channel.fake) { this.props.onJoinChannel(channel); } else { - this.props.onSelectChannel(channel.id); + this.props.onSelectChannel(channel); } this.refs.search_bar.cancel(); diff --git a/app/components/channel_drawer/channels_list/list/index.js b/app/components/channel_drawer/channels_list/list/index.js index 518e87080..e0ddc0793 100644 --- a/app/components/channel_drawer/channels_list/list/index.js +++ b/app/components/channel_drawer/channels_list/list/index.js @@ -1,7 +1,6 @@ // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {General} from 'mattermost-redux/constants'; @@ -9,8 +8,6 @@ import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/ import {showCreateOption} from 'mattermost-redux/utils/channel_utils'; import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; -import {setChannelDisplayName} from 'app/actions/views/channel'; - import List from './list'; function mapStateToProps(state, ownProps) { @@ -23,12 +20,4 @@ function mapStateToProps(state, ownProps) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - setChannelDisplayName - }, dispatch) - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(List); +export default connect(mapStateToProps, null)(List); diff --git a/app/components/channel_drawer/channels_list/list/list.js b/app/components/channel_drawer/channels_list/list/list.js index 4e469820a..3c24452c7 100644 --- a/app/components/channel_drawer/channels_list/list/list.js +++ b/app/components/channel_drawer/channels_list/list/list.js @@ -24,9 +24,6 @@ import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_ class List extends Component { static propTypes = { - actions: PropTypes.shape({ - setChannelDisplayName: PropTypes.func.isRequired - }).isRequired, canCreatePrivateChannels: PropTypes.bool.isRequired, channels: PropTypes.object.isRequired, channelMembers: PropTypes.object, @@ -95,7 +92,6 @@ class List extends Component { }; onSelectChannel = (channel) => { - this.props.actions.setChannelDisplayName(channel.display_name); this.props.onSelectChannel(channel); }; diff --git a/app/screens/channel/channel_post_list/index.js b/app/screens/channel/channel_post_list/index.js index d1de17267..2f7a97998 100644 --- a/app/screens/channel/channel_post_list/index.js +++ b/app/screens/channel/channel_post_list/index.js @@ -24,7 +24,7 @@ function makeMapStateToProps() { const posts = getPostsInChannel(state, channelId) || []; return { - channelIsLoading: (getPosts.status === RequestStatus.STARTED), + channelIsLoading: getPosts.status === RequestStatus.STARTED || state.views.channel.loading, channelIsRefreshing: refreshing, currentChannelId: getCurrentChannelId(state), posts,