mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-08 03:57:31 +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 */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
export type ChoiceItem = { label: string; key: string; value: 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 {
|
export interface BaseInput {
|
||||||
label: (() => Promise<string>) | string;
|
label: (() => Promise<string>) | string;
|
||||||
@@ -34,15 +34,15 @@ export interface BaseInput {
|
|||||||
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?: (() => Promise<string>) | string;
|
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
|
* For now, this only supports integers
|
||||||
*/
|
*/
|
||||||
export interface NumberInput extends BaseInput {
|
export interface NumberInput extends BaseInput {
|
||||||
min?: (() => Promise<number>) | number;
|
min: (() => Promise<number>) | number;
|
||||||
max?: (() => Promise<number>) | number;
|
max: (() => Promise<number>) | number;
|
||||||
step: (() => Promise<number>) | number;
|
step: (() => Promise<number>) | number;
|
||||||
defaultValue: (() => Promise<number>) | number;
|
defaultValue: (() => Promise<number>) | number;
|
||||||
inputType: "spin" | "slider";
|
inputType: "spin" | "slider";
|
||||||
@@ -94,9 +94,13 @@ export interface SmartUiComponentProps {
|
|||||||
interface SmartUiComponentState {
|
interface SmartUiComponentState {
|
||||||
currentValues: Map<string, InputType>;
|
currentValues: Map<string, InputType>;
|
||||||
errors: Map<string, string>;
|
errors: Map<string, string>;
|
||||||
|
customInputIndex: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
|
export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
|
||||||
|
private customInputs : AnyInput[] = []
|
||||||
|
private shouldRenderCustomComponents = true
|
||||||
|
|
||||||
private static readonly labelStyle = {
|
private static readonly labelStyle = {
|
||||||
color: "#393939",
|
color: "#393939",
|
||||||
fontFamily: "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
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);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
currentValues: undefined,
|
currentValues: undefined,
|
||||||
errors: new Map()
|
errors: new Map(),
|
||||||
|
customInputIndex: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setDefaultValues();
|
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> => {
|
private setDefaultValues = async (): Promise<void> => {
|
||||||
const defaults = new Map<string, InputType>();
|
const defaults = new Map<string, InputType>();
|
||||||
await this.setDefaults(this.props.descriptor.root, defaults);
|
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) {
|
||||||
if (input.customElement instanceof Function) {
|
if (input.customElement instanceof Function) {
|
||||||
input.customElement = await (input.customElement as Function)();
|
this.customInputs.push(input)
|
||||||
}
|
}
|
||||||
return 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 {
|
private renderInput(input: AnyInput): JSX.Element {
|
||||||
if (input.customElement) {
|
if (input.customElement) {
|
||||||
return input.customElement as JSX.Element;
|
return this.renderCustomInput(input)
|
||||||
}
|
}
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case "string":
|
case "string":
|
||||||
@@ -450,11 +488,18 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
return this.state.currentValues && this.state.currentValues.size ? (
|
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)}
|
||||||
|
<Stack horizontal tokens={{childrenGap: 10}}>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
styles={{ root: { width: 100 } }}
|
styles={{ root: { width: 100 } }}
|
||||||
text="submit"
|
text="submit"
|
||||||
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)}
|
onClick={async () => await this.props.descriptor.onSubmit(this.state.currentValues)}
|
||||||
/>
|
/>
|
||||||
|
<PrimaryButton
|
||||||
|
styles={{ root: { width: 100 } }}
|
||||||
|
text="discard"
|
||||||
|
onClick={async () => this.setDefaultValues()}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Spinner size={SpinnerSize.large} />
|
<Spinner size={SpinnerSize.large} />
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const OnChange = (
|
|||||||
return addToMap("onChange", 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);
|
return addToMap("customElement", customElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,28 @@ import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { initializeConfiguration } from "../ConfigContext";
|
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";
|
import { SqlX } from "./SqlX/SqlX";
|
||||||
|
|
||||||
|
const getDescriptor = (selfServeType : SelfServeTypes) : Descriptor => {
|
||||||
|
switch (selfServeType) {
|
||||||
|
case SelfServeTypes.sqlx:
|
||||||
|
return SqlX.toSmartUiDescriptor()
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const render = async () => {
|
const render = async () => {
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
await initializeConfiguration();
|
await initializeConfiguration();
|
||||||
|
const selfServeType = getSelfServeType(window.location.search)
|
||||||
|
const smartUiDescriptor = getDescriptor(selfServeType)
|
||||||
|
|
||||||
const element = (
|
const element = smartUiDescriptor ?
|
||||||
<SmartUiComponent descriptor={SqlX.toSmartUiDescriptor()} />
|
<SmartUiComponent descriptor={smartUiDescriptor} /> :
|
||||||
);
|
<h1>Invalid self serve type!</h1>
|
||||||
|
|
||||||
ReactDOM.render(element, document.getElementById("selfServeContent"));
|
ReactDOM.render(element, document.getElementById("selfServeContent"));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
InputType
|
InputType
|
||||||
} from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
} from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||||
|
|
||||||
|
const SelfServeType = "selfServeType"
|
||||||
|
|
||||||
export class SelfServeBase {
|
export class SelfServeBase {
|
||||||
public static toSmartUiDescriptor(): Descriptor {
|
public static toSmartUiDescriptor(): Descriptor {
|
||||||
return Reflect.getMetadata(this.name, this) as Descriptor;
|
return Reflect.getMetadata(this.name, this) as Descriptor;
|
||||||
@@ -38,7 +40,7 @@ export interface CommonInputTypes {
|
|||||||
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>;
|
||||||
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]>(
|
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
|
||||||
@@ -154,11 +156,13 @@ const addToDescriptor = (
|
|||||||
const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => {
|
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++) {
|
while (i < children.length) {
|
||||||
if (children[i].id === key) {
|
if (children[i]?.id === key) {
|
||||||
const value = children[i];
|
const value = children[i];
|
||||||
delete children[i];
|
delete children[i];
|
||||||
return value;
|
return value;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -171,8 +175,8 @@ const getInput = (value: CommonInputTypes): AnyInput => {
|
|||||||
|
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
case "number":
|
case "number":
|
||||||
if (!value.step || !value.defaultValue || !value.inputType) {
|
if (!value.step || !value.defaultValue || !value.inputType || !value.min || !value.max) {
|
||||||
throw new Error("step, defaultValue and inputType are needed for number type");
|
throw new Error("step, min, miax, defaultValue and inputType are needed for number type");
|
||||||
}
|
}
|
||||||
return value as NumberInput;
|
return value as NumberInput;
|
||||||
case "string":
|
case "string":
|
||||||
@@ -189,3 +193,13 @@ const getInput = (value: CommonInputTypes): AnyInput => {
|
|||||||
return value as ChoiceInput;
|
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,
|
instanceSizeOptions,
|
||||||
onInstanceCountChange,
|
onInstanceCountChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
renderTextInput,
|
renderText,
|
||||||
Sizes,
|
Sizes,
|
||||||
sqlXInfo
|
sqlXInfo
|
||||||
} from "./SqlXApis";
|
} from "./SqlXApis";
|
||||||
@@ -34,9 +34,10 @@ import { ChoiceItem } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
|
|||||||
@ClassInfo(getPromise(sqlXInfo))
|
@ClassInfo(getPromise(sqlXInfo))
|
||||||
@OnSubmit(onSubmit)
|
@OnSubmit(onSubmit)
|
||||||
export class SqlX extends SelfServeBase {
|
export class SqlX extends SelfServeBase {
|
||||||
@Label(getPromise("About"))
|
|
||||||
@CustomElement(renderTextInput)
|
@Label(getPromise("Description"))
|
||||||
static about: string;
|
@CustomElement(renderText("This is the description part of SqlX"))
|
||||||
|
static description: string;
|
||||||
|
|
||||||
@PropertyInfo(getPromise(instanceSizeInfo))
|
@PropertyInfo(getPromise(instanceSizeInfo))
|
||||||
@Label(getPromise("Instance Size"))
|
@Label(getPromise("Instance Size"))
|
||||||
@@ -44,15 +45,9 @@ export class SqlX extends SelfServeBase {
|
|||||||
@DefaultKey(getPromise(Sizes.OneCore4Gb))
|
@DefaultKey(getPromise(Sizes.OneCore4Gb))
|
||||||
static instanceSize: ChoiceItem;
|
static instanceSize: ChoiceItem;
|
||||||
|
|
||||||
@OnChange(onInstanceCountChange)
|
@Label(getPromise("About"))
|
||||||
@Label(getPromise("Instance Count"))
|
@CustomElement(renderText("This is the about part of SqlX"))
|
||||||
@Min(getPromise(0))
|
static about: string;
|
||||||
@Max(getPromise(5))
|
|
||||||
@Step(getPromise(1))
|
|
||||||
@DefaultNumberValue(getPromise(1))
|
|
||||||
@NumberInputType("slider")
|
|
||||||
@ParentOf(["instanceSize", "instanceName", "isAllowed"])
|
|
||||||
static instanceCount: number;
|
|
||||||
|
|
||||||
@Label("Feature Allowed")
|
@Label("Feature Allowed")
|
||||||
@DefaultBooleanValue(false)
|
@DefaultBooleanValue(false)
|
||||||
@@ -63,4 +58,14 @@ export class SqlX extends SelfServeBase {
|
|||||||
@Label("Instance Name")
|
@Label("Instance Name")
|
||||||
@Placeholder("instance name")
|
@Placeholder("instance name")
|
||||||
static instanceName: string;
|
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 { Text } from "office-ui-fabric-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
|
import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||||
|
import { TextComponent } from "./TextComponent";
|
||||||
|
|
||||||
export enum Sizes {
|
export enum Sizes {
|
||||||
OneCore4Gb = "OneCore4Gb",
|
OneCore4Gb = "OneCore4Gb",
|
||||||
@@ -28,7 +29,7 @@ export const onInstanceCountChange = (
|
|||||||
): Map<string, InputType> => {
|
): Map<string, InputType> => {
|
||||||
currentState.set("instanceCount", newValue);
|
currentState.set("instanceCount", newValue);
|
||||||
if ((newValue as number) === 1) {
|
if ((newValue as number) === 1) {
|
||||||
currentState.set("isAllowed", false);
|
currentState.set("instanceSize", Sizes.OneCore4Gb);
|
||||||
}
|
}
|
||||||
return currentState;
|
return currentState;
|
||||||
};
|
};
|
||||||
@@ -60,6 +61,10 @@ export const getPromise = <T extends number | string | boolean | ChoiceItem[] |
|
|||||||
return f;
|
return f;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderTextInput = async (): Promise<JSX.Element> => {
|
export const renderText = (text: string) : (currentValues: Map<string, InputType>) => Promise<JSX.Element> => {
|
||||||
return <Text>SqlX is a new feature of Cosmos DB.</Text>;
|
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