New sidebar design 1/n
Summary: Added basic infra basic types Reviewed By: lblasa Differential Revision: D50595985 fbshipit-source-id: 48ebd74bd8ccebdd8a6d69dbda344b8d831dc04f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
50b06f2efd
commit
b184500d94
@@ -18,6 +18,7 @@ import {
|
|||||||
ClientNode,
|
ClientNode,
|
||||||
Metadata,
|
Metadata,
|
||||||
SnapshotInfo,
|
SnapshotInfo,
|
||||||
|
MetadataId,
|
||||||
} from './ClientTypes';
|
} from './ClientTypes';
|
||||||
import TypedEmitter from 'typed-emitter';
|
import TypedEmitter from 'typed-emitter';
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export type LiveClientState = {
|
|||||||
nodes: Map<Id, ClientNode>;
|
nodes: Map<Id, ClientNode>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MetadataMap = Map<MetadataId, Metadata>;
|
||||||
export type Color = string;
|
export type Color = string;
|
||||||
|
|
||||||
export type UIState = {
|
export type UIState = {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {useHotkeys} from 'react-hotkeys-hook';
|
|||||||
import {Id, Metadata, MetadataId, ClientNode} from '../ClientTypes';
|
import {Id, Metadata, MetadataId, ClientNode} from '../ClientTypes';
|
||||||
import {PerfStats} from './PerfStats';
|
import {PerfStats} from './PerfStats';
|
||||||
import {Visualization2D} from './visualizer/Visualization2D';
|
import {Visualization2D} from './visualizer/Visualization2D';
|
||||||
import {Inspector} from './sidebar/Inspector';
|
|
||||||
import {TreeControls} from './tree/TreeControls';
|
import {TreeControls} from './tree/TreeControls';
|
||||||
import {Button, Spin, Typography} from 'antd';
|
import {Button, Spin, Typography} from 'antd';
|
||||||
import {QueryClientProvider} from 'react-query';
|
import {QueryClientProvider} from 'react-query';
|
||||||
@@ -30,6 +29,8 @@ import {StreamInterceptorErrorView} from './StreamInterceptorErrorView';
|
|||||||
import {queryClient} from '../utils/reactQuery';
|
import {queryClient} from '../utils/reactQuery';
|
||||||
import {FrameworkEventsTable} from './FrameworkEventsTable';
|
import {FrameworkEventsTable} from './FrameworkEventsTable';
|
||||||
import {Centered} from './shared/Centered';
|
import {Centered} from './shared/Centered';
|
||||||
|
import {SidebarV2} from './sidebarV2/SidebarV2';
|
||||||
|
import {getNode} from '../utils/map';
|
||||||
|
|
||||||
export function Component() {
|
export function Component() {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
@@ -38,6 +39,7 @@ export function Component() {
|
|||||||
const visualiserWidth = useValue(instance.uiState.visualiserWidth);
|
const visualiserWidth = useValue(instance.uiState.visualiserWidth);
|
||||||
const nodes: Map<Id, ClientNode> = useValue(instance.nodes);
|
const nodes: Map<Id, ClientNode> = useValue(instance.nodes);
|
||||||
const metadata: Map<MetadataId, Metadata> = useValue(instance.metadata);
|
const metadata: Map<MetadataId, Metadata> = useValue(instance.metadata);
|
||||||
|
const selectedNodeId = useValue(instance.uiState.selectedNode);
|
||||||
|
|
||||||
const [showPerfStats, setShowPerfStats] = useState(false);
|
const [showPerfStats, setShowPerfStats] = useState(false);
|
||||||
|
|
||||||
@@ -155,11 +157,10 @@ export function Component() {
|
|||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<DetailSidebar width={450}>
|
<DetailSidebar width={450}>
|
||||||
<Inspector
|
<SidebarV2
|
||||||
os={instance.os}
|
|
||||||
metadata={metadata}
|
metadata={metadata}
|
||||||
nodes={nodes}
|
selectedNode={getNode(selectedNodeId?.id, nodes)}
|
||||||
showExtra={openBottomPanelWithContent}
|
showBottomPanel={openBottomPanelWithContent}
|
||||||
/>
|
/>
|
||||||
</DetailSidebar>
|
</DetailSidebar>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export const Inspector: React.FC<Props> = ({
|
|||||||
frameworkEventMetadata={frameworkEventMetadata}
|
frameworkEventMetadata={frameworkEventMetadata}
|
||||||
node={selectedNode}
|
node={selectedNode}
|
||||||
events={selectedFrameworkEvents}
|
events={selectedFrameworkEvents}
|
||||||
showExtra={showExtra}
|
showBottomPanel={showExtra}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import {tracker} from '../../../utils/tracker';
|
|||||||
type Props = {
|
type Props = {
|
||||||
node: ClientNode;
|
node: ClientNode;
|
||||||
events: readonly FrameworkEvent[];
|
events: readonly FrameworkEvent[];
|
||||||
showExtra?: (title: string, element: ReactNode) => void;
|
showBottomPanel?: (title: string, element: ReactNode) => void;
|
||||||
frameworkEventMetadata: Map<FrameworkEventType, FrameworkEventMetadata>;
|
frameworkEventMetadata: Map<FrameworkEventType, FrameworkEventMetadata>;
|
||||||
onSetViewMode: (viewMode: ViewMode) => void;
|
onSetViewMode: (viewMode: ViewMode) => void;
|
||||||
};
|
};
|
||||||
@@ -50,7 +50,7 @@ type Props = {
|
|||||||
export const FrameworkEventsInspector: React.FC<Props> = ({
|
export const FrameworkEventsInspector: React.FC<Props> = ({
|
||||||
node,
|
node,
|
||||||
events,
|
events,
|
||||||
showExtra,
|
showBottomPanel: showExtra,
|
||||||
frameworkEventMetadata,
|
frameworkEventMetadata,
|
||||||
onSetViewMode,
|
onSetViewMode,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -0,0 +1,329 @@
|
|||||||
|
/**
|
||||||
|
* 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 {Input, Typography} from 'antd';
|
||||||
|
import {Panel, theme, Layout} from 'flipper-plugin';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
ClientNode,
|
||||||
|
Inspectable,
|
||||||
|
InspectableObject,
|
||||||
|
Metadata,
|
||||||
|
} from '../../ClientTypes';
|
||||||
|
import {MetadataMap} from '../../DesktopTypes';
|
||||||
|
import {NoData} from '../sidebar/inspector/NoData';
|
||||||
|
import {css, cx} from '@emotion/css';
|
||||||
|
import {upperFirst} from 'lodash';
|
||||||
|
|
||||||
|
export function AttributesInspector({
|
||||||
|
node,
|
||||||
|
metadata,
|
||||||
|
}: {
|
||||||
|
node: ClientNode;
|
||||||
|
metadata: MetadataMap;
|
||||||
|
}) {
|
||||||
|
const keys = Object.keys(node.attributes);
|
||||||
|
const sections = keys
|
||||||
|
.map((key, _) => {
|
||||||
|
/**
|
||||||
|
* The node top-level attributes refer to the displayable panels.
|
||||||
|
* The panel name is obtained by querying the metadata.
|
||||||
|
* The inspectable contains the actual attributes belonging to each panel.
|
||||||
|
*/
|
||||||
|
const metadataId: number = Number(key);
|
||||||
|
const sectionMetadata = metadata.get(metadataId);
|
||||||
|
if (sectionMetadata == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const sectionAttributes = node.attributes[
|
||||||
|
metadataId
|
||||||
|
] as InspectableObject;
|
||||||
|
|
||||||
|
return AttributeSection(
|
||||||
|
metadata,
|
||||||
|
sectionMetadata.name,
|
||||||
|
sectionAttributes,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter((section) => section != null);
|
||||||
|
|
||||||
|
if (sections.length === 0) {
|
||||||
|
return <NoData message="No data available for this element" />;
|
||||||
|
}
|
||||||
|
return <>{sections}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeSection(
|
||||||
|
metadataMap: MetadataMap,
|
||||||
|
name: string,
|
||||||
|
inspectable: InspectableObject,
|
||||||
|
) {
|
||||||
|
const children = Object.keys(inspectable.fields)
|
||||||
|
.map((key) => {
|
||||||
|
const metadataId: number = Number(key);
|
||||||
|
const attributeMetadata = metadataMap.get(metadataId);
|
||||||
|
|
||||||
|
if (attributeMetadata == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const attributeValue = inspectable.fields[metadataId];
|
||||||
|
|
||||||
|
const attributeName =
|
||||||
|
upperFirst(attributeMetadata?.name) ?? String(metadataId);
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal key={key} gap="small">
|
||||||
|
<Typography.Text
|
||||||
|
style={{
|
||||||
|
marginTop: 3, //to center with top input when multiline
|
||||||
|
flex: '0 0 30%', //take 30% of the width
|
||||||
|
color: theme.textColorSecondary,
|
||||||
|
fontWeight: 50,
|
||||||
|
}}>
|
||||||
|
{attributeName}
|
||||||
|
</Typography.Text>
|
||||||
|
|
||||||
|
<Layout.Container style={{flex: '1 1 auto'}}>
|
||||||
|
<AttributeValue
|
||||||
|
name={attributeName}
|
||||||
|
attributeMetadata={attributeMetadata}
|
||||||
|
metadataMap={metadataMap}
|
||||||
|
inspectable={attributeValue}
|
||||||
|
level={1}
|
||||||
|
/>
|
||||||
|
</Layout.Container>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter((attr) => attr != null);
|
||||||
|
|
||||||
|
if (children.length > 0) {
|
||||||
|
return (
|
||||||
|
<Panel key={name} title={name}>
|
||||||
|
<Layout.Container gap="small" padv="small">
|
||||||
|
{...children}
|
||||||
|
</Layout.Container>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disables hover and focsued states
|
||||||
|
*/
|
||||||
|
const readOnlyInput = css`
|
||||||
|
:hover {
|
||||||
|
border-color: ${theme.disabledColor} !important;
|
||||||
|
}
|
||||||
|
:focus {
|
||||||
|
border-color: ${theme.disabledColor} !important;
|
||||||
|
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-color: ${theme.disabledColor} !important;
|
||||||
|
|
||||||
|
padding: 2px 4px 2px 4px;
|
||||||
|
|
||||||
|
min-height: 20px !important; //this is for text area
|
||||||
|
`;
|
||||||
|
|
||||||
|
function StyledInput({
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
mutable,
|
||||||
|
rightAddon,
|
||||||
|
}: {
|
||||||
|
value: any;
|
||||||
|
color: string;
|
||||||
|
mutable: boolean;
|
||||||
|
rightAddon?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
size="small"
|
||||||
|
className={cx(
|
||||||
|
!mutable ? readOnlyInput : '',
|
||||||
|
css`
|
||||||
|
//set input colour when no suffix
|
||||||
|
color: ${color};
|
||||||
|
//set input colour when has suffix
|
||||||
|
.ant-input.ant-input-sm[type='text'] {
|
||||||
|
color: ${color};
|
||||||
|
}
|
||||||
|
//set colour of suffix
|
||||||
|
.ant-input.ant-input-sm[type='text'] + .ant-input-suffix {
|
||||||
|
color: ${theme.textColorSecondary};
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
bordered
|
||||||
|
readOnly={!mutable}
|
||||||
|
value={value}
|
||||||
|
suffix={rightAddon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StyledTextArea({
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
mutable,
|
||||||
|
}: {
|
||||||
|
value: any;
|
||||||
|
color: string;
|
||||||
|
mutable: boolean;
|
||||||
|
rightAddon?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Input.TextArea
|
||||||
|
autoSize
|
||||||
|
className={!mutable ? readOnlyInput : ''}
|
||||||
|
bordered
|
||||||
|
style={{color: color}}
|
||||||
|
readOnly={!mutable}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const boolColor = '#C41D7F';
|
||||||
|
const stringColor = '#AF5800';
|
||||||
|
const enumColor = '#006D75';
|
||||||
|
const numberColor = '#003EB3';
|
||||||
|
|
||||||
|
type NumberGroupValue = {value: number; addonText: string};
|
||||||
|
|
||||||
|
function NumberGroup({values}: {values: NumberGroupValue[]}) {
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal gap="small">
|
||||||
|
{values.map(({value, addonText}, idx) => (
|
||||||
|
<StyledInput
|
||||||
|
key={idx}
|
||||||
|
color={numberColor}
|
||||||
|
mutable={false}
|
||||||
|
value={value}
|
||||||
|
rightAddon={addonText}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeValue({
|
||||||
|
inspectable,
|
||||||
|
}: {
|
||||||
|
attributeMetadata: Metadata;
|
||||||
|
metadataMap: MetadataMap;
|
||||||
|
name: string;
|
||||||
|
inspectable: Inspectable;
|
||||||
|
level: number;
|
||||||
|
}) {
|
||||||
|
switch (inspectable.type) {
|
||||||
|
case 'boolean':
|
||||||
|
return (
|
||||||
|
<StyledInput
|
||||||
|
color={boolColor}
|
||||||
|
mutable={false}
|
||||||
|
value={inspectable.value ? 'TRUE' : 'FALSE'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'text':
|
||||||
|
return (
|
||||||
|
<StyledTextArea
|
||||||
|
color={stringColor}
|
||||||
|
mutable={false}
|
||||||
|
value={inspectable.value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'number':
|
||||||
|
return (
|
||||||
|
<StyledInput
|
||||||
|
color={numberColor}
|
||||||
|
mutable={false}
|
||||||
|
value={inspectable.value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'enum':
|
||||||
|
return (
|
||||||
|
<StyledInput
|
||||||
|
color={enumColor}
|
||||||
|
mutable={false}
|
||||||
|
value={inspectable.value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'size':
|
||||||
|
return (
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.width, addonText: 'W'},
|
||||||
|
{value: inspectable.value.height, addonText: 'H'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'coordinate':
|
||||||
|
return (
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.x, addonText: 'X'},
|
||||||
|
{value: inspectable.value.y, addonText: 'Y'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'coordinate3d':
|
||||||
|
return (
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.x, addonText: 'X'},
|
||||||
|
{value: inspectable.value.y, addonText: 'Y'},
|
||||||
|
{value: inspectable.value.z, addonText: 'Z'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'space':
|
||||||
|
return (
|
||||||
|
<Layout.Container gap="small" style={{flex: '0 1 auto'}}>
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.top, addonText: 'T'},
|
||||||
|
{value: inspectable.value.left, addonText: 'L'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.bottom, addonText: 'B'},
|
||||||
|
{value: inspectable.value.right, addonText: 'R'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Layout.Container>
|
||||||
|
);
|
||||||
|
case 'bounds':
|
||||||
|
return (
|
||||||
|
<Layout.Container gap="small" style={{flex: '0 1 auto'}}>
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.x, addonText: 'X'},
|
||||||
|
{value: inspectable.value.y, addonText: 'Y'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NumberGroup
|
||||||
|
values={[
|
||||||
|
{value: inspectable.value.width, addonText: 'W'},
|
||||||
|
{value: inspectable.value.height, addonText: 'H'},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Layout.Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* 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 {ClientNode, MetadataId, Metadata} from '../../ClientTypes';
|
||||||
|
import {plugin} from '../../index';
|
||||||
|
import React, {ReactNode} from 'react';
|
||||||
|
import {Layout, Tab, Tabs, usePlugin, useValue} from 'flipper-plugin';
|
||||||
|
import {NoData} from '../sidebar/inspector/NoData';
|
||||||
|
import {Tooltip} from 'antd';
|
||||||
|
import {AttributesInspector} from './AttributesInspector';
|
||||||
|
import {FrameworkEventsInspector} from '../sidebar/inspector/FrameworkEventsInspector';
|
||||||
|
import {theme} from 'flipper-plugin';
|
||||||
|
// eslint-disable-next-line rulesdir/no-restricted-imports-clone
|
||||||
|
import {Glyph} from 'flipper';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
selectedNode?: ClientNode;
|
||||||
|
metadata: Map<MetadataId, Metadata>;
|
||||||
|
showBottomPanel: (title: string, element: ReactNode) => void;
|
||||||
|
};
|
||||||
|
export function SidebarV2({selectedNode, metadata, showBottomPanel}: Props) {
|
||||||
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
|
const frameworkEventMetadata = useValue(instance.frameworkEventMetadata);
|
||||||
|
|
||||||
|
if (!selectedNode) {
|
||||||
|
return <NoData message="Please select a node to view its details" />;
|
||||||
|
}
|
||||||
|
const selectedFrameworkEvents = selectedNode.id
|
||||||
|
? instance.frameworkEvents.getAllRecordsByIndex({nodeId: selectedNode.id})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Container gap pad>
|
||||||
|
<Tabs
|
||||||
|
localStorageKeyOverride="sidebar-tabs"
|
||||||
|
grow
|
||||||
|
centered
|
||||||
|
key={selectedNode.id}>
|
||||||
|
<Tab
|
||||||
|
tab={
|
||||||
|
<Tooltip title="Attributes">
|
||||||
|
<Layout.Horizontal center>
|
||||||
|
<Glyph name="data-table" size={16} color={theme.primaryColor} />
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Tooltip>
|
||||||
|
}>
|
||||||
|
<AttributesInspector node={selectedNode} metadata={metadata} />
|
||||||
|
</Tab>
|
||||||
|
{selectedFrameworkEvents?.length > 0 && (
|
||||||
|
<Tab
|
||||||
|
key={'events'}
|
||||||
|
tab={
|
||||||
|
<Tooltip title="Events">
|
||||||
|
<Layout.Horizontal center>
|
||||||
|
<Glyph
|
||||||
|
name="weather-thunder"
|
||||||
|
size={16}
|
||||||
|
color={theme.primaryColor}
|
||||||
|
/>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Tooltip>
|
||||||
|
}>
|
||||||
|
<FrameworkEventsInspector
|
||||||
|
onSetViewMode={instance.uiActions.onSetViewMode}
|
||||||
|
frameworkEventMetadata={frameworkEventMetadata}
|
||||||
|
node={selectedNode}
|
||||||
|
events={selectedFrameworkEvents}
|
||||||
|
showBottomPanel={showBottomPanel}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
|
</Tabs>
|
||||||
|
</Layout.Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ant-design/icons": "*",
|
"@ant-design/icons": "*",
|
||||||
"@emotion/styled": "*",
|
"@emotion/styled": "*",
|
||||||
|
"@emotion/css": "*",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-dom": "*",
|
"@types/react-dom": "*",
|
||||||
|
|||||||
Reference in New Issue
Block a user