From 64e791e2539c43b8cea5703cff2f061495eb04bd Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 28 Oct 2021 07:26:45 -0700 Subject: [PATCH] Remove usage of Electron context menus Summary: Removed the usage of electron's native context menus, and replaces it with Antd based context menu's. Reviewed By: passy Differential Revision: D31990756 fbshipit-source-id: 0312cbac5fd20a1a30603ce1058c03f4291b23b1 --- desktop/app/src/NotificationsHub.tsx | 5 +- .../PluginInstaller.node.tsx.snap | 8 +- desktop/app/src/init.tsx | 9 +- desktop/app/src/sandy-chrome/SandyApp.tsx | 90 ++++++++++-------- desktop/app/src/ui/components/ContextMenu.tsx | 95 ++++++++++++++----- .../src/ui/components/ContextMenuProvider.tsx | 58 ----------- .../ui/components/searchable/FilterToken.tsx | 63 +++++------- .../src/ui/components/table/ManagedTable.tsx | 5 +- .../app/src/ui/components/table/TableHead.tsx | 5 +- desktop/app/src/ui/index.tsx | 1 - .../request-mocking/NetworkRouteManager.tsx | 2 - 11 files changed, 156 insertions(+), 185 deletions(-) delete mode 100644 desktop/app/src/ui/components/ContextMenuProvider.tsx diff --git a/desktop/app/src/NotificationsHub.tsx b/desktop/app/src/NotificationsHub.tsx index 1c468bf84..1fd6b8fe1 100644 --- a/desktop/app/src/NotificationsHub.tsx +++ b/desktop/app/src/NotificationsHub.tsx @@ -35,6 +35,7 @@ import {textContent} from 'flipper-plugin'; import createPaste from './fb-stubs/createPaste'; import {getPluginTitle} from './utils/pluginUtils'; import {getFlipperLib} from 'flipper-plugin'; +import {ContextMenuItem} from './ui/components/ContextMenu'; type OwnProps = { onClear: () => void; @@ -421,7 +422,7 @@ class NotificationItem extends Component< > { constructor(props: ItemProps & PluginNotification) { super(props); - const items: Array = []; + const items: Array = []; if (props.onHidePlugin && props.plugin) { items.push({ label: `Hide ${getPluginTitle(props.plugin)} plugin`, @@ -444,7 +445,7 @@ class NotificationItem extends Component< } state = {reportedNotHelpful: false}; - contextMenuItems: Array; + contextMenuItems: Array; deepLinkButton = React.createRef(); createPaste = () => { diff --git a/desktop/app/src/chrome/plugin-manager/__tests__/__snapshots__/PluginInstaller.node.tsx.snap b/desktop/app/src/chrome/plugin-manager/__tests__/__snapshots__/PluginInstaller.node.tsx.snap index 2c2cae04d..bd8f339f8 100644 --- a/desktop/app/src/chrome/plugin-manager/__tests__/__snapshots__/PluginInstaller.node.tsx.snap +++ b/desktop/app/src/chrome/plugin-manager/__tests__/__snapshots__/PluginInstaller.node.tsx.snap @@ -57,7 +57,7 @@ exports[`load PluginInstaller list 1`] = ` tabindex="0" >
- - <_NuxManagerContext.Provider value={_createNuxManager()}> - - - + <_NuxManagerContext.Provider value={_createNuxManager()}> + + diff --git a/desktop/app/src/sandy-chrome/SandyApp.tsx b/desktop/app/src/sandy-chrome/SandyApp.tsx index e7d58d439..27b01511c 100644 --- a/desktop/app/src/sandy-chrome/SandyApp.tsx +++ b/desktop/app/src/sandy-chrome/SandyApp.tsx @@ -148,48 +148,50 @@ export function SandyApp() { ) : null; return ( - - - - - <_Sidebar width={250} minWidth={220} maxWidth={800} gutter> - {leftMenuContent && ( - - {leftMenuContent} - - )} - - - - {staticView ? ( - - {staticView === WelcomeScreenStaticView ? ( - React.createElement(staticView) /* avoid shadow */ - ) : ( - - {React.createElement(staticView, { - logger: logger, - })} - + + + + + + <_Sidebar width={250} minWidth={220} maxWidth={800} gutter> + {leftMenuContent && ( + + {leftMenuContent} + )} - - ) : ( - - )} - {outOfContentsContainer} - - - <_PortalsManager /> - + + + + {staticView ? ( + + {staticView === WelcomeScreenStaticView ? ( + React.createElement(staticView) /* avoid shadow */ + ) : ( + + {React.createElement(staticView, { + logger: logger, + })} + + )} + + ) : ( + + )} + {outOfContentsContainer} + + + <_PortalsManager /> + + ); } @@ -220,6 +222,12 @@ const MainContainer = styled(Layout.Container)({ padding: `${theme.space.large}px ${theme.space.large}px ${theme.space.large}px 0`, }); +const RootElement = styled.div({ + display: 'flex', + height: '100%', +}); +RootElement.displayName = 'SandyAppRootElement'; + function registerStartupTime(logger: Logger) { // track time since launch const [s, ns] = process.hrtime(); diff --git a/desktop/app/src/ui/components/ContextMenu.tsx b/desktop/app/src/ui/components/ContextMenu.tsx index 772fb17e6..dd80152d5 100644 --- a/desktop/app/src/ui/components/ContextMenu.tsx +++ b/desktop/app/src/ui/components/ContextMenu.tsx @@ -7,19 +7,30 @@ * @format */ -import { - createElement, - useContext, - useCallback, - forwardRef, - Ref, - ReactElement, -} from 'react'; -import {ContextMenuContext} from './ContextMenuProvider'; +import * as React from 'react'; +import {Menu, Dropdown} from 'antd'; +import {createElement, useCallback, forwardRef, Ref, ReactElement} from 'react'; import FlexColumn from './FlexColumn'; -import {MenuItemConstructorOptions} from 'electron'; +import {CheckOutlined} from '@ant-design/icons'; -export type MenuTemplate = Array; +export type ContextMenuItem = + | { + readonly label: string; + readonly click?: () => void; + readonly role?: string; + readonly enabled?: boolean; + readonly type?: 'normal' | 'checkbox'; + readonly checked?: boolean; + } + | { + readonly type: 'separator'; + } + | { + readonly label: string; + readonly submenu: MenuTemplate; + }; + +export type MenuTemplate = ReadonlyArray; type Props = { /** List of items in the context menu. Used for static menus. */ @@ -33,6 +44,8 @@ type Props = { onMouseDown?: (e: React.MouseEvent) => any; } & C; +const contextMenuTrigger = ['contextMenu' as const]; + /** * Native context menu that is shown on secondary click. * Uses [Electron's context menu API](https://electronjs.org/docs/api/menu-item) @@ -45,21 +58,53 @@ export default forwardRef(function ContextMenu( {items, buildItems, component, children, ...otherProps}: Props, ref: Ref | null, ) { - const contextMenuManager = useContext(ContextMenuContext); const onContextMenu = useCallback(() => { - if (items != null) { - contextMenuManager?.appendToContextMenu(items); - } else if (buildItems != null) { - contextMenuManager?.appendToContextMenu(buildItems()); - } + return createContextMenu(items ?? buildItems?.() ?? []); }, [items, buildItems]); - return createElement( - component || FlexColumn, - { - ref, - onContextMenu, - ...otherProps, - }, - children, + + return ( + + {createElement( + component || FlexColumn, + { + ref, + ...otherProps, + }, + children, + )} + ); }) as (p: Props & {ref?: Ref}) => ReactElement; + +export function createContextMenu(items: MenuTemplate) { + return {items.map(createMenuItem)}; +} + +function createMenuItem(item: ContextMenuItem, idx: number) { + if ('type' in item && item.type === 'separator') { + return ; + } else if ('submenu' in item) { + return ( + + {item.submenu.map(createMenuItem)} + + ); + } else if ('label' in item) { + return ( + + ) : undefined + }> + {item.label} + + ); + } +} diff --git a/desktop/app/src/ui/components/ContextMenuProvider.tsx b/desktop/app/src/ui/components/ContextMenuProvider.tsx deleted file mode 100644 index 0c4e8bbdc..000000000 --- a/desktop/app/src/ui/components/ContextMenuProvider.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import styled from '@emotion/styled'; -import electron, {MenuItemConstructorOptions} from 'electron'; -import React, {useRef, memo, createContext, useMemo, useCallback} from 'react'; - -type MenuTemplate = Array; -interface ContextMenuManager { - appendToContextMenu(items: MenuTemplate): void; -} - -const Container = styled.div({ - display: 'flex', - height: '100%', -}); -Container.displayName = 'ContextMenuProvider:Container'; - -export const ContextMenuContext = createContext( - 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. - * @deprecated use https://ant.design/components/dropdown/#components-dropdown-demo-context-menu - */ -const ContextMenuProvider: React.FC<{}> = memo(function ContextMenuProvider({ - children, -}) { - const menuTemplate = useRef([]); - 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()}); - }, []); - - return ( - - {children} - - ); -}); - -export default ContextMenuProvider; diff --git a/desktop/app/src/ui/components/searchable/FilterToken.tsx b/desktop/app/src/ui/components/searchable/FilterToken.tsx index 69503e575..2571793da 100644 --- a/desktop/app/src/ui/components/searchable/FilterToken.tsx +++ b/desktop/app/src/ui/components/searchable/FilterToken.tsx @@ -11,24 +11,22 @@ import {Filter} from '../filter/types'; import {PureComponent} from 'react'; import Text from '../Text'; import styled from '@emotion/styled'; -import electron, {MenuItemConstructorOptions} from 'electron'; import React from 'react'; import {Property} from 'csstype'; import {theme} from 'flipper-plugin'; +import {ContextMenuItem, createContextMenu} from '../ContextMenu'; +import {Dropdown} from 'antd'; const Token = styled(Text)<{focused?: boolean; color?: Property.Color}>( (props) => ({ display: 'inline-flex', alignItems: 'center', - backgroundColor: props.focused - ? theme.textColorActive - : props.color || theme.buttonDefaultBackground, + backgroundColor: props.color || theme.buttonDefaultBackground, borderRadius: 4, marginRight: 4, padding: 4, paddingLeft: 6, height: 21, - color: props.focused ? 'white' : 'inherit', '&:active': { backgroundColor: theme.textColorActive, color: theme.textColorPrimary, @@ -103,8 +101,6 @@ type Props = { }; export default class FilterToken extends PureComponent { - _ref?: Element | null; - onMouseDown = () => { if ( this.props.filter.type !== 'enum' || @@ -117,7 +113,7 @@ export default class FilterToken extends PureComponent { }; showDetails = () => { - const menuTemplate: Array = []; + const menuTemplate: Array = []; if (this.props.filter.type === 'enum') { menuTemplate.push( @@ -155,19 +151,7 @@ export default class FilterToken extends PureComponent { }, ); } - const menu = electron.remote.Menu.buildFromTemplate(menuTemplate); - if (this._ref) { - const {bottom, left} = this._ref.getBoundingClientRect(); - - menu.popup({ - window: electron.remote.getCurrentWindow(), - // @ts-ignore: async is private API - async: true, - // Note: Electron requires the x/y parameters to be integer values for marshalling - x: Math.round(left), - y: Math.round(bottom + 8), - }); - } + return createContextMenu(menuTemplate); }; toggleFilter = () => { @@ -202,10 +186,6 @@ export default class FilterToken extends PureComponent { } }; - setRef = (ref: HTMLSpanElement | null) => { - this._ref = ref; - }; - render() { const {filter} = this.props; let color; @@ -231,21 +211,24 @@ export default class FilterToken extends PureComponent { } return ( - - - {filter.key} - - {value} - - ⌄ - - + + + + {filter.key} + + {value} + + ⌄ + + + ); } } + +const dropdownTrigger = ['click' as const]; diff --git a/desktop/app/src/ui/components/table/ManagedTable.tsx b/desktop/app/src/ui/components/table/ManagedTable.tsx index 3c99bb54b..90af61e01 100644 --- a/desktop/app/src/ui/components/table/ManagedTable.tsx +++ b/desktop/app/src/ui/components/table/ManagedTable.tsx @@ -17,12 +17,11 @@ import { TableBodyRow, TableOnAddFilter, } from './types'; -import {MenuTemplate} from '../ContextMenu'; +import {ContextMenuItem, MenuTemplate} from '../ContextMenu'; import React from 'react'; import styled from '@emotion/styled'; import AutoSizer from 'react-virtualized-auto-sizer'; import {VariableSizeList as List} from 'react-window'; -import {MenuItemConstructorOptions} from 'electron'; import TableHead from './TableHead'; import TableRow from './TableRow'; import ContextMenu from '../ContextMenu'; @@ -523,7 +522,7 @@ export class ManagedTable extends React.Component< getFlipperLib().writeTextToClipboard(cellText); }; - buildContextMenuItems: () => Array = () => { + buildContextMenuItems: () => Array = () => { const {highlightedRows} = this.state; if (highlightedRows.size === 0) { return []; diff --git a/desktop/app/src/ui/components/table/TableHead.tsx b/desktop/app/src/ui/components/table/TableHead.tsx index b282f68fc..614fac604 100644 --- a/desktop/app/src/ui/components/table/TableHead.tsx +++ b/desktop/app/src/ui/components/table/TableHead.tsx @@ -17,13 +17,12 @@ import { } from './types'; import {normalizeColumnWidth, isPercentage} from './utils'; import {PureComponent} from 'react'; -import ContextMenu from '../ContextMenu'; +import ContextMenu, {ContextMenuItem} from '../ContextMenu'; import {theme, _Interactive, _InteractiveProps} from 'flipper-plugin'; import styled from '@emotion/styled'; import {colors} from '../colors'; import FlexRow from '../FlexRow'; import invariant from 'invariant'; -import {MenuItemConstructorOptions} from 'electron'; import React from 'react'; const TableHeaderArrow = styled.span({ @@ -208,7 +207,7 @@ export default class TableHead extends PureComponent<{ onColumnResize?: TableOnColumnResize; horizontallyScrollable?: boolean; }> { - buildContextMenu = (): MenuItemConstructorOptions[] => { + buildContextMenu = (): ContextMenuItem[] => { const visibles = this.props.columnOrder .map((c) => (c.visible ? c.key : null)) .filter(Boolean) diff --git a/desktop/app/src/ui/index.tsx b/desktop/app/src/ui/index.tsx index bb0b0e6ab..2c4f04d89 100644 --- a/desktop/app/src/ui/index.tsx +++ b/desktop/app/src/ui/index.tsx @@ -60,7 +60,6 @@ export {default as Orderable} from './components/Orderable'; export {Component, PureComponent} from 'react'; // context menus and dropdowns -export {default as ContextMenuProvider} from './components/ContextMenuProvider'; export {default as ContextMenu} from './components/ContextMenu'; // file diff --git a/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx b/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx index 3ed289303..1b77b1e6d 100644 --- a/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx +++ b/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx @@ -8,8 +8,6 @@ */ import fs from 'fs'; -// eslint-disable-next-line -import electron, {OpenDialogOptions, remote} from 'electron'; import {Atom, DataTableManager, getFlipperLib} from 'flipper-plugin'; import {createContext} from 'react'; import {Header, Request} from '../types';