Summary: Added new tree id to the framework event. Its currently optional. The tree id is the parent component tree / tree manager this tree belongs to, this is so we can view all tree events at once in the table. We ony show the context menu item when we have the tree root tag which is set for component tree only at this point. We can add for blok and ios later Reviewed By: lblasa Differential Revision: D48268120 fbshipit-source-id: 3dee06309b146a0392ca32fbb5e8231883b8439a
207 lines
5.9 KiB
TypeScript
207 lines
5.9 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} 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 [bottomPanelComponent, setBottomPanelComponent] = useState<
|
|
ReactNode | undefined
|
|
>();
|
|
const openBottomPanelWithContent = (component: ReactNode) => {
|
|
setBottomPanelComponent(component);
|
|
};
|
|
const dismissBottomPanel = () => {
|
|
setBottomPanelComponent(undefined);
|
|
};
|
|
|
|
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.Horizontal
|
|
grow
|
|
style={{
|
|
borderRadius: theme.borderRadius,
|
|
backgroundColor: theme.backgroundWash,
|
|
}}>
|
|
<Layout.Container
|
|
grow
|
|
style={{
|
|
borderRadius: theme.borderRadius,
|
|
backgroundColor: theme.backgroundDefault,
|
|
}}>
|
|
<TreeControls />
|
|
<Tree2 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={350}>
|
|
<Inspector
|
|
os={instance.os}
|
|
metadata={metadata}
|
|
nodes={nodes}
|
|
showExtra={openBottomPanelWithContent}
|
|
/>
|
|
</DetailSidebar>
|
|
<BottomPanel dismiss={dismissBottomPanel}>
|
|
{bottomPanelComponent}
|
|
</BottomPanel>
|
|
</Layout.Horizontal>
|
|
</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 = {
|
|
dismiss: () => void;
|
|
children: React.ReactNode;
|
|
};
|
|
export function BottomPanel({dismiss, children}: BottomPanelProps) {
|
|
const bottomPanelRef = useRef<any>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (
|
|
bottomPanelRef.current &&
|
|
!bottomPanelRef.current.contains(event.target)
|
|
) {
|
|
dismiss();
|
|
}
|
|
};
|
|
// 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={400} gutter>
|
|
<Layout.ScrollContainer>{children}</Layout.ScrollContainer>
|
|
<div style={{margin: 10}}>
|
|
<Button type="ghost" style={{float: 'right'}} onClick={dismiss}>
|
|
Dismiss
|
|
</Button>
|
|
</div>
|
|
</ResizablePanel>
|
|
</div>
|
|
);
|
|
}
|