/* eslint-disable @typescript-eslint/no-explicit-any */ import Router from "@koa/router"; import { hasShape, predicates } from "@sealcode/ts-predicates"; import { Context } from "koa"; import qs from "qs"; import { Field, FieldsetOutput } from "sealious"; import { tempstream } from "tempstream"; import { FormField } from "../fields/field.js"; import { TextBasedSimpleField } from "../fields/simple-form-field.js"; import { StructuredArray as StructuredArrayField } from "../fields/structured-array.js"; import { FormDataValue } from "../form-types.js"; import { Derived } from "./derived.js"; import { FormControl, FormControlContext } from "./form-control.js"; import { FormFieldControl } from "./form-field-control.js"; export type UnboundControlStorage< F extends { new (...args: any): FormFieldControl } = { new (...args: any): FormFieldControl; }, > = [F, string, ConstructorParameters]; export function UnboundControl< F extends { new (...args: any): FormFieldControl }, >( controlClass: F, arbitrary_field_name: string, params: ConstructorParameters["1"] ): UnboundControlStorage { return [controlClass, arbitrary_field_name, params]; } export class StructuredArray< Subfields extends Record>, > extends FormFieldControl { constructor( public field: StructuredArrayField, public item_controls: Array ) { super([field]); } getFrameID() { return `array-frame-${this.field.name}`; } getInnerFormID() { return this.getFrameID() + "-form"; } getActionURL(form_id?: string, action?: Record) { return `${this.getFrameID()}${ form_id || action ? "?" : "" }${qs.stringify({ form_id, action })}`; } async getDefaultItemBody() { return {}; } makeSubfield(_field_name: string): FormField { return new TextBasedSimpleField(false); } async renderItemControl( ctx: Context, form_id: string, control: FormControl | UnboundControlStorage, item: FieldsetOutput, index: number ) { const fctx = { ctx, data: { raw_values: {}, messages: [], field_messages: {} }, messages: [], field_name_prefix: "", form_id, validate: false, } as FormControlContext; if (Array.isArray(control)) { const [constructor, field_name, params] = control; const field = this.makeSubfield(field_name); if (!field) { console.warn( `makeSubfield returned null for field ${field_name}` ); return ""; } field.setName(`${this.field.name}[data][${index}][${field_name}]`); const hidden_shadow = new Derived( [field], async (values, { inner_form_id, field_name }) => /* HTML */ ``, async () => ({ inner_form_id: this.getInnerFormID(), field_name: field.name, }) ); return tempstream`${new constructor(field, params).render({ ...fctx, data: { raw_values: { [field.name]: item[field_name] as FormDataValue, }, messages: [], field_messages: {}, }, })}${hidden_shadow.render({ ...fctx, data: { raw_values: { [field.name]: item[field_name] as FormDataValue, }, messages: [], field_messages: {}, }, form_id: this.getInnerFormID(), })}`; } else { return control.render(fctx); } } async renderItem( ctx: Context, form_id: string, item: FieldsetOutput, index: number ) { return tempstream /* HTML */ `
${this.item_controls.map((control) => this.renderItemControl(ctx, form_id, control, item, index) )}
`; } async getItems(ctx: Context, raw_values: Record) { return (await this.field.getParsedValue(ctx, raw_values)) .parsed as FieldsetOutput[]; } async _render( ctx: Context, form_id: string, raw_values: Record ) { const items = await this.getItems(ctx, raw_values); return tempstream /* HTML */ ` Reverse Single Reference ${items.map((item, index) => this.renderItem(ctx, form_id, item, index) )}
`; } render(fctx: FormControlContext) { return this._render(fctx.ctx, fctx.form_id, fctx.data.raw_values); } async getTargetItemID(ctx: Context): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return ctx.params.id as string; } mount(router: Router) { router.post(this.getActionURL(), async (ctx) => { const form_id = ctx.query.form_id; if (typeof form_id != "string") { throw new Error("Missing form_id query param"); } const item = await this.field.sealious_field.collection.getByID( ctx.$context, await this.getTargetItemID(ctx) ); if (!hasShape({ action: predicates.object }, ctx.$body)) { throw new Error("Missing action param"); } const field_data = (ctx.$body as Record)[ this.field.name ] || { data: [] }; if ( !hasShape( { data: predicates.array( this.field.sealious_field.value_predicate ), }, field_data ) ) { throw new Error("missing data atribute in action description"); } item.set(this.field.name, { ...ctx.$body.action, data: field_data.data || [], }); await item.save(ctx.$context); ctx.body = this._render( ctx, form_id, (await item.getDecodedBody( ctx.$context, {} )) as unknown as Record ); }); } }