diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx index 40cf77cc8..e5df4b07b 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx @@ -17,7 +17,7 @@ export const SmartUi = (): ClassDecorator => { }; }; -export const ClassInfo = (info: Info): ClassDecorator => { +export const ClassInfo = (info: (() => Promise) | Info): ClassDecorator => { return (target: Function) => { addPropertyToMap(target, "root", target.name, "info", info); }; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx index ec4d42985..bf7a29b9a 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx @@ -1,11 +1,13 @@ -import { EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent"; +import { ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent"; import { addPropertyToMap } from "./SelfServeUtils"; const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorator => { return (target, property) => { const className = (target as Function).name; - var propertyType = Reflect.getMetadata("design:type", target, property); - addPropertyToMap(target, property.toString(), className, "type", propertyType.name); + var propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase(); + + addPropertyToMap(target, property.toString(), className, "type", propertyType); + addPropertyToMap(target, property.toString(), className, "dataFieldName", property.toString()); if (!className) { throw new Error("property descriptor applied to non static field!"); @@ -20,11 +22,11 @@ export const OnChange = ( return addToMap("onChange", onChange); }; -export const PropertyInfo = (info: Info): PropertyDecorator => { +export const PropertyInfo = (info: (() => Promise) | Info): PropertyDecorator => { return addToMap("info", info); }; -export const Placeholder = (placeholder: string): PropertyDecorator => { +export const Placeholder = (placeholder: (() => Promise) | string): PropertyDecorator => { return addToMap("placeholder", placeholder); }; @@ -32,43 +34,47 @@ export const ParentOf = (children: string[]): PropertyDecorator => { return addToMap("parentOf", children); }; -export const Label = (label: string): PropertyDecorator => { +export const Label = (label: (() => Promise) | string): PropertyDecorator => { return addToMap("label", label); }; -export const DataFieldName = (dataFieldName: string): PropertyDecorator => { - return addToMap("dataFieldName", dataFieldName); -}; - -export const Min = (min: number): PropertyDecorator => { +export const Min = (min: (() => Promise) | number): PropertyDecorator => { return addToMap("min", min); }; -export const Max = (max: number): PropertyDecorator => { +export const Max = (max: (() => Promise) | number): PropertyDecorator => { return addToMap("max", max); }; -export const Step = (step: number): PropertyDecorator => { +export const Step = (step: (() => Promise) | number): PropertyDecorator => { return addToMap("step", step); }; -export const DefaultValue = (defaultValue: any): PropertyDecorator => { - return addToMap("defaultValue", defaultValue); +export const DefaultStringValue = (defaultStringValue: (() => Promise) | string): PropertyDecorator => { + return addToMap("defaultValue", defaultStringValue); }; -export const TrueLabel = (trueLabel: string): PropertyDecorator => { +export const DefaultNumberValue = (defaultNumberValue: (() => Promise) | number): PropertyDecorator => { + return addToMap("defaultValue", defaultNumberValue); +}; + +export const DefaultBooleanValue = (defaultBooleanValue: (() => Promise) | boolean): PropertyDecorator => { + return addToMap("defaultValue", defaultBooleanValue); +}; + +export const TrueLabel = (trueLabel: (() => Promise) | string): PropertyDecorator => { return addToMap("trueLabel", trueLabel); }; -export const FalseLabel = (falseLabel: string): PropertyDecorator => { +export const FalseLabel = (falseLabel: (() => Promise) | string): PropertyDecorator => { return addToMap("falseLabel", falseLabel); }; -export const Choices = (choices: EnumItem[]): PropertyDecorator => { +export const Choices = (choices: (() => Promise) | ChoiceItem[]): PropertyDecorator => { return addToMap("choices", choices); }; -export const DefaultKey = (defaultKey: string): PropertyDecorator => { +export const DefaultKey = (defaultKey: (() => Promise) | string): PropertyDecorator => { return addToMap("defaultKey", defaultKey); }; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx index 89c88c14c..c4205c211 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx @@ -24,7 +24,7 @@ export class SelfServeCmponent extends React.Component { input: { label: "Instance Count", dataFieldName: "instanceCount", - type: "Number", + type: "number", min: 1, max: 5, step: 1, @@ -44,7 +44,7 @@ export class SelfServeCmponent extends React.Component { input: { label: "Instance Size", dataFieldName: "instanceSize", - type: "Object", + type: "object", choices: [ { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx index b61d6f86d..010e39e31 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx @@ -1,6 +1,6 @@ import "reflect-metadata"; import { - EnumItem, + ChoiceItem, Node, Info, InputTypeValue, @@ -9,26 +9,26 @@ import { NumberInput, StringInput, BooleanInput, - EnumInput, + ChoiceInput, InputType } from "../../../SmartUi/SmartUiComponent"; export interface CommonInputTypes { id: string; - info?: Info; + info?: (() => Promise) | Info; parentOf?: string[]; type?: InputTypeValue; - label?: string; - placeholder?: string; + label?: (() => Promise) | string; + placeholder?: (() => Promise) | string; dataFieldName?: string; - min?: number; - max?: number; - step?: number; + min?: (() => Promise) | number; + max?: (() => Promise) | number; + step?: (() => Promise) | number; defaultValue?: any; - trueLabel?: string; - falseLabel?: string; - choices?: EnumItem[]; - defaultKey?: string; + trueLabel?: (() => Promise) | string; + falseLabel?: (() => Promise) | string; + choices?: (() => Promise) | ChoiceItem[]; + defaultKey?: (() => Promise) | string; inputType?: string; onChange?: (currentState: Map, newValue: InputType) => Map; onSubmit?: (currentValues: Map) => Promise; @@ -72,7 +72,7 @@ export const addPropertyToMap = ( propertyObject = { id: propertyKey }; } - if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type") { + if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type" && descriptorKey !== "dataFieldName") { throw new Error("duplicate descriptor"); } @@ -82,42 +82,6 @@ export const addPropertyToMap = ( Reflect.defineMetadata(metadataKey, context, target); }; -/* -const modifyParentProperty = (children: {[key: string]: any}, parentProperty: string, property: string | symbol) : any => { - if (parentProperty in children) { - children[parentProperty][property] ={id: property, input: {}} - return children - } else { - const keys = Object.keys(children) - for(var i =0; i< keys.length; i++) { - children[keys[i]] = modifyParentProperty(children[keys[i]], parentProperty, property) - return children - } - } - return children -} - -export const PropertyParser = (metadataKey: string, parentProperty?: string): PropertyDecorator => { - return (target, property) => { - let context = Reflect.getMetadata(metadataKey, target) - if(!context) { - context = {id: "root", info: undefined, input: undefined, children: {} } - context.children[property] = {id: property, input: {}} - } - if (parentProperty) { - const prevContextValue = JSON.stringify(context) - context.children = modifyParentProperty(context.children, parentProperty, property) - if (JSON.stringify(context) === prevContextValue) { - throw new Error(`${parentProperty} not defined. declare it before the child property with @Property decorator.`) - } - } else { - context.children[property] = {id: property, input: {}} - } - Reflect.defineMetadata(metadataKey, context, target) - }; -}; -*/ - export const toSmartUiDescriptor = (metadataKey: string, target: Object): void => { const context = Reflect.getMetadata(metadataKey, target) as Map; Reflect.defineMetadata(metadataKey, context, target); @@ -125,7 +89,7 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void = const root = context.get("root"); context.delete("root"); - if (!root || !("onSubmit" in root)) { + if (!root?.onSubmit) { throw new Error( "@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class." ); @@ -157,12 +121,12 @@ const addToDescriptor = ( let value = context.get(key); if (!value) { // should already be added to root - value = getChildFromRoot(key, smartUiDescriptor); - if (!value) { + const childNode = getChildFromRoot(key, smartUiDescriptor); + if (!childNode) { // if not found at root level, error out throw new Error("Either child does not exist or child has been assigned to more than one parent"); } - root.children.push(value); + root.children.push(childNode); return; } @@ -180,7 +144,7 @@ const addToDescriptor = ( root.children.push(element); }; -const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInputTypes => { +const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => { let i = 0; const children = smartUiDescriptor.root.children; for (; i < children.length; i++) { @@ -197,16 +161,16 @@ const getInput = (value: CommonInputTypes): AnyInput => { if (!value.label || !value.type || !value.dataFieldName) { throw new Error("label, onChange, type and dataFieldName are required."); } - console.log(value.type); + switch (value.type) { - case "Number": + case "number": if (!value.step || !value.defaultValue || !value.inputType) { throw new Error("step, defaultValue and inputType are needed for number type"); } return value as NumberInput; - case "String": + case "string": return value as StringInput; - case "Boolean": + case "boolean": if (!value.trueLabel || !value.falseLabel || value.defaultValue === undefined) { throw new Error("truelabel, falselabel and defaultValue are needed for boolean type"); } @@ -215,6 +179,6 @@ const getInput = (value: CommonInputTypes): AnyInput => { if (!value.choices || !value.defaultKey) { throw new Error("choices and defaultKey are needed for enum type"); } - return value as EnumInput; + return value as ChoiceInput; } }; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx index 0244e85e3..e2389b229 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx @@ -1,11 +1,9 @@ import { - DataFieldName, Label, Min, Max, Step, DefaultKey, - DefaultValue, NumberInputType, Choices, ParentOf, @@ -13,11 +11,23 @@ import { OnChange, TrueLabel, FalseLabel, - Placeholder + Placeholder, + DefaultNumberValue, + DefaultBooleanValue, + DefaultStringValue } from "./PropertyDescriptors"; -import { Descriptor, EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent"; +import { Descriptor, ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent"; import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors"; +const getPromise = (value: T) : () => Promise => { + const f = async () : Promise => { + console.log("delay start") + await SqlX.delay(100) + console.log("delay end") + return value + } + return f +} enum Sizes { OneCore4Gb = "OneCore4Gb", TwoCore8Gb = "TwoCore8Gb", @@ -25,41 +35,43 @@ enum Sizes { } @SmartUi() -@ClassInfo(SqlX.sqlXInfo) -@OnSubmit(SqlX.onSubmit) @SelfServeClass() +@ClassInfo(getPromise(SqlX.sqlXInfo)) +@OnSubmit(SqlX.onSubmit) export class SqlX { - @PropertyInfo(SqlX.instanceSizeInfo) - @Label("Instance Size") - @DataFieldName("instanceSize") - @Choices(SqlX.instanceSizeOptions) - @DefaultKey(Sizes.OneCore4Gb) - static instanceSize: EnumItem; + + public static toSmartUiDescriptor = (): Descriptor => { + return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor; + }; + + @PropertyInfo(getPromise(SqlX.instanceSizeInfo)) + @Label(getPromise("Instance Size")) + @Choices(getPromise(SqlX.instanceSizeOptions)) + @DefaultKey(getPromise(Sizes.OneCore4Gb)) + static instanceSize: ChoiceItem; @OnChange(SqlX.onInstanceCountChange) - @Label("Instance Count") - @DataFieldName("instanceCount") - @Min(1) - @Max(5) - @Step(1) - @DefaultValue(1) + @Label(getPromise("Instance Count")) + @Min(getPromise(0)) + @Max(getPromise(5)) + @Step(getPromise(1)) + @DefaultNumberValue(getPromise(1)) @NumberInputType("slider") @ParentOf(["instanceSize", "instanceName", "isAllowed"]) static instanceCount: number; - @Label("Feature Allowed") - @DataFieldName("isAllowed") - @DefaultValue(false) - @TrueLabel("allowed") - @FalseLabel("not allowed") + @Label(getPromise("Feature Allowed")) + @DefaultBooleanValue(getPromise(false)) + @TrueLabel(getPromise("allowed")) + @FalseLabel(getPromise("not allowed")) static isAllowed: boolean; - @Label("Instance Name") - @DataFieldName("instanceName") - @Placeholder("instance name") + @Label(getPromise("Instance Name")) + @DefaultStringValue(getPromise("asdf")) + @Placeholder(getPromise("instance name")) static instanceName: string; - static instanceSizeOptions: EnumItem[] = [ + static instanceSizeOptions: ChoiceItem[] = [ { label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb }, { label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb }, { label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb } @@ -97,7 +109,8 @@ export class SqlX { ); }; - public static toSmartUiDescriptor = (): Descriptor => { - return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor; - }; + static delay = (ms: number) => { + return new Promise( resolve => setTimeout(resolve, ms) ); + } } + diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx index 7084c7795..71c2df5d6 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx @@ -69,7 +69,7 @@ describe("SmartUiComponent", () => { input: { label: "Database", dataFieldName: "database", - type: "enum", + type: "object", choices: [ { label: "Database 1", key: "db1", value: "database1" }, { label: "Database 2", key: "db2", value: "database2" }, diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx index 2e1f6e188..b00fd6599 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx @@ -8,7 +8,7 @@ import { Text } from "office-ui-fabric-react/lib/Text"; import { InputType } from "../../Tables/Constants"; import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent"; import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack"; -import { Link, MessageBar, MessageBarType, PrimaryButton } from "office-ui-fabric-react"; +import { Link, MessageBar, MessageBarType, PrimaryButton, Spinner, SpinnerSize } from "office-ui-fabric-react"; import * as InputUtils from "./InputUtils"; import "./SmartUiComponent.less"; @@ -21,45 +21,45 @@ import "./SmartUiComponent.less"; * - a descriptor of the UX. */ -export type InputTypeValue = "Number" | "String" | "Boolean" | "Object"; +export type InputTypeValue = "number" | "string" | "boolean" | "object"; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -export type EnumItem = { label: string; key: string; value: any }; +export type ChoiceItem = { label: string; key: string; value: any }; -export type InputType = Number | String | Boolean | EnumItem; +export type InputType = Number | String | Boolean | ChoiceItem; interface BaseInput { - label: string; + label: (() => Promise) | string; dataFieldName: string; type: InputTypeValue; onChange?: (currentState: Map, newValue: InputType) => Map; - placeholder?: string; + placeholder?: (() => Promise) | string; } /** * For now, this only supports integers */ export interface NumberInput extends BaseInput { - min?: number; - max?: number; - step: number; - defaultValue: number; + min?: (() => Promise) | number; + max?: (() => Promise) | number + step: (() => Promise) | number + defaultValue: (() => Promise) | number inputType: "spin" | "slider"; } export interface BooleanInput extends BaseInput { - trueLabel: string; - falseLabel: string; - defaultValue: boolean; + trueLabel: (() => Promise) | string; + falseLabel: (() => Promise) | string; + defaultValue: (() => Promise) | boolean; } export interface StringInput extends BaseInput { - defaultValue?: string; + defaultValue?: (() => Promise) | string; } -export interface EnumInput extends BaseInput { - choices: EnumItem[]; - defaultKey: string; +export interface ChoiceInput extends BaseInput { + choices: (() => Promise) | ChoiceItem[]; + defaultKey: (() => Promise) | string; } export interface Info { @@ -70,11 +70,11 @@ export interface Info { }; } -export type AnyInput = NumberInput | BooleanInput | StringInput | EnumInput; +export type AnyInput = NumberInput | BooleanInput | StringInput | ChoiceInput; export interface Node { id: string; - info?: Info; + info?: (() => Promise) | Info; input?: AnyInput; children?: Node[]; } @@ -105,34 +105,98 @@ export class SmartUiComponent extends React.Component => { - const defaults = new Map(); - this.setDefaults(this.props.descriptor.root, defaults); - return defaults; - }; + private setDefaultValues = async () : Promise => { + const defaults = new Map() + await this.setDefaults(this.props.descriptor.root, defaults) + this.setState({currentValues: defaults}) + } - private setDefaults = (currentNode: Node, defaults: Map) => { - if (currentNode.input?.dataFieldName) { - defaults.set(currentNode.input.dataFieldName, this.getDefault(currentNode.input)); + private setDefaults = async (currentNode: Node, defaults: Map) : Promise => { + if (currentNode.info && currentNode.info instanceof Function) { + currentNode.info = await (currentNode.info as Function)() } - currentNode.children?.map((child: Node) => this.setDefaults(child, defaults)); + + if (currentNode.input) { + currentNode.input = await this.getModifiedInput(currentNode.input) + defaults.set(currentNode.input.dataFieldName, this.getDefaultValue(currentNode.input)); + } + + await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child, defaults))); }; - private getDefault = (input: AnyInput): InputType => { + private getModifiedInput = async (input: AnyInput): Promise => { + + if (input.label instanceof Function) { + input.label = await (input.label as Function)() + } + + if (input.placeholder instanceof Function) { + input.placeholder = await (input.placeholder as Function)() + } + switch (input.type) { - case "String": - return (input as StringInput).defaultValue; - case "Number": - return (input as NumberInput).defaultValue; - case "Boolean": - return (input as BooleanInput).defaultValue; + case "string": + const stringInput = input as StringInput + if (stringInput.defaultValue instanceof Function) { + stringInput.defaultValue = await (stringInput.defaultValue as Function)() + } + return stringInput; + case "number": + const numberInput = input as NumberInput + if (numberInput.defaultValue instanceof Function) { + numberInput.defaultValue = await (numberInput.defaultValue as Function)() + } + if (numberInput.min instanceof Function) { + numberInput.min = await (numberInput.min as Function)() + } + if (numberInput.max instanceof Function) { + numberInput.max = await (numberInput.max as Function)() + } + if (numberInput.step instanceof Function) { + numberInput.step = await (numberInput.step as Function)() + } + return numberInput; + case "boolean": + const booleanInput = input as BooleanInput + if (booleanInput.defaultValue instanceof Function) { + booleanInput.defaultValue = await (booleanInput.defaultValue as Function)() + } + if (booleanInput.trueLabel instanceof Function) { + booleanInput.trueLabel = await (booleanInput.trueLabel as Function)() + } + if (booleanInput.falseLabel instanceof Function) { + booleanInput.falseLabel = await (booleanInput.falseLabel as Function)() + } + return booleanInput; default: - return (input as EnumInput).defaultKey; + const enumInput = input as ChoiceInput + if (enumInput.defaultKey instanceof Function) { + enumInput.defaultKey = await (enumInput.defaultKey as Function)() + } + if (enumInput.choices instanceof Function) { + enumInput.choices = await (enumInput.choices as Function)() + } + return enumInput + } + }; + + private getDefaultValue = (input: AnyInput): InputType => { + switch (input.type) { + case "string": + return (input as StringInput).defaultValue as string; + case "number": + return (input as NumberInput).defaultValue as number; + case "boolean": + return (input as BooleanInput).defaultValue as boolean; + default: + return (input as ChoiceInput).defaultKey as string; } }; @@ -167,10 +231,10 @@ export class SmartUiComponent extends React.Component this.onInputChange(input, newValue)} styles={{ subComponentStyles: { @@ -233,17 +297,23 @@ export class SmartUiComponent extends React.Component this.onValidate(input, newValue, min, max)} - onIncrement={newValue => this.onIncrement(input, newValue, step, max)} - onDecrement={newValue => this.onDecrement(input, newValue, step, min)} + defaultValue={(defaultValue as number).toString()} + onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)} + onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)} + onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)} labelPosition={Position.top} styles={{ label: { @@ -263,7 +333,7 @@ export class SmartUiComponent extends React.Component this.onInputChange(input, newValue)} styles={{ titleLabel: { @@ -291,12 +361,12 @@ export class SmartUiComponent extends React.Component this.onInputChange(input, false) }, { - label: input.trueLabel, + label: input.trueLabel as string, key: "true", onSelect: () => this.onInputChange(input, true) } @@ -313,19 +383,19 @@ export class SmartUiComponent extends React.Component this.onInputChange(input, item.key.toString())} - placeholder={placeholder} - options={choices.map(c => ({ + placeholder={placeholder as string} + options={(choices as ChoiceItem[]).map(c => ({ key: c.key, text: c.value }))} @@ -342,14 +412,14 @@ export class SmartUiComponent extends React.Component - {node.info && this.renderInfo(node.info)} + {node.info && this.renderInfo(node.info as Info)} {node.input && this.renderInput(node.input)} {node.children && node.children.map(child =>
{this.renderNode(child)}
)} @@ -367,8 +437,8 @@ export class SmartUiComponent extends React.Component {this.renderNode(this.props.descriptor.root)} await this.props.descriptor.onSubmit(this.state.currentValues)} /> + : + ); } }