proper resolution of promises

This commit is contained in:
Srinath Narayanan 2020-12-03 01:11:07 -08:00
parent 4381ea447c
commit 2dbde9c31a
7 changed files with 226 additions and 171 deletions

View File

@ -17,7 +17,7 @@ export const SmartUi = (): ClassDecorator => {
}; };
}; };
export const ClassInfo = (info: Info): ClassDecorator => { export const ClassInfo = (info: (() => Promise<Info>) | Info): ClassDecorator => {
return (target: Function) => { return (target: Function) => {
addPropertyToMap(target, "root", target.name, "info", info); addPropertyToMap(target, "root", target.name, "info", info);
}; };

View File

@ -1,11 +1,13 @@
import { EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent"; import { ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
import { addPropertyToMap } from "./SelfServeUtils"; import { addPropertyToMap } from "./SelfServeUtils";
const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorator => { const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorator => {
return (target, property) => { return (target, property) => {
const className = (target as Function).name; const className = (target as Function).name;
var propertyType = Reflect.getMetadata("design:type", target, property); var propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase();
addPropertyToMap(target, property.toString(), className, "type", propertyType.name);
addPropertyToMap(target, property.toString(), className, "type", propertyType);
addPropertyToMap(target, property.toString(), className, "dataFieldName", property.toString());
if (!className) { if (!className) {
throw new Error("property descriptor applied to non static field!"); throw new Error("property descriptor applied to non static field!");
@ -20,11 +22,11 @@ export const OnChange = (
return addToMap("onChange", onChange); return addToMap("onChange", onChange);
}; };
export const PropertyInfo = (info: Info): PropertyDecorator => { export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
return addToMap("info", info); return addToMap("info", info);
}; };
export const Placeholder = (placeholder: string): PropertyDecorator => { export const Placeholder = (placeholder: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("placeholder", placeholder); return addToMap("placeholder", placeholder);
}; };
@ -32,43 +34,47 @@ export const ParentOf = (children: string[]): PropertyDecorator => {
return addToMap("parentOf", children); return addToMap("parentOf", children);
}; };
export const Label = (label: string): PropertyDecorator => { export const Label = (label: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("label", label); return addToMap("label", label);
}; };
export const DataFieldName = (dataFieldName: string): PropertyDecorator => { export const Min = (min: (() => Promise<number>) | number): PropertyDecorator => {
return addToMap("dataFieldName", dataFieldName);
};
export const Min = (min: number): PropertyDecorator => {
return addToMap("min", min); return addToMap("min", min);
}; };
export const Max = (max: number): PropertyDecorator => { export const Max = (max: (() => Promise<number>) | number): PropertyDecorator => {
return addToMap("max", max); return addToMap("max", max);
}; };
export const Step = (step: number): PropertyDecorator => { export const Step = (step: (() => Promise<number>) | number): PropertyDecorator => {
return addToMap("step", step); return addToMap("step", step);
}; };
export const DefaultValue = (defaultValue: any): PropertyDecorator => { export const DefaultStringValue = (defaultStringValue: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("defaultValue", defaultValue); return addToMap("defaultValue", defaultStringValue);
}; };
export const TrueLabel = (trueLabel: string): PropertyDecorator => { export const DefaultNumberValue = (defaultNumberValue: (() => Promise<number>) | number): PropertyDecorator => {
return addToMap("defaultValue", defaultNumberValue);
};
export const DefaultBooleanValue = (defaultBooleanValue: (() => Promise<boolean>) | boolean): PropertyDecorator => {
return addToMap("defaultValue", defaultBooleanValue);
};
export const TrueLabel = (trueLabel: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("trueLabel", trueLabel); return addToMap("trueLabel", trueLabel);
}; };
export const FalseLabel = (falseLabel: string): PropertyDecorator => { export const FalseLabel = (falseLabel: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("falseLabel", falseLabel); return addToMap("falseLabel", falseLabel);
}; };
export const Choices = (choices: EnumItem[]): PropertyDecorator => { export const Choices = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[]): PropertyDecorator => {
return addToMap("choices", choices); return addToMap("choices", choices);
}; };
export const DefaultKey = (defaultKey: string): PropertyDecorator => { export const DefaultKey = (defaultKey: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("defaultKey", defaultKey); return addToMap("defaultKey", defaultKey);
}; };

View File

@ -24,7 +24,7 @@ export class SelfServeCmponent extends React.Component {
input: { input: {
label: "Instance Count", label: "Instance Count",
dataFieldName: "instanceCount", dataFieldName: "instanceCount",
type: "Number", type: "number",
min: 1, min: 1,
max: 5, max: 5,
step: 1, step: 1,
@ -44,7 +44,7 @@ export class SelfServeCmponent extends React.Component {
input: { input: {
label: "Instance Size", label: "Instance Size",
dataFieldName: "instanceSize", dataFieldName: "instanceSize",
type: "Object", type: "object",
choices: [ choices: [
{ label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" },
{ label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" },

View File

@ -1,6 +1,6 @@
import "reflect-metadata"; import "reflect-metadata";
import { import {
EnumItem, ChoiceItem,
Node, Node,
Info, Info,
InputTypeValue, InputTypeValue,
@ -9,26 +9,26 @@ import {
NumberInput, NumberInput,
StringInput, StringInput,
BooleanInput, BooleanInput,
EnumInput, ChoiceInput,
InputType InputType
} from "../../../SmartUi/SmartUiComponent"; } from "../../../SmartUi/SmartUiComponent";
export interface CommonInputTypes { export interface CommonInputTypes {
id: string; id: string;
info?: Info; info?: (() => Promise<Info>) | Info;
parentOf?: string[]; parentOf?: string[];
type?: InputTypeValue; type?: InputTypeValue;
label?: string; label?: (() => Promise<string>) | string;
placeholder?: string; placeholder?: (() => Promise<string>) | string;
dataFieldName?: string; dataFieldName?: string;
min?: number; min?: (() => Promise<number>) | number;
max?: number; max?: (() => Promise<number>) | number;
step?: number; step?: (() => Promise<number>) | number;
defaultValue?: any; defaultValue?: any;
trueLabel?: string; trueLabel?: (() => Promise<string>) | string;
falseLabel?: string; falseLabel?: (() => Promise<string>) | string;
choices?: EnumItem[]; choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
defaultKey?: string; defaultKey?: (() => Promise<string>) | string;
inputType?: string; inputType?: string;
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>; onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>; onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
@ -72,7 +72,7 @@ export const addPropertyToMap = (
propertyObject = { id: propertyKey }; propertyObject = { id: propertyKey };
} }
if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type") { if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type" && descriptorKey !== "dataFieldName") {
throw new Error("duplicate descriptor"); throw new Error("duplicate descriptor");
} }
@ -82,42 +82,6 @@ export const addPropertyToMap = (
Reflect.defineMetadata(metadataKey, context, target); 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 => { export const toSmartUiDescriptor = (metadataKey: string, target: Object): void => {
const context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>; const context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>;
Reflect.defineMetadata(metadataKey, context, target); Reflect.defineMetadata(metadataKey, context, target);
@ -125,7 +89,7 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void =
const root = context.get("root"); const root = context.get("root");
context.delete("root"); context.delete("root");
if (!root || !("onSubmit" in root)) { if (!root?.onSubmit) {
throw new Error( throw new Error(
"@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class." "@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); let value = context.get(key);
if (!value) { if (!value) {
// should already be added to root // should already be added to root
value = getChildFromRoot(key, smartUiDescriptor); const childNode = getChildFromRoot(key, smartUiDescriptor);
if (!value) { if (!childNode) {
// if not found at root level, error out // 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"); 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; return;
} }
@ -180,7 +144,7 @@ const addToDescriptor = (
root.children.push(element); root.children.push(element);
}; };
const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInputTypes => { const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => {
let i = 0; let i = 0;
const children = smartUiDescriptor.root.children; const children = smartUiDescriptor.root.children;
for (; i < children.length; i++) { for (; i < children.length; i++) {
@ -197,16 +161,16 @@ const getInput = (value: CommonInputTypes): AnyInput => {
if (!value.label || !value.type || !value.dataFieldName) { if (!value.label || !value.type || !value.dataFieldName) {
throw new Error("label, onChange, type and dataFieldName are required."); throw new Error("label, onChange, type and dataFieldName are required.");
} }
console.log(value.type);
switch (value.type) { switch (value.type) {
case "Number": case "number":
if (!value.step || !value.defaultValue || !value.inputType) { if (!value.step || !value.defaultValue || !value.inputType) {
throw new Error("step, defaultValue and inputType are needed for number type"); throw new Error("step, defaultValue and inputType are needed for number type");
} }
return value as NumberInput; return value as NumberInput;
case "String": case "string":
return value as StringInput; return value as StringInput;
case "Boolean": case "boolean":
if (!value.trueLabel || !value.falseLabel || value.defaultValue === undefined) { if (!value.trueLabel || !value.falseLabel || value.defaultValue === undefined) {
throw new Error("truelabel, falselabel and defaultValue are needed for boolean type"); 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) { if (!value.choices || !value.defaultKey) {
throw new Error("choices and defaultKey are needed for enum type"); throw new Error("choices and defaultKey are needed for enum type");
} }
return value as EnumInput; return value as ChoiceInput;
} }
}; };

View File

@ -1,11 +1,9 @@
import { import {
DataFieldName,
Label, Label,
Min, Min,
Max, Max,
Step, Step,
DefaultKey, DefaultKey,
DefaultValue,
NumberInputType, NumberInputType,
Choices, Choices,
ParentOf, ParentOf,
@ -13,11 +11,23 @@ import {
OnChange, OnChange,
TrueLabel, TrueLabel,
FalseLabel, FalseLabel,
Placeholder Placeholder,
DefaultNumberValue,
DefaultBooleanValue,
DefaultStringValue
} from "./PropertyDescriptors"; } 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"; import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors";
const getPromise = <T extends (number | string | boolean | ChoiceItem[] | Info)>(value: T) : () => Promise<T> => {
const f = async () : Promise<T> => {
console.log("delay start")
await SqlX.delay(100)
console.log("delay end")
return value
}
return f
}
enum Sizes { enum Sizes {
OneCore4Gb = "OneCore4Gb", OneCore4Gb = "OneCore4Gb",
TwoCore8Gb = "TwoCore8Gb", TwoCore8Gb = "TwoCore8Gb",
@ -25,41 +35,43 @@ enum Sizes {
} }
@SmartUi() @SmartUi()
@ClassInfo(SqlX.sqlXInfo)
@OnSubmit(SqlX.onSubmit)
@SelfServeClass() @SelfServeClass()
@ClassInfo(getPromise(SqlX.sqlXInfo))
@OnSubmit(SqlX.onSubmit)
export class SqlX { export class SqlX {
@PropertyInfo(SqlX.instanceSizeInfo)
@Label("Instance Size") public static toSmartUiDescriptor = (): Descriptor => {
@DataFieldName("instanceSize") return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
@Choices(SqlX.instanceSizeOptions) };
@DefaultKey(Sizes.OneCore4Gb)
static instanceSize: EnumItem; @PropertyInfo(getPromise(SqlX.instanceSizeInfo))
@Label(getPromise("Instance Size"))
@Choices(getPromise(SqlX.instanceSizeOptions))
@DefaultKey(getPromise(Sizes.OneCore4Gb))
static instanceSize: ChoiceItem;
@OnChange(SqlX.onInstanceCountChange) @OnChange(SqlX.onInstanceCountChange)
@Label("Instance Count") @Label(getPromise("Instance Count"))
@DataFieldName("instanceCount") @Min(getPromise(0))
@Min(1) @Max(getPromise(5))
@Max(5) @Step(getPromise(1))
@Step(1) @DefaultNumberValue(getPromise(1))
@DefaultValue(1)
@NumberInputType("slider") @NumberInputType("slider")
@ParentOf(["instanceSize", "instanceName", "isAllowed"]) @ParentOf(["instanceSize", "instanceName", "isAllowed"])
static instanceCount: number; static instanceCount: number;
@Label("Feature Allowed") @Label(getPromise("Feature Allowed"))
@DataFieldName("isAllowed") @DefaultBooleanValue(getPromise(false))
@DefaultValue(false) @TrueLabel(getPromise("allowed"))
@TrueLabel("allowed") @FalseLabel(getPromise("not allowed"))
@FalseLabel("not allowed")
static isAllowed: boolean; static isAllowed: boolean;
@Label("Instance Name") @Label(getPromise("Instance Name"))
@DataFieldName("instanceName") @DefaultStringValue(getPromise("asdf"))
@Placeholder("instance name") @Placeholder(getPromise("instance name"))
static instanceName: string; static instanceName: string;
static instanceSizeOptions: EnumItem[] = [ static instanceSizeOptions: ChoiceItem[] = [
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb }, { label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb }, { label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb } { label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
@ -97,7 +109,8 @@ export class SqlX {
); );
}; };
public static toSmartUiDescriptor = (): Descriptor => { static delay = (ms: number) => {
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor; return new Promise( resolve => setTimeout(resolve, ms) );
};
} }
}

View File

@ -69,7 +69,7 @@ describe("SmartUiComponent", () => {
input: { input: {
label: "Database", label: "Database",
dataFieldName: "database", dataFieldName: "database",
type: "enum", type: "object",
choices: [ choices: [
{ label: "Database 1", key: "db1", value: "database1" }, { label: "Database 1", key: "db1", value: "database1" },
{ label: "Database 2", key: "db2", value: "database2" }, { label: "Database 2", key: "db2", value: "database2" },

View File

@ -8,7 +8,7 @@ import { Text } from "office-ui-fabric-react/lib/Text";
import { InputType } from "../../Tables/Constants"; import { InputType } from "../../Tables/Constants";
import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent"; import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent";
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack"; 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 * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less"; import "./SmartUiComponent.less";
@ -21,45 +21,45 @@ import "./SmartUiComponent.less";
* - a descriptor of the UX. * - 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 */ /* 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 { interface BaseInput {
label: string; label: (() => Promise<string>) | string;
dataFieldName: string; dataFieldName: string;
type: InputTypeValue; type: InputTypeValue;
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>; onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
placeholder?: string; placeholder?: (() => Promise<string>) | string;
} }
/** /**
* For now, this only supports integers * For now, this only supports integers
*/ */
export interface NumberInput extends BaseInput { export interface NumberInput extends BaseInput {
min?: number; min?: (() => Promise<number>) | number;
max?: number; max?: (() => Promise<number>) | number
step: number; step: (() => Promise<number>) | number
defaultValue: number; defaultValue: (() => Promise<number>) | number
inputType: "spin" | "slider"; inputType: "spin" | "slider";
} }
export interface BooleanInput extends BaseInput { export interface BooleanInput extends BaseInput {
trueLabel: string; trueLabel: (() => Promise<string>) | string;
falseLabel: string; falseLabel: (() => Promise<string>) | string;
defaultValue: boolean; defaultValue: (() => Promise<boolean>) | boolean;
} }
export interface StringInput extends BaseInput { export interface StringInput extends BaseInput {
defaultValue?: string; defaultValue?: (() => Promise<string>) | string;
} }
export interface EnumInput extends BaseInput { export interface ChoiceInput extends BaseInput {
choices: EnumItem[]; choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
defaultKey: string; defaultKey: (() => Promise<string>) | string;
} }
export interface Info { 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 { export interface Node {
id: string; id: string;
info?: Info; info?: (() => Promise<Info>) | Info;
input?: AnyInput; input?: AnyInput;
children?: Node[]; children?: Node[];
} }
@ -105,34 +105,98 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
constructor(props: SmartUiComponentProps) { constructor(props: SmartUiComponentProps) {
super(props); super(props);
this.state = { this.state = {
currentValues: this.setDefaultValues(), currentValues: undefined,
errors: new Map() errors: new Map()
}; };
this.setDefaultValues()
} }
private setDefaultValues = (): Map<string, InputType> => { private setDefaultValues = async () : Promise<void> => {
const defaults = new Map(); const defaults = new Map<string, InputType>()
this.setDefaults(this.props.descriptor.root, defaults); await this.setDefaults(this.props.descriptor.root, defaults)
return defaults; this.setState({currentValues: defaults})
};
private setDefaults = (currentNode: Node, defaults: Map<string, InputType>) => {
if (currentNode.input?.dataFieldName) {
defaults.set(currentNode.input.dataFieldName, this.getDefault(currentNode.input));
} }
currentNode.children?.map((child: Node) => this.setDefaults(child, defaults));
private setDefaults = async (currentNode: Node, defaults: Map<string, InputType>) : Promise<void> => {
if (currentNode.info && currentNode.info instanceof Function) {
currentNode.info = await (currentNode.info as Function)()
}
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<AnyInput> => {
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) { switch (input.type) {
case "String": case "string":
return (input as StringInput).defaultValue; const stringInput = input as StringInput
case "Number": if (stringInput.defaultValue instanceof Function) {
return (input as NumberInput).defaultValue; stringInput.defaultValue = await (stringInput.defaultValue as Function)()
case "Boolean": }
return (input as BooleanInput).defaultValue; 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: 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<SmartUiComponentProps, Sma
<div> <div>
<TextField <TextField
id={`${input.dataFieldName}-input`} id={`${input.dataFieldName}-input`}
label={input.label} label={input.label as string}
type="text" type="text"
value={input.defaultValue} defaultValue={input.defaultValue as string}
placeholder={input.placeholder} placeholder={input.placeholder as string}
onChange={(_, newValue) => this.onInputChange(input, newValue)} onChange={(_, newValue) => this.onInputChange(input, newValue)}
styles={{ styles={{
subComponentStyles: { subComponentStyles: {
@ -233,17 +297,23 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
private renderNumberInput(input: NumberInput): JSX.Element { private renderNumberInput(input: NumberInput): JSX.Element {
const { label, min, max, defaultValue, dataFieldName, step } = input; const { label, min, max, defaultValue, dataFieldName, step } = input;
const props = { label, min, max, ariaLabel: label, step }; const props = {
label: label as string,
min: min as number,
max: max as number,
ariaLabel: label as string,
step: step as number
};
if (input.inputType === "spin") { if (input.inputType === "spin") {
return ( return (
<div> <div>
<SpinButton <SpinButton
{...props} {...props}
defaultValue={defaultValue.toString()} defaultValue={(defaultValue as number).toString()}
onValidate={newValue => this.onValidate(input, newValue, min, max)} onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={newValue => this.onIncrement(input, newValue, step, max)} onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={newValue => this.onDecrement(input, newValue, step, min)} onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)}
labelPosition={Position.top} labelPosition={Position.top}
styles={{ styles={{
label: { label: {
@ -263,7 +333,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
// showValue={true} // showValue={true}
// valueFormat={} // valueFormat={}
{...props} {...props}
defaultValue={defaultValue} defaultValue={defaultValue as number}
onChange={newValue => this.onInputChange(input, newValue)} onChange={newValue => this.onInputChange(input, newValue)}
styles={{ styles={{
titleLabel: { titleLabel: {
@ -291,12 +361,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
<RadioSwitchComponent <RadioSwitchComponent
choices={[ choices={[
{ {
label: input.falseLabel, label: input.falseLabel as string,
key: "false", key: "false",
onSelect: () => this.onInputChange(input, false) onSelect: () => this.onInputChange(input, false)
}, },
{ {
label: input.trueLabel, label: input.trueLabel as string,
key: "true", key: "true",
onSelect: () => this.onInputChange(input, true) onSelect: () => this.onInputChange(input, true)
} }
@ -313,19 +383,19 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
); );
} }
private renderEnumInput(input: EnumInput): JSX.Element { private renderEnumInput(input: ChoiceInput): JSX.Element {
const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input; const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
return ( return (
<Dropdown <Dropdown
label={label} label={label as string}
selectedKey={ selectedKey={
this.state.currentValues.has(dataFieldName) this.state.currentValues.has(dataFieldName)
? (this.state.currentValues.get(dataFieldName) as string) ? (this.state.currentValues.get(dataFieldName) as string)
: defaultKey : defaultKey as string
} }
onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())} onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
placeholder={placeholder} placeholder={placeholder as string}
options={choices.map(c => ({ options={(choices as ChoiceItem[]).map(c => ({
key: c.key, key: c.key,
text: c.value text: c.value
}))} }))}
@ -342,14 +412,14 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
private renderInput(input: AnyInput): JSX.Element { private renderInput(input: AnyInput): JSX.Element {
switch (input.type) { switch (input.type) {
case "String": case "string":
return this.renderStringInput(input as StringInput); return this.renderStringInput(input as StringInput);
case "Number": case "number":
return this.renderNumberInput(input as NumberInput); return this.renderNumberInput(input as NumberInput);
case "Boolean": case "boolean":
return this.renderBooleanInput(input as BooleanInput); return this.renderBooleanInput(input as BooleanInput);
default: default:
return this.renderEnumInput(input as EnumInput); return this.renderEnumInput(input as ChoiceInput);
} }
} }
@ -358,7 +428,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return ( return (
<Stack tokens={containerStackTokens} className="widgetRendererContainer"> <Stack tokens={containerStackTokens} className="widgetRendererContainer">
{node.info && this.renderInfo(node.info)} {node.info && this.renderInfo(node.info as Info)}
{node.input && this.renderInput(node.input)} {node.input && this.renderInput(node.input)}
{node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)} {node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)}
</Stack> </Stack>
@ -367,8 +437,8 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
render(): JSX.Element { render(): JSX.Element {
const containerStackTokens: IStackTokens = { childrenGap: 20 }; const containerStackTokens: IStackTokens = { childrenGap: 20 };
return ( return (
this.state.currentValues && this.state.currentValues.size ?
<Stack tokens={containerStackTokens}> <Stack tokens={containerStackTokens}>
{this.renderNode(this.props.descriptor.root)} {this.renderNode(this.props.descriptor.root)}
<PrimaryButton <PrimaryButton
@ -377,6 +447,8 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)} onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)}
/> />
</Stack> </Stack>
:
<Spinner size={SpinnerSize.large} />
); );
} }
} }