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,16 +8,15 @@
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.OffsetChild
import com.facebook.flipper.plugins.uidebugger.descriptors.*
import com.facebook.flipper.plugins.uidebugger.litho.LithoTag
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.litho.DebugComponent
class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescriptor<DebugComponent> {
private val NAMESPACE = "DebugComponent"
override fun getName(node: DebugComponent): String {
return node.component.simpleName
@@ -54,7 +53,15 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto
override fun getActiveChild(node: DebugComponent): Any? = null
override fun getData(node: DebugComponent) = mapOf<String, InspectableObject>()
private val LayoutId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "Litho Layout")
private val UserPropsId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "User Props")
override fun getData(node: DebugComponent): Map<MetadataId, InspectableObject> {
val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
return attributeSections
}
override fun getBounds(node: DebugComponent): Bounds =
Bounds.fromRect(node.boundsInParentDebugComponent)

View File

@@ -8,15 +8,19 @@
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.SectionName
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.litho.DebugComponent
import com.facebook.litho.LithoView
object LithoViewDescriptor : ChainedDescriptor<LithoView>() {
private const val NAMESPACE = "LithoView"
private val SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: LithoView): String = node.javaClass.simpleName
override fun onGetChildren(node: LithoView): List<Any> {
@@ -28,14 +32,18 @@ object LithoViewDescriptor : ChainedDescriptor<LithoView>() {
return result
}
private val IsIncrementalMountEnabledAttributeId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isIncrementalMountEnabled")
override fun onGetData(
node: LithoView,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
attributeSections["Litho"] =
attributeSections[SectionId] =
InspectableObject(
mapOf<String, Inspectable>(
"isIncrementalMountEnabled" to
InspectableValue.Boolean(node.isIncrementalMountEnabled, false)))
mapOf(
IsIncrementalMountEnabledAttributeId to
InspectableValue.Boolean(node.isIncrementalMountEnabled)))
}
}

View File

@@ -11,6 +11,7 @@ import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.descriptors.*
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
/** a drawable or view that is mounted, along with the correct descriptor */
class MountedObject(val obj: Any, val descriptor: NodeDescriptor<Any>)
@@ -37,7 +38,7 @@ object MountedObjectDescriptor : NodeDescriptor<MountedObject> {
override fun getActiveChild(node: MountedObject): Any? = node.descriptor.getActiveChild(node.obj)
override fun getData(node: MountedObject): Map<SectionName, InspectableObject> =
override fun getData(node: MountedObject): Map<MetadataId, InspectableObject> =
node.descriptor.getData(node.obj)
override fun getTags(node: MountedObject): Set<String> = node.descriptor.getTags(node.obj)

View File

@@ -8,23 +8,30 @@
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.SectionName
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.litho.widget.TextDrawable
object TextDrawableDescriptor : ChainedDescriptor<TextDrawable>() {
private const val NAMESPACE = "TextDrawable"
private val SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: TextDrawable): String = node.javaClass.simpleName
private val TextAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "text")
override fun onGetData(
node: TextDrawable,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val props =
mapOf<String, Inspectable>("text" to InspectableValue.Text(node.text.toString(), false))
mapOf<Int, Inspectable>(TextAttributeId to InspectableValue.Text(node.text.toString()))
attributeSections["TextDrawable"] = InspectableObject(props)
attributeSections[SectionId] = InspectableObject(props)
}
}

View File

@@ -13,8 +13,10 @@ import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.core.*
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import kotlinx.serialization.json.Json
@@ -51,6 +53,12 @@ class UIDebuggerFlipperPlugin(
InitEvent.name,
Json.encodeToString(InitEvent.serializer(), InitEvent(context.applicationRef.nodeId())))
connection.send(
MetadataUpdateEvent.name,
Json.encodeToString(
MetadataUpdateEvent.serializer(),
MetadataUpdateEvent(MetadataRegister.staticMetadata())))
context.treeObserverManager.start()
}
@@ -59,6 +67,8 @@ class UIDebuggerFlipperPlugin(
this.context.connectionRef.connection = null
Log.i(LogTag, "Disconnected")
MetadataRegister.clear()
context.treeObserverManager.stop()
context.bitmapPool.recycleAll()
}

View File

@@ -33,8 +33,8 @@ open class EnumMapping<T>(private val mapping: Map<String, T>) {
"Could not convert string $key to enum value, possible values ${mapping.entries} ")
}
fun toInspectable(value: T, mutable: Boolean): InspectableValue.Enum {
return InspectableValue.Enum(Enumeration(mapping.keys, getStringRepresentation(value)), mutable)
fun toInspectable(value: T): InspectableValue.Enum {
return InspectableValue.Enum(Enumeration(mapping.keys, getStringRepresentation(value)))
}
companion object {
const val NoMapping = "__UNKNOWN_ENUM_VALUE__"

View File

@@ -10,7 +10,9 @@ package com.facebook.flipper.plugins.uidebugger.core
import android.os.Looper
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import com.facebook.flipper.plugins.uidebugger.model.Node
import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent
import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent
@@ -50,7 +52,16 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
return ScanResult(txId++, start, scanEnd, nodes)
}
override fun process(input: ScanResult) {
private fun sendMetadata() {
val metadata = MetadataRegister.dynamicMetadata()
if (metadata.size > 0) {
context.connectionRef.connection?.send(
MetadataUpdateEvent.name,
Json.encodeToString(MetadataUpdateEvent.serializer(), MetadataUpdateEvent(metadata)))
}
}
private fun sendSubtreeUpdate(input: ScanResult) {
val serialized =
Json.encodeToString(
SubtreeUpdateEvent.serializer(),
@@ -79,4 +90,9 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
socketEnd,
input.nodes.size)))
}
override fun process(input: ScanResult) {
sendMetadata()
sendSubtreeUpdate(input)
}
}

View File

