removed type requirement

This commit is contained in:
Srinath Narayanan 2020-12-02 01:45:59 -08:00
parent 69b17f1a00
commit 4381ea447c
8 changed files with 217 additions and 126 deletions

View File

@ -903,7 +903,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
tabs.push({ tabs.push({
tab: SettingsV2TabTypes.SelfServe, tab: SettingsV2TabTypes.SelfServe,
content: <SelfServeComponent propertyNames={["prop1", "prop2"]} /> content: <SelfServeComponent />
}); });
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) { if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {

View File

@ -1,5 +1,5 @@
import { Descriptor, Info } from "../../../SmartUi/SmartUiComponent"; import { Descriptor, Info, InputType } from "../../../SmartUi/SmartUiComponent";
import { addPropertyToMap, DescriptorType, toSmartUiDescriptor } from "./SelfServeUtils"; import { addPropertyToMap, toSmartUiDescriptor } from "./SelfServeUtils";
interface SelfServeBaseCLass { interface SelfServeBaseCLass {
toSmartUiDescriptor: () => Descriptor; toSmartUiDescriptor: () => Descriptor;
@ -19,6 +19,12 @@ export const SmartUi = (): ClassDecorator => {
export const ClassInfo = (info: Info): ClassDecorator => { export const ClassInfo = (info: Info): ClassDecorator => {
return (target: Function) => { return (target: Function) => {
addPropertyToMap(target, "root", target.name, "info", info, DescriptorType.ClassDescriptor); addPropertyToMap(target, "root", target.name, "info", info);
};
};
export const OnSubmit = (onSubmit: (currentValues: Map<string, InputType>) => Promise<void>): ClassDecorator => {
return (target: Function) => {
addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit);
}; };
}; };

View File

@ -1,23 +1,25 @@
import { EnumItem, Info, InputTypeValue } from "../../../SmartUi/SmartUiComponent"; import { EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
import { addPropertyToMap, DescriptorType } 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);
addPropertyToMap(target, property.toString(), className, "type", propertyType.name);
if (!className) { if (!className) {
throw new Error("property descriptor applied to non static field!"); throw new Error("property descriptor applied to non static field!");
} }
addPropertyToMap( addPropertyToMap(target, property.toString(), className, descriptorName, descriptorValue);
target,
property.toString(),
className,
descriptorName,
descriptorValue,
DescriptorType.PropertyDescriptor
);
}; };
}; };
export const OnChange = (
onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
): PropertyDecorator => {
return addToMap("onChange", onChange);
};
export const PropertyInfo = (info: Info): PropertyDecorator => { export const PropertyInfo = (info: Info): PropertyDecorator => {
return addToMap("info", info); return addToMap("info", info);
}; };
@ -30,10 +32,6 @@ export const ParentOf = (children: string[]): PropertyDecorator => {
return addToMap("parentOf", children); return addToMap("parentOf", children);
}; };
export const Type = (type: InputTypeValue): PropertyDecorator => {
return addToMap("type", type);
};
export const Label = (label: string): PropertyDecorator => { export const Label = (label: string): PropertyDecorator => {
return addToMap("label", label); return addToMap("label", label);
}; };

View File

