import { h as create, Fragment, VNode } from "preact"; import { Caption } from "./Caption.js"; import { Group } from "./Group.js"; import { InputAbsoluteTime } from "./InputAbsoluteTime.js"; import { InputAmount } from "./InputAmount.js"; import { InputArray } from "./InputArray.js"; import { InputChoiceHorizontal } from "./InputChoiceHorizontal.js"; import { InputChoiceStacked } from "./InputChoiceStacked.js"; import { InputFile } from "./InputFile.js"; import { InputInteger } from "./InputInteger.js"; import { InputSelectMultiple } from "./InputSelectMultiple.js"; import { InputSelectOne } from "./InputSelectOne.js"; import { InputText } from "./InputText.js"; import { InputTextArea } from "./InputTextArea.js"; import { InputToggle } from "./InputToggle.js"; import { Addon, StringConverter, UIFieldHandler } from "./FormProvider.js"; import { InternationalizationAPI, UIFieldElementDescription } from "../index.browser.js"; import { assertUnreachable, TranslatedString } from "@gnu-taler/taler-util"; import {UIFormFieldBaseConfig, UIFormElementConfig} from "./ui-form.js"; /** * Constrain the type with the ui props */ type FieldType = { group: Parameters[0]; caption: Parameters[0]; array: Parameters>[0]; file: Parameters>[0]; selectOne: Parameters>[0]; selectMultiple: Parameters>[0]; text: Parameters>[0]; textArea: Parameters>[0]; choiceStacked: Parameters>[0]; choiceHorizontal: Parameters>[0]; absoluteTimeText: Parameters>[0]; integer: Parameters>[0]; toggle: Parameters>[0]; amount: Parameters>[0]; }; /** * List all the form fields so typescript can type-check the form instance */ export type UIFormField = | { type: "group"; properties: FieldType["group"] } | { type: "caption"; properties: FieldType["caption"] } | { type: "array"; properties: FieldType["array"] } | { type: "file"; properties: FieldType["file"] } | { type: "amount"; properties: FieldType["amount"] } | { type: "selectOne"; properties: FieldType["selectOne"] } | { type: "selectMultiple"; properties: FieldType["selectMultiple"]; } | { type: "text"; properties: FieldType["text"] } | { type: "textArea"; properties: FieldType["textArea"] } | { type: "choiceStacked"; properties: FieldType["choiceStacked"]; } | { type: "choiceHorizontal"; properties: FieldType["choiceHorizontal"]; } | { type: "integer"; properties: FieldType["integer"] } | { type: "toggle"; properties: FieldType["toggle"] } | { type: "absoluteTimeText"; properties: FieldType["absoluteTimeText"]; }; type FieldComponentFunction = ( props: FieldType[key], ) => VNode; type UIFormFieldMap = { [key in keyof FieldType]: FieldComponentFunction; }; /** * Maps input type with component implementation */ const UIFormConfiguration: UIFormFieldMap = { group: Group, caption: Caption, //@ts-ignore array: InputArray, text: InputText, //@ts-ignore file: InputFile, textArea: InputTextArea, //@ts-ignore absoluteTimeText: InputAbsoluteTime, //@ts-ignore choiceStacked: InputChoiceStacked, //@ts-ignore choiceHorizontal: InputChoiceHorizontal, integer: InputInteger, //@ts-ignore selectOne: InputSelectOne, //@ts-ignore selectMultiple: InputSelectMultiple, //@ts-ignore toggle: InputToggle, //@ts-ignore amount: InputAmount, }; export function RenderAllFieldsByUiConfig({ fields, }: { fields: UIFormField[]; }): VNode { return create( Fragment, {}, fields.map((field, i) => { const Component = UIFormConfiguration[ field.type ] as FieldComponentFunction; return Component(field.properties); }), ); } // type FormSet = { // Provider: typeof FormProvider; // InputLine: () => typeof InputLine; // InputChoiceHorizontal: () => typeof InputChoiceHorizontal; // }; /** * Helper function that created a typed object. * * @returns */ // export function createNewForm() { // const res: FormSet = { // Provider: FormProvider, // InputLine: () => InputLine, // InputChoiceHorizontal: () => InputChoiceHorizontal, // }; // return { // Provider: res.Provider, // InputLine: res.InputLine(), // InputChoiceHorizontal: res.InputChoiceHorizontal(), // }; // } /** * convert field configuration to render function * * @param i18n_ * @param fieldConfig * @param form * @returns */ export function convertUiField( i18n_: InternationalizationAPI, fieldConfig: UIFormElementConfig[], form: object, getConverterById: GetConverterById, ): UIFormField[] { return fieldConfig.map((config) => { // NON input fields switch (config.type) { case "caption": { const resp: UIFormField = { type: config.type, properties: converBaseFieldsProps(i18n_, config), }; return resp; } case "group": { const resp: UIFormField = { type: config.type, properties: { ...converBaseFieldsProps(i18n_, config), fields: convertUiField(i18n_, config.fields, form, getConverterById), }, }; return resp; } } // Input Fields switch (config.type) { case "array": { return { type: "array", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), labelField: config.labelFieldId, fields: convertUiField(i18n_, config.fields, form, getConverterById), }, } as UIFormField; } case "absoluteTimeText": { return { type: "absoluteTimeText", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), }, } as UIFormField; } case "amount": { return { type: "amount", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), currency: config.currency, }, } as UIFormField; } case "choiceHorizontal": { return { type: "choiceHorizontal", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), choices: config.choices, }, } as UIFormField; } case "choiceStacked": { return { type: "choiceStacked", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), choices: config.choices, }, }as UIFormField; } case "file":{ return { type: "file", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), accept: config.accept, maxBites: config.maxBytes, }, } as UIFormField; } case "integer":{ return { type: "integer", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), }, } as UIFormField; } case "selectMultiple":{ return { type: "selectMultiple", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), choices: config.choices, }, } as UIFormField; } case "selectOne": { return { type: "selectOne", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), choices: config.choices, }, } as UIFormField; } case "text": { return { type: "text", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), }, } as UIFormField; } case "textArea": { return { type: "text", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), }, } as UIFormField; } case "toggle": { return { type: "toggle", properties: { ...converBaseFieldsProps(i18n_, config), ...converInputFieldsProps(form, config, getConverterById), }, } as UIFormField; } default: { assertUnreachable(config); } } }); } function getAddonById(_id: string | undefined): Addon { return undefined!; } type GetConverterById = ( id: string | undefined, config: unknown, ) => StringConverter; function converInputFieldsProps( form: object, p: UIFormFieldBaseConfig, getConverterById: GetConverterById, ) { return { converter: getConverterById(p.converterId, p), handler: getValueDeeper2(form, p.id.split(".")), name: p.name, required: p.required, disabled: p.disabled, help: p.help, placeholder: p.placeholder, tooltip: p.tooltip, label: p.label as TranslatedString, }; } function converBaseFieldsProps( i18n_: InternationalizationAPI, p: UIFieldElementDescription, ) { return { after: getAddonById(p.addonAfterId), before: getAddonById(p.addonBeforeId), hidden: p.hidden, name: p.name, help: i18n_.str`${p.help}`, label: i18n_.str`${p.label}`, tooltip: i18n_.str`${p.tooltip}`, }; } export function getValueDeeper2( object: Record, names: string[], ): UIFieldHandler { if (names.length === 0) return object as UIFieldHandler; const [head, ...rest] = names; if (!head) { return getValueDeeper2(object, rest); } if (object === undefined) { throw Error("handler not found"); } return getValueDeeper2(object[head], rest); }