Added overall defaults

This commit is contained in:
Srinath Narayanan 2020-12-10 16:59:16 -08:00
parent 95fc75cb23
commit b3b57462ef
6 changed files with 114 additions and 87 deletions

View File

@ -12,6 +12,7 @@ import { Link, MessageBar, MessageBarType, PrimaryButton, Spinner, SpinnerSize }
import * as InputUtils from "./InputUtils"; import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less"; import "./SmartUiComponent.less";
import { Widget } from "@phosphor/widgets";
/** /**
* Generic UX renderer * Generic UX renderer
@ -44,14 +45,14 @@ export interface NumberInput extends BaseInput {
min: (() => Promise<number>) | number; min: (() => Promise<number>) | number;
max: (() => Promise<number>) | number; max: (() => Promise<number>) | number;
step: (() => Promise<number>) | number; step: (() => Promise<number>) | number;
defaultValue: (() => Promise<number>) | number; defaultValue?: (() => Promise<number>) | number;
inputType: "spin" | "slider"; inputType: "spin" | "slider";
} }
export interface BooleanInput extends BaseInput { export interface BooleanInput extends BaseInput {
trueLabel: (() => Promise<string>) | string; trueLabel: (() => Promise<string>) | string;
falseLabel: (() => Promise<string>) | string; falseLabel: (() => Promise<string>) | string;
defaultValue: (() => Promise<boolean>) | boolean; defaultValue?: (() => Promise<boolean>) | boolean;
} }
export interface StringInput extends BaseInput { export interface StringInput extends BaseInput {
@ -60,7 +61,7 @@ export interface StringInput extends BaseInput {
export interface ChoiceInput extends BaseInput { export interface ChoiceInput extends BaseInput {
choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[]; choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
defaultKey: (() => Promise<string>) | string; defaultKey?: (() => Promise<string>) | string;
} }
export interface Info { export interface Info {
@ -82,6 +83,7 @@ export interface Node {
export interface Descriptor { export interface Descriptor {
root: Node; root: Node;
initialize?: () => Promise<Map<string, InputType>>;
onSubmit: (currentValues: Map<string, InputType>) => Promise<void>; onSubmit: (currentValues: Map<string, InputType>) => Promise<void>;
} }
@ -142,7 +144,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
} }
private setDefaultValues = async (): Promise<void> => { private setDefaultValues = async (): Promise<void> => {
const defaults = new Map<string, InputType>(); let defaults = new Map<string, InputType>()
if (this.props.descriptor.initialize) {
defaults = await this.props.descriptor.initialize()
}
await this.setDefaults(this.props.descriptor.root, defaults); await this.setDefaults(this.props.descriptor.root, defaults);
this.setState({ currentValues: defaults }); this.setState({ currentValues: defaults });
}; };
@ -154,7 +161,9 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
if (currentNode.input) { if (currentNode.input) {
currentNode.input = await this.getModifiedInput(currentNode.input); currentNode.input = await this.getModifiedInput(currentNode.input);
defaults.set(currentNode.input.dataFieldName, this.getDefaultValue(currentNode.input)); if (!defaults.get(currentNode.input.dataFieldName)) {
defaults.set(currentNode.input.dataFieldName, this.getDefaultValue(currentNode.input));
}
} }
await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child, defaults))); await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child, defaults)));
@ -225,7 +234,8 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
private getDefaultValue = (input: AnyInput): InputType => { private getDefaultValue = (input: AnyInput): InputType => {
switch (input.type) { switch (input.type) {
case "string": case "string":
return (input as StringInput).defaultValue as string; const stringInput = input as StringInput
return stringInput.defaultValue ? (stringInput.defaultValue as string) : "";
case "number": case "number":
return (input as NumberInput).defaultValue as number; return (input as NumberInput).defaultValue as number;
case "boolean": case "boolean":
@ -261,6 +271,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
}; };
private renderStringInput(input: StringInput): JSX.Element { private renderStringInput(input: StringInput): JSX.Element {
const value = this.state.currentValues.get(input.dataFieldName) as string
return ( return (
<div className="stringInputContainer"> <div className="stringInputContainer">
<div> <div>
@ -268,7 +279,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
id={`${input.dataFieldName}-input`} id={`${input.dataFieldName}-input`}
label={input.label as string} label={input.label as string}
type="text" type="text"
defaultValue={input.defaultValue as string} value={value}
placeholder={input.placeholder as string} placeholder={input.placeholder as string}
onChange={(_, newValue) => this.onInputChange(input, newValue)} onChange={(_, newValue) => this.onInputChange(input, newValue)}
styles={{ styles={{
@ -340,12 +351,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
step: step as number step: step as number
}; };
const value = this.state.currentValues.get(dataFieldName) as number
if (input.inputType === "spin") { if (input.inputType === "spin") {
return ( return (
<div> <div>
<SpinButton <SpinButton
{...props} {...props}
defaultValue={(defaultValue as number).toString()} value={value.toString()}
onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)} onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)} onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)} onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)}
@ -368,7 +380,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
// showValue={true} // showValue={true}
// valueFormat={} // valueFormat={}
{...props} {...props}
defaultValue={defaultValue as number} value={value}
onChange={newValue => this.onInputChange(input, newValue)} onChange={newValue => this.onInputChange(input, newValue)}
styles={{ styles={{
titleLabel: { titleLabel: {
@ -486,18 +498,21 @@ 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 this.state.currentValues && this.state.currentValues.size ? ( return this.state.currentValues && this.state.currentValues.size ? (
<Stack tokens={containerStackTokens}> <Stack tokens={containerStackTokens} styles={{root: {width: 400, padding: 10}}}>
{this.renderNode(this.props.descriptor.root)} {this.renderNode(this.props.descriptor.root)}
<Stack horizontal tokens={{childrenGap: 10}}> <Stack horizontal tokens={{childrenGap: 10}}>
<PrimaryButton <PrimaryButton
styles={{ root: { width: 100 } }} styles={{ root: { width: 100 } }}
text="submit" text="submit"
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)} onClick={async () => {
await this.props.descriptor.onSubmit(this.state.currentValues)
this.setDefaultValues()
}}
/> />
<PrimaryButton <PrimaryButton
styles={{ root: { width: 100 } }} styles={{ root: { width: 100 } }}
text="discard" text="discard"
onClick={async () => this.setDefaultValues()} onClick={async () => await this.setDefaultValues()}
/> />
</Stack> </Stack>
</Stack> </Stack>

View File

@ -18,3 +18,9 @@ export const OnSubmit = (onSubmit: (currentValues: Map<string, InputType>) => Pr
addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit); addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit);
}; };
}; };
export const Initialize = (initialize: () => Promise<Map<string, InputType>>): ClassDecorator => {
return (target: Function) => {
addPropertyToMap(target, "root", target.name, "initialize", initialize);
};
};

View File

@ -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"; 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) => { return (target, property) => {
const className = (target as Function).name; const className = (target as Function).name;
var propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase(); 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) { if (!className) {
throw new Error("property descriptor applied to non static field!"); 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 = ( export const OnChange = (
onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType> onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
): PropertyDecorator => { ): PropertyDecorator => {
return addToMap("onChange", onChange); return addToMap({name: "onChange", value: onChange});
}; };
export const CustomElement = (customElement: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element): PropertyDecorator => { export const CustomElement = (customElement: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element): PropertyDecorator => {
return addToMap("customElement", customElement); return addToMap({name: "customElement", value: customElement});
}; };
export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => { export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
return addToMap("info", info); return addToMap({name: "info", value: info});
}; };
export const Placeholder = (placeholder: (() => Promise<string>) | string): PropertyDecorator => { export const Placeholder = (placeholder: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("placeholder", placeholder); return addToMap({name: "placeholder", value: placeholder});
}; };
export const ParentOf = (children: string[]): PropertyDecorator => { export const ParentOf = (children: string[]): PropertyDecorator => {
return addToMap("parentOf", children); return addToMap({name: "parentOf", value: children});
}; };
export const Label = (label: (() => Promise<string>) | string): PropertyDecorator => { export const Label = (label: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("label", label); return addToMap({name: "label", value: label});
}; };
export const Min = (min: (() => Promise<number>) | number): PropertyDecorator => { export const NumberInput = (min: (() => Promise<number>) | number,
return addToMap("min", min); max: (() => Promise<number>) | number,
}; step: (() => Promise<number>) | number,
numberInputType: string,
export const Max = (max: (() => Promise<number>) | number): PropertyDecorator => { defaultNumberValue?: (() => Promise<number>) | number,
return addToMap("max", max); ): PropertyDecorator => {
}; return addToMap(
{name: "min", value: min},
export const Step = (step: (() => Promise<number>) | number): PropertyDecorator => { {name: "max", value: max},
return addToMap("step", step); {name: "step", value: step},
{name: "defaultValue", value: defaultNumberValue},
{name: "inputType", value: numberInputType}
);
}; };
export const DefaultStringValue = (defaultStringValue: (() => Promise<string>) | string): PropertyDecorator => { export const DefaultStringValue = (defaultStringValue: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("defaultValue", defaultStringValue); return addToMap({name: "defaultValue", value: defaultStringValue});
}; };
export const DefaultNumberValue = (defaultNumberValue: (() => Promise<number>) | number): PropertyDecorator => { export const BooleanInput = (trueLabel: (() => Promise<string>) | string,
return addToMap("defaultValue", defaultNumberValue); falseLabel: (() => Promise<string>) | string,
defaultBooleanValue?: (() => Promise<boolean>) | boolean): PropertyDecorator => {
return addToMap(
{name: "defaultValue", value: defaultBooleanValue},
{name: "trueLabel", value: trueLabel},
{name: "falseLabel", value: falseLabel}
);
}; };
export const DefaultBooleanValue = (defaultBooleanValue: (() => Promise<boolean>) | boolean): PropertyDecorator => { export const ChoiceInput = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[],
return addToMap("defaultValue", defaultBooleanValue); defaultKey?: (() => Promise<string>) | string): PropertyDecorator => {
}; return addToMap(
{name: "choices", value: choices},
export const TrueLabel = (trueLabel: (() => Promise<string>) | string): PropertyDecorator => { {name: "defaultKey", value: defaultKey}
return addToMap("trueLabel", trueLabel); );
};
export const FalseLabel = (falseLabel: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("falseLabel", falseLabel);
};
export const Choices = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[]): PropertyDecorator => {
return addToMap("choices", choices);
};
export const DefaultKey = (defaultKey: (() => Promise<string>) | string): PropertyDecorator => {
return addToMap("defaultKey", defaultKey);
};
export const NumberInputType = (numberInputType: string): PropertyDecorator => {
return addToMap("inputType", numberInputType);
}; };

View File

@ -40,6 +40,7 @@ export interface CommonInputTypes {
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>;
initialize?: () => Promise<Map<string, InputType>>;
customElement?: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element; customElement?: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element;
} }
@ -106,6 +107,7 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void =
let smartUiDescriptor = { let smartUiDescriptor = {
onSubmit: root.onSubmit, onSubmit: root.onSubmit,
initialize: root.initialize,
root: { root: {
id: "root", id: "root",
info: root.info, info: root.info,
@ -175,20 +177,20 @@ const getInput = (value: CommonInputTypes): AnyInput => {
switch (value.type) { switch (value.type) {
case "number": case "number":
if (!value.step || !value.defaultValue || !value.inputType || !value.min || !value.max) { if (!value.step || !value.inputType || !value.min || !value.max) {
throw new Error("step, min, miax, defaultValue and inputType are needed for number type"); throw new Error("step, min, miax 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) {
throw new Error("truelabel, falselabel and defaultValue are needed for boolean type"); throw new Error("truelabel and falselabel are needed for boolean type");
} }
return value as BooleanInput; return value as BooleanInput;
default: default:
if (!value.choices || !value.defaultKey) { if (!value.choices) {
throw new Error("choices and defaultKey are needed for enum type"); throw new Error("choices are needed for enum type");
} }
return value as ChoiceInput; return value as ChoiceInput;
} }

View File

@ -1,24 +1,19 @@
import { import {
Label, Label,
Min,
Max,
Step,
DefaultKey,
NumberInputType,
Choices,
ParentOf, ParentOf,
PropertyInfo, PropertyInfo,
OnChange, OnChange,
TrueLabel,
FalseLabel,
Placeholder, Placeholder,
DefaultNumberValue, CustomElement,
DefaultBooleanValue, DefaultStringValue,
CustomElement ChoiceInput,
BooleanInput,
NumberInput
} from "../PropertyDescriptors"; } from "../PropertyDescriptors";
import { SmartUi, ClassInfo, OnSubmit } from "../ClassDescriptors"; import { SmartUi, ClassInfo, OnSubmit, Initialize } from "../ClassDescriptors";
import { import {
getPromise, getPromise,
initializeSqlX,
instanceSizeInfo, instanceSizeInfo,
instanceSizeOptions, instanceSizeOptions,
onInstanceCountChange, onInstanceCountChange,
@ -32,6 +27,7 @@ import { ChoiceItem } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
@SmartUi() @SmartUi()
@ClassInfo(getPromise(sqlXInfo)) @ClassInfo(getPromise(sqlXInfo))
@Initialize(initializeSqlX)
@OnSubmit(onSubmit) @OnSubmit(onSubmit)
export class SqlX extends SelfServeBase { export class SqlX extends SelfServeBase {
@ -39,10 +35,10 @@ export class SqlX extends SelfServeBase {
@CustomElement(renderText("This is the description part of SqlX")) @CustomElement(renderText("This is the description part of SqlX"))
static description: string; static description: string;
@PropertyInfo(getPromise(instanceSizeInfo))
@Label(getPromise("Instance Size")) @Label(getPromise("Instance Size"))
@Choices(getPromise(instanceSizeOptions)) @PropertyInfo(getPromise(instanceSizeInfo))
@DefaultKey(getPromise(Sizes.OneCore4Gb)) //@ChoiceInput(getPromise(instanceSizeOptions), getPromise(Sizes.OneCore4Gb))
@ChoiceInput(getPromise(instanceSizeOptions))
static instanceSize: ChoiceItem; static instanceSize: ChoiceItem;
@Label(getPromise("About")) @Label(getPromise("About"))
@ -50,22 +46,18 @@ export class SqlX extends SelfServeBase {
static about: string; static about: string;
@Label("Feature Allowed") @Label("Feature Allowed")
@DefaultBooleanValue(false) //@BooleanInput("allowed", "not allowed", false)
@TrueLabel("allowed") @BooleanInput("allowed", "not allowed")
@FalseLabel("not allowed")
static isAllowed: boolean; static isAllowed: boolean;
@Label("Instance Name") @Label("Instance Name")
@Placeholder("instance name") @Placeholder("instance name")
static instanceName: string; static instanceName: string;
@OnChange(onInstanceCountChange)
@Label(getPromise("Instance Count")) @Label(getPromise("Instance Count"))
@Min(getPromise(0)) @OnChange(onInstanceCountChange)
@Max(getPromise(5))
@Step(getPromise(1))
@DefaultNumberValue(getPromise(1))
@NumberInputType("slider")
@ParentOf(["instanceSize", "about", "instanceName", "isAllowed", ]) @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; static instanceCount: number;
} }

View File

@ -2,6 +2,7 @@ import { Text } from "office-ui-fabric-react";
import React from "react"; import React from "react";
import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent"; import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
import { TextComponent } from "./TextComponent"; import { TextComponent } from "./TextComponent";
import {SessionStorageUtility} from "../../Shared/StorageUtility"
export enum Sizes { export enum Sizes {
OneCore4Gb = "OneCore4Gb", OneCore4Gb = "OneCore4Gb",
@ -45,6 +46,20 @@ export const onSubmit = async (currentValues: Map<string, InputType>): Promise<v
", isAllowed:" + ", isAllowed:" +
currentValues.get("isAllowed") currentValues.get("isAllowed")
); );
SessionStorageUtility.setEntry("instanceCount", currentValues.get("instanceCount")?.toString())
SessionStorageUtility.setEntry("instanceSize", currentValues.get("instanceSize")?.toString())
SessionStorageUtility.setEntry("instanceName", currentValues.get("instanceName")?.toString())
SessionStorageUtility.setEntry("isAllowed", currentValues.get("isAllowed")?.toString())
};
export const initializeSqlX = async () : Promise<Map<string, InputType>> => {
let defaults = new Map<string, InputType>()
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<void> => { export const delay = (ms: number): Promise<void> => {
@ -63,7 +78,6 @@ export const getPromise = <T extends number | string | boolean | ChoiceItem[] |
export const renderText = (text: string) : (currentValues: Map<string, InputType>) => Promise<JSX.Element> => { export const renderText = (text: string) : (currentValues: Map<string, InputType>) => Promise<JSX.Element> => {
const f = async (currentValues: Map<string, InputType>): Promise<JSX.Element> => { const f = async (currentValues: Map<string, InputType>): Promise<JSX.Element> => {
//return <Text>SqlX is a new feature of Cosmos DB.</Text>;
return <TextComponent text={text} currentValues={currentValues}/> return <TextComponent text={text} currentValues={currentValues}/>
}; };
return f return f