2D wire frame highlight from tree, select from wireframe
Summary: Introduced some basic bidirectional link between tree and wireframe, the specific interaction will need some tweaking but this should get us started. When hovering over the tree we halt the rendering of the wireframe up to that point, this allows us to explore parent views that layout child views. When clicking a view in the wireframe it is 'seleceted' as if it was clicked in the tree. This set the tree selection so you can identify it in the tree as well as opens the side bar Reviewed By: lblasa Differential Revision: D39539277 fbshipit-source-id: 3beb1ad4cb56b398c640ac3e7fac2cc97f3f1a18
This commit is contained in:
committed by
Facebook GitHub Bot
parent
cf176bb071
commit
f3b7552338
@@ -9,23 +9,40 @@
|
|||||||
|
|
||||||
import {Id, UINode} from '../types';
|
import {Id, UINode} from '../types';
|
||||||
import {DataNode} from 'antd/es/tree';
|
import {DataNode} from 'antd/es/tree';
|
||||||
import {Tree as AntTree, Typography} from 'antd';
|
import {Tree as AntTree} from 'antd';
|
||||||
import {DownOutlined} from '@ant-design/icons';
|
import {DownOutlined} from '@ant-design/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export function Tree(props: {
|
export function Tree(props: {
|
||||||
rootId: Id;
|
rootId: Id;
|
||||||
nodes: Map<Id, UINode>;
|
nodes: Map<Id, UINode>;
|
||||||
setSelectedNode: (id: Id) => void;
|
selectedNode?: Id;
|
||||||
|
onSelectNode: (id: Id) => void;
|
||||||
|
onHoveredNode: (id?: Id) => void;
|
||||||
}) {
|
}) {
|
||||||
const [antTree, inactive] = nodesToAntTree(props.rootId, props.nodes);
|
const [antTree, inactive] = nodesToAntTree(props.rootId, props.nodes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AntTree
|
<AntTree
|
||||||
|
onMouseLeave={() => {
|
||||||
|
//when mouse exits the entire tree then unhover
|
||||||
|
props.onHoveredNode(undefined);
|
||||||
|
}}
|
||||||
showIcon
|
showIcon
|
||||||
showLine
|
showLine
|
||||||
|
titleRender={(node) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => {
|
||||||
|
props.onHoveredNode(node.key as Id);
|
||||||
|
}}>
|
||||||
|
{node.title}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
selectedKeys={[props.selectedNode ?? '']}
|
||||||
onSelect={(selected) => {
|
onSelect={(selected) => {
|
||||||
props.setSelectedNode(selected[0] as Id);
|
props.onSelectNode(selected[0] as Id);
|
||||||
}}
|
}}
|
||||||
defaultExpandAll
|
defaultExpandAll
|
||||||
expandedKeys={[...props.nodes.keys()].filter(
|
expandedKeys={[...props.nodes.keys()].filter(
|
||||||
|
|||||||
@@ -9,48 +9,52 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Bounds, Id, Tag, UINode} from '../types';
|
import {Bounds, Id, Tag, UINode} from '../types';
|
||||||
import {styled, Layout} from 'flipper-plugin';
|
import {styled, Layout, theme} from 'flipper-plugin';
|
||||||
import {Typography} from 'antd';
|
import {Typography} from 'antd';
|
||||||
|
|
||||||
export const Visualization2D: React.FC<
|
export const Visualization2D: React.FC<
|
||||||
{root: Id; nodes: Map<Id, UINode>} & React.HTMLAttributes<HTMLDivElement>
|
{
|
||||||
> = ({root, nodes}) => {
|
root: Id;
|
||||||
//
|
nodes: Map<Id, UINode>;
|
||||||
const bounds = nodes.get(root)?.bounds;
|
hoveredNode?: Id;
|
||||||
const rootBorderStyle = bounds
|
onSelectNode: (id: Id) => void;
|
||||||
? {
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
borderWidth: '3px',
|
> = ({root, nodes, hoveredNode, onSelectNode}) => {
|
||||||
margin: '-3px',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderColor: 'black',
|
|
||||||
width: bounds.width / 2,
|
|
||||||
height: bounds.height / 2,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
return (
|
return (
|
||||||
<Layout.Container gap="large">
|
<Layout.Container gap="large">
|
||||||
<Typography.Title>Visualizer</Typography.Title>
|
<Typography.Title>Visualizer</Typography.Title>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
//this sets the reference frame for the absolute positioning
|
//this sets the reference frame for the absolute positioning
|
||||||
//of the nodes
|
//of the individual absolutely positioned nodes
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
// ...rootBorderStyle,
|
|
||||||
}}>
|
}}>
|
||||||
<VisualizationNode isRoot nodeId={root} nodes={nodes} />;
|
<Visualization2DNode
|
||||||
|
isRoot
|
||||||
|
nodeId={root}
|
||||||
|
nodes={nodes}
|
||||||
|
hoveredNode={hoveredNode}
|
||||||
|
onSelectNode={onSelectNode}
|
||||||
|
/>
|
||||||
|
;
|
||||||
</div>
|
</div>
|
||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function VisualizationNode({
|
function Visualization2DNode({
|
||||||
nodeId,
|
nodeId,
|
||||||
nodes,
|
nodes,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
hoveredNode,
|
||||||
|
onSelectNode,
|
||||||
}: {
|
}: {
|
||||||
isRoot: boolean;
|
isRoot: boolean;
|
||||||
nodeId: Id;
|
nodeId: Id;
|
||||||
nodes: Map<Id, UINode>;
|
nodes: Map<Id, UINode>;
|
||||||
|
hoveredNode?: Id;
|
||||||
|
onSelectNode: (id: Id) => void;
|
||||||
}) {
|
}) {
|
||||||
const node = nodes.get(nodeId);
|
const node = nodes.get(nodeId);
|
||||||
|
|
||||||
@@ -58,19 +62,28 @@ function VisualizationNode({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let childrenIds = node.children;
|
const isHovered = hoveredNode === nodeId;
|
||||||
|
|
||||||
//if there is an active child dont draw the other children
|
let childrenIds: Id[] = [];
|
||||||
//this means we don't draw overlapping activities / tabs
|
|
||||||
if (node.activeChild) {
|
if (!isHovered) {
|
||||||
childrenIds = [node.activeChild];
|
//if there is an active child don't draw the other children
|
||||||
|
//this means we don't draw overlapping activities / tabs etc
|
||||||
|
if (node.activeChild) {
|
||||||
|
childrenIds = [node.activeChild];
|
||||||
|
} else {
|
||||||
|
childrenIds = node.children;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = childrenIds.map((childId) => (
|
const children = childrenIds.map((childId) => (
|
||||||
<VisualizationNode
|
<Visualization2DNode
|
||||||
isRoot={false}
|
isRoot={false}
|
||||||
key={childId}
|
key={childId}
|
||||||
nodeId={childId}
|
nodeId={childId}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
hoveredNode={hoveredNode}
|
||||||
|
onSelectNode={onSelectNode}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -81,7 +94,15 @@ function VisualizationNode({
|
|||||||
const isZeroWidthOrHeight =
|
const isZeroWidthOrHeight =
|
||||||
node.bounds?.height === 0 || node.bounds?.width === 0;
|
node.bounds?.height === 0 || node.bounds?.width === 0;
|
||||||
return (
|
return (
|
||||||
<BoundsBox bounds={node.bounds} isRoot={isRoot} tags={node.tags}>
|
<BoundsBox
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSelectNode(nodeId);
|
||||||
|
}}
|
||||||
|
bounds={node.bounds}
|
||||||
|
isRoot={isRoot}
|
||||||
|
tags={node.tags}
|
||||||
|
isHovered={isHovered}>
|
||||||
{/* Dirty hack to avoid showing highly overlapping text */}
|
{/* Dirty hack to avoid showing highly overlapping text */}
|
||||||
{!hasOverlappingChild && !isZeroWidthOrHeight && node.bounds
|
{!hasOverlappingChild && !isZeroWidthOrHeight && node.bounds
|
||||||
? node.name
|
? node.name
|
||||||
@@ -94,11 +115,13 @@ function VisualizationNode({
|
|||||||
const BoundsBox = styled.div<{
|
const BoundsBox = styled.div<{
|
||||||
bounds?: Bounds;
|
bounds?: Bounds;
|
||||||
isRoot: boolean;
|
isRoot: boolean;
|
||||||
|
isHovered: boolean;
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
}>((props) => {
|
}>((props) => {
|
||||||
const bounds = props.bounds ?? {x: 0, y: 0, width: 0, height: 0};
|
const bounds = props.bounds ?? {x: 0, y: 0, width: 0, height: 0};
|
||||||
return {
|
return {
|
||||||
// borderWidth: props.isRoot ? '5px' : '1px',
|
// borderWidth: props.isRoot ? '5px' : '1px',
|
||||||
|
cursor: 'pointer',
|
||||||
borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
//to offset the border
|
//to offset the border
|
||||||
margin: '-1px',
|
margin: '-1px',
|
||||||
@@ -109,6 +132,7 @@ const BoundsBox = styled.div<{
|
|||||||
: 'black',
|
: 'black',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
backgroundColor: props.isHovered ? theme.selectionBackgroundColor : 'white',
|
||||||
//todo need to understand why its so big and needs halving
|
//todo need to understand why its so big and needs halving
|
||||||
left: bounds.x / 2,
|
left: bounds.x / 2,
|
||||||
top: bounds.y / 2,
|
top: bounds.y / 2,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function Component() {
|
|||||||
|
|
||||||
const [showPerfStats, setShowPerfStats] = useState(false);
|
const [showPerfStats, setShowPerfStats] = useState(false);
|
||||||
const [selectedNode, setSelectedNode] = useState<Id | undefined>(undefined);
|
const [selectedNode, setSelectedNode] = useState<Id | undefined>(undefined);
|
||||||
|
const [hoveredNode, setHoveredNode] = useState<Id | undefined>(undefined);
|
||||||
|
|
||||||
useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show));
|
useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show));
|
||||||
|
|
||||||
@@ -58,11 +59,18 @@ export function Component() {
|
|||||||
<Layout.ScrollContainer>
|
<Layout.ScrollContainer>
|
||||||
<Layout.Horizontal>
|
<Layout.Horizontal>
|
||||||
<Tree
|
<Tree
|
||||||
setSelectedNode={setSelectedNode}
|
selectedNode={selectedNode}
|
||||||
|
onSelectNode={setSelectedNode}
|
||||||
|
onHoveredNode={setHoveredNode}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
rootId={rootId}
|
rootId={rootId}
|
||||||
/>
|
/>
|
||||||
<Visualization2D root={rootId} nodes={nodes} />
|
<Visualization2D
|
||||||
|
root={rootId}
|
||||||
|
nodes={nodes}
|
||||||
|
hoveredNode={hoveredNode}
|
||||||
|
onSelectNode={setSelectedNode}
|
||||||
|
/>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Layout.ScrollContainer>
|
</Layout.ScrollContainer>
|
||||||
{selectedNode && renderAttributesInspector(nodes.get(selectedNode))}
|
{selectedNode && renderAttributesInspector(nodes.get(selectedNode))}
|
||||||
|
|||||||
Reference in New Issue
Block a user