MM-34407 Use Avatars for Sidebar DMs (#5334) (#5358)

* MM-34407 Use Avatars for Sidebar DMs

* Update app/components/channel_icon.js

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

* UI feedback

* Align the private channel icon

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
(cherry picked from commit 1b36529853)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Mattermost Build 2021-04-29 15:18:48 +02:00 committed by GitHub
parent 562d3ac9ad
commit a60d3eeb92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 610 additions and 624 deletions

View file

@ -1,236 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
View,
} from 'react-native';
import {General} from '@mm-redux/constants';
import CompassIcon from '@components/compass_icon';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class ChannelIcon extends React.PureComponent {
static propTypes = {
isActive: PropTypes.bool,
isInfo: PropTypes.bool,
isUnread: PropTypes.bool,
hasDraft: PropTypes.bool,
membersCount: PropTypes.number,
size: PropTypes.number,
status: PropTypes.string,
theme: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
isArchived: PropTypes.bool.isRequired,
isBot: PropTypes.bool.isRequired,
testID: PropTypes.string,
};
static defaultProps = {
isActive: false,
isInfo: false,
isUnread: false,
size: 12,
};
render() {
const {
isActive,
isUnread,
isInfo,
hasDraft,
membersCount,
size,
status,
theme,
type,
isArchived,
isBot,
testID,
} = this.props;
const style = getStyleSheet(theme);
let activeIcon;
let unreadIcon;
let activeGroupBox;
let unreadGroupBox;
let activeGroup;
let unreadGroup;
let offlineColor = changeOpacity(theme.sidebarText, 0.5);
if (isUnread) {
unreadIcon = style.iconUnread;
unreadGroupBox = style.groupBoxUnread;
unreadGroup = style.groupUnread;
}
if (isActive) {
activeIcon = style.iconActive;
activeGroupBox = style.groupBoxActive;
activeGroup = style.groupActive;
}
if (isInfo) {
activeIcon = style.iconInfo;
activeGroupBox = style.groupBoxInfo;
activeGroup = style.groupInfo;
offlineColor = changeOpacity(theme.centerChannelColor, 0.5);
}
let icon;
if (isArchived) {
icon = (
<CompassIcon
name='archive-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
testID={`${testID}.archive`}
/>
);
} else if (isBot) {
icon = (
<CompassIcon
name='robot-happy'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: -1.5, top: -1}]}
testID={`${testID}.bot`}
/>
);
} else if (hasDraft) {
icon = (
<CompassIcon
name='pencil-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
testID={`${testID}.draft`}
/>
);
} else if (type === General.OPEN_CHANNEL) {
icon = (
<CompassIcon
name='globe'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
testID={`${testID}.public`}
/>
);
} else if (type === General.PRIVATE_CHANNEL) {
icon = (
<CompassIcon
name='lock-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
testID={`${testID}.private`}
/>
);
} else if (type === General.GM_CHANNEL) {
const fontSize = (size - 10);
icon = (
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}>
<Text
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
testID={`${testID}.gm_member_count`}
>
{membersCount}
</Text>
</View>
);
} else if (type === General.DM_CHANNEL) {
switch (status) {
case General.AWAY:
icon = (
<CompassIcon
name='clock'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.awayIndicator}]}
testID={`${testID}.away`}
/>
);
break;
case General.DND:
icon = (
<CompassIcon
name='minus-circle'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.dndIndicator}]}
testID={`${testID}.dnd`}
/>
);
break;
case General.ONLINE:
icon = (
<CompassIcon
name='check-circle'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.onlineIndicator}]}
testID={`${testID}.online`}
/>
);
break;
default:
icon = (
<CompassIcon
name='circle-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: offlineColor}]}
testID={`${testID}.offline`}
/>
);
break;
}
}
return (
<View style={[style.container, {height: size}]}>
{icon}
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
marginRight: 8,
alignItems: 'center',
},
icon: {
color: changeOpacity(theme.sidebarText, 0.4),
},
iconActive: {
color: theme.sidebarTextActiveColor,
},
iconUnread: {
color: theme.sidebarUnreadText,
},
iconInfo: {
color: theme.centerChannelColor,
},
groupBox: {
alignSelf: 'flex-start',
alignItems: 'center',
backgroundColor: changeOpacity(theme.sidebarText, 0.3),
borderColor: changeOpacity(theme.sidebarText, 0.3),
borderWidth: 1,
borderRadius: 2,
justifyContent: 'center',
},
groupBoxActive: {
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
},
groupBoxUnread: {
backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
},
groupBoxInfo: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
},
group: {
color: changeOpacity(theme.sidebarText, 0.6),
fontSize: 10,
fontWeight: '600',
},
groupActive: {
color: theme.sidebarTextActiveColor,
},
groupUnread: {
color: theme.sidebarUnreadText,
},
groupInfo: {
color: theme.centerChannelColor,
},
};
});

