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
This commit is contained in:
Luke De Feo
2023-08-01 10:32:29 -07:00
committed by Facebook GitHub Bot
parent 129c848b78
commit ce1fdfdf19
6 changed files with 123 additions and 4 deletions

View File

@@ -95,9 +95,16 @@ export type UIActions = {
onPlayPauseToggled: () => void; onPlayPauseToggled: () => void;
onSearchTermUpdated: (searchTerm: string) => void; onSearchTermUpdated: (searchTerm: string) => void;
onSetWireFrameMode: (WireFrameMode: WireFrameMode) => 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 = export type StreamState =
| {state: 'Ok'} | {state: 'Ok'}

View File

@@ -6,8 +6,9 @@
* *
* @format * @format
*/ */
import {FrameworkEvent, Id, ClientNode} from '../../ClientTypes'; import {FrameworkEvent, Id, ClientNode} from '../../ClientTypes';
import {ViewMode} from '../../DesktopTypes'; import {OnSelectNode, ViewMode} from '../../DesktopTypes';
import React, {ReactNode} from 'react'; import React, {ReactNode} from 'react';
import {DataSource, getFlipperLib} from 'flipper-plugin'; import {DataSource, getFlipperLib} from 'flipper-plugin';
import {Dropdown, Menu} from 'antd'; import {Dropdown, Menu} from 'antd';
@@ -21,6 +22,9 @@ import {
CopyOutlined, CopyOutlined,
FullscreenExitOutlined, FullscreenExitOutlined,
FullscreenOutlined, FullscreenOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
NodeExpandOutlined,
SnippetsOutlined, SnippetsOutlined,
TableOutlined, TableOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
@@ -33,6 +37,10 @@ export const ContextMenu: React.FC<{
onFocusNode: (id?: Id) => void; onFocusNode: (id?: Id) => void;
onContextMenuOpen: (open: boolean) => void; onContextMenuOpen: (open: boolean) => void;
onSetViewMode: (viewMode: ViewMode) => void; onSetViewMode: (viewMode: ViewMode) => void;
onExpandRecursively: (id: Id) => void;
onCollapseRecursively: (id: Id) => void;
onCollapseNonAncestors: (id: Id) => void;
onSelectNode: OnSelectNode;
}> = ({ }> = ({
nodes, nodes,
frameworkEvents, frameworkEvents,
@@ -42,11 +50,51 @@ export const ContextMenu: React.FC<{
onFocusNode, onFocusNode,
onContextMenuOpen, onContextMenuOpen,
onSetViewMode, onSetViewMode,
onExpandRecursively,
onCollapseRecursively,
onCollapseNonAncestors,
onSelectNode,
}) => { }) => {
const copyItems: ReactNode[] = []; const copyItems: ReactNode[] = [];
const hoveredNode = nodes.get(hoveredNodeId ?? Number.MAX_SAFE_INTEGER); const hoveredNode = nodes.get(hoveredNodeId ?? Number.MAX_SAFE_INTEGER);
let treeCollapseItems: ReactNode[] = [];
if (hoveredNode) { if (hoveredNode) {
treeCollapseItems = [
<UIDebuggerMenuItem
key="expand-recursive"
text="Expand recursively"
icon={<MenuUnfoldOutlined />}
onClick={() => {
onExpandRecursively(hoveredNode.id);
onSelectNode(hoveredNode.id, 'context-menu');
tracker.track('context-menu-expand-recursive', {});
}}
/>,
<UIDebuggerMenuItem
key="collapse-recursive"
text="Collapse recurisvely"
icon={<MenuFoldOutlined />}
onClick={() => {
onCollapseRecursively(hoveredNode.id);
onSelectNode(hoveredNode.id, 'context-menu');
tracker.track('context-menu-collapse-recursive', {});
}}
/>,
<UIDebuggerMenuItem
key="collapse-non-ancestors"
text="Collapse non ancestors"
icon={<NodeExpandOutlined />}
onClick={() => {
onCollapseNonAncestors(hoveredNode.id);
onSelectNode(hoveredNode.id, 'context-menu');
tracker.track('context-menu-collapse-non-ancestors', {});
}}
/>,
<Menu.Divider key="expand-divider" />,
];
copyItems.push( copyItems.push(
<UIDebuggerMenuItem <UIDebuggerMenuItem
key="Copy Element name" key="Copy Element name"
@@ -131,6 +179,7 @@ export const ContextMenu: React.FC<{
}} }}
overlay={() => ( overlay={() => (
<Menu> <Menu>
{treeCollapseItems}
{focus} {focus}
{removeFocus} {removeFocus}
{frameworkEventsTable} {frameworkEventsTable}

View File

@@ -211,9 +211,13 @@ export function Tree2({
focusedNodeId={focusedNode} focusedNodeId={focusedNode}
hoveredNodeId={hoveredNode} hoveredNodeId={hoveredNode}
nodes={nodes} nodes={nodes}
onSelectNode={instance.uiActions.onSelectNode}
onSetViewMode={instance.uiActions.onSetViewMode} onSetViewMode={instance.uiActions.onSetViewMode}
onContextMenuOpen={instance.uiActions.onContextMenuOpen} onContextMenuOpen={instance.uiActions.onContextMenuOpen}
onFocusNode={instance.uiActions.onFocusNode}> onFocusNode={instance.uiActions.onFocusNode}
onCollapseNonAncestors={instance.uiActions.onCollapseAllNonAncestors}
onCollapseRecursively={instance.uiActions.onCollapseAllRecursively}
onExpandRecursively={instance.uiActions.onExpandAllRecursively}>
<div <div
//We use this normal divs flexbox sizing to measure how much vertical space we need for the child div //We use this normal divs flexbox sizing to measure how much vertical space we need for the child div
ref={grandParentRef} ref={grandParentRef}

View File

@@ -130,6 +130,10 @@ export const Visualization2D: React.FC<
const onClickOverlay = () => { const onClickOverlay = () => {
instance.uiActions.onSelectNode(hoveredNodeId, 'visualiser'); instance.uiActions.onSelectNode(hoveredNodeId, 'visualiser');
if (hoveredNodeId != null) {
instance.uiActions.onCollapseAllNonAncestors(hoveredNodeId);
}
if (targetMode.state !== 'disabled') { if (targetMode.state !== 'disabled') {
setTargetMode({ setTargetMode({
state: 'selected', state: 'selected',

View File

@@ -36,7 +36,10 @@ export function uiActions(
}); });
}; };
const onSelectNode = (node: Id | undefined, source: SelectionSource) => { 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); uiState.selectedNode.set(undefined);
} else { } else {
uiState.selectedNode.set({id: node, source}); uiState.selectedNode.set({id: node, source});
@@ -83,6 +86,52 @@ export function uiActions(
uiState.isContextMenuOpen.set(open); 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) => { const onFocusNode = (node?: Id) => {
if (node != null) { if (node != null) {
const focusedNode = nodes.get().get(node); const focusedNode = nodes.get().get(node);
@@ -164,5 +213,8 @@ export function uiActions(
onPlayPauseToggled, onPlayPauseToggled,
onSearchTermUpdated, onSearchTermUpdated,
onSetWireFrameMode, onSetWireFrameMode,
onCollapseAllNonAncestors,
onExpandAllRecursively,
onCollapseAllRecursively,
}; };
} }

View File

@@ -56,6 +56,9 @@ type TrackerEvents = {
on: boolean; on: boolean;
}; };
'target-mode-adjusted': {}; 'target-mode-adjusted': {};
'context-menu-expand-recursive': {};
'context-menu-collapse-recursive': {};
'context-menu-collapse-non-ancestors': {};
}; };
export interface Tracker { export interface Tracker {