import { MessageBarType } from "office-ui-fabric-react"; import "reflect-metadata"; import { Node, AnyDisplay, BooleanInput, ChoiceInput, ChoiceItem, Description, DescriptionDisplay, Info, InputType, InputTypeValue, NumberInput, SelfServeDescriptor, SmartUiInput, StringInput, SelfServeNotificationType, } from "./SelfServeTypes"; export enum SelfServeType { // No self serve type passed, launch explorer none = "none", // Unsupported self serve type passed as feature flag invalid = "invalid", // Add your self serve types here example = "example", sqlx = "sqlx", } export interface DecoratorProperties { id: string; info?: (() => Promise) | Info; type?: InputTypeValue; label?: (() => Promise) | string; placeholder?: (() => Promise) | string; dataFieldName?: string; min?: (() => Promise) | number; max?: (() => Promise) | number; step?: (() => Promise) | number; trueLabel?: (() => Promise) | string; falseLabel?: (() => Promise) | string; choices?: (() => Promise) | ChoiceItem[]; uiType?: string; errorMessage?: string; description?: (() => Promise) | Description; onChange?: (currentState: Map, newValue: InputType) => Map; onSave?: (currentValues: Map) => Promise; initialize?: () => Promise>; } const setValue = ( name: T, value: K, fieldObject: DecoratorProperties ): void => { fieldObject[name] = value; }; const getValue = (name: T, fieldObject: DecoratorProperties): unknown => { return fieldObject[name]; }; export const addPropertyToMap = ( target: unknown, propertyName: string, className: string, descriptorName: keyof DecoratorProperties, descriptorValue: K ): void => { const context = (Reflect.getMetadata(className, target) as Map) ?? new Map(); updateContextWithDecorator(context, propertyName, className, descriptorName, descriptorValue); Reflect.defineMetadata(className, context, target); }; export const updateContextWithDecorator = ( context: Map, propertyName: string, className: string, descriptorName: keyof DecoratorProperties, descriptorValue: K ): void => { if (!(context instanceof Map)) { throw new Error(`@SmartUi should be the first decorator for the class '${className}'.`); } const propertyObject = context.get(propertyName) ?? { id: propertyName }; if (getValue(descriptorName, propertyObject) && descriptorName !== "type" && descriptorName !== "dataFieldName") { throw new Error( `Duplicate value passed for '${descriptorName}' on property '${propertyName}' of class '${className}'` ); } setValue(descriptorName, descriptorValue, propertyObject); context.set(propertyName, propertyObject); }; export const buildSmartUiDescriptor = (className: string, target: unknown): void => { const context = Reflect.getMetadata(className, target) as Map; const smartUiDescriptor = mapToSmartUiDescriptor(context); Reflect.defineMetadata(className, smartUiDescriptor, target); }; export const mapToSmartUiDescriptor = (context: Map): SelfServeDescriptor => { const root = context.get("root"); context.delete("root"); const inputNames: string[] = []; const smartUiDescriptor: SelfServeDescriptor = { root: { id: "root", info: root?.info, children: [], }, }; while (context.size > 0) { const key = context.keys().next().value; addToDescriptor(context, smartUiDescriptor.root, key, inputNames); } smartUiDescriptor.inputNames = inputNames; return smartUiDescriptor; }; const addToDescriptor = ( context: Map, root: Node, key: string, inputNames: string[] ): void => { const value = context.get(key); inputNames.push(value.id); const element = { id: value.id, info: value.info, input: getInput(value), children: [], } as Node; context.delete(key); root.children.push(element); }; const getInput = (value: DecoratorProperties): AnyDisplay => { switch (value.type) { case "number": if (!value.label || !value.step || !value.uiType || !value.min || !value.max) { value.errorMessage = `label, step, min, max and uiType are required for number input '${value.id}'.`; } return value as NumberInput; case "string": if (value.description) { return value as DescriptionDisplay; } if (!value.label) { value.errorMessage = `label is required for string input '${value.id}'.`; } return value as StringInput; case "boolean": if (!value.label || !value.trueLabel || !value.falseLabel) { value.errorMessage = `label, truelabel and falselabel are required for boolean input '${value.id}'.`; } return value as BooleanInput; default: if (!value.label || !value.choices) { value.errorMessage = `label and choices are required for Choice input '${value.id}'.`; } return value as ChoiceInput; } }; export const getMessageBarType = (type: SelfServeNotificationType): MessageBarType => { switch (type) { case SelfServeNotificationType.info: return MessageBarType.info; case SelfServeNotificationType.warning: return MessageBarType.warning; case SelfServeNotificationType.error: return MessageBarType.error; } };