diff --git a/desktop/plugins/public/ui-debugger/components/fb-stubs/IDEContextMenu.tsx b/desktop/plugins/public/ui-debugger/components/fb-stubs/IDEContextMenu.tsx
index 062d09029..a80b8c651 100644
--- a/desktop/plugins/public/ui-debugger/components/fb-stubs/IDEContextMenu.tsx
+++ b/desktop/plugins/public/ui-debugger/components/fb-stubs/IDEContextMenu.tsx
@@ -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 [];
}
diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx
index 3f281ead7..d88c4f487 100644
--- a/desktop/plugins/public/ui-debugger/components/main.tsx
+++ b/desktop/plugins/public/ui-debugger/components/main.tsx
@@ -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 (
-
-
- {props.children}
-
-
- );
-}
-
type BottomPanelProps = {
title: string;
dismiss: () => void;
diff --git a/desktop/plugins/public/ui-debugger/components/shared/Centered.tsx b/desktop/plugins/public/ui-debugger/components/shared/Centered.tsx
new file mode 100644
index 000000000..657a0b7bb
--- /dev/null
+++ b/desktop/plugins/public/ui-debugger/components/shared/Centered.tsx
@@ -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 (
+
+
+ {props.children}
+
+
+ );
+}
diff --git a/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx b/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx
index 56044a33b..657dce2cc 100644
--- a/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx
+++ b/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx
@@ -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;
@@ -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 = [
- }
- onClick={() => {
- onExpandRecursively(hoveredNode.id);
- onSelectNode(hoveredNode.id, 'context-menu');
- tracker.track('context-menu-expand-recursive', {});
- }}
- />,
-
- }
- onClick={() => {
- onCollapseRecursively(hoveredNode.id);
- onSelectNode(hoveredNode.id, 'context-menu');
- tracker.track('context-menu-collapse-recursive', {});
- }}
- />,
- }
- onClick={() => {
- onCollapseNonAncestors(hoveredNode.id);
- onSelectNode(hoveredNode.id, 'context-menu');
- tracker.track('context-menu-collapse-non-ancestors', {});
- }}
- />,
- ,
- ];
-
- copyItems.push(
- }
- onClick={() => {
- tracker.track('context-menu-name-copied', {name: hoveredNode.name});
- getFlipperLib().writeTextToClipboard(hoveredNode.name);
- }}
- />,
- );
-
- copyItems.push(
- Object.entries(hoveredNode.inlineAttributes).map(([key, value]) => (
- }
- onClick={() => {
- tracker.track('context-menu-copied', {
- name: hoveredNode.name,
- key,
- value,
- });
- getFlipperLib().writeTextToClipboard(value);
- }}
- />
- )),
- );
-
- copyItems.push(
- ,
- );
- }
const focus = hoveredNode != null &&
focusedNodeId !== hoveredNodeId &&
hoveredNode.bounds.height !== 0 &&
- hoveredNode.bounds.width !== 0 && (
- }
- onClick={() => {
- onFocusNode(hoveredNodeId);
- }}
- />
- );
+ hoveredNode.bounds.width !== 0 && {
+ key: 'focus',
+ label: `Focus element`,
+ icon: ,
+ onClick: () => {
+ onFocusNode(hoveredNodeId);
+ },
+ };
- const removeFocus = focusedNodeId && (
- }
- onClick={() => {
- onFocusNode(undefined);
- }}
- />
- );
+ const removeFocus = focusedNodeId && {
+ key: 'remove-focus',
+ label: 'Remove focus',
+ icon: ,
+ onClick: () => {
+ onFocusNode(undefined);
+ },
+ };
const matchingFrameworkEvents =
(hoveredNode &&
@@ -160,42 +88,100 @@ export const ContextMenu: React.FC<{
[];
const frameworkEventsTable = matchingFrameworkEvents.length > 0 &&
- hoveredNode && (
- {
- onSetViewMode({
- mode: 'frameworkEventsTable',
- nodeId: hoveredNode.id,
- isTree: hoveredNode.tags.includes('TreeRoot'),
- });
- }}
- icon={}
- />
- );
+ hoveredNode && {
+ key: 'events-table',
+ label: 'Explore events',
+ icon: ,
+ 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: ,
+ onClick: () => {
+ onExpandRecursively(hoveredNode.id);
+ onSelectNode(hoveredNode.id, 'context-menu');
+ tracker.track('context-menu-expand-recursive', {});
+ },
+ },
+ {
+ key: 'collapse-recursive',
+ label: 'Collapse recurisvely',
+ icon: ,
+ onClick: () => {
+ onCollapseRecursively(hoveredNode.id);
+ onSelectNode(hoveredNode.id, 'context-menu');
+ tracker.track('context-menu-collapse-recursive', {});
+ },
+ },
+ {
+ key: 'collapse-non-ancestors',
+ label: 'Collapse non ancestors',
+ icon: ,
+ 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: ,
+ 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: ,
+ onClick: () => {
+ tracker.track('context-menu-copied', {
+ name: hoveredNode.name,
+ key,
+ value,
+ });
+ getFlipperLib().writeTextToClipboard(value);
+ },
+ }),
+ ),
+ ...(bigGrepContextMenuItems(hoveredNode) || []),
+ ...(ideContextMenuItems(hoveredNode, () =>
+ setIdeItemsRerender((value) => value + 1),
+ ) || []),
+ ]);
return (
{
+ onOpenChange={(visible) => {
onContextMenuOpen(visible);
}}
- overlay={() => {
- return (
-
- );
+ menu={{
+ items,
+ onClick: () => {
+ onContextMenuOpen(false);
+ },
}}
trigger={['contextMenu']}>
{children}
diff --git a/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx b/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx
deleted file mode 100644
index 765ddb9c0..000000000
--- a/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx
+++ /dev/null
@@ -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 (
- {
- onClick?.();
- instance.uiActions.onContextMenuOpen(false);
- }}>
-
- {icon}
- {text}
-
-
- );
-};
diff --git a/desktop/plugins/public/ui-debugger/utils/array.tsx b/desktop/plugins/public/ui-debugger/utils/array.tsx
new file mode 100644
index 000000000..fa8c990d5
--- /dev/null
+++ b/desktop/plugins/public/ui-debugger/utils/array.tsx
@@ -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(
+ arr: T[],
+): Exclude[] {
+ return arr.filter(Boolean) as Exclude[];
+}