RN-153 Show emoji reactions on posts (#745)

* RN-153 Show emoji reactions on posts

* Review Feedback

* Change makeMapStateToProps in export
This commit is contained in:
Chris Duarte 2017-07-17 10:27:16 -06:00 committed by enahum
parent 93d7697dcc
commit 9db0a38e8b
7 changed files with 208 additions and 3 deletions

View file

@ -15,13 +15,15 @@ export default class Emoji extends React.PureComponent {
customEmojis: PropTypes.object,
emojiName: PropTypes.string.isRequired,
literal: PropTypes.string,
padding: PropTypes.number,
size: PropTypes.number.isRequired,
textStyle: CustomPropTypes.Style
}
static defaultProps = {
customEmojis: new Map(),
literal: ''
literal: '',
padding: 10
}
render() {
@ -29,6 +31,7 @@ export default class Emoji extends React.PureComponent {
customEmojis,
emojiName,
literal,
padding,
size,
textStyle
} = this.props;
@ -48,7 +51,7 @@ export default class Emoji extends React.PureComponent {
return (
<Image
style={{width: size, height: size, padding: 10}}
style={{width: size, height: size, padding}}
source={{uri: imageUrl}}
/>
);

View file

@ -23,6 +23,7 @@ function mapStateToProps(state, ownProps) {
attachments: post.props && post.props.attachments,
fileIds: post.file_ids,
hasBeenDeleted: post.state === Posts.POST_DELETED,
hasReactions: post.has_reactions,
isFailed: post.failed,
isFlagged: isPostFlagged(post.id, myPreferences),
isPending: post.id === post.pending_post_id,

View file

@ -21,6 +21,7 @@ import SlackAttachments from 'app/components/slack_attachments';
import {emptyFunction} from 'app/utils/general';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import Reactions from 'app/components/reactions';
class PostBody extends PureComponent {
static propTypes = {
@ -33,6 +34,7 @@ class PostBody extends PureComponent {
canEdit: PropTypes.bool,
fileIds: PropTypes.array,
hasBeenDeleted: PropTypes.bool,
hasReactions: PropTypes.bool,
intl: intlShape.isRequired,
isFailed: PropTypes.bool,
isFlagged: PropTypes.bool,
@ -132,6 +134,7 @@ class PostBody extends PureComponent {
canDelete,
canEdit,
hasBeenDeleted,
hasReactions,
isFailed,
isFlagged,
isPending,
@ -143,6 +146,7 @@ class PostBody extends PureComponent {
onPostDelete,
onPostEdit,
onPress,
postId,
renderReplyBar,
theme,
toggleSelected
@ -219,6 +223,7 @@ class PostBody extends PureComponent {
{messageComponent}
{this.renderSlackAttachments(messageStyle, blockStyles, textStyles)}
{this.renderFileAttachments()}
{hasReactions && <Reactions postId={postId}/>}
</OptionsContext>
</View>
{isFailed &&

View file

@ -0,0 +1,54 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {addReaction, getReactionsForPost, removeReaction} from 'mattermost-redux/actions/posts';
import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {getTheme} from 'app/selectors/preferences';
import Reactions from './reactions';
function makeMapStateToProps() {
const getReactionsForPostSelector = makeGetReactionsForPost();
return function mapStateToProps(state, ownProps) {
const currentUserId = getCurrentUser(state);
const reactionsForPost = getReactionsForPostSelector(state, ownProps.postId);
const highlightedReactions = [];
const reactionsByName = reactionsForPost.reduce((reactions, reaction) => {
if (reactions.has(reaction.emoji_name)) {
reactions.get(reaction.emoji_name).push(reaction);
} else {
reactions.set(reaction.emoji_name, [reaction]);
}
if (reaction.user_id === currentUserId) {
highlightedReactions.push(reaction.emoji_name);
}
return reactions;
}, new Map());
return {
highlightedReactions,
reactions: reactionsByName,
theme: getTheme(state)
};
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReaction,
getReactionsForPost,
removeReaction
}, dispatch)
};
}
export default connect(makeMapStateToProps, mapDispatchToProps)(Reactions);

View file

@ -0,0 +1,70 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
Text,
TouchableOpacity
} from 'react-native';
import Emoji from 'app/components/emoji';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class Reaction extends PureComponent {
static propTypes = {
count: PropTypes.number.isRequired,
emojiName: PropTypes.string.isRequired,
highlight: PropTypes.bool.isRequired,
onPress: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired
}
handlePress = () => {
const {emojiName, highlight, onPress} = this.props;
onPress(emojiName, highlight);
}
render() {
const {count, emojiName, highlight, theme} = this.props;
const styles = getStyleSheet(theme);
return (
<TouchableOpacity
onPress={this.handlePress}
style={[styles.reaction, (highlight && styles.highlight)]}
>
<Emoji
emojiName={emojiName}
size={15}
padding={5}
/>
<Text style={styles.count}>{count}</Text>
</TouchableOpacity>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return StyleSheet.create({
count: {
color: theme.linkColor,
marginLeft: 6
},
highlight: {
backgroundColor: changeOpacity(theme.linkColor, 0.1)
},
reaction: {
alignItems: 'center',
borderRadius: 2,
borderColor: changeOpacity(theme.linkColor, 0.4),
borderWidth: 1,
flexDirection: 'row',
marginRight: 6,
marginVertical: 5,
paddingVertical: 2,
paddingHorizontal: 6
}
});
});

View file

@ -0,0 +1,72 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
View
} from 'react-native';
import Reaction from './reaction';
export default class Reactions extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
addReaction: PropTypes.func.isRequired,
getReactionsForPost: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired
}).isRequired,
highlightedReactions: PropTypes.array.isRequired,
postId: PropTypes.string.isRequired,
reactions: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
}
componentDidMount() {
const {actions, postId} = this.props;
actions.getReactionsForPost(postId);
}
handleReactionPress = (emoji, remove) => {
const {actions, postId} = this.props;
if (remove) {
actions.removeReaction(postId, emoji);
} else {
actions.addReaction(postId, emoji);
}
}
renderReactions = () => {
const {highlightedReactions, reactions, theme} = this.props;
return Array.from(reactions.keys()).map((r) => {
return (
<Reaction
key={r}
count={reactions.get(r).length}
emojiName={r}
highlight={highlightedReactions.includes(r)}
onPress={this.handleReactionPress}
theme={theme}
/>
);
});
}
render() {
return (
<View style={style.reactions}>
{this.renderReactions()}
</View>
);
}
}
const style = StyleSheet.create({
reactions: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start'
}
});

View file

@ -3645,7 +3645,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/e3b029e3df3d894485a27a9ca3cc7cbe7711ddb9"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/0d2b682cabc3893a0124f6674bcbfb653eead4a5"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"