Added ability to pause the incoming updates from the client
Summary: There were a few subtleties around what to the auto expanding / collapsing for active children but otherwise this is quite straightforward Reviewed By: lblasa Differential Revision: D41548252 fbshipit-source-id: c153d00210d859463a51753dadf2e5aabeb7ea35
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ced04c7cec
commit
57dcf72763
@@ -11,13 +11,14 @@ import React, {useState} from 'react';
|
|||||||
import {plugin} from '../index';
|
import {plugin} from '../index';
|
||||||
import {DetailSidebar, Layout, usePlugin, useValue} from 'flipper-plugin';
|
import {DetailSidebar, Layout, usePlugin, useValue} from 'flipper-plugin';
|
||||||
import {useHotkeys} from 'react-hotkeys-hook';
|
import {useHotkeys} from 'react-hotkeys-hook';
|
||||||
import {Id, Metadata, MetadataId, Snapshot, UINode} from '../types';
|
import {Id, Metadata, MetadataId, UINode} from '../types';
|
||||||
import {PerfStats} from './PerfStats';
|
import {PerfStats} from './PerfStats';
|
||||||
import {Tree} from './Tree';
|
import {Tree} from './Tree';
|
||||||
import {Visualization2D} from './Visualization2D';
|
import {Visualization2D} from './Visualization2D';
|
||||||
import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers';
|
import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers';
|
||||||
import {Inspector} from './sidebar/Inspector';
|
import {Inspector} from './sidebar/Inspector';
|
||||||
import {Input, Spin} from 'antd';
|
import {Button, Input, Spin, Tooltip} from 'antd';
|
||||||
|
import {PauseCircleOutlined, PlayCircleOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
export function Component() {
|
export function Component() {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
@@ -33,6 +34,7 @@ export function Component() {
|
|||||||
const searchTerm = useValue(instance.uiState.searchTerm);
|
const searchTerm = useValue(instance.uiState.searchTerm);
|
||||||
const {ctrlPressed} = useKeyboardModifiers();
|
const {ctrlPressed} = useKeyboardModifiers();
|
||||||
|
|
||||||
|
const isPaused = useValue(instance.uiState.isPaused);
|
||||||
function renderSidebar(
|
function renderSidebar(
|
||||||
node: UINode | undefined,
|
node: UINode | undefined,
|
||||||
metadata: Map<MetadataId, Metadata>,
|
metadata: Map<MetadataId, Metadata>,
|
||||||
@@ -53,10 +55,26 @@ export function Component() {
|
|||||||
return (
|
return (
|
||||||
<Layout.Horizontal grow>
|
<Layout.Horizontal grow>
|
||||||
<Layout.Container grow pad="medium" gap="small">
|
<Layout.Container grow pad="medium" gap="small">
|
||||||
<Input
|
<Layout.Horizontal padh="small" gap="small">
|
||||||
value={searchTerm}
|
<Input
|
||||||
onChange={(e) => instance.uiState.searchTerm.set(e.target.value)}
|
value={searchTerm}
|
||||||
/>
|
onChange={(e) => instance.uiState.searchTerm.set(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
shape="circle"
|
||||||
|
onClick={() =>
|
||||||
|
instance.setPlayPause(!instance.uiState.isPaused.get())
|
||||||
|
}
|
||||||
|
icon={
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
isPaused ? 'Resume live updates' : 'Pause incoming updates'
|
||||||
|
}>
|
||||||
|
{isPaused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
|
||||||
|
</Tooltip>
|
||||||
|
}></Button>
|
||||||
|
</Layout.Horizontal>
|
||||||
<Layout.ScrollContainer>
|
<Layout.ScrollContainer>
|
||||||
<Tree
|
<Tree
|
||||||
selectedNode={selectedNode}
|
selectedNode={selectedNode}
|
||||||
|
|||||||
@@ -7,7 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {PluginClient, createState, createDataSource} from 'flipper-plugin';
|
import {
|
||||||
|
PluginClient,
|
||||||
|
createState,
|
||||||
|
createDataSource,
|
||||||
|
produce,
|
||||||
|
} from 'flipper-plugin';
|
||||||
import {
|
import {
|
||||||
Events,
|
Events,
|
||||||
Id,
|
Id,
|
||||||
@@ -20,6 +25,12 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import './node_modules/react-complex-tree/lib/style.css';
|
import './node_modules/react-complex-tree/lib/style.css';
|
||||||
|
|
||||||
|
type SnapshotInfo = {nodeId: Id; base64Image: Snapshot};
|
||||||
|
type LiveClientState = {
|
||||||
|
snapshotInfo: SnapshotInfo | null;
|
||||||
|
nodes: Map<Id, UINode>;
|
||||||
|
};
|
||||||
|
|
||||||
export function plugin(client: PluginClient<Events>) {
|
export function plugin(client: PluginClient<Events>) {
|
||||||
const rootId = createState<Id | undefined>(undefined);
|
const rootId = createState<Id | undefined>(undefined);
|
||||||
const metadata = createState<Map<MetadataId, Metadata>>(new Map());
|
const metadata = createState<Map<MetadataId, Metadata>>(new Map());
|
||||||
@@ -49,14 +60,14 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const nodes = createState<Map<Id, UINode>>(new Map());
|
const nodes = createState<Map<Id, UINode>>(new Map());
|
||||||
const snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>(
|
const snapshot = createState<SnapshotInfo | null>(null);
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const uiState = {
|
const uiState = {
|
||||||
//used to disabled hover effects which cause rerenders and mess up the existing context menu
|
//used to disabled hover effects which cause rerenders and mess up the existing context menu
|
||||||
isContextMenuOpen: createState<boolean>(false),
|
isContextMenuOpen: createState<boolean>(false),
|
||||||
|
|
||||||
|
isPaused: createState(false),
|
||||||
|
|
||||||
//The reason for the array as that user could be hovering multiple overlapping nodes at once in the visualiser.
|
//The reason for the array as that user could be hovering multiple overlapping nodes at once in the visualiser.
|
||||||
//The nodes are sorted by area since you most likely want to select the smallest node under your cursor
|
//The nodes are sorted by area since you most likely want to select the smallest node under your cursor
|
||||||
hoveredNodes: createState<Id[]>([]),
|
hoveredNodes: createState<Id[]>([]),
|
||||||
@@ -67,8 +78,8 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
client.onMessage('coordinateUpdate', (event) => {
|
client.onMessage('coordinateUpdate', (event) => {
|
||||||
nodes.update((draft) => {
|
liveClientData = produce(liveClientData, (draft) => {
|
||||||
const node = draft.get(event.nodeId);
|
const node = draft.nodes.get(event.nodeId);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
console.warn(`Coordinate update for non existing node `, event);
|
console.warn(`Coordinate update for non existing node `, event);
|
||||||
} else {
|
} else {
|
||||||
@@ -76,17 +87,48 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
node.bounds.y = event.coordinate.y;
|
node.bounds.y = event.coordinate.y;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (uiState.isPaused.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.set(liveClientData.nodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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.treeState.update((draft) => {
|
||||||
|
liveClientData.nodes.forEach((node) => {
|
||||||
|
collapseinActiveChildren(node, draft);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
nodes.set(liveClientData.nodes);
|
||||||
|
snapshot.set(liveClientData.snapshotInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//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
|
||||||
|
let liveClientData: LiveClientState = {
|
||||||
|
snapshotInfo: null,
|
||||||
|
nodes: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
const seenNodes = new Set<Id>();
|
const seenNodes = new Set<Id>();
|
||||||
client.onMessage('subtreeUpdate', (event) => {
|
client.onMessage('subtreeUpdate', (event) => {
|
||||||
if (event.snapshot) {
|
liveClientData = produce(liveClientData, (draft) => {
|
||||||
snapshot.set({nodeId: event.rootId, base64Image: event.snapshot});
|
if (event.snapshot) {
|
||||||
}
|
draft.snapshotInfo = {
|
||||||
|
nodeId: event.rootId,
|
||||||
|
base64Image: event.snapshot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
nodes.update((draft) => {
|
|
||||||
event.nodes.forEach((node) => {
|
event.nodes.forEach((node) => {
|
||||||
draft.set(node.id, node);
|
draft.nodes.set(node.id, node);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,28 +139,42 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
}
|
}
|
||||||
seenNodes.add(node.id);
|
seenNodes.add(node.id);
|
||||||
|
|
||||||
if (node.activeChild) {
|
if (!uiState.isPaused.get()) {
|
||||||
const inactiveChildren = node.children.filter(
|
//we need to not do this while paused as you may move to another screen / tab
|
||||||
(child) => child !== node.activeChild,
|
//and it would collapse the tree node for the activity you were paused on.
|
||||||
);
|
collapseinActiveChildren(node, draft);
|
||||||
|
|
||||||
draft.expandedNodes = draft.expandedNodes.filter(
|
|
||||||
(nodeId) => !inactiveChildren.includes(nodeId),
|
|
||||||
);
|
|
||||||
draft.expandedNodes.push(node.activeChild);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!uiState.isPaused.get()) {
|
||||||
|
nodes.set(liveClientData.nodes);
|
||||||
|
snapshot.set(liveClientData.snapshotInfo);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rootId,
|
rootId,
|
||||||
uiState,
|
uiState,
|
||||||
nodes,
|
nodes,
|
||||||
metadata,
|
|
||||||
snapshot,
|
snapshot,
|
||||||
|
metadata,
|
||||||
perfEvents,
|
perfEvents,
|
||||||
|
setPlayPause,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collapseinActiveChildren(node: UINode, draft: TreeState) {
|
||||||
|
if (node.activeChild) {
|
||||||
|
const inactiveChildren = node.children.filter(
|
||||||
|
(child) => child !== node.activeChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
draft.expandedNodes = draft.expandedNodes.filter(
|
||||||
|
(nodeId) => !inactiveChildren.includes(nodeId),
|
||||||
|
);
|
||||||
|
draft.expandedNodes.push(node.activeChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {Component} from './components/main';
|
export {Component} from './components/main';
|
||||||
|
|||||||
Reference in New Issue
Block a user