Improving Performance Part 3 (#1011)
* Update mattermost-redux * Tap on Deleted post to remove it * Improvements when rendering post additional content * Avoid unnecessary re-renders on the post list component * Avoid unnecessary re-renders on the channel post list component * Avoid unnecessary re-renders on the channel screen * Feedback review
This commit is contained in:
parent
f8414559b2
commit
f50b2b9412
24 changed files with 468 additions and 318 deletions
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FlatList, Platform, RefreshControl, ScrollView, StyleSheet, View} from 'react-native';
|
||||
import {FlatList, Platform, ScrollView, StyleSheet, View} from 'react-native';
|
||||
|
||||
import RefreshList from 'app/components/refresh_list';
|
||||
|
||||
import VirtualList from './virtual_list';
|
||||
|
||||
|
|
@ -64,7 +66,7 @@ export default class InvertibleFlatList extends PureComponent {
|
|||
<ScrollView
|
||||
{...props}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
<RefreshList
|
||||
refreshing={props.refreshing}
|
||||
onRefresh={props.onRefresh}
|
||||
tintColor={theme.centerChannelColor}
|
||||
|
|
|
|||
|
|
@ -12,23 +12,20 @@ import {getTheme} from 'app/selectors/preferences';
|
|||
|
||||
import Post from './post';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const post = getPost(state, ownProps.post.id);
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const post = getPost(state, ownProps.postId);
|
||||
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
post,
|
||||
config,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
highlight: ownProps.post.highlight,
|
||||
license,
|
||||
roles,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
return {
|
||||
post,
|
||||
config,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
highlight: ownProps.highlight,
|
||||
license,
|
||||
roles,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -43,4 +40,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(Post);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Post);
|
||||
|
|
|
|||
|
|
@ -6,17 +6,8 @@ import {bindActionCreators} from 'redux';
|
|||
|
||||
import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostAttachmentOpenGraph from './post_attachment_opengraph';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
|
|
@ -25,4 +16,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
export default connect(null, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
|
|
|
|||
|
|
@ -190,11 +190,20 @@ class PostBody extends PureComponent {
|
|||
let messageComponent;
|
||||
if (hasBeenDeleted) {
|
||||
messageComponent = (
|
||||
<FormattedText
|
||||
style={messageStyle}
|
||||
id='post_body.deleted'
|
||||
defaultMessage='(message deleted)'
|
||||
/>
|
||||
<TouchableHighlight
|
||||
onHideUnderlay={this.handleHideUnderlay}
|
||||
onPress={onPress}
|
||||
onShowUnderlay={this.handleShowUnderlay}
|
||||
underlayColor='transparent'
|
||||
>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<FormattedText
|
||||
style={messageStyle}
|
||||
id='post_body.deleted'
|
||||
defaultMessage='(message deleted)'
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
body = (<View>{messageComponent}</View>);
|
||||
} else if (message.length) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ function makeMapStateToProps() {
|
|||
const link = getFirstLink(ownProps.message);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
...getDimensions(state),
|
||||
config,
|
||||
link,
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
const {link, openGraphData, showLinkPreviews} = this.props;
|
||||
const {link, openGraphData, showLinkPreviews, theme} = this.props;
|
||||
const attachments = this.getSlackAttachment();
|
||||
if (attachments) {
|
||||
return attachments;
|
||||
|
|
@ -109,6 +109,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
<PostAttachmentOpenGraph
|
||||
link={link}
|
||||
openGraphData={openGraphData}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -244,22 +245,22 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {link, openGraphData} = this.props;
|
||||
const {link, openGraphData, postProps} = this.props;
|
||||
const {linkLoaded, linkLoadError} = this.state;
|
||||
let isYouTube = false;
|
||||
let isImage = false;
|
||||
let isOpenGraph = false;
|
||||
const {attachments} = postProps;
|
||||
|
||||
if (link) {
|
||||
isYouTube = isYoutubeLink(link);
|
||||
isImage = isImageLink(link);
|
||||
isOpenGraph = Boolean(openGraphData && openGraphData.description);
|
||||
if (!link && !attachments) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (((isImage && !isOpenGraph) || isYouTube) && !linkLoadError) {
|
||||
const embed = this.generateToggleableEmbed(isImage, isYouTube);
|
||||
if (embed && (linkLoaded || isYouTube)) {
|
||||
return embed;
|
||||
}
|
||||
const isYouTube = isYoutubeLink(link);
|
||||
const isImage = isImageLink(link);
|
||||
const isOpenGraph = Boolean(openGraphData && openGraphData.description);
|
||||
|
||||
if (((isImage && !isOpenGraph) || isYouTube) && !linkLoadError) {
|
||||
const embed = this.generateToggleableEmbed(isImage, isYouTube);
|
||||
if (embed && (linkLoaded || isYouTube)) {
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ import {getTheme} from 'app/selectors/preferences';
|
|||
|
||||
import PostList from './post_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
|
|
@ -13,7 +14,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
export default class LoadMorePosts extends PureComponent {
|
||||
class LoadMorePosts extends PureComponent {
|
||||
static propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
loadMore: PropTypes.func,
|
||||
|
|
@ -72,3 +73,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
loading: state.views.channel.loadingPosts[ownProps.channelId] || false
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LoadMorePosts);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ export default class PostList extends PureComponent {
|
|||
channelId: PropTypes.string,
|
||||
currentUserId: PropTypes.string,
|
||||
indicateNewMessages: PropTypes.bool,
|
||||
isLoadingMore: PropTypes.bool,
|
||||
isSearchResult: PropTypes.bool,
|
||||
lastViewedAt: PropTypes.number,
|
||||
loadMore: PropTypes.func,
|
||||
|
|
@ -36,17 +35,26 @@ export default class PostList extends PureComponent {
|
|||
onPostPress: PropTypes.func,
|
||||
onRefresh: PropTypes.func,
|
||||
posts: PropTypes.array.isRequired,
|
||||
refreshing: PropTypes.bool,
|
||||
renderReplies: PropTypes.bool,
|
||||
showLoadMore: PropTypes.bool,
|
||||
shouldRenderReplyButton: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
loadMore: () => true
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.channelId !== this.props.channelId && this.refs.list) {
|
||||
// When switching channels make sure we start from the bottom
|
||||
this.refs.list.scrollToOffset({y: 0, animated: false});
|
||||
}
|
||||
}
|
||||
|
||||
getPostsWithDates = () => {
|
||||
const {posts, indicateNewMessages, currentUserId, lastViewedAt, showLoadMore} = this.props;
|
||||
const list = addDatesToPostList(posts, {indicateNewMessages, currentUserId, lastViewedAt});
|
||||
|
||||
if (showLoadMore) {
|
||||
return [...list, LOAD_MORE_POSTS];
|
||||
}
|
||||
|
|
@ -58,18 +66,8 @@ export default class PostList extends PureComponent {
|
|||
if (item instanceof Date) {
|
||||
return item.getTime();
|
||||
}
|
||||
if (item === General.START_OF_NEW_MESSAGES || item === LOAD_MORE_POSTS) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return item.id;
|
||||
};
|
||||
|
||||
loadMorePosts = () => {
|
||||
const {loadMore, isLoadingMore} = this.props;
|
||||
if (typeof loadMore === 'function' && !isLoadingMore) {
|
||||
loadMore();
|
||||
}
|
||||
return item.id || item;
|
||||
};
|
||||
|
||||
onRefresh = () => {
|
||||
|
|
@ -89,9 +87,10 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
renderChannelIntro = () => {
|
||||
const {channelId, navigator, refreshing, showLoadMore} = this.props;
|
||||
const {channelId, navigator, showLoadMore} = this.props;
|
||||
|
||||
if (channelId && !showLoadMore && !refreshing) {
|
||||
// FIXME: Only show the channel intro when we are at the very start of the channel
|
||||
if (channelId && !showLoadMore) {
|
||||
return (
|
||||
<View>
|
||||
<ChannelIntro navigator={navigator}/>
|
||||
|
|
@ -124,10 +123,7 @@ export default class PostList extends PureComponent {
|
|||
}
|
||||
if (item === LOAD_MORE_POSTS && this.props.showLoadMore) {
|
||||
return (
|
||||
<LoadMorePosts
|
||||
loading={this.props.isLoadingMore}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
<LoadMorePosts theme={this.props.theme}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +145,8 @@ export default class PostList extends PureComponent {
|
|||
|
||||
return (
|
||||
<Post
|
||||
post={post}
|
||||
highlight={post.highlight}
|
||||
postId={post.id}
|
||||
renderReplies={renderReplies}
|
||||
isFirstReply={post.isFirstReply}
|
||||
isLastReply={post.isLastReply}
|
||||
|
|
@ -163,24 +160,26 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {channelId, refreshing, theme} = this.props;
|
||||
const {channelId, loadMore, theme} = this.props;
|
||||
|
||||
const refreshControl = {
|
||||
refreshing
|
||||
refreshing: false
|
||||
};
|
||||
|
||||
if (channelId) {
|
||||
refreshControl.onRefresh = this.onRefresh;
|
||||
}
|
||||
|
||||
const data = this.getPostsWithDates();
|
||||
return (
|
||||
<FlatList
|
||||
data={this.getPostsWithDates()}
|
||||
initialNumToRender={20}
|
||||
ref='list'
|
||||
data={data}
|
||||
initialNumToRender={15}
|
||||
inverted={true}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.renderChannelIntro}
|
||||
onEndReached={this.loadMorePosts}
|
||||
onEndReached={loadMore}
|
||||
onEndReachedThreshold={0}
|
||||
{...refreshControl}
|
||||
renderItem={this.renderItem}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {createPost} from 'mattermost-redux/actions/posts';
|
||||
import {userTyping} from 'mattermost-redux/actions/websocket';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {canUploadFilesOnMobile} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ function mapStateToProps(state, ownProps) {
|
|||
const currentDraft = ownProps.rootId ? getThreadDraft(state, ownProps.rootId) : getCurrentChannelDraft(state);
|
||||
|
||||
return {
|
||||
channelId: getCurrentChannelId(state),
|
||||
canUploadFiles: canUploadFilesOnMobile(state),
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
|
|
|
|||
23
app/components/refresh_list/index.js
Normal file
23
app/components/refresh_list/index.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
|
||||
import RefreshList from './refresh_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const networkOnline = getConnection(state);
|
||||
let {refreshing} = state.views.channel;
|
||||
|
||||
if (!networkOnline) {
|
||||
refreshing = false;
|
||||
}
|
||||
|
||||
return {
|
||||
refreshing
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(RefreshList);
|
||||
19
app/components/refresh_list/refresh_list.js
Normal file
19
app/components/refresh_list/refresh_list.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {RefreshControl} from 'react-native';
|
||||
|
||||
export default class RefreshList extends PureComponent {
|
||||
static propTypes = {
|
||||
...RefreshControl.propTypes
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<RefreshControl
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
app/components/retry_bar_indicator/index.js
Normal file
25
app/components/retry_bar_indicator/index.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
|
||||
import RetryBarIndicator from './retry_bar_indicator';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {websocket: websocketRequest} = state.requests.general;
|
||||
const networkOnline = getConnection(state);
|
||||
const webSocketOnline = websocketRequest.status === RequestStatus.SUCCESS;
|
||||
|
||||
let failed = state.views.channel.retryFailed && webSocketOnline;
|
||||
if (!networkOnline) {
|
||||
failed = false;
|
||||
}
|
||||
|
||||
return {
|
||||
failed
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps)(RetryBarIndicator);
|
||||
65
app/components/retry_bar_indicator/retry_bar_indicator.js
Normal file
65
app/components/retry_bar_indicator/retry_bar_indicator.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Animated,
|
||||
StyleSheet
|
||||
} from 'react-native';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class RetryBarIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
failed: PropTypes.bool
|
||||
};
|
||||
|
||||
state = {
|
||||
retryMessageHeight: new Animated.Value(0)
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.failed !== nextProps.failed) {
|
||||
this.toggleRetryMessage(nextProps.failed);
|
||||
}
|
||||
}
|
||||
|
||||
toggleRetryMessage = (show = true) => {
|
||||
const value = show ? 38 : 0;
|
||||
Animated.timing(this.state.retryMessageHeight, {
|
||||
toValue: value,
|
||||
duration: 350
|
||||
}).start();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {retryMessageHeight} = this.state;
|
||||
const refreshIndicatorDimensions = {
|
||||
height: retryMessageHeight
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedView style={[style.refreshIndicator, refreshIndicatorDimensions]}>
|
||||
<FormattedText
|
||||
id='mobile.retry_message'
|
||||
defaultMessage='Refreshing messages failed. Pull up to try again.'
|
||||
style={{color: 'white', flex: 1, fontSize: 12}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
refreshIndicator: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fb8000',
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
|
|
@ -10,7 +10,6 @@ import {
|
|||
View
|
||||
} from 'react-native';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ChannelDrawer from 'app/components/channel_drawer';
|
||||
|
|
@ -49,8 +48,7 @@ class Channel extends PureComponent {
|
|||
currentTeamId: PropTypes.string,
|
||||
currentChannelId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
webSocketRequest: PropTypes.object,
|
||||
channelsRequestStatus: PropTypes.string
|
||||
channelsRequestFailed: PropTypes.bool
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
|
|
@ -64,14 +62,6 @@ class Channel extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {startPeriodicStatusUpdates} = this.props.actions;
|
||||
|
||||
try {
|
||||
startPeriodicStatusUpdates();
|
||||
} catch (error) {
|
||||
// We don't care about the error
|
||||
}
|
||||
|
||||
// Mark current channel as read when opening app while logged in
|
||||
if (this.props.currentChannelId) {
|
||||
this.props.actions.markChannelAsRead(this.props.currentChannelId);
|
||||
|
|
@ -126,19 +116,21 @@ class Channel extends PureComponent {
|
|||
});
|
||||
|
||||
handleConnectionChange = (isConnected) => {
|
||||
const {actions, webSocketRequest} = this.props;
|
||||
const {closeWebSocket, connection, initWebSocket} = actions;
|
||||
const {actions} = this.props;
|
||||
const {
|
||||
closeWebSocket,
|
||||
connection,
|
||||
initWebSocket,
|
||||
startPeriodicStatusUpdates,
|
||||
stopPeriodicStatusUpdates
|
||||
} = actions;
|
||||
|
||||
if (isConnected) {
|
||||
if (!webSocketRequest || webSocketRequest.status === RequestStatus.NOT_STARTED) {
|
||||
try {
|
||||
initWebSocket(Platform.OS);
|
||||
} catch (error) {
|
||||
// We don't care if it fails
|
||||
}
|
||||
}
|
||||
initWebSocket(Platform.OS);
|
||||
startPeriodicStatusUpdates();
|
||||
} else {
|
||||
closeWebSocket(true);
|
||||
stopPeriodicStatusUpdates();
|
||||
}
|
||||
connection(isConnected);
|
||||
};
|
||||
|
|
@ -168,7 +160,7 @@ class Channel extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
channelsRequestStatus,
|
||||
channelsRequestFailed,
|
||||
currentChannelId,
|
||||
intl,
|
||||
navigator,
|
||||
|
|
@ -178,7 +170,7 @@ class Channel extends PureComponent {
|
|||
const style = getStyleFromTheme(theme);
|
||||
|
||||
if (!currentChannelId) {
|
||||
if (channelsRequestStatus === RequestStatus.FAILURE) {
|
||||
if (channelsRequestFailed) {
|
||||
return (
|
||||
<PostListRetry
|
||||
retry={this.retryLoadChannels}
|
||||
|
|
@ -204,10 +196,11 @@ class Channel extends PureComponent {
|
|||
<OfflineIndicator/>
|
||||
<View style={style.header}>
|
||||
<ChannelDrawerButton/>
|
||||
<ChannelTitle
|
||||
onPress={this.goToChannelInfo}
|
||||
<ChannelTitle onPress={this.goToChannelInfo}/>
|
||||
<ChannelSearchButton
|
||||
navigator={navigator}
|
||||
theme={theme}
|
||||
/>
|
||||
<ChannelSearchButton navigator={navigator}/>
|
||||
</View>
|
||||
</View>
|
||||
<KeyboardLayout
|
||||
|
|
@ -215,16 +208,11 @@ class Channel extends PureComponent {
|
|||
style={style.keyboardLayout}
|
||||
>
|
||||
<View style={style.postList}>
|
||||
<ChannelPostList
|
||||
channelId={currentChannelId}
|
||||
navigator={navigator}
|
||||
/>
|
||||
<ChannelPostList navigator={navigator}/>
|
||||
</View>
|
||||
<ChannelLoader theme={theme}/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
onChangeText={this.handleDraftChanged}
|
||||
channelId={currentChannelId}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Animated,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View
|
||||
|
|
@ -13,11 +12,9 @@ import {
|
|||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostListRetry from 'app/components/post_list_retry';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
import RetryBarIndicator from 'app/components/retry_bar_indicator';
|
||||
|
||||
class ChannelPostList extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -30,13 +27,11 @@ class ChannelPostList extends PureComponent {
|
|||
}).isRequired,
|
||||
channelDisplayName: PropTypes.string,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelIsRefreshing: PropTypes.bool,
|
||||
channelRefreshingFailed: PropTypes.bool,
|
||||
channelType: PropTypes.string,
|
||||
currentUserId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
lastViewedAt: PropTypes.number,
|
||||
loadingPosts: PropTypes.bool,
|
||||
navigator: PropTypes.object,
|
||||
posts: PropTypes.array.isRequired,
|
||||
postVisibility: PropTypes.number,
|
||||
|
|
@ -45,8 +40,6 @@ class ChannelPostList extends PureComponent {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
posts: [],
|
||||
loadingPosts: false,
|
||||
postVisibility: 15
|
||||
};
|
||||
|
||||
|
|
@ -54,29 +47,18 @@ class ChannelPostList extends PureComponent {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
retryMessageHeight: new Animated.Value(0),
|
||||
visiblePosts: this.getVisiblePosts(props),
|
||||
showLoadMore: props.posts.length >= props.postVisibility
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {channelRefreshingFailed: nextChannelRefreshingFailed, posts: nextPosts} = nextProps;
|
||||
const {posts: nextPosts} = nextProps;
|
||||
|
||||
if (nextChannelRefreshingFailed && nextPosts.length) {
|
||||
this.toggleRetryMessage();
|
||||
} else if (!nextChannelRefreshingFailed || !nextPosts.length) {
|
||||
this.toggleRetryMessage(false);
|
||||
}
|
||||
|
||||
const showLoadMore = nextProps.posts.length >= nextProps.postVisibility;
|
||||
const showLoadMore = nextPosts.length >= nextProps.postVisibility;
|
||||
let visiblePosts = this.state.visiblePosts;
|
||||
|
||||
if (nextProps.posts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) {
|
||||
if (nextPosts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) {
|
||||
visiblePosts = this.getVisiblePosts(nextProps);
|
||||
}
|
||||
|
||||
|
|
@ -86,22 +68,10 @@ class ChannelPostList extends PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
getVisiblePosts = (props) => {
|
||||
return props.posts.slice(0, props.postVisibility);
|
||||
};
|
||||
|
||||
toggleRetryMessage = (show = true) => {
|
||||
const value = show ? 38 : 0;
|
||||
Animated.timing(this.state.retryMessageHeight, {
|
||||
toValue: value,
|
||||
duration: 350
|
||||
}).start();
|
||||
};
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions, channelId, channelDisplayName, channelType, intl, navigator, theme} = this.props;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
|
@ -156,18 +126,15 @@ class ChannelPostList extends PureComponent {
|
|||
const {
|
||||
actions,
|
||||
channelId,
|
||||
channelIsRefreshing,
|
||||
channelRefreshingFailed,
|
||||
currentUserId,
|
||||
lastViewedAt,
|
||||
loadingPosts,
|
||||
navigator,
|
||||
posts,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
retryMessageHeight,
|
||||
showLoadMore,
|
||||
visiblePosts
|
||||
} = this.state;
|
||||
|
|
@ -185,7 +152,6 @@ class ChannelPostList extends PureComponent {
|
|||
<PostList
|
||||
posts={visiblePosts}
|
||||
loadMore={this.loadMorePosts}
|
||||
isLoadingMore={loadingPosts}
|
||||
showLoadMore={showLoadMore}
|
||||
onPostPress={this.goToThread}
|
||||
onRefresh={actions.setChannelRefreshing}
|
||||
|
|
@ -195,25 +161,14 @@ class ChannelPostList extends PureComponent {
|
|||
lastViewedAt={lastViewedAt}
|
||||
channelId={channelId}
|
||||
navigator={navigator}
|
||||
refreshing={channelIsRefreshing}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const refreshIndicatorDimensions = {
|
||||
height: retryMessageHeight
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
{component}
|
||||
<AnimatedView style={[style.refreshIndicator, refreshIndicatorDimensions]}>
|
||||
<FormattedText
|
||||
id='mobile.retry_message'
|
||||
defaultMessage='Refreshing messages failed. Pull up to try again.'
|
||||
style={{color: 'white', flex: 1, fontSize: 12}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
<RetryBarIndicator/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -222,16 +177,6 @@ class ChannelPostList extends PureComponent {
|
|||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
refreshIndicator: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fb8000',
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import {connect} from 'react-redux';
|
|||
import {selectPost} from 'mattermost-redux/actions/posts';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {makeGetPostsInChannel} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentChannelId, getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
|
|
@ -19,45 +18,23 @@ function makeMapStateToProps() {
|
|||
const getChannel = makeGetChannel();
|
||||
const getPostsInChannel = makeGetPostsInChannel();
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const channelId = ownProps.channelId;
|
||||
const {getPosts, getPostsRetryAttempts, getPostsSince, getPostsSinceRetryAttempts} = state.requests.posts;
|
||||
return function mapStateToProps(state) {
|
||||
const channelId = getCurrentChannelId(state);
|
||||
const posts = getPostsInChannel(state, channelId) || [];
|
||||
const {websocket: websocketRequest} = state.requests.general;
|
||||
const networkOnline = getConnection(state);
|
||||
const webSocketOnline = websocketRequest.status === RequestStatus.SUCCESS;
|
||||
|
||||
let getPostsStatus;
|
||||
if (getPostsRetryAttempts > 0) {
|
||||
getPostsStatus = getPosts.status;
|
||||
} else if (getPostsSinceRetryAttempts > 1) {
|
||||
getPostsStatus = getPostsSince.status;
|
||||
}
|
||||
|
||||
let channelIsRefreshing = state.views.channel.refreshing;
|
||||
let channelRefreshingFailed = getPostsStatus === RequestStatus.FAILURE && webSocketOnline;
|
||||
if (!networkOnline) {
|
||||
channelIsRefreshing = false;
|
||||
channelRefreshingFailed = false;
|
||||
}
|
||||
|
||||
const channelRefreshingFailed = state.views.channel.retryFailed;
|
||||
const channel = getChannel(state, {id: channelId}) || {};
|
||||
|
||||
return {
|
||||
channelId,
|
||||
channelIsRefreshing,
|
||||
channelRefreshingFailed,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
channelType: channel.type,
|
||||
channelDisplayName: channel.display_name,
|
||||
posts,
|
||||
postVisibility: state.views.channel.postVisibility[channelId],
|
||||
loadingPosts: state.views.channel.loadingPosts[channelId],
|
||||
lastViewedAt: getMyCurrentChannelMembership(state).last_viewed_at,
|
||||
networkOnline,
|
||||
totalMessageCount: channel.total_msg_count,
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
theme: getTheme(state)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -74,4 +51,54 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelPostList);
|
||||
function areStatesEqual(next, prev) {
|
||||
const nextChannelId = next.entities.channels.currentChannelId;
|
||||
const prevChannelId = prev.entities.channels.currentChannelId;
|
||||
if (prevChannelId !== nextChannelId) {
|
||||
return false;
|
||||
}
|
||||
const nextVisible = next.views.channel.postVisibility[nextChannelId];
|
||||
const prevVisible = prev.views.channel.postVisibility[nextChannelId];
|
||||
const nextPostsIds = next.entities.posts.postsInChannel[nextChannelId];
|
||||
const prevPostsIds = prev.entities.posts.postsInChannel[nextChannelId];
|
||||
|
||||
// When we don't have posts and we failed to get the post after tha max retry attempts
|
||||
const prevRetryFailed = prev.views.channel.retryFailed;
|
||||
const nextRetryFailed = next.views.channel.retryFailed;
|
||||
if (prevRetryFailed !== nextRetryFailed && nextRetryFailed && !nextPostsIds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we don't have post and visibility is not set we don't need to re-render
|
||||
if (!nextPostsIds || !nextVisible || !prevVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const nextVisiblePostsIds = nextPostsIds.slice(0, nextVisible);
|
||||
const prevVisiblePostsIds = prevPostsIds ? prevPostsIds.slice(0, prevVisible) : [];
|
||||
|
||||
// if we have a different amount of post we should re-render
|
||||
if (nextVisiblePostsIds.length !== prevVisiblePostsIds.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {status: nextStatus} = next.requests.posts.getPosts;
|
||||
const {status: prevStatus} = prev.requests.posts.getPosts;
|
||||
|
||||
// if we are requesting post for the first time we should re-render
|
||||
if (prevStatus === RequestStatus.STARTED && nextStatus === RequestStatus.SUCCESS &&
|
||||
nextPostsIds.length !== prevPostsIds.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if at least one post id changed we need to re-render
|
||||
for (let i = 0; i <= nextVisiblePostsIds.length; i++) {
|
||||
if (nextVisiblePostsIds[i] && nextVisiblePostsIds[i] !== prevVisiblePostsIds[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(ChannelPostList);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
|
|
@ -15,15 +13,11 @@ import {
|
|||
} from 'react-native';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {clearSearch} from 'mattermost-redux/actions/search';
|
||||
|
||||
import {handlePostDraftChanged} from 'app/actions/views/channel';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
|
||||
const SEARCH = 'search';
|
||||
|
||||
class ChannelSearchButton extends PureComponent {
|
||||
export default class ChannelSearchButton extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
clearSearch: PropTypes.func.isRequired,
|
||||
|
|
@ -33,7 +27,7 @@ class ChannelSearchButton extends PureComponent {
|
|||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
handlePress = async () => {
|
||||
handlePress = wrapWithPreventDoubleTap(async () => {
|
||||
const {actions, navigator, theme} = this.props;
|
||||
|
||||
await actions.clearSearch();
|
||||
|
|
@ -52,7 +46,7 @@ class ChannelSearchButton extends PureComponent {
|
|||
theme
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
@ -61,7 +55,7 @@ class ChannelSearchButton extends PureComponent {
|
|||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => preventDoubleTap(this.handlePress, this)}
|
||||
onPress={this.handlePress}
|
||||
style={style.container}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
|
|
@ -89,21 +83,3 @@ const style = StyleSheet.create({
|
|||
zIndex: 30
|
||||
}
|
||||
});
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
clearSearch,
|
||||
handlePostDraftChanged
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelSearchButton);
|
||||
22
app/screens/channel/channel_search_button/index.js
Normal file
22
app/screens/channel/channel_search_button/index.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {clearSearch} from 'mattermost-redux/actions/search';
|
||||
|
||||
import {handlePostDraftChanged} from 'app/actions/views/channel';
|
||||
|
||||
import ChannelSearchButton from './channel_search_button';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
clearSearch,
|
||||
handlePostDraftChanged
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ChannelSearchButton);
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
function ChannelTitle(props) {
|
||||
const channelName = props.displayName || props.currentChannel.display_name;
|
||||
let icon;
|
||||
if (channelName) {
|
||||
icon = (
|
||||
<Icon
|
||||
style={{marginHorizontal: 6}}
|
||||
size={12}
|
||||
name='chevron-down'
|
||||
color={props.theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flexDirection: 'row', flex: 1}}
|
||||
onPress={props.onPress}
|
||||
>
|
||||
<View style={{flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginHorizontal: 15}}>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
color: props.theme.sidebarHeaderTextColor,
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold'}}
|
||||
>
|
||||
{channelName}
|
||||
</Text>
|
||||
{icon}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
ChannelTitle.propTypes = {
|
||||
currentChannel: PropTypes.object,
|
||||
displayName: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
ChannelTitle.defaultProps = {
|
||||
currentChannel: {},
|
||||
displayName: null,
|
||||
theme: {}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentChannel: getCurrentChannel(state) || {},
|
||||
displayName: state.views.channel.displayName,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChannelTitle);
|
||||
88
app/screens/channel/channel_title/channel_title.js
Normal file
88
app/screens/channel/channel_title/channel_title.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class ChannelTitle extends PureComponent {
|
||||
static propTypes = {
|
||||
currentChannelName: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentChannel: {},
|
||||
displayName: null,
|
||||
theme: {}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {currentChannelName, displayName, onPress, theme} = this.props;
|
||||
const channelName = displayName || currentChannelName;
|
||||
const style = getStyle(theme);
|
||||
let icon;
|
||||
if (channelName) {
|
||||
icon = (
|
||||
<Icon
|
||||
style={style.icon}
|
||||
size={12}
|
||||
name='chevron-down'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={style.container}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.text}
|
||||
>
|
||||
{channelName}
|
||||
</Text>
|
||||
{icon}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: 15
|
||||
},
|
||||
icon: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
marginHorizontal: 6
|
||||
},
|
||||
text: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 17,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
};
|
||||
});
|
||||
22
app/screens/channel/channel_title/index.js
Normal file
22
app/screens/channel/channel_title/index.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelTitle from './channel_title';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentChannel = getCurrentChannel(state);
|
||||
|
||||
return {
|
||||
currentChannelName: currentChannel ? currentChannel.display_name : '',
|
||||
displayName: state.views.channel.displayName,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChannelTitle);
|
||||
|
|
@ -4,6 +4,13 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'mattermost-redux/actions/users';
|
||||
import {init as initWebSocket, close as closeWebSocket} from 'mattermost-redux/actions/websocket';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar,
|
||||
|
|
@ -13,29 +20,16 @@ import {connection} from 'app/actions/device';
|
|||
import {selectFirstAvailableTeam} from 'app/actions/views/select_team';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'mattermost-redux/actions/users';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {
|
||||
init as initWebSocket,
|
||||
close as closeWebSocket
|
||||
} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
import Channel from './channel';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {websocket} = state.requests.general;
|
||||
function mapStateToProps(state) {
|
||||
const {myChannels: channelsRequest} = state.requests.channels;
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
theme: getTheme(state),
|
||||
webSocketRequest: websocket,
|
||||
channelsRequestStatus: channelsRequest.status
|
||||
channelsRequestFailed: channelsRequest.status === RequestStatus.FAILURE
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -57,4 +51,27 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Channel);
|
||||
function areStatesEqual(next, prev) {
|
||||
// When switching teams
|
||||
if (next.entities.teams.currentTeamId !== prev.entities.teams.currentTeamId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When we have a new channel after switching teams
|
||||
const prevChannelId = prev.entities.channels.currentChannelId;
|
||||
const nextChannelId = next.entities.channels.currentChannelId;
|
||||
if (nextChannelId !== prevChannelId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When getting the channels for a team and the request fails
|
||||
const prevStatus = prev.requests.channels.myChannels.status;
|
||||
const nextStatus = next.requests.channels.myChannels.status;
|
||||
if (!nextChannelId && prevStatus === RequestStatus.STARTED && nextStatus === RequestStatus.FAILURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(Channel);
|
||||
|
|
|
|||
|
|
@ -3878,7 +3878,7 @@ makeerror@1.0.x:
|
|||
|
||||
mattermost-redux@mattermost/mattermost-redux#master:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/85db14af9a6d8c402213b0eb3c372f1ac6adad34"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/6932c3fd4064434c5b30cff9898d724cdc687648"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
harmony-reflect "1.5.1"
|
||||
|
|
|
|||
Loading…
Reference in a new issue