@ -2,26 +2,13 @@ import React from "react";
import { Descriptor, InputType, SmartUiComponent } from "../../../SmartUi/SmartUiComponent"; import { Descriptor, InputType, SmartUiComponent } from "../../../SmartUi/SmartUiComponent";
import { SqlX } from "./SqlX"; import { SqlX } from "./SqlX";
interface SelfServeComponentProps { export class SelfServeCmponent extends React.Component {
propertyNames: string[]; private onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => {
} console.log(currentValues.get("instanceCount"), currentValues.get("instanceSize"));
};
export class SelfServeCmponent extends React.Component<SelfServeComponentProps> {
private properties: any = {};
constructor(props: SelfServeComponentProps) {
super(props);
let stringer = "{";
for (var i = 0; i < props.propertyNames.length; i++) {
stringer += `"${props.propertyNames[i]}":null,`;
}
stringer = stringer.substring(0, stringer.length - 1);
console.log(stringer);
stringer += "}";
this.properties = JSON.parse(stringer);
}
private selfServeData: Descriptor = { private selfServeData: Descriptor = {
onSubmit: this.onSubmit,
root: { root: {
id: "root", id: "root",
info: { info: {
@ -37,12 +24,19 @@ export class SelfServeCmponent extends React.Component<SelfServeComponentProps>
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,
defaultValue: 1, defaultValue: 1,
inputType: "slider" inputType: "slider",
onChange: (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
currentState.set("instanceCount", newValue);
if ((newValue as number) === 1) {
currentState.set("instanceSize", "1Core4Gb");
}
return currentState;
}
} }
}, },
{ {
@ -50,33 +44,25 @@ export class SelfServeCmponent extends React.Component<SelfServeComponentProps>
input: { input: {
label: "Instance Size", label: "Instance Size",
dataFieldName: "instanceSize", dataFieldName: "instanceSize",
type: "enum", 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" },
{ label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" } { label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" }
], ],
defaultKey: "1Core4Gb" defaultKey: "1Core4Gb",
onChange: (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
currentState.set("instanceSize", newValue);
return currentState;
}
} }
} }
] ]
} }
}; };
private exampleCallbacks = (newValues: Map<string, InputType>): void => {
for (var i = 0; i < this.props.propertyNames.length; i++) {
const prop = this.props.propertyNames[i];
const newVal = newValues.get(prop);
if (newVal) {
this.properties[`${prop}`] = newVal;
}
}
console.log(this.properties);
};
public render(): JSX.Element { public render(): JSX.Element {
//return <SmartUiComponent descriptor={this.selfServeData} onChange={this.exampleCallbacks} /> //return <SmartUiComponent descriptor={this.selfServeData} />
return <SmartUiComponent descriptor={SqlX.toSmartUiDescriptor()} onChange={this.exampleCallbacks} />; return <SmartUiComponent descriptor={SqlX.toSmartUiDescriptor()} />;
} }
} }

View File

@ -9,7 +9,8 @@ import {
NumberInput, NumberInput,
StringInput, StringInput,
BooleanInput, BooleanInput,
EnumInput EnumInput,
InputType
} from "../../../SmartUi/SmartUiComponent"; } from "../../../SmartUi/SmartUiComponent";
export interface CommonInputTypes { export interface CommonInputTypes {
@ -29,11 +30,8 @@ export interface CommonInputTypes {
choices?: EnumItem[]; choices?: EnumItem[];
defaultKey?: string; defaultKey?: string;
inputType?: string; inputType?: string;
} onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
export enum DescriptorType {
ClassDescriptor,
PropertyDescriptor
} }
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>( const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
@ -53,13 +51,11 @@ const getValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T
export const addPropertyToMap = ( export const addPropertyToMap = (
target: Object, target: Object,
property: string, propertyKey: string,
metadataKey: string, metadataKey: string,
descriptorName: string, descriptorName: string,
descriptorValue: any, descriptorValue: any
descriptorType: DescriptorType
): void => { ): void => {
const propertyKey = property.toString();
const descriptorKey = descriptorName.toString() as keyof CommonInputTypes; const descriptorKey = descriptorName.toString() as keyof CommonInputTypes;
let context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>; let context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>;
@ -67,12 +63,16 @@ export const addPropertyToMap = (
context = new Map<String, CommonInputTypes>(); context = new Map<String, CommonInputTypes>();
} }
if (!(context instanceof Map)) {
throw new Error("@SmartUi should be the first decorator for the class.");
}
let propertyObject = context.get(propertyKey); let propertyObject = context.get(propertyKey);
if (!propertyObject) { if (!propertyObject) {
propertyObject = { id: propertyKey }; propertyObject = { id: propertyKey };
} }
if (getValue(descriptorKey, propertyObject)) { if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type") {
throw new Error("duplicate descriptor"); throw new Error("duplicate descriptor");
} }
@ -125,7 +125,14 @@ 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)) {
throw new Error(
"@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
);
}
let smartUiDescriptor = { let smartUiDescriptor = {
onSubmit: root.onSubmit,
root: { root: {
id: "root", id: "root",
info: root.info, info: root.info,
@ -187,25 +194,27 @@ const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInp
}; };
const getInput = (value: CommonInputTypes): AnyInput => { 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) { 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) { 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");
} }
return value as BooleanInput; return value as BooleanInput;
case "enum": default:
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 EnumInput;
default:
throw new Error("Unknown type");
} }
}; };

