initial commit for right panel

This commit is contained in:
Srinath Narayanan 2021-07-30 12:25:40 +05:30
parent 6d46e48490
commit 09cb9c80bd
7 changed files with 78 additions and 22 deletions

View File

@ -1,6 +1,7 @@
import { import {
Dropdown, DocumentCard,
IDropdownOption, DocumentCardDetails,
Dropdown, IDropdownOption,
IStackTokens, IStackTokens,
Label, Label,
Link, Link,
@ -12,19 +13,19 @@ import {
Stack, Stack,
Text, Text,
TextField, TextField,
Toggle, Toggle
} from "@fluentui/react"; } from "@fluentui/react";
import { TFunction } from "i18next"; import { TFunction } from "i18next";
import * as React from "react"; import * as React from "react";
import { import {
ChoiceItem, ChoiceItem,
Description, Description,
DescriptionType, DescriptionType, Info,
Info,
InputType, InputType,
InputTypeValue, InputTypeValue,
NumberUiType, NumberUiType,
SmartUiInput, SmartUiInput,
Style
} from "../../../SelfServe/SelfServeTypes"; } from "../../../SelfServe/SelfServeTypes";
import { ToolTipLabelComponent } from "../Settings/SettingsSubComponents/ToolTipLabelComponent"; import { ToolTipLabelComponent } from "../Settings/SettingsSubComponents/ToolTipLabelComponent";
import * as InputUtils from "./InputUtils"; import * as InputUtils from "./InputUtils";
@ -87,6 +88,7 @@ interface Node {
info?: Info; info?: Info;
input?: AnyDisplay; input?: AnyDisplay;
children?: Node[]; children?: Node[];
style?: Style
} }
export interface SmartUiDescriptor { export interface SmartUiDescriptor {
@ -194,6 +196,14 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
if (description.type === DescriptionType.Text) { if (description.type === DescriptionType.Text) {
return descriptionElement; return descriptionElement;
} else if (description.type === DescriptionType.Card) {
return (
<DocumentCard styles={{ root: { display: "inline-block", padding: 10 } }}>
<DocumentCardDetails>
{descriptionElement}
</DocumentCardDetails>
</DocumentCard>
)
} }
const messageBarType = const messageBarType =
description.type === DescriptionType.InfoMessageBar ? MessageBarType.info : MessageBarType.warning; description.type === DescriptionType.InfoMessageBar ? MessageBarType.info : MessageBarType.warning;
@ -396,18 +406,29 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
} }
} }
private renderNode(node: Node): JSX.Element { private renderNode(node: Node, isSectionFloatRight: boolean): JSX.Element {
const containerStackTokens: IStackTokens = { childrenGap: 10 }; const containerStackTokens: IStackTokens = { childrenGap: 10 };
const isNodeFloatRight = node.style?.isFloatRight === true;
console.log(isSectionFloatRight, isNodeFloatRight, node.id)
return ( return (
<Stack tokens={containerStackTokens} className="widgetRendererContainer"> <Stack tokens={containerStackTokens} className="widgetRendererContainer">
{isSectionFloatRight === isNodeFloatRight ?
<>
<Stack.Item>{node.input && this.renderElement(node.input, node.info as Info)}</Stack.Item> <Stack.Item>{node.input && this.renderElement(node.input, node.info as Info)}</Stack.Item>
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)} </>
: <></>
}
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child, isSectionFloatRight)}</div>)}
</Stack> </Stack>
); );
} }
render(): JSX.Element { render(): JSX.Element {
return this.renderNode(this.props.descriptor.root); return (
<Stack horizontal tokens={{ childrenGap: 50 }}>
{this.renderNode(this.props.descriptor.root, false)}
{this.renderNode(this.props.descriptor.root, true)}
</Stack>
);
} }
} }

View File

