diff --git a/optic/component_helpers/helper.py b/optic/component_helpers/helper.py index f81fdd192..3f4de1a80 100644 --- a/optic/component_helpers/helper.py +++ b/optic/component_helpers/helper.py @@ -1,5 +1,8 @@ from typing import Any, Callable, Type, TypeVar +from google.protobuf import json_format +from google.protobuf.message import Message + import optic.protos.ui_pb2 as pb from optic.events import OpticEvent from optic.key import Key @@ -7,12 +10,9 @@ class ComponentWithChildren: - def __init__( - self, - component: pb.Component, - ): + def __init__(self, type_name: str, proto: Message, key: str | None = None): self.prev_current_node = runtime().context().current_node() - self.component = component + self.component = create_component(type_name=type_name, proto=proto, key=key) def __enter__(self): runtime().context().set_current_node(self.component) @@ -24,13 +24,24 @@ def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore self.prev_current_node.children.append(self.component) -def insert_component(type: pb.Type, key: str | None = None): +def create_component( + type_name: str, proto: Message, key: str | None = None +) -> pb.Component: + type = pb.Type(name=type_name, value=proto.SerializeToString()) + if runtime().debug_mode: + type.debug_json = json_format.MessageToJson( + proto, preserving_proto_field_name=True + ) + + return pb.Component(key=pb.Key(key=key) if key else None, type=type) + + +def insert_component(type_name: str, proto: Message, key: str | None = None): """ Inserts a component into the current context's current node. """ - runtime().context().current_node().children.append( - pb.Component(key=pb.Key(key=key) if key else None, type=type) + create_component(type_name=type_name, proto=proto, key=key) ) diff --git a/optic/components/box/box.py b/optic/components/box/box.py index 60842ed13..09ab17dfb 100644 --- a/optic/components/box/box.py +++ b/optic/components/box/box.py @@ -1,7 +1,6 @@ from pydantic import validate_arguments import optic.components.box.box_pb2 as box_pb -import optic.protos.ui_pb2 as pb from optic.component_helpers import ComponentWithChildren @@ -18,13 +17,7 @@ def box( label (str): The text to be displayed """ return ComponentWithChildren( - component=pb.Component( - key=pb.Key(key=key or ""), - type=pb.Type( - name="box", - value=box_pb.BoxType( - background_color=background_color - ).SerializeToString(), - ), - ), + key=key, + type_name="box", + proto=box_pb.BoxType(background_color=background_color), ) diff --git a/optic/components/button/button.py b/optic/components/button/button.py index ff61f21d7..a90d27001 100644 --- a/optic/components/button/button.py +++ b/optic/components/button/button.py @@ -3,8 +3,10 @@ from pydantic import validate_arguments import optic.components.button.button_pb2 as button_pb -import optic.protos.ui_pb2 as pb -from optic.component_helpers import handler_type, insert_component +from optic.component_helpers import ( + handler_type, + insert_component, +) from optic.events import ClickEvent @@ -24,11 +26,9 @@ def button( """ insert_component( key=key, - type=pb.Type( - name="button", - value=button_pb.ButtonType( - label=label, - on_click_handler_id=handler_type(on_click), - ).SerializeToString(), + type_name="button", + proto=button_pb.ButtonType( + label=label, + on_click_handler_id=handler_type(on_click), ), ) diff --git a/optic/components/checkbox/checkbox.py b/optic/components/checkbox/checkbox.py index 3d447484c..038680f5a 100644 --- a/optic/components/checkbox/checkbox.py +++ b/optic/components/checkbox/checkbox.py @@ -4,7 +4,6 @@ from pydantic import validate_arguments import optic.components.checkbox.checkbox_pb2 as checkbox_pb -import optic.protos.ui_pb2 as pb from optic.component_helpers import ( handler_type, insert_component, @@ -36,12 +35,10 @@ def checkbox( """ insert_component( key=key, - type=pb.Type( - name="checkbox", - value=checkbox_pb.CheckboxType( - label=label, - on_update_handler_id=handler_type(on_update), - ).SerializeToString(), + type_name="checkbox", + proto=checkbox_pb.CheckboxType( + label=label, + on_update_handler_id=handler_type(on_update), ), ) diff --git a/optic/components/text/text.py b/optic/components/text/text.py index 48bd40847..9ea7b5a2a 100644 --- a/optic/components/text/text.py +++ b/optic/components/text/text.py @@ -1,7 +1,6 @@ from pydantic import validate_arguments import optic.components.text.text_pb2 as text_pb2 -import optic.protos.ui_pb2 as pb from optic.component_helpers import insert_component @@ -12,8 +11,5 @@ def text( key: str | None = None, ): insert_component( - key=key, - type=pb.Type( - name="text", value=text_pb2.TextType(text=text).SerializeToString() - ), + key=key, type_name="text", proto=text_pb2.TextType(text=text) ) diff --git a/optic/components/text_input/text_input.py b/optic/components/text_input/text_input.py index 3529b6df5..4f112b45d 100644 --- a/optic/components/text_input/text_input.py +++ b/optic/components/text_input/text_input.py @@ -3,7 +3,6 @@ from pydantic import validate_arguments import optic.components.text_input.text_input_pb2 as text_input_pb -import optic.protos.ui_pb2 as pb from optic.component_helpers import handler_type, insert_component from optic.events import ChangeEvent @@ -24,11 +23,9 @@ def text_input( """ insert_component( key=key, - type=pb.Type( - name="text_input", - value=text_input_pb.TextInputType( - label=label, - on_change_handler_id=handler_type(on_change), - ).SerializeToString(), + type_name="text_input", + proto=text_input_pb.TextInputType( + label=label, + on_change_handler_id=handler_type(on_change), ), ) diff --git a/optic/web/src/dev_tools/component_tree/component_tree.ts b/optic/web/src/dev_tools/component_tree/component_tree.ts index 297228678..8a5786f10 100644 --- a/optic/web/src/dev_tools/component_tree/component_tree.ts +++ b/optic/web/src/dev_tools/component_tree/component_tree.ts @@ -8,6 +8,7 @@ import { import {CdkTreeModule} from '@angular/cdk/tree'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; +import {ComponentObject} from '../services/logger'; /** Flat node with expandable and level information */ export interface ExampleFlatNode { @@ -26,7 +27,7 @@ export interface ExampleFlatNode { imports: [CdkTreeModule, MatTreeModule, MatButtonModule, MatIconModule], }) export class ComponentTree { - @Input({required: true}) component!: InputNode; + @Input({required: true}) component!: ComponentObject; @Output() nodeSelected = new EventEmitter(); keys() { @@ -73,7 +74,7 @@ export class ComponentTree { } } -function mapObject(object: InputNode): DisplayNode { +function mapObject(object: ComponentObject): DisplayNode { const node: DisplayNode = { componentName: '', text: '', @@ -87,15 +88,16 @@ function mapObject(object: InputNode): DisplayNode { return `${key}=${JSON.stringify(value)}`; }) .join(', '); - node.text = `${object.type.name}(${values})`; - node.properties = object.type.value; - (node.properties as any).key = object.key?.key; - node.componentName = object.type.name; + const name = object.type.name; + node.text = `${name}(${values})`; + node.properties = object.type as any; + (node.properties as any).key = object.key; + node.componentName = name; } else { node.text = ``; } - if (object.childrenList) { - node.children = object.childrenList.map((child) => mapObject(child)); + if (object.children) { + node.children = object.children.map((child) => mapObject(child)); } return node; } diff --git a/optic/web/src/dev_tools/components_panel/components_panel.ts b/optic/web/src/dev_tools/components_panel/components_panel.ts index f531f8914..e509dbfff 100644 --- a/optic/web/src/dev_tools/components_panel/components_panel.ts +++ b/optic/web/src/dev_tools/components_panel/components_panel.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {Logger, RenderLogModel} from '../services/logger'; +import {ComponentObject, Logger, RenderLogModel} from '../services/logger'; import { ComponentTree, ExampleFlatNode, @@ -18,13 +18,13 @@ export class ComponentsPanel { selectedNode!: ExampleFlatNode; constructor(private logger: Logger) {} - component(): InputNode { + component(): ComponentObject { const renderLog = this.logger .getLogs() .slice() .reverse() .find((log) => log.type === 'Render') as RenderLogModel; - return renderLog?.rootComponent as InputNode; + return renderLog?.rootComponent as ComponentObject; } onNodeSelected(node: ExampleFlatNode): void { diff --git a/optic/web/src/dev_tools/services/logger.ts b/optic/web/src/dev_tools/services/logger.ts index 9fd77ef85..5f49a15e8 100644 --- a/optic/web/src/dev_tools/services/logger.ts +++ b/optic/web/src/dev_tools/services/logger.ts @@ -3,6 +3,7 @@ import { States, UserEvent, Component as ComponentProto, + Type, } from 'optic/optic/protos/ui_jspb_proto_pb/optic/protos/ui_pb'; import {TypeDeserializer} from './type_deserializer'; import {Observable, Subject} from 'rxjs'; @@ -55,8 +56,6 @@ export class Logger { duration, }; case 'RenderLog': - const rootComponent = input.rootComponent.toObject(); - this.updateComponent(rootComponent); return { type: 'Render', timestamp: Date.now(), @@ -64,28 +63,39 @@ export class Logger { states: input.states .getStatesList() .map((s) => jsonParse(s.getData())) as object[], - rootComponent, + rootComponent: this.mapComponent(input.rootComponent), }; } } - updateComponent(component: object): void { - const type = (component as any)['type']; - if (type) { - type['value'] = this._typeDeserializer.deserialize( - type['name'], - type['value'], - ); - } - const children = (component as any)['childrenList']; - if (children) { - for (const child of children) { - this.updateComponent(child); - } + mapComponent(component: ComponentProto): ComponentObject { + const debugJson = component.getType()?.getDebugJson(); + let type; + if (debugJson) { + type = { + name: component.getType()!.getName(), + value: jsonParse(debugJson) as object, + }; } + return { + type, + key: component.getKey()?.getKey(), + children: component + .getChildrenList() + .map((child) => this.mapComponent(child)), + }; } } +export interface ComponentObject { + type?: { + name: string; + value: object; + }; + key?: string; + children: ComponentObject[]; +} + export interface BaseLogModel { type: string; timestamp: number; // Use Date.now() @@ -107,7 +117,7 @@ export interface UserEventLogModel extends BaseLogModel { export interface RenderLogModel extends BaseLogModel { type: 'Render'; - rootComponent: object; + rootComponent: ComponentObject; states: object[]; }