@@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
/**
* A chained descriptor is a special type of descriptor that models the inheritance hierarchy in
@@ -72,8 +73,8 @@ abstract class ChainedDescriptor<T> : NodeDescriptor<T> {
open fun onGetChildren(node: T): List<Any>? = null
final override fun getData(node: T): Map<SectionName, InspectableObject> {
val builder = mutableMapOf<String, InspectableObject>()
final override fun getData(node: T): Map<MetadataId, InspectableObject> {
val builder = mutableMapOf<MetadataId, InspectableObject>()
onGetData(node, builder)
var curDescriptor: ChainedDescriptor<T>? = mSuper
@@ -90,7 +91,7 @@ abstract class ChainedDescriptor<T> : NodeDescriptor<T> {
* Get the data to show for this node in the sidebar of the inspector. Each key will be a have its
* own section
*/
open fun onGetData(node: T, attributeSections: MutableMap<SectionName, InspectableObject>) {}
open fun onGetData(node: T, attributeSections: MutableMap<MetadataId, InspectableObject>) {}
/** Get a snapshot of the node. */
final override fun getSnapshot(node: T, bitmap: Bitmap?): Bitmap? {

View File

@@ -8,22 +8,25 @@
package com.facebook.flipper.plugins.uidebugger.descriptors
import android.graphics.drawable.ColorDrawable
import com.facebook.flipper.plugins.uidebugger.model.Color
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.*
object ColorDrawableDescriptor : ChainedDescriptor<ColorDrawable>() {
private const val NAMESPACE = "ColorDrawable"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
private var ColorAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "color")
override fun onGetName(node: ColorDrawable): String = node.javaClass.simpleName
override fun onGetData(
node: ColorDrawable,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val props = mutableMapOf<String, Inspectable>()
props["color"] = InspectableValue.Color(Color.fromColor(node.color), mutable = true)
val props = mutableMapOf<Int, Inspectable>()
props[ColorAttributeId] = InspectableValue.Color(Color.fromColor(node.color))
attributeSections["ColorDrawable"] = InspectableObject(props.toMap())
attributeSections[SectionId] = InspectableObject(props.toMap())
}
}

View File

@@ -12,6 +12,15 @@ import android.os.Build
import com.facebook.flipper.plugins.uidebugger.model.*
object DrawableDescriptor : ChainedDescriptor<Drawable>() {
private const val NAMESPACE = "Drawable"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
private var AlphaAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "alpha")
private var BoundsAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bounds")
override fun onGetName(node: Drawable): String = node.javaClass.simpleName
override fun onGetBounds(node: Drawable): Bounds =
@@ -19,16 +28,16 @@ object DrawableDescriptor : ChainedDescriptor<Drawable>() {
override fun onGetData(
node: Drawable,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val props = mutableMapOf<String, Inspectable>()
props["alpha"] = InspectableValue.Number(node.alpha, true)
val props = mutableMapOf<Int, Inspectable>()
props[AlphaAttributeId] = InspectableValue.Number(node.alpha)
val bounds = node.bounds
props["bounds"] = InspectableValue.Bounds(Bounds.fromRect(bounds))
props[BoundsAttributeId] = InspectableValue.Bounds(Bounds.fromRect(bounds))
attributeSections["Drawable"] = InspectableObject(props.toMap())
attributeSections[SectionId] = InspectableObject(props.toMap())
}
}

View File

@@ -11,9 +11,14 @@ import android.os.Bundle
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
object FragmentFrameworkDescriptor : ChainedDescriptor<android.app.Fragment>() {
private const val NAMESPACE = "Fragment"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: android.app.Fragment): String {
return node.javaClass.simpleName
}
@@ -23,19 +28,24 @@ object FragmentFrameworkDescriptor : ChainedDescriptor<android.app.Fragment>() {
override fun onGetData(
node: android.app.Fragment,
attributeSections: MutableMap<String, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val args: Bundle = node.arguments
val props = mutableMapOf<String, Inspectable>()
val props = mutableMapOf<Int, Inspectable>()
for (key in args.keySet()) {
val metadata = MetadataRegister.get(NAMESPACE, key)
val identifier =
metadata?.id
?: MetadataRegister.registerDynamic(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, key)
when (val value = args[key]) {
is Number -> props[key] = InspectableValue.Number(value)
is Boolean -> props[key] = InspectableValue.Boolean(value)
is String -> props[key] = InspectableValue.Text(value)
is Number -> props[identifier] = InspectableValue.Number(value)
is Boolean -> props[identifier] = InspectableValue.Boolean(value)
is String -> props[identifier] = InspectableValue.Text(value)
}
}
attributeSections["Fragment"] = InspectableObject(props.toMap())
attributeSections[SectionId] = InspectableObject(props.toMap())
}
}

View File

@@ -10,9 +10,14 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
object FragmentSupportDescriptor : ChainedDescriptor<androidx.fragment.app.Fragment>() {
private const val NAMESPACE = "Fragment"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: androidx.fragment.app.Fragment): String {
return node.javaClass.simpleName
}
@@ -22,19 +27,23 @@ object FragmentSupportDescriptor : ChainedDescriptor<androidx.fragment.app.Fragm
override fun onGetData(
node: androidx.fragment.app.Fragment,
attributeSections: MutableMap<String, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val args = node.arguments
args?.let { bundle ->
val props = mutableMapOf<String, Inspectable>()
val props = mutableMapOf<Int, Inspectable>()
for (key in bundle.keySet()) {
val metadata = MetadataRegister.get(NAMESPACE, key)
val identifier =
metadata?.id
?: MetadataRegister.registerDynamic(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, key)
when (val value = bundle[key]) {
is Number -> props[key] = InspectableValue.Number(value)
is Boolean -> props[key] = InspectableValue.Boolean(value)
is String -> props[key] = InspectableValue.Text(value)
is Number -> props[identifier] = InspectableValue.Number(value)
is Boolean -> props[identifier] = InspectableValue.Boolean(value)
is String -> props[identifier] = InspectableValue.Text(value)
}
}
attributeSections["Fragment"] = InspectableObject(props.toMap())
attributeSections[SectionId] = InspectableObject(props.toMap())
}
}
}

View File

@@ -12,18 +12,27 @@ import android.widget.ImageView.ScaleType
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
object ImageViewDescriptor : ChainedDescriptor<ImageView>() {
private const val NAMESPACE = "ImageView"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
private var ScaleTypeAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "scaleType")
override fun onGetName(node: ImageView): String = node.javaClass.simpleName
override fun onGetData(
node: ImageView,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val props = mutableMapOf<String, Inspectable>()
props["scaleType"] = scaleTypeMapping.toInspectable(node.scaleType, true)
val props = mutableMapOf<Int, Inspectable>()
props[ScaleTypeAttributeId] = scaleTypeMapping.toInspectable(node.scaleType)
attributeSections["ImageView"] = InspectableObject(props)
attributeSections[SectionId] = InspectableObject(props)
}
private val scaleTypeMapping: EnumMapping<ScaleType> =

View File

