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,
}; component,
children,
static contextTypes = { ...otherProps
appendToContextMenu: PropTypes.func, }: Props<C>) {
}; const contextMenuManager = useContext(ContextMenuContext);
const onContextMenu = useCallback(() => {
onContextMenu = () => { if (items != null) {
if (typeof this.context.appendToContextMenu === 'function') { contextMenuManager?.appendToContextMenu(items);
if (this.props.items != null) { } else if (buildItems != null) {
this.context.appendToContextMenu(this.props.items); contextMenuManager?.appendToContextMenu(buildItems());
} else if (this.props.buildItems != null) {
this.context.appendToContextMenu(this.props.buildItems());
}
} }
}; }, []);
return createElement(
render() { component || FlexColumn,
const { {
items: _items, onContextMenu,
buildItems: _buildItems, ...otherProps,
component, },
...props children,
} = this.props; );
return React.createElement(
component,
{
onContextMenu: this.onContextMenu,
...props,
},
this.props.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) {
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 (
return {appendToContextMenu: this.appendToContextMenu}; <ContextMenuContext.Provider value={contextMenuManager}>
} <Container onContextMenu={onContextMenu}>{children}</Container>
</ContextMenuContext.Provider>
);
});
_menuTemplate: MenuTemplate = []; export default ContextMenuProvider;
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>
);
}
}