import { is, predicates } from "@sealcode/ts-predicates"; import { Context } from "koa"; import { FormDataValue } from "../form-types.js"; import { FieldParseResult, FormField } from "./field.js"; import { Table as TableControl, TABLE_COLUMN_FIELD_INDEX_PLACEHOLDER, } from "../controls/table.js"; import { FormControl } from "../controls/form-control.js"; export type ExtractFormFieldParsed = Field extends FormField ? NonNullable : Field extends FormField ? T : never; export type TableFieldParsed> = { [field_name in keyof F]: ExtractFormFieldParsed; }[]; export class Table> extends FormField< false, TableFieldParsed > { constructor(public columns: F) { super(false); } getEmptyValue() { return []; } setName(name: string) { super.setName(name); for (const [column_name, field] of Object.entries(this.columns)) { field.setName( `${this.name}[${TABLE_COLUMN_FIELD_INDEX_PLACEHOLDER}][${column_name}]` ); } return this; } async parse( ctx: Context, raw_value: FormDataValue ): Promise>> { // we expect an array-like object: {"0": {...}, ...etc} if (!is(raw_value, predicates.object)) { return { parsable: false, error: "Expected an object", parsed_value: null, }; } const result: { [field_name in keyof F]: unknown }[] = []; for (const row_number in raw_value) { const row_data = raw_value[row_number]; const parsed_row: Record = {}; if (!is(row_data, predicates.object)) { // eslint-disable-next-line @typescript-eslint/no-base-to-string throw new Error(`Expected object, got ${String(row_data)}`); } for (const key of Object.keys(row_data)) { const field = this.columns[key]; if (field == undefined) { return { parsable: false, error: `Unknown column: ${key}`, parsed_value: null, }; } // eslint-disable-next-line no-await-in-loop const parse_result = await field.parse( ctx, // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access (row_data as any)[key] ); if (!parse_result.parsable) { const message = `Invalid value for row ${row_number}.${key}: ${parse_result.error}`; console.warn( `Invalid value for row ${row_number}.${key}: ${parse_result.error}` ); return { parsable: false, error: message, parsed_value: null, }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any parsed_row[key] = parse_result.parsed_value as any; } result.push(parsed_row as { [field_name in keyof F]: unknown }); } const ret = { parsed_value: result, parsable: true, error: null }; return ret as FieldParseResult>; } getControl(): TableControl { return new TableControl(this, { label: this.label || this.name, subfield_controls: Object.fromEntries( Object.entries(this.columns).map(([column_name, field]) => [ column_name as keyof F, field.getControl(), ]) ) as Record, allow_adding: true, make_new_row: async () => Object.fromEntries( Object.keys(this.columns).map((key) => [key, ""]) ) as Record, }); } }