added custom element and base class

This commit is contained in:
Srinath Narayanan 2020-12-07 02:23:20 -08:00
parent 2dbde9c31a
commit 90fb7e7d8f
9 changed files with 161 additions and 134 deletions

View File

@ -45,7 +45,7 @@ import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readM
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress"; import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { isEmpty } from "underscore"; import { isEmpty } from "underscore";
import { SelfServeCmponent as SelfServeComponent } from "./SettingsSubComponents/SelfServe/SelfServeComponent"; import { SelfServeComponent } from "./SettingsSubComponents/SelfServe/SelfServeComponent";
interface SettingsV2TabInfo { interface SettingsV2TabInfo {
tab: SettingsV2TabTypes; tab: SettingsV2TabTypes;

View File

@ -1,16 +1,6 @@
import { Descriptor, Info, InputType } from "../../../SmartUi/SmartUiComponent"; import { Info, InputType } from "../../../SmartUi/SmartUiComponent";
import { addPropertyToMap, toSmartUiDescriptor } from "./SelfServeUtils"; import { addPropertyToMap, toSmartUiDescriptor } from "./SelfServeUtils";
interface SelfServeBaseCLass {
toSmartUiDescriptor: () => Descriptor;
}
export function SelfServeClass() {
return <U extends SelfServeBaseCLass>(constructor: U) => {
constructor;
};
}
export const SmartUi = (): ClassDecorator => { export const SmartUi = (): ClassDecorator => {
return (target: Function) => { return (target: Function) => {
toSmartUiDescriptor(target.name, target); toSmartUiDescriptor(target.name, target);

View File

@ -22,6 +22,10 @@ export const OnChange = (
return addToMap("onChange", onChange); return addToMap("onChange", onChange);
}; };
export const CustomElement = (customElement: (() => Promise<JSX.Element>) | JSX.Element): PropertyDecorator => {
return addToMap("customElement", customElement);
};
export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => { export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
return addToMap("info", info); return addToMap("info", info);
}; };

View File

@ -2,7 +2,7 @@ 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";
export class SelfServeCmponent extends React.Component { export class SelfServeComponent extends React.Component {
private onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => { private onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => {
console.log(currentValues.get("instanceCount"), currentValues.get("instanceSize")); console.log(currentValues.get("instanceCount"), currentValues.get("instanceSize"));
}; };

View File

@ -13,6 +13,12 @@ import {
InputType InputType
} from "../../../SmartUi/SmartUiComponent"; } from "../../../SmartUi/SmartUiComponent";
export class SelfServeBase {
public static toSmartUiDescriptor(): Descriptor {
return Reflect.getMetadata(this.name, this) as Descriptor;
}
}
export interface CommonInputTypes { export interface CommonInputTypes {
id: string; id: string;
info?: (() => Promise<Info>) | Info; info?: (() => Promise<Info>) | Info;
@ -32,6 +38,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>;
customElement?: (() => Promise<JSX.Element>) | JSX.Element;
} }
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>( const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(

View File

@ -14,43 +14,39 @@ import {
Placeholder, Placeholder,
DefaultNumberValue, DefaultNumberValue,
DefaultBooleanValue, DefaultBooleanValue,
DefaultStringValue DefaultStringValue,
CustomElement
} from "./PropertyDescriptors"; } from "./PropertyDescriptors";
import { Descriptor, ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent"; import { SmartUi, ClassInfo, OnSubmit } from "./ClassDescriptors";
import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors"; import { ChoiceItem } from "../../../SmartUi/SmartUiComponent";
import {
const getPromise = <T extends (number | string | boolean | ChoiceItem[] | Info)>(value: T) : () => Promise<T> => { getPromise,
const f = async () : Promise<T> => { instanceSizeInfo,
console.log("delay start") instanceSizeOptions,
await SqlX.delay(100) onInstanceCountChange,
console.log("delay end") onSubmit,
return value renderTextInput,
} Sizes,
return f sqlXInfo
} } from "./SqlXApis";
enum Sizes { import { SelfServeBase } from "./SelfServeUtils";
OneCore4Gb = "OneCore4Gb",
TwoCore8Gb = "TwoCore8Gb",
FourCore16Gb = "FourCore16Gb"
}
@SmartUi() @SmartUi()
@SelfServeClass() @ClassInfo(getPromise(sqlXInfo))
@ClassInfo(getPromise(SqlX.sqlXInfo)) @OnSubmit(onSubmit)
@OnSubmit(SqlX.onSubmit) export class SqlX extends SelfServeBase {
export class SqlX { @Label(getPromise("About"))
@CustomElement(renderTextInput)
static about: string;
public static toSmartUiDescriptor = (): Descriptor => { @PropertyInfo(getPromise(instanceSizeInfo))
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
};
@PropertyInfo(getPromise(SqlX.instanceSizeInfo))
@Label(getPromise("Instance Size")) @Label(getPromise("Instance Size"))
@Choices(getPromise(SqlX.instanceSizeOptions)) @Choices(getPromise(instanceSizeOptions))
//@Choices(instanceSizeOptions)
@DefaultKey(getPromise(Sizes.OneCore4Gb)) @DefaultKey(getPromise(Sizes.OneCore4Gb))
static instanceSize: ChoiceItem; static instanceSize: ChoiceItem;
@OnChange(SqlX.onInstanceCountChange) @OnChange(onInstanceCountChange)
@Label(getPromise("Instance Count")) @Label(getPromise("Instance Count"))
@Min(getPromise(0)) @Min(getPromise(0))
@Max(getPromise(5)) @Max(getPromise(5))
@ -60,57 +56,13 @@ export class SqlX {
@ParentOf(["instanceSize", "instanceName", "isAllowed"]) @ParentOf(["instanceSize", "instanceName", "isAllowed"])
static instanceCount: number; static instanceCount: number;
@Label(getPromise("Feature Allowed")) @Label("Feature Allowed")
@DefaultBooleanValue(getPromise(false)) @DefaultBooleanValue(false)
@TrueLabel(getPromise("allowed")) @TrueLabel("allowed")
@FalseLabel(getPromise("not allowed")) @FalseLabel("not allowed")
static isAllowed: boolean; static isAllowed: boolean;
@Label(getPromise("Instance Name")) @Label("Instance Name")
@DefaultStringValue(getPromise("asdf")) @Placeholder("instance name")
@Placeholder(getPromise("instance name"))
static instanceName: string; static instanceName: string;
static instanceSizeOptions: ChoiceItem[] = [
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
];
static sqlXInfo: Info = {
message: "SqlX is a self serve class"
};
static instanceSizeInfo: Info = {
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")
);
};
static delay = (ms: number) => {
return new Promise( resolve => setTimeout(resolve, ms) );
}
}

View File

@ -0,0 +1,65 @@
import { Text } from "office-ui-fabric-react";
import React from "react";
import { ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
export enum Sizes {
OneCore4Gb = "OneCore4Gb",
TwoCore8Gb = "TwoCore8Gb",
FourCore16Gb = "FourCore16Gb"
}
export const instanceSizeOptions: ChoiceItem[] = [
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
];
export const sqlXInfo: Info = {
message: "SqlX is a self serve class"
};
export const instanceSizeInfo: Info = {
message: "instance size will be updated in the future"
};
export const 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;
};
export const 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")
);
};
export const delay = (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms));
};
export const getPromise = <T extends number | string | boolean | ChoiceItem[] | Info>(value: T): (() => Promise<T>) => {
const f = async (): Promise<T> => {
console.log("delay start");
await delay(100);
console.log("delay end");
return value;
};
return f;
};
export const renderTextInput = async (): Promise<JSX.Element> => {
return <Text>SqlX is a new feature of Cosmos DB</Text>;
};

View File

@ -4,7 +4,7 @@ import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
describe("SmartUiComponent", () => { describe("SmartUiComponent", () => {
const exampleData: Descriptor = { const exampleData: Descriptor = {
onSubmit: async (currentValues: Map<string, InputType>) => {}, onSubmit: async () => {},
root: { root: {
id: "root", id: "root",
info: { info: {

View File

@ -28,12 +28,13 @@ export type ChoiceItem = { label: string; key: string; value: any };
export type InputType = Number | String | Boolean | ChoiceItem; export type InputType = Number | String | Boolean | ChoiceItem;
interface BaseInput { export interface BaseInput {
label: (() => Promise<string>) | 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?: (() => Promise<string>) | string; placeholder?: (() => Promise<string>) | string;
customElement?: (() => Promise<JSX.Element>) | JSX.Element;
} }
/** /**
@ -41,9 +42,9 @@ interface BaseInput {
*/ */
export interface NumberInput extends BaseInput { 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";
} }
@ -109,22 +110,22 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
errors: new Map() errors: new Map()
}; };
this.setDefaultValues() this.setDefaultValues();
} }
private setDefaultValues = async (): Promise<void> => { private setDefaultValues = async (): Promise<void> => {
const defaults = new Map<string, InputType>() const defaults = new Map<string, InputType>();
await this.setDefaults(this.props.descriptor.root, defaults) await this.setDefaults(this.props.descriptor.root, defaults);
this.setState({currentValues: defaults}) this.setState({ currentValues: defaults });
} };
private setDefaults = async (currentNode: Node, defaults: Map<string, InputType>): Promise<void> => { private setDefaults = async (currentNode: Node, defaults: Map<string, InputType>): Promise<void> => {
if (currentNode.info && currentNode.info instanceof Function) { if (currentNode.info && currentNode.info instanceof Function) {
currentNode.info = await (currentNode.info as Function)() currentNode.info = await (currentNode.info as Function)();
} }
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)); defaults.set(currentNode.input.dataFieldName, this.getDefaultValue(currentNode.input));
} }
@ -132,58 +133,64 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
}; };
private getModifiedInput = async (input: AnyInput): Promise<AnyInput> => { private getModifiedInput = async (input: AnyInput): Promise<AnyInput> => {
if (input.label instanceof Function) { if (input.label instanceof Function) {
input.label = await (input.label as Function)() input.label = await (input.label as Function)();
} }
if (input.placeholder instanceof Function) { if (input.placeholder instanceof Function) {
input.placeholder = await (input.placeholder as Function)() input.placeholder = await (input.placeholder as Function)();
}
if (input.customElement) {
if (input.customElement instanceof Function) {
input.customElement = await (input.customElement as Function)();
}
return input;
} }
switch (input.type) { switch (input.type) {
case "string": case "string":
const stringInput = input as StringInput const stringInput = input as StringInput;
if (stringInput.defaultValue instanceof Function) { if (stringInput.defaultValue instanceof Function) {
stringInput.defaultValue = await (stringInput.defaultValue as Function)() stringInput.defaultValue = await (stringInput.defaultValue as Function)();
} }
return stringInput; return stringInput;
case "number": case "number":
const numberInput = input as NumberInput const numberInput = input as NumberInput;
if (numberInput.defaultValue instanceof Function) { if (numberInput.defaultValue instanceof Function) {
numberInput.defaultValue = await (numberInput.defaultValue as Function)() numberInput.defaultValue = await (numberInput.defaultValue as Function)();
} }
if (numberInput.min instanceof Function) { if (numberInput.min instanceof Function) {
numberInput.min = await (numberInput.min as Function)() numberInput.min = await (numberInput.min as Function)();
} }
if (numberInput.max instanceof Function) { if (numberInput.max instanceof Function) {
numberInput.max = await (numberInput.max as Function)() numberInput.max = await (numberInput.max as Function)();
} }
if (numberInput.step instanceof Function) { if (numberInput.step instanceof Function) {
numberInput.step = await (numberInput.step as Function)() numberInput.step = await (numberInput.step as Function)();
} }
return numberInput; return numberInput;
case "boolean": case "boolean":
const booleanInput = input as BooleanInput const booleanInput = input as BooleanInput;
if (booleanInput.defaultValue instanceof Function) { if (booleanInput.defaultValue instanceof Function) {
booleanInput.defaultValue = await (booleanInput.defaultValue as Function)() booleanInput.defaultValue = await (booleanInput.defaultValue as Function)();
} }
if (booleanInput.trueLabel instanceof Function) { if (booleanInput.trueLabel instanceof Function) {
booleanInput.trueLabel = await (booleanInput.trueLabel as Function)() booleanInput.trueLabel = await (booleanInput.trueLabel as Function)();
} }
if (booleanInput.falseLabel instanceof Function) { if (booleanInput.falseLabel instanceof Function) {
booleanInput.falseLabel = await (booleanInput.falseLabel as Function)() booleanInput.falseLabel = await (booleanInput.falseLabel as Function)();
} }
return booleanInput; return booleanInput;
default: default:
const enumInput = input as ChoiceInput const enumInput = input as ChoiceInput;
if (enumInput.defaultKey instanceof Function) { if (enumInput.defaultKey instanceof Function) {
enumInput.defaultKey = await (enumInput.defaultKey as Function)() enumInput.defaultKey = await (enumInput.defaultKey as Function)();
} }
if (enumInput.choices instanceof Function) { if (enumInput.choices instanceof Function) {
enumInput.choices = await (enumInput.choices as Function)() enumInput.choices = await (enumInput.choices as Function)();
} }
return enumInput return enumInput;
} }
}; };
@ -391,7 +398,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
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 as string : (defaultKey as string)
} }
onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())} onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
placeholder={placeholder as string} placeholder={placeholder as string}
@ -411,6 +418,9 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
} }
private renderInput(input: AnyInput): JSX.Element { private renderInput(input: AnyInput): JSX.Element {
if (input.customElement) {
return input.customElement as JSX.Element;
}
switch (input.type) { switch (input.type) {
case "string": case "string":
return this.renderStringInput(input as StringInput); return this.renderStringInput(input as StringInput);
@ -437,8 +447,7 @@ 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 ? (
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
@ -447,7 +456,7 @@ 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} /> <Spinner size={SpinnerSize.large} />
); );
} }