Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import _ from 'underscore';
import React from 'react';
import {View} from 'react-native';
import {
View, Pressable,
} from 'react-native';
import Text from './Text';
import styles from '../styles/styles';
import * as StyleUtils from '../styles/StyleUtils';
Expand All @@ -16,14 +18,9 @@ import colors from '../styles/colors';
import variables from '../styles/variables';
import MultipleAvatars from './MultipleAvatars';
import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars';
import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction';
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
import * as DeviceCapabilities from '../libs/DeviceCapabilities';
import ControlSelection from '../libs/ControlSelection';

const propTypes = {
...menuItemPropTypes,
...windowDimensionsPropTypes,
};

const defaultProps = {
Expand All @@ -49,13 +46,11 @@ const defaultProps = {
subtitle: undefined,
iconType: CONST.ICON_TYPE_ICON,
onPress: () => {},
onSecondaryInteraction: undefined,
interactive: true,
fallbackIcon: Expensicons.FallbackAvatar,
brickRoadIndicator: '',
floatRightAvatars: [],
shouldStackHorizontally: false,
shouldBlockSelection: false,
};

const MenuItem = (props) => {
Expand All @@ -76,7 +71,7 @@ const MenuItem = (props) => {
]);

return (
<PressableWithSecondaryInteraction
<Pressable
onPress={(e) => {
if (props.disabled) {
return;
Expand All @@ -88,17 +83,13 @@ const MenuItem = (props) => {

props.onPress(e);
}}
onPressIn={() => props.shouldBlockSelection && props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={ControlSelection.unblock}
onSecondaryInteraction={props.onSecondaryInteraction}
style={({hovered, pressed}) => ([
props.style,
StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive), true),
..._.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle],
styles.popoverMaxWidth,
])}
disabled={props.disabled}
ref={props.forwardedRef}
>
{({hovered, pressed}) => (
<>
Expand Down Expand Up @@ -221,14 +212,12 @@ const MenuItem = (props) => {
</View>
</>
)}
</PressableWithSecondaryInteraction>
</Pressable>
);
};

MenuItem.propTypes = propTypes;
MenuItem.defaultProps = defaultProps;
MenuItem.displayName = 'MenuItem';
export default withWindowDimensions(React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<MenuItem {...props} forwardedRef={ref} />
)));

export default MenuItem;
45 changes: 11 additions & 34 deletions src/components/MenuItemList.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import _ from 'underscore';
import PropTypes from 'prop-types';
import MenuItem from './MenuItem';
import menuItemPropTypes from './menuItemPropTypes';
import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu';
import {CONTEXT_MENU_TYPES} from '../pages/home/report/ContextMenu/ContextMenuActions';

const propTypes = {
/** An array of props that are pass to individual MenuItem components */
Expand All @@ -14,38 +12,17 @@ const defaultProps = {
menuItems: [],
};

const MenuItemList = (props) => {
let popoverAnchor;

/**
* Handle the secondary interaction for a menu item.
*
* @param {*} link the menu item link or function to get the link
* @param {Event} e the interaction event
*/
const secondaryInteraction = (link, e) => {
if (typeof link === 'function') {
link().then(url => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor));
} else if (!_.isEmpty(link)) {
ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor);
}
};

return (
<>
{_.map(props.menuItems, menuItemProps => (
<MenuItem
key={menuItemProps.title}
onSecondaryInteraction={!_.isUndefined(menuItemProps.link) ? e => secondaryInteraction(menuItemProps.link, e) : undefined}
ref={el => popoverAnchor = el}
shouldBlockSelection={Boolean(menuItemProps.link)}
// eslint-disable-next-line react/jsx-props-no-spreading
{...menuItemProps}
/>
))}
</>
);
};
const MenuItemList = props => (
<>
{_.map(props.menuItems, menuItemProps => (
<MenuItem
key={menuItemProps.title}
// eslint-disable-next-line react/jsx-props-no-spreading
{...menuItemProps}
/>
))}
</>
);

MenuItemList.displayName = 'MenuItemList';
MenuItemList.propTypes = propTypes;
Expand Down
31 changes: 10 additions & 21 deletions src/components/PressableWithSecondaryInteraction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import {Pressable} from 'react-native';
import * as pressableWithSecondaryInteractionPropTypes from './pressableWithSecondaryInteractionPropTypes';
import styles from '../../styles/styles';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import * as StyleUtils from '../../styles/StyleUtils';

/**
* This is a special Pressable that calls onSecondaryInteraction when LongPressed, or right-clicked.
*/
class PressableWithSecondaryInteraction extends Component {
constructor(props) {
super(props);
this.executeSecondaryInteraction = this.executeSecondaryInteraction.bind(this);
this.executeSecondaryInteractionOnContextMenu = this.executeSecondaryInteractionOnContextMenu.bind(this);
}

Expand All @@ -27,28 +25,11 @@ class PressableWithSecondaryInteraction extends Component {
this.pressableRef.removeEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu);
}

/**
* @param {Event} e - the secondary interaction event
*/
executeSecondaryInteraction(e) {
if (DeviceCapabilities.hasHoverSupport()) {
return;
}
if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) {
this.pressableRef.blur();
}
this.props.onSecondaryInteraction(e);
}

