ABC-123 Add handling for loading custom emojis asynchronously (#1406)

* Add async handling of custom emoji to posts and channel info

* Add server request to emoji autocomplete

* Add custom emoji paging and server search to emoji picker

* Fix android

* Update mattermost-redux and revert temp change

* Backwards compatibility fixes

* Updates per feedback

* Set server version when user is not logged in

* Reset section index when search is cleared or cancelled

* Some small fixes and upgrade mattermost-redux

* Minor sorting changes
This commit is contained in:
Joram Wilander 2018-02-15 17:38:45 -05:00 committed by GitHub
parent d156c9f320
commit 84a2a0fab8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 303 additions and 92 deletions

View file

@ -32,3 +32,13 @@ export function addRecentEmoji(emoji) {
emoji
};
}
export function incrementEmojiPickerPage() {
return async (dispatch) => {
dispatch({
type: ViewTypes.INCREMENT_EMOJI_PICKER_PAGE
});
return {data: true};
};
}

View file

@ -10,6 +10,8 @@ import {
View
} from 'react-native';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import Emoji from 'app/components/emoji';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@ -20,7 +22,8 @@ const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
export default class EmojiSuggestion extends Component {
static propTypes = {
actions: PropTypes.shape({
addReactionToLatestPost: PropTypes.func.isRequired
addReactionToLatestPost: PropTypes.func.isRequired,
autocompleteCustomEmojis: PropTypes.func.isRequired
}).isRequired,
cursorPosition: PropTypes.number,
emojis: PropTypes.array.isRequired,
@ -30,7 +33,8 @@ export default class EmojiSuggestion extends Component {
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
rootId: PropTypes.string,
value: PropTypes.string
value: PropTypes.string,
serverVersion: PropTypes.string
};
static defaultProps = {
@ -43,6 +47,12 @@ export default class EmojiSuggestion extends Component {
dataSource: []
};
constructor(props) {
super(props);
this.matchTerm = '';
}
componentWillReceiveProps(nextProps) {
if (nextProps.isSearch) {
return;
@ -54,7 +64,6 @@ export default class EmojiSuggestion extends Component {
if (!match || this.state.emojiComplete) {
this.setState({
active: false,
matchTerm: null,
emojiComplete: false
});
@ -63,18 +72,33 @@ export default class EmojiSuggestion extends Component {
return;
}
const matchTerm = match[3];
const oldMatchTerm = this.matchTerm;
this.matchTerm = match[3] || '';
const matchTermChanged = matchTerm !== this.state.matchTerm;
if (matchTermChanged) {
this.setState({
matchTerm
});
// If we're server version 4.7 or higher
if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
if (this.matchTerm !== oldMatchTerm && this.matchTerm.length) {
this.props.actions.autocompleteCustomEmojis(this.matchTerm);
return;
}
if (this.props.emojis !== nextProps.emojis) {
this.handleFuzzySearch(this.matchTerm, nextProps);
} else if (!this.matchTerm.length) {
const initialEmojis = [...nextProps.emojis];
initialEmojis.splice(0, 300);
const data = initialEmojis.sort();
this.setEmojiData(data);
}
return;
}
if (matchTermChanged) {
this.handleFuzzySearch(matchTerm, nextProps);
} else if (!matchTerm.length) {
// If we're server version 4.6 or lower
if (this.matchTerm !== oldMatchTerm) {
this.handleFuzzySearch(this.matchTerm, nextProps);
} else if (!this.matchTerm.length) {
const initialEmojis = [...nextProps.emojis];
initialEmojis.splice(0, 300);
const data = initialEmojis.sort();

View file

@ -6,6 +6,8 @@ import {createSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {autocompleteCustomEmojis} from 'mattermost-redux/actions/emojis';
import {Client4} from 'mattermost-redux/client';
import {addReactionToLatestPost} from 'app/actions/views/emoji';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@ -43,14 +45,16 @@ function mapStateToProps(state) {
return {
fuse,
emojis,
theme: getTheme(state)
theme: getTheme(state),
serverVersion: state.entities.general.serverVersion || Client4.getServerVersion()
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReactionToLatestPost
addReactionToLatestPost,
autocompleteCustomEmojis
}, dispatch)
};
}

View file

@ -13,9 +13,6 @@ import {
import FastImage from 'react-native-fast-image';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
import {Client4} from 'mattermost-redux/client';
const scaleEmojiBasedOnDevice = (size) => {
if (Platform.OS === 'ios') {
@ -26,8 +23,26 @@ const scaleEmojiBasedOnDevice = (size) => {
export default class Emoji extends React.PureComponent {
static propTypes = {
customEmojis: PropTypes.object,
/*
* Emoji text name.
*/
emojiName: PropTypes.string.isRequired,
/*
* Image URL for the emoji.
*/
imageUrl: PropTypes.string.isRequired,
/*
* Set if this is a custom emoji.
*/
isCustomEmoji: PropTypes.bool.isRequired,
/*
* Set to render only the text and no image.
*/
displayTextOnly: PropTypes.bool,
literal: PropTypes.string,
size: PropTypes.number,
textStyle: CustomPropTypes.Style,
@ -36,14 +51,15 @@ export default class Emoji extends React.PureComponent {
static defaultProps = {
customEmojis: new Map(),
literal: ''
literal: '',
imageUrl: '',
isCustomEmoji: false
};
constructor(props) {
super(props);
this.state = {
...this.getImageUrl(props),
originalWidth: 0,
originalHeight: 0
};
@ -51,24 +67,22 @@ export default class Emoji extends React.PureComponent {
componentWillMount() {
this.mounted = true;
if (this.state.imageUrl && this.state.isCustomEmoji) {
this.updateImageHeight(this.state.imageUrl);
if (!this.props.displayTextOnly && this.props.imageUrl && this.props.isCustomEmoji) {
this.updateImageHeight(this.props.imageUrl);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.customEmojis !== this.props.customEmojis || nextProps.emojiName !== this.props.emojiName) {
if (nextProps.emojiName !== this.props.emojiName) {
this.setState({
...this.getImageUrl(nextProps),
originalWidth: 0,
originalHeight: 0
});
}
}
componentWillUpdate(nextProps, nextState) {
if (nextState.imageUrl !== this.state.imageUrl && nextState.imageUrl && nextState.isCustomEmoji) {
this.updateImageHeight(nextState.imageUrl);
if (!nextProps.displayTextOnly && nextProps.imageUrl && nextProps.isCustomEmoji &&
nextProps.imageUrl !== this.props.imageUrl) {
this.updateImageHeight(nextProps.imageUrl);
}
}
@ -76,28 +90,6 @@ export default class Emoji extends React.PureComponent {
this.mounted = false;
}
getImageUrl = (props = this.props) => {
const emojiName = props.emojiName;
let imageUrl = '';
let isCustomEmoji = false;
if (EmojiIndicesByAlias.has(emojiName)) {
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
} else if (props.customEmojis.has(emojiName)) {
const emoji = props.customEmojis.get(emojiName);
imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
isCustomEmoji = true;
}
return {
imageUrl,
isCustomEmoji
};
}
updateImageHeight = (imageUrl) => {
Image.getSize(imageUrl, (originalWidth, originalHeight) => {
if (this.mounted) {
@ -113,7 +105,9 @@ export default class Emoji extends React.PureComponent {
const {
literal,
textStyle,
token
token,
imageUrl,
displayTextOnly
} = this.props;
let size = this.props.size;
@ -124,19 +118,12 @@ export default class Emoji extends React.PureComponent {
size = scaleEmojiBasedOnDevice(fontSize);
}
if (!this.state.imageUrl) {
if (displayTextOnly) {
return <Text style={textStyle}>{literal}</Text>;
}
let ImageComponent;
if (Platform.OS === 'android') {
ImageComponent = Image;
} else {
ImageComponent = FastImage;
}
const source = {
uri: this.state.imageUrl,
uri: imageUrl,
headers: {
Authorization: `Bearer ${token}`
}
@ -170,6 +157,22 @@ export default class Emoji extends React.PureComponent {
// force a new image to be rendered when the size changes
const key = Platform.OS === 'android' ? (height + '-' + width) : null;
let ImageComponent;
if (Platform.OS === 'android') {
ImageComponent = Image;
} else {
ImageComponent = FastImage;
}
if (!imageUrl) {
return (
<ImageComponent
key={key}
style={{width, height, marginTop}}
/>
);
}
return (
<ImageComponent
key={key}

View file

@ -4,12 +4,40 @@
import {connect} from 'react-redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {Client4} from 'mattermost-redux/client';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
import Emoji from './emoji';
function mapStateToProps(state) {
function mapStateToProps(state, ownProps) {
const emojiName = ownProps.emojiName;
const customEmojis = getCustomEmojisByName(state);
let imageUrl = '';
let isCustomEmoji = false;
let displayTextOnly = false;
if (EmojiIndicesByAlias.has(emojiName)) {
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
} else if (customEmojis.has(emojiName)) {
const emoji = customEmojis.get(emojiName);
imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
isCustomEmoji = true;
} else {
displayTextOnly = state.entities.emojis.nonExistentEmoji.has(emojiName) ||
getConfig(state).EnableCustomEmoji !== 'true' ||
getCurrentUserId(state) === '' ||
!isMinimumServerVersion(Client4.getServerVersion(), 4, 7);
}
return {
customEmojis: getCustomEmojisByName(state),
imageUrl,
isCustomEmoji,
displayTextOnly,
token: state.entities.general.credentials.token
};
}

View file

@ -5,6 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
ActivityIndicator,
FlatList,
KeyboardAvoidingView,
Platform,
@ -17,6 +18,8 @@ import DeviceInfo from 'react-native-device-info';
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
import sectionListGetItemLayout from 'react-native-section-list-get-item-layout';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import SafeAreaView from 'app/components/safe_area_view';
@ -30,16 +33,25 @@ const EMOJI_SIZE = 30;
const EMOJI_GUTTER = 7.5;
const SECTION_MARGIN = 15;
const SECTION_HEADER_HEIGHT = 28;
const EMOJIS_PER_PAGE = 200;
export default class EmojiPicker extends PureComponent {
static propTypes = {
fuse: PropTypes.object.isRequired,
customEmojisEnabled: PropTypes.bool.isRequired,
customEmojiPage: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
emojis: PropTypes.array.isRequired,
emojisBySection: PropTypes.array.isRequired,
deviceWidth: PropTypes.number.isRequired,
fuse: PropTypes.object.isRequired,
isLandscape: PropTypes.bool.isRequired,
onEmojiPress: PropTypes.func,
theme: PropTypes.object.isRequired
serverVersion: PropTypes.string,
theme: PropTypes.object.isRequired,
actions: PropTypes.shape({
getCustomEmojis: PropTypes.func.isRequired,
incrementEmojiPickerPage: PropTypes.func.isRequired,
searchCustomEmojis: PropTypes.func.isRequired
}).isRequired
};
static defaultProps = {
@ -70,20 +82,31 @@ export default class EmojiPicker extends PureComponent {
emojiSectionIndexByOffset,
filteredEmojis: [],
searchTerm: '',
currentSectionIndex: 0
currentSectionIndex: 0,
missingPages: isMinimumServerVersion(this.props.serverVersion, 4, 7)
};
}
componentWillReceiveProps(nextProps) {
let rebuildEmojis = false;
if (this.props.deviceWidth !== nextProps.deviceWidth) {
this.setState({
emojis: this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth)
});
rebuildEmojis = true;
if (this.refs.search_bar) {
this.refs.search_bar.blur();
}
}
if (this.props.emojis !== nextProps.emojis) {
rebuildEmojis = true;
}
if (rebuildEmojis) {
const emojis = this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth);
this.setState({
emojis
});
}
}
renderableEmojis = (emojis, deviceWidth) => {
@ -139,13 +162,22 @@ export default class EmojiPicker extends PureComponent {
};
changeSearchTerm = (text) => {
this.setState({
const nextState = {
searchTerm: text
});
};
if (!text) {
nextState.currentSectionIndex = 0;
}
this.setState(nextState);
clearTimeout(this.searchTermTimeout);
const timeout = text ? 100 : 0;
this.searchTermTimeout = setTimeout(() => {
this.searchTermTimeout = setTimeout(async () => {
if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
await this.props.actions.searchCustomEmojis(text);
}
const filteredEmojis = this.searchEmojis(text);
this.setState({
filteredEmojis
@ -155,6 +187,7 @@ export default class EmojiPicker extends PureComponent {
cancelSearch = () => {
this.setState({
currentSectionIndex: 0,
filteredEmojis: [],
searchTerm: ''
});
@ -214,6 +247,26 @@ export default class EmojiPicker extends PureComponent {
);
};
loadMoreCustomEmojis = async () => {
if (!this.props.customEmojisEnabled || !isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
return;
}
const {data} = await this.props.actions.getCustomEmojis(this.props.customEmojiPage, EMOJIS_PER_PAGE);
this.setState({loadingMore: false});
if (!data) {
return;
}
if (data.length < EMOJIS_PER_PAGE) {
this.setState({missingPages: false});
return;
}
this.props.actions.incrementEmojiPickerPage();
}
onScroll = (e) => {
if (this.state.jumpToSection) {
return;
@ -285,9 +338,13 @@ export default class EmojiPicker extends PureComponent {
);
};
handleSectionIconPress = (index) => {
handleSectionIconPress = (index, isCustomSection = false) => {
this.scrollToSectionTries = 0;
this.scrollToSection(index);
if (isCustomSection && this.props.customEmojiPage === 0) {
this.loadMoreCustomEmojis();
}
}
renderSectionIcons = () => {
@ -295,7 +352,7 @@ export default class EmojiPicker extends PureComponent {
const styles = getStyleSheetFromTheme(theme);
return this.state.emojis.map((section, index) => {
const onPress = () => this.handleSectionIconPress(index);
const onPress = () => this.handleSectionIconPress(index, section.key === 'custom');
return (
<TouchableOpacity
@ -317,6 +374,21 @@ export default class EmojiPicker extends PureComponent {
this.sectionList = c;
};
renderFooter = () => {
if (!this.state.missingPages) {
return null;
}
const {theme} = this.props;
const styles = getStyleSheetFromTheme(theme);
return (
<View style={styles.loading}>
<ActivityIndicator/>
</View>
);
}
render() {
const {deviceWidth, isLandscape, theme} = this.props;
const {emojis, filteredEmojis, searchTerm} = this.state;
@ -353,6 +425,9 @@ export default class EmojiPicker extends PureComponent {
onScrollToIndexFailed={this.handleScrollToSectionFailed}
onMomentumScrollEnd={this.onMomentumScrollEnd}
pageSize={30}
ListFooterComponent={this.renderFooter}
onEndReached={this.loadMoreCustomEmojis}
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1}
/>
);
}
@ -511,6 +586,10 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
},
wrapper: {
flex: 1
},
loading: {
flex: 1,
alignItems: 'center'
}
};
});

View file

@ -8,6 +8,7 @@ import {
TouchableOpacity,
View
} from 'react-native';
import shallowEqual from 'shallow-equals';
import Emoji from 'app/components/emoji';
@ -20,7 +21,7 @@ export default class EmojiPickerRow extends Component {
}
shouldComponentUpdate(nextProps) {
return this.props.items.length !== nextProps.items.length;
return !shallowEqual(this.props.items, nextProps.items);
}
renderEmojis = (emoji, index, emojis) => {
@ -92,4 +93,4 @@ const styles = StyleSheet.create({
emojiRight: {
marginRight: 0
}
});
});

View file

@ -3,11 +3,16 @@
import {connect} from 'react-redux';
import {createSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {getDimensions, isLandscape} from 'app/selectors/device';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getCustomEmojis, searchCustomEmojis} from 'mattermost-redux/actions/emojis';
import {Client4} from 'mattermost-redux/client';
import {incrementEmojiPickerPage} from 'app/actions/views/emoji';
import {getDimensions, isLandscape} from 'app/selectors/device';
import {CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from 'app/utils/emojis';
import EmojiPicker from './emoji_picker';
@ -152,8 +157,21 @@ function mapStateToProps(state) {
emojisBySection,
deviceWidth,
isLandscape: isLandscape(state),
theme: getTheme(state)
theme: getTheme(state),
customEmojisEnabled: getConfig(state).EnableCustomEmoji === 'true',
customEmojiPage: state.views.emoji.emojiPickerCustomPage,
serverVersion: state.entities.general.serverVersion || Client4.getServerVersion()
};
}
export default connect(mapStateToProps)(EmojiPicker);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getCustomEmojis,
incrementEmojiPickerPage,
searchCustomEmojis
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(EmojiPicker);

View file

@ -62,7 +62,9 @@ const ViewTypes = keyMirror({
ADD_RECENT_EMOJI: null,
EXTENSION_SELECTED_TEAM_ID: null,
ANNOUNCEMENT_BANNER: null
ANNOUNCEMENT_BANNER: null,
INCREMENT_EMOJI_PICKER_PAGE: null
});
export default {

View file

@ -22,6 +22,7 @@ import semver from 'semver';
import {General} from 'mattermost-redux/constants';
import {setAppState, setDeviceToken, setServerVersion} from 'mattermost-redux/actions/general';
import {markChannelAsRead} from 'mattermost-redux/actions/channels';
import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {logError} from 'mattermost-redux/actions/errors';
import {loadMe, logout} from 'mattermost-redux/actions/users';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
@ -56,6 +57,7 @@ import {init as initAnalytics} from 'app/utils/segment';
import {captureException, initializeSentry, LOGGER_JAVASCRIPT, LOGGER_NATIVE} from 'app/utils/sentry';
import tracker from 'app/utils/time_tracker';
import {stripTrailingSlashes} from 'app/utils/url';
import {EmojiIndicesByAlias} from 'app/utils/emojis';
import LocalConfig from 'assets/config';
@ -90,6 +92,7 @@ export default class Mattermost {
setJSExceptionHandler(this.errorHandler, false);
setNativeExceptionHandler(this.nativeErrorHandler, false);
setSystemEmojis(EmojiIndicesByAlias);
}
errorHandler = (e, isFatal) => {

View file

@ -0,0 +1,23 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {UserTypes} from 'mattermost-redux/action_types';
import {ViewTypes} from 'app/constants';
function emojiPickerCustomPage(state = 0, action) {
switch (action.type) {
case ViewTypes.INCREMENT_EMOJI_PICKER_PAGE:
return state + 1;
case UserTypes.LOGOUT_SUCCESS:
return 0;
default:
return state;
}
}
export default combineReducers({
emojiPickerCustomPage
});

View file

@ -16,6 +16,7 @@ import search from './search';
import selectServer from './select_server';
import team from './team';
import thread from './thread';
import emoji from './emoji';
export default combineReducers({
announcement,
@ -30,5 +31,6 @@ export default combineReducers({
search,
selectServer,
team,
thread
thread,
emoji
});

View file

@ -43,7 +43,8 @@ class ChannelInfo extends PureComponent {
getChannelStats: PropTypes.func.isRequired,
leaveChannel: PropTypes.func.isRequired,
favoriteChannel: PropTypes.func.isRequired,
unfavoriteChannel: PropTypes.func.isRequired
unfavoriteChannel: PropTypes.func.isRequired,
getCustomEmojisInText: PropTypes.func.isRequired
})
};
@ -57,6 +58,7 @@ class ChannelInfo extends PureComponent {
componentDidMount() {
this.props.actions.getChannelStats(this.props.currentChannel.id);
this.props.actions.getCustomEmojisInText(this.props.currentChannel.header);
}
componentWillReceiveProps(nextProps) {

View file

@ -11,6 +11,7 @@ import {
} from 'app/actions/views/channel';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCustomEmojisInText} from 'mattermost-redux/actions/emojis';
import {favoriteChannel, getChannelStats, deleteChannel, unfavoriteChannel} from 'mattermost-redux/actions/channels';
import {General} from 'mattermost-redux/constants';
import {
@ -68,7 +69,8 @@ function mapDispatchToProps(dispatch) {
getChannelStats,
leaveChannel,
favoriteChannel,
unfavoriteChannel
unfavoriteChannel,
getCustomEmojisInText
}, dispatch)
};
}

View file

@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getPing, resetPing} from 'mattermost-redux/actions/general';
import {getPing, resetPing, setServerVersion} from 'mattermost-redux/actions/general';
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
import {loadConfigAndLicense} from 'app/actions/views/root';
@ -37,7 +37,8 @@ function mapDispatchToProps(dispatch) {
handleServerUrlChanged,
loadConfigAndLicense,
resetPing,
setLastUpgradeCheck
setLastUpgradeCheck,
setServerVersion
}, dispatch)
};
}

View file

@ -40,7 +40,8 @@ class SelectServer extends PureComponent {
handleServerUrlChanged: PropTypes.func.isRequired,
loadConfigAndLicense: PropTypes.func.isRequired,
resetPing: PropTypes.func.isRequired,
setLastUpgradeCheck: PropTypes.func.isRequired
setLastUpgradeCheck: PropTypes.func.isRequired,
setServerVersion: PropTypes.func.isRequired
}).isRequired,
allowOtherServers: PropTypes.bool,
config: PropTypes.object,
@ -214,7 +215,8 @@ class SelectServer extends PureComponent {
const {
getPing,
handleServerUrlChanged,
loadConfigAndLicense
loadConfigAndLicense,
setServerVersion
} = this.props.actions;
this.setState({
@ -245,6 +247,7 @@ class SelectServer extends PureComponent {
if (!result.error) {
loadConfigAndLicense();
setServerVersion(Client4.getServerVersion());
}
this.setState({

View file

@ -71,7 +71,7 @@ export default function configureAppStore(initialState) {
};
},
null,
{whitelist: ['views']} // Only run this filter the views state (or any other entry that ends up being named views)
{whitelist: ['views']} // Only run this filter on the views state (or any other entry that ends up being named views)
);
const emojiBlackList = {nonExistentEmoji: true};

View file

@ -1684,7 +1684,13 @@ colors@0.5.x:
version "0.5.1"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
combined-stream@^1.0.5, combined-stream@~1.0.5:
combined-stream@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
dependencies:
delayed-stream "~1.0.0"
combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
dependencies:
@ -4018,7 +4024,7 @@ makeerror@1.0.x:
mattermost-redux@mattermost/mattermost-redux:
version "1.2.0"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/17237100e4ba15c73e158973044282d1dcb5562e"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/624b545605695ade03aa0604ef14ba44c4f06d9f"
dependencies:
deep-equal "1.0.1"
form-data "2.3.1"