MM-19337 Enable users to view archived channels (#3514)
* Archived channels dropdown in more channels modal * Rename redux actions for archived channels * Fixed tests and updated snapshots * Unit test for search in more_channels * Use translation for dropdown label * Minimum server requirement for dropdown * Use BottomSheet instead of Picker component * loadPublicAndArchivedChannels action instead of separate channel get actions * Add styles to StyleSheet * Update mattermost-redux hash * Update mattermost-redux hash * Default case for switching channels dropdown * Improve imports in more_channels.js component * Fix typo in import * Add padding to dropdown if landscape * Update snapshot * Page counter for public and archive channels * Updated mattermost-redux commit hash * Bottom sheet title for ios * i18n-extract for new showArchived and showPublic strings * Update mattermost-redux commit hash to latest master
This commit is contained in:
parent
fbf712dc8e
commit
6020df0b38
9 changed files with 200 additions and 31 deletions
|
|
@ -14,6 +14,8 @@ import {
|
|||
leaveChannel as serviceLeaveChannel,
|
||||
selectChannel,
|
||||
getChannelStats,
|
||||
getChannels,
|
||||
getArchivedChannels,
|
||||
} from 'mattermost-redux/actions/channels';
|
||||
import {
|
||||
getPosts,
|
||||
|
|
@ -73,6 +75,26 @@ export function loadChannelsByTeamName(teamName) {
|
|||
};
|
||||
}
|
||||
|
||||
export function loadPublicAndArchivedChannels(teamId, publicPage, archivedPage, perPage, shouldLoadArchivedChannels) {
|
||||
return async (dispatch) => {
|
||||
return dispatch(getChannels(
|
||||
teamId,
|
||||
publicPage,
|
||||
perPage
|
||||
)).then(async (publicChannels) => {
|
||||
if (shouldLoadArchivedChannels) {
|
||||
const archivedChannels = await dispatch(getArchivedChannels(
|
||||
teamId,
|
||||
archivedPage,
|
||||
perPage
|
||||
));
|
||||
return archivedChannels;
|
||||
}
|
||||
return publicChannels;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import CustomListRow from 'app/components/custom_list/custom_list_row';
|
|||
export default class ChannelListRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
isArchived: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
...CustomListRow.propTypes,
|
||||
|
|
@ -53,7 +54,7 @@ export default class ChannelListRow extends React.PureComponent {
|
|||
<View style={style.container}>
|
||||
<View style={style.titleContainer}>
|
||||
<Icon
|
||||
name='globe'
|
||||
name={this.props.isArchived ? 'archive' : 'globe'}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.displayName}>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,34 @@ exports[`MoreChannels should match snapshot 1`] = `
|
|||
value=""
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityRole="button"
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"fontWeight": "bold",
|
||||
"marginBottom": 10,
|
||||
"marginLeft": 10,
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="caret-down"
|
||||
size={12}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
<CustomList
|
||||
data={
|
||||
Array [
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {connect} from 'react-redux';
|
|||
import {createSelector} from 'reselect';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getChannels, joinChannel, searchChannels} from 'mattermost-redux/actions/channels';
|
||||
import {joinChannel, searchChannels} from 'mattermost-redux/actions/channels';
|
||||
import {getChannelsInCurrentTeam, getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
|
@ -15,11 +15,13 @@ import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
|||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {handleSelectChannel, setChannelDisplayName} from 'app/actions/views/channel';
|
||||
import {handleSelectChannel, setChannelDisplayName, loadPublicAndArchivedChannels} from 'app/actions/views/channel';
|
||||
|
||||
import MoreChannels from './more_channels';
|
||||
|
||||
const joinableChannels = createSelector(
|
||||
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
const joinablePublicChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
getMyChannelMemberships,
|
||||
(channels, myMembers) => {
|
||||
|
|
@ -29,11 +31,19 @@ const joinableChannels = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
const teamArchivedChannels = createSelector(
|
||||
getChannelsInCurrentTeam,
|
||||
(channels) => {
|
||||
return channels.filter((c) => c.delete_at !== 0);
|
||||
}
|
||||
);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const config = getConfig(state);
|
||||
const license = getLicense(state);
|
||||
const roles = getCurrentUserRoles(state);
|
||||
const channels = joinableChannels(state);
|
||||
const channels = joinablePublicChannels(state);
|
||||
const archivedChannels = teamArchivedChannels(state);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
|
||||
return {
|
||||
|
|
@ -41,8 +51,10 @@ function mapStateToProps(state) {
|
|||
currentUserId: getCurrentUserId(state),
|
||||
currentTeamId,
|
||||
channels,
|
||||
archivedChannels,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
canShowArchivedChannels: isMinimumServerVersion(state.entities.general.serverVersion, 5, 18),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +63,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
handleSelectChannel,
|
||||
joinChannel,
|
||||
getChannels,
|
||||
loadPublicAndArchivedChannels,
|
||||
searchChannels,
|
||||
setChannelDisplayName,
|
||||
}, dispatch),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {Platform, View} from 'react-native';
|
||||
import {Platform, View, Text} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
|
@ -12,6 +13,7 @@ import {General} from 'mattermost-redux/constants';
|
|||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import CustomList from 'app/components/custom_list';
|
||||
import ChannelListRow from 'app/components/custom_list/channel_list_row';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
|
@ -33,18 +35,20 @@ export default class MoreChannels extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
joinChannel: PropTypes.func.isRequired,
|
||||
getChannels: PropTypes.func.isRequired,
|
||||
loadPublicAndArchivedChannels: PropTypes.func.isRequired,
|
||||
searchChannels: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string,
|
||||
canCreateChannels: PropTypes.bool.isRequired,
|
||||
channels: PropTypes.array,
|
||||
archivedChannels: PropTypes.array,
|
||||
closeButton: PropTypes.object,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
canShowArchivedChannels: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -59,12 +63,15 @@ export default class MoreChannels extends PureComponent {
|
|||
super(props, context);
|
||||
|
||||
this.searchTimeoutId = 0;
|
||||
this.page = -1;
|
||||
this.publicPage = -1;
|
||||
this.archivedPage = -1;
|
||||
this.next = true;
|
||||
this.mounted = false;
|
||||
|
||||
this.state = {
|
||||
channels: props.channels.slice(0, General.CHANNELS_CHUNK_SIZE),
|
||||
archivedChannels: props.archivedChannels.slice(0, General.CHANNELS_CHUNK_SIZE),
|
||||
typeOfChannels: 'public',
|
||||
loading: false,
|
||||
adding: false,
|
||||
term: '',
|
||||
|
|
@ -130,10 +137,11 @@ export default class MoreChannels extends PureComponent {
|
|||
}
|
||||
|
||||
cancelSearch = () => {
|
||||
const {channels} = this.props;
|
||||
const {channels, archivedChannels} = this.props;
|
||||
|
||||
this.setState({
|
||||
channels,
|
||||
archivedChannels,
|
||||
term: '',
|
||||
});
|
||||
};
|
||||
|
|
@ -143,15 +151,17 @@ export default class MoreChannels extends PureComponent {
|
|||
};
|
||||
|
||||
doGetChannels = () => {
|
||||
const {actions, currentTeamId} = this.props;
|
||||
const {actions, currentTeamId, canShowArchivedChannels} = this.props;
|
||||
const {loading, term} = this.state;
|
||||
|
||||
if (this.next && !loading && !term && this.mounted) {
|
||||
this.setState({loading: true}, () => {
|
||||
actions.getChannels(
|
||||
actions.loadPublicAndArchivedChannels(
|
||||
currentTeamId,
|
||||
this.page + 1,
|
||||
General.CHANNELS_CHUNK_SIZE
|
||||
this.publicPage + 1,
|
||||
this.archivedPage + 1,
|
||||
General.CHANNELS_CHUNK_SIZE,
|
||||
canShowArchivedChannels,
|
||||
).then(this.loadedChannels);
|
||||
});
|
||||
}
|
||||
|
|
@ -295,36 +305,77 @@ export default class MoreChannels extends PureComponent {
|
|||
|
||||
renderItem = (props) => {
|
||||
return (
|
||||
<ChannelListRow {...props}/>
|
||||
<ChannelListRow
|
||||
{...props}
|
||||
isArchived={this.state.typeOfChannels === 'archived'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
searchChannels = (text) => {
|
||||
const {actions, channels, currentTeamId} = this.props;
|
||||
const {actions, channels, archivedChannels, currentTeamId, canShowArchivedChannels} = this.props;
|
||||
const {typeOfChannels} = this.state;
|
||||
|
||||
if (text) {
|
||||
const filtered = this.filterChannels(channels, text);
|
||||
this.setState({
|
||||
channels: filtered,
|
||||
term: text,
|
||||
});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
if (typeOfChannels === 'public') {
|
||||
const filtered = this.filterChannels(channels, text);
|
||||
this.setState({
|
||||
channels: filtered,
|
||||
term: text,
|
||||
});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
} else if (typeOfChannels === 'archived' && canShowArchivedChannels) {
|
||||
const filtered = this.filterChannels(archivedChannels, text);
|
||||
this.setState({
|
||||
archivedChannels: filtered,
|
||||
term: text,
|
||||
});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
}
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
actions.searchChannels(currentTeamId, text.toLowerCase());
|
||||
actions.searchChannels(currentTeamId, text.toLowerCase(), typeOfChannels === 'archived');
|
||||
}, General.SEARCH_TIMEOUT_MILLISECONDS);
|
||||
} else {
|
||||
this.cancelSearch();
|
||||
}
|
||||
};
|
||||
|
||||
handleDropdownClick = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
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';
|
||||
BottomSheet.showBottomSheetWithOptions({
|
||||
options: [publicChannelsText, archivedChannelsText, cancelText],
|
||||
cancelButtonIndex: 2,
|
||||
title: titleText,
|
||||
}, (value) => {
|
||||
let typeOfChannels;
|
||||
switch (value) {
|
||||
case 0:
|
||||
typeOfChannels = 'public';
|
||||
break;
|
||||
case 1:
|
||||
typeOfChannels = 'archived';
|
||||
break;
|
||||
default:
|
||||
typeOfChannels = this.state.typeOfChannels;
|
||||
}
|
||||
this.setState({typeOfChannels});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {theme, isLandscape} = this.props;
|
||||
const {adding, channels, loading, term} = this.state;
|
||||
const {theme, isLandscape, canShowArchivedChannels} = this.props;
|
||||
const {adding, channels, archivedChannels, loading, term, typeOfChannels} = this.state;
|
||||
const more = term ? () => true : 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}/>);
|
||||
|
|
@ -340,6 +391,31 @@ export default class MoreChannels extends PureComponent {
|
|||
}),
|
||||
};
|
||||
|
||||
let activeChannels = channels;
|
||||
|
||||
if (canShowArchivedChannels && typeOfChannels === 'archived') {
|
||||
activeChannels = archivedChannels;
|
||||
}
|
||||
|
||||
let channelDropdown;
|
||||
if (canShowArchivedChannels) {
|
||||
channelDropdown = (
|
||||
<View style={[style.titleContainer, padding(isLandscape)]}>
|
||||
<Text
|
||||
accessibilityRole={'button'}
|
||||
style={style.channelDropdown}
|
||||
onPress={this.handleDropdownClick}
|
||||
>
|
||||
{typeOfChannels === 'public' ? publicChannelsText : archivedChannelsText}
|
||||
{' '}
|
||||
<Icon
|
||||
name={'caret-down'}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
|
|
@ -362,8 +438,9 @@ export default class MoreChannels extends PureComponent {
|
|||
value={term}
|
||||
/>
|
||||
</View>
|
||||
{channelDropdown}
|
||||
<CustomList
|
||||
data={channels}
|
||||
data={activeChannels}
|
||||
extraData={loading}
|
||||
key='custom_list'
|
||||
loading={loading}
|
||||
|
|
@ -411,5 +488,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 26,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
channelDropdown: {
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 10,
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('MoreChannels', () => {
|
|||
const actions = {
|
||||
handleSelectChannel: jest.fn(),
|
||||
joinChannel: jest.fn(),
|
||||
getChannels: jest.fn().mockResolvedValue({data: [{id: 'id2', name: 'name2', display_name: 'display_name2'}]}),
|
||||
loadPublicAndArchivedChannels: jest.fn().mockResolvedValue({data: [{id: 'id2', name: 'name2', display_name: 'display_name2'}]}),
|
||||
searchChannels: jest.fn(),
|
||||
setChannelDisplayName: jest.fn(),
|
||||
};
|
||||
|
|
@ -25,12 +25,14 @@ describe('MoreChannels', () => {
|
|||
actions,
|
||||
canCreateChannels: true,
|
||||
channels: [{id: 'id', name: 'name', display_name: 'display_name'}],
|
||||
archivedChannels: [{id: 'id2', name: 'archived', display_name: 'archived channel'}],
|
||||
closeButton: {},
|
||||
currentUserId: 'current_user_id',
|
||||
currentTeamId: 'current_team_id',
|
||||
theme: Preferences.THEMES.default,
|
||||
componentId: 'component-id',
|
||||
isLandscape: false,
|
||||
canShowArchivedChannels: true,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
@ -91,4 +93,20 @@ describe('MoreChannels', () => {
|
|||
expect(wrapper.state('term')).toEqual('');
|
||||
expect(wrapper.state('channels')).toEqual(baseProps.channels);
|
||||
});
|
||||
|
||||
test('should search correct channels', () => {
|
||||
const wrapper = shallow(
|
||||
<MoreChannels {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setState({typeOfChannels: 'public'});
|
||||
instance.searchChannels('display_name');
|
||||
expect(wrapper.state('channels')).toEqual(baseProps.channels);
|
||||
|
||||
wrapper.setState({typeOfChannels: 'archived'});
|
||||
instance.searchChannels('archived channel');
|
||||
expect(wrapper.state('archivedChannels')).toEqual(baseProps.archivedChannels);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -492,7 +492,12 @@
|
|||
"modal.manual_status.auto_responder.message_dnd": "Would you like to switch your status to \"Do Not Disturb\" and disable Automatic Replies?",
|
||||
"modal.manual_status.auto_responder.message_offline": "Would you like to switch your status to \"Offline\" and disable Automatic Replies?",
|
||||
"modal.manual_status.auto_responder.message_online": "Would you like to switch your status to \"Online\" and disable Automatic Replies?",
|
||||
"more_channels.archivedChannels": "Archived Channels",
|
||||
"more_channels.dropdownTitle": "Show",
|
||||
"more_channels.noMore": "No more channels to join",
|
||||
"more_channels.publicChannels": "Public Channels",
|
||||
"more_channels.showArchivedChannels": "Show: Archived Channels",
|
||||
"more_channels.showPublicChannels": "Show: Public Channels",
|
||||
"more_channels.title": "More Channels",
|
||||
"msg_typing.areTyping": "{users} and {last} are typing...",
|
||||
"msg_typing.isTyping": "{user} is typing...",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -7816,8 +7816,8 @@
|
|||
}
|
||||
},
|
||||
"mattermost-redux": {
|
||||
"version": "github:mattermost/mattermost-redux#98a375cea54f78fb6c0ede01ee4e9aec009f9fa5",
|
||||
"from": "github:mattermost/mattermost-redux#98a375cea54f78fb6c0ede01ee4e9aec009f9fa5",
|
||||
"version": "github:mattermost/mattermost-redux#ebd4fd361e982bf2f9b5737642dc5fcf6e1fa3f7",
|
||||
"from": "github:mattermost/mattermost-redux#ebd4fd361e982bf2f9b5737642dc5fcf6e1fa3f7",
|
||||
"requires": {
|
||||
"form-data": "2.5.1",
|
||||
"gfycat-sdk": "1.4.18",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"intl": "1.2.5",
|
||||
"jail-monkey": "2.3.0",
|
||||
"jsc-android": "241213.1.0",
|
||||
"mattermost-redux": "github:mattermost/mattermost-redux#98a375cea54f78fb6c0ede01ee4e9aec009f9fa5",
|
||||
"mattermost-redux": "github:mattermost/mattermost-redux#ebd4fd361e982bf2f9b5737642dc5fcf6e1fa3f7",
|
||||
"mime-db": "1.42.0",
|
||||
"moment-timezone": "0.5.27",
|
||||
"prop-types": "15.7.2",
|
||||
|
|
|
|||
Loading…
Reference in a new issue