Added focus mode to vizualizer
Summary: Introduced an outer div which is the size of the real root node so that focusing doesnt shift the UI. Reviewed By: antonk52 Differential Revision: D41492999 fbshipit-source-id: 336104e5d18d773953e0a58a699acc7660c4045f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4b566dbe03
commit
8ae367dbf6
@@ -10,9 +10,9 @@
|
|||||||
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
||||||
import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types';
|
import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types';
|
||||||
|
|
||||||
import {styled, theme, usePlugin, useValue} from 'flipper-plugin';
|
import {produce, styled, theme, usePlugin, useValue} from 'flipper-plugin';
|
||||||
import {plugin} from '../index';
|
import {plugin} from '../index';
|
||||||
import {throttle, isEqual, head} from 'lodash';
|
import {head, isEqual, throttle} from 'lodash';
|
||||||
|
|
||||||
export const Visualization2D: React.FC<
|
export const Visualization2D: React.FC<
|
||||||
{
|
{
|
||||||
@@ -23,15 +23,21 @@ export const Visualization2D: React.FC<
|
|||||||
modifierPressed: boolean;
|
modifierPressed: boolean;
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
> = ({rootId, nodes, selectedNode, onSelectNode, modifierPressed}) => {
|
> = ({rootId, nodes, selectedNode, onSelectNode, modifierPressed}) => {
|
||||||
const root = useMemo(() => toNestedNode(rootId, nodes), [rootId, nodes]);
|
|
||||||
const rootNodeRef = useRef<HTMLDivElement>();
|
const rootNodeRef = useRef<HTMLDivElement>();
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
const snapshot = useValue(instance.snapshot);
|
const snapshot = useValue(instance.snapshot);
|
||||||
|
const focusedNodeId = useValue(instance.focusedNode);
|
||||||
|
|
||||||
|
const focusState = useMemo(() => {
|
||||||
|
const rootNode = toNestedNode(rootId, nodes);
|
||||||
|
return rootNode && caclulateFocusState(rootNode, focusedNodeId);
|
||||||
|
}, [focusedNodeId, rootId, nodes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mouseListener = throttle((ev: MouseEvent) => {
|
const mouseListener = throttle((ev: MouseEvent) => {
|
||||||
const domRect = rootNodeRef.current?.getBoundingClientRect();
|
const domRect = rootNodeRef.current?.getBoundingClientRect();
|
||||||
if (!root || !domRect) {
|
if (!focusState || !domRect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +51,9 @@ export const Visualization2D: React.FC<
|
|||||||
y: offsetMouse.y * pxScaleFactor,
|
y: offsetMouse.y * pxScaleFactor,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hitNodes = hitTest(root, scaledMouse).map((node) => node.id);
|
const hitNodes = hitTest(focusState.focusedRoot, scaledMouse).map(
|
||||||
|
(node) => node.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hitNodes.length > 0 &&
|
hitNodes.length > 0 &&
|
||||||
@@ -59,14 +67,20 @@ export const Visualization2D: React.FC<
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('mousemove', mouseListener);
|
window.removeEventListener('mousemove', mouseListener);
|
||||||
};
|
};
|
||||||
}, [instance.hoveredNodes, root]);
|
}, [instance.hoveredNodes, focusState, nodes]);
|
||||||
|
|
||||||
if (!root) {
|
if (!focusState) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshotNode = snapshot && nodes.get(snapshot.nodeId);
|
const snapshotNode = snapshot && nodes.get(snapshot.nodeId);
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
//this div is to ensure that the size of the visualiser doesnt change when focusings on a subtree
|
||||||
|
style={{
|
||||||
|
width: toPx(focusState.actualRoot.bounds.width),
|
||||||
|
height: toPx(focusState.actualRoot.bounds.height),
|
||||||
|
}}>
|
||||||
<div
|
<div
|
||||||
ref={rootNodeRef as any}
|
ref={rootNodeRef as any}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
@@ -75,34 +89,41 @@ export const Visualization2D: React.FC<
|
|||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
/**
|
/**
|
||||||
* This relative position is so the root visualization 2DNode and outer border has a non static element to
|
* This relative position is so the rootNode visualization 2DNode and outer border has a non static element to
|
||||||
* position itself relative to.
|
* position itself relative to.
|
||||||
*
|
*
|
||||||
* Subsequent Visualization2DNode are positioned relative to their parent as each one is position absolute
|
* Subsequent Visualization2DNode are positioned relative to their parent as each one is position absolute
|
||||||
* which despite the name acts are a reference point for absolute positioning...
|
* which despite the name acts are a reference point for absolute positioning...
|
||||||
|
*
|
||||||
|
* When focused the global offset of the focussed node is used to offset and size this 'root' node
|
||||||
*/
|
*/
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: toPx(root.bounds.width),
|
|
||||||
height: toPx(root.bounds.height),
|
marginLeft: toPx(focusState.focusedRootGlobalOffset.x),
|
||||||
|
marginTop: toPx(focusState.focusedRootGlobalOffset.y),
|
||||||
|
width: toPx(focusState.focusedRoot.bounds.width),
|
||||||
|
height: toPx(focusState.focusedRoot.bounds.height),
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
{snapshot && snapshotNode && (
|
{snapshotNode && (
|
||||||
<img
|
<img
|
||||||
src={'data:image/png;base64,' + snapshot.base64Image}
|
src={'data:image/png;base64,' + snapshot.base64Image}
|
||||||
style={{
|
style={{
|
||||||
|
marginLeft: toPx(-focusState.focusedRootGlobalOffset.x),
|
||||||
|
marginTop: toPx(-focusState.focusedRootGlobalOffset.y),
|
||||||
width: toPx(snapshotNode.bounds.width),
|
width: toPx(snapshotNode.bounds.width),
|
||||||
height: toPx(snapshotNode.bounds.height),
|
height: toPx(snapshotNode.bounds.height),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<OuterBorder />
|
|
||||||
<MemoedVisualizationNode2D
|
<MemoedVisualizationNode2D
|
||||||
node={root}
|
node={focusState.focusedRoot}
|
||||||
selectedNode={selectedNode}
|
selectedNode={selectedNode}
|
||||||
onSelectNode={onSelectNode}
|
onSelectNode={onSelectNode}
|
||||||
modifierPressed={modifierPressed}
|
modifierPressed={modifierPressed}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -279,6 +300,59 @@ function toNestedNode(
|
|||||||
return root ? uiNodeToNestedNode(root) : undefined;
|
return root ? uiNodeToNestedNode(root) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FocusState = {
|
||||||
|
actualRoot: NestedNode;
|
||||||
|
focusedRoot: NestedNode;
|
||||||
|
focusedRootGlobalOffset: Coordinate;
|
||||||
|
};
|
||||||
|
|
||||||
|
function caclulateFocusState(root: NestedNode, target?: Id): FocusState {
|
||||||
|
const rootFocusState = {
|
||||||
|
actualRoot: root,
|
||||||
|
focusedRoot: root,
|
||||||
|
focusedRootGlobalOffset: {x: 0, y: 0},
|
||||||
|
};
|
||||||
|
if (target == null) {
|
||||||
|
return rootFocusState;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
findNodeAndGlobalOffsetRec(root, {x: 0, y: 0}, root, target) ||
|
||||||
|
rootFocusState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNodeAndGlobalOffsetRec(
|
||||||
|
node: NestedNode,
|
||||||
|
globalOffset: Coordinate,
|
||||||
|
root: NestedNode,
|
||||||
|
target: Id,
|
||||||
|
): FocusState | undefined {
|
||||||
|
const nextOffset = {
|
||||||
|
x: globalOffset.x + node.bounds.x,
|
||||||
|
y: globalOffset.y + node.bounds.y,
|
||||||
|
};
|
||||||
|
if (node.id === target) {
|
||||||
|
//since we have already applied the this nodes offset to the root node in the visualiser we zero it out here so it isn't counted twice
|
||||||
|
const focusedRoot = produce(node, (draft) => {
|
||||||
|
draft.bounds.x = 0;
|
||||||
|
draft.bounds.y = 0;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
actualRoot: root,
|
||||||
|
focusedRoot,
|
||||||
|
focusedRootGlobalOffset: nextOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const child of node.children) {
|
||||||
|
const offset = findNodeAndGlobalOffsetRec(child, nextOffset, root, target);
|
||||||
|
if (offset != null) {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function hitTest(node: NestedNode, mouseCoordinate: Coordinate): NestedNode[] {
|
function hitTest(node: NestedNode, mouseCoordinate: Coordinate): NestedNode[] {
|
||||||
const res: NestedNode[] = [];
|
const res: NestedNode[] = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user