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 {