Remove react complex tree
Reviewed By: lblasa Differential Revision: D41875029 fbshipit-source-id: 2af58610fe0d0f644aa8450a4210fd52f8ed4db6
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ed35623bef
commit
74247ee721
@@ -1,306 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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,
|
|
||||||
styled,
|
|
||||||
} 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<Id, UINode>;
|
|
||||||
selectedNode?: Id;
|
|
||||||
onSelectNode: (id: Id) => void;
|
|
||||||
}) {
|
|
||||||
const instance = usePlugin(plugin);
|
|
||||||
const expandedItems = useValue(instance.uiState.expandedNodes);
|
|
||||||
const focused = useValue(instance.uiState.focusedNode);
|
|
||||||
|
|
||||||
const items = useMemo(
|
|
||||||
() => toComplexTree(focused || props.rootId, props.nodes),
|
|
||||||
[focused, props.nodes, props.rootId],
|
|
||||||
);
|
|
||||||
const hoveredNodes = useValue(instance.uiState.hoveredNodes);
|
|
||||||
const treeEnvRef = useRef<TreeEnvironmentRef>();
|
|
||||||
|
|
||||||
const searchTerm = useValue(instance.uiState.searchTerm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//this makes the keyboard arrow controls work always, even when using the visualiser
|
|
||||||
treeEnvRef.current?.focusTree('tree', true);
|
|
||||||
}, [props.selectedNode]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onMouseLeave={() => {
|
|
||||||
instance.uiState.hoveredNodes.set([]);
|
|
||||||
}}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
'--rct-color-tree-bg': theme.white,
|
|
||||||
'--rct-color-tree-focus-outline': theme.dividerColor,
|
|
||||||
'--rct-color-focustree-item-focused-border':
|
|
||||||
theme.selectionBackgroundColor,
|
|
||||||
'--rct-color-focustree-item-selected-bg':
|
|
||||||
theme.selectionBackgroundColor,
|
|
||||||
'--rct-color-nonfocustree-item-selected-bg':
|
|
||||||
theme.selectionBackgroundColor,
|
|
||||||
} as React.CSSProperties
|
|
||||||
}>
|
|
||||||
<HighlightProvider
|
|
||||||
text={searchTerm}
|
|
||||||
highlightColor={theme.searchHighlightBackground.yellow}>
|
|
||||||
<ControlledTreeEnvironment
|
|
||||||
ref={treeEnvRef as any}
|
|
||||||
items={items}
|
|
||||||
getItemTitle={(item) => item.data.name}
|
|
||||||
canRename={false}
|
|
||||||
canDragAndDrop={false}
|
|
||||||
viewState={{
|
|
||||||
tree: {
|
|
||||||
focusedItem: head(hoveredNodes),
|
|
||||||
expandedItems: [...expandedItems],
|
|
||||||
selectedItems: props.selectedNode ? [props.selectedNode] : [],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onFocusItem={(item) => {
|
|
||||||
instance.uiState.hoveredNodes.set([item.index]);
|
|
||||||
}}
|
|
||||||
onExpandItem={(item) => {
|
|
||||||
instance.uiState.expandedNodes.update((draft) => {
|
|
||||||
draft.add(item.index);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onCollapseItem={(item) =>
|
|
||||||
instance.uiState.expandedNodes.update((draft) => {
|
|
||||||
draft.delete(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.uiState.isContextMenuOpen.get()) {
|
|
||||||
instance.uiState.hoveredNodes.set([item.index]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}}>
|
|
||||||
<ComplexTree
|
|
||||||
treeId="tree"
|
|
||||||
rootItem={FakeNode.id as any} //the typing in in the library is wrong here
|
|
||||||
treeLabel="UI"
|
|
||||||
/>
|
|
||||||
</ControlledTreeEnvironment>
|
|
||||||
</HighlightProvider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//copied from https://github.com/lukasbach/react-complex-tree/blob/e3dcc435933284376a0fc6e3cc651e67ead678b5/packages/core/src/renderers/createDefaultRenderers.tsx
|
|
||||||
const cx = (...classNames: Array<string | undefined | false>) =>
|
|
||||||
classNames.filter((cn) => !!cn).join(' ');
|
|
||||||
const renderDepthOffset = 5;
|
|
||||||
|
|
||||||
const DecorationImage = styled.img({
|
|
||||||
height: 12,
|
|
||||||
marginRight: 5,
|
|
||||||
width: 12,
|
|
||||||
});
|
|
||||||
function defaultIcon(node: UINode) {
|
|
||||||
if (node.tags.includes('Litho')) {
|
|
||||||
return <DecorationImage src="icons/litho-logo.png" />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderItem<C extends string = never>({
|
|
||||||
item,
|
|
||||||
depth,
|
|
||||||
children,
|
|
||||||
arrow,
|
|
||||||
context,
|
|
||||||
}: {
|
|
||||||
item: TreeItem<UINode>;
|
|
||||||
depth: number;
|
|
||||||
children: React.ReactNode | null;
|
|
||||||
title: React.ReactNode;
|
|
||||||
arrow: React.ReactNode;
|
|
||||||
context: TreeItemRenderContext<C>;
|
|
||||||
info: TreeInformation;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
{...(context.itemContainerWithChildrenProps as any)}
|
|
||||||
className={cx(
|
|
||||||
'rct-tree-item-li',
|
|
||||||
item.hasChildren && 'rct-tree-item-li-hasChildren',
|
|
||||||
context.isSelected && 'rct-tree-item-li-selected',
|
|
||||||
context.isExpanded && 'rct-tree-item-li-expanded',
|
|
||||||
context.isFocused && 'rct-tree-item-li-focused',
|
|
||||||
context.isDraggingOver && 'rct-tree-item-li-dragging-over',
|
|
||||||
context.isSearchMatching && 'rct-tree-item-li-search-match',
|
|
||||||
)}>
|
|
||||||
<ContextMenu node={item.data} id={item.index} title={item.data.name}>
|
|
||||||
<div
|
|
||||||
{...(context.itemContainerWithoutChildrenProps as any)}
|
|
||||||
style={{
|
|
||||||
paddingLeft: `${(depth + 1) * renderDepthOffset}px`,
|
|
||||||
}}
|
|
||||||
className={cx(
|
|
||||||
'rct-tree-item-title-container',
|
|
||||||
item.hasChildren && 'rct-tree-item-title-container-hasChildren',
|
|
||||||
context.isSelected && 'rct-tree-item-title-container-selected',
|
|
||||||
context.isExpanded && 'rct-tree-item-title-container-expanded',
|
|
||||||
context.isFocused && 'rct-tree-item-title-container-focused',
|
|
||||||
context.isDraggingOver &&
|
|
||||||
'rct-tree-item-title-container-dragging-over',
|
|
||||||
context.isSearchMatching &&
|
|
||||||
'rct-tree-item-title-container-search-match',
|
|
||||||
)}>
|
|
||||||
{arrow}
|
|
||||||
<div
|
|
||||||
{...(context.interactiveElementProps as any)}
|
|
||||||
className={cx(
|
|
||||||
'rct-tree-item-button',
|
|
||||||
item.hasChildren && 'rct-tree-item-button-hasChildren',
|
|
||||||
context.isSelected && 'rct-tree-item-button-selected',
|
|
||||||
context.isExpanded && 'rct-tree-item-button-expanded',
|
|
||||||
context.isFocused && 'rct-tree-item-button-focused',
|
|
||||||
context.isDraggingOver && 'rct-tree-item-button-dragging-over',
|
|
||||||
context.isSearchMatching && 'rct-tree-item-button-search-match',
|
|
||||||
)}>
|
|
||||||
{defaultIcon(item.data)}
|
|
||||||
<HighlightedText text={item.data.name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ContextMenu>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContextMenuProps = {node: UINode; id: Id; title: string};
|
|
||||||
|
|
||||||
const ContextMenu: React.FC<ContextMenuProps> = ({id, title, children}) => {
|
|
||||||
const instance = usePlugin(plugin);
|
|
||||||
const focusedNode = instance.uiState.focusedNode.get();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
onVisibleChange={(visible) => {
|
|
||||||
instance.uiState.isContextMenuOpen.set(visible);
|
|
||||||
}}
|
|
||||||
overlay={() => (
|
|
||||||
<Menu>
|
|
||||||
{focusedNode !== head(instance.uiState.hoveredNodes.get()) && (
|
|
||||||
<UIDebuggerMenuItem
|
|
||||||
key="focus"
|
|
||||||
text={`Focus ${title}`}
|
|
||||||
onClick={() => {
|
|
||||||
instance.uiState.focusedNode.set(id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{focusedNode && (
|
|
||||||
<UIDebuggerMenuItem
|
|
||||||
key="remove-focus"
|
|
||||||
text="Remove focus"
|
|
||||||
onClick={() => {
|
|
||||||
instance.uiState.focusedNode.set(undefined);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
trigger={['contextMenu']}>
|
|
||||||
<div>{children}</div>
|
|
||||||
</Dropdown>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function HighlightedText(props: {text: string}) {
|
|
||||||
const highlightManager: HighlightManager = useHighlighter();
|
|
||||||
return <span>{highlightManager.render(props.text)}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FakeNode: UINode = {
|
|
||||||
id: 'Fakeroot',
|
|
||||||
qualifiedName: 'Fakeroot',
|
|
||||||
name: 'Fakeroot',
|
|
||||||
inlineAttributes: {},
|
|
||||||
children: [],
|
|
||||||
attributes: {},
|
|
||||||
bounds: {x: 0, y: 0, height: 0, width: 0},
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function toComplexTree(
|
|
||||||
root: Id,
|
|
||||||
nodes: Map<Id, UINode>,
|
|
||||||
): Record<Id, TreeItem<UINode>> {
|
|
||||||
const res: Record<Id, TreeItem<UINode>> = {};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -13,12 +13,11 @@ import {DetailSidebar, Layout, usePlugin, useValue} from 'flipper-plugin';
|
|||||||
import {useHotkeys} from 'react-hotkeys-hook';
|
import {useHotkeys} from 'react-hotkeys-hook';
|
||||||
import {Id, Metadata, MetadataId, UINode} from '../types';
|
import {Id, Metadata, MetadataId, UINode} from '../types';
|
||||||
import {PerfStats} from './PerfStats';
|
import {PerfStats} from './PerfStats';
|
||||||
import {Tree} from './Tree';
|
|
||||||
import {Visualization2D} from './Visualization2D';
|
import {Visualization2D} from './Visualization2D';
|
||||||
import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers';
|
import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers';
|
||||||
import {Inspector} from './sidebar/Inspector';
|
import {Inspector} from './sidebar/Inspector';
|
||||||
import {Controls} from './Controls';
|
import {Controls} from './Controls';
|
||||||
import {Input, Spin} from 'antd';
|
import {Spin} from 'antd';
|
||||||
import FeedbackRequest from './fb-stubs/feedback';
|
import FeedbackRequest from './fb-stubs/feedback';
|
||||||
import {Tree2} from './Tree2';
|
import {Tree2} from './Tree2';
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
Snapshot,
|
Snapshot,
|
||||||
UINode,
|
UINode,
|
||||||
} from './types';
|
} from './types';
|
||||||
import './node_modules/react-complex-tree/lib/style.css';
|
|
||||||
import {Draft} from 'immer';
|
import {Draft} from 'immer';
|
||||||
|
|
||||||
type SnapshotInfo = {nodeId: Id; base64Image: Snapshot};
|
type SnapshotInfo = {nodeId: Id; base64Image: Snapshot};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-complex-tree" : "^1.1.11",
|
|
||||||
"react-hotkeys-hook": "^3.4.7"
|
"react-hotkeys-hook": "^3.4.7"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {TreeItemIndex} from 'react-complex-tree';
|
|
||||||
|
|
||||||
export type Events = {
|
export type Events = {
|
||||||
init: InitEvent;
|
init: InitEvent;
|
||||||
subtreeUpdate: SubtreeUpdateEvent;
|
subtreeUpdate: SubtreeUpdateEvent;
|
||||||
@@ -121,7 +119,7 @@ export type Color = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type Snapshot = string;
|
export type Snapshot = string;
|
||||||
export type Id = number | TreeItemIndex;
|
export type Id = number;
|
||||||
|
|
||||||
export type MetadataId = number;
|
export type MetadataId = number;
|
||||||
export type TreeState = {expandedNodes: Id[]};
|
export type TreeState = {expandedNodes: Id[]};
|
||||||
|
|||||||
@@ -1644,11 +1644,6 @@ react-color@^2.19.3:
|
|||||||
reactcss "^1.2.0"
|
reactcss "^1.2.0"
|
||||||
tinycolor2 "^1.4.1"
|
tinycolor2 "^1.4.1"
|
||||||
|
|
||||||
react-complex-tree@^1.1.11:
|
|
||||||
version "1.1.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-complex-tree/-/react-complex-tree-1.1.11.tgz#430520d12908b033a4b278be0dfd8d0aa6654a85"
|
|
||||||
integrity sha512-hAkm2ZRH2lwZd7NEzZMQI8db/jI5T2fJsbwHX8oNPrG/WPdakc3eNpm2A4gLk2SBa88HeU6mnauVXg6Q6fJLow==
|
|
||||||
|
|
||||||
react-devtools-core@^4.26.1:
|
react-devtools-core@^4.26.1:
|
||||||
version "4.26.1"
|
version "4.26.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.1.tgz#2893fea58089be64c5356d5bd0eebda8d1bbf317"
|
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.1.tgz#2893fea58089be64c5356d5bd0eebda8d1bbf317"
|
||||||
|
|||||||
Reference in New Issue
Block a user