Page MenuHomeSealhub

multiple-files.ts
No OneTemporary

multiple-files.ts

import Router from "@koa/router";
import {
Collection,
File as SealiousFile,
Field as SealiousField,
} from "sealious";
import { Context } from "koa";
import { inputWrapper } from "../../utils/input-wrapper";
import { FlatTemplatable, tempstream } from "tempstream";
import { CollectionItem } from "../../../../sealious/@types/src/main";
import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files";
import { FormDataValue } from "../form";
import { FormFieldControl } from "./form-field-control";
import { is, predicates } from "@sealcode/ts-predicates";
export type MultipleFilesOptions = { label?: string; uploadLabel?: string };
export class MultipleFiles extends FormFieldControl {
public options: MultipleFilesOptions;
constructor(
public field: MultipleFilesField,
options?: MultipleFilesOptions
) {
super([field]);
this.options = options || {};
}
mount(router: Router) {
router.get(this.getFrameRelativeURL(), async (ctx) => {
ctx.body = this.renderFrame(
ctx,
await this.renderFrameContent(ctx)
);
});
router.post(
this.getFrameRelativeURL() + "/delete/:file_item_id",
async (ctx) => {
await this.deleteFileAssociation(ctx, ctx.params.file_item_id);
ctx.body = this.renderFrame(
ctx,
await this.renderFrameContent(ctx)
);
}
);
router.post(this.getFrameRelativeURL() + "/add", async (ctx) => {
const file = ctx.$body.file;
if (!file || !is(file, predicates.object)) {
ctx.body = "Missing file";
return;
}
await this.addFileAssociation(ctx, file as unknown as SealiousFile);
ctx.body = this.renderFrame(
ctx,
await this.renderFrameContent(ctx)
);
});
}
async deleteFileAssociation(ctx: Context, file_item_id: string) {
await ctx.$app.collections[
this.field.collection_field.referencing_collection
].removeByID(ctx.$context, file_item_id);
}
async addFileAssociation(ctx: Context, file: SealiousFile) {
const file_field = this.getFileField();
if (!file_field) {
throw new Error("No file field in referencing collection");
}
const body = {
[this.field.collection_field.referencing_field]:
await this.field.getItemId(ctx),
[file_field.name]: file,
};
await ctx.$app.collections[
this.field.collection_field.referencing_collection
].create(ctx.$context, body);
}
getFrameRelativeURL() {
return `${this.field.name}_files`;
}
getFrameID(): string {
// the "A" is necessary here
return `A${this.field.name}__multiple-fields-control`;
}
renderFrame(ctx: Context, content?: FlatTemplatable) {
return tempstream/* HTML */ `<turbo-frame
${content ? "" : `src="./${this.getFrameRelativeURL()}"`}
id="${this.getFrameID()}"
target="_top"
>
${content || ""}
</turbo-frame>`;
}
render(
ctx: Context,
_data: Record<string, FormDataValue>,
_messages: [],
field_name_prefix: string,
form_id: string,
validate: boolean
): FlatTemplatable | Promise<FlatTemplatable> {
return this.renderFrame(ctx, "");
}
getReferencingCollection() {
const result =
this.field.collection_field.app.collections[
this.field.collection_field.referencing_collection
];
return result;
}
getFileField(): SealiousField | null {
for (const [field_name, field] of Object.entries(
this.getReferencingCollection().fields
)) {
if (field.handles_large_data) {
return field;
}
}
return null;
}
async extractFileFromItem(
item: CollectionItem
): Promise<SealiousFile | null> {
const sealious_field = this.getFileField();
if (!sealious_field) {
return null;
}
return await SealiousFile.fromID(
(item.collection as Collection).app,
item.get(sealious_field.name).id
);
}
async renderFileItem(
fileItem: CollectionItem,
file: SealiousFile
): Promise<FlatTemplatable> {
return /* HTML */ `<li>
<a href="${file.getURL()}" data-turbo="false">
${file.filename || "no filename"}
</a>
<form
data-turbo-frame="${this.getFrameID()}"
action="${this.getFrameRelativeURL()}/delete/${fileItem.id}"
method="POST"
>
<input type="submit" value="X" class="file-list-action" />
</form>
</li>`;
}
async getFileItems(ctx: Context) {
const item_id = await this.field.getItemId(ctx);
const {
items: [item],
} = await this.field.collection_field.collection
.list(ctx.$context)
.ids([item_id])
.attach({ [this.field.collection_field.name]: true })
.fetch();
return item
.getAttachments(this.field.collection_field.name)
.filter((f) => f);
}
async renderFrameContent(ctx: Context): Promise<FlatTemplatable> {
const files = (
await Promise.all(
(
await this.getFileItems(ctx)
).map(async (item) => [
item,
await this.extractFileFromItem(item),
])
)
).filter(([_, f]) => f !== null) as [CollectionItem, SealiousFile][];
return inputWrapper(
["multiple-files", this.field.name],
tempstream/* HTML */ `
<label>${this.options.label || this.field.name}</label>
<ul>
${files.map(([item, file]) =>
this.renderFileItem(item, file)
)}
</ul>
<form
action="${this.getFrameRelativeURL()}/add"
method="POST"
enctype="multipart/form-data"
data-turbo-frame="${this.getFrameID()}"
>
<input type="file" name="file" />
<input
type="submit"
value="${this.options.uploadLabel || "Upload"}"
class="file-list-action"
/>
</form>
`
);
}
}

File Metadata

Mime Type
text/html
Expires
Sat, Sep 20, 14:12 (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
949258
Default Alt Text
multiple-files.ts (5 KB)

Event Timeline