Added custom renderer as async type

This commit is contained in:
Srinath Narayanan
2020-12-10 12:50:04 -08:00
parent c97eb6018b
commit 95fc75cb23
7 changed files with 130 additions and 34 deletions

View File

@@ -26,7 +26,7 @@ export type InputTypeValue = "number" | "string" | "boolean" | "object";
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export type ChoiceItem = { label: string; key: string; value: any };
export type InputType = Number | String | Boolean | ChoiceItem;
export type InputType = Number | String | Boolean | ChoiceItem | JSX.Element;
export interface BaseInput {
label: (() => Promise<string>) | string;
@@ -34,15 +34,15 @@ export interface BaseInput {
type: InputTypeValue;
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
placeholder?: (() => Promise<string>) | string;
customElement?: (() => Promise<JSX.Element>) | JSX.Element;
customElement?: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element;
}
/**
* For now, this only supports integers
*/
export interface NumberInput extends BaseInput {
min?: (() => Promise<number>) | number;
max?: (() => Promise<number>) | number;
min: (() => Promise<number>) | number;
max: (() => Promise<number>) | number;
step: (() => Promise<number>) | number;
defaultValue: (() => Promise<number>) | number;
inputType: "spin" | "slider";
@@ -94,9 +94,13 @@ export interface SmartUiComponentProps {
interface SmartUiComponentState {
currentValues: Map<string, InputType>;
errors: Map<string, string>;
customInputIndex: number
}
export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
private customInputs : AnyInput[] = []
private shouldRenderCustomComponents = true
private static readonly labelStyle = {
color: "#393939",
fontFamily: "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
@@ -107,12 +111,36 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
super(props);
this.state = {
currentValues: undefined,
errors: new Map()
errors: new Map(),
customInputIndex: 0
};
this.setDefaultValues();
}
componentDidUpdate = async () : Promise<void> => {
if (!this.customInputs.length) {
return
}
if (!this.shouldRenderCustomComponents) {
this.shouldRenderCustomComponents = true;
return;
}
if (this.state.customInputIndex === this.customInputs.length) {
this.shouldRenderCustomComponents = false
this.setState({customInputIndex: 0})
return
}
const input = this.customInputs[this.state.customInputIndex]
const dataFieldName = input.dataFieldName;
const element = await (input.customElement as Function)(this.state.currentValues)
const { currentValues } = this.state;
currentValues.set(dataFieldName, element);
this.setState({ currentValues: currentValues, customInputIndex: this.state.customInputIndex + 1});
}
private setDefaultValues = async (): Promise<void> => {
const defaults = new Map<string, InputType>();
await this.setDefaults(this.props.descriptor.root, defaults);
@@ -143,7 +171,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
if (input.customElement) {
if (input.customElement instanceof Function) {
input.customElement = await (input.customElement as Function)();
this.customInputs.push(input)
}
return input;
}
@@ -417,9 +445,19 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
);
}
private renderCustomInput(input: AnyInput) : JSX.Element {
if (input.customElement instanceof Function) {
const dataFieldName = input.dataFieldName;
const element = this.state.currentValues.get(dataFieldName) as JSX.Element
return element ? element : <></>
} else {
return input.customElement as JSX.Element;
}
}
private renderInput(input: AnyInput): JSX.Element {
if (input.customElement) {
return input.customElement as JSX.Element;
return this.renderCustomInput(input)
}
switch (input.type) {
case "string":
@@ -450,11 +488,18 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return this.state.currentValues && this.state.currentValues.size ? (
<Stack tokens={containerStackTokens}>
{this.renderNode(this.props.descriptor.root)}
<Stack horizontal tokens={{childrenGap: 10}}>
<PrimaryButton
styles={{ root: { width: 100 } }}
text="submit"
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)}
/>
<PrimaryButton
styles={{ root: { width: 100 } }}
text="discard"
onClick={async () => this.setDefaultValues()}
/>
</Stack>
</Stack>
) : (
<Spinner size={SpinnerSize.large} />

View File

@@ -22,7 +22,7 @@ export const OnChange = (
return addToMap("onChange", onChange);
};
export const CustomElement = (customElement: (() => Promise<JSX.Element>) | JSX.Element): PropertyDecorator => {
export const CustomElement = (customElement: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element): PropertyDecorator => {
return addToMap("customElement", customElement);
};

View File

@@ -3,16 +3,28 @@ import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import React from "react";
import * as ReactDOM from "react-dom";
import { initializeConfiguration } from "../ConfigContext";
import { SmartUiComponent } from "../Explorer/Controls/SmartUi/SmartUiComponent";
import { Descriptor, SmartUiComponent } from "../Explorer/Controls/SmartUi/SmartUiComponent";
import { getSelfServeType, SelfServeTypes } from "./SelfServeUtils";
import { SqlX } from "./SqlX/SqlX";
const getDescriptor = (selfServeType : SelfServeTypes) : Descriptor => {
switch (selfServeType) {
case SelfServeTypes.sqlx:
return SqlX.toSmartUiDescriptor()
default:
return undefined;
}
}
const render = async () => {
initializeIcons();
await initializeConfiguration();
const selfServeType = getSelfServeType(window.location.search)
const smartUiDescriptor = getDescriptor(selfServeType)
const element = (
<SmartUiComponent descriptor={SqlX.toSmartUiDescriptor()} />
);
const element = smartUiDescriptor ?
<SmartUiComponent descriptor={smartUiDescriptor} /> :
<h1>Invalid self serve type!</h1>
ReactDOM.render(element, document.getElementById("selfServeContent"));
};

View File

@@ -13,6 +13,8 @@ import {
InputType
} from "../Explorer/Controls/SmartUi/SmartUiComponent";
const SelfServeType = "selfServeType"
export class SelfServeBase {
public static toSmartUiDescriptor(): Descriptor {
return Reflect.getMetadata(this.name, this) as Descriptor;
@@ -38,7 +40,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;
customElement?: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element;
}
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
@@ -154,11 +156,13 @@ const addToDescriptor = (
const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => {
let i = 0;
const children = smartUiDescriptor.root.children;
for (; i < children.length; i++) {
if (children[i].id === key) {
while (i < children.length) {
if (children[i]?.id === key) {
const value = children[i];
delete children[i];
return value;
} else {
i++;
}
}
return undefined;
@@ -171,8 +175,8 @@ const getInput = (value: CommonInputTypes): AnyInput => {
switch (value.type) {
case "number":
if (!value.step || !value.defaultValue || !value.inputType) {
throw new Error("step, defaultValue and inputType are needed for number type");
if (!value.step || !value.defaultValue || !value.inputType || !value.min || !value.max) {
throw new Error("step, min, miax, defaultValue and inputType are needed for number type");
}
return value as NumberInput;
case "string":
@@ -189,3 +193,13 @@ const getInput = (value: CommonInputTypes): AnyInput => {
return value as ChoiceInput;
}
};
export enum SelfServeTypes {
sqlx="sqlx"
}
export const getSelfServeType = (search: string): SelfServeTypes => {
const params = new URLSearchParams(search);
const selfServeTypeParam = params.get(SelfServeType)?.toLowerCase()
return SelfServeTypes[selfServeTypeParam as keyof typeof SelfServeTypes]
}

View File

@@ -23,7 +23,7 @@ import {
instanceSizeOptions,
onInstanceCountChange,
onSubmit,
renderTextInput,
renderText,
Sizes,
sqlXInfo
} from "./SqlXApis";
@@ -34,9 +34,10 @@ import { ChoiceItem } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
@ClassInfo(getPromise(sqlXInfo))
@OnSubmit(onSubmit)
export class SqlX extends SelfServeBase {
@Label(getPromise("About"))
@CustomElement(renderTextInput)
static about: string;
@Label(getPromise("Description"))
@CustomElement(renderText("This is the description part of SqlX"))
static description: string;
@PropertyInfo(getPromise(instanceSizeInfo))
@Label(getPromise("Instance Size"))
@@ -44,15 +45,9 @@ export class SqlX extends SelfServeBase {
@DefaultKey(getPromise(Sizes.OneCore4Gb))
static instanceSize: ChoiceItem;
@OnChange(onInstanceCountChange)
@Label(getPromise("Instance Count"))
@Min(getPromise(0))
@Max(getPromise(5))
@Step(getPromise(1))
@DefaultNumberValue(getPromise(1))
@NumberInputType("slider")
@ParentOf(["instanceSize", "instanceName", "isAllowed"])
static instanceCount: number;
@Label(getPromise("About"))
@CustomElement(renderText("This is the about part of SqlX"))
static about: string;
@Label("Feature Allowed")
@DefaultBooleanValue(false)
@@ -63,4 +58,14 @@ export class SqlX extends SelfServeBase {
@Label("Instance Name")
@Placeholder("instance name")
static instanceName: string;
@OnChange(onInstanceCountChange)
@Label(getPromise("Instance Count"))
@Min(getPromise(0))
@Max(getPromise(5))
@Step(getPromise(1))
@DefaultNumberValue(getPromise(1))
@NumberInputType("slider")
@ParentOf(["instanceSize", "about", "instanceName", "isAllowed", ])
static instanceCount: number;
}

View File

@@ -1,6 +1,7 @@
import { Text } from "office-ui-fabric-react";
import React from "react";
import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
import { TextComponent } from "./TextComponent";
export enum Sizes {
OneCore4Gb = "OneCore4Gb",
@@ -28,7 +29,7 @@ export const onInstanceCountChange = (
): Map<string, InputType> => {
currentState.set("instanceCount", newValue);
if ((newValue as number) === 1) {
currentState.set("isAllowed", false);
currentState.set("instanceSize", Sizes.OneCore4Gb);
}
return currentState;
};
@@ -60,6 +61,10 @@ export const getPromise = <T extends number | string | boolean | ChoiceItem[] |
return f;
};
export const renderTextInput = async (): Promise<JSX.Element> => {
return <Text>SqlX is a new feature of Cosmos DB.</Text>;
};
export const renderText = (text: string) : (currentValues: Map<string, InputType>) => Promise<JSX.Element> => {
const f = async (currentValues: Map<string, InputType>): Promise<JSX.Element> => {
//return <Text>SqlX is a new feature of Cosmos DB.</Text>;
return <TextComponent text={text} currentValues={currentValues}/>
};
return f
}

View File

@@ -0,0 +1,15 @@
import React from "react";
import { Text } from "office-ui-fabric-react";
import { InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
interface TextComponentProps {
text: string;
currentValues: Map<string, InputType>
}
export class TextComponent extends React.Component<TextComponentProps> {
public render() {
return <Text>{this.props.text}, instanceCount: {this.props.currentValues?.get("instanceCount")}</Text>
}
}