desktop/plugins/public/layout/InspectorSidebar.tsx (200 lines of code) (raw):

/** * 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 { ManagedDataInspector, FlexCenter, styled, colors, PluginClient, Element, Client, Logger, } from 'flipper'; import {Panel} from 'flipper-plugin'; import {PureComponent, useState} 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 title={id} pad> <ManagedDataInspector data={this.props.data} setValue={this.props.onValueChanged ? this.setValue : undefined} extractValue={this.extractValue} expandRoot collapsed tooltips={this.props.tooltips} /> </Panel> ); } } type Props = { element: Element | null; tooltips?: Object; onValueChanged: OnValueChanged | null; client: PluginClient; realClient: Client; logger: Logger; inSnapshotMode: boolean; }; type ElementSnapshot = { element: Element | null; snapshot: String | null; }; const Sidebar: React.FC<Props> = (props: Props) => { const {element} = props; const [elementSnapshot, setElementSnapshot] = useState<ElementSnapshot>(); 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]); useEffect(() => { if ( props.inSnapshotMode && (!elementSnapshot || elementSnapshot.element != element) ) { props.client .call('getSnapshot', { id: props.element?.id, name: props.element?.name, }) .then((response) => { setElementSnapshot({element: element, snapshot: response.snapshot}); }) .catch((e) => { console.log( 'ElementsInspector unable to obtain screenshot for the selected item', e, ); }); } }, [ element, elementSnapshot, props.client, props.element?.id, props.element?.name, props.inSnapshotMode, ]); const sidebarExtensions = (SidebarExtensions && element?.data && Object.entries(SidebarExtensions).map(([ext, Comp]) => ( <Comp key={ext} client={props.client} realClient={props.realClient} logger={props.logger} selectedNode={element} /> ))) || []; const sidebarInspector = sectionDefs.map((def) => ( <InspectorSidebarSection tooltips={props.tooltips} key={def.key} id={def.id} data={def.data} onValueChanged={props.onValueChanged} /> )); const sidebarPreview = props.inSnapshotMode && elementSnapshot?.snapshot ? ( <Panel key="preview" title="Preview" pad> <img style={{ display: 'block', marginLeft: 'auto', marginRight: 'auto', width: '100%', }} src={'data:image/png;base64,' + elementSnapshot?.snapshot} /> </Panel> ) : null; 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 ( <> {sidebarExtensions} {sidebarInspector} {sidebarPreview} </> ); }; export default Sidebar;