From 35fee72ef3d75b7a9681353ab7a1ca5bacff150e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 3 May 2024 18:23:01 -0300 Subject: form implemented, moving functions to web-utils some final testing still pedning --- packages/web-util/src/forms/DefaultForm.tsx | 13 +- .../src/forms/InputAbsoluteTime.stories.tsx | 4 +- .../web-util/src/forms/InputAmount.stories.tsx | 4 +- packages/web-util/src/forms/InputArray.stories.tsx | 4 +- packages/web-util/src/forms/InputArray.tsx | 3 +- .../src/forms/InputChoiceHorizontal.stories.tsx | 4 +- .../src/forms/InputChoiceStacked.stories.tsx | 4 +- packages/web-util/src/forms/InputFile.stories.tsx | 4 +- packages/web-util/src/forms/InputFile.tsx | 54 ++- .../web-util/src/forms/InputInteger.stories.tsx | 4 +- packages/web-util/src/forms/InputLine.stories.tsx | 4 +- .../src/forms/InputSelectMultiple.stories.tsx | 4 +- .../web-util/src/forms/InputSelectMultiple.tsx | 2 +- .../web-util/src/forms/InputSelectOne.stories.tsx | 4 +- packages/web-util/src/forms/InputText.stories.tsx | 4 +- .../web-util/src/forms/InputTextArea.stories.tsx | 4 +- .../web-util/src/forms/InputToggle.stories.tsx | 4 +- packages/web-util/src/forms/index.ts | 1 + packages/web-util/src/forms/ui-form.ts | 451 +++++++++++++++++++++ 19 files changed, 528 insertions(+), 48 deletions(-) create mode 100644 packages/web-util/src/forms/ui-form.ts (limited to 'packages/web-util/src') 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 { - design: DoubleColumnForm; +export interface FlexibleForm_Deprecated { + design: DoubleColumnForm_Deprecated; behavior?: (form: Partial) => FormState; } @@ -20,9 +21,9 @@ export interface FlexibleForm { * have a description. * Every sections contain a set of fields. */ -export type DoubleColumnForm = Array; +export type DoubleColumnForm_Deprecated = Array; -export type DoubleColumnFormSection = { +export type DoubleColumnFormSection_Deprecated = { title: TranslatedString; description?: TranslatedString; fields: UIFormField[]; @@ -39,14 +40,14 @@ export function DefaultForm({ onSubmit, children, readOnly, -}: Omit, "computeFormState"> & { form: FlexibleForm }): VNode { +}: Omit, "computeFormState"> & { form: FlexibleForm_Deprecated }): VNode { return (
{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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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( // 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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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( props: { maxBites: number; accept?: string } & UIFormProps, ): VNode { const { - name, label, - placeholder, tooltip, required, help: propsHelp, @@ -26,6 +24,20 @@ export function InputFile( if (state.hidden) { return
; } + + 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 (
( tooltip={tooltip} required={required} /> - {!value || !(value as string).startsWith("data:image/") ? ( + {!dataUri ? (
( {!state.disabled && (
) : (
- + {(dataUri as string).startsWith("data:image/") ? ( + + ) : ( +
+ )} + {fileName ? ( +
+ {fileName} +
+ ) : ( + + )} {!state.disabled && (
= { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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( max?: number; } & UIFormProps, ): 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(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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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 = { +const form: FlexibleForm_Deprecated = { 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; + // behavior?: (form: Partial) => FormState; +} + +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; + }; +}; + +type UIFormFieldConfigChoiseStacked = { + type: "choiceStacked"; + properties: UIFormFieldBaseConfig & { + choices: Array; + }; +}; + +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; + }; +}; +type UIFormFieldConfigSelectOne = { + type: "selectOne"; + properties: UIFormFieldBaseConfig & { + choices: Array; + }; +}; +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; + +const codecForUIFormFieldBaseDescriptionTemplate = < + T extends UIFieldBaseDescription, +>() => + buildCodecForObject() + .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() + .property("id", codecForUiFieldId()) + .property("converterId", codecOptional(codecForString())) + .property("disabled", codecOptional(codecForBoolean())) + .property("required", codecOptional(codecForBoolean())) + .property("placeholder", codecOptional(codecForString())); + +const codecForUIFormFieldBaseConfig = (): Codec => + 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 => + buildCodecForObject() + .property("type", codecForConstString("absoluteTime")) + .property("properties", codecForUIFormFieldAbsoluteTimeConfig()) + .build("UIFormFieldConfigAbsoluteTime"); + +const codecForUIFormFieldAmountConfig = (): Codec< + UIFormFieldConfigAmount["properties"] +> => + codecForUIFormFieldBaseConfigTemplate() + .property("currency", codecForString()) + .property("max", codecOptional(codecForNumber())) + .property("min", codecOptional(codecForNumber())) + .build("UIFormFieldConfigAmount.properties"); + +const codecForUiFormFieldAmount = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("amount")) + .property("properties", codecForUIFormFieldAmountConfig()) + .build("UIFormFieldConfigAmount"); + +const codecForUIFormFieldArrayConfig = (): Codec< + UIFormFieldConfigArray["properties"] +> => + codecForUIFormFieldBaseConfigTemplate() + .property("labelFieldId", codecForUiFieldId()) + .property("fields", codecForList(codecForUiFormField())) + .build("UIFormFieldConfigArray.properties"); + +const codecForUiFormFieldArray = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("array")) + .property("properties", codecForUIFormFieldArrayConfig()) + .build("UIFormFieldConfigArray"); + +const codecForUiFormFieldCaption = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("caption")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigCaption"); + +const codecForUiFormSelectUiChoice = (): Codec => + buildCodecForObject() + .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 => + buildCodecForObject() + .property("type", codecForConstString("choiceHorizontal")) + .property("properties", codecForUIFormFieldWithChoiseConfig()) + .build("UIFormFieldConfigChoiseHorizontal"); + +const codecForUiFormFieldChoiceStacked = + (): Codec => + buildCodecForObject() + .property("type", codecForConstString("choiceStacked")) + .property("properties", codecForUIFormFieldWithChoiseConfig()) + .build("UIFormFieldConfigChoiseStacked"); + +const codecForUIFormFieldFileConfig = (): Codec< + UIFormFieldConfigFile["properties"] +> => + codecForUIFormFieldBaseConfigTemplate() + .property("accept", codecOptional(codecForString())) + .property("maxBytes", codecOptional(codecForNumber())) + .property("minBytes", codecOptional(codecForNumber())) + .build("UIFormFieldConfigFile.properties"); + +const codecForUiFormFieldFile = (): Codec => + buildCodecForObject() + .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 => + buildCodecForObject() + .property("type", codecForConstString("group")) + .property("properties", codecForUIFormFieldWithFieldsConfig()) + .build("UiFormFieldGroup"); + +const codecForUiFormFieldInteger = (): Codec => + buildCodecForObject() + .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 => + buildCodecForObject() + .property("type", codecForConstString("selectMultiple")) + .property("properties", codecForUIFormFieldSelectMultipleConfig()) + .build("UiFormFieldSelectMultiple"); + +const codecForUiFormFieldSelectOne = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("selectOne")) + .property("properties", codecForUIFormFieldWithChoiseConfig()) + .build("UIFormFieldConfigSelectOne"); + +const codecForUiFormFieldText = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("text")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigText"); + +const codecForUiFormFieldTextArea = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("textArea")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigTextArea"); + +const codecForUiFormFieldToggle = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("toggle")) + .property("properties", codecForUIFormFieldBaseConfig()) + .build("UIFormFieldConfigToggle"); + +const codecForUiFormField = (): Codec => + buildCodecForUnion() + .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 => + buildCodecForObject() + .property("title", codecForString()) + .property("description", codecOptional(codecForString())) + .property("fields", codecForList(codecForUiFormField())) + .build("DoubleColumnFormSection"); + +const codecForDoubleColumnForm = (): Codec => + buildCodecForObject() + .property("type", codecForConstString("double-column")) + .property("design", codecForList(codecForDoubleColumnFormSection())) + .build("DoubleColumnForm"); + +const codecForFlexibleForm = (): Codec => + buildCodecForUnion() + .discriminateOn("type") + .alternative("double-column", codecForDoubleColumnForm()) + .build("FlexibleForm"); + +const codecForFormMetadata = (): Codec => + buildCodecForObject() + .property("label", codecForString()) + .property("id", codecForString()) + .property("version", codecForNumber()) + .property("config", codecForFlexibleForm()) + .build("FormMetadata"); + +export const codecForUIForms = (): Codec => + buildCodecForObject() + .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; +} -- cgit v1.2.3