Files
flipper/desktop/plugins/public/layout/InspectorSidebar.tsx
Michel Weststrate 5db2ef1275 Perf fixes
Summary:
This diff fixes some more perf bottlenecks in the layout inspector (see the diffs earlier in the stack for the total picture). Mostly:

1. Pass down stable refs from the root for callbacks and configuration
2. Remove the deep-equality check in the sidebar section rendering, which has a pretty significant constant overhead, especially if the selection didn't change
3. If the selection changes, the correct semantics is to reset the sidebar rather than trying to reconcile the elements. (A consequence of this is that Panel collapse state isn't preserved atm after changing selection, will address that in a later diff)

This reduces average render time for sidebar from ~20 to ~2 ms.

Reviewed By: priteshrnandgaonkar

Differential Revision: D27677353

fbshipit-source-id: ba183b7e3d778c0b3c8e7ca0d51535ce99a097ca
2021-04-15 07:48:33 -07:00

176 lines
4.4 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its 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 {
ManagedDataInspector,
Panel,
FlexCenter,
styled,
colors,
PluginClient,
Element,
Client,
Logger,
} from 'flipper';
import {PureComponent} from 'react';
import React from 'react';
import {useMemo, useEffect} from 'react';
import {kebabCase} from 'lodash';
import {SidebarExtensions} from './extensions/fb-stubs/SidebarExtensions';
const NoData = styled(FlexCenter)({
fontSize: 18,
color: colors.macOSTitleBarIcon,
});
type OnValueChanged = (path: Array<string>, val: any) => void;
type InspectorSidebarSectionProps = {
data: any;
id: string;
onValueChanged: OnValueChanged | null;
tooltips?: Object;
};
class InspectorSidebarSection extends PureComponent<InspectorSidebarSectionProps> {
setValue = (path: Array<string>, value: any) => {
if (this.props.onValueChanged) {
this.props.onValueChanged([this.props.id, ...path], value);
}
};
extractValue = (val: any, _depth: number) => {
if (val && val.__type__) {
return {
mutable: Boolean(val.__mutable__),
type: val.__type__ === 'auto' ? typeof val.value : val.__type__,
value: val.value,
};
} else {
return {
mutable: typeof val === 'object',
type: typeof val,
value: val,
};
}
};
render() {
const {id} = this.props;
return (
<Panel heading={id} floating={false} grow={false}>
<ManagedDataInspector
data={this.props.data}
setValue={this.props.onValueChanged ? this.setValue : undefined}
extractValue={this.extractValue}
expandRoot={true}
collapsed={true}
tooltips={this.props.tooltips}
/>
</Panel>
);
}
}
type Props = {
element: Element | null;
tooltips?: Object;
onValueChanged: OnValueChanged | null;
client: PluginClient;
realClient: Client;
logger: Logger;
};
const Sidebar: React.FC<Props> = (props: Props) => {
const {element} = props;
const [sectionDefs, sectionKeys] = useMemo(() => {
const sectionKeys = [];
const sectionDefs = [];
if (element && element.data)
for (const key in element.data) {
if (key === 'Extra Sections') {
for (const extraSection in element.data[key]) {
const section = element.data[key][extraSection];
let data = {};
// data might be sent as stringified JSON, we want to parse it for a nicer persentation.
if (typeof section === 'string') {
try {
data = JSON.parse(section);
} catch (e) {
// data was not a valid JSON, type is required to be an object
console.error(
`ElementsInspector unable to parse extra section: ${extraSection}`,
);
data = {};
}
} else {
data = section;
}
sectionKeys.push(kebabCase(extraSection));
sectionDefs.push({
key: extraSection,
id: extraSection,
data: data,
});
}
} else {
sectionKeys.push(kebabCase(key));
sectionDefs.push({
key,
id: key,
data: element.data[key],
});
}
}
return [sectionDefs, sectionKeys];
}, [element]);
const sections: Array<React.ReactNode> = (
(SidebarExtensions &&
element?.data &&
Object.entries(SidebarExtensions).map(([ext, Comp]) => (
<Comp
key={ext}
client={props.client}
realClient={props.realClient}
logger={props.logger}
selectedNode={element}
/>
))) ||
[]
).concat(
sectionDefs.map((def) => (
<InspectorSidebarSection
tooltips={props.tooltips}
key={def.key}
id={def.id}
data={def.data}
onValueChanged={props.onValueChanged}
/>
)),
);
useEffect(() => {
sectionKeys.map((key) =>
props.logger.track('usage', `layout-sidebar-extension:${key}:loaded`),
);
}, [sectionKeys.join(',')]);
if (!element || !element.data) {
return <NoData grow>No data</NoData>;
}
return <>{sections}</>;
};
export default Sidebar;