No longer autoscroll when selecting via tree
Summary: Added selection source concept to onSelect callback. This allows us to only autoscroll the tree when selection source is the visualiser. We had feedback that the horizontal autoscrolling whilst using the tree was unhelpful. A side benefit of selection source is better tracking of how people use kb, tree vs visualiser to select things Changelog: UIDebugger only autoscroll horizontally when selecting via the visualiser Reviewed By: lblasa Differential Revision: D47334078 fbshipit-source-id: d7eadddb8d3d0fd428d5c294b2dccc2f1efa5a95
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d9c8dbf404
commit
a6bc8933cc
@@ -7,7 +7,13 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {FrameworkEvent, FrameworkEventType, Id, UINode} from '../types';
|
import {
|
||||||
|
FrameworkEvent,
|
||||||
|
FrameworkEventType,
|
||||||
|
Id,
|
||||||
|
OnSelectNode,
|
||||||
|
UINode,
|
||||||
|
} from '../types';
|
||||||
import React, {
|
import React, {
|
||||||
ReactNode,
|
ReactNode,
|
||||||
Ref,
|
Ref,
|
||||||
@@ -18,7 +24,6 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Atom,
|
|
||||||
getFlipperLib,
|
getFlipperLib,
|
||||||
HighlightManager,
|
HighlightManager,
|
||||||
HighlightProvider,
|
HighlightProvider,
|
||||||
@@ -86,7 +91,7 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
|
|||||||
nodes,
|
nodes,
|
||||||
focusedNode || rootId,
|
focusedNode || rootId,
|
||||||
expandedNodes,
|
expandedNodes,
|
||||||
selectedNode,
|
selectedNode?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const refs: React.RefObject<HTMLLIElement>[] = treeNodes.map(() =>
|
const refs: React.RefObject<HTMLLIElement>[] = treeNodes.map(() =>
|
||||||
@@ -119,7 +124,7 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
|
|||||||
useKeyboardShortcuts(
|
useKeyboardShortcuts(
|
||||||
treeNodes,
|
treeNodes,
|
||||||
rowVirtualizer,
|
rowVirtualizer,
|
||||||
selectedNode,
|
selectedNode?.id,
|
||||||
hoveredNode,
|
hoveredNode,
|
||||||
instance.uiActions.onSelectNode,
|
instance.uiActions.onSelectNode,
|
||||||
instance.uiActions.onHoverNode,
|
instance.uiActions.onHoverNode,
|
||||||
@@ -162,17 +167,27 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (selectedNode) {
|
if (selectedNode != null) {
|
||||||
const idx = treeNodes.findIndex((node) => node.id === selectedNode);
|
const selectedTreeNode = treeNodes.find(
|
||||||
|
(node) => node.id === selectedNode?.id,
|
||||||
|
);
|
||||||
|
|
||||||
const kbIsNoLongerReservingScroll =
|
const ref = parentRef.current;
|
||||||
new Date().getTime() > (isUsingKBToScrollUtill.current ?? 0);
|
if (
|
||||||
|
ref != null &&
|
||||||
|
selectedTreeNode != null &&
|
||||||
|
selectedNode?.source === 'visualiser'
|
||||||
|
) {
|
||||||
|
ref.scrollLeft =
|
||||||
|
Math.max(0, selectedTreeNode.depth - 10) * renderDepthOffset;
|
||||||
|
|
||||||
if (idx !== -1 && kbIsNoLongerReservingScroll) {
|
let scrollToIndex = selectedTreeNode.idx;
|
||||||
parentRef.current!!.scrollLeft =
|
|
||||||
Math.max(0, treeNodes[idx].depth - 10) * renderDepthOffset;
|
|
||||||
|
|
||||||
rowVirtualizer.scrollToIndex(idx, {align: 'auto'});
|
if (selectedTreeNode.idx > rowVirtualizer.range.endIndex) {
|
||||||
|
//when scrolling down the scrollbar gets in the way if you scroll to the precise node
|
||||||
|
scrollToIndex = Math.min(scrollToIndex + 1, treeNodes.length);
|
||||||
|
}
|
||||||
|
rowVirtualizer.scrollToIndex(scrollToIndex, {align: 'auto'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOTE: We don't want to add refs or tree nodes to the dependency list since when new data comes in over the wire
|
// NOTE: We don't want to add refs or tree nodes to the dependency list since when new data comes in over the wire
|
||||||
@@ -226,7 +241,7 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
|
|||||||
frameworkEvents={frameworkEvents}
|
frameworkEvents={frameworkEvents}
|
||||||
frameworkEventsMonitoring={frameworkEventsMonitoring}
|
frameworkEventsMonitoring={frameworkEventsMonitoring}
|
||||||
highlightedNodes={highlightedNodes}
|
highlightedNodes={highlightedNodes}
|
||||||
selectedNode={selectedNode}
|
selectedNode={selectedNode?.id}
|
||||||
hoveredNode={hoveredNode}
|
hoveredNode={hoveredNode}
|
||||||
isUsingKBToScroll={isUsingKBToScrollUtill}
|
isUsingKBToScroll={isUsingKBToScrollUtill}
|
||||||
isContextMenuOpen={isContextMenuOpen}
|
isContextMenuOpen={isContextMenuOpen}
|
||||||
@@ -296,7 +311,7 @@ function TreeItemContainer({
|
|||||||
hoveredNode?: Id;
|
hoveredNode?: Id;
|
||||||
isUsingKBToScroll: RefObject<MillisSinceEpoch>;
|
isUsingKBToScroll: RefObject<MillisSinceEpoch>;
|
||||||
isContextMenuOpen: boolean;
|
isContextMenuOpen: boolean;
|
||||||
onSelectNode: (node?: Id) => void;
|
onSelectNode: OnSelectNode;
|
||||||
onExpandNode: (node: Id) => void;
|
onExpandNode: (node: Id) => void;
|
||||||
onCollapseNode: (node: Id) => void;
|
onCollapseNode: (node: Id) => void;
|
||||||
onHoverNode: (node: Id) => void;
|
onHoverNode: (node: Id) => void;
|
||||||
@@ -333,7 +348,7 @@ function TreeItemContainer({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelectNode(treeNode.id);
|
onSelectNode(treeNode.id, 'tree');
|
||||||
}}
|
}}
|
||||||
item={treeNode}
|
item={treeNode}
|
||||||
style={{overflow: 'visible'}}>
|
style={{overflow: 'visible'}}>
|
||||||
@@ -688,7 +703,7 @@ function useKeyboardShortcuts(
|
|||||||
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
||||||
selectedNode: Id | undefined,
|
selectedNode: Id | undefined,
|
||||||
hoveredNodeId: Id | undefined,
|
hoveredNodeId: Id | undefined,
|
||||||
onSelectNode: (id?: Id) => void,
|
onSelectNode: OnSelectNode,
|
||||||
onHoverNode: (id?: Id) => void,
|
onHoverNode: (id?: Id) => void,
|
||||||
onExpandNode: (id: Id) => void,
|
onExpandNode: (id: Id) => void,
|
||||||
onCollapseNode: (id: Id) => void,
|
onCollapseNode: (id: Id) => void,
|
||||||
@@ -702,7 +717,7 @@ function useKeyboardShortcuts(
|
|||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
if (hoveredNodeId != null) {
|
if (hoveredNodeId != null) {
|
||||||
onSelectNode(hoveredNodeId);
|
onSelectNode(hoveredNodeId, 'keyboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -793,7 +808,7 @@ function moveSelectedNodeUpOrDown(
|
|||||||
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
||||||
hoveredNode: Id | undefined,
|
hoveredNode: Id | undefined,
|
||||||
selectedNode: Id | undefined,
|
selectedNode: Id | undefined,
|
||||||
onSelectNode: (id?: Id) => void,
|
onSelectNode: OnSelectNode,
|
||||||
onHoverNode: (id?: Id) => void,
|
onHoverNode: (id?: Id) => void,
|
||||||
isUsingKBToScrollUntill: React.MutableRefObject<MillisSinceEpoch>,
|
isUsingKBToScrollUntill: React.MutableRefObject<MillisSinceEpoch>,
|
||||||
) {
|
) {
|
||||||
@@ -818,7 +833,7 @@ function moveSelectedNodeViaKeyBoard(
|
|||||||
newIdx: number,
|
newIdx: number,
|
||||||
treeNodes: TreeNode[],
|
treeNodes: TreeNode[],
|
||||||
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
||||||
onSelectNode: (id?: Id) => void,
|
onSelectNode: OnSelectNode,
|
||||||
onHoverNode: (id?: Id) => void,
|
onHoverNode: (id?: Id) => void,
|
||||||
isUsingKBToScrollUntil: React.MutableRefObject<number>,
|
isUsingKBToScrollUntil: React.MutableRefObject<number>,
|
||||||
) {
|
) {
|
||||||
@@ -826,7 +841,7 @@ function moveSelectedNodeViaKeyBoard(
|
|||||||
const newNode = treeNodes[newIdx];
|
const newNode = treeNodes[newIdx];
|
||||||
|
|
||||||
extendKBControlLease(isUsingKBToScrollUntil);
|
extendKBControlLease(isUsingKBToScrollUntil);
|
||||||
onSelectNode(newNode.id);
|
onSelectNode(newNode.id, 'keyboard');
|
||||||
onHoverNode(newNode.id);
|
onHoverNode(newNode.id);
|
||||||
|
|
||||||
rowVirtualizer.scrollToIndex(newIdx, {align: 'auto'});
|
rowVirtualizer.scrollToIndex(newIdx, {align: 'auto'});
|
||||||
|
|||||||
@@ -8,7 +8,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useEffect, useMemo, useRef} from 'react';
|
import React, {useEffect, useMemo, useRef} from 'react';
|
||||||
import {Bounds, Coordinate, Id, NestedNode, UINode} from '../types';
|
import {
|
||||||
|
Bounds,
|
||||||
|
Coordinate,
|
||||||
|
Id,
|
||||||
|
NestedNode,
|
||||||
|
OnSelectNode,
|
||||||
|
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';
|
||||||
@@ -21,7 +28,7 @@ export const Visualization2D: React.FC<
|
|||||||
{
|
{
|
||||||
width: number;
|
width: number;
|
||||||
nodes: Map<Id, UINode>;
|
nodes: Map<Id, UINode>;
|
||||||
onSelectNode: (id?: Id) => void;
|
onSelectNode: OnSelectNode;
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
> = ({width, nodes, onSelectNode}) => {
|
> = ({width, nodes, onSelectNode}) => {
|
||||||
const rootNodeRef = useRef<HTMLDivElement>();
|
const rootNodeRef = useRef<HTMLDivElement>();
|
||||||
@@ -129,7 +136,7 @@ export const Visualization2D: React.FC<
|
|||||||
{selectedNodeId && (
|
{selectedNodeId && (
|
||||||
<OverlayBorder
|
<OverlayBorder
|
||||||
type="selected"
|
type="selected"
|
||||||
nodeId={selectedNodeId}
|
nodeId={selectedNodeId.id}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -185,7 +192,7 @@ function Visualization2DNode({
|
|||||||
onSelectNode,
|
onSelectNode,
|
||||||
}: {
|
}: {
|
||||||
node: NestedNode;
|
node: NestedNode;
|
||||||
onSelectNode: (id?: Id) => void;
|
onSelectNode: OnSelectNode;
|
||||||
}) {
|
}) {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
@@ -236,7 +243,7 @@ function Visualization2DNode({
|
|||||||
|
|
||||||
const hoveredNodes = instance.uiState.hoveredNodes.get();
|
const hoveredNodes = instance.uiState.hoveredNodes.get();
|
||||||
|
|
||||||
onSelectNode(hoveredNodes[0]);
|
onSelectNode(hoveredNodes[0], 'visualiser');
|
||||||
}}>
|
}}>
|
||||||
<NodeBorder />
|
<NodeBorder />
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const Inspector: React.FC<Props> = ({
|
|||||||
showExtra,
|
showExtra,
|
||||||
}) => {
|
}) => {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const selectedNodeId = useValue(instance.uiState.selectedNode);
|
const selectedNodeId = useValue(instance.uiState.selectedNode)?.id;
|
||||||
const frameworkEvents = useValue(instance.frameworkEvents);
|
const frameworkEvents = useValue(instance.frameworkEvents);
|
||||||
|
|
||||||
const selectedNode = selectedNodeId ? nodes.get(selectedNodeId) : undefined;
|
const selectedNode = selectedNodeId ? nodes.get(selectedNodeId) : undefined;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Atom} from 'flipper-plugin';
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
export function useDelay(delayTimeMs: number) {
|
export function useDelay(delayTimeMs: number) {
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ import {
|
|||||||
Id,
|
Id,
|
||||||
Metadata,
|
Metadata,
|
||||||
MetadataId,
|
MetadataId,
|
||||||
|
NodeSelection,
|
||||||
PerformanceStatsEvent,
|
PerformanceStatsEvent,
|
||||||
|
SelectionSource,
|
||||||
SnapshotInfo,
|
SnapshotInfo,
|
||||||
StreamInterceptorError,
|
StreamInterceptorError,
|
||||||
StreamState,
|
StreamState,
|
||||||
|
UIActions,
|
||||||
UINode,
|
UINode,
|
||||||
UIState,
|
UIState,
|
||||||
} from './types';
|
} from './types';
|
||||||
@@ -204,7 +207,7 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
|
|
||||||
highlightedNodes,
|
highlightedNodes,
|
||||||
|
|
||||||
selectedNode: createState<Id | undefined>(undefined),
|
selectedNode: createState<NodeSelection | undefined>(undefined),
|
||||||
//used to indicate whether we will higher the visualizer / tree when a matching event comes in
|
//used to indicate whether we will higher the visualizer / tree when a matching event comes in
|
||||||
//also whether or not will show running total in the tree
|
//also whether or not will show running total in the tree
|
||||||
frameworkEventMonitoring: createState(
|
frameworkEventMonitoring: createState(
|
||||||
@@ -371,34 +374,28 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type UIActions = {
|
|
||||||
onHoverNode: (node?: Id) => void;
|
|
||||||
onFocusNode: (focused?: Id) => void;
|
|
||||||
onContextMenuOpen: (open: boolean) => void;
|
|
||||||
onSelectNode: (node?: Id) => void;
|
|
||||||
onExpandNode: (node: Id) => void;
|
|
||||||
onCollapseNode: (node: Id) => void;
|
|
||||||
setVisualiserWidth: (width: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
function uiActions(uiState: UIState, nodes: Atom<Map<Id, UINode>>): UIActions {
|
function uiActions(uiState: UIState, nodes: Atom<Map<Id, UINode>>): UIActions {
|
||||||
const onExpandNode = (node: Id) => {
|
const onExpandNode = (node: Id) => {
|
||||||
uiState.expandedNodes.update((draft) => {
|
uiState.expandedNodes.update((draft) => {
|
||||||
draft.add(node);
|
draft.add(node);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const onSelectNode = (node?: Id) => {
|
const onSelectNode = (node: Id | undefined, source: SelectionSource) => {
|
||||||
if (uiState.selectedNode.get() === node) {
|
if (node == null || uiState.selectedNode.get()?.id === node) {
|
||||||
uiState.selectedNode.set(undefined);
|
uiState.selectedNode.set(undefined);
|
||||||
} else {
|
} else {
|
||||||
uiState.selectedNode.set(node);
|
uiState.selectedNode.set({id: node, source});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
const selectedNode = nodes.get().get(node);
|
const selectedNode = nodes.get().get(node);
|
||||||
const tags = selectedNode?.tags;
|
const tags = selectedNode?.tags;
|
||||||
if (tags) {
|
if (tags) {
|
||||||
tracker.track('node-selected', {name: selectedNode.name, tags});
|
tracker.track('node-selected', {
|
||||||
|
name: selectedNode.name,
|
||||||
|
tags,
|
||||||
|
source: source,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let current = selectedNode?.parent;
|
let current = selectedNode?.parent;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {getFlipperLib} from 'flipper-plugin';
|
import {getFlipperLib} from 'flipper-plugin';
|
||||||
|
import {SelectionSource} from '.';
|
||||||
import {FrameworkEventType, Tag} from './types';
|
import {FrameworkEventType, Tag} from './types';
|
||||||
|
|
||||||
const UI_DEBUGGER_IDENTIFIER = 'ui-debugger';
|
const UI_DEBUGGER_IDENTIFIER = 'ui-debugger';
|
||||||
@@ -15,6 +16,7 @@ const UI_DEBUGGER_IDENTIFIER = 'ui-debugger';
|
|||||||
type NodeEventPayload = {
|
type NodeEventPayload = {
|
||||||
name: string;
|
name: string;
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
|
source?: SelectionSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TrackerEvents = {
|
type TrackerEvents = {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export type UIState = {
|
|||||||
searchTerm: Atom<string>;
|
searchTerm: Atom<string>;
|
||||||
isContextMenuOpen: Atom<boolean>;
|
isContextMenuOpen: Atom<boolean>;
|
||||||
hoveredNodes: Atom<Id[]>;
|
hoveredNodes: Atom<Id[]>;
|
||||||
selectedNode: Atom<Id | undefined>;
|
selectedNode: Atom<NodeSelection | undefined>;
|
||||||
highlightedNodes: Atom<Set<Id>>;
|
highlightedNodes: Atom<Set<Id>>;
|
||||||
focusedNode: Atom<Id | undefined>;
|
focusedNode: Atom<Id | undefined>;
|
||||||
expandedNodes: Atom<Set<Id>>;
|
expandedNodes: Atom<Set<Id>>;
|
||||||
@@ -24,6 +24,27 @@ export type UIState = {
|
|||||||
frameworkEventMonitoring: Atom<Map<FrameworkEventType, boolean>>;
|
frameworkEventMonitoring: Atom<Map<FrameworkEventType, boolean>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NodeSelection = {
|
||||||
|
id: Id;
|
||||||
|
source: SelectionSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OnSelectNode = (
|
||||||
|
node: Id | undefined,
|
||||||
|
source: SelectionSource,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type UIActions = {
|
||||||
|
onHoverNode: (node?: Id) => void;
|
||||||
|
onFocusNode: (focused?: Id) => void;
|
||||||
|
onContextMenuOpen: (open: boolean) => void;
|
||||||
|
onSelectNode: OnSelectNode;
|
||||||
|
onExpandNode: (node: Id) => void;
|
||||||
|
onCollapseNode: (node: Id) => void;
|
||||||
|
setVisualiserWidth: (width: number) => void;
|
||||||
|
};
|
||||||
|
export type SelectionSource = 'visualiser' | 'tree' | 'keyboard';
|
||||||
|
|
||||||
export type StreamState =
|
export type StreamState =
|
||||||
| {state: 'Ok'}
|
| {state: 'Ok'}
|
||||||
| {state: 'RetryingAfterError'}
|
| {state: 'RetryingAfterError'}
|
||||||
|
|||||||
Reference in New Issue
Block a user