diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx index 4d3d8ac73..453b486b1 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx @@ -12,6 +12,7 @@ import { Link, MessageBar, MessageBarType, PrimaryButton, Spinner, SpinnerSize } import * as InputUtils from "./InputUtils"; import "./SmartUiComponent.less"; +import { Widget } from "@phosphor/widgets"; /** * Generic UX renderer @@ -44,14 +45,14 @@ export interface NumberInput extends BaseInput { min: (() => Promise) | number; max: (() => Promise) | number; step: (() => Promise) | number; - defaultValue: (() => Promise) | number; + defaultValue?: (() => Promise) | number; inputType: "spin" | "slider"; } export interface BooleanInput extends BaseInput { trueLabel: (() => Promise) | string; falseLabel: (() => Promise) | string; - defaultValue: (() => Promise) | boolean; + defaultValue?: (() => Promise) | boolean; } export interface StringInput extends BaseInput { @@ -60,7 +61,7 @@ export interface StringInput extends BaseInput { export interface ChoiceInput extends BaseInput { choices: (() => Promise) | ChoiceItem[]; - defaultKey: (() => Promise) | string; + defaultKey?: (() => Promise) | string; } export interface Info { @@ -82,6 +83,7 @@ export interface Node { export interface Descriptor { root: Node; + initialize?: () => Promise>; onSubmit: (currentValues: Map) => Promise; } @@ -142,7 +144,12 @@ export class SmartUiComponent extends React.Component => { - const defaults = new Map(); + let defaults = new Map() + + if (this.props.descriptor.initialize) { + defaults = await this.props.descriptor.initialize() + } + await this.setDefaults(this.props.descriptor.root, defaults); this.setState({ currentValues: defaults }); }; @@ -154,7 +161,9 @@ export class SmartUiComponent extends React.Component await this.setDefaults(child, defaults))); @@ -225,7 +234,8 @@ export class SmartUiComponent extends React.Component { switch (input.type) { case "string": - return (input as StringInput).defaultValue as string; + const stringInput = input as StringInput + return stringInput.defaultValue ? (stringInput.defaultValue as string) : ""; case "number": return (input as NumberInput).defaultValue as number; case "boolean": @@ -261,6 +271,7 @@ export class SmartUiComponent extends React.Component
@@ -268,7 +279,7 @@ export class SmartUiComponent extends React.Component this.onInputChange(input, newValue)} styles={{ @@ -340,12 +351,13 @@ export class SmartUiComponent extends React.Component 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)} @@ -368,7 +380,7 @@ export class SmartUiComponent extends React.Component this.onInputChange(input, newValue)} styles={{ titleLabel: { @@ -486,18 +498,21 @@ export class SmartUiComponent extends React.Component + {this.renderNode(this.props.descriptor.root)} await this.props.descriptor.onSubmit(this.state.currentValues)} + onClick={async () => { + await this.props.descriptor.onSubmit(this.state.currentValues) + this.setDefaultValues() + }} /> this.setDefaultValues()} + onClick={async () => await this.setDefaultValues()} /> diff --git a/src/SelfServe/ClassDescriptors.tsx b/src/SelfServe/ClassDescriptors.tsx index 9cd45315b..a657b950a 100644 --- a/src/SelfServe/ClassDescriptors.tsx +++ b/src/SelfServe/ClassDescriptors.tsx @@ -18,3 +18,9 @@ export const OnSubmit = (onSubmit: (currentValues: Map) => Pr addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit); }; }; + +export const Initialize = (initialize: () => Promise>): ClassDecorator => { + return (target: Function) => { + addPropertyToMap(target, "root", target.name, "initialize", initialize); + }; +}; diff --git a/src/SelfServe/PropertyDescriptors.tsx b/src/SelfServe/PropertyDescriptors.tsx index 2978ae59c..42e8061bd 100644 --- a/src/SelfServe/PropertyDescriptors.tsx +++ b/src/SelfServe/PropertyDescriptors.tsx @@ -1,7 +1,12 @@ -import { ChoiceItem, Info, InputType } from "../Explorer/Controls/SmartUi/SmartUiComponent"; +import { ChoiceItem, Descriptor, Info, InputType } from "../Explorer/Controls/SmartUi/SmartUiComponent"; import { addPropertyToMap } from "./SelfServeUtils"; -const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorator => { +interface Decorator { + name: string, + value: any +} + +const addToMap = (...decorators: Decorator[]): PropertyDecorator => { return (target, property) => { const className = (target as Function).name; var propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase(); @@ -12,76 +17,69 @@ const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorat if (!className) { throw new Error("property descriptor applied to non static field!"); } - addPropertyToMap(target, property.toString(), className, descriptorName, descriptorValue); + decorators.map((decorator: Decorator) => addPropertyToMap(target, property.toString(), className, decorator.name, decorator.value)); }; }; export const OnChange = ( onChange: (currentState: Map, newValue: InputType) => Map ): PropertyDecorator => { - return addToMap("onChange", onChange); + return addToMap({name: "onChange", value: onChange}); }; export const CustomElement = (customElement: ((currentValues: Map) => Promise) | JSX.Element): PropertyDecorator => { - return addToMap("customElement", customElement); + return addToMap({name: "customElement", value: customElement}); }; export const PropertyInfo = (info: (() => Promise) | Info): PropertyDecorator => { - return addToMap("info", info); + return addToMap({name: "info", value: info}); }; export const Placeholder = (placeholder: (() => Promise) | string): PropertyDecorator => { - return addToMap("placeholder", placeholder); + return addToMap({name: "placeholder", value: placeholder}); }; export const ParentOf = (children: string[]): PropertyDecorator => { - return addToMap("parentOf", children); + return addToMap({name: "parentOf", value: children}); }; export const Label = (label: (() => Promise) | string): PropertyDecorator => { - return addToMap("label", label); + return addToMap({name: "label", value: label}); }; -export const Min = (min: (() => Promise) | number): PropertyDecorator => { - return addToMap("min", min); -}; - -export const Max = (max: (() => Promise) | number): PropertyDecorator => { - return addToMap("max", max); -}; - -export const Step = (step: (() => Promise) | number): PropertyDecorator => { - return addToMap("step", step); +export const NumberInput = (min: (() => Promise) | number, +max: (() => Promise) | number, +step: (() => Promise) | number, +numberInputType: string, +defaultNumberValue?: (() => Promise) | number, +): PropertyDecorator => { + return addToMap( + {name: "min", value: min}, + {name: "max", value: max}, + {name: "step", value: step}, + {name: "defaultValue", value: defaultNumberValue}, + {name: "inputType", value: numberInputType} + ); }; export const DefaultStringValue = (defaultStringValue: (() => Promise) | string): PropertyDecorator => { - return addToMap("defaultValue", defaultStringValue); + return addToMap({name: "defaultValue", value: defaultStringValue}); }; -export const DefaultNumberValue = (defaultNumberValue: (() => Promise) | number): PropertyDecorator => { - return addToMap("defaultValue", defaultNumberValue); +export const BooleanInput = (trueLabel: (() => Promise) | string, +falseLabel: (() => Promise) | string, +defaultBooleanValue?: (() => Promise) | boolean): PropertyDecorator => { + return addToMap( + {name: "defaultValue", value: defaultBooleanValue}, + {name: "trueLabel", value: trueLabel}, + {name: "falseLabel", value: falseLabel} + ); }; -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: (() => Promise) | string): PropertyDecorator => { - return addToMap("falseLabel", falseLabel); -}; - -export const Choices = (choices: (() => Promise) | ChoiceItem[]): PropertyDecorator => { - return addToMap("choices", choices); -}; - -export const DefaultKey = (defaultKey: (() => Promise) | string): PropertyDecorator => { - return addToMap("defaultKey", defaultKey); -}; - -export const NumberInputType = (numberInputType: string): PropertyDecorator => { - return addToMap("inputType", numberInputType); +export const ChoiceInput = (choices: (() => Promise) | ChoiceItem[], +defaultKey?: (() => Promise) | string): PropertyDecorator => { + return addToMap( + {name: "choices", value: choices}, + {name: "defaultKey", value: defaultKey} + ); }; diff --git a/src/SelfServe/SelfServeUtils.tsx b/src/SelfServe/SelfServeUtils.tsx index 8cf105143..a9ed5ff02 100644 --- a/src/SelfServe/SelfServeUtils.tsx +++ b/src/SelfServe/SelfServeUtils.tsx @@ -40,6 +40,7 @@ export interface CommonInputTypes { inputType?: string; onChange?: (currentState: Map, newValue: InputType) => Map; onSubmit?: (currentValues: Map) => Promise; + initialize?: () => Promise>; customElement?: ((currentValues: Map) => Promise) | JSX.Element; } @@ -106,6 +107,7 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void = let smartUiDescriptor = { onSubmit: root.onSubmit, + initialize: root.initialize, root: { id: "root", info: root.info, @@ -175,20 +177,20 @@ const getInput = (value: CommonInputTypes): AnyInput => { switch (value.type) { case "number": - if (!value.step || !value.defaultValue || !value.inputType || !value.min || !value.max) { - throw new Error("step, min, miax, defaultValue and inputType are needed for number type"); + if (!value.step || !value.inputType || !value.min || !value.max) { + throw new Error("step, min, miax and inputType are needed for number type"); } return value as NumberInput; case "string": return value as StringInput; case "boolean": - if (!value.trueLabel || !value.falseLabel || value.defaultValue === undefined) { - throw new Error("truelabel, falselabel and defaultValue are needed for boolean type"); + if (!value.trueLabel || !value.falseLabel) { + throw new Error("truelabel and falselabel are needed for boolean type"); } return value as BooleanInput; default: - if (!value.choices || !value.defaultKey) { - throw new Error("choices and defaultKey are needed for enum type"); + if (!value.choices) { + throw new Error("choices are needed for enum type"); } return value as ChoiceInput; } diff --git a/src/SelfServe/SqlX/SqlX.tsx b/src/SelfServe/SqlX/SqlX.tsx index 3a722d008..9bbdc57a7 100644 --- a/src/SelfServe/SqlX/SqlX.tsx +++ b/src/SelfServe/SqlX/SqlX.tsx @@ -1,24 +1,19 @@ import { Label, - Min, - Max, - Step, - DefaultKey, - NumberInputType, - Choices, ParentOf, PropertyInfo, OnChange, - TrueLabel, - FalseLabel, Placeholder, - DefaultNumberValue, - DefaultBooleanValue, - CustomElement + CustomElement, + DefaultStringValue, + ChoiceInput, + BooleanInput, + NumberInput } from "../PropertyDescriptors"; -import { SmartUi, ClassInfo, OnSubmit } from "../ClassDescriptors"; +import { SmartUi, ClassInfo, OnSubmit, Initialize } from "../ClassDescriptors"; import { getPromise, + initializeSqlX, instanceSizeInfo, instanceSizeOptions, onInstanceCountChange, @@ -32,6 +27,7 @@ import { ChoiceItem } from "../../Explorer/Controls/SmartUi/SmartUiComponent"; @SmartUi() @ClassInfo(getPromise(sqlXInfo)) +@Initialize(initializeSqlX) @OnSubmit(onSubmit) export class SqlX extends SelfServeBase { @@ -39,10 +35,10 @@ export class SqlX extends SelfServeBase { @CustomElement(renderText("This is the description part of SqlX")) static description: string; - @PropertyInfo(getPromise(instanceSizeInfo)) @Label(getPromise("Instance Size")) - @Choices(getPromise(instanceSizeOptions)) - @DefaultKey(getPromise(Sizes.OneCore4Gb)) + @PropertyInfo(getPromise(instanceSizeInfo)) + //@ChoiceInput(getPromise(instanceSizeOptions), getPromise(Sizes.OneCore4Gb)) + @ChoiceInput(getPromise(instanceSizeOptions)) static instanceSize: ChoiceItem; @Label(getPromise("About")) @@ -50,22 +46,18 @@ export class SqlX extends SelfServeBase { static about: string; @Label("Feature Allowed") - @DefaultBooleanValue(false) - @TrueLabel("allowed") - @FalseLabel("not allowed") + //@BooleanInput("allowed", "not allowed", false) + @BooleanInput("allowed", "not allowed") static isAllowed: boolean; @Label("Instance Name") @Placeholder("instance name") static instanceName: string; - @OnChange(onInstanceCountChange) @Label(getPromise("Instance Count")) - @Min(getPromise(0)) - @Max(getPromise(5)) - @Step(getPromise(1)) - @DefaultNumberValue(getPromise(1)) - @NumberInputType("slider") + @OnChange(onInstanceCountChange) @ParentOf(["instanceSize", "about", "instanceName", "isAllowed", ]) + //@NumberInput(getPromise(1), getPromise(5), getPromise(1), "slider", getPromise(0)) + @NumberInput(getPromise(1), getPromise(5), getPromise(1), "slider") static instanceCount: number; } diff --git a/src/SelfServe/SqlX/SqlXApis.tsx b/src/SelfServe/SqlX/SqlXApis.tsx index 7743b5d2f..40a1d4eaf 100644 --- a/src/SelfServe/SqlX/SqlXApis.tsx +++ b/src/SelfServe/SqlX/SqlXApis.tsx @@ -2,6 +2,7 @@ import { Text } from "office-ui-fabric-react"; import React from "react"; import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent"; import { TextComponent } from "./TextComponent"; +import {SessionStorageUtility} from "../../Shared/StorageUtility" export enum Sizes { OneCore4Gb = "OneCore4Gb", @@ -45,6 +46,20 @@ export const onSubmit = async (currentValues: Map): Promise> => { + let defaults = new Map() + defaults.set("instanceCount", parseInt(SessionStorageUtility.getEntry("instanceCount"))) + defaults.set("instanceSize", SessionStorageUtility.getEntry("instanceSize")) + defaults.set("instanceName", SessionStorageUtility.getEntry("instanceName")) + defaults.set("isAllowed", SessionStorageUtility.getEntry("isAllowed") === "true") + return defaults }; export const delay = (ms: number): Promise => { @@ -63,7 +78,6 @@ export const getPromise = ) => Promise => { const f = async (currentValues: Map): Promise => { - //return SqlX is a new feature of Cosmos DB.; return }; return f