/* eslint @typescript-eslint/no-explicit-any: off */ import { Context } from "koa"; import { CollectionItem, FieldTypes as SealiousFieldTypes } from "sealious"; import { FormDataValue, FormData } from "../form-types.js"; import { CheckboxedListField } from "./checkboxed-list.js"; import { ExtractedFieldInfo } from "../../utils/extract-fields-from-collection.js"; import { wrapKeyName } from "../../utils/wrap-attributes.js"; export class DeepReverseSingleReferenceList extends CheckboxedListField< false, string > { public item_label_getter: (item: CollectionItem) => string = (item) => item.get("name") as string; constructor( public sealious_field: SealiousFieldTypes.DeepReverseSingleReference ) { super(false, async (ctx: Context) => { const { items } = await ctx.$app.collections[ sealious_field.target_collection ] .list(ctx.$context) .fetch(); return items.map((s) => ({ label: this.item_label_getter(s), value: s.id, })); }); } setItemLabelGetter(getter: (item: CollectionItem) => string) { this.item_label_getter = getter; } getSealiousCreateValue(): never { throw new Error( "This field is not set directly through field value, but instead relies on postSealiousCreate and postSealiousEdit" ); } async postSealiousCreate( ctx: Context, created_item: CollectionItem, form_data: FormData ) { // First, we remove links refering to current item const { items: existing_links } = await ctx.$app.collections[ this.sealious_field.intermediary_collection ] .list(ctx.$context) .filter({ [this.sealious_field.referencing_field]: created_item.id, }) .fetch(); const { parsed: value } = await this.getParsedValue( ctx, form_data.raw_values, false ); const should_exist_ids = Object.entries(value || {}) .filter(([, value]) => value === "on") .map(([key]) => key); const to_delete = existing_links.filter( (link) => !should_exist_ids.includes( link.get( this.sealious_field.intermediary_field_that_points_there ) as string ) ); const to_create_ids = should_exist_ids.filter( (id) => !existing_links .map((link) => // eslint-disable-next-line @typescript-eslint/no-unsafe-return link.get( this.sealious_field .intermediary_field_that_points_there ) ) .includes(id) ); await Promise.all( to_delete.map((item) => { return item.remove(ctx.$context); }) ); // Then, we iterate over all selected values and insert appropriate missing links const promises = to_create_ids.map(async (id) => { await this.sealious_field .getReferencingCollection() .create(ctx.$context, { [this.sealious_field.referencing_field]: created_item.id, [this.sealious_field.intermediary_field_that_points_there]: id, }); }); await Promise.all(promises); } async postSealiousEdit( ctx: Context, edited_item: CollectionItem, value: FormData ) { return this.postSealiousCreate(ctx, edited_item, value); } async sealiousValueToForm( _ctx: Context, value: string[] | null ): Promise { return Object.fromEntries((value || []).map((id) => [id, "on"])); } generateFieldDeclaration( _field_info: ExtractedFieldInfo, vars: { form_field_types: string; sealious_field: string } ): string { return `new ${vars.form_field_types}.${this.constructor.name}(${vars.sealious_field})`; } generateCreateValueGetter(field_info: ExtractedFieldInfo): string { return `${wrapKeyName( field_info.name )}: undefined /* handled below in postSealiousCreate */`; } }