mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-24 04:57:50 +00:00
proper resolution of promises
This commit is contained in:
parent
4381ea447c
commit
2dbde9c31a
@ -17,7 +17,7 @@ export const SmartUi = (): ClassDecorator => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClassInfo = (info: Info): ClassDecorator => {
|
export const ClassInfo = (info: (() => Promise<Info>) | Info): ClassDecorator => {
|
||||||
return (target: Function) => {
|
return (target: Function) => {
|
||||||
addPropertyToMap(target, "root", target.name, "info", info);
|
addPropertyToMap(target, "root", target.name, "info", info);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
import { ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
||||||
import { addPropertyToMap } 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);
|
var propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase();
|
||||||
addPropertyToMap(target, property.toString(), className, "type", propertyType.name);
|
|
||||||
|
addPropertyToMap(target, property.toString(), className, "type", propertyType);
|
||||||
|
addPropertyToMap(target, property.toString(), className, "dataFieldName", property.toString());
|
||||||
|
|
||||||
if (!className) {
|
if (!className) {
|
||||||
throw new Error("property descriptor applied to non static field!");
|
throw new Error("property descriptor applied to non static field!");
|
||||||
@ -20,11 +22,11 @@ export const OnChange = (
|
|||||||
return addToMap("onChange", onChange);
|
return addToMap("onChange", onChange);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PropertyInfo = (info: Info): PropertyDecorator => {
|
export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
|
||||||
return addToMap("info", info);
|
return addToMap("info", info);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Placeholder = (placeholder: string): PropertyDecorator => {
|
export const Placeholder = (placeholder: (() => Promise<string>) | string): PropertyDecorator => {
|
||||||
return addToMap("placeholder", placeholder);
|
return addToMap("placeholder", placeholder);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,43 +34,47 @@ export const ParentOf = (children: string[]): PropertyDecorator => {
|
|||||||
return addToMap("parentOf", children);
|
return addToMap("parentOf", children);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Label = (label: string): PropertyDecorator => {
|
export const Label = (label: (() => Promise<string>) | string): PropertyDecorator => {
|
||||||
return addToMap("label", label);
|
return addToMap("label", label);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DataFieldName = (dataFieldName: string): PropertyDecorator => {
|
export const Min = (min: (() => Promise<number>) | number): PropertyDecorator => {
|
||||||
return addToMap("dataFieldName", dataFieldName);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Min = (min: number): PropertyDecorator => {
|
|
||||||
return addToMap("min", min);
|
return addToMap("min", min);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Max = (max: number): PropertyDecorator => {
|
export const Max = (max: (() => Promise<number>) | number): PropertyDecorator => {
|
||||||
return addToMap("max", max);
|
return addToMap("max", max);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Step = (step: number): PropertyDecorator => {
|
export const Step = (step: (() => Promise<number>) | number): PropertyDecorator => {
|
||||||
return addToMap("step", step);
|
return addToMap("step", step);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DefaultValue = (defaultValue: any): PropertyDecorator => {
|
export const DefaultStringValue = (defaultStringValue: (() => Promise<string>) | string): PropertyDecorator => {
|
||||||
return addToMap("defaultValue", defaultValue);
|
return addToMap("defaultValue", defaultStringValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TrueLabel = (trueLabel: string): PropertyDecorator => {
|
export const DefaultNumberValue = (defaultNumberValue: (() => Promise<number>) | number): PropertyDecorator => {
|
||||||
|
return addToMap("defaultValue", defaultNumberValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultBooleanValue = (defaultBooleanValue: (() => Promise<boolean>) | boolean): PropertyDecorator => {
|
||||||
|
return addToMap("defaultValue", defaultBooleanValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TrueLabel = (trueLabel: (() => Promise<string>) | string): PropertyDecorator => {
|
||||||
return addToMap("trueLabel", trueLabel);
|
return addToMap("trueLabel", trueLabel);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FalseLabel = (falseLabel: string): PropertyDecorator => {
|
export const FalseLabel = (falseLabel: (() => Promise<string>) | string): PropertyDecorator => {
|
||||||
return addToMap("falseLabel", falseLabel);
|
return addToMap("falseLabel", falseLabel);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Choices = (choices: EnumItem[]): PropertyDecorator => {
|
export const Choices = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[]): PropertyDecorator => {
|
||||||
return addToMap("choices", choices);
|
return addToMap("choices", choices);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DefaultKey = (defaultKey: string): PropertyDecorator => {
|
export const DefaultKey = (defaultKey: (() => Promise<string>) | string): PropertyDecorator => {
|
||||||
return addToMap("defaultKey", defaultKey);
|
return addToMap("defaultKey", defaultKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export class SelfServeCmponent extends React.Component {
|
|||||||
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,
|
||||||
@ -44,7 +44,7 @@ export class SelfServeCmponent extends React.Component {
|
|||||||
input: {
|
input: {
|
||||||
label: "Instance Size",
|
label: "Instance Size",
|
||||||
dataFieldName: "instanceSize",
|
dataFieldName: "instanceSize",
|
||||||
type: "Object",
|
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" },
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import {
|
import {
|
||||||
EnumItem,
|
ChoiceItem,
|
||||||
Node,
|
Node,
|
||||||
Info,
|
Info,
|
||||||
InputTypeValue,
|
InputTypeValue,
|
||||||
@ -9,26 +9,26 @@ import {
|
|||||||
NumberInput,
|
NumberInput,
|
||||||
StringInput,
|
StringInput,
|
||||||
BooleanInput,
|
BooleanInput,
|
||||||
EnumInput,
|
ChoiceInput,
|
||||||
InputType
|
InputType
|
||||||
} from "../../../SmartUi/SmartUiComponent";
|
} from "../../../SmartUi/SmartUiComponent";
|
||||||
|
|
||||||
export interface CommonInputTypes {
|
export interface CommonInputTypes {
|
||||||
id: string;
|
id: string;
|
||||||
info?: Info;
|
info?: (() => Promise<Info>) | Info;
|
||||||
parentOf?: string[];
|
parentOf?: string[];
|
||||||
type?: InputTypeValue;
|
type?: InputTypeValue;
|
||||||
label?: string;
|
label?: (() => Promise<string>) | string;
|
||||||
placeholder?: string;
|
placeholder?: (() => Promise<string>) | string;
|
||||||
dataFieldName?: string;
|
dataFieldName?: string;
|
||||||
min?: number;
|
min?: (() => Promise<number>) | number;
|
||||||
max?: number;
|
max?: (() => Promise<number>) | number;
|
||||||
step?: number;
|
step?: (() => Promise<number>) | number;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
trueLabel?: string;
|
trueLabel?: (() => Promise<string>) | string;
|
||||||
falseLabel?: string;
|
falseLabel?: (() => Promise<string>) | string;
|
||||||
choices?: EnumItem[];
|
choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
||||||
defaultKey?: string;
|
defaultKey?: (() => Promise<string>) | string;
|
||||||
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>;
|
||||||
@ -72,7 +72,7 @@ export const addPropertyToMap = (
|
|||||||
propertyObject = { id: propertyKey };
|
propertyObject = { id: propertyKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type") {
|
if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type" && descriptorKey !== "dataFieldName") {
|
||||||
throw new Error("duplicate descriptor");
|
throw new Error("duplicate descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,42 +82,6 @@ export const addPropertyToMap = (
|
|||||||
Reflect.defineMetadata(metadataKey, context, target);
|
Reflect.defineMetadata(metadataKey, context, target);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
const modifyParentProperty = (children: {[key: string]: any}, parentProperty: string, property: string | symbol) : any => {
|
|
||||||
if (parentProperty in children) {
|
|
||||||
children[parentProperty][property] ={id: property, input: {}}
|
|
||||||
return children
|
|
||||||
} else {
|
|
||||||
const keys = Object.keys(children)
|
|
||||||
for(var i =0; i< keys.length; i++) {
|
|
||||||
children[keys[i]] = modifyParentProperty(children[keys[i]], parentProperty, property)
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PropertyParser = (metadataKey: string, parentProperty?: string): PropertyDecorator => {
|
|
||||||
return (target, property) => {
|
|
||||||
let context = Reflect.getMetadata(metadataKey, target)
|
|
||||||
if(!context) {
|
|
||||||
context = {id: "root", info: undefined, input: undefined, children: {} }
|
|
||||||
context.children[property] = {id: property, input: {}}
|
|
||||||
}
|
|
||||||
if (parentProperty) {
|
|
||||||
const prevContextValue = JSON.stringify(context)
|
|
||||||
context.children = modifyParentProperty(context.children, parentProperty, property)
|
|
||||||
if (JSON.stringify(context) === prevContextValue) {
|
|
||||||
throw new Error(`${parentProperty} not defined. declare it before the child property with @Property decorator.`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.children[property] = {id: property, input: {}}
|
|
||||||
}
|
|
||||||
Reflect.defineMetadata(metadataKey, context, target)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const toSmartUiDescriptor = (metadataKey: string, target: Object): void => {
|
export const toSmartUiDescriptor = (metadataKey: string, target: Object): void => {
|
||||||
const context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>;
|
const context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>;
|
||||||
Reflect.defineMetadata(metadataKey, context, target);
|
Reflect.defineMetadata(metadataKey, context, target);
|
||||||
@ -125,7 +89,7 @@ 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)) {
|
if (!root?.onSubmit) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
|
"@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
|
||||||
);
|
);
|
||||||
@ -157,12 +121,12 @@ const addToDescriptor = (
|
|||||||
let value = context.get(key);
|
let value = context.get(key);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// should already be added to root
|
// should already be added to root
|
||||||
value = getChildFromRoot(key, smartUiDescriptor);
|
const childNode = getChildFromRoot(key, smartUiDescriptor);
|
||||||
if (!value) {
|
if (!childNode) {
|
||||||
// if not found at root level, error out
|
// if not found at root level, error out
|
||||||
throw new Error("Either child does not exist or child has been assigned to more than one parent");
|
throw new Error("Either child does not exist or child has been assigned to more than one parent");
|
||||||
}
|
}
|
||||||
root.children.push(value);
|
root.children.push(childNode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +144,7 @@ const addToDescriptor = (
|
|||||||
root.children.push(element);
|
root.children.push(element);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInputTypes => {
|
const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const children = smartUiDescriptor.root.children;
|
const children = smartUiDescriptor.root.children;
|
||||||
for (; i < children.length; i++) {
|
for (; i < children.length; i++) {
|
||||||
@ -197,16 +161,16 @@ const getInput = (value: CommonInputTypes): AnyInput => {
|
|||||||
if (!value.label || !value.type || !value.dataFieldName) {
|
if (!value.label || !value.type || !value.dataFieldName) {
|
||||||
throw new Error("label, onChange, type and dataFieldName are required.");
|
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 === undefined) {
|
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");
|
||||||
}
|
}
|
||||||
@ -215,6 +179,6 @@ const getInput = (value: CommonInputTypes): AnyInput => {
|
|||||||
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 ChoiceInput;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
DataFieldName,
|
|
||||||
Label,
|
Label,
|
||||||
Min,
|
Min,
|
||||||
Max,
|
Max,
|
||||||
Step,
|
Step,
|
||||||
DefaultKey,
|
DefaultKey,
|
||||||
DefaultValue,
|
|
||||||
NumberInputType,
|
NumberInputType,
|
||||||
Choices,
|
Choices,
|
||||||
ParentOf,
|
ParentOf,
|
||||||
@ -13,11 +11,23 @@ import {
|
|||||||
OnChange,
|
OnChange,
|
||||||
TrueLabel,
|
TrueLabel,
|
||||||
FalseLabel,
|
FalseLabel,
|
||||||
Placeholder
|
Placeholder,
|
||||||
|
DefaultNumberValue,
|
||||||
|
DefaultBooleanValue,
|
||||||
|
DefaultStringValue
|
||||||
} from "./PropertyDescriptors";
|
} from "./PropertyDescriptors";
|
||||||
import { Descriptor, EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
import { Descriptor, ChoiceItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
||||||
import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors";
|
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 {
|
enum Sizes {
|
||||||
OneCore4Gb = "OneCore4Gb",
|
OneCore4Gb = "OneCore4Gb",
|
||||||
TwoCore8Gb = "TwoCore8Gb",
|
TwoCore8Gb = "TwoCore8Gb",
|
||||||
@ -25,41 +35,43 @@ enum Sizes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SmartUi()
|
@SmartUi()
|
||||||
@ClassInfo(SqlX.sqlXInfo)
|
|
||||||
@OnSubmit(SqlX.onSubmit)
|
|
||||||
@SelfServeClass()
|
@SelfServeClass()
|
||||||
|
@ClassInfo(getPromise(SqlX.sqlXInfo))
|
||||||
|
@OnSubmit(SqlX.onSubmit)
|
||||||
export class SqlX {
|
export class SqlX {
|
||||||
@PropertyInfo(SqlX.instanceSizeInfo)
|
|
||||||
@Label("Instance Size")
|
public static toSmartUiDescriptor = (): Descriptor => {
|
||||||
@DataFieldName("instanceSize")
|
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
|
||||||
@Choices(SqlX.instanceSizeOptions)
|
};
|
||||||
@DefaultKey(Sizes.OneCore4Gb)
|
|
||||||
static instanceSize: EnumItem;
|
@PropertyInfo(getPromise(SqlX.instanceSizeInfo))
|
||||||
|
@Label(getPromise("Instance Size"))
|
||||||
|
@Choices(getPromise(SqlX.instanceSizeOptions))
|
||||||
|
@DefaultKey(getPromise(Sizes.OneCore4Gb))
|
||||||
|
static instanceSize: ChoiceItem;
|
||||||
|
|
||||||
@OnChange(SqlX.onInstanceCountChange)
|
@OnChange(SqlX.onInstanceCountChange)
|
||||||
@Label("Instance Count")
|
@Label(getPromise("Instance Count"))
|
||||||
@DataFieldName("instanceCount")
|
@Min(getPromise(0))
|
||||||
@Min(1)
|
@Max(getPromise(5))
|
||||||
@Max(5)
|
@Step(getPromise(1))
|
||||||
@Step(1)
|
@DefaultNumberValue(getPromise(1))
|
||||||
@DefaultValue(1)
|
|
||||||
@NumberInputType("slider")
|
@NumberInputType("slider")
|
||||||
@ParentOf(["instanceSize", "instanceName", "isAllowed"])
|
@ParentOf(["instanceSize", "instanceName", "isAllowed"])
|
||||||
static instanceCount: number;
|
static instanceCount: number;
|
||||||
|
|
||||||
@Label("Feature Allowed")
|
@Label(getPromise("Feature Allowed"))
|
||||||
@DataFieldName("isAllowed")
|
@DefaultBooleanValue(getPromise(false))
|
||||||
@DefaultValue(false)
|
@TrueLabel(getPromise("allowed"))
|
||||||
@TrueLabel("allowed")
|
@FalseLabel(getPromise("not allowed"))
|
||||||
@FalseLabel("not allowed")
|
|
||||||
static isAllowed: boolean;
|
static isAllowed: boolean;
|
||||||
|
|
||||||
@Label("Instance Name")
|
@Label(getPromise("Instance Name"))
|
||||||
@DataFieldName("instanceName")
|
@DefaultStringValue(getPromise("asdf"))
|
||||||
@Placeholder("instance name")
|
@Placeholder(getPromise("instance name"))
|
||||||
static instanceName: string;
|
static instanceName: string;
|
||||||
|
|
||||||
static instanceSizeOptions: EnumItem[] = [
|
static instanceSizeOptions: ChoiceItem[] = [
|
||||||
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
|
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
|
||||||
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
|
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
|
||||||
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
|
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
|
||||||
@ -97,7 +109,8 @@ export class SqlX {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public static toSmartUiDescriptor = (): Descriptor => {
|
static delay = (ms: number) => {
|
||||||
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
|
return new Promise( resolve => setTimeout(resolve, ms) );
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ describe("SmartUiComponent", () => {
|
|||||||
input: {
|
input: {
|
||||||
label: "Database",
|
label: "Database",
|
||||||
dataFieldName: "database",
|
dataFieldName: "database",
|
||||||
type: "enum",
|
type: "object",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "Database 1", key: "db1", value: "database1" },
|
{ label: "Database 1", key: "db1", value: "database1" },
|
||||||
{ label: "Database 2", key: "db2", value: "database2" },
|
{ label: "Database 2", key: "db2", value: "database2" },
|
||||||
|
@ -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, PrimaryButton } from "office-ui-fabric-react";
|
import { Link, MessageBar, MessageBarType, PrimaryButton, Spinner, SpinnerSize } from "office-ui-fabric-react";
|
||||||
|
|
||||||
import * as InputUtils from "./InputUtils";
|
import * as InputUtils from "./InputUtils";
|
||||||
import "./SmartUiComponent.less";
|
import "./SmartUiComponent.less";
|
||||||
@ -21,45 +21,45 @@ import "./SmartUiComponent.less";
|
|||||||
* - a descriptor of the UX.
|
* - a descriptor of the UX.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type InputTypeValue = "Number" | "String" | "Boolean" | "Object";
|
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 ChoiceItem = { label: string; key: string; value: any };
|
||||||
|
|
||||||
export type InputType = Number | String | Boolean | EnumItem;
|
export type InputType = Number | String | Boolean | ChoiceItem;
|
||||||
|
|
||||||
interface BaseInput {
|
interface BaseInput {
|
||||||
label: 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?: string;
|
placeholder?: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For now, this only supports integers
|
* For now, this only supports integers
|
||||||
*/
|
*/
|
||||||
export interface NumberInput extends BaseInput {
|
export interface NumberInput extends BaseInput {
|
||||||
min?: number;
|
min?: (() => Promise<number>) | number;
|
||||||
max?: number;
|
max?: (() => Promise<number>) | number
|
||||||
step: number;
|
step: (() => Promise<number>) | number
|
||||||
defaultValue: number;
|
defaultValue: (() => Promise<number>) | number
|
||||||
inputType: "spin" | "slider";
|
inputType: "spin" | "slider";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BooleanInput extends BaseInput {
|
export interface BooleanInput extends BaseInput {
|
||||||
trueLabel: string;
|
trueLabel: (() => Promise<string>) | string;
|
||||||
falseLabel: string;
|
falseLabel: (() => Promise<string>) | string;
|
||||||
defaultValue: boolean;
|
defaultValue: (() => Promise<boolean>) | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StringInput extends BaseInput {
|
export interface StringInput extends BaseInput {
|
||||||
defaultValue?: string;
|
defaultValue?: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnumInput extends BaseInput {
|
export interface ChoiceInput extends BaseInput {
|
||||||
choices: EnumItem[];
|
choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
||||||
defaultKey: string;
|
defaultKey: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Info {
|
export interface Info {
|
||||||
@ -70,11 +70,11 @@ export interface Info {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyInput = NumberInput | BooleanInput | StringInput | EnumInput;
|
export type AnyInput = NumberInput | BooleanInput | StringInput | ChoiceInput;
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
id: string;
|
id: string;
|
||||||
info?: Info;
|
info?: (() => Promise<Info>) | Info;
|
||||||
input?: AnyInput;
|
input?: AnyInput;
|
||||||
children?: Node[];
|
children?: Node[];
|
||||||
}
|
}
|
||||||
@ -105,34 +105,98 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
constructor(props: SmartUiComponentProps) {
|
constructor(props: SmartUiComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
currentValues: this.setDefaultValues(),
|
currentValues: undefined,
|
||||||
errors: new Map()
|
errors: new Map()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.setDefaultValues()
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDefaultValues = (): Map<string, InputType> => {
|
private setDefaultValues = async () : Promise<void> => {
|
||||||
const defaults = new Map();
|
const defaults = new Map<string, InputType>()
|
||||||
this.setDefaults(this.props.descriptor.root, defaults);
|
await this.setDefaults(this.props.descriptor.root, defaults)
|
||||||
return defaults;
|
this.setState({currentValues: 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 setDefaults = async (currentNode: Node, defaults: Map<string, InputType>) : Promise<void> => {
|
||||||
|
if (currentNode.info && currentNode.info instanceof Function) {
|
||||||
|
currentNode.info = await (currentNode.info as Function)()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentNode.input) {
|
||||||
|
currentNode.input = await this.getModifiedInput(currentNode.input)
|
||||||
|
defaults.set(currentNode.input.dataFieldName, this.getDefaultValue(currentNode.input));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child, defaults)));
|
||||||
};
|
};
|
||||||
|
|
||||||
private getDefault = (input: AnyInput): InputType => {
|
private getModifiedInput = async (input: AnyInput): Promise<AnyInput> => {
|
||||||
|
|
||||||
|
if (input.label instanceof Function) {
|
||||||
|
input.label = await (input.label as Function)()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.placeholder instanceof Function) {
|
||||||
|
input.placeholder = await (input.placeholder as Function)()
|
||||||
|
}
|
||||||
|
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case "String":
|
case "string":
|
||||||
return (input as StringInput).defaultValue;
|
const stringInput = input as StringInput
|
||||||
case "Number":
|
if (stringInput.defaultValue instanceof Function) {
|
||||||
return (input as NumberInput).defaultValue;
|
stringInput.defaultValue = await (stringInput.defaultValue as Function)()
|
||||||
case "Boolean":
|
}
|
||||||
return (input as BooleanInput).defaultValue;
|
return stringInput;
|
||||||
|
case "number":
|
||||||
|
const numberInput = input as NumberInput
|
||||||
|
if (numberInput.defaultValue instanceof Function) {
|
||||||
|
numberInput.defaultValue = await (numberInput.defaultValue as Function)()
|
||||||
|
}
|
||||||
|
if (numberInput.min instanceof Function) {
|
||||||
|
numberInput.min = await (numberInput.min as Function)()
|
||||||
|
}
|
||||||
|
if (numberInput.max instanceof Function) {
|
||||||
|
numberInput.max = await (numberInput.max as Function)()
|
||||||
|
}
|
||||||
|
if (numberInput.step instanceof Function) {
|
||||||
|
numberInput.step = await (numberInput.step as Function)()
|
||||||
|
}
|
||||||
|
return numberInput;
|
||||||
|
case "boolean":
|
||||||
|
const booleanInput = input as BooleanInput
|
||||||
|
if (booleanInput.defaultValue instanceof Function) {
|
||||||
|
booleanInput.defaultValue = await (booleanInput.defaultValue as Function)()
|
||||||
|
}
|
||||||
|
if (booleanInput.trueLabel instanceof Function) {
|
||||||
|
booleanInput.trueLabel = await (booleanInput.trueLabel as Function)()
|
||||||
|
}
|
||||||
|
if (booleanInput.falseLabel instanceof Function) {
|
||||||
|
booleanInput.falseLabel = await (booleanInput.falseLabel as Function)()
|
||||||
|
}
|
||||||
|
return booleanInput;
|
||||||
default:
|
default:
|
||||||
return (input as EnumInput).defaultKey;
|
const enumInput = input as ChoiceInput
|
||||||
|
if (enumInput.defaultKey instanceof Function) {
|
||||||
|
enumInput.defaultKey = await (enumInput.defaultKey as Function)()
|
||||||
|
}
|
||||||
|
if (enumInput.choices instanceof Function) {
|
||||||
|
enumInput.choices = await (enumInput.choices as Function)()
|
||||||
|
}
|
||||||
|
return enumInput
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private getDefaultValue = (input: AnyInput): InputType => {
|
||||||
|
switch (input.type) {
|
||||||
|
case "string":
|
||||||
|
return (input as StringInput).defaultValue as string;
|
||||||
|
case "number":
|
||||||
|
return (input as NumberInput).defaultValue as number;
|
||||||
|
case "boolean":
|
||||||
|
return (input as BooleanInput).defaultValue as boolean;
|
||||||
|
default:
|
||||||
|
return (input as ChoiceInput).defaultKey as string;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,10 +231,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
id={`${input.dataFieldName}-input`}
|
id={`${input.dataFieldName}-input`}
|
||||||
label={input.label}
|
label={input.label as string}
|
||||||
type="text"
|
type="text"
|
||||||
value={input.defaultValue}
|
defaultValue={input.defaultValue as string}
|
||||||
placeholder={input.placeholder}
|
placeholder={input.placeholder as string}
|
||||||
onChange={(_, newValue) => this.onInputChange(input, newValue)}
|
onChange={(_, newValue) => this.onInputChange(input, newValue)}
|
||||||
styles={{
|
styles={{
|
||||||
subComponentStyles: {
|
subComponentStyles: {
|
||||||
@ -233,17 +297,23 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
|
|
||||||
private renderNumberInput(input: NumberInput): JSX.Element {
|
private renderNumberInput(input: NumberInput): JSX.Element {
|
||||||
const { label, min, max, defaultValue, dataFieldName, step } = input;
|
const { label, min, max, defaultValue, dataFieldName, step } = input;
|
||||||
const props = { label, min, max, ariaLabel: label, step };
|
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") {
|
if (input.inputType === "spin") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SpinButton
|
<SpinButton
|
||||||
{...props}
|
{...props}
|
||||||
defaultValue={defaultValue.toString()}
|
defaultValue={(defaultValue as number).toString()}
|
||||||
onValidate={newValue => this.onValidate(input, newValue, min, max)}
|
onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)}
|
||||||
onIncrement={newValue => this.onIncrement(input, newValue, step, max)}
|
onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)}
|
||||||
onDecrement={newValue => this.onDecrement(input, newValue, step, min)}
|
onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)}
|
||||||
labelPosition={Position.top}
|
labelPosition={Position.top}
|
||||||
styles={{
|
styles={{
|
||||||
label: {
|
label: {
|
||||||
@ -263,7 +333,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
// showValue={true}
|
// showValue={true}
|
||||||
// valueFormat={}
|
// valueFormat={}
|
||||||
{...props}
|
{...props}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue as number}
|
||||||
onChange={newValue => this.onInputChange(input, newValue)}
|
onChange={newValue => this.onInputChange(input, newValue)}
|
||||||
styles={{
|
styles={{
|
||||||
titleLabel: {
|
titleLabel: {
|
||||||
@ -291,12 +361,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
<RadioSwitchComponent
|
<RadioSwitchComponent
|
||||||
choices={[
|
choices={[
|
||||||
{
|
{
|
||||||
label: input.falseLabel,
|
label: input.falseLabel as string,
|
||||||
key: "false",
|
key: "false",
|
||||||
onSelect: () => this.onInputChange(input, false)
|
onSelect: () => this.onInputChange(input, false)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: input.trueLabel,
|
label: input.trueLabel as string,
|
||||||
key: "true",
|
key: "true",
|
||||||
onSelect: () => this.onInputChange(input, true)
|
onSelect: () => this.onInputChange(input, true)
|
||||||
}
|
}
|
||||||
@ -313,19 +383,19 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderEnumInput(input: EnumInput): JSX.Element {
|
private renderEnumInput(input: ChoiceInput): JSX.Element {
|
||||||
const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
|
const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={label}
|
label={label as string}
|
||||||
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
|
: defaultKey as string
|
||||||
}
|
}
|
||||||
onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
|
onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder as string}
|
||||||
options={choices.map(c => ({
|
options={(choices as ChoiceItem[]).map(c => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
text: c.value
|
text: c.value
|
||||||
}))}
|
}))}
|
||||||
@ -342,14 +412,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);
|
||||||
default:
|
default:
|
||||||
return this.renderEnumInput(input as EnumInput);
|
return this.renderEnumInput(input as ChoiceInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +428,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
|
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
|
||||||
{node.info && this.renderInfo(node.info)}
|
{node.info && this.renderInfo(node.info as Info)}
|
||||||
{node.input && this.renderInput(node.input)}
|
{node.input && this.renderInput(node.input)}
|
||||||
{node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)}
|
{node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -367,8 +437,8 @@ 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 ?
|
||||||
<Stack tokens={containerStackTokens}>
|
<Stack tokens={containerStackTokens}>
|
||||||
{this.renderNode(this.props.descriptor.root)}
|
{this.renderNode(this.props.descriptor.root)}
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
@ -377,6 +447,8 @@ 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} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user