@ -2,7 +2,7 @@
* @module SelfServe/Decorators * @module SelfServe/Decorators
*/ */
import { ChoiceItem, Description, Info, NumberUiType, OnChangeCallback, RefreshParams } from "./SelfServeTypes"; import { ChoiceItem, Description, Info, NumberUiType, OnChangeCallback, RefreshParams, Style } from "./SelfServeTypes";
import { addPropertyToMap, buildSmartUiDescriptor, DecoratorProperties } from "./SelfServeUtils"; import { addPropertyToMap, buildSmartUiDescriptor, DecoratorProperties } from "./SelfServeUtils";
type ValueOf<T> = T[keyof T]; type ValueOf<T> = T[keyof T];
@ -162,6 +162,14 @@ export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecora
return addToMap({ name: "info", value: info }); return addToMap({ name: "info", value: info });
}; };
/**
* Indicates that the UI element corresponding to the property should have an Info bubble. The Info
* bubble is the icon that looks like an "i" which users click on to get more information about the UI element.
*/
export const Styles = (style: (() => Promise<Style>) | Style): PropertyDecorator => {
return addToMap({ name: "style", value: style });
};
/** /**
* Indicates that this property should correspond to a UI element with the given parameters. * Indicates that this property should correspond to a UI element with the given parameters.
*/ */

View File