View file

@ -0,0 +1,184 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {StyleProp, Text, View, ViewStyle} from 'react-native';
import CompassIcon from '@components/compass_icon';
import ProfilePicture from '@components/profile_picture';
import {General} from '@mm-redux/constants';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {Theme} from '@mm-redux/types/preferences';
type Props = {
hasDraft: boolean;
isActive: boolean;
isArchived: boolean;
isInfo: boolean;
isUnread: boolean;
membersCount: number;
size: number;
statusStyle?: StyleProp<ViewStyle>;
testID?: string;
theme: Theme;
type: string;
userId?: string;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
alignItems: 'center',
justifyContent: 'center',
},
icon: {
color: changeOpacity(theme.sidebarText, 0.4),
},
iconActive: {
color: theme.sidebarTextActiveColor,
},
iconUnread: {
color: theme.sidebarUnreadText,
},
iconInfo: {
color: theme.centerChannelColor,
},
groupBox: {
alignItems: 'center',
backgroundColor: changeOpacity(theme.sidebarText, 0.16),
borderRadius: 4,
justifyContent: 'center',
},
groupBoxActive: {
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
},
groupBoxUnread: {
backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
},
groupBoxInfo: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
},
group: {
color: theme.sidebarText,
fontSize: 10,
fontWeight: '600',
},
groupActive: {
color: theme.sidebarTextActiveColor,
},
groupUnread: {
color: theme.sidebarUnreadText,
},
groupInfo: {
color: theme.centerChannelColor,
},
};
});
const ChannelIcon = (props: Props) => {
const style = getStyleSheet(props.theme);
let activeIcon;
let unreadIcon;
let activeGroupBox;
let unreadGroupBox;
let activeGroup;
let unreadGroup;
if (props.isUnread) {
unreadIcon = style.iconUnread;
unreadGroupBox = style.groupBoxUnread;
unreadGroup = style.groupUnread;
}
if (props.isActive) {
activeIcon = style.iconActive;
activeGroupBox = style.groupBoxActive;
activeGroup = style.groupActive;
}
if (props.isInfo) {
activeIcon = style.iconInfo;
activeGroupBox = style.groupBoxInfo;
activeGroup = style.groupInfo;
}
let icon;
let extraStyle;
if (props.isArchived) {
icon = (
<CompassIcon
name='archive-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 1}]}
testID={`${props.testID}.archive`}
/>
);
} else if (props.hasDraft) {
icon = (
<CompassIcon
name='pencil-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 2}]}
testID={`${props.testID}.draft`}
/>
);
} else if (props.type === General.OPEN_CHANNEL) {
icon = (
<CompassIcon
name='globe'
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 1}]}
testID={`${props.testID}.public`}
/>
);
} else if (props.type === General.PRIVATE_CHANNEL) {
icon = (
<CompassIcon
name='lock-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 0.5}]}
testID={`${props.testID}.private`}
/>
);
} else if (props.type === General.GM_CHANNEL) {
const fontSize = (props.size - 12);
const boxSize = (props.size - 4);
icon = (
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: boxSize, height: boxSize}]}>
<Text
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
testID={`${props.testID}.gm_member_count`}
>
{props.membersCount}
</Text>
</View>
);
} else if (props.type === General.DM_CHANNEL) {
// extraStyle = {marginRight: 6};
icon = (
<ProfilePicture
size={props.size}
statusSize={12}
userId={props.userId}
testID={props.testID}
statusStyle={props.statusStyle}
/>
);
}
return (
<View style={[style.container, extraStyle, {width: props.size, height: props.size}]}>
{icon}
</View>
);
};
ChannelIcon.defaultProps = {
hasDraft: false,
isActive: false,
isArchived: false,
isInfo: false,
isUnread: false,
membersCount: 0,
size: 12,
};
export default ChannelIcon;

View file

