mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 12:21:23 +00:00
Added comments for Example
This commit is contained in:
@@ -396,7 +396,7 @@ export interface DataExplorerInputsFrame {
|
||||
isAuthWithresourceToken?: boolean;
|
||||
defaultCollectionThroughput?: CollectionCreationDefaults;
|
||||
flights?: readonly string[];
|
||||
selfServeType?: SelfServeTypes
|
||||
selfServeType?: SelfServeTypes;
|
||||
}
|
||||
|
||||
export interface CollectionCreationDefaults {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
|
||||
import { SmartUiComponent, Descriptor } from "./SmartUiComponent";
|
||||
|
||||
describe("SmartUiComponent", () => {
|
||||
const exampleData: Descriptor = {
|
||||
onSubmit: async () => {},
|
||||
onSubmit: async () => {
|
||||
return;
|
||||
},
|
||||
initialize: async () => {
|
||||
return undefined;
|
||||
},
|
||||
root: {
|
||||
id: "root",
|
||||
info: {
|
||||
@@ -25,7 +30,7 @@ describe("SmartUiComponent", () => {
|
||||
max: 500,
|
||||
step: 10,
|
||||
defaultValue: 400,
|
||||
inputType: "spin",
|
||||
inputType: "spinner",
|
||||
onChange: undefined
|
||||
}
|
||||
},
|
||||
@@ -71,9 +76,9 @@ describe("SmartUiComponent", () => {
|
||||
dataFieldName: "database",
|
||||
type: "object",
|
||||
choices: [
|
||||
{ label: "Database 1", key: "db1", value: "database1" },
|
||||
{ label: "Database 2", key: "db2", value: "database2" },
|
||||
{ label: "Database 3", key: "db3", value: "database3" }
|
||||
{ label: "Database 1", key: "db1" },
|
||||
{ label: "Database 2", key: "db2" },
|
||||
{ label: "Database 3", key: "db3" }
|
||||
],
|
||||
onChange: undefined,
|
||||
defaultKey: "db2"
|
||||
|
||||
@@ -9,10 +9,8 @@ import { InputType } from "../../Tables/Constants";
|
||||
import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent";
|
||||
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
|
||||
import { Link, MessageBar, MessageBarType, PrimaryButton, Spinner, SpinnerSize } from "office-ui-fabric-react";
|
||||
|
||||
import * as InputUtils from "./InputUtils";
|
||||
import "./SmartUiComponent.less";
|
||||
import { Widget } from "@phosphor/widgets";
|
||||
|
||||
/**
|
||||
* Generic UX renderer
|
||||
@@ -24,10 +22,12 @@ import { Widget } from "@phosphor/widgets";
|
||||
|
||||
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 NumberInputType = "spinner" | "slider";
|
||||
|
||||
export type InputType = Number | String | Boolean | ChoiceItem | JSX.Element;
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
export type ChoiceItem = { label: string; key: string };
|
||||
|
||||
export type InputType = number | string | boolean | ChoiceItem | JSX.Element;
|
||||
|
||||
export interface BaseInput {
|
||||
label: (() => Promise<string>) | string;
|
||||
@@ -45,23 +45,23 @@ export interface NumberInput extends BaseInput {
|
||||
min: (() => Promise<number>) | number;
|
||||
max: (() => Promise<number>) | number;
|
||||
step: (() => Promise<number>) | number;
|
||||
defaultValue?: (() => Promise<number>) | number;
|
||||
inputType: "spin" | "slider";
|
||||
defaultValue?: number;
|
||||
inputType: NumberInputType;
|
||||
}
|
||||
|
||||
export interface BooleanInput extends BaseInput {
|
||||
trueLabel: (() => Promise<string>) | string;
|
||||
falseLabel: (() => Promise<string>) | string;
|
||||
defaultValue?: (() => Promise<boolean>) | boolean;
|
||||
defaultValue?: boolean;
|
||||
}
|
||||
|
||||
export interface StringInput extends BaseInput {
|
||||
defaultValue?: (() => Promise<string>) | string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export interface ChoiceInput extends BaseInput {
|
||||
choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
||||
defaultKey?: (() => Promise<string>) | string;
|
||||
defaultKey?: string;
|
||||
}
|
||||
|
||||
export interface Info {
|
||||
@@ -83,7 +83,7 @@ export interface Node {
|
||||
|
||||
export interface Descriptor {
|
||||
root: Node;
|
||||
initialize?: () => Promise<Map<string, InputType>>;
|
||||
initialize: () => Promise<Map<string, InputType>>;
|
||||
onSubmit: (currentValues: Map<string, InputType>) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -95,13 +95,15 @@ export interface SmartUiComponentProps {
|
||||
|
||||
interface SmartUiComponentState {
|
||||
currentValues: Map<string, InputType>;
|
||||
baselineValues: Map<string, InputType>;
|
||||
errors: Map<string, string>;
|
||||
customInputIndex: number
|
||||
customInputIndex: number;
|
||||
isRefreshing: boolean;
|
||||
}
|
||||
|
||||
export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
|
||||
private customInputs : AnyInput[] = []
|
||||
private shouldRenderCustomComponents = true
|
||||
private customInputs: AnyInput[] = [];
|
||||
private shouldRenderCustomComponents = true;
|
||||
|
||||
private static readonly labelStyle = {
|
||||
color: "#393939",
|
||||
@@ -112,17 +114,19 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
constructor(props: SmartUiComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentValues: undefined,
|
||||
baselineValues: new Map(),
|
||||
currentValues: new Map(),
|
||||
errors: new Map(),
|
||||
customInputIndex: 0
|
||||
customInputIndex: 0,
|
||||
isRefreshing: false
|
||||
};
|
||||
|
||||
this.setDefaultValues();
|
||||
}
|
||||
|
||||
componentDidUpdate = async () : Promise<void> => {
|
||||
componentDidUpdate = async (): Promise<void> => {
|
||||
if (!this.customInputs.length) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!this.shouldRenderCustomComponents) {
|
||||
this.shouldRenderCustomComponents = true;
|
||||
@@ -130,43 +134,56 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
}
|
||||
|
||||
if (this.state.customInputIndex === this.customInputs.length) {
|
||||
this.shouldRenderCustomComponents = false
|
||||
this.setState({customInputIndex: 0})
|
||||
return
|
||||
this.shouldRenderCustomComponents = false;
|
||||
this.setState({ customInputIndex: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const input = this.customInputs[this.state.customInputIndex]
|
||||
const input = this.customInputs[this.state.customInputIndex];
|
||||
const dataFieldName = input.dataFieldName;
|
||||
const element = await (input.customElement as Function)(this.state.currentValues)
|
||||
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> => {
|
||||
let defaults = new Map<string, InputType>()
|
||||
|
||||
if (this.props.descriptor.initialize) {
|
||||
defaults = await this.props.descriptor.initialize()
|
||||
}
|
||||
|
||||
await this.setDefaults(this.props.descriptor.root, defaults);
|
||||
this.setState({ currentValues: defaults });
|
||||
this.setState({ currentValues: currentValues, customInputIndex: this.state.customInputIndex + 1 });
|
||||
};
|
||||
|
||||
private setDefaults = async (currentNode: Node, defaults: Map<string, InputType>): Promise<void> => {
|
||||
private setDefaultValues = async (): Promise<void> => {
|
||||
this.setState({ isRefreshing: true });
|
||||
await this.setDefaults(this.props.descriptor.root);
|
||||
this.setState({ isRefreshing: false });
|
||||
await this.initialize();
|
||||
};
|
||||
|
||||
private initialize = async (): Promise<void> => {
|
||||
this.setState({ isRefreshing: true });
|
||||
let { currentValues, baselineValues } = this.state;
|
||||
const initialValues = await this.props.descriptor.initialize();
|
||||
for (const key of initialValues.keys()) {
|
||||
currentValues = currentValues.set(key, initialValues.get(key));
|
||||
baselineValues = baselineValues.set(key, initialValues.get(key));
|
||||
}
|
||||
this.setState({ currentValues: currentValues, baselineValues: baselineValues, isRefreshing: false });
|
||||
};
|
||||
|
||||
private discard = (): void => {
|
||||
let { currentValues } = this.state;
|
||||
const { baselineValues } = this.state;
|
||||
for (const key of baselineValues.keys()) {
|
||||
currentValues = currentValues.set(key, baselineValues.get(key));
|
||||
}
|
||||
this.setState({ currentValues: currentValues });
|
||||
};
|
||||
|
||||
private setDefaults = async (currentNode: Node): 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);
|
||||
if (!defaults.get(currentNode.input.dataFieldName)) {
|
||||
defaults.set(currentNode.input.dataFieldName, this.getDefaultValue(currentNode.input));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child, defaults)));
|
||||
await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child)));
|
||||
};
|
||||
|
||||
private getModifiedInput = async (input: AnyInput): Promise<AnyInput> => {
|
||||
@@ -180,23 +197,17 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
|
||||
if (input.customElement) {
|
||||
if (input.customElement instanceof Function) {
|
||||
this.customInputs.push(input)
|
||||
this.customInputs.push(input);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
switch (input.type) {
|
||||
case "string":
|
||||
const stringInput = input as StringInput;
|
||||
if (stringInput.defaultValue instanceof Function) {
|
||||
stringInput.defaultValue = await (stringInput.defaultValue as Function)();
|
||||
}
|
||||
return stringInput;
|
||||
case "number":
|
||||
case "string": {
|
||||
return input as 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)();
|
||||
}
|
||||
@@ -207,11 +218,9 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
numberInput.step = await (numberInput.step as Function)();
|
||||
}
|
||||
return numberInput;
|
||||
case "boolean":
|
||||
}
|
||||
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)();
|
||||
}
|
||||
@@ -219,29 +228,32 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
booleanInput.falseLabel = await (booleanInput.falseLabel as Function)();
|
||||
}
|
||||
return booleanInput;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
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":
|
||||
const stringInput = input as StringInput
|
||||
case "string": {
|
||||
const stringInput = input as StringInput;
|
||||
return stringInput.defaultValue ? (stringInput.defaultValue as string) : "";
|
||||
case "number":
|
||||
}
|
||||
case "number": {
|
||||
return (input as NumberInput).defaultValue as number;
|
||||
case "boolean":
|
||||
}
|
||||
case "boolean": {
|
||||
return (input as BooleanInput).defaultValue as boolean;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
return (input as ChoiceInput).defaultKey as string;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -271,7 +283,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
};
|
||||
|
||||
private renderStringInput(input: StringInput): JSX.Element {
|
||||
const value = this.state.currentValues.get(input.dataFieldName) as string
|
||||
const value = this.state.currentValues.get(input.dataFieldName) as string;
|
||||
return (
|
||||
<div className="stringInputContainer">
|
||||
<div>
|
||||
@@ -342,7 +354,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
};
|
||||
|
||||
private renderNumberInput(input: NumberInput): JSX.Element {
|
||||
const { label, min, max, defaultValue, dataFieldName, step } = input;
|
||||
const { label, min, max, dataFieldName, step } = input;
|
||||
const props = {
|
||||
label: label as string,
|
||||
min: min as number,
|
||||
@@ -351,13 +363,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
step: step as number
|
||||
};
|
||||
|
||||
const value = this.state.currentValues.get(dataFieldName) as number
|
||||
if (input.inputType === "spin") {
|
||||
const value = this.state.currentValues.get(dataFieldName) as number;
|
||||
if (input.inputType === "spinner") {
|
||||
return (
|
||||
<div>
|
||||
<SpinButton
|
||||
{...props}
|
||||
value={value.toString()}
|
||||
value={value?.toString()}
|
||||
onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)}
|
||||
onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)}
|
||||
onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)}
|
||||
@@ -377,8 +389,6 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
} else if (input.inputType === "slider") {
|
||||
return (
|
||||
<Slider
|
||||
// showValue={true}
|
||||
// valueFormat={}
|
||||
{...props}
|
||||
value={value}
|
||||
onChange={newValue => this.onInputChange(input, newValue)}
|
||||
@@ -444,7 +454,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
placeholder={placeholder as string}
|
||||
options={(choices as ChoiceItem[]).map(c => ({
|
||||
key: c.key,
|
||||
text: c.value
|
||||
text: c.label
|
||||
}))}
|
||||
styles={{
|
||||
label: {
|
||||
@@ -457,11 +467,11 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
);
|
||||
}
|
||||
|
||||
private renderCustomInput(input: AnyInput) : JSX.Element {
|
||||
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 : <></>
|
||||
const element = this.state.currentValues.get(dataFieldName) as JSX.Element;
|
||||
return element ? element : <></>;
|
||||
} else {
|
||||
return input.customElement as JSX.Element;
|
||||
}
|
||||
@@ -469,7 +479,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
|
||||
private renderInput(input: AnyInput): JSX.Element {
|
||||
if (input.customElement) {
|
||||
return this.renderCustomInput(input)
|
||||
return this.renderCustomInput(input);
|
||||
}
|
||||
switch (input.type) {
|
||||
case "string":
|
||||
@@ -484,12 +494,14 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
}
|
||||
|
||||
private renderNode(node: Node): JSX.Element {
|
||||
const containerStackTokens: IStackTokens = { childrenGap: 10 };
|
||||
const containerStackTokens: IStackTokens = { childrenGap: 15 };
|
||||
|
||||
return (
|
||||
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
|
||||
{node.info && this.renderInfo(node.info as Info)}
|
||||
{node.input && this.renderInput(node.input)}
|
||||
<Stack.Item>
|
||||
{node.info && this.renderInfo(node.info as Info)}
|
||||
{node.input && this.renderInput(node.input)}
|
||||
</Stack.Item>
|
||||
{node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)}
|
||||
</Stack>
|
||||
);
|
||||
@@ -497,27 +509,28 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
|
||||
render(): JSX.Element {
|
||||
const containerStackTokens: IStackTokens = { childrenGap: 20 };
|
||||
return this.state.currentValues && this.state.currentValues.size ? (
|
||||
<Stack tokens={containerStackTokens} styles={{root: {width: 400, padding: 10}}}>
|
||||
{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)
|
||||
this.setDefaultValues()
|
||||
}}
|
||||
/>
|
||||
<PrimaryButton
|
||||
styles={{ root: { width: 100 } }}
|
||||
text="discard"
|
||||
onClick={async () => await this.setDefaultValues()}
|
||||
/>
|
||||
return this.state.currentValues && this.state.currentValues.size && !this.state.isRefreshing ? (
|
||||
<div style={{ overflowX: "auto" }}>
|
||||
<Stack tokens={containerStackTokens} styles={{ root: { width: 400, padding: 10 } }}>
|
||||
{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);
|
||||
this.initialize();
|
||||
}}
|
||||
/>
|
||||
<PrimaryButton styles={{ root: { width: 100 } }} text="discard" onClick={() => this.discard()} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
) : (
|
||||
<Spinner size={SpinnerSize.large} styles={{root: {textAlign: "center", justifyContent: "center", width: "100%", height: "100%"}}}/>
|
||||
<Spinner
|
||||
size={SpinnerSize.large}
|
||||
styles={{ root: { textAlign: "center", justifyContent: "center", width: "100%", height: "100%" } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ export default class Explorer {
|
||||
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
||||
public isRefreshingExplorer: ko.Observable<boolean>;
|
||||
private resourceTree: ResourceTreeAdapter;
|
||||
private selfServeComponentAdapter: SelfServeComponentAdapter
|
||||
private selfServeComponentAdapter: SelfServeComponentAdapter;
|
||||
|
||||
// Resource Token
|
||||
public resourceTokenDatabaseId: ko.Observable<string>;
|
||||
@@ -260,7 +260,7 @@ export default class Explorer {
|
||||
|
||||
// React adapters
|
||||
private commandBarComponentAdapter: CommandBarComponentAdapter;
|
||||
private selfServeLoadingComponentAdapter : SelfServeLoadingComponentAdapter;
|
||||
private selfServeLoadingComponentAdapter: SelfServeLoadingComponentAdapter;
|
||||
private splashScreenAdapter: SplashScreenComponentAdapter;
|
||||
private notificationConsoleComponentAdapter: NotificationConsoleComponentAdapter;
|
||||
private dialogComponentAdapter: DialogComponentAdapter;
|
||||
@@ -1862,16 +1862,16 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public setSelfServeType(inputs: ViewModels.DataExplorerInputsFrame): void {
|
||||
const selfServeTypeForTest = inputs.features[Constants.Features.selfServeTypeForTest]
|
||||
const selfServeTypeForTest = inputs.features[Constants.Features.selfServeTypeForTest];
|
||||
if (selfServeTypeForTest) {
|
||||
const selfServeType = SelfServeTypes[selfServeTypeForTest?.toLowerCase() as keyof typeof SelfServeTypes]
|
||||
this.selfServeType(selfServeType ? selfServeType : SelfServeTypes.invalid)
|
||||
const selfServeType = SelfServeTypes[selfServeTypeForTest?.toLowerCase() as keyof typeof SelfServeTypes];
|
||||
this.selfServeType(selfServeType ? selfServeType : SelfServeTypes.invalid);
|
||||
} else if (inputs.selfServeType) {
|
||||
this.selfServeType(inputs.selfServeType)
|
||||
this.selfServeType(inputs.selfServeType);
|
||||
} else {
|
||||
this.selfServeType(SelfServeTypes.none)
|
||||
this._setLoadingStatusText("Connecting...", "Welcome to Azure Cosmos DB")
|
||||
this._setConnectingImage()
|
||||
this.selfServeType(SelfServeTypes.none);
|
||||
this._setLoadingStatusText("Connecting...", "Welcome to Azure Cosmos DB");
|
||||
this._setConnectingImage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1894,7 +1894,7 @@ export default class Explorer {
|
||||
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
||||
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
||||
this.setFeatureFlagsFromFlights(inputs.flights);
|
||||
this.setSelfServeType(inputs)
|
||||
this.setSelfServeType(inputs);
|
||||
|
||||
if (!!inputs.dataExplorerVersion) {
|
||||
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
||||
@@ -3011,7 +3011,7 @@ export default class Explorer {
|
||||
|
||||
private _setConnectingImage() {
|
||||
const connectingImage = document.getElementById("explorerConnectingImage");
|
||||
connectingImage.innerHTML="<img src=\"../images/HdeConnectCosmosDB.svg\" >";
|
||||
connectingImage.innerHTML = '<img src="../images/HdeConnectCosmosDB.svg" >';
|
||||
}
|
||||
|
||||
private _openSetupNotebooksPaneForQuickstart(): void {
|
||||
|
||||
37
src/Main.tsx
37
src/Main.tsx
@@ -126,8 +126,11 @@ const App: React.FunctionComponent = () => {
|
||||
|
||||
return (
|
||||
<div className="flexContainer">
|
||||
<div id="divSelfServe" className="flexContainer" data-bind="visible: selfServeType() && selfServeType() !== 'none', react: selfServeComponentAdapter">
|
||||
</div>
|
||||
<div
|
||||
id="divSelfServe"
|
||||
className="flexContainer"
|
||||
data-bind="visible: selfServeType() && selfServeType() !== 'none', react: selfServeComponentAdapter"
|
||||
></div>
|
||||
<div id="divExplorer" className="flexContainer hideOverflows" style={{ display: "none" }}>
|
||||
{/* Main Command Bar - Start */}
|
||||
<div data-bind="visible: selfServeType() === 'none', react: commandBarComponentAdapter" />
|
||||
@@ -305,7 +308,10 @@ const App: React.FunctionComponent = () => {
|
||||
data-bind="visible: !isRefreshingExplorer() && tabsManager.openedTabs().length === 0"
|
||||
>
|
||||
<form className="connectExplorerFormContainer">
|
||||
<div className="connectExplorer" data-bind="visible: selfServeType() === 'none', react: splashScreenAdapter" />
|
||||
<div
|
||||
className="connectExplorer"
|
||||
data-bind="visible: selfServeType() === 'none', react: splashScreenAdapter"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
@@ -375,14 +381,23 @@ const App: React.FunctionComponent = () => {
|
||||
{/* Global loader - Start */}
|
||||
<div className="splashLoaderContainer" data-bind="visible: isRefreshingExplorer">
|
||||
<div className="splashLoaderContentContainer">
|
||||
<div data-bind="visible: selfServeType() === undefined, react: selfServeLoadingComponentAdapter">
|
||||
</div>
|
||||
<p className="connectExplorerContent" id="explorerConnectingImage" data-bind="visible: selfServeType() === 'none'">
|
||||
</p>
|
||||
<p className="splashLoaderTitle" id="explorerLoadingStatusTitle" data-bind="visible: selfServeType() === 'none'">
|
||||
</p>
|
||||
<p className="splashLoaderText" id="explorerLoadingStatusText" role="alert" data-bind="visible: selfServeType() === 'none'">
|
||||
</p>
|
||||
<div data-bind="visible: selfServeType() === undefined, react: selfServeLoadingComponentAdapter"></div>
|
||||
<p
|
||||
className="connectExplorerContent"
|
||||
id="explorerConnectingImage"
|
||||
data-bind="visible: selfServeType() === 'none'"
|
||||
></p>
|
||||
<p
|
||||
className="splashLoaderTitle"
|
||||
id="explorerLoadingStatusTitle"
|
||||
data-bind="visible: selfServeType() === 'none'"
|
||||
></p>
|
||||
<p
|
||||
className="splashLoaderText"
|
||||
id="explorerLoadingStatusText"
|
||||
role="alert"
|
||||
data-bind="visible: selfServeType() === 'none'"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Global loader - End */}
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
import React from "react";
|
||||
import { Text } from "office-ui-fabric-react";
|
||||
import { HoverCard, HoverCardType, Stack, Text } from "office-ui-fabric-react";
|
||||
import { InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
|
||||
interface TextComponentProps {
|
||||
text: string;
|
||||
currentValues: Map<string, InputType>
|
||||
text: string;
|
||||
currentValues: Map<string, InputType>;
|
||||
}
|
||||
|
||||
export class TextComponent extends React.Component<TextComponentProps> {
|
||||
private onHover = (): JSX.Element => {
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 5, padding: 15 }}>
|
||||
<Text>Choice: {this.props.currentValues.get("choiceInput")?.toString()}</Text>
|
||||
<Text>Boolean: {this.props.currentValues.get("booleanInput")?.toString()}</Text>
|
||||
<Text>String: {this.props.currentValues.get("stringInput")?.toString()}</Text>
|
||||
<Text>Slider: {this.props.currentValues.get("numberSliderInput")?.toString()}</Text>
|
||||
<Text>Spinner: {this.props.currentValues.get("numberSpinnerInput")?.toString()}</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return <Text>{this.props.text}, instanceCount: {this.props.currentValues?.get("instanceCount")}</Text>
|
||||
}
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<HoverCard plainCardProps={{ onRenderPlainCard: this.onHover }} instantOpenOnClick type={HoverCardType.plain}>
|
||||
<Text styles={{ root: { fontWeight: 600 } }}>{this.props.text}</Text>
|
||||
</HoverCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,59 +5,185 @@ import {
|
||||
OnChange,
|
||||
Placeholder,
|
||||
CustomElement,
|
||||
DefaultStringValue,
|
||||
ChoiceInput,
|
||||
BooleanInput,
|
||||
NumberInput
|
||||
} from "../PropertyDescriptors";
|
||||
import { SmartUi, ClassInfo, OnSubmit, Initialize } from "../ClassDescriptors";
|
||||
import {
|
||||
getPromise,
|
||||
initializeSelfServeExample,
|
||||
instanceSizeInfo,
|
||||
instanceSizeOptions,
|
||||
onInstanceCountChange,
|
||||
choiceInfo,
|
||||
choiceOptions,
|
||||
onSliderChange,
|
||||
onSubmit,
|
||||
renderText,
|
||||
Sizes,
|
||||
selfServeExampleInfo
|
||||
selfServeExampleInfo,
|
||||
descriptionElement,
|
||||
initializeNumberMaxValue
|
||||
} from "./ExampleApis";
|
||||
import { SelfServeBase } from "../SelfServeUtils";
|
||||
import { ChoiceItem } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
|
||||
@SmartUi()
|
||||
@ClassInfo(getPromise(selfServeExampleInfo))
|
||||
@Initialize(initializeSelfServeExample)
|
||||
@OnSubmit(onSubmit)
|
||||
export class SelfServeExample extends SelfServeBase {
|
||||
/*
|
||||
This is an example self serve class that auto generates UI components for your feature.
|
||||
|
||||
@Label(getPromise("Description"))
|
||||
@CustomElement(renderText("This is the description."))
|
||||
Each self serve class
|
||||
- Needs to extends the SelfServeBase class.
|
||||
- Needs to have the @SmartUi() descriptor to tell the compiler that UI needs to be generated from this class.
|
||||
- Needs to have an @OnSubmit() descriptor, a callback for when the submit button is clicked.
|
||||
- Needs to have an @Initialize() descriptor, to set default values for the inputs.
|
||||
|
||||
You can test this self serve UI by using the featureflag '?feature.selfServeTypeForTest=example'
|
||||
and plumb in similar feature flags for your own self serve class.
|
||||
|
||||
The default values and functions used for this class can be found in ExampleApis.tsx
|
||||
*/
|
||||
|
||||
/*
|
||||
@SmartUi()
|
||||
- role: Generated the JSON required to convert this class into the required UI. This is done during compile time.
|
||||
*/
|
||||
@SmartUi()
|
||||
/*
|
||||
@OnSubmit()
|
||||
- input: (currentValues: Map<string, InputType>) => Promise<void>
|
||||
- role: Callback that is triggerred when the submit button is clicked. You should perform your rest API
|
||||
calls here using the data from the different inputs passed as a Map to this callback function.
|
||||
|
||||
In this example, the onSubmit callback simply sets the value for keys corresponding to the field name
|
||||
in the SessionStorage.
|
||||
*/
|
||||
@OnSubmit(onSubmit)
|
||||
/*
|
||||
@ClassInfo()
|
||||
- input: Info | () => Promise<Info>
|
||||
- role: Display an Info bar as the first element of the UI.
|
||||
*/
|
||||
@ClassInfo(selfServeExampleInfo)
|
||||
/*
|
||||
@Initialize()
|
||||
- input: () => Promise<Map<string, InputType>>
|
||||
- role: Set default values for the properties of this class.
|
||||
|
||||
The static properties of this class (namely choiceInput, booleanInput, stringInput, numberSliderInput, numberSpinnerInput)
|
||||
will each correspond to an UI element. Their values can be of 'InputType'. Their defaults can be set by setting
|
||||
values in a Map corresponding to the field's name.
|
||||
|
||||
Typically, you can make rest calls in the async function passed to @Initialize() to fetch the initial values for
|
||||
these fields. This is called after the onSubmit callback, to reinitialize the defaults.
|
||||
|
||||
In this example, the initializeSelfServeExample function simply reads the SessionStorage to fetch the default values
|
||||
for these fields. These are then set when the changes are submitted.
|
||||
*/
|
||||
@Initialize(initializeSelfServeExample)
|
||||
export class SelfServeExample extends SelfServeBase {
|
||||
/*
|
||||
@CustomElement()
|
||||
- input: JSX.Element | (currentValues: Map<string, InputType> => Promise<JSX.Element>)
|
||||
- role: Display a custom element by either passing the element itself, or by passing a function that takes the current values
|
||||
and renders a Component / JSX.Element.
|
||||
|
||||
In this example, we first use a static JSX.Element to show a description text. We also declare a CustomComponent, that
|
||||
takes a Map of propertyName -> value, as input. It uses this to display a Hoverable Card which shows a snapshot of
|
||||
the current values.
|
||||
*/
|
||||
@CustomElement(descriptionElement)
|
||||
static description: string;
|
||||
|
||||
@Label(getPromise("Instance Size"))
|
||||
@PropertyInfo(getPromise(instanceSizeInfo))
|
||||
//@ChoiceInput(getPromise(instanceSizeOptions), getPromise(Sizes.OneCore4Gb))
|
||||
@ChoiceInput(getPromise(instanceSizeOptions))
|
||||
static instanceSize: ChoiceItem;
|
||||
/*
|
||||
@ParentOf()
|
||||
- input: string[]
|
||||
- role: Determines which UI elements are the children of which UI element. An array containing the names of the child properties
|
||||
is passsed. You need to make sure these children are declared in this Class as proeprties.
|
||||
*/
|
||||
@ParentOf(["choiceInput", "booleanInput", "stringInput", "numberSliderInput", "numberSpinnerInput"])
|
||||
@CustomElement(renderText("Hover to see current values..."))
|
||||
static currentValues: string;
|
||||
|
||||
@Label(getPromise("About"))
|
||||
@CustomElement(renderText("This is the about ."))
|
||||
static about: string;
|
||||
/*
|
||||
@Label()
|
||||
- input: string | () => Promise<string>
|
||||
- role: Adds a label for the UI element. This is ignored for a custom element but is required for all other properties.
|
||||
*/
|
||||
@Label("Choice")
|
||||
|
||||
@Label("Feature Allowed")
|
||||
//@BooleanInput("allowed", "not allowed", false)
|
||||
@BooleanInput("allowed", "not allowed")
|
||||
static isAllowed: boolean;
|
||||
/*
|
||||
@PropertyInfo()
|
||||
- input: Info | () => Promise<Info>
|
||||
- role: Display an Info bar above the UI element for this property.
|
||||
*/
|
||||
@PropertyInfo(choiceInfo)
|
||||
|
||||
@Label("Instance Name")
|
||||
/*
|
||||
@ChoiceInput()
|
||||
- input: ChoiceItem[] | () => Promise<ChoiceItem[]>
|
||||
- role: Display a dropdown with choices.
|
||||
*/
|
||||
@ChoiceInput(choiceOptions)
|
||||
static choiceInput: ChoiceItem;
|
||||
|
||||
@Label("Boolean")
|
||||
/*
|
||||
@BooleanInput()
|
||||
- input:
|
||||
trueLabel : string | () => Promise<string>
|
||||
falseLabel : string | () => Promise<string>
|
||||
- role: Add a boolean input eith radio buttons for true and false values.
|
||||
*/
|
||||
@BooleanInput({
|
||||
trueLabel: "allowed",
|
||||
falseLabel: "not allowed"
|
||||
})
|
||||
static booleanInput: boolean;
|
||||
|
||||
@Label("String")
|
||||
/*
|
||||
@PlaceHolder()
|
||||
- input: string | () => Promise<string>
|
||||
- role: Adds a placeholder for the string input
|
||||
*/
|
||||
@Placeholder("instance name")
|
||||
static instanceName: string;
|
||||
static stringInput: string;
|
||||
|
||||
@Label(getPromise("Instance Count"))
|
||||
@OnChange(onInstanceCountChange)
|
||||
@ParentOf(["instanceSize", "about", "instanceName", "isAllowed", ])
|
||||
//@NumberInput(getPromise(1), getPromise(5), getPromise(1), "slider", getPromise(0))
|
||||
@NumberInput(getPromise(1), getPromise(5), getPromise(1), "slider")
|
||||
static instanceCount: number;
|
||||
@Label("Slider")
|
||||
|
||||
/*
|
||||
@OnChange()
|
||||
- input: (currentValues: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
|
||||
- role: Takes a Map of current values and the newValue for this property as inputs. This is called when a property
|
||||
changes its value in the UI. This can be used to change other input values based on some other input.
|
||||
|
||||
The new Map of propertyName -> value is returned.
|
||||
|
||||
In this example, the onSliderChange function sets the spinner input to the same value as the slider input
|
||||
when the slider in moved in the UI.
|
||||
*/
|
||||
@OnChange(onSliderChange)
|
||||
|
||||
/*
|
||||
@NumberInput()
|
||||
- input:
|
||||
min : number | () => Promise<number>
|
||||
max : number | () => Promise<number>
|
||||
step : number | () => Promise<number>
|
||||
numberInputType : NumberInputType
|
||||
- role: Display a numeric input as slider or a spinner. The Min, Max and step to increase by need to be provided as well.
|
||||
In this example, the Max value is fetched via an async function. This is resolved every time the UI is reloaded.
|
||||
*/
|
||||
@NumberInput({
|
||||
min: 1,
|
||||
max: initializeNumberMaxValue,
|
||||
step: 1,
|
||||
numberInputType: "slider"
|
||||
})
|
||||
static numberSliderInput: number;
|
||||
|
||||
@Label("Spinner")
|
||||
@NumberInput({
|
||||
min: 1,
|
||||
max: initializeNumberMaxValue,
|
||||
step: 1,
|
||||
numberInputType: "spinner"
|
||||
})
|
||||
static numberSpinnerInput: number;
|
||||
}
|
||||
|
||||
@@ -1,83 +1,70 @@
|
||||
import React from "react";
|
||||
import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
import { TextComponent } from "./CustomComponent";
|
||||
import {SessionStorageUtility} from "../../Shared/StorageUtility"
|
||||
import { SessionStorageUtility } from "../../Shared/StorageUtility";
|
||||
import { Text } from "office-ui-fabric-react";
|
||||
|
||||
export enum Sizes {
|
||||
OneCore4Gb = "OneCore4Gb",
|
||||
TwoCore8Gb = "TwoCore8Gb",
|
||||
FourCore16Gb = "FourCore16Gb"
|
||||
export enum Choices {
|
||||
Choice1 = "Choice1",
|
||||
Choice2 = "Choice2",
|
||||
Choice3 = "Choice3"
|
||||
}
|
||||
|
||||
export const instanceSizeOptions: ChoiceItem[] = [
|
||||
{ label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
|
||||
{ label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
|
||||
{ label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
|
||||
export const choiceOptions: ChoiceItem[] = [
|
||||
{ label: "Choice 1", key: Choices.Choice1 },
|
||||
{ label: "Choice 2", key: Choices.Choice2 },
|
||||
{ label: "Choice 3", key: Choices.Choice3 }
|
||||
];
|
||||
|
||||
export const selfServeExampleInfo: Info = {
|
||||
message: "This is a self serve class"
|
||||
};
|
||||
|
||||
export const instanceSizeInfo: Info = {
|
||||
message: "instance size will be updated in the future"
|
||||
export const choiceInfo: Info = {
|
||||
message: "More choices can be added in the future."
|
||||
};
|
||||
|
||||
export const onInstanceCountChange = (
|
||||
currentState: Map<string, InputType>,
|
||||
newValue: InputType
|
||||
): Map<string, InputType> => {
|
||||
currentState.set("instanceCount", newValue);
|
||||
if ((newValue as number) === 1) {
|
||||
currentState.set("instanceSize", Sizes.OneCore4Gb);
|
||||
}
|
||||
export const onSliderChange = (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
|
||||
currentState.set("numberSliderInput", newValue);
|
||||
currentState.set("numberSpinnerInput", newValue);
|
||||
return currentState;
|
||||
};
|
||||
|
||||
export const 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")
|
||||
);
|
||||
|
||||
SessionStorageUtility.setEntry("instanceCount", currentValues.get("instanceCount")?.toString())
|
||||
SessionStorageUtility.setEntry("instanceSize", currentValues.get("instanceSize")?.toString())
|
||||
SessionStorageUtility.setEntry("instanceName", currentValues.get("instanceName")?.toString())
|
||||
SessionStorageUtility.setEntry("isAllowed", currentValues.get("isAllowed")?.toString())
|
||||
SessionStorageUtility.setEntry("choiceInput", currentValues.get("choiceInput")?.toString());
|
||||
SessionStorageUtility.setEntry("booleanInput", currentValues.get("booleanInput")?.toString());
|
||||
SessionStorageUtility.setEntry("stringInput", currentValues.get("stringInput")?.toString());
|
||||
SessionStorageUtility.setEntry("numberSliderInput", currentValues.get("numberSliderInput")?.toString());
|
||||
SessionStorageUtility.setEntry("numberSpinnerInput", currentValues.get("numberSpinnerInput")?.toString());
|
||||
};
|
||||
|
||||
export const initializeSelfServeExample = async () : Promise<Map<string, InputType>> => {
|
||||
let defaults = new Map<string, InputType>()
|
||||
defaults.set("instanceCount", parseInt(SessionStorageUtility.getEntry("instanceCount")))
|
||||
defaults.set("instanceSize", SessionStorageUtility.getEntry("instanceSize"))
|
||||
defaults.set("instanceName", SessionStorageUtility.getEntry("instanceName"))
|
||||
defaults.set("isAllowed", SessionStorageUtility.getEntry("isAllowed") === "true")
|
||||
return defaults
|
||||
};
|
||||
|
||||
export const delay = (ms: number): Promise<void> => {
|
||||
const delay = (ms: number): Promise<void> => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
export const getPromise = <T extends number | string | boolean | ChoiceItem[] | Info>(value: T): (() => Promise<T>) => {
|
||||
const f = async (): Promise<T> => {
|
||||
console.log("delay start");
|
||||
await delay(100);
|
||||
console.log("delay end");
|
||||
return value;
|
||||
};
|
||||
return f;
|
||||
export const initializeSelfServeExample = async (): Promise<Map<string, InputType>> => {
|
||||
await delay(1000);
|
||||
const defaults = new Map<string, InputType>();
|
||||
defaults.set("choiceInput", SessionStorageUtility.getEntry("choiceInput"));
|
||||
defaults.set("booleanInput", SessionStorageUtility.getEntry("booleanInput") === "true");
|
||||
defaults.set("stringInput", SessionStorageUtility.getEntry("stringInput"));
|
||||
const numberSliderInput = parseInt(SessionStorageUtility.getEntry("numberSliderInput"));
|
||||
defaults.set("numberSliderInput", !isNaN(numberSliderInput) ? numberSliderInput : 1);
|
||||
const numberSpinnerInput = parseInt(SessionStorageUtility.getEntry("numberSpinnerInput"));
|
||||
defaults.set("numberSpinnerInput", !isNaN(numberSpinnerInput) ? numberSpinnerInput : 1);
|
||||
return defaults;
|
||||
};
|
||||
|
||||
export const renderText = (text: string) : (currentValues: Map<string, InputType>) => Promise<JSX.Element> => {
|
||||
const f = async (currentValues: Map<string, InputType>): Promise<JSX.Element> => {
|
||||
return <TextComponent text={text} currentValues={currentValues}/>
|
||||
export const initializeNumberMaxValue = async (): Promise<number> => {
|
||||
await delay(2000);
|
||||
return 5;
|
||||
};
|
||||
|
||||
export const descriptionElement = <Text>This is an example of Self serve class.</Text>;
|
||||
|
||||
export const renderText = (text: string): ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) => {
|
||||
const elementPromiseFunction = async (currentValues: Map<string, InputType>): Promise<JSX.Element> => {
|
||||
return <TextComponent text={text} currentValues={currentValues} />;
|
||||
};
|
||||
return f
|
||||
}
|
||||
return elementPromiseFunction;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ChoiceItem, Descriptor, Info, InputType } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
import { ChoiceItem, Info, InputType, NumberInputType } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
import { addPropertyToMap } from "./SelfServeUtils";
|
||||
|
||||
interface Decorator {
|
||||
name: string,
|
||||
value: any
|
||||
name: string;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
const addToMap = (...decorators: Decorator[]): PropertyDecorator => {
|
||||
return (target, property) => {
|
||||
const className = (target as Function).name;
|
||||
var propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase();
|
||||
const propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase();
|
||||
|
||||
addPropertyToMap(target, property.toString(), className, "type", propertyType);
|
||||
addPropertyToMap(target, property.toString(), className, "dataFieldName", property.toString());
|
||||
@@ -17,69 +17,68 @@ const addToMap = (...decorators: Decorator[]): PropertyDecorator => {
|
||||
if (!className) {
|
||||
throw new Error("property descriptor applied to non static field!");
|
||||
}
|
||||
decorators.map((decorator: Decorator) => addPropertyToMap(target, property.toString(), className, decorator.name, decorator.value));
|
||||
decorators.map((decorator: Decorator) =>
|
||||
addPropertyToMap(target, property.toString(), className, decorator.name, decorator.value)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export const OnChange = (
|
||||
onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
|
||||
): PropertyDecorator => {
|
||||
return addToMap({name: "onChange", value: onChange});
|
||||
return addToMap({ name: "onChange", value: onChange });
|
||||
};
|
||||
|
||||
export const CustomElement = (customElement: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element): PropertyDecorator => {
|
||||
return addToMap({name: "customElement", value: customElement});
|
||||
export const CustomElement = (
|
||||
customElement: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element
|
||||
): PropertyDecorator => {
|
||||
return addToMap({ name: "customElement", value: customElement });
|
||||
};
|
||||
|
||||
export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
|
||||
return addToMap({name: "info", value: info});
|
||||
return addToMap({ name: "info", value: info });
|
||||
};
|
||||
|
||||
export const Placeholder = (placeholder: (() => Promise<string>) | string): PropertyDecorator => {
|
||||
return addToMap({name: "placeholder", value: placeholder});
|
||||
return addToMap({ name: "placeholder", value: placeholder });
|
||||
};
|
||||
|
||||
export const ParentOf = (children: string[]): PropertyDecorator => {
|
||||
return addToMap({name: "parentOf", value: children});
|
||||
return addToMap({ name: "parentOf", value: children });
|
||||
};
|
||||
|
||||
export const Label = (label: (() => Promise<string>) | string): PropertyDecorator => {
|
||||
return addToMap({name: "label", value: label});
|
||||
return addToMap({ name: "label", value: label });
|
||||
};
|
||||
|
||||
export const NumberInput = (min: (() => Promise<number>) | number,
|
||||
max: (() => Promise<number>) | number,
|
||||
step: (() => Promise<number>) | number,
|
||||
numberInputType: string,
|
||||
defaultNumberValue?: (() => Promise<number>) | number,
|
||||
): PropertyDecorator => {
|
||||
export interface NumberInputOptions {
|
||||
min: (() => Promise<number>) | number;
|
||||
max: (() => Promise<number>) | number;
|
||||
step: (() => Promise<number>) | number;
|
||||
numberInputType: NumberInputType;
|
||||
}
|
||||
|
||||
export const NumberInput = (numberInputOptions: NumberInputOptions): PropertyDecorator => {
|
||||
return addToMap(
|
||||
{name: "min", value: min},
|
||||
{name: "max", value: max},
|
||||
{name: "step", value: step},
|
||||
{name: "defaultValue", value: defaultNumberValue},
|
||||
{name: "inputType", value: numberInputType}
|
||||
{ name: "min", value: numberInputOptions.min },
|
||||
{ name: "max", value: numberInputOptions.max },
|
||||
{ name: "step", value: numberInputOptions.step },
|
||||
{ name: "inputType", value: numberInputOptions.numberInputType }
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultStringValue = (defaultStringValue: (() => Promise<string>) | string): PropertyDecorator => {
|
||||
return addToMap({name: "defaultValue", value: defaultStringValue});
|
||||
};
|
||||
export interface BooleanInputOptions {
|
||||
trueLabel: (() => Promise<string>) | string;
|
||||
falseLabel: (() => Promise<string>) | string;
|
||||
}
|
||||
|
||||
export const BooleanInput = (trueLabel: (() => Promise<string>) | string,
|
||||
falseLabel: (() => Promise<string>) | string,
|
||||
defaultBooleanValue?: (() => Promise<boolean>) | boolean): PropertyDecorator => {
|
||||
export const BooleanInput = (booleanInputOptions: BooleanInputOptions): PropertyDecorator => {
|
||||
return addToMap(
|
||||
{name: "defaultValue", value: defaultBooleanValue},
|
||||
{name: "trueLabel", value: trueLabel},
|
||||
{name: "falseLabel", value: falseLabel}
|
||||
{ name: "trueLabel", value: booleanInputOptions.trueLabel },
|
||||
{ name: "falseLabel", value: booleanInputOptions.falseLabel }
|
||||
);
|
||||
};
|
||||
|
||||
export const ChoiceInput = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[],
|
||||
defaultKey?: (() => Promise<string>) | string): PropertyDecorator => {
|
||||
return addToMap(
|
||||
{name: "choices", value: choices},
|
||||
{name: "defaultKey", value: defaultKey}
|
||||
);
|
||||
export const ChoiceInput = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[]): PropertyDecorator => {
|
||||
return addToMap({ name: "choices", value: choices });
|
||||
};
|
||||
|
||||
@@ -18,28 +18,31 @@ export class SelfServeComponentAdapter implements ReactAdapter {
|
||||
constructor(container: Explorer) {
|
||||
this.container = container;
|
||||
this.parameters = ko.observable(Date.now());
|
||||
this.container.selfServeType.subscribe(() => {this.triggerRender()})
|
||||
this.container.selfServeType.subscribe(() => {
|
||||
this.triggerRender();
|
||||
});
|
||||
}
|
||||
|
||||
private getDescriptor = (selfServeType : SelfServeTypes) : Descriptor => {
|
||||
private getDescriptor = (selfServeType: SelfServeTypes): Descriptor => {
|
||||
switch (selfServeType) {
|
||||
case SelfServeTypes.example:
|
||||
return SelfServeExample.toSmartUiDescriptor()
|
||||
return SelfServeExample.toSmartUiDescriptor();
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
const selfServeType = this.container.selfServeType()
|
||||
const smartUiDescriptor = this.getDescriptor(selfServeType)
|
||||
};
|
||||
|
||||
|
||||
const element = smartUiDescriptor ?
|
||||
<SmartUiComponent descriptor={smartUiDescriptor} /> :
|
||||
public renderComponent(): JSX.Element {
|
||||
const selfServeType = this.container.selfServeType();
|
||||
const smartUiDescriptor = this.getDescriptor(selfServeType);
|
||||
|
||||
const element = smartUiDescriptor ? (
|
||||
<SmartUiComponent descriptor={smartUiDescriptor} />
|
||||
) : (
|
||||
<h1>Invalid self serve type!</h1>
|
||||
|
||||
return element
|
||||
);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private triggerRender() {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class SelfServeLoadingComponentAdapter implements ReactAdapter {
|
||||
}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <Spinner size={SpinnerSize.large} />
|
||||
return <Spinner size={SpinnerSize.large} />;
|
||||
}
|
||||
|
||||
private triggerRender() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
InputType
|
||||
} from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
|
||||
const SelfServeType = "selfServeType"
|
||||
const SelfServeType = "selfServeType";
|
||||
|
||||
export class SelfServeBase {
|
||||
public static toSmartUiDescriptor(): Descriptor {
|
||||
@@ -32,11 +32,9 @@ export interface CommonInputTypes {
|
||||
min?: (() => Promise<number>) | number;
|
||||
max?: (() => Promise<number>) | number;
|
||||
step?: (() => Promise<number>) | number;
|
||||
defaultValue?: any;
|
||||
trueLabel?: (() => Promise<string>) | string;
|
||||
falseLabel?: (() => Promise<string>) | string;
|
||||
choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
||||
defaultKey?: (() => Promise<string>) | string;
|
||||
inputType?: string;
|
||||
onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
|
||||
onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
|
||||
@@ -52,10 +50,7 @@ const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T
|
||||
fieldObject[name] = value;
|
||||
};
|
||||
|
||||
const getValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
|
||||
name: T,
|
||||
fieldObject: CommonInputTypes
|
||||
): K => {
|
||||
const getValue = <T extends keyof CommonInputTypes>(name: T, fieldObject: CommonInputTypes): unknown => {
|
||||
return fieldObject[name];
|
||||
};
|
||||
|
||||
@@ -67,10 +62,10 @@ export const addPropertyToMap = (
|
||||
descriptorValue: any
|
||||
): void => {
|
||||
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>;
|
||||
|
||||
if (!context) {
|
||||
context = new Map<String, CommonInputTypes>();
|
||||
context = new Map<string, CommonInputTypes>();
|
||||
}
|
||||
|
||||
if (!(context instanceof Map)) {
|
||||
@@ -93,7 +88,7 @@ export const addPropertyToMap = (
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
const root = context.get("root");
|
||||
@@ -105,7 +100,13 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void =
|
||||
);
|
||||
}
|
||||
|
||||
let smartUiDescriptor = {
|
||||
if (!root?.initialize) {
|
||||
throw new Error(
|
||||
"@Initialize decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
|
||||
);
|
||||
}
|
||||
|
||||
const smartUiDescriptor = {
|
||||
onSubmit: root.onSubmit,
|
||||
initialize: root.initialize,
|
||||
root: {
|
||||
@@ -124,12 +125,12 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void =
|
||||
};
|
||||
|
||||
const addToDescriptor = (
|
||||
context: Map<String, CommonInputTypes>,
|
||||
context: Map<string, CommonInputTypes>,
|
||||
smartUiDescriptor: Descriptor,
|
||||
root: Node,
|
||||
key: String
|
||||
key: string
|
||||
): void => {
|
||||
let value = context.get(key);
|
||||
const value = context.get(key);
|
||||
if (!value) {
|
||||
// should already be added to root
|
||||
const childNode = getChildFromRoot(key, smartUiDescriptor);
|
||||
@@ -149,13 +150,13 @@ const addToDescriptor = (
|
||||
children: []
|
||||
} as Node;
|
||||
context.delete(key);
|
||||
for (let childKey in childrenKeys) {
|
||||
for (const childKey in childrenKeys) {
|
||||
addToDescriptor(context, smartUiDescriptor, element, childrenKeys[childKey]);
|
||||
}
|
||||
root.children.push(element);
|
||||
};
|
||||
|
||||
const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => {
|
||||
const getChildFromRoot = (key: string, smartUiDescriptor: Descriptor): Node => {
|
||||
let i = 0;
|
||||
const children = smartUiDescriptor.root.children;
|
||||
while (i < children.length) {
|
||||
@@ -171,8 +172,8 @@ const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): Node => {
|
||||
};
|
||||
|
||||
const getInput = (value: CommonInputTypes): AnyInput => {
|
||||
if (!value.label || !value.type || !value.dataFieldName) {
|
||||
throw new Error("label, onChange, type and dataFieldName are required.");
|
||||
if (!value.label && !value.customElement) {
|
||||
throw new Error("label is required.");
|
||||
}
|
||||
|
||||
switch (value.type) {
|
||||
@@ -197,13 +198,13 @@ const getInput = (value: CommonInputTypes): AnyInput => {
|
||||
};
|
||||
|
||||
export enum SelfServeTypes {
|
||||
none="none",
|
||||
invalid="invalid",
|
||||
example="example"
|
||||
none = "none",
|
||||
invalid = "invalid",
|
||||
example = "example"
|
||||
}
|
||||
|
||||
export const getSelfServeType = (search: string): SelfServeTypes => {
|
||||
const params = new URLSearchParams(search);
|
||||
const selfServeTypeParam = params.get(SelfServeType)?.toLowerCase()
|
||||
return SelfServeTypes[selfServeTypeParam as keyof typeof SelfServeTypes]
|
||||
}
|
||||
const selfServeTypeParam = params.get(SelfServeType)?.toLowerCase();
|
||||
return SelfServeTypes[selfServeTypeParam as keyof typeof SelfServeTypes];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user