/** * 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 {FrameworkEvent, Id, ClientNode} from '../../ClientTypes'; import {OnSelectNode, ViewMode} from '../../DesktopTypes'; import React, {ReactNode} from 'react'; import {DataSource, getFlipperLib} from 'flipper-plugin'; import {Dropdown, Menu} from 'antd'; import {UIDebuggerMenuItem} from '../util/UIDebuggerMenuItem'; import {tracker} from '../../utils/tracker'; import { BigGrepContextMenuItems, IDEContextMenuItems, } from '../fb-stubs/IDEContextMenu'; import { CopyOutlined, FullscreenExitOutlined, FullscreenOutlined, MenuFoldOutlined, MenuUnfoldOutlined, NodeExpandOutlined, SnippetsOutlined, TableOutlined, } from '@ant-design/icons'; export const ContextMenu: React.FC<{ frameworkEvents: DataSource; nodes: Map; hoveredNodeId?: Id; focusedNodeId?: Id; onFocusNode: (id?: Id) => void; onContextMenuOpen: (open: boolean) => void; onSetViewMode: (viewMode: ViewMode) => void; onExpandRecursively: (id: Id) => void; onCollapseRecursively: (id: Id) => void; onCollapseNonAncestors: (id: Id) => void; onSelectNode: OnSelectNode; }> = ({ nodes, frameworkEvents, hoveredNodeId, children, focusedNodeId, onFocusNode, onContextMenuOpen, onSetViewMode, onExpandRecursively, onCollapseRecursively, onCollapseNonAncestors, onSelectNode, }) => { const copyItems: ReactNode[] = []; 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); }} /> ); const removeFocus = focusedNodeId && ( } onClick={() => { onFocusNode(undefined); }} /> ); const matchingFrameworkEvents = (hoveredNode && frameworkEvents.getAllRecordsByIndex({nodeId: hoveredNode.id})) ?? []; const frameworkEventsTable = hoveredNode?.tags.includes('TreeRoot') && matchingFrameworkEvents.length > 0 && ( { onSetViewMode({ mode: 'frameworkEventsTable', treeRootId: hoveredNode?.id ?? '', }); }} icon={} /> ); return ( { onContextMenuOpen(visible); }} overlay={() => { return ( {treeCollapseItems} {focus} {removeFocus} {frameworkEventsTable} {(focus || removeFocus || frameworkEventsTable) && ( )} {copyItems} {hoveredNode && ( )} ); }} trigger={['contextMenu']}> {children} ); };