@ -1,221 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Platform, View} from 'react-native';
import FastImage from 'react-native-fast-image';
import {Client4} from '@client/rest';
import CompassIcon from '@components/compass_icon';
import UserStatus from '@components/user_status';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
const STATUS_BUFFER = Platform.select({
ios: 3,
android: 2,
});
export default class ProfilePicture extends PureComponent {
static propTypes = {
isCurrentUser: PropTypes.bool,
size: PropTypes.number,
statusSize: PropTypes.number,
iconSize: PropTypes.number,
user: PropTypes.object,
userId: PropTypes.string,
showStatus: PropTypes.bool,
status: PropTypes.string,
edit: PropTypes.bool,
imageUri: PropTypes.string,
profileImageUri: PropTypes.string,
profileImageRemove: PropTypes.bool,
theme: PropTypes.object,
testID: PropTypes.string,
actions: PropTypes.shape({
getStatusForId: PropTypes.func.isRequired,
setProfileImageUri: PropTypes.func.isRequired,
}),
};
static defaultProps = {
showStatus: true,
size: 128,
statusSize: 14,
edit: false,
};
state = {
pictureUrl: null,
};
componentDidMount() {
const {actions, edit, imageUri, profileImageUri, user, status} = this.props;
this.mounted = true;
if (!status && user) {
actions.getStatusForId(user.id);
}
if (profileImageUri) {
this.setImageURL(profileImageUri);
} else if (edit && imageUri) {
this.setImageURL(imageUri);
} else if (user) {
const uri = Client4.getProfilePictureUrl(user.id, user.last_picture_update);
this.setImageURL(uri);
this.clearProfileImageUri();
}
}
componentWillUnmount() {
this.mounted = false;
}
setImageURL = (pictureUrl) => {
if (this.mounted) {
this.setState({pictureUrl});
}
};
clearProfileImageUri = () => {
if (this.props.isCurrentUser && this.props.profileImageUri !== '') {
this.props.actions.setProfileImageUri('');
}
}
componentDidUpdate(prevProps) {
if (this.props.profileImageRemove !== prevProps.profileImageRemove) {
this.setImageURL(null);
} else if (this.mounted) {
if (this.props.edit && this.props.imageUri && this.props.imageUri !== prevProps.imageUri) {
this.setImageURL(this.props.imageUri);
return;
}
if (this.props.profileImageUri !== '' && this.props.profileImageUri !== prevProps.profileImageUri) {
this.setImageURL(this.props.profileImageUri);
}
const url = prevProps.user ? Client4.getProfilePictureUrl(prevProps.user.id, prevProps.user.last_picture_update) : null;
const nextUrl = this.props.user ? Client4.getProfilePictureUrl(this.props.user.id, this.props.user.last_picture_update) : null;
if (nextUrl && url !== nextUrl) {
// empty function is so that promise unhandled is not triggered in dev mode
this.setImageURL(nextUrl);
this.clearProfileImageUri();
}
}
}
render() {
const {edit, showStatus, theme, user, size, testID} = this.props;
const {pictureUrl} = this.state;
const style = getStyleSheet(theme);
let statusIcon;
let statusStyle;
let containerStyle = {
width: size + STATUS_BUFFER,
height: size + STATUS_BUFFER,
};
if (edit) {
const iconColor = changeOpacity(theme.centerChannelColor, 0.6);
statusStyle = {
width: this.props.statusSize,
height: this.props.statusSize,
backgroundColor: theme.centerChannelBg,
};
statusIcon = (
<CompassIcon
name='camera-outline'
size={this.props.statusSize / 1.7}
color={iconColor}
/>
);
} else if (this.props.status && !edit) {
statusIcon = (
<UserStatus
size={this.props.statusSize}
status={this.props.status}
/>
);
}
let source = null;
let image;
if (pictureUrl) {
let prefix = '';
if (Platform.OS === 'android' && !pictureUrl.startsWith('content://') &&
!pictureUrl.startsWith('http://') && !pictureUrl.startsWith('https://')) {
prefix = 'file://';
}
source = {
uri: `${prefix}${pictureUrl}`,
};
image = (
<FastImage
key={pictureUrl}
style={{width: this.props.size, height: this.props.size, borderRadius: this.props.size / 2}}
source={source}
/>
);
} else {
containerStyle = {
width: size + (STATUS_BUFFER - 1),
height: size + (STATUS_BUFFER - 1),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
};
image = (
<CompassIcon
name='account-outline'
size={this.props.iconSize || this.props.size}
style={style.icon}
/>
);
}
return (
<View
style={[style.container, containerStyle]}
testID={`${testID}.${user?.id}`}
>
{image}
{(showStatus || edit) && (user && !user.is_bot) &&
<View style={[style.statusWrapper, statusStyle, {borderRadius: this.props.statusSize / 2}]}>
{statusIcon}
</View>
}
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 80,
},
icon: {
color: changeOpacity(theme.centerChannelColor, 0.48),
},
statusWrapper: {
position: 'absolute',
bottom: 0,
right: 0,
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.centerChannelBg,
},
status: {
color: theme.centerChannelBg,
},
};
});

View file

