Files
flipper/desktop/plugins/public/ui-debugger/components/main.tsx
Luke De Feo d5814ea17c Use bottom panel as detail view for framework events
Summary: Now when you click on an event the bottom bar appears automatically showing you every thing you need

Reviewed By: lblasa

Differential Revision: D48318694

fbshipit-source-id: 6505e439d949941dc0e091b9576d7d1321d8a05f
2023-08-21 04:24:16 -07:00

251 lines
7.0 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, {ReactNode, useEffect, useRef, useState} from 'react';
import {plugin} from '../index';
import {
DetailSidebar,
Layout,
usePlugin,
useValue,
_Sidebar as ResizablePanel,
theme,
} from 'flipper-plugin';
import {useHotkeys} from 'react-hotkeys-hook';
import {Id, Metadata, MetadataId, ClientNode} from '../ClientTypes';
import {PerfStats} from './PerfStats';
import {Visualization2D} from './visualizer/Visualization2D';
import {Inspector} from './sidebar/Inspector';
import {TreeControls} from './tree/TreeControls';
import {Button, Spin, Typography} from 'antd';
import {QueryClientProvider} from 'react-query';
import {Tree2} from './tree/Tree';
import {StreamInterceptorErrorView} from './StreamInterceptorErrorView';
import {queryClient} from '../utils/reactQuery';
import {FrameworkEventsTable} from './FrameworkEventsTable';
export function Component() {
const instance = usePlugin(plugin);
const rootId = useValue(instance.rootId);
const streamState = useValue(instance.uiState.streamState);
const visualiserWidth = useValue(instance.uiState.visualiserWidth);
const nodes: Map<Id, ClientNode> = useValue(instance.nodes);
const metadata: Map<MetadataId, Metadata> = useValue(instance.metadata);
const [showPerfStats, setShowPerfStats] = useState(false);
useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show));
const viewMode = useValue(instance.uiState.viewMode);
const [bottomPanel, setBottomPanel] = useState<
{title: string; component: ReactNode} | undefined
>();
const openBottomPanelWithContent = (title: string, component: ReactNode) => {
setBottomPanel({title, component});
};
const dismissBottomPanel = () => {
setBottomPanel(undefined);
};
const [bottomPanelHeight, setBottomPanelHeight] = useState(400);
if (showPerfStats)
return (
<PerfStats
frameworkEvents={instance.frameworkEvents}
uiState={instance.uiState}
rootId={rootId}
nodes={nodes}
events={instance.perfEvents}
/>
);
if (streamState.state === 'FatalError') {
return (
<StreamInterceptorErrorView
title="Fatal Error"
message={`Something has gone horribly wrong, we are aware of this and are looking into it, details ${streamState.error.name} ${streamState.error.message}`}
button={
<Button onClick={streamState.clearCallBack} type="primary">
Reset
</Button>
}
/>
);
}
if (streamState.state === 'StreamInterceptorRetryableError') {
return (
<StreamInterceptorErrorView
message={streamState.error.message}
title={streamState.error.title}
button={
<Button onClick={streamState.retryCallback} type="primary">
Retry
</Button>
}
/>
);
}
if (rootId == null || streamState.state === 'RetryingAfterError') {
return (
<Centered>
<Spin data-testid="loading-indicator" />
</Centered>
);
}
if (viewMode.mode === 'frameworkEventsTable') {
return <FrameworkEventsTable treeId={viewMode.treeRootId} />;
}
return (
<QueryClientProvider client={queryClient}>
<Layout.Container grow>
<Layout.Horizontal
grow
style={{
borderRadius: theme.borderRadius,
backgroundColor: theme.backgroundWash,
}}>
<Layout.Container
grow
style={{
borderRadius: theme.borderRadius,
backgroundColor: theme.backgroundDefault,
}}>
<TreeControls />
<Tree2
additionalHeightOffset={
bottomPanel != null ? bottomPanelHeight : 0
}
nodes={nodes}
rootId={rootId}
/>
</Layout.Container>
<ResizablePanel
position="right"
minWidth={200}
width={visualiserWidth + theme.space.large}
maxWidth={800}
onResize={(width) => {
instance.uiActions.setVisualiserWidth(width);
}}
gutter>
<Visualization2D
width={visualiserWidth}
nodes={nodes}
onSelectNode={instance.uiActions.onSelectNode}
/>
</ResizablePanel>
<DetailSidebar width={450}>
<Inspector
os={instance.os}
metadata={metadata}
nodes={nodes}
showExtra={openBottomPanelWithContent}
/>
</DetailSidebar>
</Layout.Horizontal>
{bottomPanel && (
<BottomPanel
title={bottomPanel.title}
height={bottomPanelHeight}
setHeight={setBottomPanelHeight}
dismiss={dismissBottomPanel}>
{bottomPanel.component}
</BottomPanel>
)}
</Layout.Container>
</QueryClientProvider>
);
}
export function Centered(props: {children: React.ReactNode}) {
return (
<Layout.Horizontal center grow>
<Layout.Container center grow>
{props.children}
</Layout.Container>
</Layout.Horizontal>
);
}
type BottomPanelProps = {
title: string;
dismiss: () => void;
children: React.ReactNode;
height: number;
setHeight: (height: number) => void;
};
export function BottomPanel({
title,
dismiss,
children,
height,
setHeight,
}: BottomPanelProps) {
const bottomPanelRef = useRef<any>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
bottomPanelRef.current &&
!bottomPanelRef.current.contains(event.target)
) {
setTimeout(() => {
//push to back of event queue so that you can still select item in the tree
dismiss();
}, 0);
}
};
// Add event listener when the component is mounted.
document.addEventListener('mousedown', handleClickOutside);
// Remove event listener when component is unmounted.
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [bottomPanelRef, dismiss]);
if (!children) {
return <></>;
}
return (
<div ref={bottomPanelRef}>
<ResizablePanel
position="bottom"
minHeight={200}
height={height}
onResize={(_, height) => setHeight(height)}
gutter>
<Layout.Container grow>
<Layout.Horizontal
center
pad="small"
style={{
justifyContent: 'space-between',
}}>
<Typography.Title level={3}>{title}</Typography.Title>
<Button type="ghost" onClick={dismiss}>
Dismiss
</Button>
</Layout.Horizontal>
<Layout.ScrollContainer pad="small">
{children}
</Layout.ScrollContainer>
</Layout.Container>
</ResizablePanel>
</div>
);
}