mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-27 04:41:48 +00:00
Added custom renderer as async type
This commit is contained in:
@@ -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} />
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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"));
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
15
src/SelfServe/SqlX/TextComponent.tsx
Normal file
15
src/SelfServe/SqlX/TextComponent.tsx
Normal 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>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user