summaryrefslogtreecommitdiff
path: root/packages/web-util/src/forms/ui-form.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web-util/src/forms/ui-form.ts')
-rw-r--r--packages/web-util/src/forms/ui-form.ts363
1 files changed, 363 insertions, 0 deletions
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..012499d6d
--- /dev/null
+++ b/packages/web-util/src/forms/ui-form.ts
@@ -0,0 +1,363 @@
+import {
+ buildCodecForObject,
+ buildCodecForUnion,
+ Codec,
+ codecForBoolean,
+ codecForConstString,
+ codecForLazy,
+ codecForList,
+ codecForNumber,
+ codecForString,
+ codecForTimestamp,
+ codecOptional,
+ Integer,
+ TalerProtocolTimestamp,
+} from "@gnu-taler/taler-util";
+
+export type FormConfiguration = DoubleColumnForm;
+
+export type DoubleColumnForm = {
+ type: "double-column";
+ design: DoubleColumnFormSection[];
+ // behavior?: (form: Partial<T>) => FormState<T>;
+};
+
+export type DoubleColumnFormSection = {
+ title: string;
+ description?: string;
+ fields: UIFormElementConfig[];
+};
+
+// export interface BaseForm {
+// state: TalerExchangeApi.AmlState;
+// threshold: AmountJson;
+// }
+
+export type UIFormElementConfig =
+ | UIFormElementGroup
+ | UIFormElementCaption
+ | UIFormFieldAbsoluteTime
+ | UIFormFieldAmount
+ | UIFormFieldArray
+ | UIFormFieldChoiseHorizontal
+ | UIFormFieldChoiseStacked
+ | UIFormFieldFile
+ | UIFormFieldInteger
+ | UIFormFieldSelectMultiple
+ | UIFormFieldSelectOne
+ | UIFormFieldText
+ | UIFormFieldTextArea
+ | UIFormFieldToggle;
+
+type UIFormFieldAbsoluteTime = {
+ type: "absoluteTimeText";
+ max?: TalerProtocolTimestamp;
+ min?: TalerProtocolTimestamp;
+ pattern: string;
+} & UIFormFieldBaseConfig;
+
+type UIFormFieldAmount = {
+ type: "amount";
+ max?: Integer;
+ min?: Integer;
+ currency: string;
+} & UIFormFieldBaseConfig;
+
+type UIFormFieldArray = {
+ type: "array";
+ // id of the field shown when the array is collapsed
+ labelFieldId: UIHandlerId;
+ fields: UIFormElementConfig[];
+} & UIFormFieldBaseConfig;
+
+type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;
+
+type UIFormElementGroup = {
+ type: "group";
+ fields: UIFormElementConfig[];
+} & UIFieldElementDescription;
+
+type UIFormFieldChoiseHorizontal = {
+ type: "choiceHorizontal";
+ choices: Array<SelectUiChoice>;
+} & UIFormFieldBaseConfig;
+
+type UIFormFieldChoiseStacked = {
+ type: "choiceStacked";
+ choices: Array<SelectUiChoice>;
+} & UIFormFieldBaseConfig;
+
+type UIFormFieldFile = {
+ type: "file";
+ 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;
+} & UIFormFieldBaseConfig;
+
+type UIFormFieldInteger = {
+ type: "integer";
+ max?: Integer;
+ min?: Integer;
+} & UIFormFieldBaseConfig;
+
+interface SelectUiChoice {
+ label: string;
+ description?: string;
+ value: string;
+}
+
+type UIFormFieldSelectMultiple = {
+ type: "selectMultiple";
+ max?: Integer;
+ min?: Integer;
+ unique?: boolean;
+ choices: Array<SelectUiChoice>;
+} & UIFormFieldBaseConfig;
+
+type UIFormFieldSelectOne = {
+ type: "selectOne";
+ choices: Array<SelectUiChoice>;
+} & UIFormFieldBaseConfig;
+type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;
+type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
+type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig;
+
+export type UIFieldElementDescription = {
+ /* 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, usually below and dimmer*/
+ help?: string;
+
+ /* name of the field, useful for a11y */
+ name: string;
+
+ /* if the field should be initially hidden */
+ hidden?: boolean;
+
+ /* ui element to show before */
+ addonBeforeId?: string;
+
+ /* ui element to show after */
+ addonAfterId?: string;
+};
+
+export type UIFormFieldBaseConfig = UIFieldElementDescription & {
+ /* example to be shown inside the field */
+ placeholder?: string;
+
+ /* show a mark as required */
+ required?: boolean;
+
+ /* readonly and dim */
+ disabled?: boolean;
+
+ /* conversion id to convert 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 UIFieldElementDescription,
+>() =>
+ 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 codecForUiFormFieldAbsoluteTime = (): Codec<UIFormFieldAbsoluteTime> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldAbsoluteTime>()
+ .property("type", codecForConstString("absoluteTimeText"))
+ .property("pattern", codecForString())
+ .property("max", codecOptional(codecForTimestamp))
+ .property("min", codecOptional(codecForTimestamp))
+ .build("UIFormFieldAbsoluteTime");
+
+const codecForUiFormFieldAmount = (): Codec<UIFormFieldAmount> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldAmount>()
+ .property("type", codecForConstString("amount"))
+ .property("currency", codecForString())
+ .property("max", codecOptional(codecForNumber()))
+ .property("min", codecOptional(codecForNumber()))
+ .build("UIFormFieldAmount");
+
+const codecForUiFormFieldArray = (): Codec<UIFormFieldArray> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldArray>()
+ .property("type", codecForConstString("array"))
+ .property("labelFieldId", codecForUiFieldId())
+ .property("tooltip", codecOptional(codecForString()))
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ .property("fields", codecForList(codecForUiFormField()))
+ .build("UIFormFieldArray");
+
+const codecForUiFormFieldCaption = (): Codec<UIFormElementCaption> =>
+ codecForUIFormFieldBaseDescriptionTemplate<UIFormElementCaption>()
+ .property("type", codecForConstString("caption"))
+ .build("UIFormFieldCaption");
+
+const codecForUiFormSelectUiChoice = (): Codec<SelectUiChoice> =>
+ buildCodecForObject<SelectUiChoice>()
+ .property("description", codecOptional(codecForString()))
+ .property("label", codecForString())
+ .property("value", codecForString())
+ .build("SelectUiChoice");
+
+const codecForUiFormFieldChoiceHorizontal =
+ (): Codec<UIFormFieldChoiseHorizontal> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldChoiseHorizontal>()
+ .property("type", codecForConstString("choiceHorizontal"))
+ .property("choices", codecForList(codecForUiFormSelectUiChoice()))
+ .build("UIFormFieldChoiseHorizontal");
+
+const codecForUiFormFieldChoiceStacked = (): Codec<UIFormFieldChoiseStacked> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldChoiseStacked>()
+ .property("type", codecForConstString("choiceStacked"))
+ .property("choices", codecForList(codecForUiFormSelectUiChoice()))
+ .build("UIFormFieldChoiseStacked");
+
+const codecForUiFormFieldFile = (): Codec<UIFormFieldFile> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldFile>()
+ .property("type", codecForConstString("file"))
+ .property("accept", codecOptional(codecForString()))
+ .property("maxBytes", codecOptional(codecForNumber()))
+ .property("minBytes", codecOptional(codecForNumber()))
+ .build("UIFormFieldFile");
+
+const codecForUiFormFieldGroup = (): Codec<UIFormElementGroup> =>
+ codecForUIFormFieldBaseDescriptionTemplate<UIFormElementGroup>()
+ .property("type", codecForConstString("group"))
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ .property("fields", codecForList(codecForUiFormField()))
+ .build("UiFormFieldGroup");
+
+const codecForUiFormFieldInteger = (): Codec<UIFormFieldInteger> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldInteger>()
+ .property("type", codecForConstString("integer"))
+ // .property("properties", codecForUIFormFieldBaseConfig())
+ .property("max", codecOptional(codecForNumber()))
+ .property("min", codecOptional(codecForNumber()))
+ .build("UIFormFieldInteger");
+
+const codecForUiFormFieldSelectMultiple =
+ (): Codec<UIFormFieldSelectMultiple> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectMultiple>()
+ .property("type", codecForConstString("selectMultiple"))
+ .property("max", codecOptional(codecForNumber()))
+ .property("min", codecOptional(codecForNumber()))
+ .property("unique", codecOptional(codecForBoolean()))
+ .property("choices", codecForList(codecForUiFormSelectUiChoice()))
+ .build("UiFormFieldSelectMultiple");
+
+const codecForUiFormFieldSelectOne = (): Codec<UIFormFieldSelectOne> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectOne>()
+ .property("type", codecForConstString("selectOne"))
+ .property("choices", codecForList(codecForUiFormSelectUiChoice()))
+ .build("UIFormFieldSelectOne");
+
+const codecForUiFormFieldText = (): Codec<UIFormFieldText> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldText>()
+ .property("type", codecForConstString("text"))
+ .build("UIFormFieldText");
+
+const codecForUiFormFieldTextArea = (): Codec<UIFormFieldTextArea> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldTextArea>()
+ .property("type", codecForConstString("textArea"))
+ .build("UIFormFieldTextArea");
+
+const codecForUiFormFieldToggle = (): Codec<UIFormFieldToggle> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldToggle>()
+ .property("type", codecForConstString("toggle"))
+ .build("UIFormFieldToggle");
+
+const codecForUiFormField = (): Codec<UIFormElementConfig> =>
+ buildCodecForUnion<UIFormElementConfig>()
+ .discriminateOn("type")
+ .alternative("array", codecForLazy(codecForUiFormFieldArray))
+ .alternative("group", codecForLazy(codecForUiFormFieldGroup))
+ .alternative("absoluteTimeText", 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 codecForFormConfiguration = (): Codec<FormConfiguration> =>
+ buildCodecForUnion<FormConfiguration>()
+ .discriminateOn("type")
+ .alternative("double-column", codecForDoubleColumnForm())
+ .build<FormConfiguration>("FormConfiguration");
+
+const codecForFormMetadata = (): Codec<FormMetadata> =>
+ buildCodecForObject<FormMetadata>()
+ .property("label", codecForString())
+ .property("id", codecForString())
+ .property("version", codecForNumber())
+ .property("config", codecForFormConfiguration())
+ .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: FormConfiguration;
+};
+
+export interface UiForms {
+ // Where libeufin backend is localted
+ // default: window.origin without "webui/"
+ forms: Array<FormMetadata>;
+}