From ce1fdfdf19fd169fb54728a45030e04bfebc3067 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Tue, 1 Aug 2023 10:32:29 -0700 Subject: [PATCH] Added context menu items for collapsing and expanding nodes Summary: Added 3 context menu items: - expand recursive - collapse recursive These are self explanatory. I also collapse non ancestors. This collapses everything except your direct ancestor path to the root. Quite useful to refocus the tree on a node Changelog: UIDebugger - added context menu items for exanding and collapsing the tree. Reviewed By: aigoncharov Differential Revision: D47949840 fbshipit-source-id: 6eebba182fe2092fbf5f0db0ec5ff728c3900424 --- .../public/ui-debugger/DesktopTypes.tsx | 9 +++- .../components/tree/ContextMenu.tsx | 51 +++++++++++++++++- .../ui-debugger/components/tree/Tree.tsx | 6 ++- .../components/visualizer/Visualization2D.tsx | 4 ++ .../public/ui-debugger/plugin/uiActions.tsx | 54 ++++++++++++++++++- .../public/ui-debugger/utils/tracker.tsx | 3 ++ 6 files changed, 123 insertions(+), 4 deletions(-) diff --git a/desktop/plugins/public/ui-debugger/DesktopTypes.tsx b/desktop/plugins/public/ui-debugger/DesktopTypes.tsx index 8c7f56da9..db68be5ed 100644 --- a/desktop/plugins/public/ui-debugger/DesktopTypes.tsx +++ b/desktop/plugins/public/ui-debugger/DesktopTypes.tsx @@ -95,9 +95,16 @@ export type UIActions = { onPlayPauseToggled: () => void; onSearchTermUpdated: (searchTerm: string) => void; onSetWireFrameMode: (WireFrameMode: WireFrameMode) => void; + onExpandAllRecursively: (nodeId: Id) => void; + onCollapseAllNonAncestors: (nodeId: Id) => void; + onCollapseAllRecursively: (nodeId: Id) => void; }; -export type SelectionSource = 'visualiser' | 'tree' | 'keyboard'; +export type SelectionSource = + | 'visualiser' + | 'tree' + | 'keyboard' + | 'context-menu'; export type StreamState = | {state: 'Ok'} diff --git a/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx b/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx index 6deaa3fe7..ffd7bcfcf 100644 --- a/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx +++ b/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx @@ -6,8 +6,9 @@ * * @format */ + import {FrameworkEvent, Id, ClientNode} from '../../ClientTypes'; -import {ViewMode} from '../../DesktopTypes'; +import {OnSelectNode, ViewMode} from '../../DesktopTypes'; import React, {ReactNode} from 'react'; import {DataSource, getFlipperLib} from 'flipper-plugin'; import {Dropdown, Menu} from 'antd'; @@ -21,6 +22,9 @@ import { CopyOutlined, FullscreenExitOutlined, FullscreenOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, + NodeExpandOutlined, SnippetsOutlined, TableOutlined, } from '@ant-design/icons'; @@ -33,6 +37,10 @@ export const ContextMenu: React.FC<{ 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, @@ -42,11 +50,51 @@ export const ContextMenu: React.FC<{ 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( ( + {treeCollapseItems} {focus} {removeFocus} {frameworkEventsTable} diff --git a/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx b/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx index 120cfc382..1fce9dad7 100644 --- a/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx +++ b/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx @@ -211,9 +211,13 @@ export function Tree2({ focusedNodeId={focusedNode} hoveredNodeId={hoveredNode} nodes={nodes} + onSelectNode={instance.uiActions.onSelectNode} onSetViewMode={instance.uiActions.onSetViewMode} onContextMenuOpen={instance.uiActions.onContextMenuOpen} - onFocusNode={instance.uiActions.onFocusNode}> + onFocusNode={instance.uiActions.onFocusNode} + onCollapseNonAncestors={instance.uiActions.onCollapseAllNonAncestors} + onCollapseRecursively={instance.uiActions.onCollapseAllRecursively} + onExpandRecursively={instance.uiActions.onExpandAllRecursively}>
{ instance.uiActions.onSelectNode(hoveredNodeId, 'visualiser'); + if (hoveredNodeId != null) { + instance.uiActions.onCollapseAllNonAncestors(hoveredNodeId); + } + if (targetMode.state !== 'disabled') { setTargetMode({ state: 'selected', diff --git a/desktop/plugins/public/ui-debugger/plugin/uiActions.tsx b/desktop/plugins/public/ui-debugger/plugin/uiActions.tsx index ef73b9a7e..0d6582669 100644 --- a/desktop/plugins/public/ui-debugger/plugin/uiActions.tsx +++ b/desktop/plugins/public/ui-debugger/plugin/uiActions.tsx @@ -36,7 +36,10 @@ export function uiActions( }); }; const onSelectNode = (node: Id | undefined, source: SelectionSource) => { - if (node == null || uiState.selectedNode.get()?.id === node) { + if ( + node == null || + (uiState.selectedNode.get()?.id === node && source !== 'context-menu') + ) { uiState.selectedNode.set(undefined); } else { uiState.selectedNode.set({id: node, source}); @@ -83,6 +86,52 @@ export function uiActions( uiState.isContextMenuOpen.set(open); }; + const onCollapseAllNonAncestors = (nodeId: Id) => { + //this is not the simplest way to achieve this but on android there is a parent pointer missing for the decor view + //due to the nested obversers. + uiState.expandedNodes.update((draft) => { + const nodesMap = nodes.get(); + let prevNode: Id | null = null; + let curNode = nodesMap.get(nodeId); + while (curNode != null) { + for (const child of curNode.children) { + if (child !== prevNode) { + draft.delete(child); + } + } + prevNode = curNode.id; + curNode = nodesMap.get(curNode?.parent ?? 'Nonode'); + } + }); + }; + + function treeTraverseUtil( + nodeID: Id, + nodeVisitor: (node: ClientNode) => void, + ) { + const nodesMap = nodes.get(); + + const node = nodesMap.get(nodeID); + if (node != null) { + nodeVisitor(node); + for (const childId of node.children) { + treeTraverseUtil(childId, nodeVisitor); + } + } + } + + const onExpandAllRecursively = (nodeId: Id) => { + uiState.expandedNodes.update((draft) => { + treeTraverseUtil(nodeId, (node) => draft.add(node.id)); + }); + }; + + const onCollapseAllRecursively = (nodeId: Id) => { + uiState.expandedNodes.update((draft) => { + treeTraverseUtil(nodeId, (node) => draft.delete(node.id)); + }); + }; + const onFocusNode = (node?: Id) => { if (node != null) { const focusedNode = nodes.get().get(node); @@ -164,5 +213,8 @@ export function uiActions( onPlayPauseToggled, onSearchTermUpdated, onSetWireFrameMode, + onCollapseAllNonAncestors, + onExpandAllRecursively, + onCollapseAllRecursively, }; } diff --git a/desktop/plugins/public/ui-debugger/utils/tracker.tsx b/desktop/plugins/public/ui-debugger/utils/tracker.tsx index e00a46c76..4b9d67e0b 100644 --- a/desktop/plugins/public/ui-debugger/utils/tracker.tsx +++ b/desktop/plugins/public/ui-debugger/utils/tracker.tsx @@ -56,6 +56,9 @@ type TrackerEvents = { on: boolean; }; 'target-mode-adjusted': {}; + 'context-menu-expand-recursive': {}; + 'context-menu-collapse-recursive': {}; + 'context-menu-collapse-non-ancestors': {}; }; export interface Tracker {