Squashing several bugs (#221)
* Squashing several bugs * Fix websocket tests
This commit is contained in:
parent
c933da341a
commit
b3b3d9a39a
24 changed files with 174 additions and 203 deletions
|
|
@ -19,6 +19,7 @@
|
|||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
|||
|
|
@ -101,8 +101,10 @@ export function selectInitialChannel(teamId) {
|
|||
const state = getState();
|
||||
const {channels, myMembers} = state.entities.channels;
|
||||
const currentChannelId = state.entities.channels.currentId;
|
||||
const currentChannel = channels[currentChannelId];
|
||||
|
||||
if (channels[currentChannelId] && myMembers[currentChannelId] && channels[currentChannelId].team_id === teamId) {
|
||||
if (currentChannel && myMembers[currentChannelId] &&
|
||||
(currentChannel.team_id === teamId || currentChannel.type === Constants.DM_CHANNEL)) {
|
||||
await selectChannel(currentChannelId)(dispatch, getState);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,19 @@ export function handleTeamChange(team) {
|
|||
};
|
||||
}
|
||||
|
||||
export function selectFirstAvailableTeam() {
|
||||
return async (dispatch, getState) => {
|
||||
const {teams: allTeams, myMembers} = getState().entities.teams;
|
||||
const teams = Object.keys(myMembers).map((key) => allTeams[key]);
|
||||
const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0];
|
||||
|
||||
if (firstTeam) {
|
||||
handleTeamChange(firstTeam)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
handleTeamChange
|
||||
handleTeamChange,
|
||||
selectFirstAvailableTeam
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import React from 'react';
|
|||
|
||||
import {Alert, ListView, Platform, StyleSheet, Text, View} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {buildDisplayNameAndTypeComparable} from 'service/utils/channel_utils';
|
||||
import {Constants} from 'service/constants';
|
||||
import LineDivider from 'app/components/line_divider';
|
||||
import ChannelItem from './channel_item';
|
||||
|
|
@ -79,6 +78,11 @@ class ChannelList extends React.Component {
|
|||
}).isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.firstUnreadChannel = null;
|
||||
|
|
@ -111,39 +115,34 @@ class ChannelList extends React.Component {
|
|||
return data.findIndex((obj) => obj.display_name === displayName);
|
||||
};
|
||||
|
||||
getAboveAndBelow = (index) => {
|
||||
const channel = this.state.dataSource.getRowData(0, index);
|
||||
const result = buildDisplayNameAndTypeComparable(channel).localeCompare(buildDisplayNameAndTypeComparable(this.props.currentChannel));
|
||||
if (result < 0) {
|
||||
return {above: true, below: false};
|
||||
} else if (result > 0) {
|
||||
return {above: false, below: true};
|
||||
}
|
||||
return {above: false, below: false};
|
||||
};
|
||||
|
||||
updateUnreadIndicators = (v) => {
|
||||
let showAbove = false;
|
||||
let showBelow = false;
|
||||
|
||||
if (this.firstUnreadChannel) {
|
||||
const index = this.getRowIndex(this.firstUnreadChannel);
|
||||
if (index >= 0 && !v.s1[index]) {
|
||||
showAbove = this.getAboveAndBelow(index).above;
|
||||
}
|
||||
}
|
||||
if (v.s1) {
|
||||
const visibleIndexes = Object.keys(v.s1);
|
||||
const firstVisible = parseInt(visibleIndexes[0], 10);
|
||||
const lastVisible = parseInt(visibleIndexes[visibleIndexes.length - 1], 10);
|
||||
|
||||
if (this.lastUnreadChannel) {
|
||||
const index = this.getRowIndex(this.lastUnreadChannel);
|
||||
if (index >= 0 && !v.s1[index]) {
|
||||
showBelow = this.getAboveAndBelow(index).below;
|
||||
if (this.firstUnreadChannel) {
|
||||
const index = this.getRowIndex(this.firstUnreadChannel);
|
||||
if (index < firstVisible) {
|
||||
showAbove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showAbove,
|
||||
showBelow
|
||||
});
|
||||
if (this.lastUnreadChannel) {
|
||||
const index = this.getRowIndex(this.lastUnreadChannel);
|
||||
if (index > lastVisible) {
|
||||
showBelow = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showAbove,
|
||||
showBelow
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const style = StyleSheet.create({
|
|||
|
||||
export default class PostList extends React.Component {
|
||||
static propTypes = {
|
||||
posts: React.PropTypes.object.isRequired,
|
||||
posts: React.PropTypes.array.isRequired,
|
||||
theme: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ export default class PostList extends React.Component {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
dataSource: this.state.dataSource.cloneWithRowsAndSections(nextProps.posts)
|
||||
dataSource: this.state.dataSource.cloneWithRows(nextProps.posts)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ export default class PostList extends React.Component {
|
|||
}
|
||||
|
||||
return this.renderPost(row);
|
||||
}
|
||||
};
|
||||
|
||||
renderDateHeader = (date) => {
|
||||
return (
|
||||
|
|
@ -55,7 +55,7 @@ export default class PostList extends React.Component {
|
|||
date={date}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderPost = (post) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ export default class PostTextbox extends React.PureComponent {
|
|||
|
||||
blur = () => {
|
||||
this.refs.input.getWrappedInstance().blur();
|
||||
}
|
||||
};
|
||||
|
||||
handleContentSizeChange = (e) => {
|
||||
this.setState({
|
||||
contentHeight: e.nativeEvent.contentSize.height
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sendMessage = () => {
|
||||
if (this.props.value.trim().length === 0) {
|
||||
|
|
@ -63,7 +63,7 @@ export default class PostTextbox extends React.PureComponent {
|
|||
|
||||
this.props.actions.createPost(this.props.teamId, post);
|
||||
this.props.onChangeText('');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const theme = this.props.theme;
|
||||
|
|
@ -104,8 +104,10 @@ export default class PostTextbox extends React.PureComponent {
|
|||
onChangeText={this.props.onChangeText}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
onSubmitEditing={this.sendMessage}
|
||||
multiline={true}
|
||||
underlineColorAndroid='transparent'
|
||||
style={{
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderWidth: 1,
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ import Drawer from 'app/components/drawer';
|
|||
import FormattedText from 'app/components/formatted_text';
|
||||
import OptionsModal from 'app/components/options_modal';
|
||||
import {RouteTransitions} from 'app/navigation/routes';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
|
||||
class Router extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: React.PropTypes.object,
|
||||
theme: React.PropTypes.object,
|
||||
modalVisible: React.PropTypes.bool.isRequired,
|
||||
actions: React.PropTypes.shape({
|
||||
closeDrawers: React.PropTypes.func.isRequired,
|
||||
|
|
@ -72,7 +74,10 @@ class Router extends React.Component {
|
|||
renderLeftComponent={renderLeftComponent}
|
||||
renderTitleComponent={renderTitleComponent}
|
||||
renderRightComponent={renderRightComponent}
|
||||
style={navigationProps.headerStyle}
|
||||
style={[
|
||||
navigationProps.headerStyle,
|
||||
{backgroundColor: this.props.theme.sidebarHeaderBg}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -221,6 +226,7 @@ function mapStateToProps(state) {
|
|||
const modalVisible = state.views.optionsModal.visible;
|
||||
return {
|
||||
navigation: state.navigation,
|
||||
theme: getTheme(state),
|
||||
modalVisible
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import {
|
|||
Text
|
||||
} from 'react-native';
|
||||
|
||||
import {Constants} from 'service/constants';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import EventEmitter from 'service/utils/event_emitter';
|
||||
|
||||
import ChannelDrawerButton from './channel_drawer_button';
|
||||
import ChannelMenuButton from './channel_menu_button';
|
||||
|
|
@ -20,6 +22,7 @@ export default class Channel extends React.PureComponent {
|
|||
actions: React.PropTypes.shape({
|
||||
loadChannelsIfNecessary: React.PropTypes.func.isRequired,
|
||||
loadProfilesAndTeamMembersForDMSidebar: React.PropTypes.func.isRequired,
|
||||
selectFirstAvailableTeam: React.PropTypes.func.isRequired,
|
||||
selectInitialChannel: React.PropTypes.func.isRequired,
|
||||
openChannelDrawer: React.PropTypes.func.isRequired,
|
||||
openRightMenuDrawer: React.PropTypes.func.isRequired,
|
||||
|
|
@ -47,21 +50,13 @@ export default class Channel extends React.PureComponent {
|
|||
renderRightComponent: (props, emitter) => {
|
||||
return <ChannelMenuButton emitter={emitter}/>;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
leftSidebarOpen: false,
|
||||
rightSidebarOpen: false
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('open_channel_drawer', this.openChannelDrawer);
|
||||
this.props.subscribeToHeaderEvent('open_right_menu', this.openRightMenuDrawer);
|
||||
this.props.subscribeToHeaderEvent('show_channel_info', this.props.actions.goToChannelInfo);
|
||||
EventEmitter.on('leave_team', this.handleLeaveTeam);
|
||||
const teamId = this.props.currentTeam.id;
|
||||
this.props.actions.initWebSocket();
|
||||
this.loadChannels(teamId);
|
||||
|
|
@ -77,6 +72,7 @@ export default class Channel extends React.PureComponent {
|
|||
componentWillUnmount() {
|
||||
this.props.actions.closeWebSocket();
|
||||
this.props.unsubscribeFromHeaderEvent('open_channel_drawer');
|
||||
EventEmitter.off('leave_team', this.handleLeaveTeam);
|
||||
}
|
||||
|
||||
loadChannels = (teamId) => {
|
||||
|
|
@ -96,9 +92,13 @@ export default class Channel extends React.PureComponent {
|
|||
this.props.actions.openRightMenuDrawer();
|
||||
};
|
||||
|
||||
attachPostTextbox = (c) => {
|
||||
this.postTextbox = c;
|
||||
}
|
||||
attachPostTextbox = (ref) => {
|
||||
this.postTextbox = ref;
|
||||
};
|
||||
|
||||
handleLeaveTeam = () => {
|
||||
this.props.actions.selectFirstAvailableTeam();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
@ -113,17 +113,23 @@ export default class Channel extends React.PureComponent {
|
|||
return <Text>{'Waiting on channel'}</Text>;
|
||||
}
|
||||
|
||||
let teamId = currentChannel.team_id;
|
||||
if (currentChannel.type === Constants.DM_CHANNEL) {
|
||||
teamId = currentTeam.id;
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={{flex: 1, backgroundColor: theme.centerChannelBg}}
|
||||
keyboardVerticalOffset={65}
|
||||
>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<ChannelPostList channel={currentChannel}/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
value={this.props.postDraft}
|
||||
teamId={currentChannel.team_id}
|
||||
teamId={teamId}
|
||||
channelId={currentChannel.id}
|
||||
onChangeText={this.props.actions.handlePostDraftChanged}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
selectInitialChannel,
|
||||
handlePostDraftChanged
|
||||
} from 'app/actions/views/channel';
|
||||
import {selectFirstAvailableTeam} from 'app/actions/views/select_team';
|
||||
|
||||
import {getCurrentChannel} from 'service/selectors/entities/channels';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
|
|
@ -43,6 +44,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar,
|
||||
selectFirstAvailableTeam,
|
||||
selectInitialChannel,
|
||||
openChannelDrawer,
|
||||
openRightMenuDrawer,
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Text,
|
||||
Platform,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
export default class ChannelHeader extends React.PureComponent {
|
||||
static propTypes = {
|
||||
displayName: React.PropTypes.string.isRequired,
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
openLeftDrawer: React.PropTypes.func.isRequired,
|
||||
openRightDrawer: React.PropTypes.func.isRequired,
|
||||
goToChannelInfo: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
displayName,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const containerStyle = {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
marginTop: 20
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<View style={{flexDirection: 'column', justifyContent: 'center', alignItems: 'center'}}>
|
||||
<TouchableHighlight
|
||||
onPress={this.props.openLeftDrawer}
|
||||
style={{height: 25, width: 25, marginLeft: 10, marginRight: 10}}
|
||||
>
|
||||
<Icon
|
||||
name='bars'
|
||||
size={25}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', backgroundColor: theme.sidebarHeaderBg}}>
|
||||
<TouchableHighlight onPress={this.props.goToChannelInfo}>
|
||||
<View>
|
||||
<Text style={{color: theme.sidebarHeaderTextColor, fontSize: 15, fontWeight: 'bold'}}>
|
||||
{displayName}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
<TouchableHighlight
|
||||
onPress={this.props.openRightDrawer}
|
||||
style={{height: 50, width: 50}}
|
||||
>
|
||||
<Text style={{color: theme.sidebarHeaderTextColor}}>{'>'}</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {Constants} from 'service/constants';
|
||||
import {getMyPreferences, getTheme} from 'service/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from 'service/selectors/entities/users';
|
||||
import {getUserIdFromChannelName} from 'service/utils/channel_utils';
|
||||
import {displayUsername} from 'service/utils/user_utils';
|
||||
|
||||
import ChannelHeader from './channel_header';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const currentChannel = ownProps.currentChannel;
|
||||
|
||||
let displayName = '';
|
||||
if (currentChannel) {
|
||||
if (currentChannel.type === Constants.DM_CHANNEL) {
|
||||
const otherUser = getUser(state, getUserIdFromChannelName(getCurrentUserId(state), currentChannel));
|
||||
|
||||
if (otherUser) {
|
||||
displayName = displayUsername(otherUser, getMyPreferences(state));
|
||||
}
|
||||
} else {
|
||||
displayName = currentChannel.display_name;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
displayName,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChannelHeader);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ChannelHeaderContainer from './channel_header_container';
|
||||
|
||||
export default ChannelHeaderContainer;
|
||||
|
|
@ -11,8 +11,8 @@ export default class ChannelPostList extends React.Component {
|
|||
loadPostsIfNecessary: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channel: React.PropTypes.object.isRequired,
|
||||
posts: React.PropTypes.object.isRequired
|
||||
}
|
||||
posts: React.PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.actions.loadPostsIfNecessary(this.props.channel);
|
||||
|
|
|
|||
|
|
@ -62,14 +62,18 @@ const getPostsInCurrentChannelGroupedByDay = createSelector(
|
|||
postsByDay[dateString].push(post);
|
||||
}
|
||||
|
||||
let posts = [];
|
||||
|
||||
// Push the date on after the posts so that it's rendered first
|
||||
for (const dateString in postsByDay) {
|
||||
if (postsByDay.hasOwnProperty(dateString)) {
|
||||
postsByDay[dateString].push(new Date(dateString));
|
||||
}
|
||||
|
||||
posts = posts.concat(postsByDay[dateString]);
|
||||
}
|
||||
|
||||
return postsByDay;
|
||||
return posts;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ class ChannelInfo extends PureComponent {
|
|||
currentChannelMemberCount: PropTypes.number,
|
||||
isFavorite: PropTypes.bool.isRequired,
|
||||
leaveChannelRequest: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
goToChannelMembers: PropTypes.func.isRequired,
|
||||
|
|
@ -53,7 +52,7 @@ class ChannelInfo extends PureComponent {
|
|||
goBack: PropTypes.func.isRequired,
|
||||
leaveChannel: PropTypes.func.isRequired
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -90,7 +89,7 @@ class ChannelInfo extends PureComponent {
|
|||
const toggleFavorite = isFavorite ? unmarkFavorite : markFavorite;
|
||||
this.setState({isFavorite: !isFavorite});
|
||||
toggleFavorite(currentChannel.id);
|
||||
}
|
||||
};
|
||||
|
||||
handleLeave() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
|
@ -122,7 +121,7 @@ class ChannelInfo extends PureComponent {
|
|||
renderLeaveChannelRow() {
|
||||
const channel = this.props.currentChannel;
|
||||
const isDefaultChannel = channel.name === Constants.DEFAULT_CHANNEL;
|
||||
const isDirectMessage = channel.type === 'D';
|
||||
const isDirectMessage = channel.type === Constants.DM_CHANNEL;
|
||||
return !isDefaultChannel && !isDirectMessage;
|
||||
}
|
||||
|
||||
|
|
@ -130,15 +129,13 @@ class ChannelInfo extends PureComponent {
|
|||
const {
|
||||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
theme
|
||||
currentChannelMemberCount
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<ScrollView
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={{backgroundColor: theme.centerChannelBg}}
|
||||
>
|
||||
<ChannelInfoHeader
|
||||
createAt={currentChannel.create_at}
|
||||
|
|
@ -156,7 +153,7 @@ class ChannelInfo extends PureComponent {
|
|||
togglable={true}
|
||||
/>
|
||||
<View style={style.separatorContainer}>
|
||||
<View style={[style.separator, {backgroundColor: this.props.theme.centerChannelBg}]}/>
|
||||
<View style={style.separator}/>
|
||||
</View>
|
||||
<ChannelInfoRow
|
||||
action={() => true}
|
||||
|
|
@ -165,7 +162,7 @@ class ChannelInfo extends PureComponent {
|
|||
textId='channel_header.notificationPreferences'
|
||||
/>
|
||||
<View style={style.separatorContainer}>
|
||||
<View style={[style.separator, {backgroundColor: this.props.theme.centerChannelBg}]}/>
|
||||
<View style={style.separator}/>
|
||||
</View>
|
||||
<ChannelInfoRow
|
||||
action={this.props.actions.goToChannelMembers}
|
||||
|
|
@ -175,7 +172,7 @@ class ChannelInfo extends PureComponent {
|
|||
textId='channel_header.manageMembers'
|
||||
/>
|
||||
<View style={style.separatorContainer}>
|
||||
<View style={[style.separator, {backgroundColor: this.props.theme.centerChannelBg}]}/>
|
||||
<View style={style.separator}/>
|
||||
</View>
|
||||
<ChannelInfoRow
|
||||
action={this.props.actions.goToChannelAddMembers}
|
||||
|
|
@ -184,7 +181,7 @@ class ChannelInfo extends PureComponent {
|
|||
textId='channel_header.addMembers'
|
||||
/>
|
||||
<View style={style.separatorContainer}>
|
||||
<View style={[style.separator, {backgroundColor: this.props.theme.centerChannelBg}]}/>
|
||||
<View style={style.separator}/>
|
||||
</View>
|
||||
<ChannelInfoRow
|
||||
action={() => this.handleLeave()}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {goToChannelMembers, goToChannelAddMembers, goBack} from 'app/actions/nav
|
|||
import {getChannelStats} from 'service/actions/channels';
|
||||
import {markFavorite, unmarkFavorite, leaveChannel} from 'app/actions/views/channel';
|
||||
import {getCurrentChannel, getCurrentChannelStats, getChannelsByCategory} from 'service/selectors/entities/channels';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
import {getUser} from 'service/selectors/entities/users';
|
||||
|
||||
import ChannelInfo from './channel_info';
|
||||
|
|
@ -29,8 +28,7 @@ function mapStateToProps(state, ownProps) {
|
|||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
isFavorite,
|
||||
leaveChannelRequest,
|
||||
theme: getTheme(state)
|
||||
leaveChannelRequest
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ const defaults = {
|
|||
},
|
||||
headerStyle: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#367FB0'
|
||||
alignItems: 'center'
|
||||
},
|
||||
allowSwipe: false
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default class Root extends React.Component {
|
|||
|
||||
static navigationProps = {
|
||||
hideNavBar: true
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// Any initialization logic for navigation, setting up the client, etc should go here
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ platform :android do
|
|||
|
||||
desc 'Build Release file'
|
||||
lane :dev do
|
||||
build_android({release: false})
|
||||
build_android({release: true})
|
||||
end
|
||||
|
||||
desc 'Submit a new Beta Build to Google Play'
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import Client from 'service/client';
|
||||
import EventEmitter from 'service/utils/event_emitter';
|
||||
import websocketClient from 'service/client/websocket_client';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
import {
|
||||
Constants,
|
||||
ChannelTypes,
|
||||
|
|
@ -241,16 +241,7 @@ function handleLeaveTeamEvent(msg, dispatch, getState) {
|
|||
|
||||
// if they are on the team being removed deselect the current team and channel
|
||||
if (teams.currentId === msg.data.team_id) {
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: TeamsTypes.SELECT_TEAM,
|
||||
data: ''
|
||||
},
|
||||
{
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: ''
|
||||
}
|
||||
]), getState);
|
||||
EventEmitter.emit('leave_team');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {createSelector} from 'reselect';
|
||||
import {getCurrentTeamId} from 'service/selectors/entities/teams';
|
||||
import {buildDisplayableChannelList} from 'service/utils/channel_utils';
|
||||
import {buildDisplayableChannelList, completeDirectChannelInfo} from 'service/utils/channel_utils';
|
||||
|
||||
function getAllChannels(state) {
|
||||
return state.entities.channels.channels;
|
||||
|
|
@ -20,8 +20,14 @@ export function getCurrentChannelId(state) {
|
|||
export const getCurrentChannel = createSelector(
|
||||
getAllChannels,
|
||||
getCurrentChannelId,
|
||||
(allChannels, currentChannelId) => {
|
||||
return allChannels[currentChannelId];
|
||||
(state) => state.entities.users,
|
||||
(state) => state.entities.preferences.myPreferences,
|
||||
(allChannels, currentChannelId, users, myPreferences) => {
|
||||
const channel = allChannels[currentChannelId];
|
||||
if (channel) {
|
||||
return completeDirectChannelInfo(users, myPreferences, channel);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ function createFakeChannelCurried(userId) {
|
|||
return (otherUserId) => createFakeChannel(userId, otherUserId);
|
||||
}
|
||||
|
||||
function completeDirectChannelInfo(usersState, myPreferences, channel) {
|
||||
export function completeDirectChannelInfo(usersState, myPreferences, channel) {
|
||||
if (!isDirectChannel(channel)) {
|
||||
return channel;
|
||||
}
|
||||
|
|
|
|||
59
service/utils/event_emitter.js
Normal file
59
service/utils/event_emitter.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
function isFunction(obj) {
|
||||
return typeof obj === 'function';
|
||||
}
|
||||
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
addListener(label, callback) {
|
||||
if (!this.listeners.has(label)) {
|
||||
this.listeners.set(label, []);
|
||||
}
|
||||
this.listeners.get(label).push(callback);
|
||||
}
|
||||
|
||||
on(label, callback) {
|
||||
this.addListener(label, callback);
|
||||
}
|
||||
|
||||
removeListener(label, callback) {
|
||||
const listeners = this.listeners.get(label);
|
||||
let index;
|
||||
|
||||
if (listeners && listeners.length) {
|
||||
index = listeners.reduce((i, listener, idx) => {
|
||||
return (isFunction(listener) && listener === callback) ? idx : i;
|
||||
}, -1);
|
||||
|
||||
if (index > -1) {
|
||||
listeners.splice(index, 1);
|
||||
this.listeners.set(label, listeners);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
off(label, callback) {
|
||||
this.removeListener(label, callback);
|
||||
}
|
||||
|
||||
emit(label, ...args) {
|
||||
const listeners = this.listeners.get(label);
|
||||
|
||||
if (listeners && listeners.length) {
|
||||
listeners.forEach((listener) => {
|
||||
listener(...args);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default new EventEmitter();
|
||||
|
|
@ -134,8 +134,8 @@ describe('Actions.Websocket', () => {
|
|||
await ChannelActions.selectChannel(channel.id)(store.dispatch, store.getState);
|
||||
await client.removeUserFromTeam(team.id, TestHelper.basicUser.id);
|
||||
|
||||
const {currentId} = store.getState().entities.teams;
|
||||
assert.strictEqual(currentId, '');
|
||||
const {myMembers} = store.getState().entities.teams;
|
||||
assert.ifError(myMembers[team.id]);
|
||||
});
|
||||
|
||||
it('Websocket Handle User Added', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue