Update context menu for new ant design api
Summary: Mostly mechanical change from jsx to object based api. However some changes: 1. Managed to get rid of UIDebugger context menu item. its now possible to listen to when any context menu is clicked 2. The construction code is cleaner. no more mutable arrary and pushing, its just a big spliced literal 3. Had to change how the ide function worked. It is dynamic and used react query hook to update the number of items. Added a callback to recreate this behaviour. Reviewed By: aigoncharov Differential Revision: D48910165 fbshipit-source-id: 9a71f5ecd302e6ff72194f83a13839f78e9b0796
This commit is contained in:
committed by
Facebook GitHub Bot
parent
32ef1bd565
commit
b336ed38fa
@@ -7,16 +7,21 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {MenuProps} from 'antd';
|
||||
|
||||
import {ClientNode} from '../../ClientTypes';
|
||||
|
||||
export async function prefetchSourceFileLocation(_: ClientNode) {}
|
||||
|
||||
export function IDEContextMenuItems(_: {node: ClientNode}) {
|
||||
return <></>;
|
||||
type MenuItems = MenuProps['items'];
|
||||
|
||||
export function ideContextMenuItems(
|
||||
_node: ClientNode,
|
||||
_onResultsUpdated: () => void,
|
||||
): MenuItems {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function BigGrepContextMenuItems(_: {node: ClientNode}) {
|
||||
return <></>;
|
||||
export function bigGrepContextMenuItems(_: ClientNode): MenuItems {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {Tree2} from './tree/Tree';
|
||||
import {StreamInterceptorErrorView} from './StreamInterceptorErrorView';
|
||||
import {queryClient} from '../utils/reactQuery';
|
||||
import {FrameworkEventsTable} from './FrameworkEventsTable';
|
||||
import {Centered} from './shared/Centered';
|
||||
|
||||
export function Component() {
|
||||
const instance = usePlugin(plugin);
|
||||
@@ -175,16 +176,6 @@ export function Component() {
|
||||
);
|
||||
}
|
||||
|
||||
export function Centered(props: {children: React.ReactNode}) {
|
||||
return (
|
||||
<Layout.Horizontal center grow>
|
||||
<Layout.Container center grow>
|
||||
{props.children}
|
||||
</Layout.Container>
|
||||
</Layout.Horizontal>
|
||||
);
|
||||
}
|
||||
|
||||
type BottomPanelProps = {
|
||||
title: string;
|
||||
dismiss: () => void;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {Layout} from 'flipper-plugin';
|
||||
import React from 'react';
|
||||
|
||||
export function Centered(props: {children: React.ReactNode}) {
|
||||
return (
|
||||
<Layout.Horizontal center grow>
|
||||
<Layout.Container center grow>
|
||||
{props.children}
|
||||
</Layout.Container>
|
||||
</Layout.Horizontal>
|
||||
);
|
||||
}
|
||||
@@ -9,16 +9,16 @@
|
||||
|
||||
import {FrameworkEvent, Id, ClientNode} from '../../ClientTypes';
|
||||
import {OnSelectNode, ViewMode} from '../../DesktopTypes';
|
||||
import React, {ReactNode} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {DataSource, getFlipperLib} from 'flipper-plugin';
|
||||
import {Dropdown, Menu} from 'antd';
|
||||
import {UIDebuggerMenuItem} from '../util/UIDebuggerMenuItem';
|
||||
import {Dropdown, MenuProps} from 'antd';
|
||||
import {tracker} from '../../utils/tracker';
|
||||
import {
|
||||
BigGrepContextMenuItems,
|
||||
IDEContextMenuItems,
|
||||
bigGrepContextMenuItems,
|
||||
ideContextMenuItems,
|
||||
} from '../fb-stubs/IDEContextMenu';
|
||||
import {
|
||||
CopyFilled,
|
||||
CopyOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
@@ -28,6 +28,9 @@ import {
|
||||
SnippetsOutlined,
|
||||
TableOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {filterOutFalsy} from '../../utils/array';
|
||||
|
||||
type MenuItems = MenuProps['items'];
|
||||
|
||||
export const ContextMenu: React.FC<{
|
||||
frameworkEvents: DataSource<FrameworkEvent>;
|
||||
@@ -55,104 +58,29 @@ export const ContextMenu: React.FC<{
|
||||
onCollapseNonAncestors,
|
||||
onSelectNode,
|
||||
}) => {
|
||||
const copyItems: ReactNode[] = [];
|
||||
const [_, setIdeItemsRerender] = useState(0);
|
||||
const hoveredNode = nodes.get(hoveredNodeId ?? Number.MAX_SAFE_INTEGER);
|
||||
|
||||
let treeCollapseItems: ReactNode[] = [];
|
||||
if (hoveredNode) {
|
||||
treeCollapseItems = [
|
||||
<UIDebuggerMenuItem
|
||||
key="expand-recursive"
|
||||
text="Expand recursively"
|
||||
icon={<MenuUnfoldOutlined />}
|
||||
onClick={() => {
|
||||
onExpandRecursively(hoveredNode.id);
|
||||
onSelectNode(hoveredNode.id, 'context-menu');
|
||||
tracker.track('context-menu-expand-recursive', {});
|
||||
}}
|
||||
/>,
|
||||
|
||||
<UIDebuggerMenuItem
|
||||
key="collapse-recursive"
|
||||
text="Collapse recurisvely"
|
||||
icon={<MenuFoldOutlined />}
|
||||
onClick={() => {
|
||||
onCollapseRecursively(hoveredNode.id);
|
||||
onSelectNode(hoveredNode.id, 'context-menu');
|
||||
tracker.track('context-menu-collapse-recursive', {});
|
||||
}}
|
||||
/>,
|
||||
<UIDebuggerMenuItem
|
||||
key="collapse-non-ancestors"
|
||||
text="Collapse non ancestors"
|
||||
icon={<NodeExpandOutlined />}
|
||||
onClick={() => {
|
||||
onCollapseNonAncestors(hoveredNode.id);
|
||||
onSelectNode(hoveredNode.id, 'context-menu');
|
||||
tracker.track('context-menu-collapse-non-ancestors', {});
|
||||
}}
|
||||
/>,
|
||||
<Menu.Divider key="expand-divider" />,
|
||||
];
|
||||
|
||||
copyItems.push(
|
||||
<UIDebuggerMenuItem
|
||||
key="Copy Element name"
|
||||
text="Copy Element name"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => {
|
||||
tracker.track('context-menu-name-copied', {name: hoveredNode.name});
|
||||
getFlipperLib().writeTextToClipboard(hoveredNode.name);
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
copyItems.push(
|
||||
Object.entries(hoveredNode.inlineAttributes).map(([key, value]) => (
|
||||
<UIDebuggerMenuItem
|
||||
key={key}
|
||||
text={`Copy ${key}`}
|
||||
icon={<SnippetsOutlined />}
|
||||
onClick={() => {
|
||||
tracker.track('context-menu-copied', {
|
||||
name: hoveredNode.name,
|
||||
key,
|
||||
value,
|
||||
});
|
||||
getFlipperLib().writeTextToClipboard(value);
|
||||
}}
|
||||
/>
|
||||
)),
|
||||
);
|
||||
|
||||
copyItems.push(
|
||||
<BigGrepContextMenuItems key="big-grep" node={hoveredNode} />,
|
||||
);
|
||||
}
|
||||
const focus = hoveredNode != null &&
|
||||
focusedNodeId !== hoveredNodeId &&
|
||||
hoveredNode.bounds.height !== 0 &&
|
||||
hoveredNode.bounds.width !== 0 && (
|
||||
<UIDebuggerMenuItem
|
||||
key="focus"
|
||||
text={`Focus element`}
|
||||
icon={<FullscreenExitOutlined />}
|
||||
onClick={() => {
|
||||
onFocusNode(hoveredNodeId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
hoveredNode.bounds.width !== 0 && {
|
||||
key: 'focus',
|
||||
label: `Focus element`,
|
||||
icon: <FullscreenExitOutlined />,
|
||||
onClick: () => {
|
||||
onFocusNode(hoveredNodeId);
|
||||
},
|
||||
};
|
||||
|
||||
const removeFocus = focusedNodeId && (
|
||||
<UIDebuggerMenuItem
|
||||
key="remove-focus"
|
||||
text="Remove focus"
|
||||
icon={<FullscreenOutlined />}
|
||||
onClick={() => {
|
||||
onFocusNode(undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const removeFocus = focusedNodeId && {
|
||||
key: 'remove-focus',
|
||||
label: 'Remove focus',
|
||||
icon: <FullscreenOutlined />,
|
||||
onClick: () => {
|
||||
onFocusNode(undefined);
|
||||
},
|
||||
};
|
||||
|
||||
const matchingFrameworkEvents =
|
||||
(hoveredNode &&
|
||||
@@ -160,42 +88,100 @@ export const ContextMenu: React.FC<{
|
||||
[];
|
||||
|
||||
const frameworkEventsTable = matchingFrameworkEvents.length > 0 &&
|
||||
hoveredNode && (
|
||||
<UIDebuggerMenuItem
|
||||
text="Explore events"
|
||||
onClick={() => {
|
||||
onSetViewMode({
|
||||
mode: 'frameworkEventsTable',
|
||||
nodeId: hoveredNode.id,
|
||||
isTree: hoveredNode.tags.includes('TreeRoot'),
|
||||
});
|
||||
}}
|
||||
icon={<TableOutlined />}
|
||||
/>
|
||||
);
|
||||
hoveredNode && {
|
||||
key: 'events-table',
|
||||
label: 'Explore events',
|
||||
icon: <TableOutlined />,
|
||||
onClick: () => {
|
||||
onSetViewMode({
|
||||
mode: 'frameworkEventsTable',
|
||||
nodeId: hoveredNode.id,
|
||||
isTree: hoveredNode.tags.includes('TreeRoot'),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const focusItems = [focus, removeFocus, frameworkEventsTable];
|
||||
|
||||
const items: MenuItems =
|
||||
hoveredNode == null
|
||||
? []
|
||||
: filterOutFalsy([
|
||||
{
|
||||
key: 'expand-recursive',
|
||||
label: 'Expand recursively',
|
||||
icon: <MenuUnfoldOutlined />,
|
||||
onClick: () => {
|
||||
onExpandRecursively(hoveredNode.id);
|
||||
onSelectNode(hoveredNode.id, 'context-menu');
|
||||
tracker.track('context-menu-expand-recursive', {});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'collapse-recursive',
|
||||
label: 'Collapse recurisvely',
|
||||
icon: <MenuFoldOutlined />,
|
||||
onClick: () => {
|
||||
onCollapseRecursively(hoveredNode.id);
|
||||
onSelectNode(hoveredNode.id, 'context-menu');
|
||||
tracker.track('context-menu-collapse-recursive', {});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'collapse-non-ancestors',
|
||||
label: 'Collapse non ancestors',
|
||||
icon: <NodeExpandOutlined />,
|
||||
onClick: () => {
|
||||
onCollapseNonAncestors(hoveredNode.id);
|
||||
onSelectNode(hoveredNode.id, 'context-menu');
|
||||
tracker.track('context-menu-collapse-non-ancestors', {});
|
||||
},
|
||||
},
|
||||
{type: 'divider'},
|
||||
...focusItems,
|
||||
focusItems.length > 0 && {type: 'divider'},
|
||||
{
|
||||
key: 'Copy Element name',
|
||||
label: 'Copy Element name',
|
||||
icon: <CopyOutlined />,
|
||||
onClick: () => {
|
||||
tracker.track('context-menu-name-copied', {
|
||||
name: hoveredNode.name,
|
||||
});
|
||||
getFlipperLib().writeTextToClipboard(hoveredNode.name);
|
||||
},
|
||||
},
|
||||
...Object.entries(hoveredNode.inlineAttributes).map(
|
||||
([key, value]) => ({
|
||||
key: key,
|
||||
label: `Copy ${key}`,
|
||||
icon: <SnippetsOutlined />,
|
||||
onClick: () => {
|
||||
tracker.track('context-menu-copied', {
|
||||
name: hoveredNode.name,
|
||||
key,
|
||||
value,
|
||||
});
|
||||
getFlipperLib().writeTextToClipboard(value);
|
||||
},
|
||||
}),
|
||||
),
|
||||
...(bigGrepContextMenuItems(hoveredNode) || []),
|
||||
...(ideContextMenuItems(hoveredNode, () =>
|
||||
setIdeItemsRerender((value) => value + 1),
|
||||
) || []),
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
onVisibleChange={(visible) => {
|
||||
onOpenChange={(visible) => {
|
||||
onContextMenuOpen(visible);
|
||||
}}
|
||||
overlay={() => {
|
||||
return (
|
||||
<Menu>
|
||||
{treeCollapseItems}
|
||||
{focus}
|
||||
{removeFocus}
|
||||
{frameworkEventsTable}
|
||||
{(focus || removeFocus || frameworkEventsTable) && (
|
||||
<Menu.Divider key="divider-focus" />
|
||||
)}
|
||||
{copyItems}
|
||||
|
||||
{hoveredNode && (
|
||||
<IDEContextMenuItems key="ide" node={hoveredNode} />
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
menu={{
|
||||
items,
|
||||
onClick: () => {
|
||||
onContextMenuOpen(false);
|
||||
},
|
||||
}}
|
||||
trigger={['contextMenu']}>
|
||||
{children}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {Menu} from 'antd';
|
||||
import {usePlugin, useValue, Layout} from 'flipper-plugin';
|
||||
import {plugin} from '../../index';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* The Menu item visibility event does not fire when a menu item is clicked.
|
||||
* This is apparently by design https://github.com/ant-design/ant-design/issues/4994#issuecomment-281585872
|
||||
* This component simply wraps a menu item but will ensure that the atom is set to false when an item is clicked.
|
||||
* Additionally, it ensures menu items do not render when the atom is false
|
||||
*/
|
||||
export const UIDebuggerMenuItem: React.FC<{
|
||||
text: string;
|
||||
icon?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
}> = ({text, onClick, icon, onMouseEnter, onMouseLeave}) => {
|
||||
const instance = usePlugin(plugin);
|
||||
|
||||
const isMenuOpen = useValue(instance.uiState.isContextMenuOpen);
|
||||
/**
|
||||
* The menu is not a controlled component and seems to be a bit slow to close when user clicks on it.
|
||||
* React may rerender the menu before it has time to close resulting in seeing an incorrect context menu for a frame.
|
||||
* This is here to just hide all the menu items when the menu closes. A little strange but works well in practice.
|
||||
*/
|
||||
if (!isMenuOpen) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Menu.Item
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
disabled={onClick == null}
|
||||
onClick={() => {
|
||||
onClick?.();
|
||||
instance.uiActions.onContextMenuOpen(false);
|
||||
}}>
|
||||
<Layout.Horizontal center gap="small">
|
||||
{icon}
|
||||
{text}
|
||||
</Layout.Horizontal>
|
||||
</Menu.Item>
|
||||
);
|
||||
};
|
||||
14
desktop/plugins/public/ui-debugger/utils/array.tsx
Normal file
14
desktop/plugins/public/ui-debugger/utils/array.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
export function filterOutFalsy<T>(
|
||||
arr: T[],
|
||||
): Exclude<T, false | 0 | '' | null | undefined>[] {
|
||||
return arr.filter(Boolean) as Exclude<T, false | 0 | '' | null | undefined>[];
|
||||
}
|
||||
Reference in New Issue
Block a user