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 { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { isEmpty } from "underscore";
import { SelfServeCmponent as SelfServeComponent } from "./SettingsSubComponents/SelfServe/SelfServeComponent";
import { SelfServeComponent } from "./SettingsSubComponents/SelfServe/SelfServeComponent";
interface SettingsV2TabInfo {
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";
interface SelfServeBaseCLass {
toSmartUiDescriptor: () => Descriptor;
}
export function SelfServeClass() {
return <U extends SelfServeBaseCLass>(constructor: U) => {
constructor;
};
}
export const SmartUi = (): ClassDecorator => {
return (target: Function) => {
toSmartUiDescriptor(target.name, target);

View File

@ -22,6 +22,10 @@ export const 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 => {
return addToMap("info", info);
};

View File

@ -2,7 +2,7 @@ import React from "react";
import { Descriptor, InputType, SmartUiComponent } from "../../../SmartUi/SmartUiComponent";
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> => {
console.log(currentValues.get("instanceCount"), currentValues.get("instanceSize"));
};

View File

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

View File

@ -14,43 +14,39 @@ import {
Placeholder,
DefaultNumberValue,
DefaultBooleanValue,
DefaultStringValue
DefaultStringValue,
CustomElement
} from "./PropertyDescriptors";
import { Descriptor, ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
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 {
OneCore4Gb = "OneCore4Gb",
TwoCore8Gb = "TwoCore8Gb",
FourCore16Gb = "FourCore16Gb"
}
import { SmartUi, ClassInfo, OnSubmit } from "./ClassDescriptors";
import { ChoiceItem } from "../../../SmartUi/SmartUiComponent";
import {
getPromise,
instanceSizeInfo,
instanceSizeOptions,
onInstanceCountChange,
onSubmit,
renderTextInput,
Sizes,
sqlXInfo
} from "./SqlXApis";
import { SelfServeBase } from "./SelfServeUtils";
@SmartUi()
@SelfServeClass()
@ClassInfo(getPromise(SqlX.sqlXInfo))
@OnSubmit(SqlX.onSubmit)
export class SqlX {
@ClassInfo(getPromise(sqlXInfo))
@OnSubmit(onSubmit)
export class SqlX extends SelfServeBase {
@Label(getPromise("About"))
@CustomElement(renderTextInput)
static about: string;
public static toSmartUiDescriptor = (): Descriptor => {
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
};
@PropertyInfo(getPromise(SqlX.instanceSizeInfo))
@PropertyInfo(getPromise(instanceSizeInfo))
@Label(getPromise("Instance Size"))
@Choices(getPromise(SqlX.instanceSizeOptions))
@Choices(getPromise(instanceSizeOptions))
//@Choices(instanceSizeOptions)
@DefaultKey(getPromise(Sizes.OneCore4Gb))
static instanceSize: ChoiceItem;
@OnChange(SqlX.onInstanceCountChange)
@OnChange(onInstanceCountChange)
@Label(getPromise("Instance Count"))
@Min(getPromise(0))
@Max(getPromise(5))
@ -60,57 +56,13 @@ export class SqlX {
@ParentOf(["instanceSize", "instanceName", "isAllowed"])
static instanceCount: number;
@Label(getPromise("Feature Allowed"))
@DefaultBooleanValue(getPromise(false))
@TrueLabel(getPromise("allowed"))
@FalseLabel(getPromise("not allowed"))
@Label("Feature Allowed")
@DefaultBooleanValue(false)
@TrueLabel("allowed")
@FalseLabel("not allowed")
static isAllowed: boolean;
@Label(getPromise("Instance Name"))
@DefaultStringValue(getPromise("asdf"))
@Placeholder(getPromise("instance name"))
@Label("Instance Name")
@Placeholder("instance name")
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", () => {
const exampleData: Descriptor = {
onSubmit: async (currentValues: Map<string, InputType>) => {},
onSubmit: async () => {},
root: {
id: "root",
info: {

View File

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