@ -0,0 +1,216 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useState} from 'react';
import {Platform, StyleProp, View, ViewProps, ViewStyle} from 'react-native';
import FastImage from 'react-native-fast-image';
import {Client4} from '@client/rest';
import CompassIcon from '@components/compass_icon';
import UserStatus from '@components/user_status';
import {useDidUpdate} from '@hooks';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {Theme} from '@mm-redux/types/preferences';
import type {UserProfile} from '@mm-redux/types/users';
const STATUS_BUFFER = Platform.select({
ios: 3,
android: 2,
});
type ProfilePictureProps = {
actions: {
setProfileImageUri: (imageUri?: string) => void;
getStatusForId: (id: string) => void;
};
edit: boolean;
iconSize?: number;
imageUri?: string;
isCurrentUser: boolean;
profileImageRemove?: boolean;
profileImageUri?: string;
showStatus: boolean;
size: number;
status?: string;
statusSize: number;
statusStyle?: StyleProp<ViewProps> | any;
user?: UserProfile;
userId: string;
theme: Theme;
testID?: string;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 80,
},
icon: {
color: changeOpacity(theme.centerChannelColor, 0.48),
},
statusWrapper: {
position: 'absolute',
bottom: 0,
right: 0,
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.centerChannelBg,
borderWidth: 1,
borderColor: theme.centerChannelBg,
},
status: {
color: theme.centerChannelBg,
},
};
});
const ProfilePicture = (props: ProfilePictureProps) => {
const [pictureUrl, setPictureUrl] = useState<string|undefined>();
const style = getStyleSheet(props.theme);
const buffer = STATUS_BUFFER || 0;
const clearProfileImageUri = () => {
if (props.isCurrentUser && props.profileImageUri !== '') {
props.actions.setProfileImageUri('');
}
};
useEffect(() => {
const {edit, imageUri, profileImageUri, user, status} = props;
const {getStatusForId} = props.actions;
if (!status && user) {
getStatusForId(user.id);
}
if (profileImageUri) {
setPictureUrl(profileImageUri);
} else if (edit && imageUri) {
setPictureUrl(imageUri);
} else if (user) {
const uri = Client4.getProfilePictureUrl(user.id, user.last_picture_update);
setPictureUrl(uri);
clearProfileImageUri();
}
}, []);
useDidUpdate(() => {
setPictureUrl(undefined);
}, [props.profileImageRemove]);
useDidUpdate(() => {
const {edit, imageUri} = props;
if (edit && imageUri) {
setPictureUrl(imageUri);
}
}, [props.edit, props.imageUri]);
useDidUpdate(() => {
const {profileImageUri} = props;
if (profileImageUri) {
setPictureUrl(profileImageUri);
}
}, [props.profileImageUri]);
useDidUpdate(() => {
const {user} = props;
const url = user ? Client4.getProfilePictureUrl(user.id, user.last_picture_update) : undefined;
if (url !== pictureUrl) {
setPictureUrl(url);
}
}, [props.user]);
let statusIcon;
let statusStyle = props.statusStyle;
let containerStyle: StyleProp<ViewStyle> = {
width: props.size + buffer,
height: props.size + buffer,
};
if (props.edit) {
const iconColor = changeOpacity(props.theme.centerChannelColor, 0.6);
statusStyle = {
width: props.statusSize,
height: props.statusSize,
backgroundColor: props.theme.centerChannelBg,
};
statusIcon = (
<CompassIcon
name='camera-outline'
size={props.statusSize / 1.7}
color={iconColor}
/>
);
} else if (props.status && !props.edit) {
statusIcon = (
<UserStatus
size={props.statusSize}
status={props.status}
/>
);
}
let source = null;
let image;
if (pictureUrl) {
let prefix = '';
if (Platform.OS === 'android' && !pictureUrl.startsWith('content://') &&
!pictureUrl.startsWith('http://') && !pictureUrl.startsWith('https://')) {
prefix = 'file://';
}
source = {
uri: `${prefix}${pictureUrl}`,
};
image = (
<FastImage
key={pictureUrl}
style={{width: props.size, height: props.size, borderRadius: props.size / 2}}
source={source}
/>
);
} else {
containerStyle = {
width: props.size + (buffer - 1),
height: props.size + (buffer - 1),
backgroundColor: changeOpacity(props.theme.centerChannelColor, 0.08),
};
image = (
<CompassIcon
name='account-outline'
size={props.iconSize || props.size}
style={style.icon}
/>
);
}
return (
<View
style={[style.container, containerStyle]}
testID={`${props.testID}.${props.user?.id}`}
>
{image}
{(props.showStatus || props.edit) && (!props.user?.is_bot) &&
<View style={[style.statusWrapper, statusStyle, {borderRadius: props.statusSize / 2}]}>
{statusIcon}
</View>
}
</View>
);
};
ProfilePicture.defaultProps = {
showStatus: true,
size: 128,
statusSize: 14,
edit: false,
};
export default ProfilePicture;

View file

