/** * 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 {Id, UINode} from '../types'; import React, {useEffect, useMemo, useRef} from 'react'; import { Tree as ComplexTree, ControlledTreeEnvironment, TreeItem, TreeInformation, TreeItemRenderContext, InteractionMode, TreeEnvironmentRef, } from 'react-complex-tree'; import {plugin} from '../index'; import { usePlugin, useValue, HighlightManager, HighlightProvider, useHighlighter, theme, } from 'flipper-plugin'; import {head} from 'lodash'; import {Dropdown, Menu} from 'antd'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; export function Tree(props: { rootId: Id; nodes: Map; selectedNode?: Id; onSelectNode: (id: Id) => void; }) { const instance = usePlugin(plugin); const expandedItems = useValue(instance.treeState).expandedNodes; const focused = useValue(instance.focusedNode); const items = useMemo( () => toComplexTree(focused || props.rootId, props.nodes), [focused, props.nodes, props.rootId], ); const hoveredNodes = useValue(instance.hoveredNodes); const treeEnvRef = useRef(); const searchTerm = useValue(instance.searchTerm); useEffect(() => { //this makes the keyboard arrow controls work always, even when using the visualiser treeEnvRef.current?.focusTree('tree', true); }, [props.selectedNode]); return ( item.data.name} canRename={false} canDragAndDrop={false} viewState={{ tree: { focusedItem: head(hoveredNodes), expandedItems, selectedItems: props.selectedNode ? [props.selectedNode] : [], }, }} onFocusItem={(item) => { instance.hoveredNodes.set([item.index]); }} onExpandItem={(item) => { instance.treeState.update((draft) => { draft.expandedNodes.push(item.index); }); }} onCollapseItem={(item) => instance.treeState.update((draft) => { draft.expandedNodes = draft.expandedNodes.filter( (expandedItemIndex) => expandedItemIndex !== item.index, ); }) } renderItem={renderItem} onSelectItems={(items) => props.onSelectNode(items[0])} defaultInteractionMode={{ mode: 'custom', extends: InteractionMode.DoubleClickItemToExpand, createInteractiveElementProps: ( item, treeId, actions, renderFlags, ) => ({ onClick: () => { if (renderFlags.isSelected) { actions.unselectItem(); } else { actions.selectItem(); } }, onMouseOver: () => { if (!instance.isContextMenuOpen.get()) { instance.hoveredNodes.set([item.index]); } }, }), }}> ); } //copied from https://github.com/lukasbach/react-complex-tree/blob/e3dcc435933284376a0fc6e3cc651e67ead678b5/packages/core/src/renderers/createDefaultRenderers.tsx const cx = (...classNames: Array) => classNames.filter((cn) => !!cn).join(' '); const renderDepthOffset = 5; function renderItem({ item, depth, children, arrow, context, }: { item: TreeItem; depth: number; children: React.ReactNode | null; title: React.ReactNode; arrow: React.ReactNode; context: TreeItemRenderContext; info: TreeInformation; }) { return (
  • {arrow}
    {children}
  • ); } type ContextMenuProps = {node: UINode; id: Id; title: string}; const ContextMenu: React.FC = ({id, title, children}) => { const instance = usePlugin(plugin); const focusedNode = instance.focusedNode.get(); return ( { instance.isContextMenuOpen.set(visible); }} overlay={() => ( {focusedNode !== head(instance.hoveredNodes.get()) && ( { instance.focusedNode.set(id); }} /> )} {focusedNode && ( { instance.focusedNode.set(undefined); }} /> )} )} trigger={['contextMenu']}>
    {children}
    ); }; function HighlightedText(props: {text: string}) { const highlightManager: HighlightManager = useHighlighter(); return {highlightManager.render(props.text)}; } const FakeNode: UINode = { id: 'Fakeroot', qualifiedName: 'Fakeroot', name: 'Fakeroot', children: [], attributes: {}, bounds: {x: 0, y: 0, height: 0, width: 0}, tags: [], }; function toComplexTree( root: Id, nodes: Map, ): Record> { const res: Record> = {}; for (const node of nodes.values()) { res[node.id] = { index: node.id, children: node.children, data: node, hasChildren: node.children.length > 0, }; } //the library doesnt render the root node so we insert a fake one which will never be rendered //https://github.com/lukasbach/react-complex-tree/issues/42 res[FakeNode.id] = { index: FakeNode.id, children: [root], hasChildren: true, data: FakeNode, }; return res; }