diff --git a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx index a6aa4b147..6a82fbbb8 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, {useEffect, useMemo, useRef} from 'react'; +import React, {ReactNode, useEffect, useMemo, useRef, useState} from 'react'; import {Bounds, Coordinate, Id, ClientNode} from '../ClientTypes'; import {NestedNode, OnSelectNode} from '../DesktopTypes'; @@ -17,6 +17,11 @@ import {head, isEqual, throttle} from 'lodash'; import {Dropdown, Menu, Tooltip} from 'antd'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; import {useDelay} from '../hooks/useDelay'; +import { + AimOutlined, + FullscreenExitOutlined, + FullscreenOutlined, +} from '@ant-design/icons'; export const Visualization2D: React.FC< { @@ -311,7 +316,7 @@ const OverlayBorder = styled.div<{ width: toPx(node?.bounds?.width ?? 0), height: toPx(node?.bounds?.height ?? 0), boxSizing: 'border-box', - borderWidth: type === 'selected' ? 3 : 2, + borderWidth: 3, borderStyle: 'solid', color: 'transparent', borderColor: @@ -339,34 +344,84 @@ function getTotalOffset(id: Id, nodes: Map): Coordinate { return offset; } +function notEmpty(value: TValue | null | undefined): value is TValue { + return value != null; +} + +const iconStyle = {fontSize: 14}; const ContextMenu: React.FC<{nodes: Map}> = ({children}) => { const instance = usePlugin(plugin); - const focusedNodeId = useValue(instance.uiState.focusedNode); - const hoveredNodeId = head(useValue(instance.uiState.hoveredNodes)); + const hoveredNodeIds = useValue(instance.uiState.hoveredNodes); + const nodes = useValue(instance.nodes); - const hoveredNode = hoveredNodeId ? nodes.get(hoveredNodeId) : null; + + const hoveredNodes = hoveredNodeIds + .map((id) => nodes.get(id)) + .filter(notEmpty) + .reverse(); + + const focusItems = hoveredNodes.map((node: ClientNode) => ( + { + instance.uiActions.onHoverNode(node.id); + }} + text={node.name} + onClick={() => { + instance.uiActions.onFocusNode(node.id); + }} + /> + )); + + const selectItems = hoveredNodes.map((node: ClientNode) => ( + { + instance.uiActions.onHoverNode(node.id); + }} + onClick={() => { + instance.uiActions.onSelectNode(node.id, 'visualiser'); + }} + /> + )); + + //since the context menu changes the hover state to indicate where you are this + //causes a rerender and therefore changes the context menu items. to work around + //we grab the hovered items at the time the context menu opens and this is unaffected + //by any further changes to hover state + const [staticItems, setStaticItems] = useState<{ + focusItems: ReactNode[]; + selectItems: ReactNode[]; + }>({ + selectItems: [], + focusItems: [], + }); return ( { instance.uiActions.onContextMenuOpen(open); + if (open) { + setStaticItems({focusItems: focusItems, selectItems: selectItems}); + } }} trigger={['contextMenu']} overlay={() => { return ( - {hoveredNode != null && hoveredNode?.id !== focusedNodeId && ( - { - instance.uiActions.onFocusNode(hoveredNode?.id); - }} - /> + {staticItems.focusItems.length > 0 && ( + }> + {staticItems.focusItems} + )} + {focusedNodeId != null && ( } key="remove-focus" text="Remove focus" onClick={() => { @@ -374,6 +429,16 @@ const ContextMenu: React.FC<{nodes: Map}> = ({children}) => { }} /> )} + + {focusedNodeId != null && } + + {staticItems.selectItems.length > 0 && ( + }> + {staticItems.selectItems} + + )} ); }}> diff --git a/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx b/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx index 9292b1055..765ddb9c0 100644 --- a/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx +++ b/desktop/plugins/public/ui-debugger/components/util/UIDebuggerMenuItem.tsx @@ -22,7 +22,9 @@ export const UIDebuggerMenuItem: React.FC<{ text: string; icon?: React.ReactNode; onClick?: () => void; -}> = ({text, onClick, icon}) => { + onMouseEnter?: () => void; + onMouseLeave?: () => void; +}> = ({text, onClick, icon, onMouseEnter, onMouseLeave}) => { const instance = usePlugin(plugin); const isMenuOpen = useValue(instance.uiState.isContextMenuOpen); @@ -36,6 +38,8 @@ export const UIDebuggerMenuItem: React.FC<{ } return ( { onClick?.();