UID refactor 4/ Expose readonly UIState
Summary: Currently state writes can either go through a named handler that is easy to find and debug or they can directly modify the state. By exposing readonly atoms only we ensure that all state writes go through a UIACtions. This adds consistency and ease of future debugging. E.g We could add a utility to wrap all ui actions with logging statements Reviewed By: antonk52 Differential Revision: D47547531 fbshipit-source-id: f88651169d8e7c5f7e31068d64f9aa5b6b573647
This commit is contained in:
committed by
Facebook GitHub Bot
parent
87a1b657c3
commit
957a336349
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Atom} from 'flipper-plugin';
|
import {Atom, _ReadOnlyAtom} from 'flipper-plugin';
|
||||||
import {
|
import {
|
||||||
Id,
|
Id,
|
||||||
FrameworkEventType,
|
FrameworkEventType,
|
||||||
@@ -35,6 +35,14 @@ export type UIState = {
|
|||||||
filterMainThreadMonitoring: Atom<boolean>;
|
filterMainThreadMonitoring: Atom<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//enumerates the keys of input type and casts each to ReadOnlyAtom, this is so we only expose read only atoms to the UI
|
||||||
|
//and all writes come through UIActions
|
||||||
|
type TransformToReadOnly<T> = {
|
||||||
|
[P in keyof T]: T[P] extends Atom<infer U> ? _ReadOnlyAtom<U> : T[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReadOnlyUIState = TransformToReadOnly<UIState>;
|
||||||
|
|
||||||
export type StreamFlowState = {paused: boolean};
|
export type StreamFlowState = {paused: boolean};
|
||||||
|
|
||||||
export type NestedNode = {
|
export type NestedNode = {
|
||||||
@@ -62,7 +70,7 @@ export type OnSelectNode = (
|
|||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export type UIActions = {
|
export type UIActions = {
|
||||||
onHoverNode: (node?: Id) => void;
|
onHoverNode: (...node: Id[]) => void;
|
||||||
onFocusNode: (focused?: Id) => void;
|
onFocusNode: (focused?: Id) => void;
|
||||||
onContextMenuOpen: (open: boolean) => void;
|
onContextMenuOpen: (open: boolean) => void;
|
||||||
onSelectNode: OnSelectNode;
|
onSelectNode: OnSelectNode;
|
||||||
@@ -71,6 +79,12 @@ export type UIActions = {
|
|||||||
setVisualiserWidth: (width: number) => void;
|
setVisualiserWidth: (width: number) => void;
|
||||||
onSetFilterMainThreadMonitoring: (toggled: boolean) => void;
|
onSetFilterMainThreadMonitoring: (toggled: boolean) => void;
|
||||||
onSetViewMode: (viewMode: ViewMode) => void;
|
onSetViewMode: (viewMode: ViewMode) => void;
|
||||||
|
onSetFrameworkEventMonitored: (
|
||||||
|
eventType: FrameworkEventType,
|
||||||
|
monitored: boolean,
|
||||||
|
) => void;
|
||||||
|
onPlayPauseToggled: () => void;
|
||||||
|
onSearchTermUpdated: (searchTerm: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SelectionSource = 'visualiser' | 'tree' | 'keyboard';
|
export type SelectionSource = 'visualiser' | 'tree' | 'keyboard';
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {usePlugin, useValue, Layout} from 'flipper-plugin';
|
import {usePlugin, useValue, Layout} from 'flipper-plugin';
|
||||||
import {FrameworkEventType} from '../ClientTypes';
|
import {FrameworkEventType} from '../ClientTypes';
|
||||||
import {tracker} from '../utils/tracker';
|
|
||||||
import {debounce} from 'lodash';
|
|
||||||
|
|
||||||
const searchTermUpdated = debounce((searchTerm: string) => {
|
|
||||||
tracker.track('search-term-updated', {searchTerm});
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
export const Controls: React.FC = () => {
|
export const Controls: React.FC = () => {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
@@ -49,23 +43,12 @@ export const Controls: React.FC = () => {
|
|||||||
const [showFrameworkEventsModal, setShowFrameworkEventsModal] =
|
const [showFrameworkEventsModal, setShowFrameworkEventsModal] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
const onSetEventMonitored: (
|
|
||||||
eventType: FrameworkEventType,
|
|
||||||
monitored: boolean,
|
|
||||||
) => void = (eventType: FrameworkEventType, monitored: boolean) => {
|
|
||||||
tracker.track('framework-event-monitored', {eventType, monitored});
|
|
||||||
instance.uiState.frameworkEventMonitoring.update((draft) =>
|
|
||||||
draft.set(eventType, monitored),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Horizontal pad="small" gap="small">
|
<Layout.Horizontal pad="small" gap="small">
|
||||||
<Input
|
<Input
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
instance.uiState.searchTerm.set(e.target.value);
|
instance.uiActions.onSearchTermUpdated(e.target.value);
|
||||||
searchTermUpdated(e.target.value);
|
|
||||||
}}
|
}}
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
@@ -73,11 +56,7 @@ export const Controls: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
onClick={() => {
|
onClick={instance.uiActions.onPlayPauseToggled}
|
||||||
const isPaused = !instance.uiState.isPaused.get();
|
|
||||||
tracker.track('play-pause-toggled', {paused: isPaused});
|
|
||||||
instance.setPlayPause(isPaused);
|
|
||||||
}}
|
|
||||||
icon={
|
icon={
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={isPaused ? 'Resume live updates' : 'Pause incoming updates'}>
|
title={isPaused ? 'Resume live updates' : 'Pause incoming updates'}>
|
||||||
@@ -102,7 +81,7 @@ export const Controls: React.FC = () => {
|
|||||||
instance.uiActions.onSetFilterMainThreadMonitoring
|
instance.uiActions.onSetFilterMainThreadMonitoring
|
||||||
}
|
}
|
||||||
frameworkEventTypes={[...frameworkEventMonitoring.entries()]}
|
frameworkEventTypes={[...frameworkEventMonitoring.entries()]}
|
||||||
onSetEventMonitored={onSetEventMonitored}
|
onSetEventMonitored={instance.uiActions.onSetFrameworkEventMonitored}
|
||||||
visible={showFrameworkEventsModal}
|
visible={showFrameworkEventsModal}
|
||||||
onCancel={() => setShowFrameworkEventsModal(false)}
|
onCancel={() => setShowFrameworkEventsModal(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
ClientNode,
|
ClientNode,
|
||||||
FrameworkEvent,
|
FrameworkEvent,
|
||||||
} from '../ClientTypes';
|
} from '../ClientTypes';
|
||||||
import {UIState} from '../DesktopTypes';
|
import {ReadOnlyUIState} from '../DesktopTypes';
|
||||||
import React, {useMemo} from 'react';
|
import React, {useMemo} from 'react';
|
||||||
import {
|
import {
|
||||||
DataInspector,
|
DataInspector,
|
||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
|
|
||||||
export function PerfStats(props: {
|
export function PerfStats(props: {
|
||||||
uiState: UIState;
|
uiState: ReadOnlyUIState;
|
||||||
nodes: Map<Id, ClientNode>;
|
nodes: Map<Id, ClientNode>;
|
||||||
rootId?: Id;
|
rootId?: Id;
|
||||||
events: DataSource<DynamicPerformanceStatsEvent, number>;
|
events: DataSource<DynamicPerformanceStatsEvent, number>;
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export function Tree2({
|
|||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
if (isContextMenuOpen === false) {
|
if (isContextMenuOpen === false) {
|
||||||
instance.uiState.hoveredNodes.set([]);
|
instance.uiActions.onHoverNode();
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
|
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
|
||||||
@@ -756,7 +756,7 @@ function useKeyboardShortcuts(
|
|||||||
selectedNode: Id | undefined,
|
selectedNode: Id | undefined,
|
||||||
hoveredNodeId: Id | undefined,
|
hoveredNodeId: Id | undefined,
|
||||||
onSelectNode: OnSelectNode,
|
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,
|
||||||
isUsingKBToScrollUntill: React.MutableRefObject<number>,
|
isUsingKBToScrollUntill: React.MutableRefObject<number>,
|
||||||
@@ -861,7 +861,7 @@ function moveSelectedNodeUpOrDown(
|
|||||||
hoveredNode: Id | undefined,
|
hoveredNode: Id | undefined,
|
||||||
selectedNode: Id | undefined,
|
selectedNode: Id | undefined,
|
||||||
onSelectNode: OnSelectNode,
|
onSelectNode: OnSelectNode,
|
||||||
onHoverNode: (id?: Id) => void,
|
onHoverNode: (...id: Id[]) => void,
|
||||||
isUsingKBToScrollUntill: React.MutableRefObject<MillisSinceEpoch>,
|
isUsingKBToScrollUntill: React.MutableRefObject<MillisSinceEpoch>,
|
||||||
) {
|
) {
|
||||||
const nodeToUse = selectedNode != null ? selectedNode : hoveredNode;
|
const nodeToUse = selectedNode != null ? selectedNode : hoveredNode;
|
||||||
@@ -886,7 +886,7 @@ function moveSelectedNodeViaKeyBoard(
|
|||||||
treeNodes: TreeNode[],
|
treeNodes: TreeNode[],
|
||||||
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
||||||
onSelectNode: OnSelectNode,
|
onSelectNode: OnSelectNode,
|
||||||
onHoverNode: (id?: Id) => void,
|
onHoverNode: (...id: Id[]) => void,
|
||||||
isUsingKBToScrollUntil: React.MutableRefObject<number>,
|
isUsingKBToScrollUntil: React.MutableRefObject<number>,
|
||||||
) {
|
) {
|
||||||
if (newIdx >= 0 && newIdx < treeNodes.length) {
|
if (newIdx >= 0 && newIdx < treeNodes.length) {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const Visualization2D: React.FC<
|
|||||||
hitNodes.length > 0 &&
|
hitNodes.length > 0 &&
|
||||||
!isEqual(hitNodes, instance.uiState.hoveredNodes.get())
|
!isEqual(hitNodes, instance.uiState.hoveredNodes.get())
|
||||||
) {
|
) {
|
||||||
instance.uiState.hoveredNodes.set(hitNodes);
|
instance.uiActions.onHoverNode(...hitNodes);
|
||||||
}
|
}
|
||||||
}, MouseThrottle);
|
}, MouseThrottle);
|
||||||
window.addEventListener('mousemove', mouseListener);
|
window.addEventListener('mousemove', mouseListener);
|
||||||
@@ -95,6 +95,7 @@ export const Visualization2D: React.FC<
|
|||||||
instance.uiState.isContextMenuOpen,
|
instance.uiState.isContextMenuOpen,
|
||||||
width,
|
width,
|
||||||
snapshotNode,
|
snapshotNode,
|
||||||
|
instance.uiActions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!focusState || !snapshotNode) {
|
if (!focusState || !snapshotNode) {
|
||||||
@@ -110,7 +111,7 @@ export const Visualization2D: React.FC<
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
//the context menu triggers this callback but we dont want to remove hover effect
|
//the context menu triggers this callback but we dont want to remove hover effect
|
||||||
if (!instance.uiState.isContextMenuOpen.get()) {
|
if (!instance.uiState.isContextMenuOpen.get()) {
|
||||||
instance.uiState.hoveredNodes.set([]);
|
instance.uiActions.onHoverNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseInVisualiserRef.current = false;
|
mouseInVisualiserRef.current = false;
|
||||||
@@ -338,7 +339,7 @@ const ContextMenu: React.FC<{nodes: Map<Id, ClientNode>}> = ({children}) => {
|
|||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
onVisibleChange={(open) => {
|
onVisibleChange={(open) => {
|
||||||
instance.uiState.isContextMenuOpen.set(open);
|
instance.uiActions.onContextMenuOpen(open);
|
||||||
}}
|
}}
|
||||||
trigger={['contextMenu']}
|
trigger={['contextMenu']}
|
||||||
overlay={() => {
|
overlay={() => {
|
||||||
@@ -358,7 +359,7 @@ const ContextMenu: React.FC<{nodes: Map<Id, ClientNode>}> = ({children}) => {
|
|||||||
key="remove-focus"
|
key="remove-focus"
|
||||||
text="Remove focus"
|
text="Remove focus"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
instance.uiState.focusedNode.set(undefined);
|
instance.uiActions.onFocusNode(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const UIDebuggerMenuItem: React.FC<{
|
|||||||
disabled={onClick == null}
|
disabled={onClick == null}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClick?.();
|
onClick?.();
|
||||||
instance.uiState.isContextMenuOpen.set(false);
|
instance.uiActions.onContextMenuOpen(false);
|
||||||
}}>
|
}}>
|
||||||
<Layout.Horizontal center gap="small">
|
<Layout.Horizontal center gap="small">
|
||||||
{icon}
|
{icon}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
createDataSource,
|
createDataSource,
|
||||||
createState,
|
createState,
|
||||||
PluginClient,
|
PluginClient,
|
||||||
produce,
|
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {
|
import {
|
||||||
Events,
|
Events,
|
||||||
@@ -34,11 +33,13 @@ import {
|
|||||||
StreamState,
|
StreamState,
|
||||||
UIActions,
|
UIActions,
|
||||||
ViewMode,
|
ViewMode,
|
||||||
|
ReadOnlyUIState,
|
||||||
} from './DesktopTypes';
|
} from './DesktopTypes';
|
||||||
import {Draft} from 'immer';
|
import {Draft} from 'immer';
|
||||||
import {tracker} from './utils/tracker';
|
import {tracker} from './utils/tracker';
|
||||||
import {getStreamInterceptor} from './fb-stubs/StreamInterceptor';
|
import {getStreamInterceptor} from './fb-stubs/StreamInterceptor';
|
||||||
import {prefetchSourceFileLocation} from './components/fb-stubs/IDEContextMenu';
|
import {prefetchSourceFileLocation} from './components/fb-stubs/IDEContextMenu';
|
||||||
|
import {debounce} from 'lodash';
|
||||||
|
|
||||||
type LiveClientState = {
|
type LiveClientState = {
|
||||||
snapshotInfo: SnapshotInfo | null;
|
snapshotInfo: SnapshotInfo | null;
|
||||||
@@ -234,25 +235,9 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
expandedNodes: createState<Set<Id>>(new Set()),
|
expandedNodes: createState<Set<Id>>(new Set()),
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPlayPause = (isPaused: boolean) => {
|
|
||||||
uiState.isPaused.set(isPaused);
|
|
||||||
if (!isPaused) {
|
|
||||||
//When going back to play mode then set the atoms to the live state to rerender the latest
|
|
||||||
//Also need to fixed expanded state for any change in active child state
|
|
||||||
uiState.expandedNodes.update((draft) => {
|
|
||||||
liveClientData.nodes.forEach((node) => {
|
|
||||||
collapseinActiveChildren(node, draft);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
nodesAtom.set(liveClientData.nodes);
|
|
||||||
snapshot.set(liveClientData.snapshotInfo);
|
|
||||||
checkFocusedNodeStillActive(uiState, nodesAtom.get());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//this is the client data is what drives all of desktop UI
|
//this is the client data is what drives all of desktop UI
|
||||||
//it is always up-to-date with the client regardless of whether we are paused or not
|
//it is always up-to-date with the client regardless of whether we are paused or not
|
||||||
let liveClientData: LiveClientState = {
|
const mutableLiveClientData: LiveClientState = {
|
||||||
snapshotInfo: null,
|
snapshotInfo: null,
|
||||||
nodes: new Map(),
|
nodes: new Map(),
|
||||||
};
|
};
|
||||||
@@ -330,13 +315,10 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
nodes: Map<Id, ClientNode>,
|
nodes: Map<Id, ClientNode>,
|
||||||
snapshotInfo: SnapshotInfo | undefined,
|
snapshotInfo: SnapshotInfo | undefined,
|
||||||
) {
|
) {
|
||||||
liveClientData = produce(liveClientData, (draft) => {
|
|
||||||
if (snapshotInfo) {
|
if (snapshotInfo) {
|
||||||
draft.snapshotInfo = snapshotInfo;
|
mutableLiveClientData.snapshotInfo = snapshotInfo;
|
||||||
}
|
}
|
||||||
|
mutableLiveClientData.nodes = nodes;
|
||||||
draft.nodes = nodes;
|
|
||||||
});
|
|
||||||
|
|
||||||
uiState.expandedNodes.update((draft) => {
|
uiState.expandedNodes.update((draft) => {
|
||||||
for (const node of nodes.values()) {
|
for (const node of nodes.values()) {
|
||||||
@@ -354,8 +336,8 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!uiState.isPaused.get()) {
|
if (!uiState.isPaused.get()) {
|
||||||
nodesAtom.set(liveClientData.nodes);
|
nodesAtom.set(mutableLiveClientData.nodes);
|
||||||
snapshot.set(liveClientData.snapshotInfo);
|
snapshot.set(mutableLiveClientData.snapshotInfo);
|
||||||
|
|
||||||
checkFocusedNodeStillActive(uiState, nodesAtom.get());
|
checkFocusedNodeStillActive(uiState, nodesAtom.get());
|
||||||
}
|
}
|
||||||
@@ -378,14 +360,13 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
rootId,
|
rootId,
|
||||||
uiState,
|
uiState: uiState as ReadOnlyUIState,
|
||||||
uiActions: uiActions(uiState, nodesAtom),
|
uiActions: uiActions(uiState, nodesAtom, snapshot, mutableLiveClientData),
|
||||||
nodes: nodesAtom,
|
nodes: nodesAtom,
|
||||||
frameworkEvents,
|
frameworkEvents,
|
||||||
snapshot,
|
snapshot,
|
||||||
metadata,
|
metadata,
|
||||||
perfEvents,
|
perfEvents,
|
||||||
setPlayPause,
|
|
||||||
os,
|
os,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -393,6 +374,8 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
function uiActions(
|
function uiActions(
|
||||||
uiState: UIState,
|
uiState: UIState,
|
||||||
nodes: Atom<Map<Id, ClientNode>>,
|
nodes: Atom<Map<Id, ClientNode>>,
|
||||||
|
snapshot: Atom<SnapshotInfo | null>,
|
||||||
|
liveClientData: LiveClientState,
|
||||||
): UIActions {
|
): UIActions {
|
||||||
const onExpandNode = (node: Id) => {
|
const onExpandNode = (node: Id) => {
|
||||||
uiState.expandedNodes.update((draft) => {
|
uiState.expandedNodes.update((draft) => {
|
||||||
@@ -434,9 +417,9 @@ function uiActions(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onHoverNode = (node?: Id) => {
|
const onHoverNode = (...node: Id[]) => {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
uiState.hoveredNodes.set([node]);
|
uiState.hoveredNodes.set(node);
|
||||||
} else {
|
} else {
|
||||||
uiState.hoveredNodes.set([]);
|
uiState.hoveredNodes.set([]);
|
||||||
}
|
}
|
||||||
@@ -473,6 +456,43 @@ function uiActions(
|
|||||||
uiState.viewMode.set(viewMode);
|
uiState.viewMode.set(viewMode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSetFrameworkEventMonitored = (
|
||||||
|
eventType: FrameworkEventType,
|
||||||
|
monitored: boolean,
|
||||||
|
) => {
|
||||||
|
tracker.track('framework-event-monitored', {eventType, monitored});
|
||||||
|
uiState.frameworkEventMonitoring.update((draft) =>
|
||||||
|
draft.set(eventType, monitored),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPlayPauseToggled = () => {
|
||||||
|
const isPaused = !uiState.isPaused.get();
|
||||||
|
tracker.track('play-pause-toggled', {paused: isPaused});
|
||||||
|
uiState.isPaused.set(isPaused);
|
||||||
|
if (!isPaused) {
|
||||||
|
//When going back to play mode then set the atoms to the live state to rerender the latest
|
||||||
|
//Also need to fixed expanded state for any change in active child state
|
||||||
|
uiState.expandedNodes.update((draft) => {
|
||||||
|
liveClientData.nodes.forEach((node) => {
|
||||||
|
collapseinActiveChildren(node, draft);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
nodes.set(liveClientData.nodes);
|
||||||
|
snapshot.set(liveClientData.snapshotInfo);
|
||||||
|
checkFocusedNodeStillActive(uiState, nodes.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchTermUpdatedDebounced = debounce((searchTerm: string) => {
|
||||||
|
tracker.track('search-term-updated', {searchTerm});
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
const onSearchTermUpdated = (searchTerm: string) => {
|
||||||
|
uiState.searchTerm.set(searchTerm);
|
||||||
|
searchTermUpdatedDebounced(searchTerm);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onExpandNode,
|
onExpandNode,
|
||||||
onCollapseNode,
|
onCollapseNode,
|
||||||
@@ -483,6 +503,9 @@ function uiActions(
|
|||||||
setVisualiserWidth,
|
setVisualiserWidth,
|
||||||
onSetFilterMainThreadMonitoring,
|
onSetFilterMainThreadMonitoring,
|
||||||
onSetViewMode,
|
onSetViewMode,
|
||||||
|
onSetFrameworkEventMonitored,
|
||||||
|
onPlayPauseToggled,
|
||||||
|
onSearchTermUpdated,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user