diff --git a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx index e7309d4c6..e37e62291 100644 --- a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx +++ b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx @@ -13,7 +13,7 @@ import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types'; import {produce, styled, theme, usePlugin, useValue} from 'flipper-plugin'; import {plugin} from '../index'; import {head, isEqual, throttle} from 'lodash'; -import {Dropdown, Menu} from 'antd'; +import {Dropdown, Menu, Tooltip} from 'antd'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; export const Visualization2D: React.FC< @@ -167,20 +167,8 @@ function Visualization2DNode({ }) { 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 {isHovered, isLongHovered} = useHoverStates(node.id); let nestedChildren: NestedNode[]; @@ -208,41 +196,85 @@ function Visualization2DNode({ /> )); - const bounds = node.bounds ?? {x: 0, y: 0, width: 0, height: 0}; - return ( -
{ - e.stopPropagation(); - - const hoveredNodes = instance.uiState.hoveredNodes.get(); - if (hoveredNodes[0] === selectedNode) { - onSelectNode(undefined); - } else { - onSelectNode(hoveredNodes[0]); - } + - - {isHovered &&

{node.name}

} - {children} -
+
{ + e.stopPropagation(); + + const hoveredNodes = instance.uiState.hoveredNodes.get(); + if (hoveredNodes[0] === selectedNode) { + onSelectNode(undefined); + } else { + onSelectNode(hoveredNodes[0]); + } + }}> + + {children} +
+ ); } +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}> = ({children}) => { const instance = usePlugin(plugin); @@ -307,6 +339,7 @@ const NodeBorder = styled.div<{tags: Tag[]; hovered: boolean}>((props) => ({ : 'black', })); +const longHoverDelay = 200; const outerBorderWidth = '10px'; const outerBorderOffset = `-${outerBorderWidth}`;