@sealcode/sealgen 0.14.15 → 0.15.0
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 +7 -7
- package/@types/src/forms/controls/multiple-photos.d.ts +2 -2
- package/@types/src/forms/controls/photo.d.ts +3 -4
- package/@types/src/forms/controls/photo.test.d.ts +1 -0
- package/@types/src/forms/controls/simple-input.d.ts +2 -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 -10
- package/dist/forms/controls/multiple-photos.js +8 -2
- package/dist/forms/controls/photo.js +10 -10
- package/dist/forms/controls/photo.test.js +81 -0
- package/dist/forms/controls/simple-input.js +10 -3
- 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 +3 -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 -17
- package/src/forms/controls/multiple-photos.ts +9 -4
- package/src/forms/controls/photo.test.ts +90 -0
- package/src/forms/controls/photo.ts +12 -10
- package/src/forms/controls/simple-input.ts +12 -4
- 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 "sealious";
|
|
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,7 +1,7 @@
|
|
|
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, Collection, FilePointer } 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";
|
|
@@ -10,7 +10,7 @@ import { FormControlContext } from "./form-control.js";
|
|
|
10
10
|
export type MultipleFilesOptions = {
|
|
11
11
|
label?: string;
|
|
12
12
|
uploadLabel?: string;
|
|
13
|
-
getAdditionalFields: (ctx: Context, file:
|
|
13
|
+
getAdditionalFields: (ctx: Context, file: FilePointer) => Promise<Record<string, unknown>>;
|
|
14
14
|
};
|
|
15
15
|
export declare class MultipleFiles extends FormFieldControl {
|
|
16
16
|
field: MultipleFilesField;
|
|
@@ -19,16 +19,16 @@ export declare class MultipleFiles extends FormFieldControl {
|
|
|
19
19
|
getClassModifiers(): string[];
|
|
20
20
|
mount(router: Router): void;
|
|
21
21
|
deleteFileAssociation(ctx: Context, file_item_id: string): Promise<void>;
|
|
22
|
-
addFileAssociation(ctx: Context, file:
|
|
22
|
+
addFileAssociation(ctx: Context, file: FilePointer): Promise<void>;
|
|
23
23
|
getFrameRelativeURL(): string;
|
|
24
24
|
getFrameID(): string;
|
|
25
|
-
renderFrame(
|
|
25
|
+
renderFrame(_ctx: Context, content?: FlatTemplatable): import("stream").Readable;
|
|
26
26
|
render(fctx: FormControlContext): FlatTemplatable | Promise<FlatTemplatable>;
|
|
27
27
|
getReferencingCollection(): Collection;
|
|
28
28
|
getFileField(): SealiousField | null;
|
|
29
|
-
extractFileFromItem(item: CollectionItem): Promise<
|
|
30
|
-
renderFileItemPreview(_fileItem: CollectionItem, file:
|
|
31
|
-
renderFileItem(fileItem: CollectionItem, file:
|
|
29
|
+
extractFileFromItem(item: CollectionItem): Promise<FilePointer | null>;
|
|
30
|
+
renderFileItemPreview(_fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
|
|
31
|
+
renderFileItem(fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
|
|
32
32
|
renderFileRemoveButton(fileItem: CollectionItem): FlatTemplatable;
|
|
33
33
|
getFileItems(ctx: Context): Promise<CollectionItem<any>[]>;
|
|
34
34
|
getInputAttributes(): {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionItem, FilePointer } from "sealious";
|
|
2
2
|
import { FlatTemplatable } from "tempstream";
|
|
3
3
|
import { MultipleFiles, MultipleFilesOptions } from "./multiple-files.js";
|
|
4
4
|
import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files.js";
|
|
@@ -14,5 +14,5 @@ export declare class MultiplePhotos extends MultipleFiles {
|
|
|
14
14
|
name: string;
|
|
15
15
|
multiple: boolean;
|
|
16
16
|
};
|
|
17
|
-
renderFileItem(fileItem: CollectionItem, file:
|
|
17
|
+
renderFileItem(fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
|
|
18
18
|
}
|
|
@@ -6,15 +6,14 @@ 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 "sealious";
|
|
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
|
-
|
|
16
|
-
filename?: string;
|
|
17
|
-
} | null): Promise<FlatTemplatable>;
|
|
15
|
+
renderFilePreview(ctx: Context, file: FilePointer | null): Promise<FlatTemplatable>;
|
|
16
|
+
getWrapperModifiers(): string[];
|
|
18
17
|
getInputAttributes(fctx: FormControlContext): Promise<{
|
|
19
18
|
accept: string;
|
|
20
19
|
onchange: 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;
|
|
@@ -28,5 +28,6 @@ export declare class SimpleInput<ParsedValue, Options extends SimpleInputOptions
|
|
|
28
28
|
getInputAttributes(fctx: FormControlContext): Promise<Record<string, string | boolean>>;
|
|
29
29
|
postInput(_: Context): Promise<FlatTemplatable>;
|
|
30
30
|
renderInput(_ctx: Context, attributes_str: string, _data: Record<string, FormDataValue>): Promise<FlatTemplatable>;
|
|
31
|
+
getWrapperModifiers(): string[];
|
|
31
32
|
render<Fields extends Record<string, FormField>>(fctx: FormControlContext): Promise<FlatTemplatable>;
|
|
32
33
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { Context } from "koa";
|
|
2
|
-
import {
|
|
2
|
+
import { FileManager, FilePointer } from "sealious";
|
|
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 "sealious";
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
PathFilePointer
|
|
3
3
|
} from "sealious";
|
|
4
4
|
import { inputWrapper } from "../../utils/input-wrapper.js";
|
|
5
5
|
import { tempstream } from "tempstream";
|
|
@@ -43,10 +43,7 @@ class MultipleFiles extends FormFieldControl {
|
|
|
43
43
|
}
|
|
44
44
|
await Promise.all(
|
|
45
45
|
files.map(
|
|
46
|
-
(file) => this.addFileAssociation(
|
|
47
|
-
ctx,
|
|
48
|
-
file
|
|
49
|
-
)
|
|
46
|
+
(file) => this.addFileAssociation(ctx, file)
|
|
50
47
|
)
|
|
51
48
|
);
|
|
52
49
|
ctx.body = this.renderFrame(
|
|
@@ -76,7 +73,7 @@ class MultipleFiles extends FormFieldControl {
|
|
|
76
73
|
getFrameID() {
|
|
77
74
|
return `A${this.field.name}__multiple-fields-control`;
|
|
78
75
|
}
|
|
79
|
-
renderFrame(
|
|
76
|
+
renderFrame(_ctx, content) {
|
|
80
77
|
return tempstream`<turbo-frame
|
|
81
78
|
${content ? "" : `src="./${this.getFrameRelativeURL()}"`}
|
|
82
79
|
id="${this.getFrameID()}"
|
|
@@ -107,15 +104,17 @@ class MultipleFiles extends FormFieldControl {
|
|
|
107
104
|
if (!sealious_field) {
|
|
108
105
|
return null;
|
|
109
106
|
}
|
|
110
|
-
return await
|
|
111
|
-
item.
|
|
112
|
-
item.get(sealious_field.name).id
|
|
107
|
+
return await item.collection.app.FileManager.fromToken(
|
|
108
|
+
item.get(sealious_field.name).token
|
|
113
109
|
);
|
|
114
110
|
}
|
|
115
111
|
async renderFileItemPreview(_fileItem, file) {
|
|
116
|
-
return file.
|
|
112
|
+
return file.getOriginalFilename();
|
|
117
113
|
}
|
|
118
114
|
async renderFileItem(fileItem, file) {
|
|
115
|
+
if (!(file instanceof PathFilePointer)) {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
119
118
|
return tempstream`<li class="file-list-item">
|
|
120
119
|
<a
|
|
121
120
|
class="file-list-item__preview"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PathFilePointer } from "sealious";
|
|
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,28 +9,29 @@ 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()
|
|
29
23
|
})}
|
|
30
24
|
</div>`;
|
|
31
25
|
}
|
|
26
|
+
getWrapperModifiers() {
|
|
27
|
+
return [
|
|
28
|
+
...super.getWrapperModifiers(),
|
|
29
|
+
"control-type__image",
|
|
30
|
+
"control-type__photo"
|
|
31
|
+
];
|
|
32
|
+
}
|
|
32
33
|
async getInputAttributes(fctx) {
|
|
33
34
|
const original = await super.getInputAttributes(fctx);
|
|
34
|
-
const { parsed } = await this.field.getValue(fctx.ctx, fctx.data);
|
|
35
35
|
return {
|
|
36
36
|
...original,
|
|
37
37
|
accept: "image/*",
|
|
@@ -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 "sealious";
|
|
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"))
|
|
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"),
|
|
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
|
|
@@ -55,19 +55,26 @@ class SimpleInput extends FormFieldControl {
|
|
|
55
55
|
async renderInput(_ctx, attributes_str, _data) {
|
|
56
56
|
return `<input ${attributes_str} />`;
|
|
57
57
|
}
|
|
58
|
+
getWrapperModifiers() {
|
|
59
|
+
return [
|
|
60
|
+
this.getID(),
|
|
61
|
+
this.getType(),
|
|
62
|
+
"type__" + this.getType(),
|
|
63
|
+
getRequiredClass(this.field.required)
|
|
64
|
+
];
|
|
65
|
+
}
|
|
58
66
|
async render(fctx) {
|
|
59
67
|
const id = this.getID();
|
|
60
68
|
const label = this.getLabel();
|
|
61
|
-
const type = this.getType();
|
|
62
69
|
const { valid, message } = await this.field.getValue(
|
|
63
70
|
fctx.ctx,
|
|
64
71
|
fctx.data
|
|
65
72
|
);
|
|
66
73
|
return inputWrapper(
|
|
67
|
-
|
|
74
|
+
this.getWrapperModifiers(),
|
|
68
75
|
tempstream`
|
|
69
76
|
${label ? `<label for="${id}">${label}</label>` : ""}
|
|
70
|
-
${await this.preInput(fctx
|
|
77
|
+
${await this.preInput(fctx, fctx.data)}
|
|
71
78
|
${this.renderInput(
|
|
72
79
|
fctx.ctx,
|
|
73
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 "sealious";
|
|
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.0",
|
|
4
4
|
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"@sealcode/ts-predicates": "^0.4.3",
|
|
56
56
|
"deepmerge": "^4.3.1",
|
|
57
57
|
"esbuild": "^0.20.0",
|
|
58
|
+
"escape-goat": "^4.0.0",
|
|
58
59
|
"google-fonts-helper": "^3.4.1",
|
|
59
60
|
"js-convert-case": "^4.2.0",
|
|
60
61
|
"locreq": "^3.0.0",
|
|
@@ -71,6 +72,6 @@
|
|
|
71
72
|
"peerDependencies": {
|
|
72
73
|
"koa": "^2.13.0",
|
|
73
74
|
"koa-responsive-image-router": "^0.2.24",
|
|
74
|
-
"sealious": "^0.
|
|
75
|
+
"sealious": "^0.19.2"
|
|
75
76
|
}
|
|
76
77
|
}
|
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 "sealious";
|
|
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,9 +1,10 @@
|
|
|
1
1
|
import Router from "@koa/router";
|
|
2
2
|
import {
|
|
3
3
|
CollectionItem,
|
|
4
|
-
File as SealiousFile,
|
|
5
4
|
Field as SealiousField,
|
|
6
5
|
Collection,
|
|
6
|
+
FilePointer,
|
|
7
|
+
PathFilePointer,
|
|
7
8
|
} from "sealious";
|
|
8
9
|
import { Context } from "koa";
|
|
9
10
|
import { inputWrapper } from "../../utils/input-wrapper.js";
|
|
@@ -19,7 +20,7 @@ export type MultipleFilesOptions = {
|
|
|
19
20
|
uploadLabel?: string;
|
|
20
21
|
getAdditionalFields: (
|
|
21
22
|
ctx: Context,
|
|
22
|
-
file:
|
|
23
|
+
file: FilePointer
|
|
23
24
|
) => Promise<Record<string, unknown>>;
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -57,7 +58,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
57
58
|
);
|
|
58
59
|
|
|
59
60
|
router.post(this.getFrameRelativeURL() + "/add", async (ctx) => {
|
|
60
|
-
let files = ctx.$body.files as
|
|
61
|
+
let files = ctx.$body.files as FilePointer | FilePointer[];
|
|
61
62
|
if (!files || !is(files, predicates.object)) {
|
|
62
63
|
ctx.body = "Missing files";
|
|
63
64
|
return;
|
|
@@ -67,10 +68,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
67
68
|
}
|
|
68
69
|
await Promise.all(
|
|
69
70
|
files.map((file) =>
|
|
70
|
-
this.addFileAssociation(
|
|
71
|
-
ctx,
|
|
72
|
-
file as unknown as SealiousFile
|
|
73
|
-
)
|
|
71
|
+
this.addFileAssociation(ctx, file as unknown as FilePointer)
|
|
74
72
|
)
|
|
75
73
|
);
|
|
76
74
|
ctx.body = this.renderFrame(
|
|
@@ -86,7 +84,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
86
84
|
].removeByID(ctx.$context, file_item_id);
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
async addFileAssociation(ctx: Context, file:
|
|
87
|
+
async addFileAssociation(ctx: Context, file: FilePointer) {
|
|
90
88
|
const file_field = this.getFileField();
|
|
91
89
|
if (!file_field) {
|
|
92
90
|
throw new Error("No file field in referencing collection");
|
|
@@ -111,7 +109,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
111
109
|
return `A${this.field.name}__multiple-fields-control`;
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
renderFrame(
|
|
112
|
+
renderFrame(_ctx: Context, content?: FlatTemplatable) {
|
|
115
113
|
return tempstream/* HTML */ `<turbo-frame
|
|
116
114
|
${content ? "" : `src="./${this.getFrameRelativeURL()}"`}
|
|
117
115
|
id="${this.getFrameID()}"
|
|
@@ -148,28 +146,30 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
148
146
|
|
|
149
147
|
async extractFileFromItem(
|
|
150
148
|
item: CollectionItem
|
|
151
|
-
): Promise<
|
|
149
|
+
): Promise<FilePointer | null> {
|
|
152
150
|
const sealious_field = this.getFileField();
|
|
153
151
|
if (!sealious_field) {
|
|
154
152
|
return null;
|
|
155
153
|
}
|
|
156
|
-
return await
|
|
157
|
-
|
|
158
|
-
item.get(sealious_field.name).id
|
|
154
|
+
return await item.collection.app.FileManager.fromToken(
|
|
155
|
+
item.get(sealious_field.name).token
|
|
159
156
|
);
|
|
160
157
|
}
|
|
161
158
|
|
|
162
159
|
async renderFileItemPreview(
|
|
163
160
|
_fileItem: CollectionItem,
|
|
164
|
-
file:
|
|
161
|
+
file: FilePointer
|
|
165
162
|
): Promise<FlatTemplatable> {
|
|
166
|
-
return file.
|
|
163
|
+
return file.getOriginalFilename();
|
|
167
164
|
}
|
|
168
165
|
|
|
169
166
|
async renderFileItem(
|
|
170
167
|
fileItem: CollectionItem,
|
|
171
|
-
file:
|
|
168
|
+
file: FilePointer
|
|
172
169
|
): Promise<FlatTemplatable> {
|
|
170
|
+
if (!(file instanceof PathFilePointer)) {
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
173
|
return tempstream/* HTML */ `<li class="file-list-item">
|
|
174
174
|
<a
|
|
175
175
|
class="file-list-item__preview"
|
|
@@ -226,7 +226,7 @@ export class MultipleFiles extends FormFieldControl {
|
|
|
226
226
|
await this.extractFileFromItem(item),
|
|
227
227
|
])
|
|
228
228
|
)
|
|
229
|
-
).filter(([_, f]) => f !== null) as [CollectionItem,
|
|
229
|
+
).filter(([_, f]) => f !== null) as [CollectionItem, FilePointer][];
|
|
230
230
|
|
|
231
231
|
return inputWrapper(
|
|
232
232
|
["multiple-files", this.field.name, ...this.getClassModifiers()],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionItem, FilePointer, PathFilePointer } from "sealious";
|
|
2
2
|
import { FlatTemplatable, tempstream } from "tempstream";
|
|
3
3
|
import { MultipleFiles, MultipleFilesOptions } from "./multiple-files.js";
|
|
4
4
|
import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files.js";
|
|
@@ -23,9 +23,12 @@ export class MultiplePhotos extends MultipleFiles {
|
|
|
23
23
|
|
|
24
24
|
async renderFileItem(
|
|
25
25
|
fileItem: CollectionItem,
|
|
26
|
-
file:
|
|
26
|
+
file: FilePointer
|
|
27
27
|
): Promise<FlatTemplatable> {
|
|
28
|
-
|
|
28
|
+
if (!(file instanceof PathFilePointer)) {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
const path = await file.getPath();
|
|
29
32
|
if (!path) {
|
|
30
33
|
return "missing file";
|
|
31
34
|
}
|
|
@@ -36,7 +39,9 @@ export class MultiplePhotos extends MultipleFiles {
|
|
|
36
39
|
sizesAttr: "150px",
|
|
37
40
|
})}
|
|
38
41
|
</a>
|
|
39
|
-
<span class="filename">
|
|
42
|
+
<span class="filename">
|
|
43
|
+
${(await file.getOriginalFilename()) || "no filename"}</span
|
|
44
|
+
>
|
|
40
45
|
${this.renderFileRemoveButton(fileItem)}
|
|
41
46
|
</li>`;
|
|
42
47
|
}
|
|
@@ -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 "sealious";
|
|
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")),
|
|
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"),
|
|
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 "sealious";
|
|
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(),
|
|
@@ -44,12 +39,19 @@ export class Photo extends File {
|
|
|
44
39
|
</div>`;
|
|
45
40
|
}
|
|
46
41
|
|
|
42
|
+
getWrapperModifiers(): string[] {
|
|
43
|
+
return [
|
|
44
|
+
...super.getWrapperModifiers(),
|
|
45
|
+
"control-type__image",
|
|
46
|
+
"control-type__photo",
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
47
50
|
async getInputAttributes(fctx: FormControlContext) {
|
|
48
51
|
const original = (await super.getInputAttributes(fctx)) as Record<
|
|
49
52
|
string,
|
|
50
53
|
string | boolean
|
|
51
54
|
>;
|
|
52
|
-
const { parsed } = await this.field.getValue(fctx.ctx, fctx.data);
|
|
53
55
|
return {
|
|
54
56
|
...original,
|
|
55
57
|
accept: "image/*",
|
|
@@ -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 "";
|
|
@@ -117,22 +117,30 @@ export class SimpleInput<
|
|
|
117
117
|
return `<input ${attributes_str} />`;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
getWrapperModifiers(): string[] {
|
|
121
|
+
return [
|
|
122
|
+
this.getID(),
|
|
123
|
+
this.getType(),
|
|
124
|
+
"type__" + this.getType(),
|
|
125
|
+
getRequiredClass(this.field.required),
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
|
|
120
129
|
async render<Fields extends Record<string, FormField>>(
|
|
121
130
|
fctx: FormControlContext
|
|
122
131
|
): Promise<FlatTemplatable> {
|
|
123
132
|
const id = this.getID();
|
|
124
133
|
const label = this.getLabel();
|
|
125
|
-
const type = this.getType();
|
|
126
134
|
|
|
127
135
|
const { valid, message } = await this.field.getValue(
|
|
128
136
|
fctx.ctx,
|
|
129
137
|
fctx.data
|
|
130
138
|
);
|
|
131
139
|
return inputWrapper(
|
|
132
|
-
|
|
140
|
+
this.getWrapperModifiers(),
|
|
133
141
|
tempstream/* HTML */ `
|
|
134
142
|
${label ? `<label for="${id}">${label}</label>` : ""}
|
|
135
|
-
${await this.preInput(fctx
|
|
143
|
+
${await this.preInput(fctx, fctx.data)}
|
|
136
144
|
${this.renderInput(
|
|
137
145
|
fctx.ctx,
|
|
138
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 "sealious";
|
|
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 "sealious";
|
|
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
|