MM-32921 shared channels (#5241)
* Unable to open previews from search, pinned and mentions * Updated Compass Icons * Added Icon to LHS and Channel Info * Added Icons to Manage/View Members list * Added Icon to shared users in posts * Added icon to channel header, fixed header style * Added Icons in autocomplete * WIP: Add shared shannels to browse * Adding Shared Channels string to i18n * Added remote organization to remote user profile * Updated snapshot for channel header * Added browsing shared channels * Removed the exmpty line * Added snapshot when user is remote * Reverted compass icons and added icons only needed for shared channels * Fixed i18n swapped * Copied compass-icons from webapp * Fixed showing shared channels as deleted after browsing them * Added new compass ttf to android & fixed icon style for browse channel listing * Fixed search for shared channels * user profile snapshot updated with testId * User list row snapshot updated with testID * Moved shared user check to util function * Fixed required props warning for ChannelIcon component * Update app/screens/user_profile/index.js Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com> * Removed Request related redux * Reverted client4 * Added rest calls * Updated snapshot * Reverted files * Adding back shared channels stuff to channel icon & showing shared channels only when enabled in browse channels * Fixed misc issues * moved empty array outside the function to avoid re-render * Removed renaming fields Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
6ea6366e1c
commit
6e23fbe7a7
47 changed files with 1102 additions and 94 deletions
Binary file not shown.
|
|
@ -186,6 +186,10 @@ export default class ClientBase {
|
|||
return `${this.getPostsRoute()}/${postId}`;
|
||||
}
|
||||
|
||||
getSharedChannelsRoute() {
|
||||
return `${this.getBaseRoute()}/sharedchannels`;
|
||||
}
|
||||
|
||||
getReactionsRoute() {
|
||||
return `${this.getBaseRoute()}/reactions`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import ClientGroups, {ClientGroupsMix} from './groups';
|
|||
import ClientIntegrations, {ClientIntegrationsMix} from './integrations';
|
||||
import ClientPosts, {ClientPostsMix} from './posts';
|
||||
import ClientPreferences, {ClientPreferencesMix} from './preferences';
|
||||
import ClientSharedChannels, {ClientSharedChannelsMix} from './shared_channels';
|
||||
import ClientTeams, {ClientTeamsMix} from './teams';
|
||||
import ClientTos, {ClientTosMix} from './tos';
|
||||
import ClientUsers, {ClientUsersMix} from './users';
|
||||
|
|
@ -30,6 +31,7 @@ interface Client extends ClientBase,
|
|||
ClientIntegrationsMix,
|
||||
ClientPostsMix,
|
||||
ClientPreferencesMix,
|
||||
ClientSharedChannelsMix,
|
||||
ClientTeamsMix,
|
||||
ClientTosMix,
|
||||
ClientUsersMix
|
||||
|
|
@ -46,6 +48,7 @@ class Client extends mix(ClientBase).with(
|
|||
ClientIntegrations,
|
||||
ClientPosts,
|
||||
ClientPreferences,
|
||||
ClientSharedChannels,
|
||||
ClientTeams,
|
||||
ClientTos,
|
||||
ClientUsers,
|
||||
|
|
|
|||
35
app/client/rest/shared_channels.ts
Normal file
35
app/client/rest/shared_channels.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {SharedChannel} from '@mm-redux/types/channels';
|
||||
import {RemoteCluster} from '@mm-redux/types/remote_cluster';
|
||||
import {buildQueryString} from '@mm-redux/utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
export interface ClientSharedChannelsMix {
|
||||
getRemoteClusterInfo: (remote_id: string) => Promise<RemoteCluster>;
|
||||
getSharedChannels: (teamId: string, page: number, perPage?: number) => Promise<SharedChannel[]>;
|
||||
}
|
||||
|
||||
const ClientSharedChannels = (superclass: any) => class extends superclass {
|
||||
getRemoteClusterInfo = async (remote_id: string) => {
|
||||
const response = await this.doFetch(
|
||||
`${this.getSharedChannelsRoute()}/remote_info/${remote_id}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
return {
|
||||
...response,
|
||||
remote_id,
|
||||
};
|
||||
};
|
||||
|
||||
getSharedChannels = async (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
return this.doFetch(
|
||||
`${this.getSharedChannelsRoute()}/${teamId}${buildQueryString({page, per_page: perPage})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientSharedChannels;
|
||||
|
|
@ -5,10 +5,12 @@ import React from 'react';
|
|||
import {Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
import type {Theme} from '@mm-redux/types/preferences';
|
||||
|
|
@ -18,6 +20,7 @@ interface AtMentionItemProps {
|
|||
isBot: boolean;
|
||||
isCurrentUser: boolean;
|
||||
isGuest: boolean;
|
||||
isShared: boolean;
|
||||
lastName: string;
|
||||
nickname: string;
|
||||
onPress: (username: string) => void;
|
||||
|
|
@ -45,6 +48,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
rowInfo: {
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
},
|
||||
rowFullname: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
|
|
@ -66,6 +73,7 @@ const AtMentionItem = (props: AtMentionItemProps) => {
|
|||
isBot,
|
||||
isCurrentUser,
|
||||
isGuest,
|
||||
isShared,
|
||||
lastName,
|
||||
nickname,
|
||||
onPress,
|
||||
|
|
@ -118,33 +126,47 @@ const AtMentionItem = (props: AtMentionItemProps) => {
|
|||
testID={`${testID}.profile_picture`}
|
||||
/>
|
||||
</View>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
}
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
{` @${username}`}
|
||||
</Text>
|
||||
<View style={style.rowInfo}>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name} {name}
|
||||
</Text>
|
||||
}
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
{` @${username}`}
|
||||
</Text>
|
||||
</View>
|
||||
{isShared && (
|
||||
<ChannelIcon
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={true}
|
||||
isUnread={true}
|
||||
size={18}
|
||||
shared={true}
|
||||
theme={theme}
|
||||
type={General.DM_CHANNEL}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
|||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {isShared} from '@mm-redux/utils/user_utils';
|
||||
import {isGuest} from '@utils/users';
|
||||
|
||||
import AtMentionItem from './at_mention_item';
|
||||
|
|
@ -21,6 +22,7 @@ function mapStateToProps(state, ownProps) {
|
|||
showFullName: config.ShowFullName,
|
||||
isBot: Boolean(user.is_bot),
|
||||
isGuest: isGuest(user),
|
||||
isShared: isShared(user),
|
||||
theme: getTheme(state),
|
||||
isCurrentUser: getCurrentUserId(state) === user.id,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const ChannelMentionItem = (props) => {
|
|||
isGuest,
|
||||
name,
|
||||
onPress,
|
||||
shared,
|
||||
testID,
|
||||
theme,
|
||||
type,
|
||||
|
|
@ -64,7 +65,9 @@ const ChannelMentionItem = (props) => {
|
|||
const margins = {marginLeft: insets.left, marginRight: insets.right};
|
||||
let iconName = 'globe';
|
||||
let component;
|
||||
if (type === General.PRIVATE_CHANNEL) {
|
||||
if (shared) {
|
||||
iconName = type === General.PRIVATE_CHANNEL ? 'circle-multiple-outline-lock' : 'circle-multiple-outline';
|
||||
} else if (type === General.PRIVATE_CHANNEL) {
|
||||
iconName = 'lock';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ function mapStateToProps(state, ownProps) {
|
|||
return {
|
||||
displayName,
|
||||
name: channel?.name,
|
||||
shared: channel?.shared,
|
||||
type: channel?.type,
|
||||
isBot,
|
||||
isGuest,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ type Props = {
|
|||
isInfo: boolean;
|
||||
isUnread: boolean;
|
||||
membersCount: number;
|
||||
shared: boolean;
|
||||
size: number;
|
||||
statusStyle?: StyleProp<ViewStyle>;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
testID?: string;
|
||||
theme: Theme;
|
||||
type: string;
|
||||
|
|
@ -105,7 +107,6 @@ const ChannelIcon = (props: Props) => {
|
|||
}
|
||||
|
||||
let icon;
|
||||
let extraStyle;
|
||||
if (props.isArchived) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
|
|
@ -122,6 +123,16 @@ const ChannelIcon = (props: Props) => {
|
|||
testID={`${props.testID}.draft`}
|
||||
/>
|
||||
);
|
||||
} else if (props.shared) {
|
||||
const iconName = props.type === General.PRIVATE_CHANNEL ? 'circle-multiple-outline-lock' : 'circle-multiple-outline';
|
||||
const sharedTestID = props.type === General.PRIVATE_CHANNEL ? 'channel_icon.shared_private' : 'channel_icon.shared_open';
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 0.5}]}
|
||||
testID={sharedTestID}
|
||||
/>
|
||||
);
|
||||
} else if (props.type === General.OPEN_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
|
|
@ -152,7 +163,6 @@ const ChannelIcon = (props: Props) => {
|
|||
</View>
|
||||
);
|
||||
} else if (props.type === General.DM_CHANNEL) {
|
||||
// extraStyle = {marginRight: 6};
|
||||
icon = (
|
||||
<ProfilePicture
|
||||
size={props.size}
|
||||
|
|
@ -165,7 +175,7 @@ const ChannelIcon = (props: Props) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={[style.container, extraStyle, {width: props.size, height: props.size}]}>
|
||||
<View style={[style.container, {width: props.size, height: props.size}, props.style]}>
|
||||
{icon}
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ export default class ChannelListRow extends React.PureComponent {
|
|||
const testID = this.props.testID;
|
||||
const itemTestID = `${testID}.${this.props.id}`;
|
||||
const channelDisplayNameTestID = `${testID}.display_name`;
|
||||
let icon = 'globe';
|
||||
if (this.props.isArchived) {
|
||||
icon = 'archive-outline';
|
||||
} else if (this.props.channel?.shared) {
|
||||
icon = 'circle-multiple-outline';
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomListRow
|
||||
|
|
@ -60,7 +66,7 @@ export default class ChannelListRow extends React.PureComponent {
|
|||
>
|
||||
<View style={style.titleContainer}>
|
||||
<CompassIcon
|
||||
name={this.props.isArchived ? 'archive-outline' : 'globe'}
|
||||
name={icon}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ const style = StyleSheet.create({
|
|||
width: 28,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#888',
|
||||
borderColor: 'rgba(61, 60, 64, 0.32)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
|
@ -87,7 +87,7 @@ const style = StyleSheet.create({
|
|||
justifyContent: 'center',
|
||||
},
|
||||
selectorDisabled: {
|
||||
backgroundColor: '#888',
|
||||
borderColor: 'rgba(61, 60, 64, 0.16)',
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#166DE0',
|
||||
|
|
|
|||
|
|
@ -552,3 +552,186 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
|
|||
</CustomListRow>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`UserListRow should match snapshot for remote user 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginHorizontal": 10,
|
||||
"overflow": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomListRow
|
||||
enabled={true}
|
||||
id="21345"
|
||||
onPress={[Function]}
|
||||
testID="custom_list.user_item"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#3d3c40",
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={24}
|
||||
size={32}
|
||||
testID="custom_list.user_item.21345.profile_picture"
|
||||
userId="21345"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
testID="custom_list.user_item.21345"
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
testID="custom_list.user_item.display_username"
|
||||
>
|
||||
@user
|
||||
</Text>
|
||||
<BotTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<GuestTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={true}
|
||||
membersCount={0}
|
||||
shared={true}
|
||||
size={18}
|
||||
style={
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"opacity": 0.75,
|
||||
}
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
type="D"
|
||||
/>
|
||||
</CustomListRow>
|
||||
</View>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {displayUsername, isShared} from '@mm-redux/utils/user_utils';
|
||||
|
||||
import CustomListRow from 'app/components/custom_list/custom_list_row';
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
|
|
@ -38,6 +40,27 @@ export default class UserListRow extends React.PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
renderIcon = (style) => {
|
||||
const {theme, user} = this.props;
|
||||
if (!isShared(user)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ChannelIcon
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isUnread={true}
|
||||
isInfo={true}
|
||||
size={18}
|
||||
shared={true}
|
||||
style={style.sharedUserIcon}
|
||||
theme={theme}
|
||||
type={General.DM_CHANNEL}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {
|
||||
|
|
@ -131,6 +154,7 @@ export default class UserListRow extends React.PureComponent {
|
|||
</View>
|
||||
}
|
||||
</View>
|
||||
{this.renderIcon(style)}
|
||||
</CustomListRow>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -172,5 +196,9 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
sharedUserIcon: {
|
||||
alignSelf: 'center',
|
||||
opacity: 0.75,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,6 +75,22 @@ describe('UserListRow', () => {
|
|||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for remote user', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
user: {
|
||||
...baseProps.user,
|
||||
remote_id: 'abc123',
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<UserListRow {...newProps}/>,
|
||||
{context: {intl: {formatMessage}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for currentUser with (you) populated in list', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
|
|||
import {getUser, getCurrentUser} from '@mm-redux/selectors/entities/users';
|
||||
import {isPostPendingOrFailed, isSystemMessage, isFromWebhook} from '@mm-redux/utils/post_utils';
|
||||
import {getUserCurrentTimezone} from '@mm-redux/utils/timezone_utils';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {displayUsername, isShared} from '@mm-redux/utils/user_utils';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
|
||||
import {fromAutoResponder} from 'app/utils/general';
|
||||
|
|
@ -59,6 +59,7 @@ function makeMapStateToProps() {
|
|||
isBot: user.is_bot || false,
|
||||
isGuest: isGuest(user),
|
||||
isLandscape: isLandscape(state),
|
||||
isShared: isShared(user),
|
||||
userTimezone,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export default class PostHeader extends PureComponent {
|
|||
username: PropTypes.string,
|
||||
isBot: PropTypes.bool,
|
||||
isGuest: PropTypes.bool,
|
||||
isShared: PropTypes.bool,
|
||||
userTimezone: PropTypes.string,
|
||||
enableTimezone: PropTypes.bool,
|
||||
previousPostExists: PropTypes.bool,
|
||||
|
|
@ -214,6 +215,19 @@ export default class PostHeader extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
renderMemberTypeIcon = (style) => {
|
||||
if (!this.props.isShared) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CompassIcon
|
||||
name='circle-multiple-outline'
|
||||
size={17}
|
||||
style={style.sharedUserIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderReply = () => {
|
||||
const {
|
||||
commentCount,
|
||||
|
|
@ -328,6 +342,7 @@ export default class PostHeader extends PureComponent {
|
|||
<View style={[style.container, (isPendingOrFailedPost && style.pendingPost)]}>
|
||||
<View style={style.wrapper}>
|
||||
{this.renderDisplayName()}
|
||||
{this.renderMemberTypeIcon(style)}
|
||||
{this.renderTag()}
|
||||
{dateComponent}
|
||||
{this.renderReply()}
|
||||
|
|
@ -409,6 +424,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
displayNameContainerLandscapeBotReplyWidth: {
|
||||
maxWidth: '70%',
|
||||
},
|
||||
|
||||
sharedUserIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.75),
|
||||
top: 3,
|
||||
marginRight: 6,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ export default class ChannelItem extends PureComponent {
|
|||
isUnread={isUnread}
|
||||
hasDraft={hasDraft && channelId !== currentChannelId}
|
||||
membersCount={displayName.split(',').length}
|
||||
shared={channel.shared && channel.type !== General.DM_CHANNEL}
|
||||
statusStyle={{backgroundColor: theme.sidebarBg, borderColor: 'transparent'}}
|
||||
size={24}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import GroupTypes from './groups';
|
|||
import BotTypes from './bots';
|
||||
import PluginTypes from './plugins';
|
||||
import ChannelCategoryTypes from './channel_categories';
|
||||
import RemoteClusterTypes from './remote_cluster';
|
||||
import AppsTypes from './apps';
|
||||
|
||||
export {
|
||||
|
|
@ -38,5 +39,6 @@ export {
|
|||
BotTypes,
|
||||
PluginTypes,
|
||||
ChannelCategoryTypes,
|
||||
RemoteClusterTypes,
|
||||
AppsTypes,
|
||||
};
|
||||
|
|
|
|||
8
app/mm-redux/action_types/remote_cluster.ts
Normal file
8
app/mm-redux/action_types/remote_cluster.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import keyMirror from '@mm-redux/utils/key_mirror';
|
||||
|
||||
export default keyMirror({
|
||||
REMOTE_CLUSTER_INFO_RECEIVED: null,
|
||||
});
|
||||
|
|
@ -19,6 +19,7 @@ import {Action, ActionFunc, batchActions, DispatchFunc, GetStateFunc} from '@mm-
|
|||
import {Channel, ChannelNotifyProps, ChannelMembership} from '@mm-redux/types/channels';
|
||||
|
||||
import {PreferenceType} from '@mm-redux/types/preferences';
|
||||
import {Dictionary} from '@mm-redux/types/utilities';
|
||||
|
||||
import {logError} from './errors';
|
||||
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
|
||||
|
|
@ -939,6 +940,44 @@ export function getArchivedChannels(teamId: string, page = 0, perPage: number =
|
|||
};
|
||||
}
|
||||
|
||||
// Returns all public shared channels
|
||||
export function getSharedChannels(teamId: string, page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
let channels;
|
||||
try {
|
||||
channels = await Client4.getSharedChannels(teamId, page, perPage);
|
||||
channels = (channels || []).map((channel: Dictionary<string|boolean|number>) => {
|
||||
if (channel.delete_at === undefined) {
|
||||
channel.delete_at = 0;
|
||||
}
|
||||
channel.type = General.OPEN_CHANNEL;
|
||||
channel.shared = true;
|
||||
return channel;
|
||||
});
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.GET_CHANNELS_FAILURE, error},
|
||||
logError(error),
|
||||
]));
|
||||
return {error};
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: ChannelTypes.RECEIVED_CHANNELS,
|
||||
teamId,
|
||||
data: channels,
|
||||
},
|
||||
{
|
||||
type: ChannelTypes.GET_CHANNELS_SUCCESS,
|
||||
},
|
||||
]));
|
||||
|
||||
return {data: channels};
|
||||
};
|
||||
}
|
||||
|
||||
export function getAllChannelsWithCount(page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE, notAssociatedToGroup = '', excludeDefaultChannels = false): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
dispatch({type: ChannelTypes.GET_ALL_CHANNELS_REQUEST, data: null});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import * as search from './search';
|
|||
import * as teams from './teams';
|
||||
import * as timezone from './timezone';
|
||||
import * as users from './users';
|
||||
import * as remoteCluster from './remote_cluster';
|
||||
|
||||
export {
|
||||
bots,
|
||||
|
|
@ -35,5 +36,6 @@ export {
|
|||
teams,
|
||||
timezone,
|
||||
users,
|
||||
remoteCluster,
|
||||
};
|
||||
|
||||
|
|
|
|||
15
app/mm-redux/actions/remote_cluster.ts
Normal file
15
app/mm-redux/actions/remote_cluster.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {Client4} from '@client/rest';
|
||||
import {RemoteClusterTypes} from '@mm-redux/action_types';
|
||||
|
||||
import {bindClientFunc} from './helpers';
|
||||
export function getRemoteClusterInfo(remote_id: string) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getRemoteClusterInfo,
|
||||
onSuccess: [RemoteClusterTypes.REMOTE_CLUSTER_INFO_RECEIVED],
|
||||
params: [
|
||||
remote_id,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import roles from './roles';
|
|||
import groups from './groups';
|
||||
import bots from './bots';
|
||||
import channelCategories from './channel_categories';
|
||||
import remoteCluster from './remote_cluster';
|
||||
import apps from './apps';
|
||||
|
||||
export default combineReducers({
|
||||
|
|
@ -38,5 +39,6 @@ export default combineReducers({
|
|||
groups,
|
||||
bots,
|
||||
channelCategories,
|
||||
remoteCluster,
|
||||
apps,
|
||||
});
|
||||
|
|
|
|||
24
app/mm-redux/reducers/entities/remote_cluster.ts
Normal file
24
app/mm-redux/reducers/entities/remote_cluster.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
import {RemoteClusterTypes} from '@mm-redux/action_types';
|
||||
import {GenericAction} from '@mm-redux/types/actions';
|
||||
import {Dictionary} from '@mm-redux/types/utilities';
|
||||
import {RemoteCluster} from '@mm-redux/types/remote_cluster';
|
||||
|
||||
function info(state: Dictionary<RemoteCluster> = {}, action: GenericAction) {
|
||||
switch (action.type) {
|
||||
case RemoteClusterTypes.REMOTE_CLUSTER_INFO_RECEIVED: {
|
||||
const nextState = {...state};
|
||||
nextState[action.data.remote_id] = action.data;
|
||||
return nextState;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
info,
|
||||
});
|
||||
|
|
@ -106,3 +106,18 @@ export type ChannelMemberCountByGroup = {
|
|||
};
|
||||
|
||||
export type ChannelMemberCountsByGroup = Record<string, ChannelMemberCountByGroup>;
|
||||
|
||||
export type SharedChannel = {
|
||||
channel_id: string;
|
||||
team_id: string;
|
||||
home: boolean;
|
||||
readonly: boolean;
|
||||
share_name: string;
|
||||
share_displayname: string;
|
||||
share_purpose: string;
|
||||
share_header: string;
|
||||
creator_id: string;
|
||||
create_at: number;
|
||||
update_at: number;
|
||||
remote_id: string;
|
||||
};
|
||||
|
|
|
|||
7
app/mm-redux/types/remote_cluster.ts
Normal file
7
app/mm-redux/types/remote_cluster.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
export type RemoteCluster = {
|
||||
display_name: string;
|
||||
create_at: number;
|
||||
remote_id: string;
|
||||
};
|
||||
|
|
@ -17,6 +17,7 @@ import {Role} from './roles';
|
|||
import {PreferenceType} from './preferences';
|
||||
import {Bot} from './bots';
|
||||
import {ChannelCategoriesState} from './channel_categories';
|
||||
import {RemoteCluster} from './remote_cluster';
|
||||
import {Dictionary} from './utilities';
|
||||
import {AppsState} from './apps';
|
||||
|
||||
|
|
@ -49,6 +50,11 @@ export type GlobalState = {
|
|||
gifs: any;
|
||||
groups: GroupsState;
|
||||
channelCategories: ChannelCategoriesState;
|
||||
remoteCluster: {
|
||||
info: {
|
||||
[x: string]: RemoteCluster;
|
||||
};
|
||||
};
|
||||
apps: AppsState;
|
||||
};
|
||||
errors: Array<any>;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export type UserProfile = {
|
|||
last_name: string;
|
||||
position: string;
|
||||
roles: string;
|
||||
remote_id?: string;
|
||||
locale: string;
|
||||
notify_props: UserNotifyProps;
|
||||
terms_of_service_id: string;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ export function isChannelAdmin(roles: string): boolean {
|
|||
return rolesIncludePermission(roles, General.CHANNEL_ADMIN_ROLE);
|
||||
}
|
||||
|
||||
export function isShared(user: UserProfile): boolean {
|
||||
return Boolean(user.remote_id);
|
||||
}
|
||||
|
||||
export function hasUserAccessTokenRole(roles: string): boolean {
|
||||
return rolesIncludePermission(roles, General.SYSTEM_USER_ACCESS_TOKEN_ROLE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ exports[`ChannelTitle should match snapshot 1`] = `
|
|||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
"width": "90%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -28,6 +27,8 @@ exports[`ChannelTitle should match snapshot 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold",
|
||||
"textAlign": "center",
|
||||
|
|
@ -69,7 +70,6 @@ exports[`ChannelTitle should match snapshot when is DM and has guests and the te
|
|||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
"width": "90%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -79,6 +79,8 @@ exports[`ChannelTitle should match snapshot when is DM and has guests and the te
|
|||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold",
|
||||
"textAlign": "center",
|
||||
|
|
@ -145,7 +147,6 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
|
|||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
"width": "90%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -155,6 +156,8 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
|
|||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold",
|
||||
"textAlign": "center",
|
||||
|
|
@ -178,6 +181,77 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
|
|||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelTitle should match snapshot when isChannelShared is true 1`] = `
|
||||
<ForwardRef
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="channel.title.button"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
testID="channel.nav_bar.title"
|
||||
>
|
||||
My User
|
||||
</Text>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={true}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={false}
|
||||
isUnread={false}
|
||||
membersCount={0}
|
||||
shared={true}
|
||||
size={18}
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 3,
|
||||
"marginRight": 0,
|
||||
}
|
||||
}
|
||||
theme={Object {}}
|
||||
type="P"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="chevron-down"
|
||||
size={24}
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"marginHorizontal": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
||||
<ForwardRef
|
||||
style={
|
||||
|
|
@ -196,7 +270,6 @@ exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
|||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
"width": "90%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -206,6 +279,8 @@ exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold",
|
||||
"textAlign": "center",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
|
@ -19,17 +20,18 @@ import {t} from '@utils/i18n';
|
|||
|
||||
export default class ChannelTitle extends PureComponent {
|
||||
static propTypes = {
|
||||
canHaveSubtitle: PropTypes.bool.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
currentChannelName: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
channelType: PropTypes.string,
|
||||
hasGuests: PropTypes.bool.isRequired,
|
||||
isArchived: PropTypes.bool,
|
||||
isChannelMuted: PropTypes.bool,
|
||||
isChannelShared: PropTypes.bool,
|
||||
isGuest: PropTypes.bool.isRequired,
|
||||
isSelfDMChannel: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object,
|
||||
isArchived: PropTypes.bool,
|
||||
isGuest: PropTypes.bool.isRequired,
|
||||
hasGuests: PropTypes.bool.isRequired,
|
||||
canHaveSubtitle: PropTypes.bool.isRequired,
|
||||
isSelfDMChannel: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -119,7 +121,9 @@ export default class ChannelTitle extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
channelType,
|
||||
isChannelMuted,
|
||||
isChannelShared,
|
||||
onPress,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
|
@ -150,6 +154,22 @@ export default class ChannelTitle extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
let channelIcon;
|
||||
if (isChannelShared) {
|
||||
channelIcon = (
|
||||
<ChannelIcon
|
||||
isActive={true}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
size={18}
|
||||
shared={isChannelShared}
|
||||
style={style.channelIconContainer}
|
||||
theme={theme}
|
||||
type={channelType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID={'channel.title.button'}
|
||||
|
|
@ -166,6 +186,7 @@ export default class ChannelTitle extends PureComponent {
|
|||
>
|
||||
{channelDisplayName}
|
||||
</Text>
|
||||
{channelIcon}
|
||||
{icon}
|
||||
{mutedIcon}
|
||||
</View>
|
||||
|
|
@ -187,7 +208,6 @@ const getStyle = makeStyleSheetFromTheme((theme) => {
|
|||
top: -1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
width: '90%',
|
||||
},
|
||||
icon: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
|
|
@ -198,6 +218,12 @@ const getStyle = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
flex: 0,
|
||||
flexShrink: 1,
|
||||
},
|
||||
channelIconContainer: {
|
||||
marginLeft: 3,
|
||||
marginRight: 0,
|
||||
},
|
||||
muted: {
|
||||
marginTop: 1,
|
||||
|
|
|
|||
|
|
@ -73,4 +73,19 @@ describe('ChannelTitle', () => {
|
|||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when isChannelShared is true', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
displayName: 'My User',
|
||||
isChannelShared: true,
|
||||
channelType: General.PRIVATE_CHANNEL,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<ChannelTitle {...props}/>,
|
||||
{context: {intl: {formatMessage: (intlId) => intlId.defaultMessage}}},
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,15 +29,16 @@ function mapStateToProps(state) {
|
|||
}
|
||||
|
||||
return {
|
||||
isSelfDMChannel,
|
||||
currentChannelName: currentChannel ? currentChannel.display_name : '',
|
||||
isArchived: currentChannel ? currentChannel.delete_at !== 0 : false,
|
||||
displayName: state.views.channel.displayName,
|
||||
channelType: currentChannel?.type,
|
||||
isChannelMuted: isChannelMuted(myChannelMember),
|
||||
theme: getTheme(state),
|
||||
isGuest: isTeammateGuest,
|
||||
currentChannelName: currentChannel ? currentChannel.display_name : '',
|
||||
displayName: state.views.channel.displayName,
|
||||
hasGuests: stats.guest_count > 0,
|
||||
isArchived: currentChannel ? currentChannel.delete_at !== 0 : false,
|
||||
isChannelMuted: isChannelMuted(myChannelMember),
|
||||
isChannelShared: currentChannel?.shared,
|
||||
isGuest: isTeammateGuest,
|
||||
isSelfDMChannel,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
memberCount={currentChannelMemberCount}
|
||||
onPermalinkPress={this.handlePermalinkPress}
|
||||
purpose={currentChannel.purpose}
|
||||
shared={currentChannel.shared}
|
||||
teammateId={teammateId}
|
||||
theme={theme}
|
||||
type={currentChannel.type}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
header: PropTypes.string,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
purpose: PropTypes.string,
|
||||
shared: PropTypes.bool,
|
||||
teammateId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
|
|
@ -133,6 +134,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
memberCount,
|
||||
onPermalinkPress,
|
||||
purpose,
|
||||
shared,
|
||||
teammateId,
|
||||
theme,
|
||||
type,
|
||||
|
|
@ -158,6 +160,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
membersCount={memberCount}
|
||||
size={24}
|
||||
userId={teammateId}
|
||||
shared={shared}
|
||||
theme={theme}
|
||||
type={type}
|
||||
isArchived={isArchived}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import SearchBar from '@components/search_bar';
|
|||
import StatusBar from '@components/status_bar';
|
||||
import {debounce} from '@mm-redux/actions/helpers';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {filterProfilesMatchingTerm} from '@mm-redux/utils/user_utils';
|
||||
import {filterProfilesMatchingTerm, isShared} from '@mm-redux/utils/user_utils';
|
||||
import {alertErrorIfInvalidPermissions} from '@utils/general';
|
||||
import {createProfilesSections, loadingText} from '@utils/member_list';
|
||||
import {
|
||||
|
|
@ -243,7 +243,7 @@ export default class ChannelMembers extends PureComponent {
|
|||
const selectProps = {
|
||||
selectable: true,
|
||||
selected: this.state.selectedIds[props.id],
|
||||
enabled: props.id !== this.props.currentUserId,
|
||||
enabled: props.id !== this.props.currentUserId && !isShared(props.item),
|
||||
};
|
||||
|
||||
return this.renderItem(props, selectProps);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@
|
|||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {handleSelectChannel, setChannelDisplayName} from '@actions/views/channel';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {getArchivedChannels, getChannels, joinChannel, searchChannels} from '@mm-redux/actions/channels';
|
||||
import {getChannelsInCurrentTeam, getMyChannelMemberships} from '@mm-redux/selectors/entities/channels';
|
||||
import {getArchivedChannels, getChannels, getSharedChannels, joinChannel, searchChannels} from '@mm-redux/actions/channels';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from '@mm-redux/selectors/entities/users';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {showCreateOption} from '@mm-redux/utils/channel_utils';
|
||||
|
|
@ -17,30 +15,19 @@ import {isAdmin, isSystemAdmin} from '@mm-redux/utils/user_utils';
|
|||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getConfig, getLicense} from '@mm-redux/selectors/entities/general';
|
||||
|
||||
import {teamArchivedChannels, joinablePublicChannels, joinableSharedChannels} from '@selectors/channel';
|
||||
|
||||
import MoreChannels from './more_channels';
|
||||
|
||||
const joinablePublicChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
getMyChannelMemberships,
|
||||
(channels, myMembers) => {
|
||||
return channels.filter((c) => {
|
||||
return (!myMembers[c.id] && c.type === General.OPEN_CHANNEL && c.delete_at === 0);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const teamArchivedChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
(channels) => {
|
||||
return channels.filter((c) => c.delete_at !== 0);
|
||||
},
|
||||
);
|
||||
const defaultSharedChannels = [];
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const config = getConfig(state);
|
||||
const license = getLicense(state);
|
||||
const sharedChannelsEnabled = config.ExperimentalSharedChannels === 'true';
|
||||
const roles = getCurrentUserRoles(state);
|
||||
const channels = joinablePublicChannels(state);
|
||||
const sharedChannels = sharedChannelsEnabled ? joinableSharedChannels(state) : defaultSharedChannels;
|
||||
const archivedChannels = teamArchivedChannels(state);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const canShowArchivedChannels = config.ExperimentalViewArchivedChannels === 'true' &&
|
||||
|
|
@ -51,6 +38,8 @@ function mapStateToProps(state) {
|
|||
currentUserId: getCurrentUserId(state),
|
||||
currentTeamId,
|
||||
channels,
|
||||
sharedChannels,
|
||||
sharedChannelsEnabled,
|
||||
archivedChannels,
|
||||
theme: getTheme(state),
|
||||
canShowArchivedChannels,
|
||||
|
|
@ -62,6 +51,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
getArchivedChannels,
|
||||
getChannels,
|
||||
getSharedChannels,
|
||||
handleSelectChannel,
|
||||
joinChannel,
|
||||
searchChannels,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default class MoreChannels extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
getArchivedChannels: PropTypes.func.isRequired,
|
||||
getChannels: PropTypes.func.isRequired,
|
||||
getSharedChannels: PropTypes.func.isRequired,
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
joinChannel: PropTypes.func.isRequired,
|
||||
searchChannels: PropTypes.func.isRequired,
|
||||
|
|
@ -42,6 +43,8 @@ export default class MoreChannels extends PureComponent {
|
|||
componentId: PropTypes.string,
|
||||
canCreateChannels: PropTypes.bool.isRequired,
|
||||
channels: PropTypes.array,
|
||||
sharedChannels: PropTypes.array,
|
||||
sharedChannelsEnabled: PropTypes.bool,
|
||||
archivedChannels: PropTypes.array,
|
||||
closeButton: PropTypes.object,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
|
|
@ -63,13 +66,16 @@ export default class MoreChannels extends PureComponent {
|
|||
|
||||
this.searchTimeoutId = 0;
|
||||
this.publicPage = -1;
|
||||
this.sharedPage = -1;
|
||||
this.archivedPage = -1;
|
||||
this.nextPublic = true;
|
||||
this.nextShared = true;
|
||||
this.nextArchived = true;
|
||||
this.mounted = false;
|
||||
|
||||
this.state = {
|
||||
channels: props.channels.slice(0, General.CHANNELS_CHUNK_SIZE),
|
||||
sharedChannels: props.sharedChannels.slice(0, General.CHANNELS_CHUNK_SIZE),
|
||||
archivedChannels: props.archivedChannels.slice(0, General.CHANNELS_CHUNK_SIZE),
|
||||
typeOfChannels: 'public',
|
||||
loading: false,
|
||||
|
|
@ -186,6 +192,15 @@ export default class MoreChannels extends PureComponent {
|
|||
).then(this.loadedChannels);
|
||||
}
|
||||
break;
|
||||
case 'shared':
|
||||
if (this.nextShared) {
|
||||
actions.getSharedChannels(
|
||||
currentTeamId,
|
||||
this.sharedPage + 1,
|
||||
General.CHANNELS_CHUNK_SIZE,
|
||||
).then(this.loadedChannels);
|
||||
}
|
||||
break;
|
||||
case 'archived':
|
||||
if (canShowArchivedChannels && this.nextArchived) {
|
||||
actions.getArchivedChannels(
|
||||
|
|
@ -226,8 +241,12 @@ export default class MoreChannels extends PureComponent {
|
|||
if (this.mounted) {
|
||||
const {typeOfChannels} = this.state;
|
||||
const isPublic = typeOfChannels === 'public';
|
||||
const isShared = typeOfChannels === 'shared';
|
||||
|
||||
if (isPublic) {
|
||||
if (isShared) {
|
||||
this.sharedPage += 1;
|
||||
this.nextShared = data?.length > 0;
|
||||
} else if (isPublic) {
|
||||
this.publicPage += 1;
|
||||
this.nextPublic = data?.length > 0;
|
||||
} else {
|
||||
|
|
@ -355,7 +374,7 @@ export default class MoreChannels extends PureComponent {
|
|||
}
|
||||
|
||||
searchChannels = (text) => {
|
||||
const {actions, channels, archivedChannels, currentTeamId, canShowArchivedChannels} = this.props;
|
||||
const {actions, channels, sharedChannels, archivedChannels, currentTeamId, canShowArchivedChannels} = this.props;
|
||||
const {typeOfChannels} = this.state;
|
||||
|
||||
if (text) {
|
||||
|
|
@ -366,6 +385,13 @@ export default class MoreChannels extends PureComponent {
|
|||
term: text,
|
||||
});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
} else if (typeOfChannels === 'shared') {
|
||||
const filtered = this.filterChannels(sharedChannels, text);
|
||||
this.setState({
|
||||
sharedChannels: filtered,
|
||||
term: text,
|
||||
});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
} else if (typeOfChannels === 'archived' && canShowArchivedChannels) {
|
||||
const filtered = this.filterChannels(archivedChannels, text);
|
||||
this.setState({
|
||||
|
|
@ -384,13 +410,25 @@ export default class MoreChannels extends PureComponent {
|
|||
|
||||
handleDropdownClick = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const titleText = formatMessage({id: 'more_channels.dropdownTitle', defaultMessage: 'Show'});
|
||||
const publicChannelsText = formatMessage({id: 'more_channels.publicChannels', defaultMessage: 'Public Channels'});
|
||||
const archivedChannelsText = formatMessage({id: 'more_channels.archivedChannels', defaultMessage: 'Archived Channels'});
|
||||
const titleText = formatMessage({id: 'more_channels.dropdownTitle', defaultMessage: 'Show'});
|
||||
const cancelText = 'Cancel';
|
||||
const options = [publicChannelsText, archivedChannelsText, cancelText];
|
||||
|
||||
let archivedChannelsIndex = 1;
|
||||
let sharedChannelsIndex;
|
||||
|
||||
if (this.props.sharedChannelsEnabled) {
|
||||
const sharedChannelsText = formatMessage({id: 'more_channels.sharedChannels', defaultMessage: 'Shared Channels'});
|
||||
options.splice(1, 0, sharedChannelsText);
|
||||
sharedChannelsIndex = 1;
|
||||
archivedChannelsIndex = 2;
|
||||
}
|
||||
|
||||
BottomSheet.showBottomSheetWithOptions({
|
||||
options: [publicChannelsText, archivedChannelsText, cancelText],
|
||||
cancelButtonIndex: 2,
|
||||
options,
|
||||
cancelButtonIndex: 3,
|
||||
title: titleText,
|
||||
}, (value) => {
|
||||
let typeOfChannels;
|
||||
|
|
@ -398,7 +436,10 @@ export default class MoreChannels extends PureComponent {
|
|||
case 0:
|
||||
typeOfChannels = 'public';
|
||||
break;
|
||||
case 1:
|
||||
case sharedChannelsIndex:
|
||||
typeOfChannels = 'shared';
|
||||
break;
|
||||
case archivedChannelsIndex:
|
||||
typeOfChannels = 'archived';
|
||||
break;
|
||||
default:
|
||||
|
|
@ -413,14 +454,11 @@ export default class MoreChannels extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {theme, canShowArchivedChannels} = this.props;
|
||||
const {adding, channels, archivedChannels, loading, term, typeOfChannels} = this.state;
|
||||
const {canShowArchivedChannels, sharedChannelsEnabled, theme} = this.props;
|
||||
const {adding, channels, sharedChannels, archivedChannels, loading, term, typeOfChannels} = this.state;
|
||||
const more = term ? emptyFunction : this.getChannels;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
const publicChannelsText = formatMessage({id: 'more_channels.showPublicChannels', defaultMessage: 'Show: Public Channels'});
|
||||
const archivedChannelsText = formatMessage({id: 'more_channels.showArchivedChannels', defaultMessage: 'Show: Archived Channels'});
|
||||
|
||||
let content;
|
||||
if (adding) {
|
||||
content = (<Loading color={theme.centerChannelColor}/>);
|
||||
|
|
@ -433,12 +471,22 @@ export default class MoreChannels extends PureComponent {
|
|||
|
||||
let activeChannels = channels;
|
||||
|
||||
if (canShowArchivedChannels && typeOfChannels === 'archived') {
|
||||
if (typeOfChannels === 'shared') {
|
||||
activeChannels = sharedChannels;
|
||||
} else if (canShowArchivedChannels && typeOfChannels === 'archived') {
|
||||
activeChannels = archivedChannels;
|
||||
}
|
||||
|
||||
let channelDropdown;
|
||||
if (canShowArchivedChannels) {
|
||||
if (canShowArchivedChannels || sharedChannelsEnabled) {
|
||||
let channelDropdownText;
|
||||
if (typeOfChannels === 'public') {
|
||||
channelDropdownText = formatMessage({id: 'more_channels.showPublicChannels', defaultMessage: 'Show: Public Channels'});
|
||||
} else if (typeOfChannels === 'shared') {
|
||||
channelDropdownText = formatMessage({id: 'more_channels.showSharedChannels', defaultMessage: 'Show: Shared Channels'});
|
||||
} else {
|
||||
channelDropdownText = formatMessage({id: 'more_channels.showArchivedChannels', defaultMessage: 'Show: Archived Channels'});
|
||||
}
|
||||
channelDropdown = (
|
||||
<View
|
||||
style={style.titleContainer}
|
||||
|
|
@ -450,7 +498,7 @@ export default class MoreChannels extends PureComponent {
|
|||
onPress={this.handleDropdownClick}
|
||||
testID={`more_channels.channel.dropdown.${typeOfChannels}`}
|
||||
>
|
||||
{typeOfChannels === 'public' ? publicChannelsText : archivedChannelsText}
|
||||
{channelDropdownText}
|
||||
{' '}
|
||||
<CompassIcon
|
||||
name='menu-down'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ describe('MoreChannels', () => {
|
|||
joinChannel: jest.fn(),
|
||||
getArchivedChannels: jest.fn().mockResolvedValue({data: [{id: 'id2', name: 'name2', display_name: 'display_name2', delete_at: 123}]}),
|
||||
getChannels: jest.fn().mockResolvedValue({data: [{id: 'id', name: 'name', display_name: 'display_name'}]}),
|
||||
getSharedChannels: jest.fn().mockResolvedValue({data: [{id: 'id3', name: 'shared_channel', display_name: 'shared_channel_name', shared: true}]}),
|
||||
searchChannels: jest.fn(),
|
||||
setChannelDisplayName: jest.fn(),
|
||||
};
|
||||
|
|
@ -26,6 +27,7 @@ describe('MoreChannels', () => {
|
|||
actions,
|
||||
canCreateChannels: true,
|
||||
channels: [{id: 'id', name: 'name', display_name: 'display_name'}],
|
||||
sharedChannels: [{id: 'id3', name: 'shared_channel', display_name: 'shared_channel_name'}],
|
||||
archivedChannels: [{id: 'id2', name: 'archived', display_name: 'archived channel', delete_at: 123}],
|
||||
closeButton: {},
|
||||
currentUserId: 'current_user_id',
|
||||
|
|
@ -112,6 +114,10 @@ describe('MoreChannels', () => {
|
|||
wrapper.setState({typeOfChannels: 'archived'});
|
||||
instance.searchChannels('archived channel');
|
||||
expect(wrapper.state('archivedChannels')).toEqual(baseProps.archivedChannels);
|
||||
|
||||
wrapper.setState({typeOfChannels: 'shared'});
|
||||
instance.searchChannels('shared');
|
||||
expect(wrapper.state('sharedChannels')).toEqual(baseProps.sharedChannels);
|
||||
});
|
||||
|
||||
test('Allow load more public channels', () => {
|
||||
|
|
@ -163,4 +169,29 @@ describe('MoreChannels', () => {
|
|||
instance.loadedChannels({data: []});
|
||||
expect(instance.nextArchived).toBe(false);
|
||||
});
|
||||
|
||||
test('Allow load more shared channels', () => {
|
||||
const wrapper = shallow(
|
||||
<MoreChannels {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
wrapper.setState({typeOfChannels: 'shared'});
|
||||
instance.loadedChannels({data: ['shared-1', 'shared-2']});
|
||||
expect(instance.nextShared).toBe(true);
|
||||
});
|
||||
|
||||
test('Prevent load more shared channels', () => {
|
||||
const wrapper = shallow(
|
||||
<MoreChannels {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
wrapper.setState({typeOfChannels: 'shared'});
|
||||
instance.loadedChannels({data: null});
|
||||
expect(instance.nextShared).toBe(false);
|
||||
|
||||
instance.loadedChannels({data: []});
|
||||
expect(instance.nextShared).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -154,3 +154,234 @@ exports[`user_profile should match snapshot 1`] = `
|
|||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
||||
exports[`user_profile should match snapshot when user is from remote 1`] = `
|
||||
<RNCSafeAreaView
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="user_profile.screen"
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"paddingBottom": 48,
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="user_profile.scroll_view"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"padding": 25,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={104}
|
||||
size={153}
|
||||
statusBorderWidth={6}
|
||||
statusSize={36}
|
||||
testID="user_profile.profile_picture"
|
||||
userId="4hzdnk6mg7gepe7yze6m3domnc"
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
testID="user_profile.username"
|
||||
>
|
||||
@fred
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EBEBEC",
|
||||
"height": 1,
|
||||
"marginLeft": 16,
|
||||
"marginRight": 22,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 25,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
testID="user_profile.display_block"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
"marginBottom": 10,
|
||||
"marginTop": 25,
|
||||
"textTransform": "uppercase",
|
||||
}
|
||||
}
|
||||
testID="user_profile.display_block.nickname.label"
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
testID="user_profile.display_block.nickname.value"
|
||||
>
|
||||
nick
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<FormattedText
|
||||
defaultMessage="ORGANIZATION"
|
||||
id="mobile.routes.user_profile.organization"
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
"marginBottom": 10,
|
||||
"marginTop": 25,
|
||||
"textTransform": "uppercase",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={true}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={0}
|
||||
shared={true}
|
||||
size={16}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
type="O"
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
Remote Organization
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EBEBEC",
|
||||
"height": 1,
|
||||
"marginLeft": 16,
|
||||
"marginRight": 22,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<userProfileRow
|
||||
action={[Function]}
|
||||
defaultMessage="Send Message"
|
||||
icon="send"
|
||||
iconColor="rgba(0, 0, 0, 0.7)"
|
||||
iconSize={24}
|
||||
testID="user_profile.send_message.action"
|
||||
textColor="#000"
|
||||
textId="mobile.routes.user_profile.send_message"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {getTeammateNameDisplaySetting, getTheme, getBool} from '@mm-redux/select
|
|||
import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import {loadBot} from '@mm-redux/actions/bots';
|
||||
import {getRemoteClusterInfo} from '@mm-redux/actions/remote_cluster';
|
||||
import {getBotAccounts} from '@mm-redux/selectors/entities/bots';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
|
|
@ -21,18 +22,20 @@ function mapStateToProps(state, ownProps) {
|
|||
const {createChannel: createChannelRequest} = state.requests.channels;
|
||||
const militaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const user = state.entities.users.profiles[ownProps.userId];
|
||||
|
||||
return {
|
||||
config,
|
||||
createChannelRequest,
|
||||
currentDisplayName: state.views.channel.displayName,
|
||||
user: state.entities.users.profiles[ownProps.userId],
|
||||
user,
|
||||
bot: getBotAccounts(state)[ownProps.userId],
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
enableTimezone,
|
||||
militaryTime,
|
||||
theme: getTheme(state),
|
||||
isMyUser: getCurrentUserId(state) === ownProps.userId,
|
||||
remoteClusterInfo: state.entities.remoteCluster.info[user?.remote_id],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +45,7 @@ function mapDispatchToProps(dispatch) {
|
|||
makeDirectChannel,
|
||||
setChannelDisplayName,
|
||||
loadBot,
|
||||
getRemoteClusterInfo,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ import {
|
|||
dismissAllModalsAndPopToRoot,
|
||||
} from '@actions/navigation';
|
||||
import Config from '@assets/config';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import FormattedTime from '@components/formatted_time';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {getUserCurrentTimezone} from '@mm-redux/utils/timezone_utils';
|
||||
import {alertErrorWithFallback} from '@utils/general';
|
||||
|
|
@ -41,6 +43,7 @@ export default class UserProfile extends PureComponent {
|
|||
makeDirectChannel: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
loadBot: PropTypes.func.isRequired,
|
||||
getRemoteClusterInfo: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string,
|
||||
config: PropTypes.object.isRequired,
|
||||
|
|
@ -52,6 +55,7 @@ export default class UserProfile extends PureComponent {
|
|||
militaryTime: PropTypes.bool.isRequired,
|
||||
enableTimezone: PropTypes.bool.isRequired,
|
||||
isMyUser: PropTypes.bool.isRequired,
|
||||
remoteClusterInfo: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -82,8 +86,14 @@ export default class UserProfile extends PureComponent {
|
|||
componentDidMount() {
|
||||
this.navigationEventListener = Navigation.events().bindComponent(this);
|
||||
|
||||
if (this.props.user && this.props.user.is_bot) {
|
||||
this.props.actions.loadBot(this.props.user.id);
|
||||
const {user} = this.props;
|
||||
if (user) {
|
||||
if (user.is_bot) {
|
||||
this.props.actions.loadBot(user.id);
|
||||
}
|
||||
if (user.remote_id) {
|
||||
this.props.actions.getRemoteClusterInfo(user.remote_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +196,36 @@ export default class UserProfile extends PureComponent {
|
|||
return null;
|
||||
};
|
||||
|
||||
buildOrganizationBlock = () => {
|
||||
const {theme, remoteClusterInfo} = this.props;
|
||||
if (!remoteClusterInfo) {
|
||||
return null;
|
||||
}
|
||||
const style = createStyleSheet(theme);
|
||||
return (
|
||||
<View>
|
||||
<FormattedText
|
||||
id='mobile.routes.user_profile.organization'
|
||||
defaultMessage='ORGANIZATION'
|
||||
style={style.header}
|
||||
/>
|
||||
<View style={style.organizationDataContainer}>
|
||||
<ChannelIcon
|
||||
isActive={true}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
size={16}
|
||||
shared={true}
|
||||
theme={theme}
|
||||
type={General.OPEN_CHANNEL}
|
||||
/>
|
||||
<Text style={style.text}>{remoteClusterInfo.display_name}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
buildTimezoneBlock = () => {
|
||||
const {theme, user, militaryTime} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
|
|
@ -337,6 +377,7 @@ export default class UserProfile extends PureComponent {
|
|||
{this.props.config.ShowFullName === 'true' && this.buildDisplayBlock('last_name')}
|
||||
{this.props.config.ShowEmailAddress === 'true' && this.buildDisplayBlock('email')}
|
||||
{this.buildDisplayBlock('nickname')}
|
||||
{this.buildOrganizationBlock()}
|
||||
{this.buildDisplayBlock('position')}
|
||||
{this.props.enableTimezone && this.buildTimezoneBlock()}
|
||||
</View>
|
||||
|
|
@ -451,6 +492,10 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
marginRight: 22,
|
||||
backgroundColor: '#EBEBEC',
|
||||
},
|
||||
organizationDataContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ describe('user_profile', () => {
|
|||
setChannelDisplayName: jest.fn(),
|
||||
makeDirectChannel: jest.fn(),
|
||||
loadBot: jest.fn(),
|
||||
getRemoteClusterInfo: jest.fn(),
|
||||
};
|
||||
const baseProps = {
|
||||
actions,
|
||||
|
|
@ -128,6 +129,25 @@ describe('user_profile', () => {
|
|||
expect(goToScreen).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should match snapshot when user is from remote', () => {
|
||||
const remoteUser = {
|
||||
...user,
|
||||
remote_id: 'sr23g5h456',
|
||||
};
|
||||
const clusterInfo = {
|
||||
display_name: 'Remote Organization',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<UserProfile
|
||||
{...baseProps}
|
||||
remoteClusterInfo={clusterInfo}
|
||||
user={remoteUser}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call goToEditProfile', () => {
|
||||
const goToScreen = jest.spyOn(NavigationActions, 'goToScreen');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getChannelByName} from '@mm-redux/selectors/entities/channels';
|
||||
import {getChannelByName, getChannelsInCurrentTeam, getMyChannelMemberships} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTeamByName} from '@mm-redux/selectors/entities/teams';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {isArchivedChannel} from '@mm-redux/utils/channel_utils';
|
||||
|
|
@ -60,3 +61,30 @@ export const getChannelReachable = createSelector(
|
|||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
export const joinablePublicChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
getMyChannelMemberships,
|
||||
(channels, myMembers) => {
|
||||
return channels.filter((c) => {
|
||||
return (!myMembers[c.id] && c.type === General.OPEN_CHANNEL && c.delete_at === 0);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export const joinableSharedChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
getMyChannelMemberships,
|
||||
(channels, myMembers) => {
|
||||
return channels.filter((c) => {
|
||||
return (!myMembers[c.id] && c.type === General.OPEN_CHANNEL && c.shared === true && c.delete_at === 0);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export const teamArchivedChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
(channels) => {
|
||||
return channels.filter((c) => c.delete_at !== 0);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -454,6 +454,34 @@
|
|||
"send-outline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "4dae60c666dc2fac9f861d9e70166d25",
|
||||
"css": "circle-multiple-outline-lock",
|
||||
"code": 59422,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M916.7 666.7V604.2C916.7 523.6 851.4 458.3 770.8 458.3S625 523.6 625 604.2V666.7C602 666.7 583.3 685.3 583.3 708.3V916.7C583.3 939.7 602 958.3 625 958.3H916.7C939.7 958.3 958.3 939.7 958.3 916.7V708.3C958.3 685.3 939.7 666.7 916.7 666.7ZM687.5 604.2C687.5 558.2 724.9 520.8 770.8 520.8S854.2 558.2 854.2 604.2V666.7H687.5V604.2ZM464.4 214.3C503 184.4 551.5 166.7 604.2 166.7 726.7 166.7 826.4 262.8 832.7 383.7 863.2 392.3 891.2 406.9 915.1 426.3 916.1 416.3 916.7 406.1 916.7 395.8 916.7 223.2 776.7 83.3 604.2 83.3 500.9 83.3 409.3 133.5 352.4 210.7 366.7 209.1 381.2 208.3 395.8 208.3 419.2 208.3 442.1 210.4 464.4 214.3ZM500 807.9C468.7 824 433.4 833.3 395.8 833.3 269.3 833.3 166.7 730.8 166.7 604.2S269.3 375 395.8 375C417.7 375 438.8 378 458.8 383.8 510 398.3 553.4 430.5 583.2 473 599.4 449.9 619.6 429.8 643 414.1 630.4 397.8 616.8 382.3 601.3 368.7 546.4 320.8 474.5 291.7 395.8 291.7 223.2 291.7 83.3 431.6 83.3 604.2S223.2 916.7 395.8 916.7C432.4 916.7 467.4 910.1 500 898.5V807.9ZM413.8 523.5C400.6 503.9 390.4 482.1 383.8 458.8 354.5 461.2 327.7 472.2 305.9 489.4 317.2 525.5 334.9 558.8 357.6 587.9 394.3 635 444.4 670.7 501.9 690.6 506.1 661.3 520 635.2 540.8 616.1 538.3 615.4 535.7 614.6 533.2 613.8 484 597.8 442.1 565.6 413.8 523.5Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"circle-multiple-outline-lock"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "a0d8bdcf347c632caf032dc70c78bb2a",
|
||||
"css": "circle-multiple-outline",
|
||||
"code": 984725,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M604.2 708.3C776.7 708.3 916.7 568.4 916.7 395.8 916.7 223.2 776.7 83.3 604.2 83.3 500.9 83.3 409.3 133.5 352.4 210.7 366.7 209.1 381.2 208.3 395.8 208.3 419.2 208.3 442.1 210.4 464.4 214.3 503 184.4 551.5 166.7 604.2 166.7 730.8 166.7 833.3 269.3 833.3 395.8 833.3 522.4 730.8 625 604.2 625 582.3 625 561.2 622 541.2 616.2 538.5 615.5 535.8 614.7 533.2 613.8 484 597.8 442.1 565.6 413.8 523.5 400.6 503.9 390.4 482.1 383.8 458.8 354.5 461.2 327.7 472.2 305.9 489.4 317.2 525.5 334.9 558.8 357.6 587.9 396.2 637.4 449.3 675 510.6 694.1 540.2 703.3 571.6 708.3 604.2 708.3ZM604.2 791.7C618.8 791.7 633.3 790.9 647.6 789.3 590.7 866.5 499.1 916.7 395.8 916.7 223.2 916.7 83.3 776.7 83.3 604.2 83.3 431.6 223.2 291.7 395.8 291.7 474.5 291.7 546.4 320.8 601.3 368.7L601.3 368.7C644.2 406.1 676.7 455 694.1 510.6 672.3 527.8 645.5 538.8 616.2 541.2 594.6 465.3 534.8 405.4 458.8 383.8V383.8C438.8 378 417.7 375 395.8 375 269.3 375 166.7 477.6 166.7 604.2 166.7 730.8 269.3 833.3 395.8 833.3 448.5 833.3 497 815.6 535.7 785.8 557.9 789.6 580.8 791.7 604.2 791.7Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"circle-multiple-outline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "3f2ee509f7e283a723125cb00de0b0ec",
|
||||
"css": "circle-outline",
|
||||
|
|
|
|||
|
|
@ -485,6 +485,7 @@
|
|||
"mobile.routes.user_profile": "Profile",
|
||||
"mobile.routes.user_profile.edit": "Edit",
|
||||
"mobile.routes.user_profile.local_time": "LOCAL TIME",
|
||||
"mobile.routes.user_profile.organization": "ORGANIZATION",
|
||||
"mobile.routes.user_profile.send_message": "Send Message",
|
||||
"mobile.search.after_modifier_description": "to find posts after a specific date",
|
||||
"mobile.search.before_modifier_description": "to find posts before a specific date",
|
||||
|
|
@ -567,8 +568,10 @@
|
|||
"more_channels.dropdownTitle": "Show",
|
||||
"more_channels.noMore": "No more channels to join",
|
||||
"more_channels.publicChannels": "Public Channels",
|
||||
"more_channels.sharedChannels": "Shared Channels",
|
||||
"more_channels.showArchivedChannels": "Show: Archived Channels",
|
||||
"more_channels.showPublicChannels": "Show: Public Channels",
|
||||
"more_channels.showSharedChannels": "Show: Shared Channels",
|
||||
"more_channels.title": "More Channels",
|
||||
"msg_typing.areTyping": "{users} and {last} are typing...",
|
||||
"msg_typing.isTyping": "{user} is typing...",
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in a new issue