forked from Ivasoft/mattermost-mobile
MM-17838 Wrap reactions when they exceed screen size (#3144)
* MM-17838 Wrap reactions when they exceed screen size * Hide add reaction button after 40 reactions * Hide add reaction button after 40 reactions * Fix tests * Fix crash when post has no reactions and convert to factory * Create constant and use a separate prop for allowing more reactions
This commit is contained in:
committed by
Elias Nahum
parent
07ee9f0cc9
commit
3bd3e56025
@@ -14,6 +14,7 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getChannel, isChannelReadOnlyById} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {addReaction} from 'app/actions/views/emoji';
|
||||
import {MAX_ALLOWED_REACTIONS} from 'app/constants/emoji';
|
||||
|
||||
import Reactions from './reactions';
|
||||
|
||||
@@ -27,17 +28,23 @@ function makeMapStateToProps() {
|
||||
const channelIsArchived = channel.delete_at !== 0;
|
||||
const channelIsReadOnly = isChannelReadOnlyById(state, channelId);
|
||||
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const reactions = getReactionsForPostSelector(state, ownProps.postId);
|
||||
|
||||
let canAddReaction = true;
|
||||
let canRemoveReaction = true;
|
||||
let canAddMoreReactions = true;
|
||||
if (channelIsArchived || channelIsReadOnly) {
|
||||
canAddReaction = false;
|
||||
canRemoveReaction = false;
|
||||
canAddMoreReactions = false;
|
||||
} else if (hasNewPermissions(state)) {
|
||||
canAddReaction = haveIChannelPermission(state, {
|
||||
team: teamId,
|
||||
channel: channelId,
|
||||
permission: Permissions.ADD_REACTION,
|
||||
});
|
||||
canAddMoreReactions = Object.values(reactions).length < MAX_ALLOWED_REACTIONS;
|
||||
canRemoveReaction = haveIChannelPermission(state, {
|
||||
team: teamId,
|
||||
channel: channelId,
|
||||
@@ -45,14 +52,12 @@ function makeMapStateToProps() {
|
||||
});
|
||||
}
|
||||
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const reactions = getReactionsForPostSelector(state, ownProps.postId);
|
||||
|
||||
return {
|
||||
currentUserId,
|
||||
reactions,
|
||||
theme: getTheme(state),
|
||||
canAddReaction,
|
||||
canAddMoreReactions,
|
||||
canRemoveReaction,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Image,
|
||||
ScrollView,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
@@ -26,13 +26,14 @@ export default class Reactions extends PureComponent {
|
||||
getReactionsForPost: PropTypes.func.isRequired,
|
||||
removeReaction: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canAddReaction: PropTypes.bool,
|
||||
canAddMoreReactions: PropTypes.bool,
|
||||
canRemoveReaction: PropTypes.bool.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
position: PropTypes.oneOf(['right', 'left']),
|
||||
postId: PropTypes.string.isRequired,
|
||||
reactions: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
canAddReaction: PropTypes.bool,
|
||||
canRemoveReaction: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -132,7 +133,7 @@ export default class Reactions extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {position, reactions, canAddReaction} = this.props;
|
||||
const {position, reactions, canAddMoreReactions} = this.props;
|
||||
const styles = getStyleSheet(this.props.theme);
|
||||
|
||||
if (!reactions) {
|
||||
@@ -140,7 +141,7 @@ export default class Reactions extends PureComponent {
|
||||
}
|
||||
|
||||
let addMoreReactions = null;
|
||||
if (canAddReaction) {
|
||||
if (canAddMoreReactions) {
|
||||
addMoreReactions = (
|
||||
<TouchableWithFeedback
|
||||
key='addReaction'
|
||||
@@ -173,14 +174,9 @@ export default class Reactions extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
alwaysBounceHorizontal={false}
|
||||
horizontal={true}
|
||||
overScrollMode='never'
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
>
|
||||
<View style={styles.reactionsContainer}>
|
||||
{reactionElements}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -207,5 +203,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
paddingHorizontal: 6,
|
||||
width: 40,
|
||||
},
|
||||
reactionsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
alignContent: 'flex-start',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const ALL_EMOJIS = 'all_emojis';
|
||||
export const MAX_ALLOWED_REACTIONS = 40;
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
removePost,
|
||||
} from 'mattermost-redux/actions/posts';
|
||||
import {General, Permissions} from 'mattermost-redux/constants';
|
||||
import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getChannel, getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getConfig, getLicense, hasNewPermissions} from 'mattermost-redux/selectors/entities/general';
|
||||
@@ -21,94 +22,103 @@ import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'
|
||||
import {getCurrentTeamId, getCurrentTeamUrl} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {canEditPost} from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import {MAX_ALLOWED_REACTIONS} from 'app/constants/emoji';
|
||||
import {THREAD} from 'app/constants/screen';
|
||||
import {addReaction} from 'app/actions/views/emoji';
|
||||
import {getDimensions, isLandscape} from 'app/selectors/device';
|
||||
|
||||
import PostOptions from './post_options';
|
||||
|
||||
export function mapStateToProps(state, ownProps) {
|
||||
const post = ownProps.post;
|
||||
const channel = getChannel(state, post.channel_id) || {};
|
||||
const config = getConfig(state);
|
||||
const license = getLicense(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
export function makeMapStateToProps() {
|
||||
const getReactionsForPostSelector = makeGetReactionsForPost();
|
||||
|
||||
const channelIsArchived = channel.delete_at !== 0;
|
||||
return (state, ownProps) => {
|
||||
const post = ownProps.post;
|
||||
const channel = getChannel(state, post.channel_id) || {};
|
||||
const config = getConfig(state);
|
||||
const license = getLicense(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const reactions = getReactionsForPostSelector(state, post.id);
|
||||
const channelIsArchived = channel.delete_at !== 0;
|
||||
|
||||
let canAddReaction = true;
|
||||
let canReply = true;
|
||||
let canCopyPermalink = true;
|
||||
let canCopyText = false;
|
||||
let canEdit = false;
|
||||
let canEditUntil = -1;
|
||||
let {canDelete} = ownProps;
|
||||
let canFlag = true;
|
||||
let canPin = true;
|
||||
let canAddReaction = true;
|
||||
let canReply = true;
|
||||
let canCopyPermalink = true;
|
||||
let canCopyText = false;
|
||||
let canEdit = false;
|
||||
let canEditUntil = -1;
|
||||
let {canDelete} = ownProps;
|
||||
let canFlag = true;
|
||||
let canPin = true;
|
||||
|
||||
if (hasNewPermissions(state)) {
|
||||
canAddReaction = haveIChannelPermission(state, {
|
||||
team: currentTeamId,
|
||||
channel: post.channel_id,
|
||||
permission: Permissions.ADD_REACTION,
|
||||
});
|
||||
}
|
||||
|
||||
if (ownProps.location === THREAD) {
|
||||
canReply = false;
|
||||
}
|
||||
|
||||
if (channelIsArchived || ownProps.channelIsReadOnly) {
|
||||
canAddReaction = false;
|
||||
canReply = false;
|
||||
canDelete = false;
|
||||
canPin = false;
|
||||
} else {
|
||||
canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post);
|
||||
if (canEdit && license.IsLicensed === 'true' &&
|
||||
(config.AllowEditPost === General.ALLOW_EDIT_POST_TIME_LIMIT || (config.PostEditTimeLimit !== -1 && config.PostEditTimeLimit !== '-1'))
|
||||
) {
|
||||
canEditUntil = post.create_at + (config.PostEditTimeLimit * 1000);
|
||||
if (hasNewPermissions(state)) {
|
||||
canAddReaction = haveIChannelPermission(state, {
|
||||
team: currentTeamId,
|
||||
channel: post.channel_id,
|
||||
permission: Permissions.ADD_REACTION,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ownProps.isSystemMessage) {
|
||||
canAddReaction = false;
|
||||
canReply = false;
|
||||
canCopyPermalink = false;
|
||||
canEdit = false;
|
||||
canPin = false;
|
||||
canFlag = false;
|
||||
}
|
||||
if (ownProps.hasBeenDeleted) {
|
||||
canDelete = false;
|
||||
}
|
||||
if (ownProps.location === THREAD) {
|
||||
canReply = false;
|
||||
}
|
||||
|
||||
if (!ownProps.showAddReaction) {
|
||||
canAddReaction = false;
|
||||
}
|
||||
if (channelIsArchived || ownProps.channelIsReadOnly) {
|
||||
canAddReaction = false;
|
||||
canReply = false;
|
||||
canDelete = false;
|
||||
canPin = false;
|
||||
} else {
|
||||
canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post);
|
||||
if (canEdit && license.IsLicensed === 'true' &&
|
||||
(config.AllowEditPost === General.ALLOW_EDIT_POST_TIME_LIMIT || (config.PostEditTimeLimit !== -1 && config.PostEditTimeLimit !== '-1'))
|
||||
) {
|
||||
canEditUntil = post.create_at + (config.PostEditTimeLimit * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ownProps.isSystemMessage && ownProps.managedConfig?.copyAndPasteProtection !== 'true' && post.message) {
|
||||
canCopyText = true;
|
||||
}
|
||||
if (ownProps.isSystemMessage) {
|
||||
canAddReaction = false;
|
||||
canReply = false;
|
||||
canCopyPermalink = false;
|
||||
canEdit = false;
|
||||
canPin = false;
|
||||
canFlag = false;
|
||||
}
|
||||
if (ownProps.hasBeenDeleted) {
|
||||
canDelete = false;
|
||||
}
|
||||
|
||||
return {
|
||||
...getDimensions(state),
|
||||
canAddReaction,
|
||||
canReply,
|
||||
canCopyPermalink,
|
||||
canCopyText,
|
||||
canEdit,
|
||||
canEditUntil,
|
||||
canDelete,
|
||||
canFlag,
|
||||
canPin,
|
||||
currentTeamUrl: getCurrentTeamUrl(state),
|
||||
isMyPost: currentUserId === post.user_id,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
if (!ownProps.showAddReaction) {
|
||||
canAddReaction = false;
|
||||
}
|
||||
|
||||
if (!ownProps.isSystemMessage && ownProps.managedConfig?.copyAndPasteProtection !== 'true' && post.message) {
|
||||
canCopyText = true;
|
||||
}
|
||||
|
||||
if (reactions && Object.values(reactions).length >= MAX_ALLOWED_REACTIONS) {
|
||||
canAddReaction = false;
|
||||
}
|
||||
|
||||
return {
|
||||
...getDimensions(state),
|
||||
canAddReaction,
|
||||
canReply,
|
||||
canCopyPermalink,
|
||||
canCopyText,
|
||||
canEdit,
|
||||
canEditUntil,
|
||||
canDelete,
|
||||
canFlag,
|
||||
canPin,
|
||||
currentTeamUrl: getCurrentTeamUrl(state),
|
||||
isMyPost: currentUserId === post.user_id,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -126,4 +136,4 @@ function mapDispatchToProps(dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostOptions);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(PostOptions);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {mapStateToProps} from './index';
|
||||
import {makeMapStateToProps} from './index';
|
||||
|
||||
import * as channelSelectors from 'mattermost-redux/selectors/entities/channels';
|
||||
import * as generalSelectors from 'mattermost-redux/selectors/entities/general';
|
||||
@@ -24,10 +24,24 @@ deviceSelectors.getDimensions = jest.fn();
|
||||
deviceSelectors.isLandscape = jest.fn();
|
||||
preferencesSelectors.getTheme = jest.fn();
|
||||
|
||||
describe('mapStateToProps', () => {
|
||||
const baseState = {};
|
||||
describe('makeMapStateToProps', () => {
|
||||
const baseState = {
|
||||
entities: {
|
||||
posts: {
|
||||
posts: {
|
||||
post_id: {},
|
||||
},
|
||||
reactions: {
|
||||
post_id: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const baseOwnProps = {
|
||||
post: {},
|
||||
post: {
|
||||
id: 'post_id',
|
||||
},
|
||||
};
|
||||
|
||||
test('canFlag is false for system messages', () => {
|
||||
@@ -36,6 +50,7 @@ describe('mapStateToProps', () => {
|
||||
isSystemMessage: true,
|
||||
};
|
||||
|
||||
const mapStateToProps = makeMapStateToProps();
|
||||
const props = mapStateToProps(baseState, ownProps);
|
||||
expect(props.canFlag).toBe(false);
|
||||
});
|
||||
@@ -46,7 +61,8 @@ describe('mapStateToProps', () => {
|
||||
isSystemMessage: false,
|
||||
};
|
||||
|
||||
const mapStateToProps = makeMapStateToProps();
|
||||
const props = mapStateToProps(baseState, ownProps);
|
||||
expect(props.canFlag).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -4673,7 +4673,7 @@
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
|
||||
"dev": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user