Refactor out UI debugger menu item

Summary: We have to do a couple of odd things to get the context menu items to behave. The code was duplicated between tree and visualizer. This custom component removes duplication and makes the approach a bit clearer (via js doc)

Reviewed By: antonk52

Differential Revision: D41495718

fbshipit-source-id: ec98d5101e636a2c9034c656d29991d4fe348762
This commit is contained in:
Luke De Feo
2022-11-24 09:23:16 -08:00
committed by Facebook GitHub Bot
parent ca67bfd916
commit 7fc64adfd4
3 changed files with 67 additions and 23 deletions

View File

@@ -31,6 +31,7 @@ import {
import {head} from 'lodash'; import {head} from 'lodash';
import {Dropdown, Menu} from 'antd'; import {Dropdown, Menu} from 'antd';
import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem';
export function Tree(props: { export function Tree(props: {
rootId: Id; rootId: Id;
@@ -209,23 +210,23 @@ const ContextMenu: React.FC<ContextMenuProps> = ({id, title, children}) => {
overlay={() => ( overlay={() => (
<Menu> <Menu>
{focusedNode !== head(instance.hoveredNodes.get()) && ( {focusedNode !== head(instance.hoveredNodes.get()) && (
<Menu.Item <UIDebuggerMenuItem
key="focus"
text={`Focus ${title}`}
onClick={() => { onClick={() => {
instance.focusedNode.set(id); instance.focusedNode.set(id);
instance.isContextMenuOpen.set(false); }}
}}> />
Focus {title}
</Menu.Item>
)} )}
{focusedNode && ( {focusedNode && (
<Menu.Item <UIDebuggerMenuItem
key="remove-focus"
text="Remove focus"
onClick={() => { onClick={() => {
instance.focusedNode.set(undefined); instance.focusedNode.set(undefined);
instance.isContextMenuOpen.set(false); }}
}}> />
Remove focus
</Menu.Item>
)} )}
</Menu> </Menu>
)} )}

View File

@@ -14,6 +14,7 @@ import {produce, styled, theme, usePlugin, useValue} from 'flipper-plugin';
import {plugin} from '../index'; import {plugin} from '../index';
import {head, isEqual, throttle} from 'lodash'; import {head, isEqual, throttle} from 'lodash';
import {Dropdown, Menu} from 'antd'; import {Dropdown, Menu} from 'antd';
import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem';
export const Visualization2D: React.FC< export const Visualization2D: React.FC<
{ {
@@ -244,7 +245,6 @@ const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
const hoveredNodeId = head(useValue(instance.hoveredNodes)); const hoveredNodeId = head(useValue(instance.hoveredNodes));
const nodes = useValue(instance.nodes); const nodes = useValue(instance.nodes);
const hoveredNode = hoveredNodeId ? nodes.get(hoveredNodeId) : null; const hoveredNode = hoveredNodeId ? nodes.get(hoveredNodeId) : null;
const isMenuOpen = useValue(instance.isContextMenuOpen);
return ( return (
<Dropdown <Dropdown
@@ -255,25 +255,23 @@ const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
overlay={() => { overlay={() => {
return ( return (
<Menu> <Menu>
{isMenuOpen && hoveredNode?.id !== focusedNodeId && ( {hoveredNode?.id !== focusedNodeId && (
<Menu.Item <UIDebuggerMenuItem
key="focus" key="focus"
text={`Focus ${hoveredNode?.name}`}
onClick={() => { onClick={() => {
instance.focusedNode.set(hoveredNode?.id); instance.focusedNode.set(hoveredNode?.id);
instance.isContextMenuOpen.set(false); }}
}}> />
Focus {hoveredNode?.name}
</Menu.Item>
)} )}
{isMenuOpen && focusedNodeId != null && ( {focusedNodeId != null && (
<Menu.Item <UIDebuggerMenuItem
key="remove-focus" key="remove-focus"
text="Remove focus"
onClick={() => { onClick={() => {
instance.focusedNode.set(undefined); instance.focusedNode.set(undefined);
instance.isContextMenuOpen.set(false); }}
}}> />
Remove focus
</Menu.Item>
)} )}
</Menu> </Menu>
); );

View File

@@ -0,0 +1,45 @@
/**
* 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} 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;
onClick: () => void;
}> = ({text, onClick}) => {
const instance = usePlugin(plugin);
const isMenuOpen = useValue(instance.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
onClick={() => {
onClick();
instance.isContextMenuOpen.set(false);
}}>
{text}
</Menu.Item>
);
};