Improve performance of the channel drawer (#1023)
* Improve performance of the channel drawer * Feedback review * Fix makefile * Feedback review 3 * Fix areStatesEqual * Fix Android SVG
This commit is contained in:
parent
2378ad627b
commit
cc966702fd
32 changed files with 616 additions and 556 deletions
1
Makefile
1
Makefile
|
|
@ -121,6 +121,7 @@ post-install:
|
|||
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
|
||||
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
|
||||
@sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_FULL_USER);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java
|
||||
@cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier && git clone https://github.com/adamwulf/PerformanceBezier.git
|
||||
|
||||
start-packager:
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.content.Context;
|
|||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.inprogress.reactnativeyoutube.ReactNativeYouTube;
|
||||
import io.sentry.RNSentryPackage;
|
||||
import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerPackage;
|
||||
|
|
@ -23,7 +24,6 @@ import com.gnet.bottomsheet.RNBottomSheetPackage;
|
|||
import com.learnium.RNDeviceInfo.RNDeviceInfo;
|
||||
import com.psykar.cookiemanager.CookieManagerPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.BV.LinearGradient.LinearGradientPackage;
|
||||
import com.github.yamill.orientation.OrientationPackage;
|
||||
import com.reactnativenavigation.NavigationApplication;
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ include ':reactnativenotifications'
|
|||
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
|
||||
|
||||
include ':app'
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-orientation'
|
||||
project(':react-native-orientation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation/android')
|
||||
include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
|
|
|
|||
|
|
@ -12,23 +12,23 @@ import {NavigationTypes} from 'app/constants';
|
|||
|
||||
import {setChannelDisplayName} from './channel';
|
||||
|
||||
export function handleTeamChange(team, selectChannel = true) {
|
||||
export function handleTeamChange(teamId, selectChannel = true) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
if (currentTeamId === team.id) {
|
||||
if (currentTeamId === teamId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = [
|
||||
setChannelDisplayName(''),
|
||||
{type: TeamTypes.SELECT_TEAM, data: team.id}
|
||||
{type: TeamTypes.SELECT_TEAM, data: teamId}
|
||||
];
|
||||
|
||||
if (selectChannel) {
|
||||
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''});
|
||||
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[team.id] || '';
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[teamId] || '';
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
viewChannel(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
|
|
@ -45,7 +45,7 @@ export function selectFirstAvailableTeam() {
|
|||
const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0];
|
||||
|
||||
if (firstTeam) {
|
||||
handleTeamChange(firstTeam)(dispatch, getState);
|
||||
handleTeamChange(firstTeam.id)(dispatch, getState);
|
||||
} else {
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ export default class Badge extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.count) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
{...this.panResponder.panHandlers}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
BackHandler,
|
||||
|
|
@ -24,7 +24,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|||
const DRAWER_INITIAL_OFFSET = 40;
|
||||
const DRAWER_LANDSCAPE_OFFSET = 150;
|
||||
|
||||
export default class ChannelDrawer extends PureComponent {
|
||||
export default class ChannelDrawer extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getTeams: PropTypes.func.isRequired,
|
||||
|
|
@ -37,7 +37,6 @@ export default class ChannelDrawer extends PureComponent {
|
|||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
currentChannelId: PropTypes.string.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
|
|
@ -76,11 +75,11 @@ export default class ChannelDrawer extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {isLandscape, isTablet} = this.props;
|
||||
if (nextProps.isLandscape !== isLandscape || nextProps.isTablet || isTablet) {
|
||||
const {isLandscape} = this.props;
|
||||
if (nextProps.isLandscape !== isLandscape) {
|
||||
if (this.state.openDrawerOffset !== 0) {
|
||||
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
|
||||
if (nextProps.isLandscape || nextProps.isTablet) {
|
||||
if (nextProps.isLandscape || this.props.isTablet) {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
this.setState({openDrawerOffset});
|
||||
|
|
@ -88,6 +87,19 @@ export default class ChannelDrawer extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {currentTeamId, isLandscape, teamsCount} = this.props;
|
||||
const {openDrawerOffset} = this.state;
|
||||
|
||||
if (nextState.openDrawerOffset !== openDrawerOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nextProps.currentTeamId !== currentTeamId ||
|
||||
nextProps.isLandscape !== isLandscape ||
|
||||
nextProps.teamsCount !== teamsCount;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('open_channel_drawer', this.openChannelDrawer);
|
||||
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
|
||||
|
|
@ -180,10 +192,9 @@ export default class ChannelDrawer extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
selectChannel = (channel) => {
|
||||
selectChannel = (channel, currentChannelId) => {
|
||||
const {
|
||||
actions,
|
||||
currentChannelId
|
||||
actions
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
|
|
@ -194,7 +205,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
viewChannel
|
||||
} = actions;
|
||||
|
||||
setChannelLoading();
|
||||
setChannelLoading(channel.id !== currentChannelId);
|
||||
setChannelDisplayName(channel.display_name);
|
||||
|
||||
this.closeChannelDrawer();
|
||||
|
|
@ -211,7 +222,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
joinChannel = async (channel) => {
|
||||
joinChannel = async (channel, currentChannelId) => {
|
||||
const {
|
||||
actions,
|
||||
currentTeamId,
|
||||
|
|
@ -253,7 +264,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
this.selectChannel(result.data);
|
||||
this.selectChannel(result.data, currentChannelId);
|
||||
};
|
||||
|
||||
onPageSelected = (index) => {
|
||||
|
|
@ -284,13 +295,13 @@ export default class ChannelDrawer extends PureComponent {
|
|||
};
|
||||
|
||||
showTeams = () => {
|
||||
if (this.swiperIndex === 1 && this.props.teamsCount > 1) {
|
||||
if (this.drawerSwiper && this.swiperIndex === 1 && this.props.teamsCount > 1) {
|
||||
this.drawerSwiper.getWrappedInstance().showTeamsPage();
|
||||
}
|
||||
};
|
||||
|
||||
resetDrawer = () => {
|
||||
if (this.swiperIndex !== 1) {
|
||||
if (this.drawerSwiper && this.swiperIndex !== 1) {
|
||||
this.drawerSwiper.getWrappedInstance().resetPage();
|
||||
}
|
||||
};
|
||||
|
|
@ -344,6 +355,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
onShowTeams={this.showTeams}
|
||||
onSearchStart={this.onSearchStart}
|
||||
onSearchEnds={this.onSearchEnds}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -354,7 +366,6 @@ export default class ChannelDrawer extends PureComponent {
|
|||
onPageSelected={this.onPageSelected}
|
||||
openDrawerOffset={openDrawerOffset}
|
||||
showTeams={showTeams}
|
||||
theme={theme}
|
||||
>
|
||||
{lists}
|
||||
</DrawerSwiper>
|
||||
|
|
|
|||
|
|
@ -10,45 +10,62 @@ import {
|
|||
} from 'react-native';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import ChanneIcon from 'app/components/channel_icon';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import ChannelIcon from 'app/components/channel_icon';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class ChannelItem extends PureComponent {
|
||||
static propTypes = {
|
||||
channel: PropTypes.object.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
hasUnread: PropTypes.bool.isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
currentChannelId: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
isUnread: PropTypes.bool,
|
||||
mentions: PropTypes.number.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
status: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
const {channel, onSelectChannel} = this.props;
|
||||
onPress = wrapWithPreventDoubleTap(() => {
|
||||
const {channelId, currentChannelId, displayName, onSelectChannel} = this.props;
|
||||
requestAnimationFrame(() => {
|
||||
preventDoubleTap(onSelectChannel, this, channel);
|
||||
onSelectChannel({id: channelId, display_name: displayName}, currentChannelId);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
channel,
|
||||
theme,
|
||||
channelId,
|
||||
currentChannelId,
|
||||
displayName,
|
||||
isUnread,
|
||||
mentions,
|
||||
hasUnread,
|
||||
isActive
|
||||
status,
|
||||
theme,
|
||||
type
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
let activeItem;
|
||||
let activeText;
|
||||
let unreadText;
|
||||
const isActive = channelId === currentChannelId;
|
||||
|
||||
let extraItemStyle;
|
||||
let extraTextStyle;
|
||||
let extraBorder;
|
||||
|
||||
if (isActive) {
|
||||
extraItemStyle = style.itemActive;
|
||||
extraTextStyle = style.textActive;
|
||||
|
||||
extraBorder = (
|
||||
<View style={style.borderActive}/>
|
||||
);
|
||||
} else if (isUnread) {
|
||||
extraTextStyle = style.textUnread;
|
||||
}
|
||||
|
||||
let activeBorder;
|
||||
let badge;
|
||||
|
||||
if (mentions && !isActive) {
|
||||
if (mentions) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={style.badge}
|
||||
|
|
@ -61,28 +78,16 @@ export default class ChannelItem extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (hasUnread) {
|
||||
unreadText = style.textUnread;
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
activeItem = style.itemActive;
|
||||
activeText = style.textActive;
|
||||
|
||||
activeBorder = (
|
||||
<View style={style.borderActive}/>
|
||||
);
|
||||
}
|
||||
|
||||
const icon = (
|
||||
<ChanneIcon
|
||||
<ChannelIcon
|
||||
isActive={isActive}
|
||||
hasUnread={hasUnread}
|
||||
membersCount={channel.display_name.split(',').length}
|
||||
channelId={channelId}
|
||||
isUnread={isUnread}
|
||||
membersCount={displayName.split(',').length}
|
||||
size={16}
|
||||
status={channel.status}
|
||||
status={status}
|
||||
theme={theme}
|
||||
type={channel.type}
|
||||
type={type}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
@ -92,15 +97,15 @@ export default class ChannelItem extends PureComponent {
|
|||
onPress={this.onPress}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{activeBorder}
|
||||
<View style={[style.item, activeItem]}>
|
||||
{extraBorder}
|
||||
<View style={[style.item, extraItemStyle]}>
|
||||
{icon}
|
||||
<Text
|
||||
style={[style.text, unreadText, activeText]}
|
||||
style={[style.text, extraTextStyle]}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{channel.display_name}
|
||||
{displayName}
|
||||
</Text>
|
||||
{badge}
|
||||
</View>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentChannelId, makeGetChannel, getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getChannel = makeGetChannel();
|
||||
|
||||
return (state, ownProps) => {
|
||||
const channel = getChannel(state, {id: ownProps.channelId});
|
||||
let member;
|
||||
if (ownProps.isUnread) {
|
||||
member = getMyChannelMember(state, ownProps.channelId);
|
||||
}
|
||||
|
||||
return {
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
displayName: channel.display_name,
|
||||
mentions: member ? member.mention_count : 0,
|
||||
status: channel.status,
|
||||
theme: getTheme(state),
|
||||
type: channel.type
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps)(ChannelItem);
|
||||
|
|
@ -18,7 +18,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
|
||||
import FilteredList from './filtered_list';
|
||||
import List from './list';
|
||||
import SwitchTeams from './switch_teams';
|
||||
import SwitchTeamsButton from './switch_teams_button';
|
||||
|
||||
class ChannelsList extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -45,14 +45,16 @@ class ChannelsList extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
onSelectChannel = (channel, currentChannelId) => {
|
||||
if (channel.fake) {
|
||||
this.props.onJoinChannel(channel);
|
||||
this.props.onJoinChannel(channel, currentChannelId);
|
||||
} else {
|
||||
this.props.onSelectChannel(channel);
|
||||
this.props.onSelectChannel(channel, currentChannelId);
|
||||
}
|
||||
|
||||
this.refs.search_bar.cancel();
|
||||
if (this.refs.search_bar) {
|
||||
this.refs.search_bar.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
openSettingsModal = wrapWithPreventDoubleTap(() => {
|
||||
|
|
@ -171,9 +173,9 @@ class ChannelsList extends React.PureComponent {
|
|||
>
|
||||
<View style={styles.statusBar}>
|
||||
<View style={styles.headerContainer}>
|
||||
<SwitchTeams
|
||||
<SwitchTeamsButton
|
||||
searching={searching}
|
||||
showTeams={onShowTeams}
|
||||
onShowTeams={onShowTeams}
|
||||
/>
|
||||
{title}
|
||||
{settings}
|
||||
|
|
@ -288,15 +290,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
above: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
top: 9
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mentionColor,
|
||||
fontSize: 14,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
|||
|
||||
import ChannelsList from './channels_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getChannelsWithUnreadSection, getCurrentChannel, getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {
|
||||
getSortedUnreadChannelIds,
|
||||
getSortedFavoriteChannelIds,
|
||||
getSortedPublicChannelIds,
|
||||
getSortedPrivateChannelIds,
|
||||
getSortedDirectChannelIds
|
||||
} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
|
||||
|
|
@ -12,18 +18,33 @@ import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
|||
|
||||
import List from './list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
const unreadChannelIds = getSortedUnreadChannelIds(state);
|
||||
const favoriteChannelIds = getSortedFavoriteChannelIds(state);
|
||||
const publicChannelIds = getSortedPublicChannelIds(state);
|
||||
const privateChannelIds = getSortedPrivateChannelIds(state);
|
||||
const directChannelIds = getSortedDirectChannelIds(state);
|
||||
|
||||
return {
|
||||
canCreatePrivateChannels: showCreateOption(config, license, General.PRIVATE_CHANNEL, isAdmin(roles), isSystemAdmin(roles)),
|
||||
channelMembers: getMyChannelMemberships(state),
|
||||
channels: getChannelsWithUnreadSection(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
unreadChannelIds,
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
directChannelIds,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(List);
|
||||
function areStatesEqual(next, prev) {
|
||||
const equalRoles = getCurrentUserRoles(prev) === getCurrentUserRoles(next);
|
||||
const equalChannels = next.entities.channels === prev.entities.channels;
|
||||
const equalConfig = next.entities.general.config === prev.entities.general.config;
|
||||
const equalUsers = next.entities.users.profiles === prev.entities.users.profiles;
|
||||
|
||||
return equalChannels && equalConfig && equalRoles && equalUsers;
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, null, {pure: true, areStatesEqual})(List);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import React, {Component} from 'react';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
SectionList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
|
|
@ -13,38 +12,36 @@ import {
|
|||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
||||
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
|
||||
import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
class List extends Component {
|
||||
class List extends PureComponent {
|
||||
static propTypes = {
|
||||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
channels: PropTypes.object.isRequired,
|
||||
channelMembers: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
directChannelIds: PropTypes.array.isRequired,
|
||||
favoriteChannelIds: PropTypes.array.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
publicChannelIds: PropTypes.array.isRequired,
|
||||
privateChannelIds: PropTypes.array.isRequired,
|
||||
styles: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentChannel: {}
|
||||
theme: PropTypes.object.isRequired,
|
||||
unreadChannelIds: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.firstUnreadChannel = null;
|
||||
|
||||
this.state = {
|
||||
dataSource: this.buildData(props),
|
||||
showAbove: false
|
||||
sections: this.buildSections(props),
|
||||
showIndicator: false,
|
||||
width: 0
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
|
|
@ -52,106 +49,96 @@ class List extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !deepEqual(this.props, nextProps, {strict: true}) || !deepEqual(this.state, nextState, {strict: true});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
dataSource: this.buildData(nextProps)
|
||||
}, () => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list.recordInteraction();
|
||||
this.updateUnreadIndicators({
|
||||
viewableItems: Array.from(this.refs.list._listRef._viewabilityHelper._viewableItems.values()) //eslint-disable-line
|
||||
});
|
||||
}
|
||||
});
|
||||
const {
|
||||
canCreatePrivateChannels,
|
||||
directChannelIds,
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
unreadChannelIds
|
||||
} = this.props;
|
||||
|
||||
if (nextProps.canCreatePrivateChannels !== canCreatePrivateChannels ||
|
||||
nextProps.directChannelIds !== directChannelIds || nextProps.favoriteChannelIds !== favoriteChannelIds ||
|
||||
nextProps.publicChannelIds !== publicChannelIds || nextProps.privateChannelIds !== privateChannelIds ||
|
||||
nextProps.unreadChannelIds !== unreadChannelIds) {
|
||||
this.setState({sections: this.buildSections(nextProps)});
|
||||
}
|
||||
}
|
||||
|
||||
updateUnreadIndicators = ({viewableItems}) => {
|
||||
let showAbove = false;
|
||||
const visibleIndexes = viewableItems.map((v) => v.index);
|
||||
|
||||
if (visibleIndexes.length) {
|
||||
const {dataSource} = this.state;
|
||||
const firstVisible = parseInt(visibleIndexes[0], 10);
|
||||
|
||||
if (this.firstUnreadChannel) {
|
||||
const index = dataSource.findIndex((item) => {
|
||||
return item.display_name === this.firstUnreadChannel;
|
||||
});
|
||||
showAbove = index < firstVisible;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showAbove
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.sections !== this.state.sections && this.refs.list) {
|
||||
this.refs.list.recordInteraction();
|
||||
this.updateUnreadIndicators({
|
||||
viewableItems: Array.from(this.refs.list._wrapperListRef._listRef._viewabilityHelper._viewableItems.values()) //eslint-disable-line
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
this.props.onSelectChannel(channel);
|
||||
};
|
||||
buildSections = (props) => {
|
||||
const {
|
||||
canCreatePrivateChannels,
|
||||
directChannelIds,
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
unreadChannelIds
|
||||
} = props;
|
||||
const sections = [];
|
||||
|
||||
onLayout = (event) => {
|
||||
const {width} = event.nativeEvent.layout;
|
||||
this.width = width;
|
||||
};
|
||||
|
||||
getUnreadMessages = (channel) => {
|
||||
const member = this.props.channelMembers[channel.id];
|
||||
let mentions = 0;
|
||||
let unreadCount = 0;
|
||||
if (member && channel) {
|
||||
mentions = member.mention_count;
|
||||
unreadCount = channel.total_msg_count - member.msg_count;
|
||||
|
||||
if (member.notify_props && member.notify_props.mark_unread === General.MENTION) {
|
||||
unreadCount = 0;
|
||||
}
|
||||
if (unreadChannelIds.length) {
|
||||
sections.push({
|
||||
id: 'mobile.channel_list.unreads',
|
||||
defaultMessage: 'UNREADS',
|
||||
data: unreadChannelIds,
|
||||
renderItem: this.renderUnreadItem,
|
||||
topSeparator: false,
|
||||
bottomSeparator: true
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
mentions,
|
||||
unreadCount
|
||||
};
|
||||
};
|
||||
if (favoriteChannelIds.length) {
|
||||
sections.push({
|
||||
id: 'sidebar.favorite',
|
||||
defaultMessage: 'FAVORITES',
|
||||
data: favoriteChannelIds,
|
||||
topSeparator: unreadChannelIds.length > 0,
|
||||
bottomSeparator: true
|
||||
});
|
||||
}
|
||||
|
||||
findUnreadChannels = (data) => {
|
||||
data.forEach((c) => {
|
||||
if (c.id) {
|
||||
const {mentions, unreadCount} = this.getUnreadMessages(c);
|
||||
const unread = (mentions + unreadCount) > 0;
|
||||
|
||||
if (unread && c.id !== this.props.currentChannel.id) {
|
||||
if (!this.firstUnreadChannel) {
|
||||
this.firstUnreadChannel = c.display_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.push({
|
||||
action: this.goToMoreChannels,
|
||||
id: 'sidebar.channels',
|
||||
defaultMessage: 'PUBLIC CHANNELS',
|
||||
data: publicChannelIds,
|
||||
topSeparator: favoriteChannelIds.length > 0 || unreadChannelIds.length > 0,
|
||||
bottomSeparator: publicChannelIds.length > 0
|
||||
});
|
||||
|
||||
sections.push({
|
||||
action: canCreatePrivateChannels ? this.goToCreatePrivateChannel : null,
|
||||
id: 'sidebar.pg',
|
||||
defaultMessage: 'PRIVATE CHANNELS',
|
||||
data: privateChannelIds,
|
||||
topSeparator: true,
|
||||
bottomSeparator: privateChannelIds.length > 0
|
||||
});
|
||||
|
||||
sections.push({
|
||||
action: this.goToDirectMessages,
|
||||
id: 'sidebar.direct',
|
||||
defaultMessage: 'DIRECT MESSAGES',
|
||||
data: directChannelIds,
|
||||
topSeparator: true,
|
||||
bottomSeparator: false
|
||||
});
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
createChannelElement = (channel) => {
|
||||
const {mentions, unreadCount} = this.getUnreadMessages(channel);
|
||||
const msgCount = mentions + unreadCount;
|
||||
const unread = msgCount > 0;
|
||||
|
||||
return (
|
||||
<ChannelItem
|
||||
ref={channel.id}
|
||||
channel={channel}
|
||||
hasUnread={unread}
|
||||
mentions={mentions}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
isActive={channel.isCurrent}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
createPrivateChannel = wrapWithPreventDoubleTap(() => {
|
||||
goToCreatePrivateChannel = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
|
|
@ -173,75 +160,7 @@ class List extends Component {
|
|||
});
|
||||
});
|
||||
|
||||
buildChannels = (props) => {
|
||||
const {canCreatePrivateChannels, styles} = props;
|
||||
const {
|
||||
unreadChannels,
|
||||
favoriteChannels,
|
||||
publicChannels,
|
||||
privateChannels,
|
||||
directAndGroupChannels
|
||||
} = props.channels;
|
||||
|
||||
const data = [];
|
||||
|
||||
if (unreadChannels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'mobile.channel_list.unreads', 'UNREADS', null, false, true),
|
||||
...unreadChannels
|
||||
);
|
||||
}
|
||||
|
||||
if (favoriteChannels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.favorite', 'FAVORITES', null, unreadChannels.length > 0, true),
|
||||
...favoriteChannels
|
||||
);
|
||||
}
|
||||
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.channels', 'CHANNELS', this.showMoreChannelsModal, favoriteChannels.length > 0, publicChannels.length > 0),
|
||||
...publicChannels
|
||||
);
|
||||
|
||||
let createPrivateChannel;
|
||||
if (canCreatePrivateChannels) {
|
||||
createPrivateChannel = this.createPrivateChannel;
|
||||
}
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.pg', 'PRIVATE CHANNELS', createPrivateChannel, true, privateChannels.length > 0),
|
||||
...privateChannels
|
||||
);
|
||||
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.direct', 'DIRECT MESSAGES', this.showDirectMessagesModal, true, directAndGroupChannels.length > 0),
|
||||
...directAndGroupChannels
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
buildData = (props) => {
|
||||
if (!props.currentChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = this.buildChannels(props);
|
||||
this.firstUnreadChannel = null;
|
||||
this.findUnreadChannels(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
scrollToTop = () => {
|
||||
this.refs.list.scrollToOffset({
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: true
|
||||
});
|
||||
}
|
||||
|
||||
showDirectMessagesModal = wrapWithPreventDoubleTap(() => {
|
||||
goToDirectMessages = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
|
|
@ -265,7 +184,7 @@ class List extends Component {
|
|||
});
|
||||
});
|
||||
|
||||
showMoreChannelsModal = wrapWithPreventDoubleTap(() => {
|
||||
goToMoreChannels = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
|
|
@ -286,6 +205,18 @@ class List extends Component {
|
|||
});
|
||||
});
|
||||
|
||||
keyExtractor = (item) => item.id || item;
|
||||
|
||||
onSelectChannel = (channel, currentChannelId) => {
|
||||
const {onSelectChannel} = this.props;
|
||||
onSelectChannel(channel, currentChannelId);
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
const {width} = event.nativeEvent.layout;
|
||||
this.setState({width: width - 40});
|
||||
};
|
||||
|
||||
renderSectionAction = (styles, action) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
|
|
@ -302,83 +233,115 @@ class List extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderDivider = (styles, marginLeft) => {
|
||||
renderSectionSeparator = () => {
|
||||
const {styles} = this.props;
|
||||
return (
|
||||
<View
|
||||
style={[styles.divider, {marginLeft}]}
|
||||
/>
|
||||
<View style={[styles.divider]}/>
|
||||
);
|
||||
};
|
||||
|
||||
renderItem = ({item}) => {
|
||||
if (!item.isTitle) {
|
||||
return this.createChannelElement(item);
|
||||
}
|
||||
return item.title;
|
||||
return (
|
||||
<ChannelItem
|
||||
channelId={item}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderTitle = (styles, id, defaultMessage, action, topDivider, bottomDivider) => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
renderUnreadItem = ({item}) => {
|
||||
return (
|
||||
<ChannelItem
|
||||
channelId={item}
|
||||
isUnread={true}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {intl, styles} = this.props;
|
||||
const {
|
||||
action,
|
||||
bottomSeparator,
|
||||
defaultMessage,
|
||||
id,
|
||||
isTitle: true,
|
||||
title: (
|
||||
<View>
|
||||
{topDivider && this.renderDivider(styles, 0)}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
</View>
|
||||
{bottomDivider && this.renderDivider(styles, 0)}
|
||||
topSeparator
|
||||
} = section;
|
||||
|
||||
return (
|
||||
<View>
|
||||
{topSeparator && this.renderSectionSeparator()}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
</View>
|
||||
)
|
||||
};
|
||||
{bottomSeparator && this.renderSectionSeparator()}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
scrollToTop = () => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list.scrollToOffset({
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
emitUnreadIndicatorChange = debounce((showIndicator) => {
|
||||
this.setState({showIndicator});
|
||||
}, 100);
|
||||
|
||||
updateUnreadIndicators = ({viewableItems}) => {
|
||||
const {unreadChannelIds} = this.props;
|
||||
const firstUnread = unreadChannelIds[0];
|
||||
if (firstUnread) {
|
||||
const isVisible = viewableItems.find((v) => v.item === firstUnread);
|
||||
|
||||
if (isVisible) {
|
||||
return this.emitUnreadIndicatorChange(false);
|
||||
}
|
||||
return this.emitUnreadIndicatorChange(true);
|
||||
}
|
||||
|
||||
return this.emitUnreadIndicatorChange(false);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {styles} = this.props;
|
||||
|
||||
const {dataSource, showAbove} = this.state;
|
||||
|
||||
let above;
|
||||
if (showAbove) {
|
||||
above = (
|
||||
<UnreadIndicator
|
||||
style={[styles.above, {width: (this.width - 40)}]}
|
||||
onPress={this.scrollToTop}
|
||||
text={(
|
||||
<FormattedText
|
||||
style={styles.indicatorText}
|
||||
id='sidebar.unreadAbove'
|
||||
defaultMessage='Unread post(s) above'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const {styles, theme} = this.props;
|
||||
const {sections, width, showIndicator} = this.state;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
onLayout={this.onLayout}
|
||||
>
|
||||
<FlatList
|
||||
<SectionList
|
||||
ref='list'
|
||||
data={dataSource}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyExtractor={this.keyExtractor}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
stickySectionHeadersEnabled={false}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
waitForInteraction: false
|
||||
}}
|
||||
/>
|
||||
{above}
|
||||
<UnreadIndicator
|
||||
show={showIndicator}
|
||||
style={[styles.above, {width}]}
|
||||
onPress={this.scrollToTop}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import SwitchTeams from './switch_teams';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
currentTeam: getCurrentTeam(state),
|
||||
teamMembers: getTeamMemberships(state),
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SwitchTeams);
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getMyTeamsCount, getChannelDrawerBadgeCount} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import SwitchTeamsButton from './switch_teams_button';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const team = getCurrentTeam(state);
|
||||
|
||||
return {
|
||||
currentTeamId: team.id,
|
||||
displayName: team.display_name,
|
||||
mentionCount: getChannelDrawerBadgeCount(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SwitchTeamsButton);
|
||||
|
|
@ -14,96 +14,50 @@ import Badge from 'app/components/badge';
|
|||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class SwitchTeams extends React.PureComponent {
|
||||
export default class SwitchTeamsButton extends React.PureComponent {
|
||||
static propTypes = {
|
||||
currentTeam: PropTypes.object,
|
||||
currentTeamId: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
searching: PropTypes.bool.isRequired,
|
||||
showTeams: PropTypes.func.isRequired,
|
||||
teamMembers: PropTypes.object.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
mentionCount: PropTypes.number.isRequired,
|
||||
teamsCount: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
badgeCount: this.getBadgeCount(props)
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.currentTeam !== this.props.currentTeam || nextProps.teamMembers !== this.props.teamMembers) {
|
||||
this.setState({
|
||||
badgeCount: this.getBadgeCount(nextProps)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getBadgeCount = (props) => {
|
||||
const {
|
||||
currentTeam,
|
||||
teamMembers
|
||||
} = props;
|
||||
|
||||
let mentionCount = 0;
|
||||
let messageCount = 0;
|
||||
Object.values(teamMembers).forEach((m) => {
|
||||
if (m.team_id !== currentTeam.id) {
|
||||
mentionCount = mentionCount + (m.mention_count || 0);
|
||||
messageCount = messageCount + (m.msg_count || 0);
|
||||
}
|
||||
});
|
||||
|
||||
let badgeCount;
|
||||
if (mentionCount) {
|
||||
badgeCount = mentionCount;
|
||||
} else if (messageCount) {
|
||||
badgeCount = -1;
|
||||
} else {
|
||||
badgeCount = 0;
|
||||
}
|
||||
|
||||
return badgeCount;
|
||||
};
|
||||
|
||||
showTeams = wrapWithPreventDoubleTap(() => {
|
||||
this.props.showTeams();
|
||||
this.props.onShowTeams();
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentTeam,
|
||||
currentTeamId,
|
||||
displayName,
|
||||
mentionCount,
|
||||
searching,
|
||||
teamMembers,
|
||||
teamsCount,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!currentTeam) {
|
||||
if (!currentTeamId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
badgeCount
|
||||
} = this.state;
|
||||
|
||||
if (searching || Object.keys(teamMembers).length < 2) {
|
||||
if (searching || teamsCount < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let badge;
|
||||
if (badgeCount) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={badgeCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={mentionCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
|
|
@ -119,7 +73,7 @@ export default class SwitchTeams extends React.PureComponent {
|
|||
/>
|
||||
<View style={styles.switcherDivider}/>
|
||||
<Text style={styles.switcherTeam}>
|
||||
{currentTeam.display_name.substr(0, 2).toUpperCase()}
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewPropTypes
|
||||
} from 'react-native';
|
||||
|
||||
export default class UnreadIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
style: ViewPropTypes.style,
|
||||
textStyle: Text.propTypes.style,
|
||||
text: PropTypes.node.isRequired,
|
||||
onPress: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPress: () => true
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this.props.onPress}>
|
||||
<View
|
||||
style={[Styles.container, this.props.style]}
|
||||
>
|
||||
{this.props.text}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
borderRadius: 15,
|
||||
marginHorizontal: 15,
|
||||
height: 25
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import Svg, {
|
||||
G,
|
||||
Path
|
||||
} from 'react-native-svg';
|
||||
|
||||
export default class AboveIcon extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const {color, height, width} = this.props;
|
||||
|
||||
return (
|
||||
<View style={[style.container, {height, width}]}>
|
||||
<Svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox='0 0 10 10'
|
||||
>
|
||||
<G transform='matrix(1,0,0,1,-20,-18)'>
|
||||
<G transform='matrix(0.0330723,0,0,0.0322634,15.8132,12.3164)'>
|
||||
<Path
|
||||
d='M245.803,377.493C245.803,377.493 204.794,336.485 179.398,311.088C168.55,300.24 150.962,300.24 140.114,311.088C138.327,312.875 136.517,314.686 134.73,316.473C123.882,327.321 123.882,344.908 134.73,355.756C167.972,388.998 233.949,454.975 256.949,477.975C262.158,483.184 269.223,486.111 276.591,486.111C277.38,486.111 278.176,486.111 278.965,486.111C286.332,486.111 293.397,483.184 298.607,477.975C321.607,454.975 387.584,388.998 420.826,355.756C431.674,344.908 431.674,327.321 420.826,316.473C419.039,314.686 417.228,312.875 415.441,311.088C404.593,300.24 387.005,300.24 376.158,311.088C350.761,336.485 309.753,377.493 309.753,377.493C309.753,377.493 309.753,279.687 309.753,203.94C309.753,196.573 306.826,189.508 301.617,184.298C296.408,179.089 289.342,176.162 281.975,176.162C279.191,176.162 276.364,176.162 273.58,176.162C266.213,176.162 259.148,179.089 253.939,184.298C248.729,189.508 245.803,196.573 245.803,203.94L245.803,377.493Z'
|
||||
fill={color}
|
||||
/>
|
||||
</G>
|
||||
</G>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'flex-start',
|
||||
transform: [{rotate: '180deg'}]
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewPropTypes
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import AboveIcon from './above_icon';
|
||||
|
||||
export default class UnreadIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
show: PropTypes.bool,
|
||||
style: ViewPropTypes.style,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPress: () => true
|
||||
};
|
||||
|
||||
render() {
|
||||
const {onPress, show, theme} = this.props;
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={onPress}>
|
||||
<View
|
||||
style={[style.container, this.props.style]}
|
||||
>
|
||||
<FormattedText
|
||||
style={style.indicatorText}
|
||||
id='sidebar.unreads'
|
||||
defaultMessage='More unreads'
|
||||
/>
|
||||
<AboveIcon
|
||||
width={12}
|
||||
height={12}
|
||||
color={theme.mentionColor}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
borderRadius: 15,
|
||||
marginHorizontal: 15,
|
||||
height: 25
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mentionColor,
|
||||
fontSize: 14,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import {changeOpacity} from 'app/utils/theme';
|
|||
|
||||
import Swiper from 'app/components/swiper';
|
||||
|
||||
export default class DrawerSwiper extends PureComponent {
|
||||
export default class DrawerSwiper extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
|
|
@ -24,16 +24,28 @@ export default class DrawerSwiper extends PureComponent {
|
|||
openDrawerOffset: 0
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const {deviceWidth, showTeams, theme} = this.props;
|
||||
return nextProps.deviceWidth !== deviceWidth ||
|
||||
nextProps.showTeams !== showTeams || nextProps.theme !== theme;
|
||||
}
|
||||
|
||||
runOnLayout = (shouldRun = true) => {
|
||||
this.refs.swiper.runOnLayout = shouldRun;
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.runOnLayout = shouldRun;
|
||||
}
|
||||
};
|
||||
|
||||
resetPage = () => {
|
||||
this.refs.swiper.scrollToIndex(1, false);
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.scrollToIndex(1, false);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToStart = () => {
|
||||
this.refs.swiper.scrollToStart();
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.scrollToStart();
|
||||
}
|
||||
};
|
||||
|
||||
swiperPageSelected = (index) => {
|
||||
|
|
@ -41,7 +53,9 @@ export default class DrawerSwiper extends PureComponent {
|
|||
};
|
||||
|
||||
showTeamsPage = () => {
|
||||
this.refs.swiper.scrollToIndex(0, true);
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.scrollToIndex(0, true);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getDimensions, isLandscape} from 'app/selectors/device';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
import DraweSwiper from './drawer_swiper';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...ownProps,
|
||||
...getDimensions(state),
|
||||
isLandscape: isLandscape(state)
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {joinChannel, viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {getTeams} from 'mattermost-redux/actions/teams';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {handleSelectChannel, setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel';
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
|
|
@ -21,11 +20,10 @@ function mapStateToProps(state) {
|
|||
|
||||
return {
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
currentUserId,
|
||||
isLandscape: isLandscape(state),
|
||||
isTablet: isTablet(state),
|
||||
teamsCount: Object.keys(getTeamMemberships(state)).length,
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,25 @@ import {bindActionCreators} from 'redux';
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentTeamId, getJoinableTeams} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTeamId, getJoinableTeamIds, getMySortedTeamIds} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getMySortedTeams} from 'app/selectors/teams';
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
import TeamsList from './teams_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
const currentUser = getCurrentUser(state);
|
||||
const locale = currentUser ? currentUser.locale : 'en';
|
||||
|
||||
return {
|
||||
joinableTeams: getJoinableTeams(state),
|
||||
canJoinOtherTeams: getJoinableTeamIds(state).length > 0,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentUrl: removeProtocol(getCurrentUrl(state)),
|
||||
teams: getMySortedTeams(state),
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
teamIds: getMySortedTeamIds(state, locale),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ class TeamsList extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
handleTeamChange: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
canJoinOtherTeams: PropTypes.bool.isRequired,
|
||||
closeChannelDrawer: PropTypes.func.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
joinableTeams: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
teams: PropTypes.array.isRequired,
|
||||
teamIds: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -42,11 +42,11 @@ class TeamsList extends PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
selectTeam = (team) => {
|
||||
selectTeam = (teamId) => {
|
||||
requestAnimationFrame(() => {
|
||||
const {actions, closeChannelDrawer, currentTeamId} = this.props;
|
||||
if (team.id !== currentTeamId) {
|
||||
actions.handleTeamChange(team);
|
||||
if (teamId !== currentTeamId) {
|
||||
actions.handleTeamChange(teamId);
|
||||
}
|
||||
|
||||
closeChannelDrawer();
|
||||
|
|
@ -81,25 +81,25 @@ class TeamsList extends PureComponent {
|
|||
});
|
||||
});
|
||||
|
||||
keyExtractor = (team) => {
|
||||
return team.id;
|
||||
}
|
||||
keyExtractor = (item) => {
|
||||
return item;
|
||||
};
|
||||
|
||||
renderItem = ({item}) => {
|
||||
return (
|
||||
<TeamsListItem
|
||||
selectTeam={this.selectTeam}
|
||||
team={item}
|
||||
teamId={item}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {joinableTeams, teams, theme} = this.props;
|
||||
const {canJoinOtherTeams, teamIds, theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let moreAction;
|
||||
if (Object.keys(joinableTeams).length) {
|
||||
if (canJoinOtherTeams) {
|
||||
moreAction = (
|
||||
<TouchableHighlight
|
||||
style={styles.moreActionContainer}
|
||||
|
|
@ -128,7 +128,7 @@ class TeamsList extends PureComponent {
|
|||
</View>
|
||||
</View>
|
||||
<FlatList
|
||||
data={teams}
|
||||
data={teamIds}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
viewabilityConfig={{
|
||||
|
|
|
|||
|
|
@ -5,19 +5,23 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTeamId, getTeam, makeGetBadgeCountForTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
import TeamsListItem from './teams_list_item.js';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const team = getTeam(state, ownProps.teamId);
|
||||
const getMentionCount = makeGetBadgeCountForTeamId();
|
||||
|
||||
return {
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentUrl: removeProtocol(getCurrentUrl(state)),
|
||||
teamMember: getTeamMemberships(state)[ownProps.team.id],
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
displayName: team.display_name,
|
||||
mentionCount: getMentionCount(state, ownProps.teamId),
|
||||
name: team.name,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,29 +18,32 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
static propTypes = {
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
mentionCount: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
selectTeam: PropTypes.func.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
teamMember: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
selectTeam = wrapWithPreventDoubleTap(() => {
|
||||
this.props.selectTeam(this.props.team);
|
||||
this.props.selectTeam(this.props.teamId);
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentTeamId,
|
||||
currentUrl,
|
||||
team,
|
||||
teamMember,
|
||||
displayName,
|
||||
mentionCount,
|
||||
name,
|
||||
teamId,
|
||||
theme
|
||||
} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let current;
|
||||
let badge;
|
||||
if (team.id === currentTeamId) {
|
||||
if (teamId === currentTeamId) {
|
||||
current = (
|
||||
<View style={styles.checkmarkContainer}>
|
||||
<IonIcon
|
||||
|
|
@ -51,24 +54,15 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
let badgeCount = 0;
|
||||
if (teamMember.mention_count) {
|
||||
badgeCount = teamMember.mention_count;
|
||||
} else if (teamMember.msg_count) {
|
||||
badgeCount = -1;
|
||||
}
|
||||
|
||||
if (badgeCount) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={badgeCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={mentionCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.teamWrapper}>
|
||||
|
|
@ -79,7 +73,7 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
<View style={styles.teamContainer}>
|
||||
<View style={styles.teamIconContainer}>
|
||||
<Text style={styles.teamIcon}>
|
||||
{team.display_name.substr(0, 2).toUpperCase()}
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.teamNameContainer}>
|
||||
|
|
@ -88,14 +82,14 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
ellipsizeMode='tail'
|
||||
style={styles.teamName}
|
||||
>
|
||||
{team.display_name}
|
||||
{displayName}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode='tail'
|
||||
style={styles.teamUrl}
|
||||
>
|
||||
{`${currentUrl}/${team.name}`}
|
||||
{`${currentUrl}/${name}`}
|
||||
</Text>
|
||||
</View>
|
||||
{current}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
static propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
isInfo: PropTypes.bool,
|
||||
hasUnread: PropTypes.bool,
|
||||
isUnread: PropTypes.bool,
|
||||
membersCount: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
|
|
@ -30,12 +30,12 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
static defaultProps = {
|
||||
isActive: false,
|
||||
isInfo: false,
|
||||
hasUnread: false,
|
||||
isUnread: false,
|
||||
size: 12
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isActive, hasUnread, isInfo, membersCount, size, status, theme, type} = this.props;
|
||||
const {isActive, isUnread, isInfo, membersCount, size, status, theme, type} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let activeIcon;
|
||||
|
|
@ -46,7 +46,7 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
let unreadGroup;
|
||||
let offlineColor = changeOpacity(theme.sidebarText, 0.5);
|
||||
|
||||
if (hasUnread) {
|
||||
if (isUnread) {
|
||||
unreadIcon = style.iconUnread;
|
||||
unreadGroupBox = style.groupBoxUnread;
|
||||
unreadGroup = style.groupUnread;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {General} from 'mattermost-redux/constants';
|
|||
import {
|
||||
getCurrentChannel,
|
||||
getCurrentChannelStats,
|
||||
getChannelsByCategory,
|
||||
getSortedFavoriteChannelIds,
|
||||
canManageChannelMembers
|
||||
} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId, getUser, getStatusForUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
|
|
@ -25,7 +25,7 @@ import {isAdmin, isChannelAdmin, isSystemAdmin} from 'mattermost-redux/utils/use
|
|||
|
||||
import ChannelInfo from './channel_info';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
const {config, license} = state.entities.general;
|
||||
const currentChannel = getCurrentChannel(state) || {};
|
||||
const currentChannelCreator = getUser(state, currentChannel.creator_id);
|
||||
|
|
@ -33,7 +33,7 @@ function mapStateToProps(state, ownProps) {
|
|||
const currentChannelStats = getCurrentChannelStats(state);
|
||||
const currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const favoriteChannels = getChannelsByCategory(state).favoriteChannels.map((f) => f.id);
|
||||
const favoriteChannels = getSortedFavoriteChannelIds(state);
|
||||
const isCurrent = currentChannel.id === state.entities.channels.currentChannelId;
|
||||
const isFavorite = favoriteChannels.indexOf(currentChannel.id) > -1;
|
||||
const roles = getCurrentUserRoles(state);
|
||||
|
|
@ -46,7 +46,6 @@ function mapStateToProps(state, ownProps) {
|
|||
}
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
canDeleteChannel: showDeleteOption(config, license, currentChannel, isAdmin(roles), isSystemAdmin(roles), isChannelAdmin(roles)),
|
||||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default class LoadTeam extends PureComponent {
|
|||
|
||||
onSelectTeam(team) {
|
||||
const {handleTeamChange} = this.props.actions;
|
||||
handleTeamChange(team).then(this.goToChannelView);
|
||||
handleTeamChange(team.id).then(this.goToChannelView);
|
||||
}
|
||||
|
||||
goToChannelView = () => {
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export default class SelectTeam extends PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
handleTeamChange(team);
|
||||
handleTeamChange(team.id);
|
||||
|
||||
if (userWithoutTeams) {
|
||||
this.goToChannelView();
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {getMyTeams} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
export const getMySortedTeams = createSelector(
|
||||
getMyTeams,
|
||||
getCurrentUser,
|
||||
(teams, currentUser) => {
|
||||
const locale = currentUser.locale;
|
||||
|
||||
return teams.sort((a, b) => {
|
||||
if (a.display_name !== b.display_name) {
|
||||
return a.display_name.toLowerCase().localeCompare(b.display_name.toLowerCase(), locale, {numeric: true});
|
||||
}
|
||||
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, {numeric: true});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
"react-native-passcode-status": "1.1.0",
|
||||
"react-native-sentry": "0.15.1",
|
||||
"react-native-status-bar-size": "0.3.2",
|
||||
"react-native-svg": "5.4.1",
|
||||
"react-native-svg": "react-native-community/react-native-svg#be8797be5239085d5d09eec90a926ae8da7a8583",
|
||||
"react-native-tooltip": "enahum/react-native-tooltip",
|
||||
"react-native-vector-icons": "4.3.0",
|
||||
"react-native-youtube": "1.0.0-beta.3",
|
||||
|
|
@ -51,6 +51,7 @@
|
|||
"redux-thunk": "2.2.0",
|
||||
"reselect": "3.0.1",
|
||||
"semver": "5.4.1",
|
||||
"shallow-equals": "1.0.0",
|
||||
"socketcluster": "5.0.4",
|
||||
"tinycolor2": "1.4.1",
|
||||
"url-parse": "1.1.9",
|
||||
|
|
|
|||
13
yarn.lock
13
yarn.lock
|
|
@ -3878,7 +3878,7 @@ makeerror@1.0.x:
|
|||
|
||||
mattermost-redux@mattermost/mattermost-redux#master:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/9042b154c85f65b4e91b30bf2bc612048e3d194b"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/1f64d99a0df42cf6513c12be6af965065eb1374f"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
harmony-reflect "1.5.1"
|
||||
|
|
@ -3892,6 +3892,7 @@ mattermost-redux@mattermost/mattermost-redux#master:
|
|||
redux-thunk "2.2.0"
|
||||
reselect "3.0.1"
|
||||
serialize-error "2.1.0"
|
||||
shallow-equals "1.0.0"
|
||||
|
||||
md5-hex@^1.2.0:
|
||||
version "1.3.0"
|
||||
|
|
@ -5004,9 +5005,9 @@ react-native-svg-mock@1.0.2:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg-mock/-/react-native-svg-mock-1.0.2.tgz#2dc35fb97ca1e0ea393ece6d23782ba4f58e178f"
|
||||
|
||||
react-native-svg@5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-5.4.1.tgz#c46191c786adbe9d5007342b4279efd153db8839"
|
||||
react-native-svg@react-native-community/react-native-svg#be8797be5239085d5d09eec90a926ae8da7a8583:
|
||||
version "6.0.0-rc1"
|
||||
resolved "https://codeload.github.com/react-native-community/react-native-svg/tar.gz/be8797be5239085d5d09eec90a926ae8da7a8583"
|
||||
dependencies:
|
||||
color "^0.11.1"
|
||||
lodash "^4.16.6"
|
||||
|
|
@ -5819,6 +5820,10 @@ setprototypeof@1.0.3:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
|
||||
|
||||
shallow-equals@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shallow-equals/-/shallow-equals-1.0.0.tgz#24b74bf1c634c11ed4c7182a6df6fb300dce4390"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
|
|
|
|||
Loading…
Reference in a new issue