@ -37,12 +37,16 @@ exports[`ChannelItem should match snapshot 1`] = `
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -87,6 +91,7 @@ exports[`ChannelItem should match snapshot 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -153,12 +158,16 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
hasDraft={false}
isActive={true}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -203,6 +212,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -213,7 +223,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
}
testID="main.sidebar.channels_list.list.channel_item.display_name"
>
{displayname} (you)
display_name
</Text>
</View>
</View>
@ -268,12 +278,16 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
hasDraft={false}
isActive={true}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -318,6 +332,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -383,12 +398,16 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
hasDraft={false}
isActive={true}
isArchived={true}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -433,6 +452,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -487,12 +507,16 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -537,6 +561,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -592,12 +617,16 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -642,6 +671,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -697,12 +727,16 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -747,6 +781,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -806,12 +841,16 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
hasDraft={true}
isActive={false}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -856,6 +895,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@ -913,12 +953,16 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
size={16}
status="online"
size={24}
statusStyle={
Object {
"backgroundColor": "#145dbf",
"borderColor": "transparent",
}
}
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@ -963,6 +1007,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
"marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},

View file

@ -10,11 +10,11 @@ import {
} from 'react-native';
import {intlShape} from 'react-intl';
import Badge from '@components/badge';
import ChannelIcon from '@components/channel_icon';
import {General} from '@mm-redux/constants';
import Badge from 'app/components/badge';
import ChannelIcon from 'app/components/channel_icon';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class ChannelItem extends PureComponent {
static propTypes = {
@ -33,10 +33,10 @@ export default class ChannelItem extends PureComponent {
onSelectChannel: PropTypes.func.isRequired,
shouldHideChannel: PropTypes.bool,
showUnreadForMsgs: PropTypes.bool.isRequired,
teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
unreadMsgs: PropTypes.number.isRequired,
isSearchResult: PropTypes.bool,
isBot: PropTypes.bool.isRequired,
};
static defaultProps = {
@ -77,7 +77,7 @@ export default class ChannelItem extends PureComponent {
theme,
isSearchResult,
channel,
isBot,
teammateId,
} = this.props;
// Only ever show an archived channel if it's the currently viewed channel.
@ -103,7 +103,7 @@ export default class ChannelItem extends PureComponent {
if (isSearchResult) {
isCurrenUser = channel.id === currentUserId;
} else {
isCurrenUser = channel.teammate_id === currentUserId;
isCurrenUser = teammateId === currentUserId;
}
}
if (isCurrenUser) {
@ -161,13 +161,13 @@ export default class ChannelItem extends PureComponent {
isUnread={isUnread}
hasDraft={hasDraft && channelId !== currentChannelId}
membersCount={displayName.split(',').length}
size={16}
status={channel.status}
statusStyle={{backgroundColor: theme.sidebarBg, borderColor: 'transparent'}}
size={24}
theme={theme}
type={channel.type}
isArchived={isArchived}
isBot={isBot}
testID={`${testID}.channel_icon`}
userId={teammateId}
/>
);
@ -231,6 +231,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 16,
lineHeight: 24,
paddingRight: 10,
marginLeft: 13,
maxWidth: '80%',
flex: 1,
alignSelf: 'center',

View file

@ -30,16 +30,14 @@ function makeMapStateToProps() {
const channelDraft = getDraftForChannel(state, channel.id);
let displayName = channel.display_name;
let isBot = false;
let isGuest = false;
let isArchived = channel.delete_at > 0;
let teammateId;
if (channel.type === General.DM_CHANNEL) {
const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
teammateId = getUserIdFromChannelName(currentUserId, channel.name);
const teammate = getUser(state, teammateId);
isBot = Boolean(ownProps.isSearchResult ? channel.isBot : teammate?.is_bot); //eslint-disable-line camelcase
if (teammate) {
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
displayName = displayUsername(teammate, teammateNameDisplay, false);
@ -78,13 +76,13 @@ function makeMapStateToProps() {
displayName,
hasDraft: Boolean(channelDraft.draft.trim() || channelDraft?.files?.length),
isArchived,
isBot,
isChannelMuted: isChannelMuted(member),
isGuest,
isManualUnread: isManuallyUnread(state, ownProps.channelId),
mentions: member ? member.mention_count : 0,
shouldHideChannel,
showUnreadForMsgs,
teammateId,
theme: getTheme(state),
unreadMsgs,
};

View file

@ -179,7 +179,7 @@ class FilteredList extends Component {
};
buildCurrentDMSForSearch = (props, term) => {
const {channels, teammateNameDisplay, profiles, statuses, pastDirectMessages, groupChannelMemberDetails} = props;
const {channels, currentUserId, teammateNameDisplay, profiles, pastDirectMessages, groupChannelMemberDetails} = props;
const {favoriteChannels} = channels;
const favoriteDms = favoriteChannels.filter((c) => {
@ -206,7 +206,6 @@ class FilteredList extends Component {
return {
id: u.id,
status: statuses[u.id],
display_name: displayName,
username: u.username,
email: u.email,
@ -215,10 +214,7 @@ class FilteredList extends Component {
nickname: u.nickname,
fullname: `${u.first_name} ${u.last_name}`,
delete_at: u.delete_at,
isBot: u.is_bot,
// need name key for DM's as we use it for sortChannelsByDisplayName with same display_name
name: displayName,
name: `${currentUserId}__${u.id}`,
};
});
@ -233,7 +229,7 @@ class FilteredList extends Component {
}
buildMembersForSearch = (props, term) => {
const {channels, currentUserId, teammateNameDisplay, profiles, teamProfiles, statuses, pastDirectMessages, restrictDms} = props;
const {channels, currentUserId, teammateNameDisplay, profiles, teamProfiles, pastDirectMessages, restrictDms} = props;
const {favoriteChannels, unreadChannels} = channels;
const favoriteAndUnreadDms = [...favoriteChannels, ...unreadChannels].filter((c) => {
@ -251,17 +247,15 @@ class FilteredList extends Component {
return {
id: u.id,
status: statuses[u.id],
display_name: displayName,
username: u.username,
email: u.email,
name: displayName,
name: `${currentUserId}__${u.id}`,
type: General.DM_CHANNEL,
fake: true,
nickname: u.nickname,
fullname: `${u.first_name} ${u.last_name}`,
delete_at: u.delete_at,
isBot: u.is_bot,
};
});

View file

@ -21,7 +21,7 @@ exports[`ChannelsList List should match snapshot 1`] = `
onEndReachedThreshold={2}
onScrollBeginDrag={[Function]}
onViewableItemsChanged={[Function]}
removeClippedSubviews={true}
removeClippedSubviews={false}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}

View file

@ -411,7 +411,7 @@ export default class List extends PureComponent {
ref={this.setListRef}
sections={sections}
contentContainerStyle={{paddingBottom}}
removeClippedSubviews={true}
removeClippedSubviews={Platform.OS === 'android'}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyboardShouldPersistTaps={'always'}

View file

@ -44,7 +44,7 @@ exports[`UserStatus should match snapshot, should default to offline status 1`]
name="circle-outline"
style={
Object {
"color": "rgba(61,60,64,0.3)",
"color": "rgba(184,184,184,0.64)",
"fontSize": 32,
}
}

View file

@ -1,57 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {General} from '@mm-redux/constants';
import CompassIcon from '@components/compass_icon';
import {changeOpacity} from '@utils/theme';
export default class UserStatus extends PureComponent {
static propTypes = {
isAvatar: PropTypes.bool,
size: PropTypes.number,
status: PropTypes.string,
theme: PropTypes.object.isRequired,
};
static defaultProps = {
size: 6,
status: General.OFFLINE,
};
render() {
const {size, status, theme} = this.props;
let iconName;
let iconColor;
switch (status) {
case General.AWAY:
iconName = 'clock';
iconColor = theme.awayIndicator;
break;
case General.DND:
iconName = 'minus-circle';
iconColor = theme.dndIndicator;
break;
case General.ONLINE:
iconName = 'check-circle';
iconColor = theme.onlineIndicator;
break;
default:
iconName = 'circle-outline';
iconColor = changeOpacity(theme.centerChannelColor, 0.3);
break;
}
return (
<CompassIcon
name={iconName}
style={{fontSize: size, color: iconColor}}
testID={`user_status.icon.${status}`}
/>
);
}
}

View file

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import CompassIcon from '@components/compass_icon';
import {General} from '@mm-redux/constants';
import {changeOpacity} from '@utils/theme';
import type {Theme} from '@mm-redux/types/preferences';
type UserStatusProps = {
size: number;
status: string;
theme: Theme;
};
const UserStatus = ({size, status, theme}: UserStatusProps) => {
let iconName;
let iconColor;
switch (status) {
case General.AWAY:
iconName = 'clock';
iconColor = theme.awayIndicator;
break;
case General.DND:
iconName = 'minus-circle';
iconColor = theme.dndIndicator;
break;
case General.ONLINE:
iconName = 'check-circle';
iconColor = theme.onlineIndicator;
break;
default:
iconName = 'circle-outline';
iconColor = changeOpacity('#B8B8B8', 0.64);
break;
}
return (
<CompassIcon
name={iconName}
style={{fontSize: size, color: iconColor}}
testID={`user_status.icon.${status}`}
/>
);
};
UserStatus.defaultProps = {
size: 6,
status: General.OFFLINE,
};
export default UserStatus;

18
app/hooks/did_update.ts Normal file
View file

@ -0,0 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useRef, useEffect, EffectCallback, DependencyList} from 'react';
function useDidUpdate(callback: EffectCallback, deps?: DependencyList) {
const hasMount = useRef(false);
useEffect(() => {
if (hasMount.current) {
callback();
} else {
hasMount.current = true;
}
}, deps);
}
export default useDidUpdate;

4
app/hooks/index.ts Normal file
View file

@ -0,0 +1,4 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export {default as useDidUpdate} from './did_update';

View file

@ -33,13 +33,11 @@ exports[`channelInfo should match snapshot 1`] = `
hasGuests={false}
header=""
isArchived={false}
isBot={false}
isGroupConstrained={false}
isTeammateGuest={false}
memberCount={2}
onPermalinkPress={[Function]}
purpose="Purpose"
status="status"
testID="channel_info.header"
theme={
Object {

View file

@ -27,14 +27,13 @@ exports[`channel_info_header should match snapshot 1`] = `
}
>
<ChannelIcon
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={true}
isUnread={false}
membersCount={3}
size={24}
status="status"
testID="channel_info.header.channel_icon"
theme={
Object {
@ -76,6 +75,7 @@ exports[`channel_info_header should match snapshot 1`] = `
"flex": 1,
"fontSize": 15,
"fontWeight": "600",
"marginLeft": 13,
}
}
testID="channel_info.header.display_name"
@ -334,14 +334,13 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
}
>
<ChannelIcon
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={true}
isUnread={false}
membersCount={3}
size={24}
status="status"
testID="channel_info.header.channel_icon"
theme={
Object {
@ -383,6 +382,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
"flex": 1,
"fontSize": 15,
"fontWeight": "600",
"marginLeft": 13,
}
}
testID="channel_info.header.display_name"
@ -669,14 +669,13 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
}
>
<ChannelIcon
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={true}
isUnread={false}
membersCount={3}
size={24}
status="status"
testID="channel_info.header.channel_icon"
theme={
Object {
@ -718,6 +717,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
"flex": 1,
"fontSize": 15,
"fontWeight": "600",
"marginLeft": 13,
}
}
testID="channel_info.header.display_name"
@ -976,14 +976,13 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
}
>
<ChannelIcon
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={true}
isUnread={false}
membersCount={3}
size={24}
status="status"
testID="channel_info.header.channel_icon"
theme={
Object {
@ -1025,6 +1024,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
"flex": 1,
"fontSize": 15,
"fontWeight": "600",
"marginLeft": 13,
}
}
testID="channel_info.header.display_name"
@ -1311,14 +1311,13 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
}
>
<ChannelIcon
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={true}
isUnread={false}
membersCount={3}
size={24}
status="status"
testID="channel_info.header.channel_icon"
theme={
Object {
@ -1360,6 +1359,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
"flex": 1,
"fontSize": 15,
"fontWeight": "600",
"marginLeft": 13,
}
}
testID="channel_info.header.display_name"
@ -1639,14 +1639,13 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
}
>
<ChannelIcon
hasDraft={false}
isActive={false}
isArchived={false}
isBot={false}
isInfo={true}
isUnread={false}
membersCount={3}
size={24}
status="status"
testID="channel_info.header.channel_icon"
theme={
Object {
@ -1688,6 +1687,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
"flex": 1,
"fontSize": 15,
"fontWeight": "600",
"marginLeft": 13,
}
}
testID="channel_info.header.display_name"

View file

@ -45,11 +45,9 @@ export default class ChannelInfo extends PureComponent {
currentChannelGuestCount: PropTypes.number,
currentChannelMemberCount: PropTypes.number,
currentUserId: PropTypes.string,
isBot: PropTypes.bool.isRequired,
isLandscape: PropTypes.bool.isRequired,
isTeammateGuest: PropTypes.bool.isRequired,
isDirectMessage: PropTypes.bool.isRequired,
status: PropTypes.string,
teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
};
@ -172,9 +170,8 @@ export default class ChannelInfo extends PureComponent {
currentChannelCreatorName,
currentChannelMemberCount,
currentChannelGuestCount,
status,
teammateId,
theme,
isBot,
isTeammateGuest,
} = this.props;
@ -201,11 +198,10 @@ export default class ChannelInfo extends PureComponent {
memberCount={currentChannelMemberCount}
onPermalinkPress={this.handlePermalinkPress}
purpose={currentChannel.purpose}
status={status}
teammateId={teammateId}
theme={theme}
type={currentChannel.type}
isArchived={channelIsArchived}
isBot={isBot}
isTeammateGuest={isTeammateGuest}
hasGuests={currentChannelGuestCount > 0}
isGroupConstrained={currentChannel.group_constrained}

View file

@ -44,9 +44,7 @@ describe('channelInfo', () => {
currentChannelMemberCount: 2,
currentChannelGuestCount: 0,
currentUserId: '1234',
status: 'status',
theme: Preferences.THEMES.default,
isBot: false,
isTeammateGuest: false,
isDirectMessage: false,
actions: {

View file

@ -12,18 +12,17 @@ import {
import {intlShape} from 'react-intl';
import Clipboard from '@react-native-community/clipboard';
import {popToRoot} from '@actions/navigation';
import ChannelIcon from '@components/channel_icon';
import FormattedDate from '@components/formatted_date';
import FormattedText from '@components/formatted_text';
import Markdown from '@components/markdown';
import {General} from '@mm-redux/constants';
import ChannelIcon from 'app/components/channel_icon';
import FormattedDate from 'app/components/formatted_date';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import BottomSheet from '@utils/bottom_sheet';
import {t} from '@utils/i18n';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import mattermostManaged from 'app/mattermost_managed';
import BottomSheet from 'app/utils/bottom_sheet';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
import {popToRoot} from 'app/actions/navigation';
export default class ChannelInfoHeader extends React.PureComponent {
static propTypes = {
@ -34,11 +33,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
header: PropTypes.string,
onPermalinkPress: PropTypes.func,
purpose: PropTypes.string,
status: PropTypes.string,
teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
isArchived: PropTypes.bool.isRequired,
isBot: PropTypes.bool.isRequired,
isTeammateGuest: PropTypes.bool.isRequired,
hasGuests: PropTypes.bool.isRequired,
isGroupConstrained: PropTypes.bool,
@ -135,11 +133,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
memberCount,
onPermalinkPress,
purpose,
status,
teammateId,
theme,
type,
isArchived,
isBot,
isGroupConstrained,
testID,
timeZone,
@ -148,9 +145,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
const style = getStyleSheet(theme);
const textStyles = getMarkdownTextStyles(theme);
const blockStyles = getMarkdownBlockStyles(theme);
const baseTextStyle = Platform.OS === 'ios' ?
{...style.detail, lineHeight: 20} :
style.detail;
const baseTextStyle = Platform.select({
ios: {...style.detail, lineHeight: 20},
android: style.detail,
});
return (
<View style={style.container}>
@ -159,11 +157,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
isInfo={true}
membersCount={memberCount}
size={24}
status={status}
userId={teammateId}
theme={theme}
type={type}
isArchived={isArchived}
isBot={isBot}
testID={`${testID}.channel_icon`}
/>
<Text
@ -268,6 +265,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 15,
fontWeight: '600',
color: theme.centerChannelColor,
marginLeft: 13,
},
channelNameContainer: {
flexDirection: 'row',

View file

@ -11,10 +11,9 @@ import {getCustomEmojisInText} from '@mm-redux/actions/emojis';
import {General} from '@mm-redux/constants';
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
import {getCurrentChannel, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
import {getCurrentUserId, getUser, getStatusForUserId} from '@mm-redux/selectors/entities/users';
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getUserIdFromChannelName} from '@mm-redux/utils/channel_utils';
import {displayUsername} from '@mm-redux/utils/user_utils';
import {isLandscape} from '@selectors/device';
import {isGuest} from '@utils/users';
import ChannelInfo from './channel_info';
@ -29,17 +28,13 @@ function mapStateToProps(state) {
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
const currentUserId = getCurrentUserId(state);
let status;
let isBot = false;
let teammateId;
let isTeammateGuest = false;
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
if (isDirectMessage) {
const teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
const teammate = getUser(state, teammateId);
status = getStatusForUserId(state, teammateId);
if (teammate && teammate.is_bot) {
isBot = true;
}
if (isGuest(teammate)) {
isTeammateGuest = true;
currentChannelGuestCount = 1;
@ -56,11 +51,9 @@ function mapStateToProps(state) {
currentChannelGuestCount,
currentChannelMemberCount,
currentUserId,
isBot,
isLandscape: isLandscape(state),
isTeammateGuest,
isDirectMessage,
status,
teammateId,
theme: getTheme(state),
};
}

View file

@ -17,6 +17,7 @@ module.exports = {
'@client': './app/client',
'@components': './app/components',
'@constants': './app/constants',
'@hooks': './app/hooks',
'@i18n': './app/i18n',
'@init': './app/init',
'@mm-redux': './app/mm-redux',

View file

@ -38,6 +38,8 @@
"@components/*": ["app/components/*"],
"@constants/*": ["app/constants/*"],
"@constants": ["app/constants/index"],
"@hooks": ["app/hooks/index"],
"@hooks/*": ["app/hooks/*"],
"@i18n": ["app/i18n/index"],
"@init/*": ["app/init/*"],
"@mm-redux/*": ["app/mm-redux/*"],