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 {Id, Metadata, MetadataId, UINode} from '../types';
|
||||
import {PerfStats} from './PerfStats';
|
||||
import {Tree} from './Tree';
|
||||
import {Visualization2D} from './Visualization2D';
|
||||
import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers';
|
||||
import {Inspector} from './sidebar/Inspector';
|
||||
import {Controls} from './Controls';
|
||||
import {Input, Spin} from 'antd';
|
||||
import {Spin} from 'antd';
|
||||
import FeedbackRequest from './fb-stubs/feedback';
|
||||
import {Tree2} from './Tree2';
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
Snapshot,
|
||||
UINode,
|
||||
} from './types';
|
||||
import './node_modules/react-complex-tree/lib/style.css';
|
||||
import {Draft} from 'immer';
|
||||
|
||||
type SnapshotInfo = {nodeId: Id; base64Image: Snapshot};
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"react-color": "^2.19.3",
|
||||
"react-complex-tree" : "^1.1.11",
|
||||
"react-hotkeys-hook": "^3.4.7"
|
||||
},
|
||||
"bugs": {
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {TreeItemIndex} from 'react-complex-tree';
|
||||
|
||||
export type Events = {
|
||||
init: InitEvent;
|
||||
subtreeUpdate: SubtreeUpdateEvent;
|
||||
@@ -121,7 +119,7 @@ export type Color = {
|
||||
};
|
||||
|
||||
export type Snapshot = string;
|
||||
export type Id = number | TreeItemIndex;
|
||||
export type Id = number;
|
||||
|
||||
export type MetadataId = number;
|
||||
export type TreeState = {expandedNodes: Id[]};
|
||||
|
||||
Reference in New Issue
Block a user