mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-23 20:48:11 +00:00
removed type requirement
This commit is contained in:
parent
69b17f1a00
commit
4381ea447c
@ -903,7 +903,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.SelfServe,
|
tab: SettingsV2TabTypes.SelfServe,
|
||||||
content: <SelfServeComponent propertyNames={["prop1", "prop2"]} />
|
content: <SelfServeComponent />
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Descriptor, Info } from "../../../SmartUi/SmartUiComponent";
|
import { Descriptor, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
||||||
import { addPropertyToMap, DescriptorType, toSmartUiDescriptor } from "./SelfServeUtils";
|
import { addPropertyToMap, toSmartUiDescriptor } from "./SelfServeUtils";
|
||||||
|
|
||||||
interface SelfServeBaseCLass {
|
interface SelfServeBaseCLass {
|
||||||
toSmartUiDescriptor: () => Descriptor;
|
toSmartUiDescriptor: () => Descriptor;
|
||||||
@ -19,6 +19,12 @@ export const SmartUi = (): ClassDecorator => {
|
|||||||
|
|
||||||
export const ClassInfo = (info: Info): ClassDecorator => {
|
export const ClassInfo = (info: Info): ClassDecorator => {
|
||||||
return (target: Function) => {
|
return (target: Function) => {
|
||||||
addPropertyToMap(target, "root", target.name, "info", info, DescriptorType.ClassDescriptor);
|
addPropertyToMap(target, "root", target.name, "info", info);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OnSubmit = (onSubmit: (currentValues: Map<string, InputType>) => Promise<void>): ClassDecorator => {
|
||||||
|
return (target: Function) => {
|
||||||
|
addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
import { EnumItem, Info, InputTypeValue } from "../../../SmartUi/SmartUiComponent";
|
import { EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
||||||
import { addPropertyToMap, DescriptorType } 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);
|
||||||
|
addPropertyToMap(target, property.toString(), className, "type", propertyType.name);
|
||||||
|
|
||||||
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(
|
addPropertyToMap(target, property.toString(), className, descriptorName, descriptorValue);
|
||||||
target,
|
|
||||||
property.toString(),
|
|
||||||
className,
|
|
||||||
descriptorName,
|
|
||||||
descriptorValue,
|
|
||||||
DescriptorType.PropertyDescriptor
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const OnChange = (
|
||||||
|
onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
|
||||||
|
): PropertyDecorator => {
|
||||||
|
return addToMap("onChange", onChange);
|
||||||
|
};
|
||||||
|
|
||||||
export const PropertyInfo = (info: Info): PropertyDecorator => {
|
export const PropertyInfo = (info: Info): PropertyDecorator => {
|
||||||
return addToMap("info", info);
|
return addToMap("info", info);
|
||||||
};
|
};
|
||||||
@ -30,10 +32,6 @@ export const ParentOf = (children: string[]): PropertyDecorator => {
|
|||||||
return addToMap("parentOf", children);
|
return addToMap("parentOf", children);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Type = (type: InputTypeValue): PropertyDecorator => {
|
|
||||||
return addToMap("type", type);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Label = (label: string): PropertyDecorator => {
|
export const Label = (label: string): PropertyDecorator => {
|
||||||
return addToMap("label", label);
|
return addToMap("label", label);
|
||||||
};
|
};
|
||||||
|
@ -2,26 +2,13 @@ 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";
|
||||||
|
|
||||||
interface SelfServeComponentProps {
|
export class SelfServeCmponent extends React.Component {
|
||||||
propertyNames: string[];
|
private onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => {
|
||||||
}
|
console.log(currentValues.get("instanceCount"), currentValues.get("instanceSize"));
|
||||||
|
};
|
||||||
export class SelfServeCmponent extends React.Component<SelfServeComponentProps> {
|
|
||||||
private properties: any = {};
|
|
||||||
|
|
||||||
constructor(props: SelfServeComponentProps) {
|
|
||||||
super(props);
|
|
||||||
let stringer = "{";
|
|
||||||
for (var i = 0; i < props.propertyNames.length; i++) {
|
|
||||||
stringer += `"${props.propertyNames[i]}":null,`;
|
|
||||||
}
|
|
||||||
stringer = stringer.substring(0, stringer.length - 1);
|
|
||||||
console.log(stringer);
|
|
||||||
stringer += "}";
|
|
||||||
this.properties = JSON.parse(stringer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private selfServeData: Descriptor = {
|
private selfServeData: Descriptor = {
|
||||||
|
onSubmit: this.onSubmit,
|
||||||
root: {
|
root: {
|
||||||
id: "root",
|
id: "root",
|
||||||
info: {
|
info: {
|
||||||
@ -37,12 +24,19 @@ export class SelfServeCmponent extends React.Component<SelfServeComponentProps>
|
|||||||
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,
|
||||||
defaultValue: 1,
|
defaultValue: 1,
|
||||||
inputType: "slider"
|
inputType: "slider",
|
||||||
|
onChange: (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
|
||||||
|
currentState.set("instanceCount", newValue);
|
||||||
|
if ((newValue as number) === 1) {
|
||||||
|
currentState.set("instanceSize", "1Core4Gb");
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -50,33 +44,25 @@ export class SelfServeCmponent extends React.Component<SelfServeComponentProps>
|
|||||||
input: {
|
input: {
|
||||||
label: "Instance Size",
|
label: "Instance Size",
|
||||||
dataFieldName: "instanceSize",
|
dataFieldName: "instanceSize",
|
||||||
type: "enum",
|
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" },
|
||||||
{ label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" }
|
{ label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" }
|
||||||
],
|
],
|
||||||
defaultKey: "1Core4Gb"
|
defaultKey: "1Core4Gb",
|
||||||
|
onChange: (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
|
||||||
|
currentState.set("instanceSize", newValue);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private exampleCallbacks = (newValues: Map<string, InputType>): void => {
|
|
||||||
for (var i = 0; i < this.props.propertyNames.length; i++) {
|
|
||||||
const prop = this.props.propertyNames[i];
|
|
||||||
const newVal = newValues.get(prop);
|
|
||||||
if (newVal) {
|
|
||||||
this.properties[`${prop}`] = newVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(this.properties);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
//return <SmartUiComponent descriptor={this.selfServeData} onChange={this.exampleCallbacks} />
|
//return <SmartUiComponent descriptor={this.selfServeData} />
|
||||||
return <SmartUiComponent descriptor={SqlX.toSmartUiDescriptor()} onChange={this.exampleCallbacks} />;
|
return <SmartUiComponent descriptor={SqlX.toSmartUiDescriptor()} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import {
|
|||||||
NumberInput,
|
NumberInput,
|
||||||
StringInput,
|
StringInput,
|
||||||
BooleanInput,
|
BooleanInput,
|
||||||
EnumInput
|
EnumInput,
|
||||||
|
InputType
|
||||||
} from "../../../SmartUi/SmartUiComponent";
|
} from "../../../SmartUi/SmartUiComponent";
|
||||||
|
|
||||||
export interface CommonInputTypes {
|
export interface CommonInputTypes {
|
||||||
@ -29,11 +30,8 @@ export interface CommonInputTypes {
|
|||||||
choices?: EnumItem[];
|
choices?: EnumItem[];
|
||||||
defaultKey?: string;
|
defaultKey?: string;
|
||||||
inputType?: string;
|
inputType?: string;
|
||||||
}
|
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
|
||||||
|
onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
|
||||||
export enum DescriptorType {
|
|
||||||
ClassDescriptor,
|
|
||||||
PropertyDescriptor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
|
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
|
||||||
@ -53,13 +51,11 @@ const getValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T
|
|||||||
|
|
||||||
export const addPropertyToMap = (
|
export const addPropertyToMap = (
|
||||||
target: Object,
|
target: Object,
|
||||||
property: string,
|
propertyKey: string,
|
||||||
metadataKey: string,
|
metadataKey: string,
|
||||||
descriptorName: string,
|
descriptorName: string,
|
||||||
descriptorValue: any,
|
descriptorValue: any
|
||||||
descriptorType: DescriptorType
|
|
||||||
): void => {
|
): void => {
|
||||||
const propertyKey = property.toString();
|
|
||||||
const descriptorKey = descriptorName.toString() as keyof CommonInputTypes;
|
const descriptorKey = descriptorName.toString() as keyof CommonInputTypes;
|
||||||
let context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>;
|
let context = Reflect.getMetadata(metadataKey, target) as Map<String, CommonInputTypes>;
|
||||||
|
|
||||||
@ -67,12 +63,16 @@ export const addPropertyToMap = (
|
|||||||
context = new Map<String, CommonInputTypes>();
|
context = new Map<String, CommonInputTypes>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(context instanceof Map)) {
|
||||||
|
throw new Error("@SmartUi should be the first decorator for the class.");
|
||||||
|
}
|
||||||
|
|
||||||
let propertyObject = context.get(propertyKey);
|
let propertyObject = context.get(propertyKey);
|
||||||
if (!propertyObject) {
|
if (!propertyObject) {
|
||||||
propertyObject = { id: propertyKey };
|
propertyObject = { id: propertyKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getValue(descriptorKey, propertyObject)) {
|
if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type") {
|
||||||
throw new Error("duplicate descriptor");
|
throw new Error("duplicate descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +125,14 @@ 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)) {
|
||||||
|
throw new Error(
|
||||||
|
"@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let smartUiDescriptor = {
|
let smartUiDescriptor = {
|
||||||
|
onSubmit: root.onSubmit,
|
||||||
root: {
|
root: {
|
||||||
id: "root",
|
id: "root",
|
||||||
info: root.info,
|
info: root.info,
|
||||||
@ -187,25 +194,27 @@ const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInp
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getInput = (value: CommonInputTypes): AnyInput => {
|
const getInput = (value: CommonInputTypes): AnyInput => {
|
||||||
|
if (!value.label || !value.type || !value.dataFieldName) {
|
||||||
|
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) {
|
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");
|
||||||
}
|
}
|
||||||
return value as BooleanInput;
|
return value as BooleanInput;
|
||||||
case "enum":
|
default:
|
||||||
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 EnumInput;
|
||||||
default:
|
|
||||||
throw new Error("Unknown type");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,27 +6,37 @@ import {
|
|||||||
Step,
|
Step,
|
||||||
DefaultKey,
|
DefaultKey,
|
||||||
DefaultValue,
|
DefaultValue,
|
||||||
Type,
|
|
||||||
NumberInputType,
|
NumberInputType,
|
||||||
Choices,
|
Choices,
|
||||||
ParentOf,
|
ParentOf,
|
||||||
PropertyInfo
|
PropertyInfo,
|
||||||
|
OnChange,
|
||||||
|
TrueLabel,
|
||||||
|
FalseLabel,
|
||||||
|
Placeholder
|
||||||
} from "./PropertyDescriptors";
|
} from "./PropertyDescriptors";
|
||||||
import { Descriptor, EnumItem, Info } from "../../../SmartUi/SmartUiComponent";
|
import { Descriptor, EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
|
||||||
import { SmartUi, ClassInfo, SelfServeClass } from "./ClassDescriptors";
|
import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors";
|
||||||
|
|
||||||
|
enum Sizes {
|
||||||
|
OneCore4Gb = "OneCore4Gb",
|
||||||
|
TwoCore8Gb = "TwoCore8Gb",
|
||||||
|
FourCore16Gb = "FourCore16Gb"
|
||||||
|
}
|
||||||
|
|
||||||
@SmartUi()
|
@SmartUi()
|
||||||
@SelfServeClass()
|
|
||||||
@ClassInfo(SqlX.sqlXInfo)
|
@ClassInfo(SqlX.sqlXInfo)
|
||||||
|
@OnSubmit(SqlX.onSubmit)
|
||||||
|
@SelfServeClass()
|
||||||
export class SqlX {
|
export class SqlX {
|
||||||
@PropertyInfo(SqlX.instanceSizeInfo)
|
@PropertyInfo(SqlX.instanceSizeInfo)
|
||||||
@Label("Instance Size")
|
@Label("Instance Size")
|
||||||
@DataFieldName("instanceSize")
|
@DataFieldName("instanceSize")
|
||||||
@Choices(SqlX.instanceSizeOptions)
|
@Choices(SqlX.instanceSizeOptions)
|
||||||
@DefaultKey("1Core4Gb")
|
@DefaultKey(Sizes.OneCore4Gb)
|
||||||
@Type("enum")
|
static instanceSize: EnumItem;
|
||||||
static instanceSize: any;
|
|
||||||
|
|
||||||
|
@OnChange(SqlX.onInstanceCountChange)
|
||||||
@Label("Instance Count")
|
@Label("Instance Count")
|
||||||
@DataFieldName("instanceCount")
|
@DataFieldName("instanceCount")
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@ -34,14 +44,25 @@ export class SqlX {
|
|||||||
@Step(1)
|
@Step(1)
|
||||||
@DefaultValue(1)
|
@DefaultValue(1)
|
||||||
@NumberInputType("slider")
|
@NumberInputType("slider")
|
||||||
@Type("number")
|
@ParentOf(["instanceSize", "instanceName", "isAllowed"])
|
||||||
@ParentOf(["instanceSize"])
|
static instanceCount: number;
|
||||||
static instanceCount: any;
|
|
||||||
|
@Label("Feature Allowed")
|
||||||
|
@DataFieldName("isAllowed")
|
||||||
|
@DefaultValue(false)
|
||||||
|
@TrueLabel("allowed")
|
||||||
|
@FalseLabel("not allowed")
|
||||||
|
static isAllowed: boolean;
|
||||||
|
|
||||||
|
@Label("Instance Name")
|
||||||
|
@DataFieldName("instanceName")
|
||||||
|
@Placeholder("instance name")
|
||||||
|
static instanceName: string;
|
||||||
|
|
||||||
static instanceSizeOptions: EnumItem[] = [
|
static instanceSizeOptions: EnumItem[] = [
|
||||||
{ label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" },
|
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
|
||||||
{ label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" },
|
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
|
||||||
{ label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" }
|
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
|
||||||
];
|
];
|
||||||
|
|
||||||
static sqlXInfo: Info = {
|
static sqlXInfo: Info = {
|
||||||
@ -52,6 +73,30 @@ export class SqlX {
|
|||||||
message: "instance size will be updated in the future"
|
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")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
public static toSmartUiDescriptor = (): Descriptor => {
|
public static toSmartUiDescriptor = (): Descriptor => {
|
||||||
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
|
return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor;
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
|
|||||||
|
|
||||||
describe("SmartUiComponent", () => {
|
describe("SmartUiComponent", () => {
|
||||||
const exampleData: Descriptor = {
|
const exampleData: Descriptor = {
|
||||||
|
onSubmit: async (currentValues: Map<string, InputType>) => {},
|
||||||
root: {
|
root: {
|
||||||
id: "root",
|
id: "root",
|
||||||
info: {
|
info: {
|
||||||
@ -24,7 +25,8 @@ describe("SmartUiComponent", () => {
|
|||||||
max: 500,
|
max: 500,
|
||||||
step: 10,
|
step: 10,
|
||||||
defaultValue: 400,
|
defaultValue: 400,
|
||||||
inputType: "spin"
|
inputType: "spin",
|
||||||
|
onChange: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,7 +39,8 @@ describe("SmartUiComponent", () => {
|
|||||||
max: 500,
|
max: 500,
|
||||||
step: 10,
|
step: 10,
|
||||||
defaultValue: 400,
|
defaultValue: 400,
|
||||||
inputType: "slider"
|
inputType: "slider",
|
||||||
|
onChange: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -45,7 +48,8 @@ describe("SmartUiComponent", () => {
|
|||||||
input: {
|
input: {
|
||||||
label: "Container id",
|
label: "Container id",
|
||||||
dataFieldName: "containerId",
|
dataFieldName: "containerId",
|
||||||
type: "string"
|
type: "string",
|
||||||
|
onChange: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -56,7 +60,8 @@ describe("SmartUiComponent", () => {
|
|||||||
falseLabel: "Disabled",
|
falseLabel: "Disabled",
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
dataFieldName: "analyticalStore",
|
dataFieldName: "analyticalStore",
|
||||||
type: "boolean"
|
type: "boolean",
|
||||||
|
onChange: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -70,6 +75,7 @@ describe("SmartUiComponent", () => {
|
|||||||
{ label: "Database 2", key: "db2", value: "database2" },
|
{ label: "Database 2", key: "db2", value: "database2" },
|
||||||
{ label: "Database 3", key: "db3", value: "database3" }
|
{ label: "Database 3", key: "db3", value: "database3" }
|
||||||
],
|
],
|
||||||
|
onChange: undefined,
|
||||||
defaultKey: "db2"
|
defaultKey: "db2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,12 +83,8 @@ describe("SmartUiComponent", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exampleCallbacks = (newValues: Map<string, InputType>): void => {
|
|
||||||
console.log("New values:", newValues);
|
|
||||||
};
|
|
||||||
|
|
||||||
it("should render", () => {
|
it("should render", () => {
|
||||||
const wrapper = shallow(<SmartUiComponent descriptor={exampleData} onChange={exampleCallbacks} />);
|
const wrapper = shallow(<SmartUiComponent descriptor={exampleData} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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 } from "office-ui-fabric-react";
|
import { Link, MessageBar, MessageBarType, PrimaryButton } from "office-ui-fabric-react";
|
||||||
|
|
||||||
import * as InputUtils from "./InputUtils";
|
import * as InputUtils from "./InputUtils";
|
||||||
import "./SmartUiComponent.less";
|
import "./SmartUiComponent.less";
|
||||||
@ -21,17 +21,18 @@ import "./SmartUiComponent.less";
|
|||||||
* - a descriptor of the UX.
|
* - a descriptor of the UX.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type InputTypeValue = "number" | "string" | "boolean" | "enum";
|
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 EnumItem = { label: string; key: string; value: any };
|
||||||
|
|
||||||
export type InputType = number | string | boolean | EnumItem;
|
export type InputType = Number | String | Boolean | EnumItem;
|
||||||
|
|
||||||
interface BaseInput {
|
interface BaseInput {
|
||||||
label: string;
|
label: string;
|
||||||
dataFieldName: string;
|
dataFieldName: string;
|
||||||
type: InputTypeValue;
|
type: InputTypeValue;
|
||||||
|
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +81,13 @@ export interface Node {
|
|||||||
|
|
||||||
export interface Descriptor {
|
export interface Descriptor {
|
||||||
root: Node;
|
root: Node;
|
||||||
|
onSubmit: (currentValues: Map<string, InputType>) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** Component implementation starts here ************************************* */
|
/************************** Component implementation starts here ************************************* */
|
||||||
|
|
||||||
export interface SmartUiComponentProps {
|
export interface SmartUiComponentProps {
|
||||||
descriptor: Descriptor;
|
descriptor: Descriptor;
|
||||||
onChange: (newValues: Map<string, InputType>) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SmartUiComponentState {
|
interface SmartUiComponentState {
|
||||||
@ -104,11 +105,37 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
constructor(props: SmartUiComponentProps) {
|
constructor(props: SmartUiComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
currentValues: new Map(),
|
currentValues: this.setDefaultValues(),
|
||||||
errors: new Map()
|
errors: new Map()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setDefaultValues = (): Map<string, InputType> => {
|
||||||
|
const defaults = new Map();
|
||||||
|
this.setDefaults(this.props.descriptor.root, defaults);
|
||||||
|
return 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 getDefault = (input: AnyInput): InputType => {
|
||||||
|
switch (input.type) {
|
||||||
|
case "String":
|
||||||
|
return (input as StringInput).defaultValue;
|
||||||
|
case "Number":
|
||||||
|
return (input as NumberInput).defaultValue;
|
||||||
|
case "Boolean":
|
||||||
|
return (input as BooleanInput).defaultValue;
|
||||||
|
default:
|
||||||
|
return (input as EnumInput).defaultKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private renderInfo(info: Info): JSX.Element {
|
private renderInfo(info: Info): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<MessageBar>
|
<MessageBar>
|
||||||
@ -122,10 +149,16 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onInputChange = (newValue: string | number | boolean, dataFieldName: string) => {
|
private onInputChange = (input: AnyInput, newValue: InputType) => {
|
||||||
const { currentValues } = this.state;
|
if (input.onChange) {
|
||||||
currentValues.set(dataFieldName, newValue);
|
const newValues = input.onChange(this.state.currentValues, newValue);
|
||||||
this.setState({ currentValues }, () => this.props.onChange(this.state.currentValues));
|
this.setState({ currentValues: newValues });
|
||||||
|
} else {
|
||||||
|
const dataFieldName = input.dataFieldName;
|
||||||
|
const { currentValues } = this.state;
|
||||||
|
currentValues.set(dataFieldName, newValue);
|
||||||
|
this.setState({ currentValues });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderStringInput(input: StringInput): JSX.Element {
|
private renderStringInput(input: StringInput): JSX.Element {
|
||||||
@ -138,7 +171,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
type="text"
|
type="text"
|
||||||
value={input.defaultValue}
|
value={input.defaultValue}
|
||||||
placeholder={input.placeholder}
|
placeholder={input.placeholder}
|
||||||
onChange={(_, newValue) => this.onInputChange(newValue, input.dataFieldName)}
|
onChange={(_, newValue) => this.onInputChange(input, newValue)}
|
||||||
styles={{
|
styles={{
|
||||||
subComponentStyles: {
|
subComponentStyles: {
|
||||||
label: {
|
label: {
|
||||||
@ -161,10 +194,11 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onValidate = (value: string, min: number, max: number, dataFieldName: string): string => {
|
private onValidate = (input: AnyInput, value: string, min: number, max: number): string => {
|
||||||
const newValue = InputUtils.onValidateValueChange(value, min, max);
|
const newValue = InputUtils.onValidateValueChange(value, min, max);
|
||||||
|
const dataFieldName = input.dataFieldName;
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.onInputChange(newValue, dataFieldName);
|
this.onInputChange(input, newValue);
|
||||||
this.clearError(dataFieldName);
|
this.clearError(dataFieldName);
|
||||||
return newValue.toString();
|
return newValue.toString();
|
||||||
} else {
|
} else {
|
||||||
@ -175,20 +209,22 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onIncrement = (value: string, step: number, max: number, dataFieldName: string): string => {
|
private onIncrement = (input: AnyInput, value: string, step: number, max: number): string => {
|
||||||
const newValue = InputUtils.onIncrementValue(value, step, max);
|
const newValue = InputUtils.onIncrementValue(value, step, max);
|
||||||
|
const dataFieldName = input.dataFieldName;
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.onInputChange(newValue, dataFieldName);
|
this.onInputChange(input, newValue);
|
||||||
this.clearError(dataFieldName);
|
this.clearError(dataFieldName);
|
||||||
return newValue.toString();
|
return newValue.toString();
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onDecrement = (value: string, step: number, min: number, dataFieldName: string): string => {
|
private onDecrement = (input: AnyInput, value: string, step: number, min: number): string => {
|
||||||
const newValue = InputUtils.onDecrementValue(value, step, min);
|
const newValue = InputUtils.onDecrementValue(value, step, min);
|
||||||
|
const dataFieldName = input.dataFieldName;
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.onInputChange(newValue, dataFieldName);
|
this.onInputChange(input, newValue);
|
||||||
this.clearError(dataFieldName);
|
this.clearError(dataFieldName);
|
||||||
return newValue.toString();
|
return newValue.toString();
|
||||||
}
|
}
|
||||||
@ -205,9 +241,9 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
<SpinButton
|
<SpinButton
|
||||||
{...props}
|
{...props}
|
||||||
defaultValue={defaultValue.toString()}
|
defaultValue={defaultValue.toString()}
|
||||||
onValidate={newValue => this.onValidate(newValue, min, max, dataFieldName)}
|
onValidate={newValue => this.onValidate(input, newValue, min, max)}
|
||||||
onIncrement={newValue => this.onIncrement(newValue, step, max, dataFieldName)}
|
onIncrement={newValue => this.onIncrement(input, newValue, step, max)}
|
||||||
onDecrement={newValue => this.onDecrement(newValue, step, min, dataFieldName)}
|
onDecrement={newValue => this.onDecrement(input, newValue, step, min)}
|
||||||
labelPosition={Position.top}
|
labelPosition={Position.top}
|
||||||
styles={{
|
styles={{
|
||||||
label: {
|
label: {
|
||||||
@ -228,7 +264,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
// valueFormat={}
|
// valueFormat={}
|
||||||
{...props}
|
{...props}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
onChange={newValue => this.onInputChange(newValue, dataFieldName)}
|
onChange={newValue => this.onInputChange(input, newValue)}
|
||||||
styles={{
|
styles={{
|
||||||
titleLabel: {
|
titleLabel: {
|
||||||
...SmartUiComponent.labelStyle,
|
...SmartUiComponent.labelStyle,
|
||||||
@ -257,12 +293,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
{
|
{
|
||||||
label: input.falseLabel,
|
label: input.falseLabel,
|
||||||
key: "false",
|
key: "false",
|
||||||
onSelect: () => this.onInputChange(false, dataFieldName)
|
onSelect: () => this.onInputChange(input, false)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: input.trueLabel,
|
label: input.trueLabel,
|
||||||
key: "true",
|
key: "true",
|
||||||
onSelect: () => this.onInputChange(true, dataFieldName)
|
onSelect: () => this.onInputChange(input, true)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
selectedKey={
|
selectedKey={
|
||||||
@ -278,7 +314,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderEnumInput(input: EnumInput): JSX.Element {
|
private renderEnumInput(input: EnumInput): JSX.Element {
|
||||||
const { label, defaultKey, dataFieldName, choices, placeholder } = input;
|
const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={label}
|
label={label}
|
||||||
@ -287,7 +323,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
? (this.state.currentValues.get(dataFieldName) as string)
|
? (this.state.currentValues.get(dataFieldName) as string)
|
||||||
: defaultKey
|
: defaultKey
|
||||||
}
|
}
|
||||||
onChange={(_, item: IDropdownOption) => this.onInputChange(item.key.toString(), dataFieldName)}
|
onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
options={choices.map(c => ({
|
options={choices.map(c => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
@ -306,16 +342,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);
|
||||||
case "enum":
|
|
||||||
return this.renderEnumInput(input as EnumInput);
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown input type: ${input.type}`);
|
return this.renderEnumInput(input as EnumInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +366,17 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return <>{this.renderNode(this.props.descriptor.root)}</>;
|
const containerStackTokens: IStackTokens = { childrenGap: 20 };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack tokens={containerStackTokens}>
|
||||||
|
{this.renderNode(this.props.descriptor.root)}
|
||||||
|
<PrimaryButton
|
||||||
|
styles={{ root: { width: 100 } }}
|
||||||
|
text="submit"
|
||||||
|
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user