View File

@ -6,27 +6,37 @@ import {
Step, Step,
DefaultKey, DefaultKey,
DefaultValue, DefaultValue,
Type,
NumberInputType, NumberInputType,
Choices, Choices,
ParentOf, ParentOf,
PropertyInfo PropertyInfo,
OnChange,
TrueLabel,
FalseLabel,
Placeholder
} from "./PropertyDescriptors"; } from "./PropertyDescriptors";
import { Descriptor, EnumItem, Info } from "../../../SmartUi/SmartUiComponent"; import { Descriptor, EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
import { SmartUi, ClassInfo, SelfServeClass } from "./ClassDescriptors"; import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors";
enum Sizes {
OneCore4Gb = "OneCore4Gb",
TwoCore8Gb = "TwoCore8Gb",
FourCore16Gb = "FourCore16Gb"
}
@SmartUi() @SmartUi()
@SelfServeClass()
@ClassInfo(SqlX.sqlXInfo) @ClassInfo(SqlX.sqlXInfo)
@OnSubmit(SqlX.onSubmit)
@SelfServeClass()
export class SqlX { export class SqlX {
@PropertyInfo(SqlX.instanceSizeInfo) @PropertyInfo(SqlX.instanceSizeInfo)
@Label("Instance Size") @Label("Instance Size")
@DataFieldName("instanceSize") @DataFieldName("instanceSize")
@Choices(SqlX.instanceSizeOptions) @Choices(SqlX.instanceSizeOptions)
@DefaultKey("1Core4Gb") @DefaultKey(Sizes.OneCore4Gb)
@Type("enum") static instanceSize: EnumItem;
static instanceSize: any;
@OnChange(SqlX.onInstanceCountChange)
@Label("Instance Count") @Label("Instance Count")
@DataFieldName("instanceCount") @DataFieldName("instanceCount")
@Min(1) @Min(1)
@ -34,14 +44,25 @@ export class SqlX {
@Step(1) @Step(1)
@DefaultValue(1) @DefaultValue(1)
@NumberInputType("slider") @NumberInputType("slider")
@Type("number") @ParentOf(["instanceSize", "instanceName", "isAllowed"])
@ParentOf(["instanceSize"]) static instanceCount: number;
static instanceCount: any;
@Label("Feature Allowed")
@DataFieldName("isAllowed")
@DefaultValue(false)
@TrueLabel("allowed")
@FalseLabel("not allowed")
static isAllowed: boolean;
@Label("Instance Name")
@DataFieldName("instanceName")
@Placeholder("instance name")
static instanceName: string;
static instanceSizeOptions: EnumItem[] = [ static instanceSizeOptions: EnumItem[] = [
{ label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, { label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
{ label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, { label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
{ label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" } { label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
]; ];
static sqlXInfo: Info = { static sqlXInfo: Info = {
@ -52,6 +73,30 @@ export class SqlX {
message: "instance size will be updated in the future" message: "instance size will be updated in the future"
}; };
static onInstanceCountChange = (
currentState: Map<string, InputType>,
newValue: InputType
): Map<string, InputType> => {
currentState.set("instanceCount", newValue);
if ((newValue as number) === 1) {
currentState.set("isAllowed", false);
}
return currentState;
};
static onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => {
console.log(
"instanceCount:" +
currentValues.get("instanceCount") +
", instanceSize:" +
currentValues.get("instanceSize") +
", instanceName:" +
currentValues.get("instanceName") +
", isAllowed:" +
currentValues.get("isAllowed")
);
};
public static toSmartUiDescriptor = (): Descriptor => { public static toSmartUiDescriptor = (): Descriptor => {
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor; return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
}; };

View File

@ -4,6 +4,7 @@ import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
describe("SmartUiComponent", () => { describe("SmartUiComponent", () => {
const exampleData: Descriptor = { const exampleData: Descriptor = {
onSubmit: async (currentValues: Map<string, InputType>) => {},
root: { root: {
id: "root", id: "root",
info: { info: {
@ -24,7 +25,8 @@ describe("SmartUiComponent", () => {
max: 500, max: 500,
step: 10, step: 10,
defaultValue: 400, defaultValue: 400,
inputType: "spin" inputType: "spin",
onChange: undefined
} }
}, },
{ {
@ -37,7 +39,8 @@ describe("SmartUiComponent", () => {
max: 500, max: 500,
step: 10, step: 10,
defaultValue: 400, defaultValue: 400,
inputType: "slider" inputType: "slider",
onChange: undefined
} }
}, },
{ {
@ -45,7 +48,8 @@ describe("SmartUiComponent", () => {
input: { input: {
label: "Container id", label: "Container id",
dataFieldName: "containerId", dataFieldName: "containerId",
type: "string" type: "string",
onChange: undefined
} }
}, },
{ {
@ -56,7 +60,8 @@ describe("SmartUiComponent", () => {
falseLabel: "Disabled", falseLabel: "Disabled",
defaultValue: true, defaultValue: true,
dataFieldName: "analyticalStore", dataFieldName: "analyticalStore",
type: "boolean" type: "boolean",
onChange: undefined
} }
}, },
{ {
@ -70,6 +75,7 @@ describe("SmartUiComponent", () => {
{ label: "Database 2", key: "db2", value: "database2" }, { label: "Database 2", key: "db2", value: "database2" },
{ label: "Database 3", key: "db3", value: "database3" } { label: "Database 3", key: "db3", value: "database3" }
], ],
onChange: undefined,
defaultKey: "db2" defaultKey: "db2"
} }
} }
@ -77,12 +83,8 @@ describe("SmartUiComponent", () => {
} }
}; };
const exampleCallbacks = (newValues: Map<string, InputType>): void => {
console.log("New values:", newValues);
};
it("should render", () => { it("should render", () => {
const wrapper = shallow(<SmartUiComponent descriptor={exampleData} onChange={exampleCallbacks} />); const wrapper = shallow(<SmartUiComponent descriptor={exampleData} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

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 } from "office-ui-fabric-react"; import { Link, MessageBar, MessageBarType, PrimaryButton } from "office-ui-fabric-react";
import * as InputUtils from "./InputUtils"; import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less"; import "./SmartUiComponent.less";
@ -21,17 +21,18 @@ import "./SmartUiComponent.less";
* - a descriptor of the UX. * - a descriptor of the UX.
*/ */
export type InputTypeValue = "number" | "string" | "boolean" | "enum"; 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 EnumItem = { label: string; key: string; value: any };
export type InputType = number | string | boolean | EnumItem; export type InputType = Number | String | Boolean | EnumItem;
interface BaseInput { interface BaseInput {
label: string; label: string;
dataFieldName: string; dataFieldName: string;
type: InputTypeValue; type: InputTypeValue;
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
placeholder?: string; placeholder?: string;
} }
@ -80,13 +81,13 @@ export interface Node {
export interface Descriptor { export interface Descriptor {
root: Node; root: Node;
onSubmit: (currentValues: Map<string, InputType>) => Promise<void>;
} }
/************************** Component implementation starts here ************************************* */ /************************** Component implementation starts here ************************************* */
export interface SmartUiComponentProps { export interface SmartUiComponentProps {
descriptor: Descriptor; descriptor: Descriptor;
onChange: (newValues: Map<string, InputType>) => void;
} }
interface SmartUiComponentState { interface SmartUiComponentState {
@ -104,11 +105,37 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
constructor(props: SmartUiComponentProps) { constructor(props: SmartUiComponentProps) {
super(props); super(props);
this.state = { this.state = {
currentValues: new Map(), currentValues: this.setDefaultValues(),
errors: new Map() errors: new Map()
}; };
} }
private setDefaultValues = (): Map<string, InputType> => {
const defaults = new Map();
this.setDefaults(this.props.descriptor.root, defaults);
return 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 getDefault = (input: AnyInput): InputType => {
switch (input.type) {
case "String":
return (input as StringInput).defaultValue;
case "Number":
return (input as NumberInput).defaultValue;
case "Boolean":
return (input as BooleanInput).defaultValue;
default:
return (input as EnumInput).defaultKey;
}
};
private renderInfo(info: Info): JSX.Element { private renderInfo(info: Info): JSX.Element {
return ( return (
<MessageBar> <MessageBar>
@ -122,10 +149,16 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
); );
} }
private onInputChange = (newValue: string | number | boolean, dataFieldName: string) => { private onInputChange = (input: AnyInput, newValue: InputType) => {
const { currentValues } = this.state; if (input.onChange) {
currentValues.set(dataFieldName, newValue); const newValues = input.onChange(this.state.currentValues, newValue);
this.setState({ currentValues }, () => this.props.onChange(this.state.currentValues)); this.setState({ currentValues: newValues });
} else {
const dataFieldName = input.dataFieldName;
const { currentValues } = this.state;
currentValues.set(dataFieldName, newValue);
this.setState({ currentValues });
}
}; };
private renderStringInput(input: StringInput): JSX.Element { private renderStringInput(input: StringInput): JSX.Element {
@ -138,7 +171,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
type="text" type="text"
value={input.defaultValue} value={input.defaultValue}
placeholder={input.placeholder} placeholder={input.placeholder}
onChange={(_, newValue) => this.onInputChange(newValue, input.dataFieldName)} onChange={(_, newValue) => this.onInputChange(input, newValue)}
styles={{ styles={{
subComponentStyles: { subComponentStyles: {
label: { label: {
@ -161,10 +194,11 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
this.setState({ errors }); this.setState({ errors });
} }
private onValidate = (value: string, min: number, max: number, dataFieldName: string): string => { private onValidate = (input: AnyInput, value: string, min: number, max: number): string => {
const newValue = InputUtils.onValidateValueChange(value, min, max); const newValue = InputUtils.onValidateValueChange(value, min, max);
const dataFieldName = input.dataFieldName;
if (newValue) { if (newValue) {
this.onInputChange(newValue, dataFieldName); this.onInputChange(input, newValue);
this.clearError(dataFieldName); this.clearError(dataFieldName);
return newValue.toString(); return newValue.toString();
} else { } else {
@ -175,20 +209,22 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return undefined; return undefined;
}; };
private onIncrement = (value: string, step: number, max: number, dataFieldName: string): string => { private onIncrement = (input: AnyInput, value: string, step: number, max: number): string => {
const newValue = InputUtils.onIncrementValue(value, step, max); const newValue = InputUtils.onIncrementValue(value, step, max);
const dataFieldName = input.dataFieldName;
if (newValue) { if (newValue) {
this.onInputChange(newValue, dataFieldName); this.onInputChange(input, newValue);
this.clearError(dataFieldName); this.clearError(dataFieldName);
return newValue.toString(); return newValue.toString();
} }
return undefined; return undefined;
}; };
private onDecrement = (value: string, step: number, min: number, dataFieldName: string): string => { private onDecrement = (input: AnyInput, value: string, step: number, min: number): string => {
const newValue = InputUtils.onDecrementValue(value, step, min); const newValue = InputUtils.onDecrementValue(value, step, min);
const dataFieldName = input.dataFieldName;
if (newValue) { if (newValue) {
this.onInputChange(newValue, dataFieldName); this.onInputChange(input, newValue);
this.clearError(dataFieldName); this.clearError(dataFieldName);
return newValue.toString(); return newValue.toString();
} }
@ -205,9 +241,9 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
<SpinButton <SpinButton
{...props} {...props}
defaultValue={defaultValue.toString()} defaultValue={defaultValue.toString()}
onValidate={newValue => this.onValidate(newValue, min, max, dataFieldName)} onValidate={newValue => this.onValidate(input, newValue, min, max)}
onIncrement={newValue => this.onIncrement(newValue, step, max, dataFieldName)} onIncrement={newValue => this.onIncrement(input, newValue, step, max)}
onDecrement={newValue => this.onDecrement(newValue, step, min, dataFieldName)} onDecrement={newValue => this.onDecrement(input, newValue, step, min)}
labelPosition={Position.top} labelPosition={Position.top}
styles={{ styles={{
label: { label: {
@ -228,7 +264,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
// valueFormat={} // valueFormat={}
{...props} {...props}
defaultValue={defaultValue} defaultValue={defaultValue}
onChange={newValue => this.onInputChange(newValue, dataFieldName)} onChange={newValue => this.onInputChange(input, newValue)}
styles={{ styles={{
titleLabel: { titleLabel: {
...SmartUiComponent.labelStyle, ...SmartUiComponent.labelStyle,
@ -257,12 +293,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
{ {
label: input.falseLabel, label: input.falseLabel,
key: "false", key: "false",
onSelect: () => this.onInputChange(false, dataFieldName) onSelect: () => this.onInputChange(input, false)
}, },
{ {
label: input.trueLabel, label: input.trueLabel,
key: "true", key: "true",
onSelect: () => this.onInputChange(true, dataFieldName) onSelect: () => this.onInputChange(input, true)
} }
]} ]}
selectedKey={ selectedKey={
@ -278,7 +314,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
} }
private renderEnumInput(input: EnumInput): JSX.Element { private renderEnumInput(input: EnumInput): JSX.Element {
const { label, defaultKey, dataFieldName, choices, placeholder } = input; const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
return ( return (
<Dropdown <Dropdown
label={label} label={label}
@ -287,7 +323,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
? (this.state.currentValues.get(dataFieldName) as string) ? (this.state.currentValues.get(dataFieldName) as string)
: defaultKey : defaultKey
} }
onChange={(_, item: IDropdownOption) => this.onInputChange(item.key.toString(), dataFieldName)} onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
placeholder={placeholder} placeholder={placeholder}
options={choices.map(c => ({ options={choices.map(c => ({
key: c.key, key: c.key,
@ -306,16 +342,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);
case "enum":
return this.renderEnumInput(input as EnumInput);
default: default:
throw new Error(`Unknown input type: ${input.type}`); return this.renderEnumInput(input as EnumInput);
} }
} }
@ -332,6 +366,17 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
} }
render(): JSX.Element { render(): JSX.Element {
return <>{this.renderNode(this.props.descriptor.root)}</>; const containerStackTokens: IStackTokens = { childrenGap: 20 };
return (
<Stack tokens={containerStackTokens}>
{this.renderNode(this.props.descriptor.root)}
<PrimaryButton
styles={{ root: { width: 100 } }}
text="submit"
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)}
/>
</Stack>
);
} }
} }