Page MenuHomeSealhub

collection-list.ts
No OneTemporary

collection-list.ts

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) => {
return 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<typeof ${toPascalCase(
collection_name
)}>) {
return <tr>
{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 <td>{format ? format(value, item) : value}</td>;
})}
</tr>;
}`,
};
export async function collectionListTemplate(
collection_name: string,
action_name: string,
_: string,
_hooks: Partial<typeof default_hooks> = {}
) {
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 } 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}";
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const filterFields = [
${makeDefaultFilterFields(collection_fields)}
] as {
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
}[];
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const displayFields = [
${makeDefaultDisplayFields(collection_fields)}
] as {
field: string;
label: string;
format?: (value: unknown, item: CollectionItem<typeof ${toPascalCase(
collection_name
)}>) => FlatTemplatable;
}[];
export default new (class ${action_name}Page extends SealiousItemListPage<
typeof ${uppercase_collection},
typeof BaseListPageFields
> {
fields = BaseListPageFields;
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
const query_params = qs.parse(ctx.search.slice(1));
query_params.page = "1";
const filter_values = await super.getFilterValues(ctx);
return <form>
{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 <input type="hidden" name={key} value={value}/>;
})}
{filterFields.map(({ field, render }) => {
if (!render) {
render = DefaultListFilters.fallback.render;
}
return (
render(filter_values[field] || "", this.collection.fields[field]) ||
""
);
})}
<input type="submit" />
</form>;
}
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 (
<table class="sealious-list ${toKebabCase(action_name)}-table">
{this.renderTableHead(ctx, displayFields)}
<tbody>{content}</tbody>
</table>
);
}
renderTableHead(
ctx: Context,
fields: { field: string; label?: string }[]
): FlatTemplatable {
return tempstream/* HTML */ \`<thead>
<tr>
\${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))}
<th>Actions</th>
</tr>
</thead>\`;
}
async render(ctx: Context) {
return html({
ctx,
title: "${action_name}",
description: "",
body: <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
${hooks.pre_header_html}
<h2>${action_name} List</h2>
${hooks.post_header_html}
{super.render(ctx)}
</div>
});
}
})(${uppercase_collection});
`;
return await formatWithPrettier(result);
}

File Metadata

Mime Type
text/x-java
Expires
Mon, Dec 23, 00:57 (4 h, 59 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556735
Default Alt Text
collection-list.ts (7 KB)

Event Timeline