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 {DetailSidebar, Layout, usePlugin, useValue} from 'flipper-plugin';
|
||||
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 {Tree} from './Tree';
|
||||
import {Visualization2D} from './Visualization2D';
|
||||
import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers';
|
||||
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() {
|
||||
const instance = usePlugin(plugin);
|
||||
@@ -33,6 +34,7 @@ export function Component() {
|
||||
const searchTerm = useValue(instance.uiState.searchTerm);
|
||||
const {ctrlPressed} = useKeyboardModifiers();
|
||||
|
||||
const isPaused = useValue(instance.uiState.isPaused);
|
||||
function renderSidebar(
|
||||
node: UINode | undefined,
|
||||
metadata: Map<MetadataId, Metadata>,
|
||||
@@ -53,10 +55,26 @@ export function Component() {
|
||||
return (
|
||||
<Layout.Horizontal grow>
|
||||
<Layout.Container grow pad="medium" gap="small">
|
||||
<Input
|
||||
value={searchTerm}
|
||||
onChange={(e) => instance.uiState.searchTerm.set(e.target.value)}
|
||||
/>
|
||||
<Layout.Horizontal padh="small" gap="small">
|
||||
<Input
|
||||
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>
|
||||
<Tree
|
||||
selectedNode={selectedNode}
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {PluginClient, createState, createDataSource} from 'flipper-plugin';
|
||||
import {
|
||||
PluginClient,
|
||||
createState,
|
||||
createDataSource,
|
||||
produce,
|
||||
} from 'flipper-plugin';
|
||||
import {
|
||||
Events,
|
||||
Id,
|
||||
@@ -20,6 +25,12 @@ import {
|
||||
} from './types';
|
||||
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>) {
|
||||
const rootId = createState<Id | undefined>(undefined);
|
||||
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 snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>(
|
||||
null,
|
||||
);
|
||||
const snapshot = createState<SnapshotInfo | null>(null);
|
||||
|
||||
const uiState = {
|
||||
//used to disabled hover effects which cause rerenders and mess up the existing context menu
|
||||
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 nodes are sorted by area since you most likely want to select the smallest node under your cursor
|
||||
hoveredNodes: createState<Id[]>([]),
|
||||
@@ -67,8 +78,8 @@ export function plugin(client: PluginClient<Events>) {
|
||||
};
|
||||
|
||||
client.onMessage('coordinateUpdate', (event) => {
|
||||
nodes.update((draft) => {
|
||||
const node = draft.get(event.nodeId);
|
||||
liveClientData = produce(liveClientData, (draft) => {
|
||||
const node = draft.nodes.get(event.nodeId);
|
||||
if (!node) {
|
||||
console.warn(`Coordinate update for non existing node `, event);
|
||||
} else {
|
||||
@@ -76,17 +87,48 @@ export function plugin(client: PluginClient<Events>) {
|
||||
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>();
|
||||
client.onMessage('subtreeUpdate', (event) => {
|
||||
if (event.snapshot) {
|
||||
snapshot.set({nodeId: event.rootId, base64Image: event.snapshot});
|
||||
}
|
||||
liveClientData = produce(liveClientData, (draft) => {
|
||||
if (event.snapshot) {
|
||||
draft.snapshotInfo = {
|
||||
nodeId: event.rootId,
|
||||
base64Image: event.snapshot,
|
||||
};
|
||||
}
|
||||
|
||||
nodes.update((draft) => {
|
||||
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);
|
||||
|
||||
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);
|
||||
if (!uiState.isPaused.get()) {
|
||||
//we need to not do this while paused as you may move to another screen / tab
|
||||
//and it would collapse the tree node for the activity you were paused on.
|
||||
collapseinActiveChildren(node, draft);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!uiState.isPaused.get()) {
|
||||
nodes.set(liveClientData.nodes);
|
||||
snapshot.set(liveClientData.snapshotInfo);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
rootId,
|
||||
uiState,
|
||||
nodes,
|
||||
metadata,
|
||||
snapshot,
|
||||
metadata,
|
||||
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';
|
||||
|
||||
Reference in New Issue
Block a user