diff --git a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx index 034eaaecc..ea0c10367 100644 --- a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx +++ b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx @@ -7,14 +7,15 @@ * @format */ -import React, {useEffect, useMemo, useRef, useState} from 'react'; -import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types'; +import React, {useEffect, useMemo, useRef} from 'react'; +import {Bounds, Coordinate, Id, NestedNode, 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, Tooltip} from 'antd'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; +import {useDelay} from '../hooks/useDelay'; export const Visualization2D: React.FC< { @@ -100,6 +101,13 @@ export const Visualization2D: React.FC< return (
{ + e.stopPropagation(); + //the context menu triggers this callback but we dont want to remove hover effect + if (!instance.uiState.isContextMenuOpen.get()) { + instance.uiState.hoveredNodes.set([]); + } + }} //this div is to ensure that the size of the visualiser doesnt change when focusings on a subtree style={ { @@ -109,7 +117,11 @@ export const Visualization2D: React.FC< } as React.CSSProperties }> {hoveredNodeId && ( - + )} {selectedNodeId && ( { - e.stopPropagation(); - //the context menu triggers this callback but we dont want to remove hover effect - if (!instance.uiState.isContextMenuOpen.get()) { - instance.uiState.hoveredNodes.set([]); - } - }} style={{ /** * This relative position is so the rootNode visualization 2DNode and outer border has a non static element to @@ -181,7 +186,6 @@ function Visualization2DNode({ }) { const instance = usePlugin(plugin); - const {isLongHovered} = useHoverStates(node.id); const ref = useRef(null); let nestedChildren: NestedNode[]; @@ -205,41 +209,52 @@ function Visualization2DNode({ node.id, ); + return ( +
{ + e.stopPropagation(); + + const hoveredNodes = instance.uiState.hoveredNodes.get(); + + onSelectNode(hoveredNodes[0]); + }}> + + + {children} +
+ ); +} + +function HoveredOverlay({nodeId, nodes}: {nodeId: Id; nodes: Map}) { + const node = nodes.get(nodeId); + + const isVisible = useDelay(longHoverDelay); + return ( -
{ - e.stopPropagation(); - - const hoveredNodes = instance.uiState.hoveredNodes.get(); - - onSelectNode(hoveredNodes[0]); - }}> - - - {children} -
+
); } @@ -265,7 +280,7 @@ const OverlayBorder = styled.div<{ borderStyle: 'solid', color: 'transparent', borderColor: - type === 'selected' ? theme.primaryColor : theme.selectionBackgroundColor, + type === 'selected' ? theme.primaryColor : theme.textColorPlaceholder, }; }); @@ -289,43 +304,6 @@ function getTotalOffset(id: Id, nodes: Map): Coordinate { return offset; } -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); @@ -387,7 +365,7 @@ const NodeBorder = styled.div({ borderColor: theme.disabledColor, }); -const longHoverDelay = 200; +const longHoverDelay = 500; const pxScaleFactorCssVar = '--pxScaleFactor'; const MouseThrottle = 32; diff --git a/desktop/plugins/public/ui-debugger/hooks/useDelay.tsx b/desktop/plugins/public/ui-debugger/hooks/useDelay.tsx new file mode 100644 index 000000000..73216cc91 --- /dev/null +++ b/desktop/plugins/public/ui-debugger/hooks/useDelay.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {useEffect, useRef, useState} from 'react'; + +export function useDelay(delayTimeMs: number) { + const [isDone, setIsDone] = useState(false); + const delayTimerStarted = useRef(false); + useEffect(() => { + let handle: NodeJS.Timeout | null = null; + if (delayTimerStarted.current === false) { + handle = setTimeout(() => setIsDone(true), delayTimeMs); + delayTimerStarted.current = true; + } + + return () => { + if (handle !== null) { + clearTimeout(handle); + } + }; + }, [delayTimeMs]); + + return isDone; +} diff --git a/desktop/plugins/public/ui-debugger/hooks/usefilteredValue.tsx b/desktop/plugins/public/ui-debugger/hooks/usefilteredValue.tsx deleted file mode 100644 index 5e689fb4b..000000000 --- a/desktop/plugins/public/ui-debugger/hooks/usefilteredValue.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import {Atom} from 'flipper-plugin'; -import {useEffect, useState} from 'react'; - -/** - * A hook similar to useValue that Subscribes to an atom and returns the current value. - * However the value only updates if the predicate passes. - * - * Usefull for skipping expensive react renders if an update doesnt concern you - * @param atom - * @param predicate - */ -export function useFilteredValue( - atom: Atom, - predicate: (newValue: T, prevValue?: T) => boolean, -) { - const [value, setValue] = useState(atom.get()); - - useEffect(() => { - const listener = (newValue: T, prevValue?: T) => { - if (predicate(newValue, prevValue)) { - setValue(newValue); - } - }; - atom.subscribe(listener); - return () => { - atom.unsubscribe(listener); - }; - }, [atom, predicate]); - - return value; -}