Add context menu to visualizer
Summary: Added context menu to visualizer similar to what we have on the tree Reviewed By: lblasa Differential Revision: D41494948 fbshipit-source-id: 0cfa4c98b7a68462a7103ed1ce9eaaff8c99aeee
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f78899b69f
commit
ca67bfd916
@@ -13,6 +13,7 @@ import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types';
|
|||||||
import {produce, 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 {head, isEqual, throttle} from 'lodash';
|
import {head, isEqual, throttle} from 'lodash';
|
||||||
|
import {Dropdown, Menu} from 'antd';
|
||||||
|
|
||||||
export const Visualization2D: React.FC<
|
export const Visualization2D: React.FC<
|
||||||
{
|
{
|
||||||
@@ -37,7 +38,8 @@ export const Visualization2D: React.FC<
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mouseListener = throttle((ev: MouseEvent) => {
|
const mouseListener = throttle((ev: MouseEvent) => {
|
||||||
const domRect = rootNodeRef.current?.getBoundingClientRect();
|
const domRect = rootNodeRef.current?.getBoundingClientRect();
|
||||||
if (!focusState || !domRect) {
|
|
||||||
|
if (!focusState || !domRect || instance.isContextMenuOpen.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rawMouse = {x: ev.clientX, y: ev.clientY};
|
const rawMouse = {x: ev.clientX, y: ev.clientY};
|
||||||
@@ -70,63 +72,68 @@ export const Visualization2D: React.FC<
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('mousemove', mouseListener);
|
window.removeEventListener('mousemove', mouseListener);
|
||||||
};
|
};
|
||||||
}, [instance.hoveredNodes, focusState, nodes]);
|
}, [instance.hoveredNodes, focusState, nodes, instance.isContextMenuOpen]);
|
||||||
|
|
||||||
if (!focusState) {
|
if (!focusState) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshotNode = snapshot && nodes.get(snapshot.nodeId);
|
const snapshotNode = snapshot && nodes.get(snapshot.nodeId);
|
||||||
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
|
|
||||||
ref={rootNodeRef as any}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
instance.hoveredNodes.set([]);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
/**
|
|
||||||
* This relative position is so the rootNode visualization 2DNode and outer border has a non static element to
|
|
||||||
* position itself relative to.
|
|
||||||
*
|
|
||||||
* 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...
|
|
||||||
*
|
|
||||||
* When focused the global offset of the focussed node is used to offset and size this 'root' node
|
|
||||||
*/
|
|
||||||
position: 'relative',
|
|
||||||
|
|
||||||
marginLeft: toPx(focusState.focusedRootGlobalOffset.x),
|
return (
|
||||||
marginTop: toPx(focusState.focusedRootGlobalOffset.y),
|
<ContextMenu nodes={nodes}>
|
||||||
width: toPx(focusState.focusedRoot.bounds.width),
|
<div
|
||||||
height: toPx(focusState.focusedRoot.bounds.height),
|
//this div is to ensure that the size of the visualiser doesnt change when focusings on a subtree
|
||||||
overflow: 'hidden',
|
style={{
|
||||||
|
width: toPx(focusState.actualRoot.bounds.width),
|
||||||
|
height: toPx(focusState.actualRoot.bounds.height),
|
||||||
}}>
|
}}>
|
||||||
{snapshotNode && (
|
<div
|
||||||
<img
|
ref={rootNodeRef as any}
|
||||||
src={'data:image/png;base64,' + snapshot.base64Image}
|
onMouseLeave={(e) => {
|
||||||
style={{
|
e.stopPropagation();
|
||||||
marginLeft: toPx(-focusState.focusedRootGlobalOffset.x),
|
//the context menu triggers this callback but we dont want to remove hover effect
|
||||||
marginTop: toPx(-focusState.focusedRootGlobalOffset.y),
|
if (!instance.isContextMenuOpen.get()) {
|
||||||
width: toPx(snapshotNode.bounds.width),
|
instance.hoveredNodes.set([]);
|
||||||
height: toPx(snapshotNode.bounds.height),
|
}
|
||||||
}}
|
}}
|
||||||
|
style={{
|
||||||
|
/**
|
||||||
|
* This relative position is so the rootNode visualization 2DNode and outer border has a non static element to
|
||||||
|
* position itself relative to.
|
||||||
|
*
|
||||||
|
* 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...
|
||||||
|
*
|
||||||
|
* When focused the global offset of the focussed node is used to offset and size this 'root' node
|
||||||
|
*/
|
||||||
|
position: 'relative',
|
||||||
|
marginLeft: toPx(focusState.focusedRootGlobalOffset.x),
|
||||||
|
marginTop: toPx(focusState.focusedRootGlobalOffset.y),
|
||||||
|
width: toPx(focusState.focusedRoot.bounds.width),
|
||||||
|
height: toPx(focusState.focusedRoot.bounds.height),
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
{snapshotNode && (
|
||||||
|
<img
|
||||||
|
src={'data:image/png;base64,' + snapshot.base64Image}
|
||||||
|
style={{
|
||||||
|
marginLeft: toPx(-focusState.focusedRootGlobalOffset.x),
|
||||||
|
marginTop: toPx(-focusState.focusedRootGlobalOffset.y),
|
||||||
|
width: toPx(snapshotNode.bounds.width),
|
||||||
|
height: toPx(snapshotNode.bounds.height),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MemoedVisualizationNode2D
|
||||||
|
node={focusState.focusedRoot}
|
||||||
|
selectedNode={selectedNode}
|
||||||
|
onSelectNode={onSelectNode}
|
||||||
|
modifierPressed={modifierPressed}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
<MemoedVisualizationNode2D
|
|
||||||
node={focusState.focusedRoot}
|
|
||||||
selectedNode={selectedNode}
|
|
||||||
onSelectNode={onSelectNode}
|
|
||||||
modifierPressed={modifierPressed}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -230,6 +237,52 @@ function Visualization2DNode({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
|
||||||
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
|
const focusedNodeId = useValue(instance.focusedNode);
|
||||||
|
const hoveredNodeId = head(useValue(instance.hoveredNodes));
|
||||||
|
const nodes = useValue(instance.nodes);
|
||||||
|
const hoveredNode = hoveredNodeId ? nodes.get(hoveredNodeId) : null;
|
||||||
|
const isMenuOpen = useValue(instance.isContextMenuOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
onVisibleChange={(open) => {
|
||||||
|
instance.isContextMenuOpen.set(open);
|
||||||
|
}}
|
||||||
|
trigger={['contextMenu']}
|
||||||
|
overlay={() => {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
{isMenuOpen && hoveredNode?.id !== focusedNodeId && (
|
||||||
|
<Menu.Item
|
||||||
|
key="focus"
|
||||||
|
onClick={() => {
|
||||||
|
instance.focusedNode.set(hoveredNode?.id);
|
||||||
|
instance.isContextMenuOpen.set(false);
|
||||||
|
}}>
|
||||||
|
Focus {hoveredNode?.name}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
{isMenuOpen && focusedNodeId != null && (
|
||||||
|
<Menu.Item
|
||||||
|
key="remove-focus"
|
||||||
|
onClick={() => {
|
||||||
|
instance.focusedNode.set(undefined);
|
||||||
|
instance.isContextMenuOpen.set(false);
|
||||||
|
}}>
|
||||||
|
Remove focus
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is the border that shows the green or blue line, it is implemented as a sibling to the
|
* this is the border that shows the green or blue line, it is implemented as a sibling to the
|
||||||
* node itself so that it has the same size but the border doesnt affect the sizing of its children
|
* node itself so that it has the same size but the border doesnt affect the sizing of its children
|
||||||
|
|||||||
Reference in New Issue
Block a user