@@ -0,0 +1,96 @@
/*
* 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.
*/
package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.model.Metadata
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
/**
* Registry of attribute metadata. There's two types of attributes:
* - Static attributes. Those that are known at build time.
* - Dynamic attributes. Those that are known at runtime.
*/
object MetadataRegister {
const val TYPE_IDENTITY = "identity"
const val TYPE_ATTRIBUTE = "attribute"
const val TYPE_LAYOUT = "layout"
const val TYPE_DOCUMENTATION = "documentation"
private var generator: MetadataId = 0
private val staticMetadata: MutableMap<String, Metadata> = mutableMapOf()
private val dynamicMetadata: MutableMap<String, Metadata> = mutableMapOf()
private val queried: MutableSet<MetadataId> = mutableSetOf()
fun key(namespace: String, name: String): String = "${namespace}_$name"
private fun register(
metadata: MutableMap<String, Metadata>,
type: String,
namespace: String,
name: String,
mutable: Boolean
): MetadataId {
val key = key(namespace, name)
metadata[key]?.let { m ->
return m.id
}
val identifier = ++generator
metadata[key] = Metadata(identifier, type, namespace, name, mutable)
return identifier
}
fun register(
type: String,
namespace: String,
name: String,
mutable: Boolean = false
): MetadataId {
return register(staticMetadata, type, namespace, name, mutable)
}
fun registerDynamic(
type: String,
namespace: String,
name: String,
mutable: Boolean = false
): MetadataId {
return register(dynamicMetadata, type, namespace, name, mutable)
}
fun get(namespace: String, name: String): Metadata? {
val key = key(namespace, name)
staticMetadata[key]?.let {
return it
}
return dynamicMetadata[key]
}
fun staticMetadata(): Map<MetadataId, Metadata> {
val metadata: MutableMap<MetadataId, Metadata> = mutableMapOf()
staticMetadata.forEach { entry -> metadata[entry.value.id] = entry.value }
return metadata
}
fun dynamicMetadata(): Map<MetadataId, Metadata> {
val metadata: MutableMap<MetadataId, Metadata> = mutableMapOf()
dynamicMetadata.forEach { entry ->
if (!queried.contains(entry.value.id)) {
metadata[entry.value.id] = entry.value
queried.add(entry.value.id)
}
}
return metadata
}
fun clear() {
queried.clear()
}
}

View File

