Summary:
Quick notes:
- This looks worse than it is. It adds mandatory parentheses to single argument lambdas. Lots of outrage on Twitter about it, personally I'm {emoji:1f937_200d_2642} about it.
- Space before function, e.g. `a = function ()` is now enforced. I like this because both were fine before.
- I added `eslint-config-prettier` to the config because otherwise a ton of rules conflict with eslint itself.
Close https://github.com/facebook/flipper/pull/915
Reviewed By: jknoxville
Differential Revision: D20594929
fbshipit-source-id: ca1c65376b90e009550dd6d1f4e0831d32cbff03
176 lines
4.3 KiB
TypeScript
176 lines
4.3 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,
|
|
SidebarExtensions,
|
|
Element,
|
|
Client,
|
|
Logger,
|
|
} from 'flipper';
|
|
import {Component} from 'react';
|
|
import deepEqual from 'deep-equal';
|
|
import React from 'react';
|
|
import {useMemo, useEffect} from 'react';
|
|
import {kebabCase} from 'lodash';
|
|
|
|
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 Component<InspectorSidebarSectionProps> {
|
|
setValue = (path: Array<string>, value: any) => {
|
|
if (this.props.onValueChanged) {
|
|
this.props.onValueChanged([this.props.id, ...path], value);
|
|
}
|
|
};
|
|
|
|
shouldComponentUpdate(nextProps: InspectorSidebarSectionProps) {
|
|
return (
|
|
!deepEqual(nextProps, this.props) ||
|
|
this.props.id !== nextProps.id ||
|
|
this.props.onValueChanged !== nextProps.onValueChanged
|
|
);
|
|
}
|
|
|
|
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;
|
|
if (!element || !element.data) {
|
|
return <NoData grow>No data</NoData>;
|
|
}
|
|
|
|
const [sectionDefs, sectionKeys] = useMemo(() => {
|
|
const sectionKeys = [];
|
|
const sectionDefs = [];
|
|
|
|
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];
|
|
}, [props.element]);
|
|
|
|
const sections: Array<React.ReactNode> = (
|
|
(SidebarExtensions &&
|
|
SidebarExtensions.map((ext) =>
|
|
ext(props.client, props.realClient, element.id, props.logger),
|
|
)) ||
|
|
[]
|
|
).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`),
|
|
);
|
|
}, [props.element?.data]);
|
|
return <>{sections}</>;
|
|
};
|
|
|
|
export default Sidebar;
|