diff --git a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx index e8e6bb056..034eaaecc 100644 --- a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx +++ b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx @@ -7,7 +7,7 @@ * @format */ -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types'; import {produce, styled, theme, usePlugin, useValue} from 'flipper-plugin'; @@ -15,7 +15,6 @@ import {plugin} from '../index'; import {head, isEqual, throttle} from 'lodash'; import {Dropdown, Menu, Tooltip} from 'antd'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; -import {useFilteredValue} from '../hooks/usefilteredValue'; export const Visualization2D: React.FC< { @@ -31,6 +30,8 @@ export const Visualization2D: React.FC< const snapshotNode = snapshot && nodes.get(snapshot.nodeId); const focusedNodeId = useValue(instance.uiState.focusedNode); + const selectedNodeId = useValue(instance.uiState.selectedNode); + const hoveredNodeId = head(useValue(instance.uiState.hoveredNodes)); const focusState = useMemo(() => { //use the snapshot node as root since we cant realistically visualise any node above this const rootNode = snapshot && toNestedNode(snapshot.nodeId, nodes); @@ -107,6 +108,16 @@ export const Visualization2D: React.FC< height: toPx(focusState.actualRoot.bounds.height), } as React.CSSProperties }> + {hoveredNodeId && ( + + )} + {selectedNodeId && ( + + )}
{ @@ -170,19 +181,7 @@ function Visualization2DNode({ }) { const instance = usePlugin(plugin); - const wasOrIsSelected = useCallback( - (curValue?: Id, prevValue?: Id) => - curValue === node.id || prevValue === node.id, - [node.id], - ); - const selectedNode = useFilteredValue( - instance.uiState.selectedNode, - wasOrIsSelected, - ); - - const isSelected = selectedNode === node.id; - - const {isHovered, isLongHovered} = useHoverStates(node.id); + const {isLongHovered} = useHoverStates(node.id); const ref = useRef(null); let nestedChildren: NestedNode[]; @@ -234,22 +233,62 @@ function Visualization2DNode({ e.stopPropagation(); const hoveredNodes = instance.uiState.hoveredNodes.get(); - if (hoveredNodes[0] === selectedNode) { - onSelectNode(undefined); - } else { - onSelectNode(hoveredNodes[0]); - } + + onSelectNode(hoveredNodes[0]); }}> - + + {children}
); } +const OverlayBorder = styled.div<{ + type: 'selected' | 'hovered'; + nodeId: Id; + nodes: Map; +}>(({type, nodeId, nodes}) => { + const offset = getTotalOffset(nodeId, nodes); + const node = nodes.get(nodeId); + return { + zIndex: 100, + pointerEvents: 'none', + cursor: 'pointer', + position: 'absolute', + top: toPx(offset.y), + left: toPx(offset.x), + width: toPx(node?.bounds?.width ?? 0), + height: toPx(node?.bounds?.height ?? 0), + boxSizing: 'border-box', + borderWidth: 2, + borderStyle: 'solid', + color: 'transparent', + borderColor: + type === 'selected' ? theme.primaryColor : theme.selectionBackgroundColor, + }; +}); + +/** + * computes the x,y offset of a given node from the root of the visualization + * in node coordinates + */ +function getTotalOffset(id: Id, nodes: Map): Coordinate { + const offset = {x: 0, y: 0}; + let curId: Id | undefined = id; + + while (curId != null) { + const cur = nodes.get(curId); + if (cur != null) { + offset.x += cur.bounds.x; + offset.y += cur.bounds.y; + } + curId = cur?.parent; + } + + return offset; +} + function useHoverStates(nodeId: Id) { const instance = usePlugin(plugin); const [isHovered, setIsHovered] = useState(false); @@ -309,7 +348,7 @@ const ContextMenu: React.FC<{nodes: Map}> = ({children}) => { key="focus" text={`Focus ${hoveredNode?.name}`} onClick={() => { - instance.uiState.focusedNode.set(hoveredNode?.id); + instance.uiActions.onFocusNode(hoveredNode?.id); }} /> )} @@ -335,33 +374,25 @@ const ContextMenu: React.FC<{nodes: Map}> = ({children}) => { * node itself so that it has the same size but the border doesnt affect the sizing of its children * as border is part of the box model */ -const NodeBorder = styled.div<{ - tags: Tag[]; - hovered: boolean; - selected: boolean; -}>((props) => ({ +const NodeBorder = styled.div({ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, boxSizing: 'border-box', - borderWidth: props.selected || props.hovered ? '2px' : '1px', + borderWidth: '1px', borderStyle: 'solid', color: 'transparent', - borderColor: props.selected - ? theme.primaryColor - : props.hovered - ? theme.selectionBackgroundColor - : theme.disabledColor, -})); + borderColor: theme.disabledColor, +}); const longHoverDelay = 200; const pxScaleFactorCssVar = '--pxScaleFactor'; const MouseThrottle = 32; function toPx(n: number) { - return `calc(${n}px / var(${pxScaleFactorCssVar})`; + return `calc(${n}px / var(${pxScaleFactorCssVar}))`; } function toNestedNode( diff --git a/desktop/plugins/public/ui-debugger/index.tsx b/desktop/plugins/public/ui-debugger/index.tsx index 742975a60..5f949bcd3 100644 --- a/desktop/plugins/public/ui-debugger/index.tsx +++ b/desktop/plugins/public/ui-debugger/index.tsx @@ -375,7 +375,12 @@ function uiActions(uiState: UIState, nodes: Atom>): UIActions { }); }; const onSelectNode = (node?: Id) => { - uiState.selectedNode.set(node); + if (uiState.selectedNode.get() === node) { + uiState.selectedNode.set(undefined); + } else { + uiState.selectedNode.set(node); + } + if (node) { const selectedNode = nodes.get().get(node); const tags = selectedNode?.tags; @@ -410,12 +415,14 @@ function uiActions(uiState: UIState, nodes: Atom>): UIActions { }; const onFocusNode = (node?: Id) => { - if (node) { + if (node != null) { const focusedNode = nodes.get().get(node); const tags = focusedNode?.tags; if (tags) { tracker.track('node-focused', {name: focusedNode.name, tags}); } + + uiState.selectedNode.set(undefined); } uiState.focusedNode.set(node);