Skip to content

Commit 0b6263b

Browse files
authored
Merge pull request #4194 from parasharrajat/context-menu
Context-menu repositioned
2 parents cad6872 + bec3d56 commit 0b6263b

11 files changed

+720
-460
lines changed

src/components/PopoverWithMeasuredContent.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class PopoverWithMeasuredContent extends Component {
5555

5656
this.state = {
5757
isContentMeasured: false,
58+
isVisible: false,
5859
};
5960

6061
this.popoverWidth = 0;
@@ -63,6 +64,26 @@ class PopoverWithMeasuredContent extends Component {
6364
this.measurePopover = this.measurePopover.bind(this);
6465
}
6566

67+
/**
68+
* When Popover becomes visible, we need to recalculate the Dimensions.
69+
* Skip render on Popover until recalculations have done by setting isContentMeasured false as early as possible.
70+
*
71+
* @static
72+
* @param {Object} props
73+
* @param {Object} state
74+
* @return {Object|null}
75+
*/
76+
static getDerivedStateFromProps(props, state) {
77+
// When Popover is shown recalculate
78+
if (!state.isVisible && props.isVisible) {
79+
return {isContentMeasured: false, isVisible: true};
80+
}
81+
if (!props.isVisible) {
82+
return {isVisible: false};
83+
}
84+
return null;
85+
}
86+
6687
shouldComponentUpdate(nextProps, nextState) {
6788
if (this.props.isVisible
6889
&& (nextProps.windowWidth !== this.props.windowWidth
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import _ from 'underscore';
4+
import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles';
5+
import ContextMenuItem from '../../../../components/ContextMenuItem';
6+
import {
7+
propTypes as GenericReportActionContextMenuPropTypes,
8+
defaultProps,
9+
} from './GenericReportActionContextMenuPropTypes';
10+
import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
11+
import ContextMenuActions from './ContextMenuActions';
12+
13+
const propTypes = {
14+
...GenericReportActionContextMenuPropTypes,
15+
...withLocalizePropTypes,
16+
};
17+
18+
class BaseReportActionContextMenu extends React.Component {
19+
constructor(props) {
20+
super(props);
21+
22+
this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini);
23+
}
24+
25+
render() {
26+
return this.props.isVisible && (
27+
<View style={this.wrapperStyle}>
28+
{_.map(ContextMenuActions, contextAction => contextAction.shouldShow(this.props.reportAction) && (
29+
<ContextMenuItem
30+
icon={contextAction.icon}
31+
text={this.props.translate(contextAction.textTranslateKey)}
32+
successIcon={contextAction.successIcon}
33+
successText={contextAction.successTextTranslateKey
34+
? this.props.translate(contextAction.successTextTranslateKey)
35+
: undefined}
36+
isMini={this.props.isMini}
37+
key={contextAction.textTranslateKey}
38+
onPress={() => contextAction.onPress(!this.props.isMini, {
39+
reportAction: this.props.reportAction,
40+
reportID: this.props.reportID,
41+
draftMessage: this.props.draftMessage,
42+
selection: this.props.selection,
43+
})}
44+
/>
45+
))}
46+
</View>
47+
);
48+
}
49+
}
50+
51+
BaseReportActionContextMenu.propTypes = propTypes;
52+
BaseReportActionContextMenu.defaultProps = defaultProps;
53+
54+
export default withLocalize(BaseReportActionContextMenu);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import _ from 'underscore';
2+
import lodashGet from 'lodash/get';
3+
import Str from 'expensify-common/lib/str';
4+
import {
5+
Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark,
6+
} from '../../../../components/Icon/Expensicons';
7+
import {
8+
setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft,
9+
} from '../../../../libs/actions/Report';
10+
import Clipboard from '../../../../libs/Clipboard';
11+
import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils';
12+
import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager';
13+
import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu';
14+
15+
/**
16+
* Gets the HTML version of the message in an action.
17+
* @param {Object} reportAction
18+
* @return {String}
19+
*/
20+
function getActionText(reportAction) {
21+
const message = _.last(lodashGet(reportAction, 'message', null));
22+
return lodashGet(message, 'html', '');
23+
}
24+
25+
// A list of all the context actions in this menu.
26+
export default [
27+
// Copy to clipboard
28+
{
29+
textTranslateKey: 'contextMenuItem.copyToClipboard',
30+
icon: ClipboardIcon,
31+
successTextTranslateKey: 'contextMenuItem.copied',
32+
successIcon: Checkmark,
33+
shouldShow: () => true,
34+
35+
// If return value is true, we switch the `text` and `icon` on
36+
// `ContextMenuItem` with `successText` and `successIcon` which will fallback to
37+
// the `text` and `icon`
38+
onPress: (closePopover, {reportAction, selection}) => {
39+
const message = _.last(lodashGet(reportAction, 'message', null));
40+
const html = lodashGet(message, 'html', '');
41+
const text = Str.htmlDecode(selection || lodashGet(message, 'text', ''));
42+
const isAttachment = _.has(reportAction, 'isAttachment')
43+
? reportAction.isAttachment
44+
: isReportMessageAttachment(text);
45+
if (!isAttachment) {
46+
Clipboard.setString(text);
47+
} else {
48+
Clipboard.setString(html);
49+
}
50+
if (closePopover) {
51+
hideContextMenu(true, ReportActionComposeFocusManager.focus);
52+
}
53+
},
54+
},
55+
56+
{
57+
textTranslateKey: 'reportActionContextMenu.copyLink',
58+
icon: LinkCopy,
59+
shouldShow: () => false,
60+
onPress: () => {},
61+
},
62+
63+
{
64+
textTranslateKey: 'reportActionContextMenu.markAsUnread',
65+
icon: Mail,
66+
successIcon: Checkmark,
67+
shouldShow: () => true,
68+
onPress: (closePopover, {reportAction, reportID}) => {
69+
updateLastReadActionID(reportID, reportAction.sequenceNumber);
70+
setNewMarkerPosition(reportID, reportAction.sequenceNumber);
71+
if (closePopover) {
72+
hideContextMenu(true, ReportActionComposeFocusManager.focus);
73+
}
74+
},
75+
},
76+
77+
{
78+
textTranslateKey: 'reportActionContextMenu.editComment',
79+
icon: Pencil,
80+
shouldShow: reportAction => canEditReportAction(reportAction),
81+
onPress: (closePopover, {reportID, reportAction, draftMessage}) => {
82+
const editAction = () => saveReportActionDraft(
83+
reportID,
84+
reportAction.reportActionID,
85+
_.isEmpty(draftMessage) ? getActionText(reportAction) : '',
86+
);
87+
88+
if (closePopover) {
89+
// Hide popover, then call editAction
90+
hideContextMenu(false, editAction);
91+
return;
92+
}
93+
94+
// No popover to hide, call editAction immediately
95+
editAction();
96+
},
97+
},
98+
{
99+
textTranslateKey: 'reportActionContextMenu.deleteComment',
100+
icon: Trashcan,
101+
shouldShow: reportAction => canDeleteReportAction(reportAction),
102+
onPress: (closePopover, {reportID, reportAction}) => {
103+
if (closePopover) {
104+
// Hide popover, then call showDeleteConfirmModal
105+
hideContextMenu(
106+
false,
107+
() => showDeleteModal(reportID, reportAction),
108+
);
109+
return;
110+
}
111+
112+
// No popover to hide, call showDeleteConfirmModal immediately
113+
showDeleteModal(reportID, reportAction);
114+
},
115+
},
116+
];
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import PropTypes from 'prop-types';
2+
import ReportActionPropTypes from '../ReportActionPropTypes';
3+
4+
const propTypes = {
5+
/** The ID of the report this report action is attached to. */
6+
reportID: PropTypes.number.isRequired,
7+
8+
/** The report action this context menu is attached to. */
9+
reportAction: PropTypes.shape(ReportActionPropTypes).isRequired,
10+
11+
/** If true, this component will be a small, row-oriented menu that displays icons but not text.
12+
If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */
13+
isMini: PropTypes.bool,
14+
15+
/** Controls the visibility of this component. */
16+
isVisible: PropTypes.bool,
17+
18+
/** The copy selection of text. */
19+
selection: PropTypes.string,
20+
21+
/** Draft message - if this is set the comment is in 'edit' mode */
22+
draftMessage: PropTypes.string,
23+
};
24+
25+
const defaultProps = {
26+
isMini: false,
27+
isVisible: false,
28+
selection: '',
29+
draftMessage: '',
30+
};
31+
32+
export {propTypes, defaultProps};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import _ from 'underscore';
2+
import React from 'react';
3+
import {View} from 'react-native';
4+
import PropTypes from 'prop-types';
5+
import {
6+
propTypes as GenericReportActionContextMenuPropTypes,
7+
defaultProps as GenericReportActionContextMenuDefaultProps,
8+
} from '../GenericReportActionContextMenuPropTypes';
9+
import {getMiniReportActionContextMenuWrapperStyle} from '../../../../../styles/getReportActionItemStyles';
10+
import BaseReportActionContextMenu from '../BaseReportActionContextMenu';
11+
12+
const propTypes = {
13+
..._.omit(GenericReportActionContextMenuPropTypes, ['isMini']),
14+
15+
/** Should the reportAction this menu is attached to have the appearance of being
16+
* grouped with the previous reportAction? */
17+
displayAsGroup: PropTypes.bool,
18+
};
19+
20+
const defaultProps = {
21+
..._.omit(GenericReportActionContextMenuDefaultProps, ['isMini']),
22+
displayAsGroup: false,
23+
};
24+
25+
const MiniReportActionContextMenu = props => (
26+
<View style={getMiniReportActionContextMenuWrapperStyle(props.displayAsGroup)}>
27+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
28+
<BaseReportActionContextMenu isMini {...props} />
29+
</View>
30+
);
31+
32+
MiniReportActionContextMenu.propTypes = propTypes;
33+
MiniReportActionContextMenu.defaultProps = defaultProps;
34+
MiniReportActionContextMenu.displayName = 'MiniReportActionContextMenu';
35+
36+
export default MiniReportActionContextMenu;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => null;

0 commit comments

Comments
 (0)