aboutsummaryrefslogtreecommitdiff
path: root/packages/web-util/src/forms
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web-util/src/forms')
-rw-r--r--packages/web-util/src/forms/DefaultForm.tsx13
-rw-r--r--packages/web-util/src/forms/InputAbsoluteTime.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputAmount.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputArray.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputArray.tsx3
-rw-r--r--packages/web-util/src/forms/InputChoiceHorizontal.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputChoiceStacked.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputFile.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputFile.tsx54
-rw-r--r--packages/web-util/src/forms/InputInteger.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputLine.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputSelectMultiple.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputSelectMultiple.tsx2
-rw-r--r--packages/web-util/src/forms/InputSelectOne.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputText.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputTextArea.stories.tsx4
-rw-r--r--packages/web-util/src/forms/InputToggle.stories.tsx4
-rw-r--r--packages/web-util/src/forms/index.ts1
-rw-r--r--packages/web-util/src/forms/ui-form.ts451
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>;
+}