Improve presentation of node name in hover state for visualizer

Summary: With the previous approach it was hard to read the name of the node, this should be a lot clearer. The tool top comes in a after a delay, the reason for this is because when i tried without a delay when you moved the mouse fast a ton of tooltips would appear and disappear when you passed over diffferent nodes and it was very distracting. 200ms seems to be about the sweet spot

Reviewed By: lblasa

Differential Revision: D41548248

fbshipit-source-id: 76460347730d5b1d2e968984e845be0c65255456
This commit is contained in:
Luke De Feo
2022-11-28 05:09:20 -08:00
committed by Facebook GitHub Bot
parent d9314c3b73
commit 6183671a5d

View File

@@ -13,7 +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'; import {Dropdown, Menu, Tooltip} from 'antd';
import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem';
export const Visualization2D: React.FC< export const Visualization2D: React.FC<
@@ -167,20 +167,8 @@ function Visualization2DNode({
}) { }) {
const instance = usePlugin(plugin); const instance = usePlugin(plugin);
const [isHovered, setIsHovered] = useState(false);
useEffect(() => {
const listener = (newValue?: Id[], prevValue?: Id[]) => {
if (head(prevValue) === node.id || head(newValue) === node.id) {
setIsHovered(head(newValue) === node.id);
}
};
instance.uiState.hoveredNodes.subscribe(listener);
return () => {
instance.uiState.hoveredNodes.unsubscribe(listener);
};
}, [instance.uiState.hoveredNodes, node.id]);
const isSelected = selectedNode === node.id; const isSelected = selectedNode === node.id;
const {isHovered, isLongHovered} = useHoverStates(node.id);
let nestedChildren: NestedNode[]; let nestedChildren: NestedNode[];
@@ -208,41 +196,85 @@ function Visualization2DNode({
/> />
)); ));
const bounds = node.bounds ?? {x: 0, y: 0, width: 0, height: 0};
return ( return (
<div <Tooltip
role="button" visible={isLongHovered}
tabIndex={0} placement="top"
style={{ zIndex={100}
position: 'absolute', trigger={[]}
cursor: 'pointer', title={node.name}
left: toPx(bounds.x), align={{
top: toPx(bounds.y), offset: [0, 7],
width: toPx(bounds.width),
height: toPx(bounds.height),
opacity: isSelected ? 0.5 : 1,
backgroundColor: isSelected
? theme.selectionBackgroundColor
: 'transparent',
}}
onClick={(e) => {
e.stopPropagation();
const hoveredNodes = instance.uiState.hoveredNodes.get();
if (hoveredNodes[0] === selectedNode) {
onSelectNode(undefined);
} else {
onSelectNode(hoveredNodes[0]);
}
}}> }}>
<NodeBorder hovered={isHovered} tags={node.tags}></NodeBorder> <div
{isHovered && <p style={{float: 'right'}}>{node.name}</p>} role="button"
{children} tabIndex={0}
</div> style={{
position: 'absolute',
cursor: 'pointer',
left: toPx(node.bounds.x),
top: toPx(node.bounds.y),
width: toPx(node.bounds.width),
height: toPx(node.bounds.height),
opacity: isSelected ? 0.5 : 1,
backgroundColor: isSelected
? theme.selectionBackgroundColor
: 'transparent',
}}
onClick={(e) => {
e.stopPropagation();
const hoveredNodes = instance.uiState.hoveredNodes.get();
if (hoveredNodes[0] === selectedNode) {
onSelectNode(undefined);
} else {
onSelectNode(hoveredNodes[0]);
}
}}>
<NodeBorder hovered={isHovered} tags={node.tags}></NodeBorder>
{children}
</div>
</Tooltip>
); );
} }
function useHoverStates(nodeId: Id) {
const instance = usePlugin(plugin);
const [isHovered, setIsHovered] = useState(false);
const [isLongHovered, setIsLongHovered] = useState(false);
useEffect(() => {
const listener = (newValue?: Id[], prevValue?: Id[]) => {
//only change state if the prev or next hover state affect us, this avoids rerendering the whole tree for a hover
//change
if (head(prevValue) === nodeId || head(newValue) === nodeId) {
const hovered = head(newValue) === nodeId;
setIsHovered(hovered);
if (hovered === true) {
setTimeout(() => {
const isStillHovered =
head(instance.uiState.hoveredNodes.get()) === nodeId;
if (isStillHovered) {
setIsLongHovered(true);
}
}, longHoverDelay);
} else {
setIsLongHovered(false);
}
}
};
instance.uiState.hoveredNodes.subscribe(listener);
return () => {
instance.uiState.hoveredNodes.unsubscribe(listener);
};
}, [instance.uiState.hoveredNodes, nodeId]);
return {
isHovered,
isLongHovered,
};
}
const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => { const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
const instance = usePlugin(plugin); const instance = usePlugin(plugin);
@@ -307,6 +339,7 @@ const NodeBorder = styled.div<{tags: Tag[]; hovered: boolean}>((props) => ({
: 'black', : 'black',
})); }));
const longHoverDelay = 200;
const outerBorderWidth = '10px'; const outerBorderWidth = '10px';
const outerBorderOffset = `-${outerBorderWidth}`; const outerBorderOffset = `-${outerBorderWidth}`;