Attributes Metadata

Summary:
Before this change, attributes and attribute metadata were intermingled and sent as one unit via subtree update event.

This represented a few issues:
- Repetitiveness. For each declared and dynamic attribute, metadata was included on each value unit.
- Metadata can vary in size and thus can have a negative impact on payload size.
- The attribute name which is part of metadata is a string which always overhead on processing.
- Metadata instantiation is not cheap thus this also incurs in processing overhead i.e. even instantiating a single string can have an impact.

The proposal is to separate metadata of attributes from the actual node reported attributes. This solves the problems mentioned above.

Reviewed By: LukeDefeo

Differential Revision: D40674156

fbshipit-source-id: 0788551849fbce53065f819ba503e7e4afc03cc0
This commit is contained in:
Lorenzo Blasa
2022-11-10 11:52:28 -08:00
committed by Facebook GitHub Bot
parent 27428522ce
commit 01dc22b1ab
33 changed files with 663 additions and 267 deletions

View File

@@ -8,7 +8,13 @@
*/
import React from 'react';
import {Inspectable, InspectableObject, UINode} from '../../../types';
import {
Inspectable,
InspectableObject,
Metadata,
MetadataId,
UINode,
} from '../../../types';
import {DataInspector, Panel, styled} from 'flipper-plugin';
import {Checkbox, Col, Row} from 'antd';
import {displayableName} from '../utilities/displayableName';
@@ -73,21 +79,25 @@ const NamedAttributeInspector: React.FC<NamedAttributeInspectorProps> = ({
};
const ObjectAttributeInspector: React.FC<{
metadata: Map<MetadataId, Metadata>;
name: string;
value: Record<string, Inspectable>;
fields: Record<MetadataId, Inspectable>;
level: number;
}> = ({name, value, level}) => {
}> = ({metadata, name, fields, level}) => {
return (
<div style={ContainerStyle}>
{name}
{Object.keys(value).map(function (key, _) {
{Object.keys(fields).map(function (key, _) {
const metadataId: number = Number(key);
const inspectableValue = fields[metadataId];
const attributeName = metadata.get(metadataId)?.name ?? '';
return (
<ObjectContainer
key={key}
key={metadataId}
style={{
paddingLeft: level,
}}>
{create(key, value[key], level + 2)}
{create(metadata, attributeName, inspectableValue, level + 2)}
</ObjectContainer>
);
})}
@@ -95,145 +105,143 @@ const ObjectAttributeInspector: React.FC<{
);
};
function create(key: string, inspectable: Inspectable, level: number = 2) {
function create(
metadata: Map<MetadataId, Metadata>,
name: string,
inspectable: Inspectable,
level: number = 2,
) {
switch (inspectable.type) {
case 'boolean':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<Checkbox checked={inspectable.value} disabled />
</NamedAttributeInspector>
);
case 'enum':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<EnumValue>{inspectable.value.value}</EnumValue>
</NamedAttributeInspector>
);
case 'text':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<TextValue>{inspectable.value}</TextValue>
</NamedAttributeInspector>
);
case 'number':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<NumberValue>{inspectable.value}</NumberValue>
</NamedAttributeInspector>
);
case 'color':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<ColorInspector color={inspectable.value} />
</NamedAttributeInspector>
);
case 'size':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<SizeInspector value={inspectable.value} />
</NamedAttributeInspector>
);
case 'bounds':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<BoundsInspector value={inspectable.value} />
</NamedAttributeInspector>
);
case 'coordinate':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<CoordinateInspector value={inspectable.value} />
</NamedAttributeInspector>
);
case 'coordinate3d':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<Coordinate3DInspector value={inspectable.value} />
</NamedAttributeInspector>
);
case 'space':
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<SpaceBoxInspector value={inspectable.value} />
</NamedAttributeInspector>
);
case 'object':
return (
<ObjectAttributeInspector
name={displayableName(key)}
value={inspectable.fields}
metadata={metadata}
name={displayableName(name)}
fields={inspectable.fields}
level={level}
/>
);
default:
return (
<NamedAttributeInspector name={displayableName(key)}>
<NamedAttributeInspector name={displayableName(name)}>
<TextValue>{JSON.stringify(inspectable)}</TextValue>
</NamedAttributeInspector>
);
}
}
/**
* Filter out those inspectables that affect sizing, positioning, and
* overall layout of elements.
*/
const layoutFilter = new Set([
'size',
'padding',
'margin',
'bounds',
'position',
'globalPosition',
'localVisibleRect',
'rotation',
'scale',
'pivot',
'layoutParams',
'layoutDirection',
'translation',
'elevation',
]);
function createSection(
mode: InspectorMode,
metadata: Map<MetadataId, Metadata>,
name: string,
inspectable: InspectableObject,
) {
const fields = Object.keys(inspectable.fields).filter(
(key) =>
(mode === 'attributes' && !layoutFilter.has(key)) ||
(mode === 'layout' && layoutFilter.has(key)),
);
if (!fields || fields.length === 0) {
return;
const children: any[] = [];
Object.keys(inspectable.fields).forEach((key, _index) => {
const metadataId: number = Number(key);
const attributeMetadata = metadata.get(metadataId);
if (attributeMetadata && attributeMetadata.type === mode) {
const attributeValue = inspectable.fields[metadataId];
children.push(create(metadata, attributeMetadata.name, attributeValue));
}
});
if (children.length > 0) {
return (
<Panel key={mode.concat(name)} title={name}>
{...children}
</Panel>
);
}
return (
<Panel key={name} title={name}>
{fields.map(function (key, _) {
return create(key, inspectable.fields[key]);
})}
</Panel>
);
}
type InspectorMode = 'layout' | 'attributes';
type InspectorMode = 'layout' | 'attribute';
type Props = {
node: UINode;
metadata: Map<MetadataId, Metadata>;
mode: InspectorMode;
rawDisplayEnabled?: boolean;
};
export const AttributesInspector: React.FC<Props> = ({
node,
metadata,
mode,
rawDisplayEnabled = false,
}) => {
return (
<>
{Object.keys(node.attributes).map(function (key, _) {
const metadataId: number = Number(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.
*/
return createSection(
mode,
key,
node.attributes[key] as InspectableObject,
metadata,
metadata.get(metadataId)?.name ?? '',
node.attributes[metadataId] as InspectableObject,
);
})}
{rawDisplayEnabled ?? (