Rewrite ContextMenu and ContextMenuProvider to Use Modern Context

Summary: per title

Reviewed By: mweststrate

Differential Revision: D20197959

fbshipit-source-id: 395eba0f1e2f4606a70067a63aa91a2f2a732afd
This commit is contained in:
Chaiwat Ekkaewnumchai
2020-03-03 12:55:00 -08:00
committed by Facebook Github Bot
parent ec13009673
commit 31bafadaa3
2 changed files with 56 additions and 70 deletions

View File

@@ -7,9 +7,9 @@
* @format * @format
*/ */
import * as React from 'react'; import {createElement, useContext, useCallback} from 'react';
import {ContextMenuContext} from './ContextMenuProvider';
import FlexColumn from './FlexColumn'; import FlexColumn from './FlexColumn';
import PropTypes from 'prop-types';
import {MenuItemConstructorOptions} from 'electron'; import {MenuItemConstructorOptions} from 'electron';
export type MenuTemplate = Array<MenuItemConstructorOptions>; export type MenuTemplate = Array<MenuItemConstructorOptions>;
@@ -22,7 +22,7 @@ type Props<C> = {
/** Nodes that should have a context menu */ /** Nodes that should have a context menu */
children: React.ReactNode; children: React.ReactNode;
/** The component that is used to wrap the children. Defaults to `FlexColumn`. */ /** The component that is used to wrap the children. Defaults to `FlexColumn`. */
component: React.ComponentType<any> | string; component?: React.ComponentType<any> | string;
onMouseDown?: (e: React.MouseEvent) => any; onMouseDown?: (e: React.MouseEvent) => any;
} & C; } & C;
@@ -33,39 +33,27 @@ type Props<C> = {
* *
* Separators can be added by `{type: 'separator'}` * Separators can be added by `{type: 'separator'}`
*/ */
export default class ContextMenu<C = any> extends React.Component<Props<C>> { export default function ContextMenu<C>({
static defaultProps = { items,
component: FlexColumn, buildItems,
};
static contextTypes = {
appendToContextMenu: PropTypes.func,
};
onContextMenu = () => {
if (typeof this.context.appendToContextMenu === 'function') {
if (this.props.items != null) {
this.context.appendToContextMenu(this.props.items);
} else if (this.props.buildItems != null) {
this.context.appendToContextMenu(this.props.buildItems());
}
}
};
render() {
const {
items: _items,
buildItems: _buildItems,
component,
...props
} = this.props;
return React.createElement(
component, component,
children,
...otherProps
}: Props<C>) {
const contextMenuManager = useContext(ContextMenuContext);
const onContextMenu = useCallback(() => {
if (items != null) {
contextMenuManager?.appendToContextMenu(items);
} else if (buildItems != null) {
contextMenuManager?.appendToContextMenu(buildItems());
}
}, []);
return createElement(
component || FlexColumn,
{ {
onContextMenu: this.onContextMenu, onContextMenu,
...props, ...otherProps,
}, },
this.props.children, children,
); );
}
} }

View File

@@ -7,52 +7,50 @@
* @format * @format
*/ */
import {Component} from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import electron, {MenuItemConstructorOptions} from 'electron'; import electron, {MenuItemConstructorOptions} from 'electron';
import React from 'react'; import React, {useRef, memo, createContext, useMemo, useCallback} from 'react';
import PropTypes from 'prop-types';
type MenuTemplate = Array<MenuItemConstructorOptions>; type MenuTemplate = Array<MenuItemConstructorOptions>;
interface ContextMenuManager {
appendToContextMenu(items: MenuTemplate): void;
}
const Container = styled.div({ const Container = styled.div({
display: 'contents', display: 'contents',
}); });
Container.displayName = 'ContextMenuProvider:Container'; Container.displayName = 'ContextMenuProvider:Container';
export const ContextMenuContext = createContext<ContextMenuManager | undefined>(
undefined,
);
/** /**
* Flipper's root is already wrapped with this component, so plugins should not * Flipper's root is already wrapped with this component, so plugins should not
* need to use this. ContextMenu is what you probably want to use. * need to use this. ContextMenu is what you probably want to use.
*/ */
export default class ContextMenuProvider extends Component<{ const ContextMenuProvider: React.FC<{}> = memo(function ContextMenuProvider({
children: React.ReactNode; children,
}> { }) {
static childContextTypes = { const menuTemplate = useRef<MenuTemplate>([]);
appendToContextMenu: PropTypes.func, const contextMenuManager = useMemo(
}; () => ({
appendToContextMenu(items: MenuTemplate) {
getChildContext() { menuTemplate.current = menuTemplate.current.concat(items);
return {appendToContextMenu: this.appendToContextMenu}; },
} }),
[],
_menuTemplate: MenuTemplate = [];
appendToContextMenu = (items: MenuTemplate) => {
this._menuTemplate = this._menuTemplate.concat(items);
};
onContextMenu = () => {
const menu = electron.remote.Menu.buildFromTemplate(this._menuTemplate);
this._menuTemplate = [];
// @ts-ignore: async is private electron API
menu.popup({window: electron.remote.getCurrentWindow(), async: true});
};
render() {
return (
<Container onContextMenu={this.onContextMenu}>
{this.props.children}
</Container>
); );
} const onContextMenu = useCallback(() => {
} const menu = electron.remote.Menu.buildFromTemplate(menuTemplate.current);
menuTemplate.current = [];
menu.popup({window: electron.remote.getCurrentWindow()});
}, []);
return (
<ContextMenuContext.Provider value={contextMenuManager}>
<Container onContextMenu={onContextMenu}>{children}</Container>
</ContextMenuContext.Provider>
);
});
export default ContextMenuProvider;