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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
25590e14b9
commit
64e791e253
@@ -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<Electron.MenuItemConstructorOptions> = [];
|
||||
const items: Array<ContextMenuItem> = [];
|
||||
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<Electron.MenuItemConstructorOptions>;
|
||||
contextMenuItems: Array<ContextMenuItem>;
|
||||
deepLinkButton = React.createRef();
|
||||
|
||||
createPaste = () => {
|
||||
|
||||
@@ -57,7 +57,7 @@ exports[`load PluginInstaller list 1`] = `
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
class="ant-dropdown-trigger css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
>
|
||||
<div
|
||||
class="css-1otvu18-View-FlexBox-FlexRow-TableHeadContainer ejga3101"
|
||||
@@ -132,7 +132,7 @@ exports[`load PluginInstaller list 1`] = `
|
||||
class="css-p5h61d-View-FlexBox-FlexColumn-Container esta8x30"
|
||||
>
|
||||
<div
|
||||
class="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
class="ant-dropdown-trigger css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
>
|
||||
<div
|
||||
class="css-hg3ptm-View-FlexBox-FlexRow-TableBodyRowContainer ehuguum1"
|
||||
@@ -391,7 +391,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
class="ant-dropdown-trigger css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
>
|
||||
<div
|
||||
class="css-1otvu18-View-FlexBox-FlexRow-TableHeadContainer ejga3101"
|
||||
@@ -466,7 +466,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
class="css-p5h61d-View-FlexBox-FlexColumn-Container esta8x30"
|
||||
>
|
||||
<div
|
||||
class="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
class="ant-dropdown-trigger css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
||||
>
|
||||
<div
|
||||
class="css-hg3ptm-View-FlexBox-FlexRow-TableBodyRowContainer ehuguum1"
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import {Provider} from 'react-redux';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import ContextMenuProvider from './ui/components/ContextMenuProvider';
|
||||
import GK from './fb-stubs/GK';
|
||||
import {init as initLogger} from './fb-stubs/Logger';
|
||||
import {SandyApp} from './sandy-chrome/SandyApp';
|
||||
@@ -137,11 +136,9 @@ class AppFrame extends React.Component<
|
||||
<PersistGate persistor={persistor}>
|
||||
<CacheProvider value={cache}>
|
||||
<TooltipProvider>
|
||||
<ContextMenuProvider>
|
||||
<_NuxManagerContext.Provider value={_createNuxManager()}>
|
||||
<SandyApp />
|
||||
</_NuxManagerContext.Provider>
|
||||
</ContextMenuProvider>
|
||||
<_NuxManagerContext.Provider value={_createNuxManager()}>
|
||||
<SandyApp />
|
||||
</_NuxManagerContext.Provider>
|
||||
</TooltipProvider>
|
||||
</CacheProvider>
|
||||
</PersistGate>
|
||||
|
||||
@@ -148,48 +148,50 @@ export function SandyApp() {
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Layout.Bottom>
|
||||
<Layout.Left>
|
||||
<Layout.Horizontal>
|
||||
<LeftRail
|
||||
toplevelSelection={toplevelSelection}
|
||||
setToplevelSelection={setToplevelSelection}
|
||||
/>
|
||||
<_Sidebar width={250} minWidth={220} maxWidth={800} gutter>
|
||||
{leftMenuContent && (
|
||||
<TrackingScope scope={toplevelSelection!}>
|
||||
{leftMenuContent}
|
||||
</TrackingScope>
|
||||
)}
|
||||
</_Sidebar>
|
||||
</Layout.Horizontal>
|
||||
<MainContainer>
|
||||
{staticView ? (
|
||||
<TrackingScope
|
||||
scope={
|
||||
(staticView as any).displayName ??
|
||||
staticView.name ??
|
||||
staticView.constructor?.name ??
|
||||
'unknown static view'
|
||||
}>
|
||||
{staticView === WelcomeScreenStaticView ? (
|
||||
React.createElement(staticView) /* avoid shadow */
|
||||
) : (
|
||||
<ContentContainer>
|
||||
{React.createElement(staticView, {
|
||||
logger: logger,
|
||||
})}
|
||||
</ContentContainer>
|
||||
<RootElement>
|
||||
<Layout.Bottom>
|
||||
<Layout.Left>
|
||||
<Layout.Horizontal>
|
||||
<LeftRail
|
||||
toplevelSelection={toplevelSelection}
|
||||
setToplevelSelection={setToplevelSelection}
|
||||
/>
|
||||
<_Sidebar width={250} minWidth={220} maxWidth={800} gutter>
|
||||
{leftMenuContent && (
|
||||
<TrackingScope scope={toplevelSelection!}>
|
||||
{leftMenuContent}
|
||||
</TrackingScope>
|
||||
)}
|
||||
</TrackingScope>
|
||||
) : (
|
||||
<PluginContainer logger={logger} />
|
||||
)}
|
||||
{outOfContentsContainer}
|
||||
</MainContainer>
|
||||
</Layout.Left>
|
||||
<_PortalsManager />
|
||||
</Layout.Bottom>
|
||||
</_Sidebar>
|
||||
</Layout.Horizontal>
|
||||
<MainContainer>
|
||||
{staticView ? (
|
||||
<TrackingScope
|
||||
scope={
|
||||
(staticView as any).displayName ??
|
||||
staticView.name ??
|
||||
staticView.constructor?.name ??
|
||||
'unknown static view'
|
||||
}>
|
||||
{staticView === WelcomeScreenStaticView ? (
|
||||
React.createElement(staticView) /* avoid shadow */
|
||||
) : (
|
||||
<ContentContainer>
|
||||
{React.createElement(staticView, {
|
||||
logger: logger,
|
||||
})}
|
||||
</ContentContainer>
|
||||
)}
|
||||
</TrackingScope>
|
||||
) : (
|
||||
<PluginContainer logger={logger} />
|
||||
)}
|
||||
{outOfContentsContainer}
|
||||
</MainContainer>
|
||||
</Layout.Left>
|
||||
<_PortalsManager />
|
||||
</Layout.Bottom>
|
||||
</RootElement>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<MenuItemConstructorOptions>;
|
||||
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<ContextMenuItem>;
|
||||
|
||||
type Props<C> = {
|
||||
/** List of items in the context menu. Used for static menus. */
|
||||
@@ -33,6 +44,8 @@ type Props<C> = {
|
||||
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<C>(
|
||||
{items, buildItems, component, children, ...otherProps}: Props<C>,
|
||||
ref: Ref<any> | 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 (
|
||||
<Dropdown overlay={onContextMenu} trigger={contextMenuTrigger}>
|
||||
{createElement(
|
||||
component || FlexColumn,
|
||||
{
|
||||
ref,
|
||||
...otherProps,
|
||||
},
|
||||
children,
|
||||
)}
|
||||
</Dropdown>
|
||||
);
|
||||
}) as <T>(p: Props<T> & {ref?: Ref<any>}) => ReactElement;
|
||||
|
||||
export function createContextMenu(items: MenuTemplate) {
|
||||
return <Menu>{items.map(createMenuItem)}</Menu>;
|
||||
}
|
||||
|
||||
function createMenuItem(item: ContextMenuItem, idx: number) {
|
||||
if ('type' in item && item.type === 'separator') {
|
||||
return <Menu.Divider key={idx} />;
|
||||
} else if ('submenu' in item) {
|
||||
return (
|
||||
<Menu.SubMenu key={idx} title={item.label}>
|
||||
{item.submenu.map(createMenuItem)}
|
||||
</Menu.SubMenu>
|
||||
);
|
||||
} else if ('label' in item) {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={idx}
|
||||
onClick={item.click}
|
||||
disabled={item.enabled === false}
|
||||
role={item.role}
|
||||
icon={
|
||||
item.type === 'checkbox' ? (
|
||||
<CheckOutlined
|
||||
style={{visibility: item.checked ? 'visible' : 'hidden'}}
|
||||
/>
|
||||
) : undefined
|
||||
}>
|
||||
{item.label}
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MenuItemConstructorOptions>;
|
||||
interface ContextMenuManager {
|
||||
appendToContextMenu(items: MenuTemplate): void;
|
||||
}
|
||||
|
||||
const Container = styled.div({
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
});
|
||||
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.
|
||||
* @deprecated use https://ant.design/components/dropdown/#components-dropdown-demo-context-menu
|
||||
*/
|
||||
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()});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ContextMenuContext.Provider value={contextMenuManager}>
|
||||
<Container onContextMenu={onContextMenu}>{children}</Container>
|
||||
</ContextMenuContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export default ContextMenuProvider;
|
||||
@@ -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<Props> {
|
||||
_ref?: Element | null;
|
||||
|
||||
onMouseDown = () => {
|
||||
if (
|
||||
this.props.filter.type !== 'enum' ||
|
||||
@@ -117,7 +113,7 @@ export default class FilterToken extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
showDetails = () => {
|
||||
const menuTemplate: Array<MenuItemConstructorOptions> = [];
|
||||
const menuTemplate: Array<ContextMenuItem> = [];
|
||||
|
||||
if (this.props.filter.type === 'enum') {
|
||||
menuTemplate.push(
|
||||
@@ -155,19 +151,7 @@ export default class FilterToken extends PureComponent<Props> {
|
||||
},
|
||||
);
|
||||
}
|
||||
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<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
setRef = (ref: HTMLSpanElement | null) => {
|
||||
this._ref = ref;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {filter} = this.props;
|
||||
let color;
|
||||
@@ -231,21 +211,24 @@ export default class FilterToken extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<Token
|
||||
key={`${filter.key}:${value}=${filter.type}`}
|
||||
tabIndex={-1}
|
||||
onMouseDown={this.onMouseDown}
|
||||
focused={this.props.focused}
|
||||
color={color}
|
||||
ref={this.setRef}>
|
||||
<Key type={this.props.filter.type} focused={this.props.focused}>
|
||||
{filter.key}
|
||||
</Key>
|
||||
<Value>{value}</Value>
|
||||
<Chevron tabIndex={-1} focused={this.props.focused}>
|
||||
⌄
|
||||
</Chevron>
|
||||
</Token>
|
||||
<Dropdown trigger={dropdownTrigger} overlay={this.showDetails}>
|
||||
<Token
|
||||
key={`${filter.key}:${value}=${filter.type}`}
|
||||
tabIndex={-1}
|
||||
onMouseDown={this.onMouseDown}
|
||||
focused={this.props.focused}
|
||||
color={color}>
|
||||
<Key type={this.props.filter.type} focused={this.props.focused}>
|
||||
{filter.key}
|
||||
</Key>
|
||||
<Value>{value}</Value>
|
||||
<Chevron tabIndex={-1} focused={this.props.focused}>
|
||||
⌄
|
||||
</Chevron>
|
||||
</Token>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownTrigger = ['click' as const];
|
||||
|
||||
@@ -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<MenuItemConstructorOptions> = () => {
|
||||
buildContextMenuItems: () => Array<ContextMenuItem> = () => {
|
||||
const {highlightedRows} = this.state;
|
||||
if (highlightedRows.size === 0) {
|
||||
return [];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user