/** * 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 {Divider, Input, Typography} from 'antd'; import {Panel, theme, Layout, styled} from 'flipper-plugin'; import React from 'react'; import { ClientNode, Color, Inspectable, InspectableObject, Metadata, } from '../../ClientTypes'; import {MetadataMap} from '../../DesktopTypes'; import {NoData} from '../sidebar/inspector/NoData'; import {css, cx} from '@emotion/css'; import {upperFirst, sortBy} from 'lodash'; import {any} from 'lodash/fp'; import {InspectableColor} from '../../ClientTypes'; 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 ; } return <>{sections}; } function AttributeSection( metadataMap: MetadataMap, name: string, inspectable: InspectableObject, ) { const attributesOrSubSubsections = Object.entries(inspectable.fields).map( ([fieldKey, attributeValue]) => { const metadataId: number = Number(fieldKey); const attributeMetadata = metadataMap.get(metadataId); const attributeName = upperFirst(attributeMetadata?.name) ?? String(metadataId); //subsections are complex types that are only 1 level deep const isSubSection = attributeValue.type === 'object' && !any( (inspectable) => inspectable.type === 'array' || inspectable.type === 'object', Object.values(attributeValue.fields), ); return { attributeName, attributeMetadata, isSubSection, attributeValue, metadataId, }; }, ); //push sub sections to the end const sortedAttributesOrSubsections = sortBy( attributesOrSubSubsections, [(item) => item.isSubSection], (item) => item.attributeName, ); const children = sortedAttributesOrSubsections .map(({isSubSection, attributeValue, attributeMetadata, attributeName}) => { if (attributeMetadata == null) { return null; } if (isSubSection) { if (attributeValue.type === 'object') { return ( ); } } return ( ); }) .filter((attr) => attr != null); if (children.length > 0) { return ( {...children} ); } else { return null; } } function SubSection({ attributeName, inspectableObject, metadataMap, }: { attributeName: string; inspectableObject: InspectableObject; metadataMap: MetadataMap; }) { return ( {attributeName} {Object.entries(inspectableObject.fields).map(([key, value]) => { const metadataId: number = Number(key); const attributeMetadata = metadataMap.get(metadataId); if (attributeMetadata == null) { return null; } const attributeName = upperFirst(attributeMetadata?.name) ?? String(metadataId); return ( ); })} ); } function NamedAttribute({ key, name, value, metadataMap, attributeMetadata, }: { name: string; value: Inspectable; attributeMetadata: Metadata; metadataMap: MetadataMap; key: string; }) { return ( {name} ); } /** * 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 ( ); } function StyledTextArea({ value, color, mutable, }: { value: any; color: string; mutable: boolean; rightAddon?: string; }) { return ( ); } const boolColor = '#C41D7F'; const stringColor = '#AF5800'; const enumColor = '#006D75'; const numberColor = '#003EB3'; type NumberGroupValue = {value: number; addonText: string}; function NumberGroup({values}: {values: NumberGroupValue[]}) { return ( {values.map(({value, addonText}, idx) => ( ))} ); } function AttributeValue({ inspectable, }: { attributeMetadata: Metadata; metadataMap: MetadataMap; name: string; inspectable: Inspectable; level: number; }) { switch (inspectable.type) { case 'boolean': return ( ); case 'text': return ( ); case 'number': return ( ); case 'enum': return ( ); case 'size': return ( ); case 'coordinate': return ( ); case 'coordinate3d': return ( ); case 'space': return ( ); case 'bounds': return ( ); case 'color': return ; } return null; } function ColorInspector({inspectable}: {inspectable: InspectableColor}) { return ( ); } const ColorPreview = styled.div(({background}: {background: string}) => ({ width: '28px', height: '28px', borderRadius: '8px', borderColor: theme.disabledColor, borderStyle: 'solid', boxSizing: 'border-box', borderWidth: '1px', backgroundColor: background, })); const RGBAtoHEX = (color: Color) => { const hex = (color.r | (1 << 8)).toString(16).slice(1) + (color.g | (1 << 8)).toString(16).slice(1) + (color.b | (1 << 8)).toString(16).slice(1); return '#' + hex.toUpperCase(); }; type FourItemArray = [T, T, T, T]; function TwoByTwoNumberGroup({ values, }: { values: FourItemArray; }) { return ( ); }