From b336ed38faa5071339cb7fabfe50331c23414e47 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Mon, 4 Sep 2023 02:19:53 -0700 Subject: [PATCH] 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 --- .../components/fb-stubs/IDEContextMenu.tsx | 15 +- .../public/ui-debugger/components/main.tsx | 11 +- .../components/shared/Centered.tsx | 21 ++ .../components/tree/ContextMenu.tsx | 242 +++++++++--------- .../components/util/UIDebuggerMenuItem.tsx | 54 ---- .../public/ui-debugger/utils/array.tsx | 14 + 6 files changed, 160 insertions(+), 197 deletions(-) create mode 100644 desktop/plugins/public/ui-debugger/components/shared/Centered.tsx delete mode 100644 desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx create mode 100644 desktop/plugins/public/ui-debugger/utils/array.tsx 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 ( - - {treeCollapseItems} - {focus} - {removeFocus} - {frameworkEventsTable} - {(focus || removeFocus || frameworkEventsTable) && ( - - )} - {copyItems} - - {hoveredNode && ( - - )} - - ); + 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[]; +}