/**
* @param {contextmenu} e - A right-click MouseEvent.
* https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
*/
executeSecondaryInteractionOnContextMenu(e) {
if (!this.props.onSecondaryInteraction) {
return;
}

e.stopPropagation();
if (this.props.preventDefaultContentMenu) {
e.preventDefault();
Expand All @@ -73,9 +54,17 @@ class PressableWithSecondaryInteraction extends Component {
// On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text.
return (
<Pressable
style={StyleUtils.combineStyles(this.props.inline ? styles.dInline : this.props.style)}
style={this.props.inline && styles.dInline}
onPressIn={this.props.onPressIn}
onLongPress={this.props.onSecondaryInteraction ? this.executeSecondaryInteraction : undefined}
onLongPress={(e) => {
if (DeviceCapabilities.hasHoverSupport()) {
return;
}
if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) {
this.pressableRef.blur();
}
this.props.onSecondaryInteraction(e);
}}
onPressOut={this.props.onPressOut}
onPress={this.props.onPress}
ref={el => this.pressableRef = el}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ const PressableWithSecondaryInteraction = (props) => {
ref={props.forwardedRef}
onPress={props.onPress}
onLongPress={(e) => {
if (!props.onSecondaryInteraction) {
return;
}
e.preventDefault();
HapticFeedback.longPress();
props.onSecondaryInteraction(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import PropTypes from 'prop-types';
import stylePropTypes from '../../styles/stylePropTypes';

const propTypes = {
/** The function that should be called when this pressable is pressed */
Expand All @@ -12,19 +11,13 @@ const propTypes = {
onPressOut: PropTypes.func,

/** The function that should be called when this pressable is LongPressed or right-clicked. */
onSecondaryInteraction: PropTypes.func,
onSecondaryInteraction: PropTypes.func.isRequired,

/** The children which should be contained in this wrapper component. */
children: PropTypes.oneOfType([
PropTypes.func,
PropTypes.node,
]).isRequired,
children: PropTypes.node.isRequired,

/** The ref to the search input (may be null on small screen widths) */
forwardedRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
]),
forwardedRef: PropTypes.func,

/** Prevent the default ContextMenu on web/Desktop */
preventDefaultContentMenu: PropTypes.bool,
Expand All @@ -41,9 +34,6 @@ const propTypes = {

/** Disable focus trap for the element on secondary interaction */
withoutFocusOnSecondaryInteraction: PropTypes.bool,

/** Used to apply styles to the Pressable */
style: stylePropTypes,
};

const defaultProps = {
Expand Down
12 changes: 0 additions & 12 deletions src/components/menuItemPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,6 @@ const propTypes = {

/** Prop to identify if we should load avatars vertically instead of diagonally */
shouldStackHorizontally: PropTypes.bool,

/** The function that should be called when this component is LongPressed or right-clicked. */
onSecondaryInteraction: PropTypes.func,

/** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */
shouldBlockSelection: PropTypes.bool,

/** The ref to the menu item */
forwardedRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
]),
};

export default propTypes;
47 changes: 22 additions & 25 deletions src/libs/actions/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,33 @@ function showGrowlIfOffline() {
}

/**
* @param {String} [url] the url path
* @param {String} [shortLivedAuthToken]
*
* @returns {Promise<string>}
* @param {String} url
*/
function buildOldDotURL(url, shortLivedAuthToken) {
const hasHashParams = url.indexOf('#') !== -1;
const hasURLParams = url.indexOf('?') !== -1;
function openOldDotLink(url) {
/**
* @param {String} [shortLivedAuthToken]
* @returns {Promise<string>}
*/
function buildOldDotURL(shortLivedAuthToken) {
const hasHashParams = url.indexOf('#') !== -1;
const hasURLParams = url.indexOf('?') !== -1;

const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : '';
const emailParam = `email=${encodeURIComponent(currentUserEmail)}`;
const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : '';
const emailParam = `email=${encodeURIComponent(currentUserEmail)}`;

const params = _.compact([authTokenParam, emailParam]).join('&');
const params = _.compact([authTokenParam, emailParam]).join('&');

return Environment.getOldDotEnvironmentURL()
.then((environmentURL) => {
const oldDotDomain = Url.addTrailingForwardSlash(environmentURL);
return Environment.getOldDotEnvironmentURL()
.then((environmentURL) => {
const oldDotDomain = Url.addTrailingForwardSlash(environmentURL);

// If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters.
return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`;
});
}
// If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters.
return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`;
});
}

/**
* @param {String} url the url path
*/
function openOldDotLink(url) {
if (isNetworkOffline) {
buildOldDotURL(url).then(oldDotURL => Linking.openURL(oldDotURL));
buildOldDotURL().then(oldDotURL => Linking.openURL(oldDotURL));
return;
}

Expand All @@ -71,11 +69,11 @@ function openOldDotLink(url) {
API.makeRequestWithSideEffects(
'OpenOldDotLink', {}, {},
).then((response) => {
buildOldDotURL(url, response.shortLivedAuthToken).then((oldDotUrl) => {
buildOldDotURL(response.shortLivedAuthToken).then((oldDotUrl) => {
Linking.openURL(oldDotUrl);
});
}).catch(() => {
buildOldDotURL(url).then((oldDotUrl) => {
buildOldDotURL().then((oldDotUrl) => {
Linking.openURL(oldDotUrl);
});
});
Expand All @@ -94,7 +92,6 @@ function openExternalLink(url, shouldSkipCustomSafariLogic = false) {
}

export {
buildOldDotURL,
openOldDotLink,
openExternalLink,
};
1 change: 0 additions & 1 deletion src/pages/GetAssistancePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const GetAssistancePage = (props) => {
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
wrapperStyle: [styles.cardMenuItem],
link: CONST.NEWHELP_URL,
}];

// If the user is eligible for calls with their Guide, add the 'Schedule a setup call' item at the second position in the list
Expand Down
Loading