@ -1,4 +1,4 @@
import { IsDisplayable, OnChange, PropertyInfo, RefreshOptions, Values } from "../Decorators"; import { IsDisplayable, OnChange, PropertyInfo, RefreshOptions, Styles, Values } from "../Decorators";
import { selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor"; import { selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
import { import {
ChoiceItem, ChoiceItem,
@ -10,7 +10,7 @@ import {
OnSaveResult, OnSaveResult,
RefreshResult, RefreshResult,
SelfServeBaseClass, SelfServeBaseClass,
SmartUiInput, SmartUiInput
} from "../SelfServeTypes"; } from "../SelfServeTypes";
import { import {
getMaxCollectionThroughput, getMaxCollectionThroughput,
@ -19,7 +19,7 @@ import {
getMinDatabaseThroughput, getMinDatabaseThroughput,
initialize, initialize,
onRefreshSelfServeExample, onRefreshSelfServeExample,
update, update
} from "./SelfServeExample.rp"; } from "./SelfServeExample.rp";
import { AccountProps, Regions } from "./SelfServeExample.types"; import { AccountProps, Regions } from "./SelfServeExample.types";
@ -203,6 +203,20 @@ export default class SelfServeExample extends SelfServeBaseClass {
}) })
enableLogging: boolean; enableLogging: boolean;
@Styles({ isFloatRight: true })
@Values({
labelTKey: "DescriptionLabel",
description: {
textTKey: "DescriptionText",
type: DescriptionType.Card,
link: {
href: "https://aka.ms/cosmos-create-account-portal",
textTKey: "DecriptionLinkText",
},
},
})
rightDescription: string;
@Values({ @Values({
labelTKey: "Account Name", labelTKey: "Account Name",
placeholderTKey: "AccountNamePlaceHolder", placeholderTKey: "AccountNamePlaceHolder",

View File

@ -7,7 +7,7 @@ import {
Spinner, Spinner,
SpinnerSize, SpinnerSize,
Stack, Stack,
Text, Text
} from "@fluentui/react"; } from "@fluentui/react";
import { TFunction } from "i18next"; import { TFunction } from "i18next";
import promiseRetry, { AbortError } from "p-retry"; import promiseRetry, { AbortError } from "p-retry";
@ -32,7 +32,7 @@ import {
SelfServeComponentTelemetryType, SelfServeComponentTelemetryType,
SelfServeDescriptor, SelfServeDescriptor,
SmartUiInput, SmartUiInput,
StringInput, StringInput
} from "./SelfServeTypes"; } from "./SelfServeTypes";
interface SelfServeNotification { interface SelfServeNotification {
@ -186,6 +186,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
baselineValues: Map<string, SmartUiInput> baselineValues: Map<string, SmartUiInput>
): Promise<void> => { ): Promise<void> => {
currentNode.info = await this.getResolvedValue(currentNode.info); currentNode.info = await this.getResolvedValue(currentNode.info);
currentNode.style = await this.getResolvedValue(currentNode.style);
if (currentNode.input) { if (currentNode.input) {
currentNode.input = await this.getResolvedInput(currentNode.input, currentValues, baselineValues); currentNode.input = await this.getResolvedInput(currentNode.input, currentValues, baselineValues);

View File

@ -55,6 +55,7 @@ export interface Node {
info?: (() => Promise<Info>) | Info; info?: (() => Promise<Info>) | Info;
input?: AnyDisplay; input?: AnyDisplay;
children?: Node[]; children?: Node[];
style?: (() => Promise<Style>) | Style;
} }
/**@internal */ /**@internal */
@ -82,6 +83,13 @@ export type AnyDisplay = NumberInput | BooleanInput | StringInput | ChoiceInput
/**@internal */ /**@internal */
export type InputTypeValue = "number" | "string" | "boolean" | "object"; export type InputTypeValue = "number" | "string" | "boolean" | "object";
/**
* Describes the styling of the UI element.
*/
export interface Style {
isFloatRight: boolean
}
export type initializeCallback = export type initializeCallback =
/** /**
* @returns Promise of Map of propertyName => {@linkcode SmartUiInput} which will become the current state of the UI. * @returns Promise of Map of propertyName => {@linkcode SmartUiInput} which will become the current state of the UI.
@ -228,6 +236,7 @@ export enum DescriptionType {
* Show the description as a Warning Message bar. * Show the description as a Warning Message bar.
*/ */
WarningMessageBar, WarningMessageBar,
Card
} }
/** /**

View File

@ -20,6 +20,7 @@ import {
SelfServeDescriptor, SelfServeDescriptor,
SmartUiInput, SmartUiInput,
StringInput, StringInput,
Style
} from "./SelfServeTypes"; } from "./SelfServeTypes";
/** /**
@ -92,6 +93,7 @@ export interface DecoratorProperties {
description?: (() => Promise<Description>) | Description; description?: (() => Promise<Description>) | Description;
isDynamicDescription?: boolean; isDynamicDescription?: boolean;
refreshParams?: RefreshParams; refreshParams?: RefreshParams;
style?: (() => Promise<Style>) | Style;
onChange?: ( onChange?: (
newValue: InputType, newValue: InputType,
currentState: Map<string, SmartUiInput>, currentState: Map<string, SmartUiInput>,
@ -197,6 +199,7 @@ const addToDescriptor = (
info: value.info, info: value.info,
input: getInput(value), input: getInput(value),
children: [], children: [],
style: value.style
} as Node; } as Node;
context.delete(key); context.delete(key);
root.children.push(element); root.children.push(element);

View File

@ -1,28 +1,27 @@
import { IsDisplayable, OnChange, PropertyInfo, RefreshOptions, Values } from "../Decorators"; import { IsDisplayable, OnChange, PropertyInfo, RefreshOptions, Styles, Values } from "../Decorators";
import { selfServeTrace } from "../SelfServeTelemetryProcessor"; import { selfServeTrace } from "../SelfServeTelemetryProcessor";
import { import {
ChoiceItem, ChoiceItem,
Description, Description,
DescriptionType, DescriptionType, Info,
Info,
InputType, InputType,
NumberUiType, NumberUiType,
OnSaveResult, OnSaveResult,
RefreshResult, RefreshResult,
SelfServeBaseClass, SelfServeBaseClass,
SmartUiInput, SmartUiInput
} from "../SelfServeTypes"; } from "../SelfServeTypes";
import { BladeType, generateBladeLink } from "../SelfServeUtils"; import { BladeType, generateBladeLink } from "../SelfServeUtils";
import { import {
deleteDedicatedGatewayResource, deleteDedicatedGatewayResource,
getCurrentProvisioningState, getCurrentProvisioningState,
refreshDedicatedGatewayProvisioning, refreshDedicatedGatewayProvisioning,
updateDedicatedGatewayResource, updateDedicatedGatewayResource
} from "./SqlX.rp"; } from "./SqlX.rp";
const costPerHourValue: Description = { const costPerHourValue: Description = {
textTKey: "CostText", textTKey: "CostText",
type: DescriptionType.Text, type: DescriptionType.Card,
link: { link: {
href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing", href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing",
textTKey: "DedicatedGatewayPricing", textTKey: "DedicatedGatewayPricing",
@ -338,6 +337,7 @@ export default class SqlX extends SelfServeBaseClass {
}) })
instances: number; instances: number;
@Styles({ isFloatRight: true })
@Values({ @Values({
labelTKey: "Cost", labelTKey: "Cost",
isDynamicDescription: true, isDynamicDescription: true,