@@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
/*
Descriptors are an extension point used during traversal to extract data out of arbitrary
@@ -19,8 +20,6 @@ import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
Descriptors should be stateless and each descriptor should be a singleton
*/
typealias SectionName = String
object BaseTags {
const val Declarative = "Declarative"
@@ -63,7 +62,7 @@ interface NodeDescriptor<T> {
* Get the data to show for this node in the sidebar of the inspector. The object will be shown in
* order and with a header matching the given name.
*/
fun getData(node: T): Map<SectionName, InspectableObject>
fun getData(node: T): Map<MetadataId, InspectableObject>
/**
* Set of tags to describe this node in an abstract way for the UI Unfortunately this can't be an

View File

@@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
object ObjectDescriptor : NodeDescriptor<Any> {
@@ -21,7 +22,7 @@ object ObjectDescriptor : NodeDescriptor<Any> {
override fun getChildren(node: Any) = listOf<Any>()
override fun getData(node: Any) = mutableMapOf<SectionName, InspectableObject>()
override fun getData(node: Any) = mutableMapOf<MetadataId, InspectableObject>()
override fun getBounds(node: Any): Bounds? = null

View File

@@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
/** a drawable or view that is mounted, along with the correct descriptor */
class OffsetChild(val child: Any, val descriptor: NodeDescriptor<Any>, val x: Int, val y: Int) {
@@ -34,7 +35,7 @@ object OffsetChildDescriptor : NodeDescriptor<OffsetChild> {
override fun getActiveChild(node: OffsetChild): Any? = node.descriptor.getActiveChild(node.child)
override fun getData(node: OffsetChild): Map<SectionName, InspectableObject> =
override fun getData(node: OffsetChild): Map<MetadataId, InspectableObject> =
node.descriptor.getData(node.child)
override fun getTags(node: OffsetChild): Set<String> = node.descriptor.getTags(node.child)

View File

@@ -13,45 +13,73 @@ import com.facebook.flipper.plugins.uidebugger.model.Color
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
object TextViewDescriptor : ChainedDescriptor<TextView>() {
private const val NAMESPACE = "TextView"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
private val TextAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "text")
private val TextSizeAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textSize")
private val TextColorAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textColor")
private val IsBoldAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isBold")
private val IsItalicAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isItalic")
private val WeightAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "weight")
private val TypefaceAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "typeface")
private val MinLinesAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minLines")
private val MaxLinesAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxLines")
private val MinWidthAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minWidth")
private val MaxWidthAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxWidth")
override fun onGetName(node: TextView): String = node.javaClass.simpleName
override fun onGetData(
node: TextView,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val props =
mutableMapOf<String, Inspectable>(
"text" to InspectableValue.Text(node.text.toString(), false),
"textSize" to InspectableValue.Number(node.textSize, false),
"textColor" to
InspectableValue.Color(Color.fromColor(node.textColors.defaultColor), false))
mutableMapOf<Int, Inspectable>(
TextAttributeId to InspectableValue.Text(node.text.toString()),
TextSizeAttributeId to InspectableValue.Number(node.textSize),
TextColorAttributeId to
InspectableValue.Color(Color.fromColor(node.textColors.defaultColor)))
val typeface = node.typeface
if (typeface != null) {
val typeFaceProp =
mutableMapOf<String, InspectableValue>(
"isBold" to InspectableValue.Boolean(typeface.isBold, false),
"isItalic" to InspectableValue.Boolean(typeface.isItalic, false),
mutableMapOf<Int, InspectableValue>(
IsBoldAttributeId to InspectableValue.Boolean(typeface.isBold),
IsItalicAttributeId to InspectableValue.Boolean(typeface.isItalic),
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
typeFaceProp["weight"] = InspectableValue.Number(typeface.weight, false)
typeFaceProp[WeightAttributeId] = InspectableValue.Number(typeface.weight)
}
props["typeface"] = InspectableObject(typeFaceProp)
props[TypefaceAttributeId] = InspectableObject(typeFaceProp)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
props["minLines"] = InspectableValue.Number(node.minLines, false)
props["maxLines"] = InspectableValue.Number(node.maxLines, false)
props["minWidth"] = InspectableValue.Number(node.minWidth, false)
props["maxWidth"] = InspectableValue.Number(node.maxWidth, false)
props[MinLinesAttributeId] = InspectableValue.Number(node.minLines)
props[MaxLinesAttributeId] = InspectableValue.Number(node.maxLines)
props[MinWidthAttributeId] = InspectableValue.Number(node.minWidth)
props[MaxWidthAttributeId] = InspectableValue.Number(node.maxWidth)
}
attributeSections["TextView"] = InspectableObject(props)
attributeSections[SectionId] = InspectableObject(props)
}
}

View File

@@ -23,15 +23,86 @@ import android.widget.LinearLayout
import androidx.core.widget.NestedScrollView
import androidx.viewpager.widget.ViewPager
import com.facebook.flipper.plugins.uidebugger.common.*
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.model.*
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.util.ResourcesUtil
import java.lang.reflect.Field
object ViewDescriptor : ChainedDescriptor<View>() {
private const val NAMESPACE = "View"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
private val PositionAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "position")
private val GlobalPositionAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "globalPosition")
private val SizeAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "size")
private val BoundsAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "bounds")
private val PaddingAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "padding")
private val LocalVisibleRectAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "localVisibleRect")
private val RotationAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "rotation")
private val ScaleAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "scale")
private val PivotAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "pivot")
private val LayoutParamsAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutParams")
private val LayoutDirectionAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutDirection")
private val TranslationAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "translation")
private val ElevationAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "elevation")
private val VisibilityAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "visibility")
private val BackgroundAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "background")
private val ForegroundAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "foreground")
private val AlphaAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "alpha")
private val StateAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "state")
private val StateEnabledAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "enabled")
private val StateActivatedAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "activated")
private val StateFocusedAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "focused")
private val StateSelectedAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "selected")
private val TextDirectionAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textDirection")
private val TextAlignmentAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textAlignment")
private val TagAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "tag")
private val KeyedTagsAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "keyedTags")
private val WidthAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "width")
private val HeightAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "height")
private val MarginAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "margin")
private val WeightAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "weight")
private val GravityAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "gravity")
override fun onGetName(node: View): String = node.javaClass.simpleName
override fun onGetBounds(node: View): Bounds {
@@ -64,12 +135,9 @@ object ViewDescriptor : ChainedDescriptor<View>() {
override fun onGetTags(node: View): Set<String> = BaseTags.NativeAndroid
override fun onGetData(
node: View,
attributeSections: MutableMap<SectionName, InspectableObject>
) {
override fun onGetData(node: View, attributeSections: MutableMap<MetadataId, InspectableObject>) {
val props = mutableMapOf<String, Inspectable>()
val props = mutableMapOf<Int, Inspectable>()
val positionOnScreen = IntArray(2)
node.getLocationOnScreen(positionOnScreen)
@@ -78,80 +146,83 @@ object ViewDescriptor : ChainedDescriptor<View>() {
node.getLocalVisibleRect(localVisible)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
props["position"] = InspectableValue.Coordinate3D(Coordinate3D(node.x, node.y, node.z))
props[PositionAttributeId] =
InspectableValue.Coordinate3D(Coordinate3D(node.x, node.y, node.z))
} else {
props["position"] = InspectableValue.Coordinate(Coordinate(node.x, node.y))
props[PositionAttributeId] = InspectableValue.Coordinate(Coordinate(node.x, node.y))
}
props["globalPosition"] =
props[GlobalPositionAttributeId] =
InspectableValue.Coordinate(Coordinate(positionOnScreen[0], positionOnScreen[1]))
props["size"] = InspectableValue.Size(Size(node.width, node.height), mutable = true)
props[SizeAttributeId] = InspectableValue.Size(Size(node.width, node.height))
props["bounds"] = InspectableValue.Bounds(Bounds(node.left, node.top, node.right, node.bottom))
props["padding"] =
props[BoundsAttributeId] =
InspectableValue.Bounds(Bounds(node.left, node.top, node.right, node.bottom))
props[PaddingAttributeId] =
InspectableValue.SpaceBox(
SpaceBox(node.paddingTop, node.paddingRight, node.paddingBottom, node.paddingLeft))
props["localVisibleRect"] =
props[LocalVisibleRectAttributeId] =
InspectableObject(
mapOf(
"position" to
PositionAttributeId to
InspectableValue.Coordinate(Coordinate(localVisible.left, localVisible.top)),
"size" to InspectableValue.Size(Size(localVisible.width(), localVisible.height()))),
SizeAttributeId to
InspectableValue.Size(Size(localVisible.width(), localVisible.height()))),
)
props["rotation"] =
props[RotationAttributeId] =
InspectableValue.Coordinate3D(Coordinate3D(node.rotationX, node.rotationY, node.rotation))
props["scale"] = InspectableValue.Coordinate(Coordinate(node.scaleX, node.scaleY))
props["pivot"] = InspectableValue.Coordinate(Coordinate(node.pivotX, node.pivotY))
props[ScaleAttributeId] = InspectableValue.Coordinate(Coordinate(node.scaleX, node.scaleY))
props[PivotAttributeId] = InspectableValue.Coordinate(Coordinate(node.pivotX, node.pivotY))
props["layoutParams"] = getLayoutParams(node)
props[LayoutParamsAttributeId] = getLayoutParams(node)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
props["layoutDirection"] = LayoutDirectionMapping.toInspectable(node.layoutDirection, false)
props[LayoutDirectionAttributeId] = LayoutDirectionMapping.toInspectable(node.layoutDirection)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
props["translation"] =
props[TranslationAttributeId] =
InspectableValue.Coordinate3D(
Coordinate3D(node.translationX, node.translationY, node.translationZ))
} else {
props["translation"] =
props[TranslationAttributeId] =
InspectableValue.Coordinate(Coordinate(node.translationX, node.translationY))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
props["elevation"] = InspectableValue.Number(node.elevation)
props[ElevationAttributeId] = InspectableValue.Number(node.elevation)
}
props["visibility"] = VisibilityMapping.toInspectable(node.visibility, mutable = false)
props[VisibilityAttributeId] = VisibilityMapping.toInspectable(node.visibility)
fromDrawable(node.background)?.let { background -> props["background"] = background }
fromDrawable(node.background)?.let { background -> props[BackgroundAttributeId] = background }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fromDrawable(node.foreground)?.let { foreground -> props["foreground"] = foreground }
fromDrawable(node.foreground)?.let { foreground -> props[ForegroundAttributeId] = foreground }
}
props["alpha"] = InspectableValue.Number(node.alpha, mutable = true)
props["state"] =
props[AlphaAttributeId] = InspectableValue.Number(node.alpha)
props[StateAttributeId] =
InspectableObject(
mapOf(
"enabled" to InspectableValue.Boolean(node.isEnabled, mutable = false),
"activated" to InspectableValue.Boolean(node.isActivated, mutable = false),
"focused" to InspectableValue.Boolean(node.isFocused, mutable = false),
"selected" to InspectableValue.Boolean(node.isSelected, mutable = false)))
StateEnabledAttributeId to InspectableValue.Boolean(node.isEnabled),
StateActivatedAttributeId to InspectableValue.Boolean(node.isActivated),
StateFocusedAttributeId to InspectableValue.Boolean(node.isFocused),
StateSelectedAttributeId to InspectableValue.Boolean(node.isSelected)))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
props["textDirection"] = TextDirectionMapping.toInspectable(node.textDirection, false)
props["textAlignment"] = TextAlignmentMapping.toInspectable(node.textAlignment, false)
props[TextDirectionAttributeId] = TextDirectionMapping.toInspectable(node.textDirection)
props[TextAlignmentAttributeId] = TextAlignmentMapping.toInspectable(node.textAlignment)
}
node.tag
?.let { InspectableValue.fromAny(it, mutable = false) }
?.let { tag -> props.put("tag", tag) }
?.let { tag -> props.put(TagAttributeId, tag) }
props["keyedTags"] = InspectableObject(getViewTags(node))
props[KeyedTagsAttributeId] = InspectableObject(getViewTags(node))
attributeSections["View"] = InspectableObject(props.toMap())
attributeSections[SectionId] = InspectableObject(props.toMap())
}
override fun onGetSnapshot(node: View, bitmap: Bitmap?): Bitmap? {
@@ -180,19 +251,19 @@ object ViewDescriptor : ChainedDescriptor<View>() {
private fun fromDrawable(d: Drawable?): Inspectable? {
return if (d is ColorDrawable) {
InspectableValue.Color(Color.fromColor(d.color), mutable = false)
InspectableValue.Color(Color.fromColor(d.color))
} else null
}
private fun getLayoutParams(node: View): InspectableObject {
val layoutParams = node.layoutParams
val params = mutableMapOf<String, Inspectable>()
params["width"] = LayoutParamsMapping.toInspectable(layoutParams.width, mutable = true)
params["height"] = LayoutParamsMapping.toInspectable(layoutParams.height, mutable = true)
val params = mutableMapOf<Int, Inspectable>()
params[WidthAttributeId] = LayoutParamsMapping.toInspectable(layoutParams.width)
params[HeightAttributeId] = LayoutParamsMapping.toInspectable(layoutParams.height)
if (layoutParams is ViewGroup.MarginLayoutParams) {
params["margin"] =
params[MarginAttributeId] =
InspectableValue.SpaceBox(
SpaceBox(
layoutParams.topMargin,
@@ -201,17 +272,17 @@ object ViewDescriptor : ChainedDescriptor<View>() {
layoutParams.leftMargin))
}
if (layoutParams is FrameLayout.LayoutParams) {
params["gravity"] = GravityMapping.toInspectable(layoutParams.gravity, mutable = true)
params[GravityAttributeId] = GravityMapping.toInspectable(layoutParams.gravity)
}
if (layoutParams is LinearLayout.LayoutParams) {
params["weight"] = InspectableValue.Number(layoutParams.weight, mutable = true)
params["gravity"] = GravityMapping.toInspectable(layoutParams.gravity, mutable = true)
params[WeightAttributeId] = InspectableValue.Number(layoutParams.weight)
params[GravityAttributeId] = GravityMapping.toInspectable(layoutParams.gravity)
}
return InspectableObject(params)
}
private fun getViewTags(node: View): MutableMap<String, Inspectable> {
val tags = mutableMapOf<String, Inspectable>()
private fun getViewTags(node: View): MutableMap<Int, Inspectable> {
val tags = mutableMapOf<Int, Inspectable>()
KeyedTagsField?.let { field ->
val keyedTags = field.get(node) as SparseArray<*>?
@@ -224,7 +295,13 @@ object ViewDescriptor : ChainedDescriptor<View>() {
keyedTags
.valueAt(i)
?.let { InspectableValue.fromAny(it, false) }
?.let { tags.put(id, it) }
?.let {
val metadata = MetadataRegister.get(NAMESPACE, id)
val identifier =
metadata?.id
?: MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, id)
tags.put(identifier, it)
}
i++
}
}

View File

@@ -17,6 +17,9 @@ import com.facebook.flipper.plugins.uidebugger.model.*
object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
private const val NAMESPACE = "ViewGroup"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: ViewGroup): String {
return node.javaClass.simpleName
}
@@ -37,21 +40,28 @@ object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
return children
}
private val LayoutModeAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutMode")
private val ClipChildrenAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutMode")
private val ClipToPaddingAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "clipToPadding")
override fun onGetData(
node: ViewGroup,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val viewGroupAttrs = mutableMapOf<String, Inspectable>()
val props = mutableMapOf<Int, Inspectable>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
viewGroupAttrs["LayoutMode"] = LayoutModeMapping.toInspectable(node.layoutMode, true)
viewGroupAttrs["ClipChildren"] = InspectableValue.Boolean(node.clipChildren, true)
props[LayoutModeAttributeId] = LayoutModeMapping.toInspectable(node.layoutMode)
props[ClipChildrenAttributeId] = InspectableValue.Boolean(node.clipChildren)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
viewGroupAttrs["ClipToPadding"] = InspectableValue.Boolean(node.clipToPadding, true)
props[ClipToPaddingAttributeId] = InspectableValue.Boolean(node.clipToPadding)
}
attributeSections["ViewGroup"] = InspectableObject(viewGroupAttrs)
attributeSections[SectionId] = InspectableObject(props)
}
private val LayoutModeMapping: EnumMapping<Int> =

