@sealcode/sealgen 0.19.5 → 0.19.7
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/forms/fields/field.d.ts +1 -0
- package/@types/forms/fields/fields.d.ts +2 -1
- package/@types/forms/fields/get-field-for-sealious.d.ts +2 -1
- package/@types/forms/fields/hybrid.d.ts +11 -0
- package/@types/forms/fields/item-draft.d.ts +20 -0
- package/@types/forms/form.d.ts +1 -0
- package/@types/forms/multiform.d.ts +2 -2
- package/@types/page/list.d.ts +5 -4
- package/@types/page/mountable-with-fields.d.ts +9 -2
- package/@types/page/mountable.d.ts +1 -1
- package/@types/page/page.d.ts +1 -1
- package/@types/templates/form.d.ts +2 -2
- package/lib/forms/controls/checkboxed-list-input.test.js +16 -12
- package/lib/forms/controls/file.test.js +2 -2
- package/lib/forms/controls/image.test.js +4 -4
- package/lib/forms/controls/single-reference.test.js +2 -2
- package/lib/forms/fields/field.test.js +9 -6
- package/lib/forms/fields/fields.js +2 -1
- package/lib/forms/fields/file.test.js +2 -2
- package/lib/forms/fields/get-field-for-sealious.js +6 -0
- package/lib/forms/fields/hybrid.js +37 -0
- package/lib/forms/fields/item-draft.js +34 -0
- package/lib/forms/form.js +11 -5
- package/lib/forms/form.test.js +9 -9
- package/lib/forms/multiform.js +7 -4
- package/lib/forms/multiform.test.js +7 -7
- package/lib/mount.js +4 -1
- package/lib/page/list.js +9 -6
- package/lib/page/mountable-with-fields.js +16 -1
- package/lib/page/mountable-with-fields.test.js +2 -2
- package/lib/page/mountable.js +1 -1
- package/lib/page/page.js +1 -1
- package/lib/templates/form.js +9 -18
- package/lib/templates/shared/collection-create-form.js +8 -8
- package/lib/templates/shared/collection-edit-form.js +9 -9
- package/lib/templates/shared/collection-list.js +2 -1
- package/lib/templates/shared/shared-crud-form-fields.js +4 -4
- package/lib/utils/extract-fields-from-collection.js +3 -0
- package/package.json +1 -1
- package/src/forms/controls/checkboxed-list-input.test.ts +16 -12
- package/src/forms/controls/file.test.ts +2 -2
- package/src/forms/controls/image.test.ts +4 -4
- package/src/forms/controls/single-reference.test.ts +2 -2
- package/src/forms/fields/field.test.ts +9 -6
- package/src/forms/fields/field.ts +5 -6
- package/src/forms/fields/fields.ts +2 -1
- package/src/forms/fields/file.test.ts +2 -2
- package/src/forms/fields/get-field-for-sealious.ts +8 -0
- package/src/forms/fields/hybrid.ts +47 -0
- package/src/forms/fields/item-draft.ts +44 -0
- package/src/forms/form.test.ts +15 -15
- package/src/forms/form.ts +34 -26
- package/src/forms/multiform.test.ts +7 -7
- package/src/forms/multiform.ts +10 -7
- package/src/mount.ts +11 -7
- package/src/page/list.ts +33 -28
- package/src/page/mountable-with-fields.test.ts +2 -2
- package/src/page/mountable-with-fields.ts +26 -5
- package/src/page/mountable.ts +1 -1
- package/src/page/page.ts +1 -1
- package/src/templates/form.ts +9 -18
- package/src/templates/shared/collection-create-form.ts +8 -8
- package/src/templates/shared/collection-edit-form.ts +9 -9
- package/src/templates/shared/collection-list.ts +5 -4
- package/src/templates/shared/shared-crud-form-fields.ts +5 -5
- package/src/utils/extract-fields-from-collection.ts +3 -0
package/lib/page/list.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { tempstream } from "tempstream";
|
|
2
|
-
import { Page } from "./page.js";
|
|
3
|
-
import { naturalNumbers, UrlWithNewParams } from "../utils/utils.js";
|
|
4
|
-
import { makeHiddenInputs } from "../make-hidden-inputs.js";
|
|
5
1
|
import qs from "qs";
|
|
2
|
+
import { tempstream } from "tempstream";
|
|
6
3
|
import { NumberField } from "../forms/fields/number.js";
|
|
7
4
|
import { TextBasedSimpleField } from "../forms/fields/simple-form-field.js";
|
|
5
|
+
import { makeHiddenInputs } from "../make-hidden-inputs.js";
|
|
6
|
+
import { naturalNumbers, UrlWithNewParams } from "../utils/utils.js";
|
|
7
|
+
import { Page } from "./page.js";
|
|
8
8
|
const BasePagePropsShape = {};
|
|
9
9
|
const DEFAULT_ITEMS_PER_PAGE = 12;
|
|
10
10
|
const BaseListPageFields = {
|
|
@@ -38,8 +38,11 @@ class ListPage extends Page {
|
|
|
38
38
|
this.filterFields = {};
|
|
39
39
|
this.filterControls = [];
|
|
40
40
|
}
|
|
41
|
-
init() {
|
|
42
|
-
|
|
41
|
+
init(path, router) {
|
|
42
|
+
if (this.initialized) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
super.init(path, router);
|
|
43
46
|
for (const [, field] of Object.entries(this.filterFields)) {
|
|
44
47
|
void field.init();
|
|
45
48
|
}
|
|
@@ -6,17 +6,32 @@ import { Mountable } from "./mountable.js";
|
|
|
6
6
|
class MountableWithFields extends Mountable {
|
|
7
7
|
constructor() {
|
|
8
8
|
super();
|
|
9
|
+
this.initialized = false;
|
|
9
10
|
this.field_names_prefix = "";
|
|
10
11
|
// useful for multiform, where many forms are merged into one and field assignment is made using the prefix
|
|
11
12
|
this.form_id = "";
|
|
12
13
|
if (!this.fields)
|
|
13
14
|
this.fields = {};
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
/** most routes should be setup using .mount(), but mount runs before
|
|
17
|
+
initialization so we don't yet have controls, app, etc ready for dynamic
|
|
18
|
+
routes **/
|
|
19
|
+
init(path, router) {
|
|
20
|
+
super.init(path, router);
|
|
21
|
+
if (this.initialized) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (this.getFields) {
|
|
25
|
+
this.fields = this.getFields();
|
|
26
|
+
} else if (!this.fields) {
|
|
27
|
+
throw new Error("Fields not defined and getter not provided");
|
|
28
|
+
}
|
|
29
|
+
this.controls = this.getControls(this.fields);
|
|
16
30
|
for (const [field_name, field] of Object.entries(this.fields)) {
|
|
17
31
|
void field.init();
|
|
18
32
|
field.setName(field_name);
|
|
19
33
|
}
|
|
34
|
+
this.initialized = true;
|
|
20
35
|
}
|
|
21
36
|
makeFormControlContext(ctx, data, validate, field_name_prefix = this.field_names_prefix, form_id = this.form_id) {
|
|
22
37
|
return new FormControlContext(
|
|
@@ -10,8 +10,8 @@ describe("mountable-with-fields", () => {
|
|
|
10
10
|
new class extends Form {
|
|
11
11
|
constructor() {
|
|
12
12
|
super(...arguments);
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
13
|
+
this.getFields = () => fields;
|
|
14
|
+
this.getControls = () => [];
|
|
15
15
|
}
|
|
16
16
|
async onSubmit(ctx, form_values) {
|
|
17
17
|
const data = await this.getParsedValues(ctx);
|
package/lib/page/mountable.js
CHANGED
package/lib/page/page.js
CHANGED
package/lib/templates/form.js
CHANGED
|
@@ -3,13 +3,12 @@ const defaults = {
|
|
|
3
3
|
postimport: `
|
|
4
4
|
import { Fields } from "@sealcode/sealgen";
|
|
5
5
|
import type {FormDataValue} from "@sealcode/sealgen";`,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
new Controls.SimpleInput(fields.password, {
|
|
6
|
+
get_fields: `()=>(<const>{ password: new Fields.Password(true) })`,
|
|
7
|
+
get_controls: `
|
|
8
|
+
()=><const>[new Controls.SimpleInput(fields.password, {
|
|
10
9
|
label: "Password:",
|
|
11
10
|
type: "password",
|
|
12
|
-
}),`,
|
|
11
|
+
})],`,
|
|
13
12
|
validate_method: `async validateValues(
|
|
14
13
|
ctx: Context,
|
|
15
14
|
data: Record<string, FormDataValue>
|
|
@@ -43,28 +42,20 @@ async function formTemplate(action_name, _params = defaults) {
|
|
|
43
42
|
const params = { ...defaults, ..._params };
|
|
44
43
|
const content = `import type { Context } from "koa";
|
|
45
44
|
import type { FormData } from "@sealcode/sealgen";
|
|
46
|
-
import { Form, Controls
|
|
45
|
+
import { Form, Controls } from "@sealcode/sealgen";
|
|
47
46
|
import html from "src/back/html.js";
|
|
48
47
|
|
|
49
48
|
${params.postimport}
|
|
50
49
|
|
|
51
50
|
export const actionName = "${action_name}";
|
|
52
51
|
|
|
53
|
-
const
|
|
54
|
-
${params.fields}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
Form.initFieldNames(fields);
|
|
58
|
-
|
|
59
|
-
export const ${action_name}Shape = fieldsToShape(fields);
|
|
52
|
+
const getFields = ${params.get_fields};
|
|
60
53
|
|
|
61
|
-
export default new (class ${action_name}Form extends Form<typeof
|
|
54
|
+
export default new (class ${action_name}Form extends Form<ReturnType<typeof getFields>, void> {
|
|
62
55
|
defaultSuccessMessage = "Formularz wype\u0142niony poprawnie";
|
|
63
|
-
|
|
56
|
+
getFields = getFields;
|
|
64
57
|
|
|
65
|
-
|
|
66
|
-
${params.controls}
|
|
67
|
-
];
|
|
58
|
+
getControls = ${params.get_controls};
|
|
68
59
|
|
|
69
60
|
${params.other_methods}
|
|
70
61
|
|
|
@@ -4,10 +4,10 @@ import extract_fields_from_collection from "../../utils/extract-fields-from-coll
|
|
|
4
4
|
import { wrapAttribute } from "../../utils/wrap-attributes.js";
|
|
5
5
|
async function createItemFormTemplate(create_action_name, newfilefullpath, collection_name, list_action) {
|
|
6
6
|
const collection_fields = await extract_fields_from_collection(collection_name);
|
|
7
|
-
const
|
|
7
|
+
const formFieldsGetterVar = `get${toPascalCase(collection_name)}FormFields`;
|
|
8
8
|
return formTemplate(create_action_name, {
|
|
9
9
|
success_message: "Created!",
|
|
10
|
-
postimport: `import {${
|
|
10
|
+
postimport: `import {${formFieldsGetterVar}, get${toPascalCase(
|
|
11
11
|
collection_name
|
|
12
12
|
)}FormControls }from "./shared.js";
|
|
13
13
|
|
|
@@ -18,12 +18,12 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
|
|
|
18
18
|
import { ${list_action}URL } from "src/back/routes/urls.js";
|
|
19
19
|
import {tempstream} from "tempstream";
|
|
20
20
|
`,
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
get_fields: `get${toPascalCase(collection_name)}FormFields`,
|
|
22
|
+
get_controls: `(fields: ReturnType<typeof getFields>)=>[
|
|
23
23
|
new Controls.FormHeader("Create ${toPascalCase(
|
|
24
24
|
collection_name
|
|
25
25
|
)}"),
|
|
26
|
-
...get${toPascalCase(collection_name)}FormControls()`,
|
|
26
|
+
...get${toPascalCase(collection_name)}FormControls(fields)]`,
|
|
27
27
|
can_access: `canAccess = async (ctx: Context) => {
|
|
28
28
|
const policy = ${toPascalCase(collection_name)}.getPolicy("create");
|
|
29
29
|
const response = await policy.check(ctx.$context);
|
|
@@ -41,14 +41,14 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
|
|
|
41
41
|
{
|
|
42
42
|
${collection_fields.map(
|
|
43
43
|
(field_info) => field_info.form_field.generateCreateValueGetter(field_info, {
|
|
44
|
-
form_field_types:
|
|
44
|
+
form_field_types: "FormFieldTypes",
|
|
45
45
|
sealious_field: `fields${wrapAttribute(field_info.name)}`,
|
|
46
|
-
form_fields: "fields"
|
|
46
|
+
form_fields: "this.fields"
|
|
47
47
|
})
|
|
48
48
|
).join(",\n")}
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
for(const field of Object.values(
|
|
51
|
+
for(const field of Object.values(this.fields)){
|
|
52
52
|
// eslint-disable-next-line no-await-in-loop
|
|
53
53
|
await field.postSealiousCreate(ctx, created, formData)
|
|
54
54
|
}
|
|
@@ -4,11 +4,11 @@ import extract_fields_from_collection from "../../utils/extract-fields-from-coll
|
|
|
4
4
|
import { wrapAttribute } from "../../utils/wrap-attributes.js";
|
|
5
5
|
async function editItemFormTemplate(edit_action_name, newfilefullpath, collection_name, list_action) {
|
|
6
6
|
const collection_fields = await extract_fields_from_collection(collection_name);
|
|
7
|
-
const
|
|
7
|
+
const formFieldsGetterVar = `get${toPascalCase(collection_name)}FormFields`;
|
|
8
8
|
const collectionVar = `${toPascalCase(collection_name)}`;
|
|
9
9
|
return formTemplate(edit_action_name, {
|
|
10
10
|
success_message: "Changes saved!",
|
|
11
|
-
postimport: `import {${
|
|
11
|
+
postimport: `import {${formFieldsGetterVar}, get${toPascalCase(
|
|
12
12
|
collection_name
|
|
13
13
|
)}FormControls }from "../shared.js";
|
|
14
14
|
import { ${collectionVar} } from "src/back/collections/collections.js";
|
|
@@ -17,12 +17,12 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
17
17
|
import type { FormDataValue } from "@sealcode/sealgen";
|
|
18
18
|
import type { CollectionInputWithAllKeys } from "sealious";
|
|
19
19
|
`,
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
get_fields: formFieldsGetterVar,
|
|
21
|
+
get_controls: `(fields: ReturnType<typeof getFields>)=>[
|
|
22
22
|
new Controls.FormHeader("Edit ${toPascalCase(
|
|
23
23
|
collection_name
|
|
24
24
|
)}"),
|
|
25
|
-
...get${toPascalCase(collection_name)}FormControls()`,
|
|
25
|
+
...get${toPascalCase(collection_name)}FormControls(fields)]`,
|
|
26
26
|
can_access: `canAccess = async (ctx: Context) => {
|
|
27
27
|
const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
|
|
28
28
|
const response = await policy.check(ctx.$context);
|
|
@@ -54,7 +54,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
54
54
|
sealious_field: `${collectionVar}.fields${wrapAttribute(
|
|
55
55
|
field_info.name
|
|
56
56
|
)}`,
|
|
57
|
-
form_fields: "fields"
|
|
57
|
+
form_fields: "this.fields"
|
|
58
58
|
}
|
|
59
59
|
)
|
|
60
60
|
).join(",\n")}
|
|
@@ -63,7 +63,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
63
63
|
|
|
64
64
|
await item.save(ctx.$context);
|
|
65
65
|
|
|
66
|
-
for(const field of Object.values(
|
|
66
|
+
for(const field of Object.values(this.fields)){
|
|
67
67
|
// eslint-disable-next-line no-await-in-loop
|
|
68
68
|
await field.postSealiousEdit(ctx, item, formData)
|
|
69
69
|
}
|
|
@@ -79,7 +79,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
79
79
|
return id
|
|
80
80
|
}
|
|
81
81
|
`,
|
|
82
|
-
get_initial_values: `async getInitialValues(ctx: Context): Promise<{ [key in keyof typeof fields]: FormDataValue | undefined }> {
|
|
82
|
+
get_initial_values: `async getInitialValues(ctx: Context): Promise<{ [key in keyof typeof this.fields]: FormDataValue | undefined }> {
|
|
83
83
|
const id = await this.getID(ctx);
|
|
84
84
|
|
|
85
85
|
const {
|
|
@@ -99,7 +99,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
99
99
|
${collection_fields.map(
|
|
100
100
|
(field_info) => field_info.form_field.generateInitialValue(field_info, {
|
|
101
101
|
form_field_types: "FieldTypes",
|
|
102
|
-
form_fields: "fields"
|
|
102
|
+
form_fields: "this.fields"
|
|
103
103
|
})
|
|
104
104
|
).join(",\n")}
|
|
105
105
|
};
|
|
@@ -121,7 +121,8 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
121
121
|
typeof ${uppercase_collection},
|
|
122
122
|
typeof BaseListPageFields
|
|
123
123
|
> {
|
|
124
|
-
|
|
124
|
+
getFields = ()=>BaseListPageFields;
|
|
125
|
+
getControls = ()=>[];
|
|
125
126
|
|
|
126
127
|
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
|
127
128
|
const query_params = qs.parse(ctx.search.slice(1));
|
|
@@ -12,12 +12,12 @@ ${Array.from(
|
|
|
12
12
|
collection_fields.map(
|
|
13
13
|
(field_info) => field_info.form_field.generateImportsForFieldList(field_info)
|
|
14
14
|
).flat().map(
|
|
15
|
-
({ what, from, as }) => `import {${what}${as ? ` as ${as}` : ""} } from "${from}"`
|
|
15
|
+
({ what, from, as, type }) => `import ${type ? "type " : ""}{${what}${as ? ` as ${as}` : ""} } from "${from}"`
|
|
16
16
|
)
|
|
17
17
|
).values()
|
|
18
18
|
).join(";\n")}
|
|
19
19
|
|
|
20
|
-
export const ${toPascalCase(collection_name)}FormFields = <const>{
|
|
20
|
+
export const get${toPascalCase(collection_name)}FormFields = () => <const>{
|
|
21
21
|
${collection_fields.map((field_info) => {
|
|
22
22
|
return `${field_info.name}: ${field_info.form_field.generateFieldDeclaration(field_info, {
|
|
23
23
|
form_field_types: "Fields",
|
|
@@ -28,11 +28,11 @@ export const ${toPascalCase(collection_name)}FormFields = <const>{
|
|
|
28
28
|
}).join(",\n")}
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const getFields = get${toPascalCase(collection_name)}FormFields
|
|
32
32
|
|
|
33
33
|
export const get${toPascalCase(
|
|
34
34
|
collection_name
|
|
35
|
-
)}FormControls: () => Controls.FormControl[] = () => [
|
|
35
|
+
)}FormControls: (fields: ReturnType<typeof getFields>) => Controls.FormControl[] = (fields) => [
|
|
36
36
|
${collection_fields.map(({ name }) => `fields${wrapAttribute(name)}.getControl()`).join(",\n")}
|
|
37
37
|
];
|
|
38
38
|
`;
|
|
@@ -36,6 +36,9 @@ for (const field_name in c.fields){
|
|
|
36
36
|
}
|
|
37
37
|
if(type == "single-reference"){
|
|
38
38
|
target = field.target_collection;
|
|
39
|
+
}
|
|
40
|
+
if(type == "item-draft"){
|
|
41
|
+
target = field.target_collection_name;
|
|
39
42
|
}
|
|
40
43
|
if(type == "deep-reverse-single-reference" || type =="reverse-single-reference"){
|
|
41
44
|
target = field.target_collection;
|
package/package.json
CHANGED
|
@@ -42,8 +42,8 @@ describe("checkboxed-list", () => {
|
|
|
42
42
|
router,
|
|
43
43
|
"/",
|
|
44
44
|
new (class extends Form<typeof fields, unknown> {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
getFields = () => fields;
|
|
46
|
+
getControls = () => [new CheckboxedListInput(fields.syllables)];
|
|
47
47
|
|
|
48
48
|
async onSubmit(ctx: Context) {
|
|
49
49
|
received_values = await this.getParsedValues(ctx);
|
|
@@ -91,8 +91,8 @@ describe("checkboxed-list", () => {
|
|
|
91
91
|
router,
|
|
92
92
|
"/",
|
|
93
93
|
new (class extends Form<typeof fields, unknown> {
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
getFields = () => fields;
|
|
95
|
+
getControls = () => [new CheckboxedListInput(fields.syllables)];
|
|
96
96
|
|
|
97
97
|
async onSubmit(ctx: Context) {
|
|
98
98
|
submit_called = true;
|
|
@@ -138,8 +138,8 @@ describe("checkboxed-list", () => {
|
|
|
138
138
|
router,
|
|
139
139
|
"/",
|
|
140
140
|
new (class extends Form<typeof fields, unknown> {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
getFields = () => fields;
|
|
142
|
+
getControls = () => [new CheckboxedListInput(fields.syllables)];
|
|
143
143
|
|
|
144
144
|
async onSubmit(ctx: Context) {
|
|
145
145
|
submit_called = true;
|
|
@@ -186,8 +186,8 @@ describe("checkboxed-list", () => {
|
|
|
186
186
|
router,
|
|
187
187
|
"/",
|
|
188
188
|
new (class extends Form<typeof fields, unknown> {
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
getFields = () => fields;
|
|
190
|
+
getControls = () => [new CheckboxedListInput(fields.syllables)];
|
|
191
191
|
|
|
192
192
|
async onSubmit(ctx: Context) {
|
|
193
193
|
submit_called = true;
|
|
@@ -232,8 +232,10 @@ describe("checkboxed-list", () => {
|
|
|
232
232
|
router,
|
|
233
233
|
"/",
|
|
234
234
|
new (class extends Form<typeof fields, unknown> {
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
getFields = () => fields;
|
|
236
|
+
getControls = () => [
|
|
237
|
+
new CheckboxedListInput(fields.syllables, { label: "ABC" }),
|
|
238
|
+
];
|
|
237
239
|
|
|
238
240
|
async onSubmit(ctx: Context) {
|
|
239
241
|
// noop
|
|
@@ -283,8 +285,10 @@ describe("checkboxed-list", () => {
|
|
|
283
285
|
router,
|
|
284
286
|
"/",
|
|
285
287
|
new (class extends Form<typeof fields, unknown> {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
+
getFields = () => fields;
|
|
289
|
+
getControls = () => [
|
|
290
|
+
new CheckboxedListInput(fields.syllables, { label: "Letters" }),
|
|
291
|
+
];
|
|
288
292
|
|
|
289
293
|
async onSubmit(ctx: Context) {
|
|
290
294
|
// noop
|
|
@@ -41,8 +41,8 @@ describe("file-control", () => {
|
|
|
41
41
|
router,
|
|
42
42
|
"/",
|
|
43
43
|
new (class extends Form<typeof fields, unknown> {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
getFields = () => fields;
|
|
45
|
+
getControls = () => [new Controls.File(fields.file)];
|
|
46
46
|
|
|
47
47
|
async onSubmit(_ctx: Context) {
|
|
48
48
|
submit_called = true;
|
|
@@ -41,8 +41,8 @@ describe("photo-control", () => {
|
|
|
41
41
|
router,
|
|
42
42
|
"/",
|
|
43
43
|
new (class extends Form<typeof fields, unknown> {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
getFields = () => fields;
|
|
45
|
+
getControls = () => [new Image(fields.photo)];
|
|
46
46
|
|
|
47
47
|
async onSubmit(ctx: Context) {
|
|
48
48
|
const data = await this.getParsedValues(ctx);
|
|
@@ -94,8 +94,8 @@ describe("photo-control", () => {
|
|
|
94
94
|
router,
|
|
95
95
|
"/",
|
|
96
96
|
new (class extends Form<typeof fields, unknown> {
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
getFields = () => fields;
|
|
98
|
+
getControls = () => [new Image(fields.photo)];
|
|
99
99
|
|
|
100
100
|
async onSubmit(ctx: Context) {
|
|
101
101
|
submit_called = true;
|
|
@@ -41,8 +41,8 @@ describe("single-reference.test", () => {
|
|
|
41
41
|
form_file_path,
|
|
42
42
|
await formTemplate("SomeForm", {
|
|
43
43
|
postimport: `import {Comments} from "../collections/collections.js"; import {Fields} from "@sealcode/sealgen";`,
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
get_fields: `()=>(<const>{article: new Fields.CollectionField(true, Comments.fields.article)})`,
|
|
45
|
+
get_controls: `(fields)=>[new Controls.SingleReferenceDropdown(fields.article, {getLabel: item=>item.get("title")})]`,
|
|
46
46
|
validate_method: "",
|
|
47
47
|
success_message: "Submit OK",
|
|
48
48
|
})
|
|
@@ -39,8 +39,8 @@ describe("basic field", () => {
|
|
|
39
39
|
router,
|
|
40
40
|
"/",
|
|
41
41
|
new (class extends Form<typeof fields, unknown> {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
getFields = () => fields;
|
|
43
|
+
getControls = () => [new SimpleInput(fields.text)];
|
|
44
44
|
|
|
45
45
|
async onSubmit(ctx: Context, data: FormData) {
|
|
46
46
|
received_values = await this.getParsedValues(ctx);
|
|
@@ -93,8 +93,8 @@ describe("basic field", () => {
|
|
|
93
93
|
router,
|
|
94
94
|
"/",
|
|
95
95
|
new (class extends Form<typeof fields, unknown> {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
getFields = () => fields;
|
|
97
|
+
getControls = () => [new SimpleInput(fields.text)];
|
|
98
98
|
|
|
99
99
|
async onSubmit() {
|
|
100
100
|
//noop
|
|
@@ -135,8 +135,11 @@ describe("basic field", () => {
|
|
|
135
135
|
router,
|
|
136
136
|
"/",
|
|
137
137
|
new (class extends Form<typeof fields, unknown> {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
getFields = () => fields;
|
|
139
|
+
getControls = () => [
|
|
140
|
+
new SimpleInput(fields.text),
|
|
141
|
+
new Checkbox(fields.checkbox),
|
|
142
|
+
];
|
|
140
143
|
|
|
141
144
|
async onSubmit() {
|
|
142
145
|
submit_called = true;
|
|
@@ -25,9 +25,8 @@ export type FormFieldValidationFn<ValueType> = (
|
|
|
25
25
|
export type FieldParsedValue<T> = T extends FormField<infer R> ? R : never;
|
|
26
26
|
|
|
27
27
|
export type GetPredicate<F extends FormField> =
|
|
28
|
-
F extends FormField<true>
|
|
29
|
-
|
|
30
|
-
: F["predicate"] | typeof predicates.undefined;
|
|
28
|
+
F extends FormField<true> ? F["predicate"]
|
|
29
|
+
: F["predicate"] | typeof predicates.undefined;
|
|
31
30
|
|
|
32
31
|
// for quickly testing GetPredicate conditonally applying the undefined predicate:
|
|
33
32
|
// const f = new SimpleFormField(true);
|
|
@@ -156,9 +155,8 @@ export abstract class FormField<
|
|
|
156
155
|
if (raw == undefined || raw == null || raw == "") {
|
|
157
156
|
return {
|
|
158
157
|
parsed: null,
|
|
159
|
-
message:
|
|
160
|
-
? "empty value, but field is required"
|
|
161
|
-
: "",
|
|
158
|
+
message:
|
|
159
|
+
this.required ? "empty value, but field is required" : "",
|
|
162
160
|
raw,
|
|
163
161
|
...(validate ? { valid: !this.required } : {}),
|
|
164
162
|
};
|
|
@@ -269,6 +267,7 @@ export abstract class FormField<
|
|
|
269
267
|
what: string;
|
|
270
268
|
from: string;
|
|
271
269
|
as?: string;
|
|
270
|
+
type?: boolean;
|
|
272
271
|
}[] {
|
|
273
272
|
return [];
|
|
274
273
|
}
|
|
@@ -15,13 +15,14 @@ export * from "./float.js";
|
|
|
15
15
|
export * from "./html.js";
|
|
16
16
|
export * from "./image.js";
|
|
17
17
|
export * from "./int.js";
|
|
18
|
+
export * from "./item-draft.js";
|
|
18
19
|
export * from "./json.js";
|
|
19
20
|
export * from "./markdown.js";
|
|
20
21
|
export * from "./multiple-files.js";
|
|
21
22
|
export * from "./number.js";
|
|
22
23
|
export * from "./password.js";
|
|
23
|
-
export * from "./phone-number.js";
|
|
24
24
|
export * from "./phone-number-with-country-code.js";
|
|
25
|
+
export * from "./phone-number.js";
|
|
25
26
|
export * from "./pick-from-list-sealious.js";
|
|
26
27
|
export * from "./pick-from-list.js";
|
|
27
28
|
export * from "./regexp.js";
|
|
@@ -41,8 +41,8 @@ describe("file field", () => {
|
|
|
41
41
|
router,
|
|
42
42
|
"/",
|
|
43
43
|
new (class extends Form<typeof fields, unknown> {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
getFields = () => fields;
|
|
45
|
+
getControls = () => [new Image(fields.file)];
|
|
46
46
|
|
|
47
47
|
async onSubmit(ctx: Context) {
|
|
48
48
|
received_values = await this.getParsedValues(ctx);
|
|
@@ -38,6 +38,7 @@ import { EnumMultipleSealious } from "./enum-multiple-sealious.js";
|
|
|
38
38
|
import { URLField } from "./url.js";
|
|
39
39
|
import { Image } from "./image.js";
|
|
40
40
|
import { PhoneNumberWithCountryCode } from "./phone-number-with-country-code.js";
|
|
41
|
+
import { ItemDraft } from "./item-draft.js";
|
|
41
42
|
|
|
42
43
|
export type SealiousToFormField<T> =
|
|
43
44
|
T extends SealiousFieldTypes.Boolean ? BooleanField<false>
|
|
@@ -61,6 +62,8 @@ export type SealiousToFormField<T> =
|
|
|
61
62
|
: T extends SealiousFieldTypes.HTML ? HTML<false>
|
|
62
63
|
: T extends SealiousFieldTypes.Image ? Image<false>
|
|
63
64
|
: T extends SealiousFieldTypes.Int ? Int<false>
|
|
65
|
+
: T extends SealiousFieldTypes.ItemDraft<infer C extends Collection> ?
|
|
66
|
+
ItemDraft<C>
|
|
64
67
|
: T extends SealiousFieldTypes.JsonObject ? JSONField<false>
|
|
65
68
|
: T extends SealiousFieldTypes.JDD ?
|
|
66
69
|
TextBasedSimpleField<false, SimpleInput>
|
|
@@ -170,6 +173,9 @@ export function sealiousToFormField<SF extends SealiousField<any>>(
|
|
|
170
173
|
if (sealious_field instanceof SealiousFieldTypes.Int) {
|
|
171
174
|
return new Int(sealious_field.required) as SealiousToFormField<SF>;
|
|
172
175
|
}
|
|
176
|
+
if (sealious_field instanceof SealiousFieldTypes.ItemDraft) {
|
|
177
|
+
return new ItemDraft(sealious_field) as SealiousToFormField<SF>;
|
|
178
|
+
}
|
|
173
179
|
if (sealious_field instanceof SealiousFieldTypes.JsonObject) {
|
|
174
180
|
return new JSONField(
|
|
175
181
|
sealious_field.required
|
|
@@ -260,6 +266,8 @@ export async function sealiousFieldInfoToFormField(
|
|
|
260
266
|
return new Image(field_info.is_required);
|
|
261
267
|
case "int":
|
|
262
268
|
return new Int(field_info.is_required);
|
|
269
|
+
case "item-draft":
|
|
270
|
+
return new ItemDraft({ target_collection: { fields: {} } } as any);
|
|
263
271
|
case "json-object":
|
|
264
272
|
return new JSONField(field_info.is_required);
|
|
265
273
|
case "password":
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* eslint @typescript-eslint/no-unsafe-argument: off, @typescript-eslint/no-explicit-any: off, @typescript-eslint/no-unsafe-member-access: off */
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { Hybrid as HybridControl } from "../controls/hybrid.js";
|
|
4
|
+
import { FormDataValue } from "../form-types.js";
|
|
5
|
+
import { FieldParseResult, FormField } from "./field.js";
|
|
6
|
+
|
|
7
|
+
export class Hybrid<Fields extends Record<string, FormField>> extends FormField<
|
|
8
|
+
true,
|
|
9
|
+
Record<string, unknown>
|
|
10
|
+
> {
|
|
11
|
+
constructor(public subfields: Fields) {
|
|
12
|
+
super(true);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getEmptyValue(): Record<string, unknown> {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async parse(
|
|
20
|
+
ctx: Context,
|
|
21
|
+
raw_value: FormDataValue
|
|
22
|
+
): Promise<FieldParseResult<Record<string, unknown>>> {
|
|
23
|
+
return {
|
|
24
|
+
parsable: true,
|
|
25
|
+
error: null,
|
|
26
|
+
parsed_value: Object.fromEntries(
|
|
27
|
+
await Promise.all(
|
|
28
|
+
Object.entries(this.subfields).map(
|
|
29
|
+
async ([key, subfield]) => [
|
|
30
|
+
key,
|
|
31
|
+
(
|
|
32
|
+
await subfield.getParsedValue(
|
|
33
|
+
ctx,
|
|
34
|
+
(raw_value as any)[key]
|
|
35
|
+
)
|
|
36
|
+
).parsed as FormDataValue,
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
) as Record<string, unknown>,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getControl(): HybridControl {
|
|
45
|
+
return new HybridControl(this, this.subfields);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Collection, FieldTypes as SealiousFieldTypes } from "sealious";
|
|
2
|
+
import { sealiousToFormField } from "./get-field-for-sealious.js";
|
|
3
|
+
import { Hybrid } from "./hybrid.js";
|
|
4
|
+
import { FormField } from "./field.js";
|
|
5
|
+
import { ExtractedFieldInfo } from "../../utils/extract-fields-from-collection.js";
|
|
6
|
+
import { toPascalCase } from "js-convert-case";
|
|
7
|
+
|
|
8
|
+
export class ItemDraft<C extends Collection> extends Hybrid<{
|
|
9
|
+
[name in keyof C["fields"]]: FormField;
|
|
10
|
+
}> {
|
|
11
|
+
constructor(public sealious_field: SealiousFieldTypes.ItemDraft<C>) {
|
|
12
|
+
super(
|
|
13
|
+
Object.fromEntries(
|
|
14
|
+
Object.entries(sealious_field.target_collection.fields).map(
|
|
15
|
+
([field_name, sealious_field]) => [
|
|
16
|
+
field_name,
|
|
17
|
+
sealiousToFormField(sealious_field),
|
|
18
|
+
]
|
|
19
|
+
)
|
|
20
|
+
) as unknown as {
|
|
21
|
+
[name in keyof C["fields"]]: FormField;
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
generateFieldDeclaration(
|
|
27
|
+
field_info: ExtractedFieldInfo,
|
|
28
|
+
vars: { form_field_types: string; sealious_field: string }
|
|
29
|
+
): string {
|
|
30
|
+
return `new ${vars.form_field_types}.${this.constructor.name}<typeof ${toPascalCase(field_info.target!)}>(${vars.sealious_field})`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
generateImportsForFieldList(
|
|
34
|
+
field_info: ExtractedFieldInfo
|
|
35
|
+
): { what: string; from: string; as?: string; type?: boolean }[] {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
what: toPascalCase(field_info.target!),
|
|
39
|
+
from: "src/back/collections/collections.js",
|
|
40
|
+
type: true,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
}
|