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:
parent
d156c9f320
commit
84a2a0fab8
18 changed files with 303 additions and 92 deletions
|
|
@ -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};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
23
app/reducers/views/emoji.js
Normal file
23
app/reducers/views/emoji.js
Normal 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
|
||||
});
|
||||
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
10
yarn.lock
10
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue