import React from "react"; import { CommandBar, ICommandBarItemProps, IStackTokens, MessageBar, MessageBarType, Spinner, SpinnerSize, Stack, } from "office-ui-fabric-react"; import { AnyDisplay, Node, InputType, RefreshResult, SelfServeDescriptor, SelfServeNotification, SmartUiInput, DescriptionDisplay, StringInput, NumberInput, BooleanInput, ChoiceInput, SelfServeNotificationType, } from "./SelfServeTypes"; import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent"; import { getMessageBarType } from "./SelfServeUtils"; import { Translation } from "react-i18next"; import { TFunction } from "i18next"; import "../i18n"; export interface SelfServeComponentProps { descriptor: SelfServeDescriptor; } export interface SelfServeComponentState { root: SelfServeDescriptor; currentValues: Map; baselineValues: Map; isInitializing: boolean; hasErrors: boolean; compileErrorMessage: string; notification: SelfServeNotification; refreshResult: RefreshResult; } export class SelfServeComponent extends React.Component { private smartUiGeneratorClassName: string; componentDidMount(): void { this.performRefresh(); this.initializeSmartUiComponent(); } constructor(props: SelfServeComponentProps) { super(props); this.state = { root: this.props.descriptor, currentValues: new Map(), baselineValues: new Map(), isInitializing: true, hasErrors: false, compileErrorMessage: undefined, notification: undefined, refreshResult: undefined, }; this.smartUiGeneratorClassName = this.props.descriptor.root.id; } private onError = (hasErrors: boolean): void => { this.setState({ hasErrors }); }; private initializeSmartUiComponent = async (): Promise => { this.setState({ isInitializing: true }); await this.setDefaults(); const { currentValues, baselineValues } = this.state; await this.initializeSmartUiNode(this.props.descriptor.root, currentValues, baselineValues); this.setState({ isInitializing: false, currentValues, baselineValues }); }; private setDefaults = async (): Promise => { let { currentValues, baselineValues } = this.state; const initialValues = await this.props.descriptor.initialize(); this.props.descriptor.inputNames.map((inputName) => { let initialValue = initialValues.get(inputName); if (!initialValue) { initialValue = { value: undefined, hidden: false }; } currentValues = currentValues.set(inputName, initialValue); baselineValues = baselineValues.set(inputName, initialValue); initialValues.delete(inputName); }); if (initialValues.size > 0) { const keys = []; for (const key of initialValues.keys()) { keys.push(key); } this.setState({ compileErrorMessage: `The following fields have default values set but are not input properties of this class: ${keys.join( ", " )}`, }); } this.setState({ currentValues, baselineValues }); }; public resetBaselineValues = (): void => { const currentValues = this.state.currentValues; let baselineValues = this.state.baselineValues; for (const key of currentValues.keys()) { const currentValue = currentValues.get(key); baselineValues = baselineValues.set(key, { ...currentValue }); } this.setState({ baselineValues }); }; public discard = (): void => { let { currentValues } = this.state; const { baselineValues } = this.state; for (const key of currentValues.keys()) { const baselineValue = baselineValues.get(key); currentValues = currentValues.set(key, { ...baselineValue }); } this.setState({ currentValues }); }; private initializeSmartUiNode = async ( currentNode: Node, currentValues: Map, baselineValues: Map ): Promise => { currentNode.info = await this.getResolvedValue(currentNode.info); if (currentNode.input) { currentNode.input = await this.getResolvedInput(currentNode.input, currentValues, baselineValues); } const promises = currentNode.children?.map( async (child: Node) => await this.initializeSmartUiNode(child, currentValues, baselineValues) ); if (promises) { await Promise.all(promises); } }; private getResolvedInput = async ( input: AnyDisplay, currentValues: Map, baselineValues: Map ): Promise => { input.labelTKey = await this.getResolvedValue(input.labelTKey); input.placeholderTKey = await this.getResolvedValue(input.placeholderTKey); switch (input.type) { case "string": { if ("description" in input) { const descriptionDisplay = input as DescriptionDisplay; descriptionDisplay.description = await this.getResolvedValue(descriptionDisplay.description); } return input as StringInput; } case "number": { const numberInput = input as NumberInput; numberInput.min = await this.getResolvedValue(numberInput.min); numberInput.max = await this.getResolvedValue(numberInput.max); numberInput.step = await this.getResolvedValue(numberInput.step); const dataFieldName = numberInput.dataFieldName; const defaultValue = currentValues.get(dataFieldName)?.value; if (!defaultValue) { const newDefaultValue = { value: numberInput.min, hidden: currentValues.get(dataFieldName)?.hidden }; currentValues.set(dataFieldName, newDefaultValue); baselineValues.set(dataFieldName, newDefaultValue); } return numberInput; } case "boolean": { const booleanInput = input as BooleanInput; booleanInput.trueLabelTKey = await this.getResolvedValue(booleanInput.trueLabelTKey); booleanInput.falseLabelTKey = await this.getResolvedValue(booleanInput.falseLabelTKey); return booleanInput; } default: { const choiceInput = input as ChoiceInput; choiceInput.choices = await this.getResolvedValue(choiceInput.choices); return choiceInput; } } }; public async getResolvedValue(value: T | (() => Promise)): Promise { if (value instanceof Function) { return value(); } return value; } private onInputChange = (input: AnyDisplay, newValue: InputType) => { if (input.onChange) { const newValues = input.onChange(this.state.currentValues, newValue); this.setState({ currentValues: newValues }); } else { const dataFieldName = input.dataFieldName; const { currentValues } = this.state; const currentInputValue = currentValues.get(dataFieldName); currentValues.set(dataFieldName, { value: newValue, hidden: currentInputValue?.hidden }); this.setState({ currentValues }); } }; public onSaveButtonClick = (): void => { const onSavePromise = this.props.descriptor.onSave(this.state.currentValues); onSavePromise.catch((error) => { this.setState({ notification: { message: `${error.message}`, type: SelfServeNotificationType.error, }, }); }); onSavePromise.then((notification: SelfServeNotification) => { this.setState({ notification: { message: notification.message, type: notification.type, }, }); this.resetBaselineValues(); this.onRefreshClicked(); }); }; public isDiscardButtonDisabled = (): boolean => { for (const key of this.state.currentValues.keys()) { const currentValue = JSON.stringify(this.state.currentValues.get(key)); const baselineValue = JSON.stringify(this.state.baselineValues.get(key)); if (currentValue !== baselineValue) { return false; } } return true; }; public isSaveButtonDisabled = (): boolean => { if (this.state.hasErrors) { return true; } for (const key of this.state.currentValues.keys()) { const currentValue = JSON.stringify(this.state.currentValues.get(key)); const baselineValue = JSON.stringify(this.state.baselineValues.get(key)); if (currentValue !== baselineValue) { return false; } } return true; }; private performRefresh = async (): Promise => { const refreshResult = await this.props.descriptor.onRefresh(); this.setState({ refreshResult: { ...refreshResult } }); return refreshResult; }; public onRefreshClicked = async (): Promise => { this.setState({ isInitializing: true }); const refreshResult = await this.performRefresh(); if (!refreshResult.isUpdateInProgress) { this.initializeSmartUiComponent(); } this.setState({ isInitializing: false }); }; public getCommonTranslation = (translationFunction: TFunction, key: string): string => { return translationFunction(`Common.${key}`); }; private getCommandBarItems = (translate: TFunction): ICommandBarItemProps[] => { return [ { key: "save", text: this.getCommonTranslation(translate, "Save"), iconProps: { iconName: "Save" }, split: true, disabled: this.isSaveButtonDisabled(), onClick: this.onSaveButtonClick, }, { key: "discard", text: this.getCommonTranslation(translate, "Discard"), iconProps: { iconName: "Undo" }, split: true, disabled: this.isDiscardButtonDisabled(), onClick: () => { this.discard(); }, }, { key: "refresh", text: this.getCommonTranslation(translate, "Refresh"), disabled: this.state.isInitializing, iconProps: { iconName: "Refresh" }, split: true, onClick: () => { this.onRefreshClicked(); }, }, ]; }; private getNotificationMessageTranslation = (translationFunction: TFunction, messageKey: string): string => { const translation = translationFunction(messageKey); if (translation === `${this.smartUiGeneratorClassName}.${messageKey}`) { return messageKey; } return translation; }; public render(): JSX.Element { const containerStackTokens: IStackTokens = { childrenGap: 5 }; if (this.state.compileErrorMessage) { return {this.state.compileErrorMessage}; } return ( {(translate) => { const getTranslation = (key: string): string => { return translate(`${this.smartUiGeneratorClassName}.${key}`); }; return (
{this.state.isInitializing ? ( ) : ( <> {this.state.refreshResult?.isUpdateInProgress && ( {getTranslation(this.state.refreshResult.notificationMessage)} )} {this.state.notification && ( this.setState({ notification: undefined })} > {this.getNotificationMessageTranslation(getTranslation, this.state.notification.message)} )} )}
); }}
); } }