diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index 17c88141a..7d4fb19a9 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -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, @@ -53,10 +55,26 @@ export function Component() { return ( - instance.uiState.searchTerm.set(e.target.value)} - /> + + instance.uiState.searchTerm.set(e.target.value)} + /> + + ; +}; + export function plugin(client: PluginClient) { const rootId = createState(undefined); const metadata = createState>(new Map()); @@ -49,14 +60,14 @@ export function plugin(client: PluginClient) { }); const nodes = createState>(new Map()); - const snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>( - null, - ); + const snapshot = createState(null); const uiState = { //used to disabled hover effects which cause rerenders and mess up the existing context menu isContextMenuOpen: createState(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([]), @@ -67,8 +78,8 @@ export function plugin(client: PluginClient) { }; 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) { 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(); 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) { } 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';