View File

@@ -11,9 +11,13 @@ import androidx.viewpager.widget.ViewPager
import com.facebook.flipper.plugins.uidebugger.core.FragmentTracker
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
object ViewPagerDescriptor : ChainedDescriptor<ViewPager>() {
private const val NAMESPACE = "ViewPager"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: ViewPager): String = node.javaClass.simpleName
override fun onGetActiveChild(node: ViewPager): Any? {
@@ -25,14 +29,16 @@ object ViewPagerDescriptor : ChainedDescriptor<ViewPager>() {
return child
}
private val CurrentItemIndexAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "currentItemIndex")
override fun onGetData(
node: ViewPager,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val props =
InspectableObject(
mapOf("currentItemIndex" to InspectableValue.Number(node.currentItem, false)))
mapOf(CurrentItemIndexAttributeId to InspectableValue.Number(node.currentItem)))
attributeSections["ViewPager"] = props
attributeSections[SectionId] = props
}
}

View File

@@ -14,10 +14,14 @@ import com.facebook.flipper.plugins.uidebugger.model.Color
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import java.lang.reflect.Field
object WindowDescriptor : ChainedDescriptor<Window>() {
private const val NAMESPACE = "Window"
private var SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
private var internalRStyleableClass: Class<*>? = null
private var internalRStyleableFields: Array<Field>? = null
private var internalRStyleableWindowField: Field? = null
@@ -32,7 +36,7 @@ object WindowDescriptor : ChainedDescriptor<Window>() {
@SuppressLint("PrivateApi")
override fun onGetData(
node: Window,
attributeSections: MutableMap<SectionName, InspectableObject>
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
try {
if (internalRStyleableClass == null) {
@@ -58,7 +62,7 @@ object WindowDescriptor : ChainedDescriptor<Window>() {
}
}
val props = mutableMapOf<String, Inspectable>()
val props = mutableMapOf<Int, Inspectable>()
val typedValue = TypedValue()
for ((index, attr) in windowStyleable.withIndex()) {
@@ -68,20 +72,28 @@ object WindowDescriptor : ChainedDescriptor<Window>() {
// Strip 'Windows_' (length: 7) from the name.
val name = fieldName.substring(7)
val metadata = MetadataRegister.get(NAMESPACE, name)
val identifier =
metadata?.id
?: MetadataRegister.registerDynamic(
MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, name)
when (typedValue.type) {
TypedValue.TYPE_STRING ->
props[name] = InspectableValue.Text(typedValue.string.toString())
props[identifier] = InspectableValue.Text(typedValue.string.toString())
TypedValue.TYPE_INT_BOOLEAN ->
props[name] = InspectableValue.Boolean(typedValue.data != 0)
props[identifier] = InspectableValue.Boolean(typedValue.data != 0)
TypedValue.TYPE_INT_HEX ->
props[name] = InspectableValue.Text("0x" + Integer.toHexString(typedValue.data))
props[identifier] =
InspectableValue.Text("0x" + Integer.toHexString(typedValue.data))
TypedValue.TYPE_FLOAT ->
props[name] =
props[identifier] =
InspectableValue.Number(java.lang.Float.intBitsToFloat(typedValue.data))
TypedValue.TYPE_REFERENCE -> {
val resId = typedValue.data
if (resId != 0) {
props[name] = InspectableValue.Text(node.context.resources.getResourceName(resId))
props[identifier] =
InspectableValue.Text(node.context.resources.getResourceName(resId))
}
}
else -> {
@@ -90,18 +102,18 @@ object WindowDescriptor : ChainedDescriptor<Window>() {
try {
val hexColor = "#" + Integer.toHexString(typedValue.data)
val color = android.graphics.Color.parseColor(hexColor)
props[name] = InspectableValue.Color(Color.fromColor(color))
props[identifier] = InspectableValue.Color(Color.fromColor(color))
} catch (e: Exception) {}
} else if (typedValue.type >= TypedValue.TYPE_FIRST_INT &&
typedValue.type <= TypedValue.TYPE_LAST_INT) {
props[name] = InspectableValue.Number(typedValue.data)
props[identifier] = InspectableValue.Number(typedValue.data)
}
}
}
}
}
attributeSections["Window"] = InspectableObject(props.toMap())
attributeSections[SectionId] = InspectableObject(props.toMap())
}
}
}

View File

@@ -10,12 +10,21 @@ package com.facebook.flipper.plugins.uidebugger.model
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
@kotlinx.serialization.Serializable
data class InitEvent(val rootId: Id) {
data class InitEvent(
val rootId: Id,
) {
companion object {
const val name = "init"
}
}
@kotlinx.serialization.Serializable
data class MetadataUpdateEvent(val attributeMetadata: Map<MetadataId, Metadata> = emptyMap()) {
companion object {
const val name = "metadataUpdate"
}
}
@kotlinx.serialization.Serializable
data class SubtreeUpdateEvent(
val txId: Long,

View File

@@ -16,93 +16,77 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@kotlinx.serialization.Serializable
sealed class Inspectable {
abstract val mutable: kotlin.Boolean
}
@kotlinx.serialization.Serializable sealed class Inspectable {}
// In this context, mutable means you can add/remove items,
// for native android this should probably be false.
@SerialName("array")
@Serializable
data class InspectableArray(val items: List<Inspectable>, override val mutable: Boolean = false) :
Inspectable()
data class InspectableArray(val id: Int, val items: List<Inspectable>) : Inspectable()
// In this context, mutable means you can add / remove keys,
// for native android this should probably be false.
@SerialName("object")
@Serializable
data class InspectableObject(
val fields: Map<String, Inspectable>,
override val mutable: Boolean = false
) : Inspectable()
data class InspectableObject(val fields: Map<Int, Inspectable>) : Inspectable()
@kotlinx.serialization.Serializable
sealed class InspectableValue : Inspectable() {
@kotlinx.serialization.Serializable
@SerialName("text")
class Text(val value: String, override val mutable: kotlin.Boolean = false) : InspectableValue()
class Text(val value: String) : InspectableValue()
@kotlinx.serialization.Serializable
@SerialName("boolean")
class Boolean(val value: kotlin.Boolean, override val mutable: kotlin.Boolean = false) :
InspectableValue()
class Boolean(val value: kotlin.Boolean) : InspectableValue()
@SerialName("number")
@kotlinx.serialization.Serializable
data class Number(
@Serializable(with = NumberSerializer::class) val value: kotlin.Number,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("color")
@kotlinx.serialization.Serializable
data class Color(
val value: com.facebook.flipper.plugins.uidebugger.model.Color,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("coordinate")
@kotlinx.serialization.Serializable
data class Coordinate(
val value: com.facebook.flipper.plugins.uidebugger.model.Coordinate,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("coordinate3d")
@kotlinx.serialization.Serializable
data class Coordinate3D(
val value: com.facebook.flipper.plugins.uidebugger.model.Coordinate3D,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("size")
@kotlinx.serialization.Serializable
data class Size(
val value: com.facebook.flipper.plugins.uidebugger.model.Size,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("bounds")
@kotlinx.serialization.Serializable
data class Bounds(
val value: com.facebook.flipper.plugins.uidebugger.model.Bounds,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("space")
@kotlinx.serialization.Serializable
data class SpaceBox(
val value: com.facebook.flipper.plugins.uidebugger.model.SpaceBox,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
@SerialName("enum")
@kotlinx.serialization.Serializable
data class Enum(
val value: com.facebook.flipper.plugins.uidebugger.model.Enumeration,
override val mutable: kotlin.Boolean = false
) : InspectableValue()
companion object {
@@ -112,9 +96,9 @@ sealed class InspectableValue : Inspectable() {
*/
fun fromAny(any: Any, mutable: kotlin.Boolean = false): Inspectable? {
return when (any) {
is kotlin.Number -> InspectableValue.Number(any, mutable)
is kotlin.Boolean -> InspectableValue.Boolean(any, mutable)
is kotlin.String -> InspectableValue.Text(any, mutable)
is kotlin.Number -> InspectableValue.Number(any)
is kotlin.Boolean -> InspectableValue.Boolean(any)
is kotlin.String -> InspectableValue.Text(any)
else -> null
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
package com.facebook.flipper.plugins.uidebugger.model
typealias MetadataId = Int
/**
* Represent metadata associated for an attribute. MetadataId is a unique identifier used by
* attributes to refer to its metadata. Type refers to attribute semantics. It can represent:
* identity, attributes, layout, documentation, or a custom type.
*/
@kotlinx.serialization.Serializable
data class Metadata(
val id: MetadataId,
val type: String,
val namespace: String,
val name: String,
val mutable: kotlin.Boolean,
val tags: List<String>? = emptyList()
) {}

View File

@@ -13,7 +13,7 @@ import com.facebook.flipper.plugins.uidebugger.descriptors.Id
data class Node(
val id: Id,
val name: String,
val attributes: Map<String, InspectableObject>,
val attributes: Map<MetadataId, InspectableObject>,
val bounds: Bounds?,
val tags: Set<String>,
val children: List<Id>,

View File

@@ -16,8 +16,10 @@ import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.Coordinate
import com.facebook.flipper.plugins.uidebugger.model.CoordinateUpdateEvent
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import com.facebook.flipper.plugins.uidebugger.model.Node
import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent
import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent
@@ -69,9 +71,7 @@ class TreeObserverManager(val context: Context) {
workerScope.launch {
while (isActive) {
try {
val update = updates.receive()
when (update) {
when (val update = updates.receive()) {
is SubtreeUpdate -> sendSubtreeUpdate(update)
is CoordinateUpdate -> {
val event =
@@ -88,11 +88,22 @@ class TreeObserverManager(val context: Context) {
}
}
fun sendSubtreeUpdate(treeUpdate: SubtreeUpdate) {
private fun sendMetadata() {
val metadata = MetadataRegister.dynamicMetadata()
if (metadata.size > 0) {
context.connectionRef.connection?.send(
MetadataUpdateEvent.name,
Json.encodeToString(MetadataUpdateEvent.serializer(), MetadataUpdateEvent(metadata)))
}
}
private fun sendSubtreeUpdate(treeUpdate: SubtreeUpdate) {
val onWorkerThread = System.currentTimeMillis()
val txId = txId.getAndIncrement().toLong()
var serialized: String?
sendMetadata()
val serialized: String?
if (treeUpdate.snapshot == null) {
serialized =
Json.encodeToString(

View File

@@ -39,9 +39,7 @@ class EnumMappingTest {
@Test
fun testTurnsIntoEnumInspectable() {
assertThat(
visibility.toInspectable(View.GONE, true),
equalTo(
InspectableValue.Enum(
Enumeration(setOf("VISIBLE", "INVISIBLE", "GONE"), "GONE"), mutable = true)))
visibility.toInspectable(View.GONE),
equalTo(InspectableValue.Enum(Enumeration(setOf("VISIBLE", "INVISIBLE", "GONE"), "GONE"))))
}
}

View File

@@ -11,7 +11,7 @@ import React, {useState} from 'react';
import {plugin} from '../index';
import {DetailSidebar, Layout, usePlugin, useValue} from 'flipper-plugin';
import {useHotkeys} from 'react-hotkeys-hook';
import {Id, Snapshot, UINode} from '../types';
import {Id, Metadata, MetadataId, Snapshot, UINode} from '../types';
import {PerfStats} from './PerfStats';
import {Tree} from './Tree';
import {Visualization2D} from './Visualization2D';
@@ -22,6 +22,7 @@ export function Component() {
const instance = usePlugin(plugin);
const rootId = useValue(instance.rootId);
const nodes: Map<Id, UINode> = useValue(instance.nodes);
const metadata: Map<MetadataId, Metadata> = useValue(instance.metadata);
const snapshots: Map<Id, Snapshot> = useValue(instance.snapshots);
const [showPerfStats, setShowPerfStats] = useState(false);
@@ -32,13 +33,16 @@ export function Component() {
const {ctrlPressed} = useKeyboardModifiers();
function renderSidebar(node: UINode | undefined) {
function renderSidebar(
node: UINode | undefined,
metadata: Map<MetadataId, Metadata>,
) {
if (!node) {
return;
}
return (
<DetailSidebar width={350}>
<Inspector node={node} />
<Inspector metadata={metadata} node={node} />
</DetailSidebar>
);
}
@@ -68,7 +72,7 @@ export function Component() {
onSelectNode={setSelectedNode}
modifierPressed={ctrlPressed}
/>
{selectedNode && renderSidebar(nodes.get(selectedNode))}
{selectedNode && renderSidebar(nodes.get(selectedNode), metadata)}
</Layout.Horizontal>
);
}

View File

@@ -11,16 +11,17 @@ import React from 'react';
// eslint-disable-next-line rulesdir/no-restricted-imports-clone
import {Glyph} from 'flipper';
import {Layout, Tab, Tabs} from 'flipper-plugin';
import {UINode} from '../../types';
import {Metadata, MetadataId, UINode} from '../../types';
import {IdentityInspector} from './inspector/IdentityInspector';
import {AttributesInspector} from './inspector/AttributesInspector';
import {DocumentationInspector} from './inspector/DocumentationInspector';
type Props = {
node: UINode;
metadata: Map<MetadataId, Metadata>;
};
export const Inspector: React.FC<Props> = ({node}) => {
export const Inspector: React.FC<Props> = ({node, metadata}) => {
return (
<Layout.Container gap pad>
<Tabs grow centered>
@@ -38,7 +39,11 @@ export const Inspector: React.FC<Props> = ({node}) => {
<Glyph name="data-table" size={16} />
</Layout.Horizontal>
}>
<AttributesInspector mode="attributes" node={node} />
<AttributesInspector
mode="attribute"
node={node}
metadata={metadata}
/>
</Tab>
<Tab
tab={
@@ -46,7 +51,7 @@ export const Inspector: React.FC<Props> = ({node}) => {
<Glyph name="square-ruler" size={16} />
</Layout.Horizontal>
}>
<AttributesInspector mode="layout" node={node} />
<AttributesInspector mode="layout" node={node} metadata={metadata} />
</Tab>
<Tab
tab={

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 ?? (

View File

@@ -8,12 +8,36 @@
*/
import {PluginClient, createState, createDataSource} from 'flipper-plugin';
import {Events, Id, PerfStatsEvent, Snapshot, TreeState, UINode} from './types';
import {
Events,
Id,
Metadata,
MetadataId,
PerfStatsEvent,
Snapshot,
TreeState,
UINode,
} from './types';
import './node_modules/react-complex-tree/lib/style.css';
export function plugin(client: PluginClient<Events>) {
const rootId = createState<Id | undefined>(undefined);
client.onMessage('init', (root) => rootId.set(root.rootId));
const metadata = createState<Map<MetadataId, Metadata>>(new Map());
client.onMessage('init', (event) => {
rootId.set(event.rootId);
});
client.onMessage('metadataUpdate', (event) => {
if (!event.attributeMetadata) {
return;
}
metadata.update((draft) => {
for (const [_key, value] of Object.entries(event.attributeMetadata)) {
draft.set(value.id, value);
}
});
});
const perfEvents = createDataSource<PerfStatsEvent, 'txId'>([], {
key: 'txId',
@@ -23,13 +47,13 @@ export function plugin(client: PluginClient<Events>) {
perfEvents.append(event);
});
const nodesAtom = createState<Map<Id, UINode>>(new Map());
const snapshotsAtom = createState<Map<Id, Snapshot>>(new Map());
const nodes = createState<Map<Id, UINode>>(new Map());
const snapshots = createState<Map<Id, Snapshot>>(new Map());
const treeState = createState<TreeState>({expandedNodes: []});
client.onMessage('coordinateUpdate', (event) => {
nodesAtom.update((draft) => {
nodes.update((draft) => {
const node = draft.get(event.nodeId);
if (!node) {
console.warn(`Coordinate update for non existing node `, event);
@@ -42,13 +66,13 @@ export function plugin(client: PluginClient<Events>) {
const seenNodes = new Set<Id>();
client.onMessage('subtreeUpdate', (event) => {
snapshotsAtom.update((draft) => {
snapshots.update((draft) => {
draft.set(event.rootId, event.snapshot);
});
nodesAtom.update((draft) => {
for (const node of event.nodes) {
nodes.update((draft) => {
event.nodes.forEach((node) => {
draft.set(node.id, node);
}
});
});
treeState.update((draft) => {
@@ -74,8 +98,9 @@ export function plugin(client: PluginClient<Events>) {
return {
rootId,
snapshots: snapshotsAtom,
nodes: nodesAtom,
nodes,
metadata,
snapshots,
perfEvents,
treeState,
};

View File

@@ -14,6 +14,7 @@ export type Events = {
subtreeUpdate: SubtreeUpdateEvent;
coordinateUpdate: CoordinateUpdateEvent;
perfStats: PerfStatsEvent;
metadataUpdate: UpdateMetadataEvent;
};
export type CoordinateUpdateEvent = {
@@ -29,7 +30,9 @@ export type SubtreeUpdateEvent = {
snapshot: Snapshot;
};
export type InitEvent = {rootId: Id};
export type InitEvent = {
rootId: Id;
};
export type PerfStatsEvent = {
txId: number;
@@ -43,16 +46,29 @@ export type PerfStatsEvent = {
nodesCount: number;
};
export type UpdateMetadataEvent = {
attributeMetadata: Record<MetadataId, Metadata>;
};
export type UINode = {
id: Id;
name: string;
attributes: Record<string, Inspectable>;
attributes: Record<MetadataId, Inspectable>;
children: Id[];
bounds: Bounds;
tags: Tag[];
activeChild?: Id;
};
export type Metadata = {
id: MetadataId;
type: string;
namespace: string;
name: string;
mutable: boolean;
tags?: string[];
};
export type Bounds = {
x: number;
y: number;
@@ -93,6 +109,7 @@ export type Color = {
export type Snapshot = string;
export type Id = number | TreeItemIndex;
export type MetadataId = number;
export type TreeState = {expandedNodes: Id[]};
export type Tag = 'Native' | 'Declarative' | 'Android' | 'Litho ';
@@ -113,64 +130,54 @@ export type Inspectable =
export type InspectableText = {
type: 'text';
value: string;
mutable: boolean;
};
export type InspectableNumber = {
type: 'number';
value: number;
mutable: boolean;
};
export type InspectableBoolean = {
type: 'boolean';
value: boolean;
mutable: boolean;
};
export type InspectableEnum = {
type: 'enum';
value: {value: string; values: string[]};
mutable: boolean;
};
export type InspectableColor = {
type: 'color';
value: Color;
mutable: boolean;
};
export type InspectableBounds = {
type: 'bounds';
value: Bounds;
mutable: boolean;
};
export type InspectableSize = {
type: 'size';
value: Size;
mutable: boolean;
};
export type InspectableCoordinate = {
type: 'coordinate';
value: Coordinate;
mutable: boolean;
};
export type InspectableCoordinate3D = {
type: 'coordinate3d';
value: Coordinate3D;
mutable: boolean;
};
export type InspectableSpaceBox = {
type: 'space';
value: SpaceBox;
mutable: boolean;
};
export type InspectableObject = {
type: 'object';
fields: Record<string, Inspectable>;
fields: Record<MetadataId, Inspectable>;
};