Files
flipper/desktop/plugins/public/ui-debugger/components/Controls.tsx
Luke De Feo c19dc150e6 Use event metadata in init event
Summary:
Previously we were checking the framework events stream for any new events and populating the map from that. Now we expect the init event to contain event types.

This allows us to know if the app supports framework event and therefore we dont show the controls to monitor it. Additionally we can fully populate the event monitoring dialog

Reviewed By: lblasa

Differential Revision: D42996552

fbshipit-source-id: 7850ada53d0630ba102af6c0d74d9d904f75eada
2023-02-06 04:33:11 -08:00

260 lines
6.7 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React, {useState} from 'react';
import {plugin} from '../index';
import {
Button,
Input,
Modal,
Tooltip,
Dropdown,
Menu,
Typography,
TreeSelect,
Space,
} from 'antd';
import {
MoreOutlined,
PauseCircleOutlined,
PlayCircleOutlined,
} from '@ant-design/icons';
import {usePlugin, useValue, Layout} from 'flipper-plugin';
import {FrameworkEventType} from '../types';
export const Controls: React.FC = () => {
const instance = usePlugin(plugin);
const searchTerm = useValue(instance.uiState.searchTerm);
const isPaused = useValue(instance.uiState.isPaused);
const frameworkEventMonitoring: Map<FrameworkEventType, boolean> = useValue(
instance.uiState.frameworkEventMonitoring,
);
const onSetEventMonitored: (
eventType: FrameworkEventType,
monitored: boolean,
) => void = (eventType: FrameworkEventType, monitored: boolean) => {
instance.uiState.frameworkEventMonitoring.update((draft) =>
draft.set(eventType, monitored),
);
};
return (
<Layout.Horizontal pad="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>
{frameworkEventMonitoring.size > 0 && (
<MoreOptionsMenu
onSetEventMonitored={onSetEventMonitored}
frameworkEventTypes={[...frameworkEventMonitoring.entries()]}
/>
)}
</Layout.Horizontal>
);
};
function MoreOptionsMenu({
onSetEventMonitored,
frameworkEventTypes,
}: {
onSetEventMonitored: (
eventType: FrameworkEventType,
monitored: boolean,
) => void;
frameworkEventTypes: [FrameworkEventType, boolean][];
}) {
const [showFrameworkEventsModal, setShowFrameworkEventsModal] =
useState(false);
const moreOptionsMenu = (
<Menu>
<Menu.Item
onClick={() => {
setShowFrameworkEventsModal(true);
}}>
Framework event monitoring
</Menu.Item>
</Menu>
);
return (
<>
<Dropdown
key="more"
mouseLeaveDelay={0.7}
overlay={moreOptionsMenu}
placement="bottomRight">
<Button type="text" icon={<MoreOutlined style={{fontSize: 20}} />} />
</Dropdown>
{/*invisible until shown*/}
<FrameworkEventsMonitoringModal
frameworkEventTypes={frameworkEventTypes}
onSetEventMonitored={onSetEventMonitored}
visible={showFrameworkEventsModal}
onCancel={() => setShowFrameworkEventsModal(false)}
/>
</>
);
}
function FrameworkEventsMonitoringModal({
visible,
onCancel,
onSetEventMonitored,
frameworkEventTypes,
}: {
visible: boolean;
onCancel: () => void;
onSetEventMonitored: (
eventType: FrameworkEventType,
monitored: boolean,
) => void;
frameworkEventTypes: [FrameworkEventType, boolean][];
}) {
const selectedFrameworkEvents = frameworkEventTypes
.filter(([, selected]) => selected)
.map(([eventType]) => eventType);
const treeData = buildTreeSelectData(
frameworkEventTypes.map(([type]) => type),
);
return (
<Modal
title="Framework event monitoring"
visible={visible}
footer={null}
onCancel={onCancel}>
<Space direction="vertical" size="large">
<Typography.Text>
Monitoring an event will cause the relevant node in the visualizer and
tree to highlight briefly. Additionally a running total of the number
of each event will be show in the tree inline
</Typography.Text>
<TreeSelect
treeCheckable
showSearch={false}
showCheckedStrategy={TreeSelect.SHOW_PARENT}
placeholder="Select nodes to monitor"
virtual={false} //for scrollbar
style={{
width: '100%',
}}
treeData={treeData}
treeDefaultExpandAll
value={selectedFrameworkEvents}
onSelect={(_: any, node: any) => {
for (const leaf of getAllLeaves(node)) {
onSetEventMonitored(leaf, true);
}
}}
onDeselect={(_: any, node: any) => {
for (const leaf of getAllLeaves(node)) {
onSetEventMonitored(leaf, false);
}
}}
/>
</Space>
</Modal>
);
}
type TreeSelectNode = {
title: string;
key: string;
value: string;
children: TreeSelectNode[];
};
/**
* In tree select you can select a parent which implicitly selects all children, we find them all here as the real state
* is in terms of the leaf nodes
*/
function getAllLeaves(treeSelectNode: TreeSelectNode) {
const result: string[] = [];
function getAllLeavesRec(node: TreeSelectNode) {
console.log(node);
if (node.children.length > 0) {
for (const child of node.children) {
getAllLeavesRec(child);
}
} else {
result.push(node.key);
}
}
getAllLeavesRec(treeSelectNode);
return result;
}
/**
* transformed flat event type data structure into tree
*/
function buildTreeSelectData(eventTypes: string[]): TreeSelectNode[] {
const root: TreeSelectNode = buildTreeSelectNode('root', 'root');
eventTypes.forEach((eventType) => {
const eventSubtypes = eventType.split(':');
let currentNode = root;
// Find the parent node for the current id
for (let i = 0; i < eventSubtypes.length - 1; i++) {
let foundChild = false;
for (const child of currentNode.children) {
if (child.title === eventSubtypes[i]) {
currentNode = child;
foundChild = true;
break;
}
}
if (!foundChild) {
const newNode: TreeSelectNode = buildTreeSelectNode(
eventSubtypes[i],
eventSubtypes.slice(0, i + 1).join(':'),
);
currentNode.children.push(newNode);
currentNode = newNode;
}
}
// Add the current id as a child of the parent node
currentNode.children.push(
buildTreeSelectNode(
eventSubtypes[eventSubtypes.length - 1],
eventSubtypes.slice(0, eventSubtypes.length).join(':'),
),
);
});
return root.children;
}
function buildTreeSelectNode(title: string, fullValue: string): TreeSelectNode {
return {
title: title,
key: fullValue,
value: fullValue,
children: [],
};
}