import { extractCollectionClassname } from "../../generate-collections.js"; import extract_fields_from_collection, { ExtractedFieldInfo, } from "../../utils/extract-fields-from-collection.js"; import _locreq from "locreq"; import { DefaultListFilters } from "../../page/default-list-filters.js"; import { toKebabCase, toPascalCase } from "js-convert-case"; import { formatWithPrettier } from "../../utils/prettier.js"; const target_locreq = _locreq(process.cwd()); function fieldImport({ type }: ExtractedFieldInfo): string { if (type == "image") { return ` import type { FilePointer } from "@sealcode/file-manager"; import { imageRouter } from "src/back/image-router.js"; `; } return ""; } export function makeDefaultFilterFields( collection_fields: { name: string; type: string }[] ) { return collection_fields .map((field) => { const type = field.type; return `{ field: "${field.name}", ...${ type in DefaultListFilters ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback` } }`; }) .join(",\n"); } export function makeDefaultDisplayFields( collection_fields: { name: string; type: string }[] ) { return collection_fields .map((field) => { if (field.type == "boolean") { return ` { field: "${field.name}", label: "${field.name}", format: (v: boolean) => (v ? "YES" : "NO"), }`; } if (field.type == "datetime") { return ` { field: "${field.name}", label: "${field.name}", format: (v: number) => { const d = new Date(); d.setTime(v); return d.toString().split(" ").slice(1, 5).join(" "); }, }`; } if (field.type == "image") { return `{ field: "${field.name}", label: "${field.name}", format: async (value: FilePointer|null) => { return value ? imageRouter.image(await value.getPath(), { container: { width: 45, height: 45 }, crop: { width: 45, height: 45 }, alt: "", }): ""; }, }`; } return ` { field: "${field.name}", label: "${field.name}" }`; }) .join(",\n"); } const default_hooks = { pre_header_html: "", post_header_html: "", post_import_js: "", render_item: ( collection_name: string ) => ` async renderItem(_ctx: Context, item: CollectionItem) { return {displayFields.map(({ field, format }) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any const value = item.get(field as any); return {format ? format(value, item) : value}; })} ; }`, }; export async function collectionListTemplate( collection_name: string, action_name: string, _: string, _hooks: Partial = {} ) { const hooks = { ...default_hooks, ..._hooks }; const [uppercase_collection, collection_fields] = await Promise.all([ extractCollectionClassname( target_locreq.resolve( "src/back/collections/" + collection_name + ".ts" ) ), extract_fields_from_collection(collection_name), ]); const field_import_string = Array.from( new Set(collection_fields.map((field) => fieldImport(field))) ).join("\n"); const result = `import type { Context } from "koa"; import type { CollectionItem } from "sealious"; import type { FlatTemplatable, Templatable } from "tempstream"; import { TempstreamJSX, tempstream } from "tempstream"; import { ${uppercase_collection} } from "src/back/collections/collections.js"; import html from "src/back/html.js"; import type { ListFilterRender, FormDisplayInfo } from "@sealcode/sealgen"; import { SealiousItemListPage, BaseListPageFields, DefaultListFilters, } from "@sealcode/sealgen"; import qs from "qs"; ${hooks.post_import_js} ${field_import_string} export const actionName = "${action_name}"; const filterFields:{ field: keyof (typeof ${uppercase_collection})["fields"]; render?: ListFilterRender; prepareValue?: (filter_value: unknown) => unknown; // set this function to change what filter value is passed to Sealious }[] = [ ${makeDefaultFilterFields(collection_fields)} ]; const displayFields: FormDisplayInfo[] = [ ${makeDefaultDisplayFields(collection_fields)} ]; export default new (class ${action_name}Page extends SealiousItemListPage< typeof ${uppercase_collection}, typeof BaseListPageFields > { getFields = ()=>BaseListPageFields; getControls = ()=>[]; async renderFilters(ctx: Context): Promise { const query_params = qs.parse(ctx.search.slice(1)); query_params.page = "1"; const filter_values = await super.getFilterValues(ctx); return
{Object.entries(query_params).map(([key, value]) => { if (key == "filter") { return ""; } // this is necessary to not lose any query params when the user changes the filter values return ; })} {filterFields.map(({ field, render }) => { if (!render) { render = DefaultListFilters.fallback.render; } return ( render(filter_values[field] || "", this.collection.fields[field], ctx) || "" ); })}
; } async getFilterValues(ctx: Context) { // adding opportunity to adjust the values for a given field filter before it's sent to Sealious const values = await super.getFilterValues(ctx); for (const filterField of filterFields) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const key = filterField.field as keyof typeof values; if (key in values) { const prepare_fn = filterField.prepareValue; if (prepare_fn) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment values[key] = prepare_fn(values[key]) as any; } } } return values; } ${hooks.render_item(collection_name)} renderListContainer(ctx: Context, content: Templatable): FlatTemplatable { return ( {this.renderTableHead(ctx, displayFields)} {content}
); } renderTableHead( ctx: Context, fields: { field: string; label?: string }[] ): FlatTemplatable { return tempstream /* HTML */ \` \${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))} Actions \`; } async render(ctx: Context) { return html({ ctx, title: "${action_name}", description: "", body:
${hooks.pre_header_html}

${action_name} List

${hooks.post_header_html} {super.render(ctx)}
}); } })(${uppercase_collection}); `; return await formatWithPrettier(result); }