diff options
Diffstat (limited to 'packages/web-util')
19 files changed, 528 insertions, 48 deletions
diff --git a/packages/web-util/src/forms/DefaultForm.tsx b/packages/web-util/src/forms/DefaultForm.tsx index 1c635e089..338460170 100644 --- a/packages/web-util/src/forms/DefaultForm.tsx +++ b/packages/web-util/src/forms/DefaultForm.tsx @@ -2,14 +2,15 @@ import { Fragment, VNode, h } from "preact"; import { FormProvider, FormProviderProps, FormState } from "./FormProvider.js"; import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js"; import { TranslatedString } from "@gnu-taler/taler-util"; +// import { FlexibleForm } from "./ui-form.js"; /** * Flexible form uses a DoubleColumForm for design * and may have a dynamic properties defined by * behavior function. */ -export interface FlexibleForm<T extends object> { - design: DoubleColumnForm; +export interface FlexibleForm_Deprecated<T extends object> { + design: DoubleColumnForm_Deprecated; behavior?: (form: Partial<T>) => FormState<T>; } @@ -20,9 +21,9 @@ export interface FlexibleForm<T extends object> { * have a description. * Every sections contain a set of fields. */ -export type DoubleColumnForm = Array<DoubleColumnFormSection | undefined>; +export type DoubleColumnForm_Deprecated = Array<DoubleColumnFormSection_Deprecated | undefined>; -export type DoubleColumnFormSection = { +export type DoubleColumnFormSection_Deprecated = { title: TranslatedString; description?: TranslatedString; fields: UIFormField[]; @@ -39,14 +40,14 @@ export function DefaultForm<T extends object>({ onSubmit, children, readOnly, -}: Omit<FormProviderProps<T>, "computeFormState"> & { form: FlexibleForm<T> }): VNode { +}: Omit<FormProviderProps<T>, "computeFormState"> & { form: FlexibleForm_Deprecated<T> }): VNode { return ( <FormProvider initial={initial} onUpdate={onUpdate} onSubmit={onSubmit} readOnly={readOnly} - computeFormState={form.behavior} + // computeFormState={form.behavior} > <div class="space-y-10 divide-y -mt-5 divide-gray-900/10"> {form.design.map((section, i) => { diff --git a/packages/web-util/src/forms/InputAbsoluteTime.stories.tsx b/packages/web-util/src/forms/InputAbsoluteTime.stories.tsx index 3887efe37..0d54c3f69 100644 --- a/packages/web-util/src/forms/InputAbsoluteTime.stories.tsx +++ b/packages/web-util/src/forms/InputAbsoluteTime.stories.tsx @@ -22,7 +22,7 @@ import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { today: AbsoluteTime.now() } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputAmount.stories.tsx b/packages/web-util/src/forms/InputAmount.stories.tsx index d5073ed86..f05887515 100644 --- a/packages/web-util/src/forms/InputAmount.stories.tsx +++ b/packages/web-util/src/forms/InputAmount.stories.tsx @@ -22,7 +22,7 @@ import { AmountJson, Amounts, TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { amount: Amounts.parseOrThrow("USD:10") } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputArray.stories.tsx b/packages/web-util/src/forms/InputArray.stories.tsx index eb61b04e3..143e73f02 100644 --- a/packages/web-util/src/forms/InputArray.stories.tsx +++ b/packages/web-util/src/forms/InputArray.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -49,7 +49,7 @@ const initial: TargetObject = { }] } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputArray.tsx b/packages/web-util/src/forms/InputArray.tsx index ac4617c8c..1ac96437c 100644 --- a/packages/web-util/src/forms/InputArray.tsx +++ b/packages/web-util/src/forms/InputArray.tsx @@ -157,7 +157,8 @@ export function InputArray<T extends object, K extends keyof T>( // elements should be present in the state object since this is expected to be an array //@ts-ignore - return state.elements[selectedIndex]; + // return state.elements[selectedIndex]; + return {} }} onSubmit={(v) => { const newValue = [...list]; diff --git a/packages/web-util/src/forms/InputChoiceHorizontal.stories.tsx b/packages/web-util/src/forms/InputChoiceHorizontal.stories.tsx index e1da89353..786dfe5bc 100644 --- a/packages/web-util/src/forms/InputChoiceHorizontal.stories.tsx +++ b/packages/web-util/src/forms/InputChoiceHorizontal.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "0" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputChoiceStacked.stories.tsx b/packages/web-util/src/forms/InputChoiceStacked.stories.tsx index 89f252e96..9a634d05c 100644 --- a/packages/web-util/src/forms/InputChoiceStacked.stories.tsx +++ b/packages/web-util/src/forms/InputChoiceStacked.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "some initial comment" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputFile.stories.tsx b/packages/web-util/src/forms/InputFile.stories.tsx index 9d9ad0bd7..eff18d071 100644 --- a/packages/web-util/src/forms/InputFile.stories.tsx +++ b/packages/web-util/src/forms/InputFile.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "some initial comment" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputFile.tsx b/packages/web-util/src/forms/InputFile.tsx index 6147eae59..cd0a96d1c 100644 --- a/packages/web-util/src/forms/InputFile.tsx +++ b/packages/web-util/src/forms/InputFile.tsx @@ -1,16 +1,14 @@ import { Fragment, VNode, h } from "preact"; import { UIFormProps } from "./FormProvider.js"; +import { noHandlerPropsAndNoContextForField } from "./InputArray.js"; import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; import { useField } from "./useField.js"; -import { noHandlerPropsAndNoContextForField } from "./InputArray.js"; export function InputFile<T extends object, K extends keyof T>( props: { maxBites: number; accept?: string } & UIFormProps<T, K>, ): VNode { const { - name, label, - placeholder, tooltip, required, help: propsHelp, @@ -26,6 +24,20 @@ export function InputFile<T extends object, K extends keyof T>( if (state.hidden) { return <div />; } + + const valueStr = !value ? "" : value.toString(); + const firstColon = valueStr.indexOf(";"); + + const { fileName, dataUri } = valueStr.startsWith("file:") + ? { + fileName: valueStr.substring(5, firstColon), + dataUri: valueStr.substring(firstColon + 1), + } + : { + fileName: "", + dataUri: valueStr, + }; + return ( <div class="col-span-full"> <LabelWithTooltipMaybeRequired @@ -33,7 +45,7 @@ export function InputFile<T extends object, K extends keyof T>( tooltip={tooltip} required={required} /> - {!value || !(value as string).startsWith("data:image/") ? ( + {!dataUri ? ( <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 py-1"> <div class="text-center"> <svg @@ -51,13 +63,12 @@ export function InputFile<T extends object, K extends keyof T>( {!state.disabled && ( <div class="my-2 flex text-sm leading-6 text-gray-600"> <label - for="file-upload" + for={String(props.name)} class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500" > <span>Upload a file</span> <input - id="file-upload" - name="file-upload" + id={String(props.name)} type="file" class="sr-only" accept={accept} @@ -69,6 +80,7 @@ export function InputFile<T extends object, K extends keyof T>( if (f[0].size > maxBites) { return onChange(undefined!); } + const fileName = f[0].name; return f[0].arrayBuffer().then((b) => { const b64 = window.btoa( new Uint8Array(b).reduce( @@ -76,9 +88,15 @@ export function InputFile<T extends object, K extends keyof T>( "", ), ); - return onChange( - `data:${f[0].type};base64,${b64}` as any, - ); + if (fileName) { + return onChange( + `file:${fileName};data:${f[0].type};base64,${b64}` as any, + ); + } else { + return onChange( + `data:${f[0].type};base64,${b64}` as any, + ); + } }); }} /> @@ -90,10 +108,18 @@ export function InputFile<T extends object, K extends keyof T>( </div> ) : ( <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 relative"> - <img - src={value as string} - class=" h-24 w-full object-cover relative" - /> + {(dataUri as string).startsWith("data:image/") ? ( + <img src={dataUri} class=" h-24 w-full object-cover relative" /> + ) : ( + <div /> + )} + {fileName ? ( + <div class="absolute rounded-lg border flex justify-center text-xl items-center text-white "> + {fileName} + </div> + ) : ( + <Fragment /> + )} {!state.disabled && ( <div diff --git a/packages/web-util/src/forms/InputInteger.stories.tsx b/packages/web-util/src/forms/InputInteger.stories.tsx index 04a6d1049..378736a24 100644 --- a/packages/web-util/src/forms/InputInteger.stories.tsx +++ b/packages/web-util/src/forms/InputInteger.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -38,7 +38,7 @@ const initial: TargetObject = { age: 5, } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputLine.stories.tsx b/packages/web-util/src/forms/InputLine.stories.tsx index 1c62a6164..dea5c142a 100644 --- a/packages/web-util/src/forms/InputLine.stories.tsx +++ b/packages/web-util/src/forms/InputLine.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "some initial comment" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputSelectMultiple.stories.tsx b/packages/web-util/src/forms/InputSelectMultiple.stories.tsx index b1649fecf..ab17545f5 100644 --- a/packages/web-util/src/forms/InputSelectMultiple.stories.tsx +++ b/packages/web-util/src/forms/InputSelectMultiple.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -45,7 +45,7 @@ const initial: TargetObject = { things: [], } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputSelectMultiple.tsx b/packages/web-util/src/forms/InputSelectMultiple.tsx index 12e88fcc1..1bcf85061 100644 --- a/packages/web-util/src/forms/InputSelectMultiple.tsx +++ b/packages/web-util/src/forms/InputSelectMultiple.tsx @@ -13,7 +13,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( max?: number; } & UIFormProps<T, K>, ): VNode { - const { label, choices, placeholder, tooltip, required, unique, max } = props; + const { converter, label, choices, placeholder, tooltip, required, unique, max } = props; //FIXME: remove deprecated const fieldCtx = useField<T, K>(props.name); const { value, onChange, state } = diff --git a/packages/web-util/src/forms/InputSelectOne.stories.tsx b/packages/web-util/src/forms/InputSelectOne.stories.tsx index f87aeda66..2ebde3096 100644 --- a/packages/web-util/src/forms/InputSelectOne.stories.tsx +++ b/packages/web-util/src/forms/InputSelectOne.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { things: "one" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputText.stories.tsx b/packages/web-util/src/forms/InputText.stories.tsx index 8eced7736..60b6ca224 100644 --- a/packages/web-util/src/forms/InputText.stories.tsx +++ b/packages/web-util/src/forms/InputText.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "some initial comment" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputTextArea.stories.tsx b/packages/web-util/src/forms/InputTextArea.stories.tsx index 6713548a8..ab1a695f5 100644 --- a/packages/web-util/src/forms/InputTextArea.stories.tsx +++ b/packages/web-util/src/forms/InputTextArea.stories.tsx @@ -23,7 +23,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { DefaultForm as TestedComponent, - FlexibleForm, + FlexibleForm_Deprecated, } from "./DefaultForm.js"; export default { @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "some initial comment" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/InputToggle.stories.tsx b/packages/web-util/src/forms/InputToggle.stories.tsx index e10098718..fcc57ffe2 100644 --- a/packages/web-util/src/forms/InputToggle.stories.tsx +++ b/packages/web-util/src/forms/InputToggle.stories.tsx @@ -22,7 +22,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { - FlexibleForm, + FlexibleForm_Deprecated, DefaultForm as TestedComponent, } from "./DefaultForm.js"; @@ -43,7 +43,7 @@ const initial: TargetObject = { comment: "some initial comment" } -const form: FlexibleForm<TargetObject> = { +const form: FlexibleForm_Deprecated<TargetObject> = { design: [{ title: "this is a simple form" as TranslatedString, fields: [{ diff --git a/packages/web-util/src/forms/index.ts b/packages/web-util/src/forms/index.ts index 4ff71f197..8c6c23ec5 100644 --- a/packages/web-util/src/forms/index.ts +++ b/packages/web-util/src/forms/index.ts @@ -19,5 +19,6 @@ export * from "./InputTextArea.js" export * from "./InputToggle.js" export * from "./TimePicker.js" export * from "./forms.js" +export * from "./ui-form.js" export * from "./useField.js" diff --git a/packages/web-util/src/forms/ui-form.ts b/packages/web-util/src/forms/ui-form.ts new file mode 100644 index 000000000..9bbc2e96c --- /dev/null +++ b/packages/web-util/src/forms/ui-form.ts @@ -0,0 +1,451 @@ +import { + buildCodecForObject, + buildCodecForUnion, + Codec, + codecForBoolean, + codecForConstString, + codecForLazy, + codecForList, + codecForNumber, + codecForString, + codecForTimestamp, + codecOptional, + Integer, + TalerProtocolTimestamp, +} from "@gnu-taler/taler-util"; + +export type FlexibleForm = DoubleColumnForm; + +export interface DoubleColumnForm { + type: "double-column"; + design: Array<DoubleColumnFormSection>; + // behavior?: (form: Partial<T>) => FormState<T>; +} + +export type DoubleColumnFormSection = { + title: string; + description?: string; + fields: UIFormFieldConfig[]; +}; + +// export interface BaseForm { +// state: TalerExchangeApi.AmlState; +// threshold: AmountJson; +// } + +export type UIFormFieldConfig = + | UIFormFieldConfigAbsoluteTime + | UIFormFieldConfigAmount + | UIFormFieldConfigArray + | UIFormFieldConfigCaption + | UIFormFieldConfigChoiseHorizontal + | UIFormFieldConfigChoiseStacked + | UIFormFieldConfigFile + | UIFormFieldConfigGroup + | UIFormFieldConfigInteger + | UIFormFieldConfigSelectMultiple + | UIFormFieldConfigSelectOne + | UIFormFieldConfigText + | UIFormFieldConfigTextArea + | UIFormFieldConfigToggle; + +type UIFormFieldConfigAbsoluteTime = { + type: "absoluteTime"; + properties: UIFormFieldBaseConfig & { + max?: TalerProtocolTimestamp; + min?: TalerProtocolTimestamp; + pattern: string; + }; +}; + +type UIFormFieldConfigAmount = { + type: "amount"; + properties: UIFormFieldBaseConfig & { + max?: Integer; + min?: Integer; + currency: string; + }; +}; + +type UIFormFieldConfigArray = { + type: "array"; + properties: UIFormFieldBaseConfig & { + // id of the field shown when the array is collapsed + labelFieldId: UIHandlerId; + fields: UIFormFieldConfig[]; + }; +}; + +type UIFormFieldConfigCaption = { + type: "caption"; + properties: UIFieldBaseDescription; +}; + +type UIFormFieldConfigGroup = { + type: "group"; + properties: UIFieldBaseDescription & { + fields: UIFormFieldConfig[]; + }; +}; + +type UIFormFieldConfigChoiseHorizontal = { + type: "choiceHorizontal"; + properties: UIFormFieldBaseConfig & { + choices: Array<SelectUiChoice>; + }; +}; + +type UIFormFieldConfigChoiseStacked = { + type: "choiceStacked"; + properties: UIFormFieldBaseConfig & { + choices: Array<SelectUiChoice>; + }; +}; + +type UIFormFieldConfigFile = { + type: "file"; + properties: UIFormFieldBaseConfig & { + maxBytes?: Integer; + minBytes?: Integer; + // comma-separated list of one or more file types + // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers + accept?: string; + }; +}; +type UIFormFieldConfigInteger = { + type: "integer"; + properties: UIFormFieldBaseConfig & { + max?: Integer; + min?: Integer; + }; +}; + +interface SelectUiChoice { + label: string; + description?: string; + value: string; +} + +type UIFormFieldConfigSelectMultiple = { + type: "selectMultiple"; + properties: UIFormFieldBaseConfig & { + max?: Integer; + min?: Integer; + unique?: boolean; + choices: Array<SelectUiChoice>; + }; +}; +type UIFormFieldConfigSelectOne = { + type: "selectOne"; + properties: UIFormFieldBaseConfig & { + choices: Array<SelectUiChoice>; + }; +}; +type UIFormFieldConfigText = { + type: "text"; + properties: UIFormFieldBaseConfig; +}; +type UIFormFieldConfigTextArea = { + type: "textArea"; + properties: UIFormFieldBaseConfig; +}; +type UIFormFieldConfigToggle = { + type: "toggle"; + properties: UIFormFieldBaseConfig; +}; + +export type UIFieldBaseDescription = { + /* label if the field, visible for the user */ + label: string; + /* long text to be shown on user demand */ + tooltip?: string; + + /* short text to be shown close to the field */ + help?: string; + + /* name of the field, useful for a11y */ + name: string; + + /* if the field should be initialy hidden */ + hidden?: boolean; + /* ui element to show before */ + addonBeforeId?: string; + /* ui element to show after */ + addonAfterId?: string; +}; + +export type UIFormFieldBaseConfig = UIFieldBaseDescription & { + /* example to be shown inside the field */ + placeholder?: string; + + /* show a mark as required */ + required?: boolean; + + /* readonly and dim */ + disabled?: boolean; + + /* conversion id to conver the string into the value type + the id should be known to the ui impl + */ + converterId?: string; + + /* property id of the form */ + id: UIHandlerId; +}; + +declare const __handlerId: unique symbol; +export type UIHandlerId = string & { [__handlerId]: true }; + +// FIXME: validate well formed ui field id +const codecForUiFieldId = codecForString as () => Codec<UIHandlerId>; + +const codecForUIFormFieldBaseDescriptionTemplate = < + T extends UIFieldBaseDescription, +>() => + buildCodecForObject<T>() + .property("addonAfterId", codecOptional(codecForString())) + .property("addonBeforeId", codecOptional(codecForString())) + .property("hidden", codecOptional(codecForBoolean())) + .property("help", codecOptional(codecForString())) + .property("label", codecForString()) + .property("name", codecForString()) + .property("tooltip", codecOptional(codecForString())); + +const codecForUIFormFieldBaseConfigTemplate = < + T extends UIFormFieldBaseConfig, +>() => + codecForUIFormFieldBaseDescriptionTemplate<T>() + .property("id", codecForUiFieldId()) + .property("converterId", codecOptional(codecForString())) + .property("disabled", codecOptional(codecForBoolean())) + .property("required", codecOptional(codecForBoolean())) + .property("placeholder", codecOptional(codecForString())); + +const codecForUIFormFieldBaseConfig = (): Codec<UIFormFieldBaseConfig> => + codecForUIFormFieldBaseConfigTemplate().build("UIFieldToggleProperties"); + +const codecForUIFormFieldAbsoluteTimeConfig = (): Codec< + UIFormFieldConfigAbsoluteTime["properties"] +> => + codecForUIFormFieldBaseConfigTemplate< + UIFormFieldConfigAbsoluteTime["properties"] + >() + .property("pattern", codecForString()) + .property("max", codecOptional(codecForTimestamp)) + .property("min", codecOptional(codecForTimestamp)) + .build("UIFormFieldConfigAbsoluteTime.properties"); + +const codecForUiFormFieldAbsoluteTime = + (): Codec<UIFormFieldConfigAbsoluteTime> => + buildCodecForObject<UIFormFieldConfigAbsoluteTime>() + .property("type", codecForConstString("absoluteTime")) + .property("properties", codecForUIFormFieldAbsoluteTimeConfig()) + .build("UIFormFieldConfigAbsoluteTime"); + +const codecForUIFormFieldAmountConfig = (): Codec< + UIFormFieldConfigAmount["properties"] +> => + codecForUIFormFieldBaseConfigTemplate<UIFormFieldConfigAmount["properties"]>() + .property("currency", codecForString()) + .property("max", codecOptional(codecForNumber())) + .property("min", codecOptional(codecForNumber())) + .build("UIFormFieldConfigAmount.properties"); + +const codecForUiFormFieldAmount = (): Codec<UIFormFieldConfigAmount> => + buildCodecForObject<UIFormFieldConfigAmount>() + .property("type", codecForConstString("amount")) + .property("properties", codecForUIFormFieldAmountConfig()) + .build("UIFormFieldConfigAmount"); + +const codecForUIFormFieldArrayConfig = (): Codec< + UIFormFieldConfigArray["properties"] +> => + codecForUIFormFieldBaseConfigTemplate<UIFormFieldConfigArray["properties"]>() + .property("labelFieldId", codecForUiFieldId()) + .property("fields", codecForList(codecForUiFormField())) + .build("UIFormFieldConfigArray.properties"); + +const codecForUiFormFieldArray = (): Codec<UIFormFieldConfigArray> => + buildCodecForObject<UIFormFieldConfigArray>() + .property("type", codecForConstString("array")) + .property("properties", codecForUIFormFieldArrayConfig()) + .build("UIFormFieldConfigArray"); + +const codecForUiFormFieldCaption = (): Codec<UIFormFieldConfigCaption> => + buildCodecForObject<UIFormFieldConfigCaption>() + .property("type", codecForConstString("caption")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigCaption"); + +const codecForUiFormSelectUiChoice = (): Codec<SelectUiChoice> => + buildCodecForObject<SelectUiChoice>() + .property("description", codecOptional(codecForString())) + .property("label", codecForString()) + .property("value", codecForString()) + .build("SelectUiChoice"); + +const codecForUIFormFieldWithChoiseConfig = (): Codec< + UIFormFieldConfigChoiseHorizontal["properties"] +> => + codecForUIFormFieldBaseConfigTemplate< + UIFormFieldConfigChoiseHorizontal["properties"] + >() + .property("choices", codecForList(codecForUiFormSelectUiChoice())) + .build("UIFormFieldConfigChoiseHorizontal.properties"); + +const codecForUiFormFieldChoiceHorizontal = + (): Codec<UIFormFieldConfigChoiseHorizontal> => + buildCodecForObject<UIFormFieldConfigChoiseHorizontal>() + .property("type", codecForConstString("choiceHorizontal")) + .property("properties", codecForUIFormFieldWithChoiseConfig()) + .build("UIFormFieldConfigChoiseHorizontal"); + +const codecForUiFormFieldChoiceStacked = + (): Codec<UIFormFieldConfigChoiseStacked> => + buildCodecForObject<UIFormFieldConfigChoiseStacked>() + .property("type", codecForConstString("choiceStacked")) + .property("properties", codecForUIFormFieldWithChoiseConfig()) + .build("UIFormFieldConfigChoiseStacked"); + +const codecForUIFormFieldFileConfig = (): Codec< + UIFormFieldConfigFile["properties"] +> => + codecForUIFormFieldBaseConfigTemplate<UIFormFieldConfigFile["properties"]>() + .property("accept", codecOptional(codecForString())) + .property("maxBytes", codecOptional(codecForNumber())) + .property("minBytes", codecOptional(codecForNumber())) + .build("UIFormFieldConfigFile.properties"); + +const codecForUiFormFieldFile = (): Codec<UIFormFieldConfigFile> => + buildCodecForObject<UIFormFieldConfigFile>() + .property("type", codecForConstString("file")) + .property("properties", codecForUIFormFieldFileConfig()) + .build("UIFormFieldConfigFile"); + +const codecForUIFormFieldWithFieldsConfig = (): Codec< + UIFormFieldConfigGroup["properties"] +> => + codecForUIFormFieldBaseDescriptionTemplate< + UIFormFieldConfigGroup["properties"] + >() + .property("fields", codecForList(codecForUiFormField())) + .build("UIFormFieldConfigGroup.properties"); + +const codecForUiFormFieldGroup = (): Codec<UIFormFieldConfigGroup> => + buildCodecForObject<UIFormFieldConfigGroup>() + .property("type", codecForConstString("group")) + .property("properties", codecForUIFormFieldWithFieldsConfig()) + .build("UiFormFieldGroup"); + +const codecForUiFormFieldInteger = (): Codec<UIFormFieldConfigInteger> => + buildCodecForObject<UIFormFieldConfigInteger>() + .property("type", codecForConstString("integer")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigInteger"); + +const codecForUIFormFieldSelectMultipleConfig = (): Codec< + UIFormFieldConfigSelectMultiple["properties"] +> => + codecForUIFormFieldBaseConfigTemplate< + UIFormFieldConfigSelectMultiple["properties"] + >() + .property("max", codecOptional(codecForNumber())) + .property("min", codecOptional(codecForNumber())) + .property("unique", codecOptional(codecForBoolean())) + .property("choices", codecForList(codecForUiFormSelectUiChoice())) + .build("UIFormFieldConfigSelectMultiple.properties"); + +const codecForUiFormFieldSelectMultiple = + (): Codec<UIFormFieldConfigSelectMultiple> => + buildCodecForObject<UIFormFieldConfigSelectMultiple>() + .property("type", codecForConstString("selectMultiple")) + .property("properties", codecForUIFormFieldSelectMultipleConfig()) + .build("UiFormFieldSelectMultiple"); + +const codecForUiFormFieldSelectOne = (): Codec<UIFormFieldConfigSelectOne> => + buildCodecForObject<UIFormFieldConfigSelectOne>() + .property("type", codecForConstString("selectOne")) + .property("properties", codecForUIFormFieldWithChoiseConfig()) + .build("UIFormFieldConfigSelectOne"); + +const codecForUiFormFieldText = (): Codec<UIFormFieldConfigText> => + buildCodecForObject<UIFormFieldConfigText>() + .property("type", codecForConstString("text")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigText"); + +const codecForUiFormFieldTextArea = (): Codec<UIFormFieldConfigTextArea> => + buildCodecForObject<UIFormFieldConfigTextArea>() + .property("type", codecForConstString("textArea")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigTextArea"); + +const codecForUiFormFieldToggle = (): Codec<UIFormFieldConfigToggle> => + buildCodecForObject<UIFormFieldConfigToggle>() + .property("type", codecForConstString("toggle")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigToggle"); + +const codecForUiFormField = (): Codec<UIFormFieldConfig> => + buildCodecForUnion<UIFormFieldConfig>() + .discriminateOn("type") + .alternative("array", codecForLazy(codecForUiFormFieldArray)) + .alternative("group", codecForLazy(codecForUiFormFieldGroup)) + .alternative("absoluteTime", codecForUiFormFieldAbsoluteTime()) + .alternative("amount", codecForUiFormFieldAmount()) + .alternative("caption", codecForUiFormFieldCaption()) + .alternative("choiceHorizontal", codecForUiFormFieldChoiceHorizontal()) + .alternative("choiceStacked", codecForUiFormFieldChoiceStacked()) + .alternative("file", codecForUiFormFieldFile()) + .alternative("integer", codecForUiFormFieldInteger()) + .alternative("selectMultiple", codecForUiFormFieldSelectMultiple()) + .alternative("selectOne", codecForUiFormFieldSelectOne()) + .alternative("text", codecForUiFormFieldText()) + .alternative("textArea", codecForUiFormFieldTextArea()) + .alternative("toggle", codecForUiFormFieldToggle()) + .build("UIFormField"); + +const codecForDoubleColumnFormSection = (): Codec<DoubleColumnFormSection> => + buildCodecForObject<DoubleColumnFormSection>() + .property("title", codecForString()) + .property("description", codecOptional(codecForString())) + .property("fields", codecForList(codecForUiFormField())) + .build("DoubleColumnFormSection"); + +const codecForDoubleColumnForm = (): Codec<DoubleColumnForm> => + buildCodecForObject<DoubleColumnForm>() + .property("type", codecForConstString("double-column")) + .property("design", codecForList(codecForDoubleColumnFormSection())) + .build("DoubleColumnForm"); + +const codecForFlexibleForm = (): Codec<FlexibleForm> => + buildCodecForUnion<FlexibleForm>() + .discriminateOn("type") + .alternative("double-column", codecForDoubleColumnForm()) + .build<FlexibleForm>("FlexibleForm"); + +const codecForFormMetadata = (): Codec<FormMetadata> => + buildCodecForObject<FormMetadata>() + .property("label", codecForString()) + .property("id", codecForString()) + .property("version", codecForNumber()) + .property("config", codecForFlexibleForm()) + .build("FormMetadata"); + +export const codecForUIForms = (): Codec<UiForms> => + buildCodecForObject<UiForms>() + .property("forms", codecForList(codecForFormMetadata())) + .build("UiForms"); + +export type FormMetadata = { + label: string; + id: string; + version: number; + config: FlexibleForm; +}; + +export interface UiForms { + // Where libeufin backend is localted + // default: window.origin without "webui/" + forms: Array<FormMetadata>; +} |