@sealcode/sealgen 0.14.16 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/src/forms/controls/file.d.ts +3 -5
- package/@types/src/forms/controls/multiple-files.d.ts +9 -8
- package/@types/src/forms/controls/multiple-photos.d.ts +3 -2
- package/@types/src/forms/controls/photo.d.ts +2 -4
- package/@types/src/forms/controls/photo.test.d.ts +1 -0
- package/@types/src/forms/controls/simple-input.d.ts +1 -1
- package/@types/src/forms/fields/file.d.ts +7 -4
- package/@types/src/forms/form-types.d.ts +2 -2
- package/@types/src/utils/module-dirname.d.ts +1 -0
- package/assets/important.png +0 -0
- package/dist/add-route.test.js +2 -1
- package/dist/forms/controls/file.js +26 -14
- package/dist/forms/controls/multiple-files.js +9 -12
- package/dist/forms/controls/multiple-photos.js +8 -2
- package/dist/forms/controls/photo.js +3 -9
- package/dist/forms/controls/photo.test.js +81 -0
- package/dist/forms/controls/simple-input.js +1 -1
- package/dist/forms/fields/file.js +11 -23
- package/dist/templates/stateful-page.js +0 -1
- package/dist/utils/module-dirname.js +8 -0
- package/dist/utils/render-attributes.js +3 -1
- package/package.json +4 -2
- package/src/add-route.test.ts +3 -1
- package/src/forms/controls/array.ts +3 -1
- package/src/forms/controls/file.ts +27 -14
- package/src/forms/controls/multiple-files.ts +17 -22
- package/src/forms/controls/multiple-photos.ts +10 -4
- package/src/forms/controls/photo.test.ts +90 -0
- package/src/forms/controls/photo.ts +4 -9
- package/src/forms/controls/simple-input.ts +2 -2
- package/src/forms/fields/file.ts +17 -35
- package/src/forms/form-types.ts +2 -2
- package/src/templates/stateful-page.ts +0 -1
- package/src/utils/module-dirname.ts +5 -0
- package/src/utils/render-attributes.ts +3 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { Context } from "koa";
|
|
3
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
3
4
|
import { FlatTemplatable } from "tempstream";
|
|
4
5
|
import { FileContainer } from "../fields/file.js";
|
|
5
6
|
import { FormDataValue } from "../form-types.js";
|
|
@@ -7,10 +8,7 @@ import { FormControlContext } from "./form-control.js";
|
|
|
7
8
|
import { SimpleInput } from "./simple-input.js";
|
|
8
9
|
export declare class File extends SimpleInput<FileContainer> {
|
|
9
10
|
getType(): string;
|
|
10
|
-
renderFilePreview(_ctx: Context,
|
|
11
|
-
|
|
12
|
-
filename?: string;
|
|
13
|
-
} | null): Promise<FlatTemplatable>;
|
|
14
|
-
preInput(ctx: Context, data: Record<string, FormDataValue>): Promise<import("stream").Readable>;
|
|
11
|
+
renderFilePreview(_ctx: Context, file: FilePointer | null): Promise<FlatTemplatable>;
|
|
12
|
+
preInput(fctx: FormControlContext, data: Record<string, FormDataValue>): Promise<import("stream").Readable>;
|
|
15
13
|
getInputAttributes(fctx: FormControlContext): Promise<Record<string, string | boolean>>;
|
|
16
14
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/// <reference types="koa__router" />
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
import Router from "@koa/router";
|
|
4
|
-
import { CollectionItem,
|
|
4
|
+
import { CollectionItem, Field as SealiousField } from "sealious";
|
|
5
5
|
import { Context } from "koa";
|
|
6
6
|
import { FlatTemplatable } from "tempstream";
|
|
7
7
|
import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files.js";
|
|
8
8
|
import { FormFieldControl } from "./form-field-control.js";
|
|
9
9
|
import { FormControlContext } from "./form-control.js";
|
|
10
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
10
11
|
export type MultipleFilesOptions = {
|
|
11
12
|
label?: string;
|
|
12
13
|
uploadLabel?: string;
|
|
13
|
-
getAdditionalFields: (ctx: Context, file:
|
|
14
|
+
getAdditionalFields: (ctx: Context, file: FilePointer) => Promise<Record<string, unknown>>;
|
|
14
15
|
};
|
|
15
16
|
export declare class MultipleFiles extends FormFieldControl {
|
|
16
17
|
field: MultipleFilesField;
|
|
@@ -19,16 +20,16 @@ export declare class MultipleFiles extends FormFieldControl {
|
|
|
19
20
|
getClassModifiers(): string[];
|
|
20
21
|
mount(router: Router): void;
|
|
21
22
|
deleteFileAssociation(ctx: Context, file_item_id: string): Promise<void>;
|
|
22
|
-
addFileAssociation(ctx: Context, file:
|
|
23
|
+
addFileAssociation(ctx: Context, file: FilePointer): Promise<void>;
|
|
23
24
|
getFrameRelativeURL(): string;
|
|
24
25
|
getFrameID(): string;
|
|
25
|
-
renderFrame(
|
|
26
|
+
renderFrame(_ctx: Context, content?: FlatTemplatable): import("stream").Readable;
|
|
26
27
|
render(fctx: FormControlContext): FlatTemplatable | Promise<FlatTemplatable>;
|
|
27
|
-
getReferencingCollection(): Collection;
|
|
28
|
+
getReferencingCollection(): import("sealious").Collection;
|
|
28
29
|
getFileField(): SealiousField | null;
|
|
29
|
-
extractFileFromItem(item: CollectionItem): Promise<
|
|
30
|
-
renderFileItemPreview(_fileItem: CollectionItem, file:
|
|
31
|
-
renderFileItem(fileItem: CollectionItem, file:
|
|
30
|
+
extractFileFromItem(item: CollectionItem): Promise<FilePointer | null>;
|
|
31
|
+
renderFileItemPreview(_fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
|
|
32
|
+
renderFileItem(fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
|
|
32
33
|
renderFileRemoveButton(fileItem: CollectionItem): FlatTemplatable;
|
|
33
34
|
getFileItems(ctx: Context): Promise<CollectionItem<any>[]>;
|
|
34
35
|
getInputAttributes(): {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionItem } from "sealious";
|
|
2
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
2
3
|
import { FlatTemplatable } from "tempstream";
|
|
3
4
|
import { MultipleFiles, MultipleFilesOptions } from "./multiple-files.js";
|
|
4
5
|
import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files.js";
|
|
@@ -14,5 +15,5 @@ export declare class MultiplePhotos extends MultipleFiles {
|
|
|
14
15
|
name: string;
|
|
15
16
|
multiple: boolean;
|
|
16
17
|
};
|
|
17
|
-
renderFileItem(fileItem: CollectionItem, file:
|
|
18
|
+
renderFileItem(fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
|
|
18
19
|
}
|
|
@@ -6,15 +6,13 @@ import { SimpleInputOptions } from "./simple-input.js";
|
|
|
6
6
|
import { FormControlContext } from "./form-control.js";
|
|
7
7
|
import { FileContainer } from "../fields/file.js";
|
|
8
8
|
import { KoaResponsiveImageRouter } from "koa-responsive-image-router";
|
|
9
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
9
10
|
export declare class Photo extends File {
|
|
10
11
|
field: FormField<boolean, FileContainer>;
|
|
11
12
|
imageRouter: KoaResponsiveImageRouter;
|
|
12
13
|
constructor(field: FormField<boolean, FileContainer>, imageRouter: KoaResponsiveImageRouter, options?: SimpleInputOptions);
|
|
13
14
|
getImgStyle(): Promise<string>;
|
|
14
|
-
renderFilePreview(ctx: Context,
|
|
15
|
-
id?: string;
|
|
16
|
-
filename?: string;
|
|
17
|
-
} | null): Promise<FlatTemplatable>;
|
|
15
|
+
renderFilePreview(ctx: Context, file: FilePointer | null): Promise<FlatTemplatable>;
|
|
18
16
|
getWrapperModifiers(): string[];
|
|
19
17
|
getInputAttributes(fctx: FormControlContext): Promise<{
|
|
20
18
|
accept: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -20,7 +20,7 @@ export declare class SimpleInput<ParsedValue, Options extends SimpleInputOptions
|
|
|
20
20
|
field: FormField<boolean, ParsedValue>;
|
|
21
21
|
options: SimpleInputOptions;
|
|
22
22
|
constructor(field: FormField<boolean, ParsedValue>, options?: Options);
|
|
23
|
-
preInput(_ctx:
|
|
23
|
+
preInput(_ctx: FormControlContext, _data: Record<string, FormDataValue>): Promise<FlatTemplatable>;
|
|
24
24
|
getType(): string;
|
|
25
25
|
getLabel(): string;
|
|
26
26
|
getID(): string;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { Context } from "koa";
|
|
2
|
-
import {
|
|
2
|
+
import { FileManager, FilePointer } from "@sealcode/file-manager";
|
|
3
3
|
import { FormDataValue } from "../form-types.js";
|
|
4
4
|
import { FieldParseResult, FormField } from "./field.js";
|
|
5
5
|
export type FileContainer = {
|
|
6
|
-
old:
|
|
7
|
-
new:
|
|
6
|
+
old: FilePointer | null;
|
|
7
|
+
new: FilePointer | null;
|
|
8
8
|
};
|
|
9
9
|
export declare class File<Required extends boolean> extends FormField<Required, FileContainer> {
|
|
10
|
+
readonly required: Required;
|
|
11
|
+
file_manager: FileManager;
|
|
12
|
+
constructor(required: Required, file_manager: FileManager);
|
|
10
13
|
parse(ctx: Context, raw_value: FormDataValue): Promise<FieldParseResult<FileContainer>>;
|
|
11
|
-
getDatabaseValue(ctx: Context, data: Record<string, FormDataValue>): Promise<
|
|
14
|
+
getDatabaseValue(ctx: Context, data: Record<string, FormDataValue>): Promise<FilePointer | undefined>;
|
|
12
15
|
getEmptyValue(): {
|
|
13
16
|
old: null;
|
|
14
17
|
new: null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
2
2
|
import type { FlatTemplatable } from "tempstream";
|
|
3
3
|
import type { FormField } from "./fields/field.js";
|
|
4
4
|
export type FormDataPrimitive = string | string[] | number | undefined;
|
|
5
|
-
export type FormDataValue = FormDataPrimitive | Record<string, FormDataPrimitive> |
|
|
5
|
+
export type FormDataValue = FormDataPrimitive | Record<string, FormDataPrimitive> | FilePointer;
|
|
6
6
|
export type FieldValueType<F extends FormField> = F extends FormField<infer R> ? R : never;
|
|
7
7
|
export type FormFieldsToValues<F extends Record<string, FormField<boolean, keyof F>>> = {
|
|
8
8
|
[Property in keyof F]: FieldValueType<F[Property]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function module_dirname(module_url: string): string;
|
|
Binary file
|
package/dist/add-route.test.js
CHANGED
|
@@ -4,7 +4,8 @@ import axios from "axios";
|
|
|
4
4
|
import waitPort from "wait-port";
|
|
5
5
|
import getPort from "get-port";
|
|
6
6
|
import _locreq from "locreq";
|
|
7
|
-
|
|
7
|
+
import { module_dirname } from "./utils/module-dirname.js";
|
|
8
|
+
const locreq = _locreq(module_dirname(import.meta.url));
|
|
8
9
|
const execAsync = (cmd) => {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
10
11
|
const process2 = exec(cmd);
|
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
import { tempstream } from "tempstream";
|
|
2
2
|
import { SimpleInput } from "./simple-input.js";
|
|
3
|
+
import { htmlEscape } from "escape-goat";
|
|
3
4
|
class File extends SimpleInput {
|
|
4
5
|
getType() {
|
|
5
6
|
return "file";
|
|
6
7
|
}
|
|
7
|
-
async renderFilePreview(_ctx,
|
|
8
|
-
|
|
8
|
+
async renderFilePreview(_ctx, file) {
|
|
9
|
+
const class_name = "file_preview";
|
|
10
|
+
const empty_container = `<div class="${class_name}"></div>`;
|
|
11
|
+
if (!file) {
|
|
12
|
+
return empty_container;
|
|
13
|
+
}
|
|
14
|
+
return (
|
|
15
|
+
/* HTML */
|
|
16
|
+
`<div class="${class_name}">
|
|
17
|
+
${file.getOriginalFilename()}
|
|
18
|
+
</div>`
|
|
19
|
+
);
|
|
9
20
|
}
|
|
10
|
-
async preInput(
|
|
11
|
-
const { parsed } = await this.field.getValue(ctx, data);
|
|
12
|
-
return tempstream`${this.renderFilePreview(
|
|
21
|
+
async preInput(fctx, data) {
|
|
22
|
+
const { parsed } = await this.field.getValue(fctx.ctx, data);
|
|
23
|
+
return tempstream`${this.renderFilePreview(
|
|
24
|
+
fctx.ctx,
|
|
25
|
+
parsed.old
|
|
26
|
+
)}
|
|
13
27
|
<input
|
|
14
28
|
type="hidden"
|
|
15
|
-
name="${this.field.name}[old]
|
|
16
|
-
value="${
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
value="${parsed.old?.filename || ""}"
|
|
22
|
-
/>`;
|
|
29
|
+
name="${this.field.name}[old]"
|
|
30
|
+
value="${htmlEscape(
|
|
31
|
+
parsed.old?.token || await parsed.old?.save(false) || ""
|
|
32
|
+
)}"
|
|
33
|
+
form="${fctx.form_id}"
|
|
34
|
+
/> `;
|
|
23
35
|
}
|
|
24
36
|
async getInputAttributes(fctx) {
|
|
25
37
|
const original = await super.getInputAttributes(fctx);
|
|
@@ -27,7 +39,7 @@ class File extends SimpleInput {
|
|
|
27
39
|
return {
|
|
28
40
|
...original,
|
|
29
41
|
name: this.field.name + "[new]",
|
|
30
|
-
required: original.required && !parsed.
|
|
42
|
+
required: original.required && !parsed.new,
|
|
31
43
|
value: false
|
|
32
44
|
};
|
|
33
45
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
File as SealiousFile
|
|
3
|
-
} from "sealious";
|
|
4
1
|
import { inputWrapper } from "../../utils/input-wrapper.js";
|
|
5
2
|
import { tempstream } from "tempstream";
|
|
6
3
|
import { FormFieldControl } from "./form-field-control.js";
|
|
7
4
|
import { is, predicates } from "@sealcode/ts-predicates";
|
|
8
5
|
import { renderAttributes } from "../../utils/render-attributes.js";
|
|
6
|
+
import { PathFilePointer } from "@sealcode/file-manager";
|
|
9
7
|
class MultipleFiles extends FormFieldControl {
|
|
10
8
|
constructor(field, options) {
|
|
11
9
|
super([field]);
|
|
@@ -43,10 +41,7 @@ class MultipleFiles extends FormFieldControl {
|
|
|
43
41
|
}
|
|
44
42
|
await Promise.all(
|
|
45
43
|
files.map(
|
|
46
|
-
(file) => this.addFileAssociation(
|
|
47
|
-
ctx,
|
|
48
|
-
file
|
|
49
|
-
)
|
|
44
|
+
(file) => this.addFileAssociation(ctx, file)
|
|
50
45
|
)
|
|
51
46
|
);
|
|
52
47
|
ctx.body = this.renderFrame(
|
|
@@ -76,7 +71,7 @@ class MultipleFiles extends FormFieldControl {
|
|
|
76
71
|
getFrameID() {
|
|
77
72
|
return `A${this.field.name}__multiple-fields-control`;
|
|
78
73
|
}
|
|
79
|
-
renderFrame(
|
|
74
|
+
renderFrame(_ctx, content) {
|
|
80
75
|
return tempstream`<turbo-frame
|
|
81
76
|
${content ? "" : `src="./${this.getFrameRelativeURL()}"`}
|
|
82
77
|
id="${this.getFrameID()}"
|
|
@@ -107,15 +102,17 @@ class MultipleFiles extends FormFieldControl {
|
|
|
107
102
|
if (!sealious_field) {
|
|
108
103
|
return null;
|
|
109
104
|
}
|
|
110
|
-
return await
|
|
111
|
-
item.
|
|
112
|
-
item.get(sealious_field.name).id
|
|
105
|
+
return await item.collection.app.FileManager.fromToken(
|
|
106
|
+
item.get(sealious_field.name).token
|
|
113
107
|
);
|
|
114
108
|
}
|
|
115
109
|
async renderFileItemPreview(_fileItem, file) {
|
|
116
|
-
return file.
|
|
110
|
+
return file.getOriginalFilename();
|
|
117
111
|
}
|
|
118
112
|
async renderFileItem(fileItem, file) {
|
|
113
|
+
if (!(file instanceof PathFilePointer)) {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
119
116
|
return tempstream`<li class="file-list-item">
|
|
120
117
|
<a
|
|
121
118
|
class="file-list-item__preview"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PathFilePointer } from "@sealcode/file-manager";
|
|
1
2
|
import { tempstream } from "tempstream";
|
|
2
3
|
import { MultipleFiles } from "./multiple-files.js";
|
|
3
4
|
class MultiplePhotos extends MultipleFiles {
|
|
@@ -13,7 +14,10 @@ class MultiplePhotos extends MultipleFiles {
|
|
|
13
14
|
return { ...super.getInputAttributes(), accept: "image/*" };
|
|
14
15
|
}
|
|
15
16
|
async renderFileItem(fileItem, file) {
|
|
16
|
-
|
|
17
|
+
if (!(file instanceof PathFilePointer)) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
const path = await file.getPath();
|
|
17
21
|
if (!path) {
|
|
18
22
|
return "missing file";
|
|
19
23
|
}
|
|
@@ -24,7 +28,9 @@ class MultiplePhotos extends MultipleFiles {
|
|
|
24
28
|
sizesAttr: "150px"
|
|
25
29
|
})}
|
|
26
30
|
</a>
|
|
27
|
-
<span class="filename">
|
|
31
|
+
<span class="filename">
|
|
32
|
+
${await file.getOriginalFilename() || "no filename"}</span
|
|
33
|
+
>
|
|
28
34
|
${this.renderFileRemoveButton(fileItem)}
|
|
29
35
|
</li>`;
|
|
30
36
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { tempstream } from "tempstream";
|
|
2
2
|
import { File } from "./file.js";
|
|
3
|
-
import { File as SealiousFile } from "sealious";
|
|
4
3
|
class Photo extends File {
|
|
5
4
|
constructor(field, imageRouter, options) {
|
|
6
5
|
super(field, options);
|
|
@@ -10,19 +9,14 @@ class Photo extends File {
|
|
|
10
9
|
async getImgStyle() {
|
|
11
10
|
return "width: 100px; height: 100px; object-fit: contain";
|
|
12
11
|
}
|
|
13
|
-
async renderFilePreview(ctx,
|
|
12
|
+
async renderFilePreview(ctx, file) {
|
|
14
13
|
const class_name = "file_preview";
|
|
15
14
|
const empty_container = `<div class="${class_name}"></div>`;
|
|
16
|
-
if (!
|
|
15
|
+
if (!file) {
|
|
17
16
|
return empty_container;
|
|
18
17
|
}
|
|
19
|
-
const { id } = file_data;
|
|
20
|
-
if (!id) {
|
|
21
|
-
return empty_container;
|
|
22
|
-
}
|
|
23
|
-
const file = await SealiousFile.fromID(ctx.$app, id);
|
|
24
18
|
return tempstream`<div class="${class_name}">
|
|
25
|
-
${this.imageRouter.image(file.
|
|
19
|
+
${this.imageRouter.image(await file.getPath(), {
|
|
26
20
|
resolutions: [100],
|
|
27
21
|
sizesAttr: "100px",
|
|
28
22
|
imgStyle: await this.getImgStyle()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import Router from "@koa/router";
|
|
2
|
+
import getPort from "get-port";
|
|
3
|
+
import Koa from "koa";
|
|
4
|
+
import { mount } from "../../mount.js";
|
|
5
|
+
import { Form } from "../form.js";
|
|
6
|
+
import { getBrowser } from "../../utils/browser-creator.js";
|
|
7
|
+
import { Fields } from "../../index.js";
|
|
8
|
+
import { Photo } from "./photo.js";
|
|
9
|
+
import { KoaResponsiveImageRouter } from "koa-responsive-image-router";
|
|
10
|
+
import _locreq from "locreq";
|
|
11
|
+
import { module_dirname } from "../../utils/module-dirname.js";
|
|
12
|
+
import { FileManager, PathFilePointer } from "@sealcode/file-manager";
|
|
13
|
+
const locreq = _locreq(module_dirname(import.meta.url));
|
|
14
|
+
describe("photo-controll", () => {
|
|
15
|
+
describe("basic tests", async () => {
|
|
16
|
+
let server;
|
|
17
|
+
let browser;
|
|
18
|
+
let page;
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
server.close();
|
|
21
|
+
});
|
|
22
|
+
it("sends the old photo if it wasn't changed and it has an initial value", async () => {
|
|
23
|
+
const port = await getPort();
|
|
24
|
+
console.log(`Using port ${port} for photo.test.ts`);
|
|
25
|
+
const app = new Koa();
|
|
26
|
+
const router = new Router();
|
|
27
|
+
const fields = {
|
|
28
|
+
photo: new Fields.File(false, new FileManager("/tmp", "/uploaded_files"))
|
|
29
|
+
};
|
|
30
|
+
let received_photo;
|
|
31
|
+
const imageRouter = new KoaResponsiveImageRouter({
|
|
32
|
+
staticPath: "/images",
|
|
33
|
+
thumbnailSize: 20,
|
|
34
|
+
cacheManagerResolutionThreshold: 50,
|
|
35
|
+
imageStoragePath: `/tmp/image-cache-${port}`,
|
|
36
|
+
smartCropStoragePath: `/tmp/smartcrop-storage-${port}`
|
|
37
|
+
});
|
|
38
|
+
mount(
|
|
39
|
+
router,
|
|
40
|
+
"/",
|
|
41
|
+
new class extends Form {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
this.fields = fields;
|
|
45
|
+
this.controls = [new Photo(fields.photo, imageRouter)];
|
|
46
|
+
}
|
|
47
|
+
async onSubmit(ctx) {
|
|
48
|
+
console.log("OnSubmit!");
|
|
49
|
+
const data = await this.getParsedValues(ctx);
|
|
50
|
+
console.log({ data });
|
|
51
|
+
received_photo = data.photo;
|
|
52
|
+
}
|
|
53
|
+
async getInitialValues(ctx) {
|
|
54
|
+
const file = new PathFilePointer(
|
|
55
|
+
new FileManager("/tmp", "/uploaded_files"),
|
|
56
|
+
locreq.resolve("assets/important.png")
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
photo: await file.save(false)
|
|
60
|
+
// to get the token
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async canAccess(_) {
|
|
64
|
+
return { canAccess: true, message: "" };
|
|
65
|
+
}
|
|
66
|
+
}(),
|
|
67
|
+
true
|
|
68
|
+
);
|
|
69
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
70
|
+
server = app.listen(port);
|
|
71
|
+
browser = await getBrowser();
|
|
72
|
+
const context = await browser.newContext();
|
|
73
|
+
page = await context.newPage();
|
|
74
|
+
await page.goto(`http://localhost:${port}`);
|
|
75
|
+
await page.getByRole("button", { name: "Wy\u015Blij" }).click();
|
|
76
|
+
console.log({ received_photo });
|
|
77
|
+
assert(received_photo !== void 0 && received_photo.old !== null);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=photo.test.js.map
|
|
@@ -74,7 +74,7 @@ class SimpleInput extends FormFieldControl {
|
|
|
74
74
|
this.getWrapperModifiers(),
|
|
75
75
|
tempstream`
|
|
76
76
|
${label ? `<label for="${id}">${label}</label>` : ""}
|
|
77
|
-
${await this.preInput(fctx
|
|
77
|
+
${await this.preInput(fctx, fctx.data)}
|
|
78
78
|
${this.renderInput(
|
|
79
79
|
fctx.ctx,
|
|
80
80
|
renderAttributes(await this.getInputAttributes(fctx)),
|
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
hasShape,
|
|
4
|
-
is,
|
|
5
|
-
predicates
|
|
6
|
-
} from "@sealcode/ts-predicates";
|
|
7
|
-
import { File as SealiousFile } from "sealious";
|
|
1
|
+
import { hasFieldOfType, is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
8
3
|
import { FormField } from "./field.js";
|
|
9
4
|
class File extends FormField {
|
|
5
|
+
constructor(required, file_manager) {
|
|
6
|
+
super(required);
|
|
7
|
+
this.required = required;
|
|
8
|
+
this.file_manager = file_manager;
|
|
9
|
+
}
|
|
10
10
|
async parse(ctx, raw_value) {
|
|
11
11
|
let result;
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
filename: predicates.string,
|
|
15
|
-
id: predicates.string
|
|
16
|
-
},
|
|
17
|
-
raw_value
|
|
18
|
-
) && raw_value.id) {
|
|
19
|
-
const file = await SealiousFile.fromID(
|
|
20
|
-
ctx.$app,
|
|
21
|
-
raw_value.id
|
|
22
|
-
);
|
|
12
|
+
if (is(raw_value, predicates.string)) {
|
|
13
|
+
const file = await this.file_manager.fromToken(raw_value);
|
|
23
14
|
result = {
|
|
24
15
|
parsed_value: { old: file, new: null },
|
|
25
16
|
error: null,
|
|
@@ -32,15 +23,12 @@ class File extends FormField {
|
|
|
32
23
|
parsable: false
|
|
33
24
|
};
|
|
34
25
|
} else {
|
|
35
|
-
const old_file = hasFieldOfType(raw_value, "old", predicates.
|
|
36
|
-
{ id: predicates.string, filename: predicates.string },
|
|
37
|
-
raw_value.old
|
|
38
|
-
) && raw_value.old.id ? await SealiousFile.fromID(ctx.$app, raw_value.old.id) : null;
|
|
26
|
+
const old_file = hasFieldOfType(raw_value, "old", predicates.string) ? await this.file_manager.fromToken(raw_value.old) : null;
|
|
39
27
|
const new_file = hasFieldOfType(
|
|
40
28
|
raw_value,
|
|
41
29
|
"new",
|
|
42
30
|
predicates.array(predicates.object)
|
|
43
|
-
) && raw_value.new[0] instanceof
|
|
31
|
+
) && raw_value.new[0] instanceof FilePointer ? raw_value.new[0] : null;
|
|
44
32
|
result = {
|
|
45
33
|
parsable: true,
|
|
46
34
|
error: null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealcode/sealgen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -52,9 +52,11 @@
|
|
|
52
52
|
"types": "./@types/src/index.d.ts",
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@koa/router": "^12.0.1",
|
|
55
|
+
"@sealcode/file-manager": "^1.0.2",
|
|
55
56
|
"@sealcode/ts-predicates": "^0.4.3",
|
|
56
57
|
"deepmerge": "^4.3.1",
|
|
57
58
|
"esbuild": "^0.20.0",
|
|
59
|
+
"escape-goat": "^4.0.0",
|
|
58
60
|
"google-fonts-helper": "^3.4.1",
|
|
59
61
|
"js-convert-case": "^4.2.0",
|
|
60
62
|
"locreq": "^3.0.0",
|
|
@@ -71,6 +73,6 @@
|
|
|
71
73
|
"peerDependencies": {
|
|
72
74
|
"koa": "^2.13.0",
|
|
73
75
|
"koa-responsive-image-router": "^0.2.24",
|
|
74
|
-
"sealious": "^0.
|
|
76
|
+
"sealious": "^0.19.6"
|
|
75
77
|
}
|
|
76
78
|
}
|
package/src/add-route.test.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { exec, spawn } from "child_process";
|
|
|
3
3
|
import axios from "axios";
|
|
4
4
|
import waitPort from "wait-port";
|
|
5
5
|
import getPort from "get-port";
|
|
6
|
+
|
|
6
7
|
import _locreq from "locreq";
|
|
7
|
-
|
|
8
|
+
import { module_dirname } from "./utils/module-dirname.js";
|
|
9
|
+
const locreq = _locreq(module_dirname(import.meta.url));
|
|
8
10
|
|
|
9
11
|
const execAsync = (cmd: string) => {
|
|
10
12
|
return new Promise<void>((resolve, reject) => {
|
|
@@ -193,7 +193,9 @@ export class StructuredArray<
|
|
|
193
193
|
throw new Error("Missing action param");
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
const field_data = ctx.$body
|
|
196
|
+
const field_data = (ctx.$body as Record<string, unknown>)[
|
|
197
|
+
this.field.name
|
|
198
|
+
] || { data: [] };
|
|
197
199
|
console.log({ action: field_data });
|
|
198
200
|
if (
|
|
199
201
|
!hasShape(
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Context } from "koa";
|
|
2
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
2
3
|
import { FlatTemplatable, tempstream } from "tempstream";
|
|
3
4
|
import { FileContainer } from "../fields/file.js";
|
|
4
5
|
import { FormDataValue } from "../form-types.js";
|
|
5
6
|
import { FormControlContext } from "./form-control.js";
|
|
6
7
|
import { SimpleInput } from "./simple-input.js";
|
|
8
|
+
import { htmlEscape } from "escape-goat";
|
|
7
9
|
|
|
8
10
|
export class File extends SimpleInput<FileContainer> {
|
|
9
11
|
getType() {
|
|
@@ -12,24 +14,35 @@ export class File extends SimpleInput<FileContainer> {
|
|
|
12
14
|
|
|
13
15
|
async renderFilePreview(
|
|
14
16
|
_ctx: Context,
|
|
15
|
-
|
|
17
|
+
file: FilePointer | null
|
|
16
18
|
): Promise<FlatTemplatable> {
|
|
17
|
-
|
|
19
|
+
const class_name = "file_preview";
|
|
20
|
+
const empty_container = `<div class="${class_name}"></div>`;
|
|
21
|
+
if (!file) {
|
|
22
|
+
return empty_container;
|
|
23
|
+
}
|
|
24
|
+
return /* HTML */ `<div class="${class_name}">
|
|
25
|
+
${file.getOriginalFilename()}
|
|
26
|
+
</div>`;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
async preInput(
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
async preInput(
|
|
30
|
+
fctx: FormControlContext,
|
|
31
|
+
data: Record<string, FormDataValue>
|
|
32
|
+
) {
|
|
33
|
+
const { parsed } = await this.field.getValue(fctx.ctx, data);
|
|
34
|
+
return tempstream/* HTML */ `${this.renderFilePreview(
|
|
35
|
+
fctx.ctx,
|
|
36
|
+
parsed.old
|
|
37
|
+
)}
|
|
23
38
|
<input
|
|
24
39
|
type="hidden"
|
|
25
|
-
name="${this.field.name}[old]
|
|
26
|
-
value="${
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
value="${parsed.old?.filename || ""}"
|
|
32
|
-
/>`;
|
|
40
|
+
name="${this.field.name}[old]"
|
|
41
|
+
value="${htmlEscape(
|
|
42
|
+
parsed.old?.token || (await parsed.old?.save(false)) || ""
|
|
43
|
+
)}"
|
|
44
|
+
form="${fctx.form_id}"
|
|
45
|
+
/> `;
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
async getInputAttributes(
|
|
@@ -40,7 +53,7 @@ export class File extends SimpleInput<FileContainer> {
|
|
|
40
53
|
return {
|
|
41
54
|
...original,
|
|
42
55
|
name: this.field.name + "[new]",
|
|
43
|
-
required: original.required && !parsed.
|
|
56
|
+
required: original.required && !parsed.new,
|
|
44
57
|
value: false,
|
|
45
58
|
};
|
|
46
59
|
}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import Router from "@koa/router";
|
|
2
|
-
import {
|
|
3
|
-
CollectionItem,
|
|
4
|
-
File as SealiousFile,
|
|
5
|
-
Field as SealiousField,
|
|
6
|
-
Collection,
|
|
7
|
-
} from "sealious";
|
|
2
|
+
import { CollectionItem, Field as SealiousField } from "sealious";
|
|
8
3
|
import { Context } from "koa";
|
|
9
4
|
import { inputWrapper } from "../../utils/input-wrapper.js";
|
|
10
5
|
import { FlatTemplatable, tempstream } from "tempstream";
|
|
@@ -13,13 +8,14 @@ import { FormFieldControl } from "./form-field-control.js";
|
|
|
13
8
|
import { is, predicates } from "@sealcode/ts-predicates";
|
|
14
9
|
import { renderAttributes } from "../../utils/render-attributes.js";
|
|
15
10
|
import { FormControlContext } from "./form-control.js";
|
|
11
|
+
import { FilePointer, PathFilePointer } from "@sealcode/file-manager";
|
|
16
12
|
|
|
17
13
|
export type MultipleFilesOptions = {
|
|
18
14
|
label?: string;
|
|
19
15
|
uploadLabel?: string;
|
|
20
16
|
getAdditionalFields: (
|
|
21
17
|
ctx: Context,
|
|
22
|
-
file:
|
|
18
|
+
file: FilePointer
|
|
23
19
|
) => Promise<Record<string, unknown>>;
|
|
24
20
|
};
|
|
25
21
|
|
|
@@ -57,7 +53,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
57
53
|
);
|
|
58
54
|
|
|
59
55
|
router.post(this.getFrameRelativeURL() + "/add", async (ctx) => {
|
|
60
|
-
let files = ctx.$body.files as
|
|
56
|
+
let files = ctx.$body.files as FilePointer | FilePointer[];
|
|
61
57
|
if (!files || !is(files, predicates.object)) {
|
|
62
58
|
ctx.body = "Missing files";
|
|
63
59
|
return;
|
|
@@ -67,10 +63,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
67
63
|
}
|
|
68
64
|
await Promise.all(
|
|
69
65
|
files.map((file) =>
|
|
70
|
-
this.addFileAssociation(
|
|
71
|
-
ctx,
|
|
72
|
-
file as unknown as SealiousFile
|
|
73
|
-
)
|
|
66
|
+
this.addFileAssociation(ctx, file as unknown as FilePointer)
|
|
74
67
|
)
|
|
75
68
|
);
|
|
76
69
|
ctx.body = this.renderFrame(
|
|
@@ -86,7 +79,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
86
79
|
].removeByID(ctx.$context, file_item_id);
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
async addFileAssociation(ctx: Context, file:
|
|
82
|
+
async addFileAssociation(ctx: Context, file: FilePointer) {
|
|
90
83
|
const file_field = this.getFileField();
|
|
91
84
|
if (!file_field) {
|
|
92
85
|
throw new Error("No file field in referencing collection");
|
|
@@ -111,7 +104,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
111
104
|
return `A${this.field.name}__multiple-fields-control`;
|
|
112
105
|
}
|
|
113
106
|
|
|
114
|
-
renderFrame(
|
|
107
|
+
renderFrame(_ctx: Context, content?: FlatTemplatable) {
|
|
115
108
|
return tempstream/* HTML */ `<turbo-frame
|
|
116
109
|
${content ? "" : `src="./${this.getFrameRelativeURL()}"`}
|
|
117
110
|
id="${this.getFrameID()}"
|
|
@@ -148,28 +141,30 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
148
141
|
|
|
149
142
|
async extractFileFromItem(
|
|
150
143
|
item: CollectionItem
|
|
151
|
-
): Promise<
|
|
144
|
+
): Promise<FilePointer | null> {
|
|
152
145
|
const sealious_field = this.getFileField();
|
|
153
146
|
if (!sealious_field) {
|
|
154
147
|
return null;
|
|
155
148
|
}
|
|
156
|
-
return await
|
|
157
|
-
|
|
158
|
-
item.get(sealious_field.name).id
|
|
149
|
+
return await item.collection.app.FileManager.fromToken(
|
|
150
|
+
item.get(sealious_field.name).token
|
|
159
151
|
);
|
|
160
152
|
}
|
|
161
153
|
|
|
162
154
|
async renderFileItemPreview(
|
|
163
155
|
_fileItem: CollectionItem,
|
|
164
|
-
file:
|
|
156
|
+
file: FilePointer
|
|
165
157
|
): Promise<FlatTemplatable> {
|
|
166
|
-
return file.
|
|
158
|
+
return file.getOriginalFilename();
|
|
167
159
|
}
|
|
168
160
|
|
|
169
161
|
async renderFileItem(
|
|
170
162
|
fileItem: CollectionItem,
|
|
171
|
-
file:
|
|
163
|
+
file: FilePointer
|
|
172
164
|
): Promise<FlatTemplatable> {
|
|
165
|
+
if (!(file instanceof PathFilePointer)) {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
173
168
|
return tempstream/* HTML */ `<li class="file-list-item">
|
|
174
169
|
<a
|
|
175
170
|
class="file-list-item__preview"
|
|
@@ -226,7 +221,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
226
221
|
await this.extractFileFromItem(item),
|
|
227
222
|
])
|
|
228
223
|
)
|
|
229
|
-
).filter(([_, f]) => f !== null) as [CollectionItem,
|
|
224
|
+
).filter(([_, f]) => f !== null) as [CollectionItem, FilePointer][];
|
|
230
225
|
|
|
231
226
|
return inputWrapper(
|
|
232
227
|
["multiple-files", this.field.name, ...this.getClassModifiers()],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionItem } from "sealious";
|
|
2
|
+
import { FilePointer, PathFilePointer } from "@sealcode/file-manager";
|
|
2
3
|
import { FlatTemplatable, tempstream } from "tempstream";
|
|
3
4
|
import { MultipleFiles, MultipleFilesOptions } from "./multiple-files.js";
|
|
4
5
|
import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files.js";
|
|
@@ -23,9 +24,12 @@ export class MultiplePhotos extends MultipleFiles {
|
|
|
23
24
|
|
|
24
25
|
async renderFileItem(
|
|
25
26
|
fileItem: CollectionItem,
|
|
26
|
-
file:
|
|
27
|
+
file: FilePointer
|
|
27
28
|
): Promise<FlatTemplatable> {
|
|
28
|
-
|
|
29
|
+
if (!(file instanceof PathFilePointer)) {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
const path = await file.getPath();
|
|
29
33
|
if (!path) {
|
|
30
34
|
return "missing file";
|
|
31
35
|
}
|
|
@@ -36,7 +40,9 @@ export class MultiplePhotos extends MultipleFiles {
|
|
|
36
40
|
sizesAttr: "150px",
|
|
37
41
|
})}
|
|
38
42
|
</a>
|
|
39
|
-
<span class="filename">
|
|
43
|
+
<span class="filename">
|
|
44
|
+
${(await file.getOriginalFilename()) || "no filename"}</span
|
|
45
|
+
>
|
|
40
46
|
${this.renderFileRemoveButton(fileItem)}
|
|
41
47
|
</li>`;
|
|
42
48
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Router from "@koa/router";
|
|
2
|
+
import getPort from "get-port";
|
|
3
|
+
import { Browser, Page } from "playwright";
|
|
4
|
+
import type { Context } from "koa";
|
|
5
|
+
import Koa from "koa";
|
|
6
|
+
import { mount } from "../../mount.js";
|
|
7
|
+
import { Form } from "../form.js";
|
|
8
|
+
import { getBrowser } from "../../utils/browser-creator.js";
|
|
9
|
+
import { Fields } from "../../index.js";
|
|
10
|
+
import { Photo } from "./photo.js";
|
|
11
|
+
import { KoaResponsiveImageRouter } from "koa-responsive-image-router";
|
|
12
|
+
|
|
13
|
+
import _locreq from "locreq";
|
|
14
|
+
import { module_dirname } from "../../utils/module-dirname.js";
|
|
15
|
+
import { FileManager, PathFilePointer } from "@sealcode/file-manager";
|
|
16
|
+
const locreq = _locreq(module_dirname(import.meta.url));
|
|
17
|
+
|
|
18
|
+
describe("photo-controll", () => {
|
|
19
|
+
describe("basic tests", async () => {
|
|
20
|
+
let server: ReturnType<Koa["listen"]>;
|
|
21
|
+
let browser: Browser;
|
|
22
|
+
let page: Page;
|
|
23
|
+
|
|
24
|
+
afterAll(() => {
|
|
25
|
+
server.close();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("sends the old photo if it wasn't changed and it has an initial value", async () => {
|
|
29
|
+
const port = await getPort();
|
|
30
|
+
console.log(`Using port ${port} for photo.test.ts`);
|
|
31
|
+
const app = new Koa();
|
|
32
|
+
const router = new Router();
|
|
33
|
+
|
|
34
|
+
const fields = {
|
|
35
|
+
photo: new Fields.File(false, new FileManager("/tmp", "/uploaded_files")),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let received_photo: unknown;
|
|
39
|
+
|
|
40
|
+
const imageRouter = new KoaResponsiveImageRouter({
|
|
41
|
+
staticPath: "/images",
|
|
42
|
+
thumbnailSize: 20,
|
|
43
|
+
cacheManagerResolutionThreshold: 50,
|
|
44
|
+
imageStoragePath: `/tmp/image-cache-${port}`,
|
|
45
|
+
smartCropStoragePath: `/tmp/smartcrop-storage-${port}`,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
mount(
|
|
49
|
+
router,
|
|
50
|
+
"/",
|
|
51
|
+
new (class extends Form<typeof fields, unknown> {
|
|
52
|
+
fields = fields;
|
|
53
|
+
controls = [new Photo(fields.photo, imageRouter)];
|
|
54
|
+
|
|
55
|
+
async onSubmit(ctx: Context) {
|
|
56
|
+
console.log("OnSubmit!");
|
|
57
|
+
const data = await this.getParsedValues(ctx);
|
|
58
|
+
console.log({ data });
|
|
59
|
+
received_photo = data.photo;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getInitialValues(ctx: Context) {
|
|
63
|
+
const file = new PathFilePointer(
|
|
64
|
+
new FileManager("/tmp", "/uploaded_files"),
|
|
65
|
+
locreq.resolve("assets/important.png")
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
photo: await file.save(false), // to get the token
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async canAccess(_: Context) {
|
|
73
|
+
return { canAccess: true, message: "" };
|
|
74
|
+
}
|
|
75
|
+
})(),
|
|
76
|
+
true
|
|
77
|
+
);
|
|
78
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
79
|
+
server = app.listen(port);
|
|
80
|
+
|
|
81
|
+
browser = await getBrowser();
|
|
82
|
+
const context = await browser.newContext();
|
|
83
|
+
page = await context.newPage();
|
|
84
|
+
await page.goto(`http://localhost:${port}`);
|
|
85
|
+
await page.getByRole("button", { name: "Wyślij" }).click();
|
|
86
|
+
console.log({ received_photo });
|
|
87
|
+
assert(received_photo !== undefined && (received_photo as any).old !== null);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Context } from "koa";
|
|
2
2
|
import { FlatTemplatable, tempstream } from "tempstream";
|
|
3
3
|
import { File } from "./file.js";
|
|
4
|
-
import { File as SealiousFile } from "sealious";
|
|
5
4
|
import { FormField } from "../fields/field.js";
|
|
6
5
|
import { SimpleInputOptions } from "./simple-input.js";
|
|
7
6
|
import { FormControlContext } from "./form-control.js";
|
|
8
7
|
import { FileContainer } from "../fields/file.js";
|
|
9
8
|
import { KoaResponsiveImageRouter } from "koa-responsive-image-router";
|
|
9
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
10
10
|
|
|
11
11
|
export class Photo extends File {
|
|
12
12
|
constructor(
|
|
@@ -23,20 +23,15 @@ export class Photo extends File {
|
|
|
23
23
|
|
|
24
24
|
async renderFilePreview(
|
|
25
25
|
ctx: Context,
|
|
26
|
-
|
|
26
|
+
file: FilePointer | null
|
|
27
27
|
): Promise<FlatTemplatable> {
|
|
28
28
|
const class_name = "file_preview";
|
|
29
29
|
const empty_container = `<div class="${class_name}"></div>`;
|
|
30
|
-
if (!
|
|
30
|
+
if (!file) {
|
|
31
31
|
return empty_container;
|
|
32
32
|
}
|
|
33
|
-
const { id } = file_data;
|
|
34
|
-
if (!id) {
|
|
35
|
-
return empty_container;
|
|
36
|
-
}
|
|
37
|
-
const file = await SealiousFile.fromID(ctx.$app, id);
|
|
38
33
|
return tempstream/* HTML */ `<div class="${class_name}">
|
|
39
|
-
${this.imageRouter.image(file.
|
|
34
|
+
${this.imageRouter.image(await file.getPath(), {
|
|
40
35
|
resolutions: [100],
|
|
41
36
|
sizesAttr: "100px",
|
|
42
37
|
imgStyle: await this.getImgStyle(),
|
|
@@ -47,7 +47,7 @@ export class SimpleInput<
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async preInput(
|
|
50
|
-
_ctx:
|
|
50
|
+
_ctx: FormControlContext,
|
|
51
51
|
_data: Record<string, FormDataValue>
|
|
52
52
|
): Promise<FlatTemplatable> {
|
|
53
53
|
return "";
|
|
@@ -140,7 +140,7 @@ export class SimpleInput<
|
|
|
140
140
|
this.getWrapperModifiers(),
|
|
141
141
|
tempstream/* HTML */ `
|
|
142
142
|
${label ? `<label for="${id}">${label}</label>` : ""}
|
|
143
|
-
${await this.preInput(fctx
|
|
143
|
+
${await this.preInput(fctx, fctx.data)}
|
|
144
144
|
${this.renderInput(
|
|
145
145
|
fctx.ctx,
|
|
146
146
|
renderAttributes(await this.getInputAttributes(fctx)),
|
package/src/forms/fields/file.ts
CHANGED
|
@@ -1,44 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
hasFieldOfType,
|
|
3
|
-
hasShape,
|
|
4
|
-
is,
|
|
5
|
-
predicates,
|
|
6
|
-
} from "@sealcode/ts-predicates";
|
|
1
|
+
import { hasFieldOfType, is, predicates } from "@sealcode/ts-predicates";
|
|
7
2
|
import { Context } from "koa";
|
|
8
|
-
import {
|
|
3
|
+
import { FileManager, FilePointer } from "@sealcode/file-manager";
|
|
9
4
|
import { FormDataValue } from "../form-types.js";
|
|
10
5
|
import { FieldParseResult, FormField } from "./field.js";
|
|
11
6
|
|
|
12
7
|
export type FileContainer = {
|
|
13
|
-
old:
|
|
14
|
-
new:
|
|
8
|
+
old: FilePointer | null;
|
|
9
|
+
new: FilePointer | null;
|
|
15
10
|
};
|
|
16
11
|
|
|
17
12
|
export class File<Required extends boolean> extends FormField<
|
|
18
13
|
Required,
|
|
19
14
|
FileContainer
|
|
20
15
|
> {
|
|
16
|
+
constructor(
|
|
17
|
+
public readonly required: Required,
|
|
18
|
+
public file_manager: FileManager
|
|
19
|
+
) {
|
|
20
|
+
super(required);
|
|
21
|
+
}
|
|
22
|
+
|
|
21
23
|
async parse(
|
|
22
24
|
ctx: Context,
|
|
23
25
|
raw_value: FormDataValue
|
|
24
26
|
): Promise<FieldParseResult<FileContainer>> {
|
|
25
27
|
let result;
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
(is(raw_value, predicates.object) &&
|
|
29
|
-
hasShape(
|
|
30
|
-
{
|
|
31
|
-
filename: predicates.string,
|
|
32
|
-
id: predicates.string,
|
|
33
|
-
},
|
|
34
|
-
raw_value
|
|
35
|
-
) &&
|
|
36
|
-
raw_value.id)
|
|
37
|
-
) {
|
|
38
|
-
const file = await SealiousFile.fromID(
|
|
39
|
-
ctx.$app,
|
|
40
|
-
raw_value.id as string
|
|
41
|
-
);
|
|
28
|
+
if (is(raw_value, predicates.string)) {
|
|
29
|
+
const file = await this.file_manager.fromToken(raw_value);
|
|
42
30
|
result = {
|
|
43
31
|
parsed_value: { old: file, new: null },
|
|
44
32
|
error: null,
|
|
@@ -51,21 +39,15 @@ export class File<Required extends boolean> extends FormField<
|
|
|
51
39
|
parsable: false as false,
|
|
52
40
|
};
|
|
53
41
|
} else {
|
|
54
|
-
const old_file =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{ id: predicates.string, filename: predicates.string },
|
|
58
|
-
raw_value.old
|
|
59
|
-
) &&
|
|
60
|
-
raw_value.old.id
|
|
61
|
-
? await SealiousFile.fromID(ctx.$app, raw_value.old.id)
|
|
62
|
-
: null;
|
|
42
|
+
const old_file = hasFieldOfType(raw_value, "old", predicates.string)
|
|
43
|
+
? await this.file_manager.fromToken(raw_value.old)
|
|
44
|
+
: null;
|
|
63
45
|
const new_file =
|
|
64
46
|
hasFieldOfType(
|
|
65
47
|
raw_value,
|
|
66
48
|
"new",
|
|
67
49
|
predicates.array(predicates.object)
|
|
68
|
-
) && raw_value.new[0] instanceof
|
|
50
|
+
) && raw_value.new[0] instanceof FilePointer
|
|
69
51
|
? raw_value.new[0]
|
|
70
52
|
: null;
|
|
71
53
|
result = {
|
package/src/forms/form-types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FilePointer } from "@sealcode/file-manager";
|
|
2
2
|
import type { FlatTemplatable } from "tempstream";
|
|
3
3
|
import type { FormField } from "./fields/field.js";
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ export type FormDataPrimitive = string | string[] | number | undefined;
|
|
|
6
6
|
export type FormDataValue =
|
|
7
7
|
| FormDataPrimitive
|
|
8
8
|
| Record<string, FormDataPrimitive>
|
|
9
|
-
|
|
|
9
|
+
| FilePointer;
|
|
10
10
|
|
|
11
11
|
export type FieldValueType<F extends FormField> = F extends FormField<infer R>
|
|
12
12
|
? R
|