Channel Add Members (#218)
* Channel Add Members Add ability to select rows Change member list to accept datasource instead of manage it’s own Add navbar button Add alert for empty member selection Add batched method to send to server * Feedback changes
This commit is contained in:
parent
6722c5a788
commit
c933da341a
15 changed files with 416 additions and 48 deletions
|
|
@ -74,6 +74,15 @@ export function goToChannelMembers() {
|
|||
};
|
||||
}
|
||||
|
||||
export function goToChannelAddMembers() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.ChannelAddMembers
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function openChannelDrawer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
|
|
|||
16
app/actions/views/channel_add_members.js
Normal file
16
app/actions/views/channel_add_members.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {addChannelMember} from 'service/actions/channels';
|
||||
|
||||
export function handleAddChannelMembers(teamId, channelId, members) {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const requests = members.map((m) => dispatch(addChannelMember(teamId, channelId, m, getState)));
|
||||
|
||||
await Promise.all(requests);
|
||||
} catch (error) {
|
||||
// should be handled by global error handling
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -43,21 +43,26 @@ export default class MemberList extends PureComponent {
|
|||
onRowPress: PropTypes.func,
|
||||
onListEndReached: PropTypes.func,
|
||||
onListEndReachedThreshold: PropTypes.number,
|
||||
sections: PropTypes.bool,
|
||||
preferences: PropTypes.object,
|
||||
loadingMembers: PropTypes.bool,
|
||||
listPageSize: PropTypes.number,
|
||||
listInitialSize: PropTypes.number,
|
||||
listScrollRenderAheadDistance: PropTypes.number
|
||||
listScrollRenderAheadDistance: PropTypes.number,
|
||||
showSections: PropTypes.bool,
|
||||
selectable: PropTypes.bool,
|
||||
onRowSelect: PropTypes.func,
|
||||
renderRow: PropTypes.func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onListEndReached: () => true,
|
||||
onListEndThreshold: 50,
|
||||
sections: true,
|
||||
listPageSize: 10,
|
||||
listInitialSize: 10,
|
||||
listScrollRenderAheadDistance: 200
|
||||
listScrollRenderAheadDistance: 200,
|
||||
selectable: false,
|
||||
onRowSelect: () => true,
|
||||
showSections: true
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -67,18 +72,33 @@ export default class MemberList extends PureComponent {
|
|||
rowHasChanged: (r1, r2) => r1 !== r2,
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
|
||||
});
|
||||
const dataSource = props.sections ? ds.cloneWithRowsAndSections(this.createSections(props.members)) : ds.cloneWithRows(props.members);
|
||||
let data = props.members;
|
||||
if (props.showSections) {
|
||||
data = this.createSections(props.members);
|
||||
}
|
||||
const dataSource = ds.cloneWithRowsAndSections(data);
|
||||
this.state = {
|
||||
data,
|
||||
dataSource
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {members, sections} = nextProps;
|
||||
const dataSource = sections ? this.state.dataSource.cloneWithRowsAndSections(this.createSections(members)) : this.state.dataSource.cloneWithRows(members);
|
||||
this.setState({
|
||||
dataSource
|
||||
});
|
||||
const {members, showSections} = nextProps;
|
||||
|
||||
if (members !== this.props.members || showSections !== this.props.showSections) {
|
||||
let data = members;
|
||||
if (showSections) {
|
||||
data = this.createSections(members);
|
||||
}
|
||||
|
||||
const mergedData = Object.assign({}, data, this.state.data);
|
||||
const dataSource = showSections ? this.state.dataSource.cloneWithRowsAndSections(mergedData) : this.state.dataSource.cloneWithRows(mergedData);
|
||||
this.setState({
|
||||
data: mergedData,
|
||||
dataSource
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createSections = (data) => {
|
||||
|
|
@ -97,11 +117,23 @@ export default class MemberList extends PureComponent {
|
|||
return sections;
|
||||
}
|
||||
|
||||
renderSectionHeader = (sectionData, sectionId) => {
|
||||
if (!this.props.sections) {
|
||||
return null;
|
||||
}
|
||||
handleRowSelect = (sectionId, rowId) => {
|
||||
const data = this.state.data;
|
||||
const section = [...data[sectionId]];
|
||||
|
||||
section[rowId] = Object.assign({}, section[rowId], {selected: !section[rowId].selected});
|
||||
const mergedData = Object.assign({}, data, {[sectionId]: section});
|
||||
|
||||
const id = section[rowId].id;
|
||||
|
||||
const dataSource = this.state.dataSource.cloneWithRowsAndSections(mergedData);
|
||||
this.setState({
|
||||
data: mergedData,
|
||||
dataSource
|
||||
}, () => this.props.onRowSelect(id));
|
||||
}
|
||||
|
||||
renderSectionHeader = (sectionData, sectionId) => {
|
||||
return (
|
||||
<View style={style.sectionContainer}>
|
||||
<Text style={style.sectionText}>{sectionId}</Text>
|
||||
|
|
@ -109,9 +141,13 @@ export default class MemberList extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderRow = (user) => {
|
||||
renderRow = (user, sectionId, rowId) => {
|
||||
const {id, username} = user;
|
||||
const displayName = displayUsername(user, this.props.preferences);
|
||||
let onRowSelect = null;
|
||||
if (this.props.selectable) {
|
||||
onRowSelect = () => this.handleRowSelect(sectionId, rowId);
|
||||
}
|
||||
|
||||
return (
|
||||
<MemberListRow
|
||||
|
|
@ -120,6 +156,9 @@ export default class MemberList extends PureComponent {
|
|||
displayName={displayName}
|
||||
username={username}
|
||||
onPress={this.props.onRowPress}
|
||||
selectable={this.props.selectable}
|
||||
selected={user.selected}
|
||||
onRowSelect={onRowSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -152,11 +191,13 @@ export default class MemberList extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const renderRow = this.props.renderRow || this.renderRow;
|
||||
|
||||
return (
|
||||
<ListView
|
||||
style={style.listView}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this.renderRow}
|
||||
renderRow={renderRow}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderSeparator={this.renderSeparator}
|
||||
renderFooter={this.renderFooter}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,19 @@ import React, {PropTypes} from 'react';
|
|||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
padding: 10,
|
||||
height: 65,
|
||||
paddingHorizontal: 10,
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
|
|
@ -27,14 +30,33 @@ const style = StyleSheet.create({
|
|||
marginLeft: 5,
|
||||
fontSize: 16,
|
||||
opacity: 0.7
|
||||
},
|
||||
selector: {
|
||||
height: 28,
|
||||
width: 28,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#888',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
selectorContainer: {
|
||||
height: 50,
|
||||
paddingRight: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#378FD2',
|
||||
borderWidth: 0
|
||||
}
|
||||
});
|
||||
|
||||
function createTouchableComponent(children, action) {
|
||||
return (
|
||||
<TouchableHighlight onPress={action}>
|
||||
<TouchableOpacity onPress={action}>
|
||||
{children}
|
||||
</TouchableHighlight>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -43,17 +65,40 @@ function MemberListRow(props) {
|
|||
|
||||
const RowComponent = (
|
||||
<View style={style.container}>
|
||||
{props.selectable &&
|
||||
<TouchableWithoutFeedback onPress={props.onRowSelect}>
|
||||
<View style={style.selectorContainer}>
|
||||
<View style={[style.selector, (props.selected && style.selectorFilled)]}>
|
||||
{props.selected &&
|
||||
<Icon
|
||||
name='check'
|
||||
size={16}
|
||||
color='#fff'
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
}
|
||||
<ProfilePicture
|
||||
user={user}
|
||||
size={40}
|
||||
/>
|
||||
<View style={style.textContainer}>
|
||||
<Text style={style.displayName}>
|
||||
{displayName}
|
||||
</Text>
|
||||
<Text style={style.username}>
|
||||
{`(@${username})`}
|
||||
</Text>
|
||||
<View style={{flexGrow: 1, flexDirection: 'column'}}>
|
||||
<Text style={style.displayName}>
|
||||
{displayName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{flexShrink: 1, flexDirection: 'column', flexWrap: 'wrap'}}>
|
||||
<Text
|
||||
style={style.username}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{`(@${username})`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -70,7 +115,10 @@ MemberListRow.propTypes = {
|
|||
displayName: PropTypes.string.isRequired,
|
||||
pictureURL: PropTypes.string,
|
||||
username: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func
|
||||
onPress: PropTypes.func,
|
||||
selectable: PropTypes.bool,
|
||||
onRowSelect: PropTypes.func,
|
||||
selected: PropTypes.bool
|
||||
};
|
||||
|
||||
export default MemberListRow;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
ChannelDrawer,
|
||||
ChannelInfo,
|
||||
ChannelMembers,
|
||||
ChannelAddMembers,
|
||||
LoadTeam,
|
||||
Login,
|
||||
Mfa,
|
||||
|
|
@ -43,6 +44,14 @@ export const Routes = {
|
|||
title: {id: 'channel_header.manageMembers', defaultMessage: 'Manage Members'}
|
||||
}
|
||||
},
|
||||
ChannelAddMembers: {
|
||||
key: 'ChannelAddMembers',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: ChannelAddMembers,
|
||||
navigationProps: {
|
||||
title: {id: 'channel_header.addMembers', defaultMessage: 'Add Members'}
|
||||
}
|
||||
},
|
||||
ChannelView: {
|
||||
key: 'ChannelView',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
|
|
|
|||
47
app/scenes/channel_add_members/add_member_button.js
Normal file
47
app/scenes/channel_add_members/add_member_button.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
|
||||
function AddMemberButton(props) {
|
||||
return (
|
||||
<View style={{flexDirection: 'column', justifyContent: 'center', alignItems: 'center', flex: 1}}>
|
||||
<TouchableOpacity
|
||||
onPress={() => props.emitter('add_members')}
|
||||
style={{paddingHorizontal: 15}}
|
||||
>
|
||||
<FormattedText
|
||||
id='integrations.add'
|
||||
defaultMessage='Add'
|
||||
style={{color: props.theme.sidebarHeaderTextColor}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
AddMemberButton.propTypes = {
|
||||
emitter: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
AddMemberButton.defaultProps = {
|
||||
theme: {}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AddMemberButton);
|
||||
110
app/scenes/channel_add_members/channel_add_members.js
Normal file
110
app/scenes/channel_add_members/channel_add_members.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {
|
||||
Alert,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import MemberList from 'app/components/member_list';
|
||||
|
||||
import AddMemberButton from './add_member_button';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
||||
export default class ChannelAddMembers extends PureComponent {
|
||||
static propTypes = {
|
||||
currentChannel: PropTypes.object,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
membersNotInChannel: PropTypes.array.isRequired,
|
||||
currentTeam: PropTypes.object,
|
||||
currentTeamMemberCount: PropTypes.number,
|
||||
preferences: PropTypes.object,
|
||||
loadMoreRequestStatus: PropTypes.string,
|
||||
subscribeToHeaderEvent: React.PropTypes.func,
|
||||
unsubscribeFromHeaderEvent: React.PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
getTeamStats: PropTypes.func.isRequired,
|
||||
getProfilesNotInChannel: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
handleAddChannelMembers: PropTypes.func.isRequired
|
||||
})
|
||||
}
|
||||
|
||||
static navigationProps = {
|
||||
renderRightComponent: (props, emitter) => {
|
||||
return <AddMemberButton emitter={emitter}/>;
|
||||
}
|
||||
}
|
||||
|
||||
state = {
|
||||
selectedMembers: {}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('add_members', this.handleAddMembersPress);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.props.actions.getProfilesNotInChannel(this.props.currentTeam.id, this.props.currentChannel.id, 0);
|
||||
this.props.actions.getTeamStats(this.props.currentTeam.id);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribeFromHeaderEvent('add_members');
|
||||
}
|
||||
|
||||
handleAddMembersPress = () => {
|
||||
const {selectedMembers} = this.state;
|
||||
const {actions, currentTeam, currentChannel} = this.props;
|
||||
const membersToAdd = Object.keys(selectedMembers).filter((m) => selectedMembers[m]);
|
||||
|
||||
if (!membersToAdd.length) {
|
||||
Alert.alert('Add Members', 'You must select at least one member to add to the channel.');
|
||||
return;
|
||||
}
|
||||
|
||||
actions.handleAddChannelMembers(currentTeam.id, currentChannel.id, membersToAdd).then(() => {
|
||||
actions.goBack();
|
||||
});
|
||||
}
|
||||
|
||||
loadMoreMembers = () => {
|
||||
const {currentChannelMemberCount, currentTeamMemberCount, membersNotInChannel, loadMoreRequestStatus} = this.props;
|
||||
if (loadMoreRequestStatus !== 'started' && membersNotInChannel.length < (currentTeamMemberCount - currentChannelMemberCount - 1)) {
|
||||
this.props.actions.getProfilesNotInChannel(this.props.currentTeam.id, this.props.currentChannel.id, this.props.membersNotInChannel.length);
|
||||
}
|
||||
}
|
||||
|
||||
handleRowSelect = (id) => {
|
||||
const selectedMembers = Object.assign({}, this.state.selectedMembers, {[id]: !this.state.selectedMembers[id]});
|
||||
this.setState({
|
||||
selectedMembers
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<MemberList
|
||||
members={this.props.membersNotInChannel}
|
||||
onListEndReached={this.loadMoreMembers}
|
||||
preferences={this.props.preferences}
|
||||
loadingMembers={this.props.loadMoreRequestStatus === 'started'}
|
||||
selectable={true}
|
||||
onRowSelect={this.handleRowSelect}
|
||||
showSections={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {handleAddChannelMembers} from 'app/actions/views/channel_add_members';
|
||||
import {goBack} from 'app/actions/navigation';
|
||||
import {getCurrentChannel, getCurrentChannelStats} from 'service/selectors/entities/channels';
|
||||
import {getMyPreferences} from 'service/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getCurrentTeamStats} from 'service/selectors/entities/teams';
|
||||
import {getProfilesNotInCurrentChannel} from 'service/selectors/entities/users';
|
||||
import {getTeamStats} from 'service/actions/teams';
|
||||
import {getProfilesNotInChannel} from 'service/actions/users';
|
||||
|
||||
import ChannelAddMembers from './channel_add_members';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentTeamMemberCount = getCurrentTeamStats(state) && getCurrentTeamStats(state).total_member_count;
|
||||
const currentChannelMemberCount = getCurrentChannelStats(state) && getCurrentChannelStats(state).member_count;
|
||||
|
||||
return {
|
||||
currentChannel: getCurrentChannel(state),
|
||||
membersNotInChannel: getProfilesNotInCurrentChannel(state),
|
||||
currentTeam: getCurrentTeam(state),
|
||||
currentTeamMemberCount,
|
||||
currentChannelMemberCount,
|
||||
preferences: getMyPreferences(state),
|
||||
loadMoreRequestStatus: state.requests.users.getProfilesNotInChannel.status,
|
||||
addChannelMemberRequestStatus: state.requests.channels.addChannelMember
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getTeamStats,
|
||||
getProfilesNotInChannel,
|
||||
goBack,
|
||||
handleAddChannelMembers
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(ChannelAddMembers);
|
||||
6
app/scenes/channel_add_members/index.js
Normal file
6
app/scenes/channel_add_members/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ChannelAddMembersContainer from './channel_add_members_container';
|
||||
|
||||
export default ChannelAddMembersContainer;
|
||||
|
|
@ -47,6 +47,7 @@ class ChannelInfo extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
goToChannelMembers: PropTypes.func.isRequired,
|
||||
goToChannelAddMembers: PropTypes.func.isRequired,
|
||||
markFavorite: PropTypes.func.isRequired,
|
||||
unmarkFavorite: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
|
|
@ -177,7 +178,7 @@ class ChannelInfo extends PureComponent {
|
|||
<View style={[style.separator, {backgroundColor: this.props.theme.centerChannelBg}]}/>
|
||||
</View>
|
||||
<ChannelInfoRow
|
||||
action={() => true}
|
||||
action={this.props.actions.goToChannelAddMembers}
|
||||
defaultMessage='Add Members'
|
||||
icon='user-plus'
|
||||
textId='channel_header.addMembers'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
|
|||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goToChannelMembers, goBack} from 'app/actions/navigation';
|
||||
import {goToChannelMembers, goToChannelAddMembers, goBack} from 'app/actions/navigation';
|
||||
import {getChannelStats} from 'service/actions/channels';
|
||||
import {markFavorite, unmarkFavorite, leaveChannel} from 'app/actions/views/channel';
|
||||
import {getCurrentChannel, getCurrentChannelStats, getChannelsByCategory} from 'service/selectors/entities/channels';
|
||||
|
|
@ -39,6 +39,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
getChannelStats,
|
||||
goToChannelMembers,
|
||||
goToChannelAddMembers,
|
||||
markFavorite,
|
||||
unmarkFavorite,
|
||||
leaveChannel,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Channel from './channel';
|
|||
import ChannelDrawer from './channel_drawer';
|
||||
import ChannelInfo from './channel_info';
|
||||
import ChannelMembers from './channel_members';
|
||||
import ChannelAddMembers from './channel_add_members';
|
||||
import LoadTeam from './load_team';
|
||||
import Login from './login/login_container.js';
|
||||
import Mfa from './mfa';
|
||||
|
|
@ -19,6 +20,7 @@ module.exports = {
|
|||
ChannelDrawer,
|
||||
ChannelInfo,
|
||||
ChannelMembers,
|
||||
ChannelAddMembers,
|
||||
LoadTeam,
|
||||
Login,
|
||||
Mfa,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const defaults = {
|
|||
<FormattedText
|
||||
id={title.id}
|
||||
defaultMessage={title.defaultMessage}
|
||||
style={{color: '#fff', fontSize: 15, fontWeight: 'bold'}}
|
||||
style={{color: '#fff', fontSize: 15, fontWeight: 'bold', textAlign: 'center'}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ export function getTeams(state) {
|
|||
return state.entities.teams.teams;
|
||||
}
|
||||
|
||||
export function getTeamStats(state) {
|
||||
return state.entities.teams.stats;
|
||||
}
|
||||
|
||||
export const getCurrentTeam = createSelector(
|
||||
getTeams,
|
||||
getCurrentTeamId,
|
||||
|
|
@ -28,3 +32,11 @@ export const getCurrentTeamUrl = createSelector(
|
|||
return `${currentUrl}/${currentTeam.name}`;
|
||||
}
|
||||
);
|
||||
|
||||
export const getCurrentTeamStats = createSelector(
|
||||
getCurrentTeamId,
|
||||
getTeamStats,
|
||||
(currentTeamId, teamStats) => {
|
||||
return teamStats[currentTeamId];
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ export function getProfilesInChannel(state) {
|
|||
return state.entities.users.profilesInChannel;
|
||||
}
|
||||
|
||||
export function getProfilesNotInChannel(state) {
|
||||
return state.entities.users.profilesNotInChannel;
|
||||
}
|
||||
|
||||
export function getUserStatuses(state) {
|
||||
return state.entities.users.statuses;
|
||||
}
|
||||
|
|
@ -41,28 +45,44 @@ export const getProfileSetInCurrentChannel = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getProfileSetNotInCurrentChannel = createSelector(
|
||||
getCurrentChannelId,
|
||||
getProfilesNotInChannel,
|
||||
(currentChannel, channelProfiles) => {
|
||||
return channelProfiles[currentChannel];
|
||||
}
|
||||
);
|
||||
|
||||
function sortAndInjectProfiles(profiles, profileSet) {
|
||||
const currentProfiles = [];
|
||||
if (typeof profileSet === 'undefined') {
|
||||
return currentProfiles;
|
||||
}
|
||||
|
||||
profileSet.forEach((p) => {
|
||||
currentProfiles.push(profiles[p]);
|
||||
});
|
||||
|
||||
const sortedCurrentProfiles = currentProfiles.sort((a, b) => {
|
||||
const nameA = a.username;
|
||||
const nameB = b.username;
|
||||
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
|
||||
return sortedCurrentProfiles;
|
||||
}
|
||||
|
||||
export const getProfilesInCurrentChannel = createSelector(
|
||||
getUsers,
|
||||
getProfileSetInCurrentChannel,
|
||||
(profiles, currentChannelProfileSet) => {
|
||||
const currentProfiles = [];
|
||||
if (typeof currentChannelProfileSet === 'undefined') {
|
||||
return currentProfiles;
|
||||
}
|
||||
(profiles, currentChannelProfileSet) => sortAndInjectProfiles(profiles, currentChannelProfileSet)
|
||||
);
|
||||
|
||||
currentChannelProfileSet.forEach((p) => {
|
||||
currentProfiles.push(profiles[p]);
|
||||
});
|
||||
|
||||
const sortedCurrentProfiles = currentProfiles.sort((a, b) => {
|
||||
const nameA = a.username;
|
||||
const nameB = b.username;
|
||||
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
|
||||
return sortedCurrentProfiles;
|
||||
}
|
||||
export const getProfilesNotInCurrentChannel = createSelector(
|
||||
getUsers,
|
||||
getProfileSetNotInCurrentChannel,
|
||||
(profiles, notInCurrentChannelProfileSet) => sortAndInjectProfiles(profiles, notInCurrentChannelProfileSet)
|
||||
);
|
||||
|
||||
export function getStatusForUserId(state, userId) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue