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
*/
import * as React from 'react';
import {createElement, useContext, useCallback} from 'react';
import {ContextMenuContext} from './ContextMenuProvider';
import FlexColumn from './FlexColumn';
import PropTypes from 'prop-types';
import {MenuItemConstructorOptions} from 'electron';
export type MenuTemplate = Array<MenuItemConstructorOptions>;
@@ -22,7 +22,7 @@ type Props<C> = {
/** Nodes that should have a context menu */
children: React.ReactNode;
/** 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;
} & C;
@@ -33,39 +33,27 @@ type Props<C> = {
*
* Separators can be added by `{type: 'separator'}`
*/
export default class ContextMenu<C = any> extends React.Component<Props<C>> {
static defaultProps = {
component: FlexColumn,
};
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());
}
export default function ContextMenu<C>({
items,
buildItems,
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());
}
};
render() {
const {
items: _items,
buildItems: _buildItems,
component,
...props
} = this.props;
return React.createElement(
component,
{
onContextMenu: this.onContextMenu,
...props,
},
this.props.children,
);
}
}, []);
return createElement(
component || FlexColumn,
{
onContextMenu,
...otherProps,
},
children,
);
}

View File

@@ -7,52 +7,50 @@
* @format
*/
import {Component} from 'react';
import styled from '@emotion/styled';
import electron, {MenuItemConstructorOptions} from 'electron';
import React from 'react';
import PropTypes from 'prop-types';
import React, {useRef, memo, createContext, useMemo, useCallback} from 'react';
type MenuTemplate = Array<MenuItemConstructorOptions>;
interface ContextMenuManager {
appendToContextMenu(items: MenuTemplate): void;
}
const Container = styled.div({
display: 'contents',
});
Container.displayName = 'ContextMenuProvider:Container';
export const ContextMenuContext = createContext<ContextMenuManager | undefined>(
undefined,
);
/**
* 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.
*/
export default class ContextMenuProvider extends Component<{
children: React.ReactNode;
}> {
static childContextTypes = {
appendToContextMenu: PropTypes.func,
};
const ContextMenuProvider: React.FC<{}> = memo(function ContextMenuProvider({
children,
}) {
const menuTemplate = useRef<MenuTemplate>([]);
const contextMenuManager = useMemo(
() => ({
appendToContextMenu(items: MenuTemplate) {
menuTemplate.current = menuTemplate.current.concat(items);
},
}),
[],
);
const onContextMenu = useCallback(() => {
const menu = electron.remote.Menu.buildFromTemplate(menuTemplate.current);
menuTemplate.current = [];
menu.popup({window: electron.remote.getCurrentWindow()});
}, []);
getChildContext() {
return {appendToContextMenu: this.appendToContextMenu};
}
return (
<ContextMenuContext.Provider value={contextMenuManager}>
<Container onContextMenu={onContextMenu}>{children}</Container>
</ContextMenuContext.Provider>
);
});
_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>
);
}
}
export default ContextMenuProvider;