Add ability to focus on a node to tree
Summary: Added context menu to tree nodes that 'focuses' a node. This will make the node the root of the tree. Focus state can be removed again via context menu but we could add a permanent button in the future Reviewed By: lblasa Differential Revision: D41493002 fbshipit-source-id: 43ec7a25aeea0b169cbcbb1ac20ac22ea893fee2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
01f7fa34e5
commit
4b566dbe03
@@ -25,12 +25,12 @@ import {
|
|||||||
useValue,
|
useValue,
|
||||||
HighlightManager,
|
HighlightManager,
|
||||||
HighlightProvider,
|
HighlightProvider,
|
||||||
HighlightContext,
|
|
||||||
useHighlighter,
|
useHighlighter,
|
||||||
theme,
|
theme,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
|
|
||||||
import {head} from 'lodash';
|
import {head} from 'lodash';
|
||||||
|
import {Dropdown, Menu} from 'antd';
|
||||||
|
|
||||||
export function Tree(props: {
|
export function Tree(props: {
|
||||||
rootId: Id;
|
rootId: Id;
|
||||||
@@ -40,7 +40,12 @@ export function Tree(props: {
|
|||||||
}) {
|
}) {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const expandedItems = useValue(instance.treeState).expandedNodes;
|
const expandedItems = useValue(instance.treeState).expandedNodes;
|
||||||
const items = useMemo(() => toComplexTree(props.nodes), [props.nodes]);
|
const focused = useValue(instance.focusedNode);
|
||||||
|
|
||||||
|
const items = useMemo(
|
||||||
|
() => toComplexTree(focused || props.rootId, props.nodes),
|
||||||
|
[focused, props.nodes, props.rootId],
|
||||||
|
);
|
||||||
const hoveredNodes = useValue(instance.hoveredNodes);
|
const hoveredNodes = useValue(instance.hoveredNodes);
|
||||||
const treeEnvRef = useRef<TreeEnvironmentRef>();
|
const treeEnvRef = useRef<TreeEnvironmentRef>();
|
||||||
|
|
||||||
@@ -109,7 +114,7 @@ export function Tree(props: {
|
|||||||
}}>
|
}}>
|
||||||
<ComplexTree
|
<ComplexTree
|
||||||
treeId="tree"
|
treeId="tree"
|
||||||
rootItem={props.rootId as any} //the typing in in the library is wrong here
|
rootItem={FakeNode.id as any} //the typing in in the library is wrong here
|
||||||
treeLabel="UI"
|
treeLabel="UI"
|
||||||
/>
|
/>
|
||||||
</ControlledTreeEnvironment>
|
</ControlledTreeEnvironment>
|
||||||
@@ -149,6 +154,7 @@ function renderItem<C extends string = never>({
|
|||||||
context.isDraggingOver && 'rct-tree-item-li-dragging-over',
|
context.isDraggingOver && 'rct-tree-item-li-dragging-over',
|
||||||
context.isSearchMatching && 'rct-tree-item-li-search-match',
|
context.isSearchMatching && 'rct-tree-item-li-search-match',
|
||||||
)}>
|
)}>
|
||||||
|
<ContextMenu node={item.data} id={item.index} title={item.data.name}>
|
||||||
<div
|
<div
|
||||||
{...(context.itemContainerWithoutChildrenProps as any)}
|
{...(context.itemContainerWithoutChildrenProps as any)}
|
||||||
style={{
|
style={{
|
||||||
@@ -180,27 +186,84 @@ function renderItem<C extends string = never>({
|
|||||||
<HighlightedText text={item.data.name} />
|
<HighlightedText text={item.data.name} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ContextMenu>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContextMenuProps = {node: UINode; id: Id; title: string};
|
||||||
|
|
||||||
|
const ContextMenu: React.FC<ContextMenuProps> = ({id, title, children}) => {
|
||||||
|
const instance = usePlugin(plugin);
|
||||||
|
const focusedNode = instance.focusedNode.get();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
overlay={() => (
|
||||||
|
<Menu>
|
||||||
|
{focusedNode !== head(instance.hoveredNodes.get()) && (
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
instance.focusedNode.set(id);
|
||||||
|
}}>
|
||||||
|
Focus {title}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{focusedNode && (
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
instance.focusedNode.set(undefined);
|
||||||
|
}}>
|
||||||
|
Remove focus
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
trigger={['contextMenu']}>
|
||||||
|
<div>{children}</div>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function HighlightedText(props: {text: string}) {
|
function HighlightedText(props: {text: string}) {
|
||||||
const highlightManager: HighlightManager = useHighlighter();
|
const highlightManager: HighlightManager = useHighlighter();
|
||||||
return <span>{highlightManager.render(props.text)}</span>;
|
return <span>{highlightManager.render(props.text)}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toComplexTree(nodes: Map<Id, UINode>): Record<Id, TreeItem<UINode>> {
|
const FakeNode: UINode = {
|
||||||
|
id: 'Fakeroot',
|
||||||
|
qualifiedName: 'Fakeroot',
|
||||||
|
name: 'Fakeroot',
|
||||||
|
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>> = {};
|
const res: Record<Id, TreeItem<UINode>> = {};
|
||||||
for (const node of nodes.values()) {
|
for (const node of nodes.values()) {
|
||||||
res[node.id] = {
|
res[node.id] = {
|
||||||
index: node.id,
|
index: node.id,
|
||||||
canMove: false,
|
|
||||||
canRename: false,
|
|
||||||
children: node.children,
|
children: node.children,
|
||||||
data: node,
|
data: node,
|
||||||
hasChildren: node.children.length > 0,
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
perfEvents.append(event);
|
perfEvents.append(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const focusedNode = createState<Id | undefined>(undefined);
|
||||||
|
|
||||||
const nodes = createState<Map<Id, UINode>>(new Map());
|
const nodes = createState<Map<Id, UINode>>(new Map());
|
||||||
const snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>(
|
const snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>(
|
||||||
null,
|
null,
|
||||||
@@ -108,6 +110,7 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
rootId,
|
rootId,
|
||||||
nodes,
|
nodes,
|
||||||
metadata,
|
metadata,
|
||||||
|
focusedNode,
|
||||||
snapshot,
|
snapshot,
|
||||||
hoveredNodes,
|
hoveredNodes,
|
||||||
perfEvents,
|
perfEvents,
|
||||||
|
|||||||
Reference in New Issue
Block a user