diff options
Diffstat (limited to 'packages/web-util/src/forms/ui-form.ts')
-rw-r--r-- | packages/web-util/src/forms/ui-form.ts | 363 |
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>; +} |