Page MenuHomeSealhub

list.ts
No OneTemporary

import type { Context } from "koa";
import { htmlEscape } from "escape-goat";
import type { Collection, CollectionItem } from "sealious";
import type { ListFilterRender, HTMLFunc } from "@sealcode/sealgen";
import qs from "qs";
import { FlatTemplatable, tempstream, Templatable } from "tempstream";
import {
SealiousItemListPage,
BaseListPageFields,
DefaultListFilters,
} from "@sealcode/sealgen";
import { hasAccess } from "./has-access.js";
export type ListFieldDisplayInfo<C extends Collection> = {
field: keyof C["fields"] & string;
label: string;
format?: (
value: unknown,
item: CollectionItem<C>
) => string | Promise<string>;
};
export type ListFieldFilterInfo<C extends Collection> = {
field: keyof C["fields"];
render?: ListFilterRender;
prepareValue?: (filter_value: unknown) => unknown; // set this function to change what filter value is passed to Sealious
};
export default class CRUDListPage<
C extends Collection
> extends SealiousItemListPage<C, typeof BaseListPageFields> {
fields = BaseListPageFields;
public fields_for_filters: {
field: keyof C["fields"];
render?: ListFilterRender;
prepareValue?: (filter_value: unknown) => unknown; // set this function to change what filter value is passed to Sealious
}[];
public fields_for_display: ListFieldDisplayInfo<C>[];
public html: HTMLFunc;
public title: string;
public create_url: string;
public create_button_text: string;
public delete_button_text: string;
public edit_button_text: string;
public edit_url: (id: string) => string;
public delete_url: (id: string) => string;
public make_delete_question: (item: CollectionItem<C>) => string;
constructor(args: {
collection: C;
fields_for_filters: ListFieldFilterInfo<C>[];
fields_for_display: ListFieldDisplayInfo<C>[];
html: HTMLFunc;
title: string;
create_url: string;
edit_url: (id: string) => string;
delete_url: (id: string) => string;
create_button_text: string;
delete_button_text: string;
edit_button_text: string;
make_delete_question?: (item: CollectionItem<C>) => string;
}) {
super(args.collection);
this.fields_for_filters = args.fields_for_filters;
this.fields_for_display = args.fields_for_display;
this.html = args.html;
this.title = args.title;
this.create_url = args.create_url;
this.create_button_text = args.create_button_text;
this.delete_button_text = args.delete_button_text;
this.edit_button_text = args.edit_button_text;
this.edit_url = args.edit_url;
this.delete_url = args.delete_url;
this.make_delete_question =
args.make_delete_question || (({ id }) => `Delete item ${id}?`);
}
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 tempstream/* HTML */ `<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 tempstream/* HTML */ `<input
type="hidden"
name="${key}"
value="${String(value)}"
/>`;
})}
${this.fields_for_filters.map(({ field, render }) => {
if (!render) {
render = DefaultListFilters.fallback.render;
}
return (
render(
filter_values[field] || "",
(this.collection.fields as C["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 this.fields_for_filters) {
// 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;
}
async renderItem(_ctx: Context, item: CollectionItem<C>) {
return tempstream/* HTML */ `<tr>
${this.fields_for_display.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 /* HTML*/ `<td>${
format ? format(value, item) : value
}</td>`;
})}
<td>
<div class="sealious-list__actions">
${(await hasAccess(_ctx, this.collection, "edit"))
? `<a href="${this.edit_url(item.id)}">Edytuj</a>`
: ""}
${(await hasAccess(_ctx, this.collection, "delete"))
? `<a data-turbo-method="GET"
data-turbo-confirm="${htmlEscape(
this.make_delete_question(item)
)}" href="${this.delete_url(item.id)}">${
this.delete_button_text
}</a>`
: ""}
</div>
</td>
</tr>`;
}
renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
return tempstream/* HTML */ `<table
class="sealious-list banners-list-table"
>
${this.renderTableHead(ctx, this.fields_for_display)}
<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>Akcje</th>
</tr>
</thead>`;
}
async renderNavbar(_ctx: Context) {
return /* HTML */ `<div class="crud-ui__navbar">
<h1>${this.title}</h1>
<a href="${this.create_url}"> ${this.create_button_text} </a>
</div>`;
}
async render(ctx: Context) {
return this.html({
ctx,
title: this.title,
body: tempstream`<div class="sealious-list-wrapper banners-list--wrapper crud-ui-list">
${this.renderNavbar(ctx)}
${super.render(ctx)}
</div>`,
description: "",
});
}
}

File Metadata

Mime Type
text/x-java
Expires
Fri, Jan 24, 16:50 (7 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
599016
Default Alt Text
list.ts (6 KB)

Event Timeline