@sealcode/sealgen 0.15.35 → 0.15.36
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/.xunit +59 -57
- package/@types/src/forms/controls/form-control.d.ts +2 -1
- package/@types/src/forms/controls/table.d.ts +31 -4
- package/@types/src/forms/fields/table.d.ts +4 -2
- package/@types/src/templates/form.d.ts +5 -0
- package/@types/src/templates/shared/collection-create-form.d.ts +0 -2
- package/@types/src/templates/shared/handlers/boolean.d.ts +2 -0
- package/@types/src/templates/shared/handlers/date.d.ts +2 -0
- package/@types/src/templates/shared/handlers/deep-reverse-single-reference.d.ts +2 -0
- package/@types/src/templates/shared/handlers/default.d.ts +2 -0
- package/@types/src/templates/shared/handlers/enum.d.ts +2 -0
- package/@types/src/templates/shared/handlers/file.d.ts +2 -0
- package/@types/src/templates/shared/handlers/image.d.ts +2 -0
- package/@types/src/templates/shared/handlers/int.d.ts +2 -0
- package/@types/src/templates/shared/handlers/jdd.d.ts +2 -0
- package/@types/src/templates/shared/handlers/single-reference.d.ts +2 -0
- package/@types/src/templates/shared/handlers/structured-array.d.ts +2 -0
- package/@types/src/templates/shared/shared-crud-form-fields.d.ts +29 -6
- package/@types/src/utils/extract-fields-from-collection.d.ts +12 -2
- package/@types/src/utils/with-fallback.d.ts +1 -1
- package/lib/add-crud.js +5 -5
- package/lib/cli.js +2 -0
- package/lib/forms/controls/form-control.js +2 -2
- package/lib/forms/controls/multiple-photos.js +2 -1
- package/lib/forms/controls/single-reference.test.js +1 -1
- package/lib/forms/controls/table.js +114 -12
- package/lib/forms/fields/boolean.js +1 -1
- package/lib/forms/fields/field.js +4 -1
- package/lib/forms/fields/table.js +8 -3
- package/lib/forms/form.js +7 -6
- package/lib/page/default-list-filters.js +1 -0
- package/lib/templates/form.js +25 -16
- package/lib/templates/list.js +6 -167
- package/lib/templates/shared/collection-create-form.js +23 -83
- package/lib/templates/shared/collection-edit-form.js +53 -96
- package/lib/templates/shared/collection-list.js +21 -6
- package/lib/templates/shared/confirm-item-delete.js +7 -5
- package/lib/templates/shared/handlers/boolean.js +14 -0
- package/lib/templates/shared/handlers/date.js +8 -0
- package/lib/templates/shared/handlers/deep-reverse-single-reference.js +209 -0
- package/lib/templates/shared/handlers/default.js +50 -0
- package/lib/templates/shared/handlers/enum.js +19 -0
- package/lib/templates/shared/handlers/file.js +18 -0
- package/lib/templates/shared/handlers/image.js +32 -0
- package/lib/templates/shared/handlers/int.js +11 -0
- package/lib/templates/shared/handlers/jdd.js +10 -0
- package/lib/templates/shared/handlers/single-reference.js +12 -0
- package/lib/templates/shared/handlers/structured-array.js +33 -0
- package/lib/templates/shared/item-delete.js +5 -3
- package/lib/templates/shared/shared-crud-form-fields.js +72 -82
- package/lib/utils/extract-fields-from-collection.js +31 -5
- package/package.json +2 -1
- package/src/add-crud.ts +153 -0
- package/src/cli.ts +2 -0
- package/src/forms/controls/form-control.ts +3 -2
- package/src/forms/controls/multiple-photos.ts +1 -0
- package/src/forms/controls/single-reference.test.ts +1 -1
- package/src/forms/controls/table.ts +160 -30
- package/src/forms/fields/boolean.ts +1 -1
- package/src/forms/fields/field.ts +4 -1
- package/src/forms/fields/table.ts +25 -6
- package/src/forms/form.ts +13 -7
- package/src/page/default-list-filters.ts +1 -0
- package/src/templates/form.ts +24 -15
- package/src/templates/list.ts +6 -174
- package/src/templates/shared/collection-create-form.ts +121 -0
- package/src/templates/shared/collection-edit-form.ts +196 -0
- package/src/templates/shared/collection-list.ts +252 -0
- package/src/templates/shared/confirm-item-delete.ts +78 -0
- package/src/templates/shared/handlers/boolean.ts +18 -0
- package/src/templates/shared/handlers/date.ts +12 -0
- package/src/templates/shared/handlers/deep-reverse-single-reference.ts +244 -0
- package/src/templates/shared/handlers/default.ts +58 -0
- package/src/templates/shared/handlers/enum.ts +23 -0
- package/src/templates/shared/handlers/file.ts +22 -0
- package/src/templates/shared/handlers/image.ts +45 -0
- package/src/templates/shared/handlers/int.ts +15 -0
- package/src/templates/shared/handlers/jdd.ts +14 -0
- package/src/templates/shared/handlers/single-reference.ts +16 -0
- package/src/templates/shared/handlers/structured-array.ts +42 -0
- package/src/templates/shared/item-delete.ts +45 -0
- package/src/templates/shared/shared-crud-form-fields.ts +179 -0
- package/src/utils/extract-fields-from-collection.ts +45 -6
- package/src/utils/with-fallback.ts +1 -1
- package/src/utils/write-file.ts +8 -0
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import { hasShape, is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import qs from "qs";
|
|
1
3
|
import { FormControl } from "./form-control.js";
|
|
2
4
|
class Table extends FormControl {
|
|
3
|
-
constructor(table_field,
|
|
5
|
+
constructor(table_field, options) {
|
|
4
6
|
super();
|
|
5
7
|
this.table_field = table_field;
|
|
6
|
-
this.
|
|
8
|
+
this.options = options;
|
|
7
9
|
this.role = "input";
|
|
8
10
|
}
|
|
9
11
|
async render(fctx) {
|
|
10
|
-
|
|
12
|
+
let { parsed: rows } = await this.table_field.getValue(
|
|
11
13
|
fctx.ctx,
|
|
12
14
|
fctx.data
|
|
13
15
|
);
|
|
14
16
|
if (!rows) {
|
|
15
|
-
|
|
17
|
+
rows = [];
|
|
16
18
|
}
|
|
17
19
|
return (
|
|
18
20
|
/* HTML */
|
|
@@ -27,34 +29,134 @@ class Table extends FormControl {
|
|
|
27
29
|
...rows.length >= 20 ? ["form-input__wrapper--options-count--20-or-more"] : []
|
|
28
30
|
].join(" ")}"
|
|
29
31
|
>
|
|
32
|
+
<label>${this.options.label}</label>
|
|
30
33
|
<div class="table__wrapper">
|
|
31
34
|
<table>
|
|
32
35
|
<tbody>
|
|
33
|
-
|
|
34
|
-
${rows?.map((row, row_index) => {
|
|
36
|
+
${rows?.map((row, row_index) => {
|
|
35
37
|
return (
|
|
36
38
|
/* HTML */
|
|
37
39
|
`<tr>
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
).map(([key]) => {
|
|
41
|
-
return `<td>${this.render_fields[key](
|
|
40
|
+
${Object.entries(this.table_field.columns).map(([key]) => {
|
|
41
|
+
return `<td>${this.options.render_fields[key](
|
|
42
42
|
fctx,
|
|
43
43
|
`${this.table_field.name}[${row_index}][${key}]`,
|
|
44
44
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
45
45
|
row[key]
|
|
46
46
|
)}</td>`;
|
|
47
47
|
}).join("")}
|
|
48
|
-
|
|
48
|
+
${this.options.allow_removing ? (
|
|
49
|
+
/* HTML */
|
|
50
|
+
`<td>
|
|
51
|
+
<input
|
|
52
|
+
type="submit"
|
|
53
|
+
value="${this.options.label_remove || "remove"}"
|
|
54
|
+
form="${fctx.form_id}"
|
|
55
|
+
formnovalidate
|
|
56
|
+
formaction="${this.getActionURL(
|
|
57
|
+
this.table_field.name,
|
|
58
|
+
{
|
|
59
|
+
remove: {
|
|
60
|
+
index: row_index
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)}"
|
|
64
|
+
/>
|
|
65
|
+
</td>`
|
|
66
|
+
) : ""}
|
|
67
|
+
</tr>`
|
|
49
68
|
);
|
|
50
69
|
}).join("")}
|
|
51
|
-
</tr>
|
|
52
70
|
</tbody>
|
|
53
71
|
</table>
|
|
72
|
+
${this.options.allow_adding ? (
|
|
73
|
+
/* HTML */
|
|
74
|
+
`<input
|
|
75
|
+
type="submit"
|
|
76
|
+
value="${this.options.label_add || "add"}"
|
|
77
|
+
form="${fctx.form_id}"
|
|
78
|
+
formnovalidate
|
|
79
|
+
formaction="${this.getActionURL(
|
|
80
|
+
this.table_field.name,
|
|
81
|
+
{
|
|
82
|
+
insert: { index: rows.length, value: {} }
|
|
83
|
+
}
|
|
84
|
+
)}"
|
|
85
|
+
/>`
|
|
86
|
+
) : ""}
|
|
54
87
|
</div>
|
|
55
88
|
</div>`
|
|
56
89
|
);
|
|
57
90
|
}
|
|
91
|
+
getFrameID() {
|
|
92
|
+
return `array-frame-${this.table_field.name}`;
|
|
93
|
+
}
|
|
94
|
+
getActionURL(field_name, action) {
|
|
95
|
+
return `./?${qs.stringify({
|
|
96
|
+
action,
|
|
97
|
+
field_name
|
|
98
|
+
})}`;
|
|
99
|
+
}
|
|
100
|
+
mount(router, form) {
|
|
101
|
+
router.post("/", async (ctx, next) => {
|
|
102
|
+
const action = ctx.$body.action;
|
|
103
|
+
const field_name = ctx.$body.field_name;
|
|
104
|
+
if (!is(action, predicates.object) || !is(field_name, predicates.string)) {
|
|
105
|
+
await next();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (hasShape(
|
|
109
|
+
{
|
|
110
|
+
insert: predicates.shape({ index: predicates.string })
|
|
111
|
+
},
|
|
112
|
+
action
|
|
113
|
+
)) {
|
|
114
|
+
if (field_name !== this.table_field.name) {
|
|
115
|
+
await next();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!ctx.$body[this.table_field.name]) {
|
|
119
|
+
ctx.$body[this.table_field.name] = {};
|
|
120
|
+
}
|
|
121
|
+
ctx.$body[this.table_field.name][action.insert.index] = await this.options.make_new_row(ctx);
|
|
122
|
+
}
|
|
123
|
+
if (hasShape(
|
|
124
|
+
{
|
|
125
|
+
remove: predicates.shape({ index: predicates.string })
|
|
126
|
+
},
|
|
127
|
+
action
|
|
128
|
+
)) {
|
|
129
|
+
if (field_name !== this.table_field.name) {
|
|
130
|
+
await next();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!ctx.$body[this.table_field.name]) {
|
|
134
|
+
ctx.$body[this.table_field.name] = {};
|
|
135
|
+
}
|
|
136
|
+
ctx.$body[this.table_field.name] = Object.fromEntries(
|
|
137
|
+
Object.entries(
|
|
138
|
+
Object.values(
|
|
139
|
+
ctx.$body[this.table_field.name]
|
|
140
|
+
).filter(
|
|
141
|
+
(_, index) => index != parseInt(action.remove.index)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
ctx.override_reaction = {
|
|
147
|
+
action: "stay",
|
|
148
|
+
content: form.render(
|
|
149
|
+
ctx,
|
|
150
|
+
{
|
|
151
|
+
raw_values: await form.extractRawValues(ctx),
|
|
152
|
+
messages: []
|
|
153
|
+
},
|
|
154
|
+
false
|
|
155
|
+
)
|
|
156
|
+
};
|
|
157
|
+
await next();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
58
160
|
}
|
|
59
161
|
export {
|
|
60
162
|
Table
|
|
@@ -2,7 +2,7 @@ import { is, predicates } from "@sealcode/ts-predicates";
|
|
|
2
2
|
import { FormField } from "./field.js";
|
|
3
3
|
class Table extends FormField {
|
|
4
4
|
constructor(columns) {
|
|
5
|
-
super(
|
|
5
|
+
super(false);
|
|
6
6
|
this.columns = columns;
|
|
7
7
|
}
|
|
8
8
|
getEmptyValue() {
|
|
@@ -21,6 +21,7 @@ class Table extends FormField {
|
|
|
21
21
|
const row_data = raw_value[row_number];
|
|
22
22
|
const parsed_row = {};
|
|
23
23
|
if (!is(row_data, predicates.object)) {
|
|
24
|
+
console.log("table.ts<fields>:55", this.name);
|
|
24
25
|
throw new Error(`Expected object, got ${String(row_data)}`);
|
|
25
26
|
}
|
|
26
27
|
for (const key in row_data) {
|
|
@@ -34,13 +35,17 @@ class Table extends FormField {
|
|
|
34
35
|
}
|
|
35
36
|
const parse_result = await field.parse(
|
|
36
37
|
ctx,
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
37
39
|
row_data[key]
|
|
38
40
|
);
|
|
39
41
|
if (!parse_result.parsable) {
|
|
40
|
-
|
|
42
|
+
const message = `Invalid value for row ${row_number}.${key}: ${parse_result.error}`;
|
|
43
|
+
console.warn(
|
|
44
|
+
`Invalid value for row ${row_number}.${key}: ${parse_result.error}`
|
|
45
|
+
);
|
|
41
46
|
return {
|
|
42
47
|
parsable: false,
|
|
43
|
-
error:
|
|
48
|
+
error: message,
|
|
44
49
|
parsed_value: null
|
|
45
50
|
};
|
|
46
51
|
}
|
package/lib/forms/form.js
CHANGED
|
@@ -170,6 +170,11 @@ class Form extends MountableWithFields {
|
|
|
170
170
|
}
|
|
171
171
|
await next();
|
|
172
172
|
});
|
|
173
|
+
const subrouter = new Router();
|
|
174
|
+
for (const control of this.controls) {
|
|
175
|
+
control.mount(subrouter, this);
|
|
176
|
+
}
|
|
177
|
+
router.use(path, subrouter.routes(), subrouter.allowedMethods());
|
|
173
178
|
router.get(path, async (ctx) => {
|
|
174
179
|
ctx.type = "html";
|
|
175
180
|
ctx.body = await this.render(
|
|
@@ -182,7 +187,8 @@ class Form extends MountableWithFields {
|
|
|
182
187
|
);
|
|
183
188
|
});
|
|
184
189
|
router.post(path, async (ctx) => {
|
|
185
|
-
|
|
190
|
+
console.log("REGULAR FORM HANDLE");
|
|
191
|
+
const reaction = ctx.override_reaction || await this.handlePost(ctx);
|
|
186
192
|
if (reaction.action == "stay") {
|
|
187
193
|
ctx.status = 422;
|
|
188
194
|
ctx.body = reaction.content;
|
|
@@ -191,11 +197,6 @@ class Form extends MountableWithFields {
|
|
|
191
197
|
ctx.redirect(reaction.url);
|
|
192
198
|
}
|
|
193
199
|
});
|
|
194
|
-
const subrouter = new Router();
|
|
195
|
-
for (const control of this.controls) {
|
|
196
|
-
control.mount(subrouter);
|
|
197
|
-
}
|
|
198
|
-
router.use(path, subrouter.routes(), subrouter.allowedMethods());
|
|
199
200
|
}
|
|
200
201
|
async extractRawValues(ctx) {
|
|
201
202
|
return Object.keys(ctx.$body).length ? ctx.$body : this.getInitialValues(ctx);
|
package/lib/templates/form.js
CHANGED
|
@@ -2,7 +2,9 @@ import Prettier from "prettier";
|
|
|
2
2
|
import { curryImportPath } from "../utils/import-path.js";
|
|
3
3
|
import { getPrettierConfig } from "../utils/prettier.js";
|
|
4
4
|
const defaults = {
|
|
5
|
-
postimport:
|
|
5
|
+
postimport: `
|
|
6
|
+
import { Fields } from "@sealcode/sealgen";
|
|
7
|
+
import type {FormDataValue} from "@sealcode/sealgen";`,
|
|
6
8
|
fields: `
|
|
7
9
|
email: new Fields.CollectionField(true, Users.fields.email),
|
|
8
10
|
password: new Fields.SimpleFormField(true),`,
|
|
@@ -25,16 +27,28 @@ const defaults = {
|
|
|
25
27
|
} else {
|
|
26
28
|
return { valid: true, error: "" };
|
|
27
29
|
}
|
|
28
|
-
}
|
|
30
|
+
}`,
|
|
31
|
+
onsubmit: ` async onSubmit() {
|
|
32
|
+
//noop
|
|
33
|
+
return;
|
|
34
|
+
}`,
|
|
35
|
+
can_access: ` // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
36
|
+
async canAccess(_: Context) {
|
|
37
|
+
return { canAccess: true, message: "" };
|
|
38
|
+
}`,
|
|
39
|
+
render: `async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
|
40
|
+
return html(ctx, "Form", await super.render(ctx, data, show_field_errors));
|
|
41
|
+
}`,
|
|
42
|
+
get_initial_values: "",
|
|
43
|
+
other_methods: ""
|
|
29
44
|
};
|
|
30
45
|
async function formTemplate(action_name, newfilefullpath, _params = defaults) {
|
|
31
46
|
const params = { ...defaults, ..._params };
|
|
32
47
|
const rel = curryImportPath(newfilefullpath);
|
|
33
48
|
const content = `import type { Context } from "koa";
|
|
34
|
-
import type { FormData
|
|
35
|
-
import { Form,
|
|
49
|
+
import type { FormData } from "@sealcode/sealgen";
|
|
50
|
+
import { Form, Controls, fieldsToShape } from "@sealcode/sealgen";
|
|
36
51
|
import html from "${rel("src/back/html.js")}";
|
|
37
|
-
import { Users } from "${rel("src/back/collections/collections.js")}";
|
|
38
52
|
|
|
39
53
|
${params.postimport}
|
|
40
54
|
|
|
@@ -54,20 +68,15 @@ export default new (class ${action_name}Form extends Form<typeof fields, void> {
|
|
|
54
68
|
${params.controls}
|
|
55
69
|
];
|
|
56
70
|
|
|
71
|
+
${params.other_methods}
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
async canAccess(_: Context) {
|
|
60
|
-
return { canAccess: true, message: "" };
|
|
61
|
-
}
|
|
73
|
+
${params.get_initial_values}
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
75
|
+
${params.onsubmit}
|
|
76
|
+
|
|
77
|
+
${params.can_access}
|
|
67
78
|
|
|
68
|
-
|
|
69
|
-
return html(ctx, "${action_name}", await super.render(ctx, data, show_field_errors));
|
|
70
|
-
}
|
|
79
|
+
${params.render}
|
|
71
80
|
})();
|
|
72
81
|
`;
|
|
73
82
|
return Prettier.format(content, await getPrettierConfig());
|
package/lib/templates/list.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import _locreq from "locreq";
|
|
2
|
-
import { curryImportPath } from "../utils/import-path.js";
|
|
3
1
|
import prompts from "prompts";
|
|
4
2
|
import { listCollections } from "../utils/list-collections.js";
|
|
5
|
-
import { extractCollectionClassname } from "../generate-collections.js";
|
|
6
|
-
import extract_fields_from_collection from "../utils/extract-fields-from-collection.js";
|
|
7
|
-
import { DefaultListFilters } from "../page/default-list-filters.js";
|
|
8
3
|
import { formatWithPrettier } from "../utils/prettier.js";
|
|
9
|
-
import {
|
|
10
|
-
const target_locreq = _locreq(process.cwd());
|
|
4
|
+
import { collectionListTemplate } from "./shared/collection-list.js";
|
|
11
5
|
const listTemplate = async (action_name, newfilefullpath) => {
|
|
12
6
|
const response = await prompts({
|
|
13
7
|
type: "autocomplete",
|
|
@@ -19,166 +13,11 @@ const listTemplate = async (action_name, newfilefullpath) => {
|
|
|
19
13
|
}))
|
|
20
14
|
});
|
|
21
15
|
const collection_name = response.collection;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
),
|
|
28
|
-
extract_fields_from_collection(collection_name)
|
|
29
|
-
]);
|
|
30
|
-
const importPath = curryImportPath(newfilefullpath);
|
|
31
|
-
const result = `import type { Context } from "koa";
|
|
32
|
-
import type { CollectionItem, ItemListResult } from "sealious";
|
|
33
|
-
import type { FlatTemplatable } from "tempstream";
|
|
34
|
-
import { TempstreamJSX } from "tempstream";
|
|
35
|
-
import { ${uppercase_collection} } from "${importPath(
|
|
36
|
-
target_locreq.resolve("src/back/collections/collections.ts")
|
|
37
|
-
)}";
|
|
38
|
-
import html from "${importPath(target_locreq.resolve("src/back/html.ts"))}";
|
|
39
|
-
import type { ListFilterRender } from "@sealcode/sealgen";
|
|
40
|
-
import {
|
|
41
|
-
SealiousItemListPage,
|
|
42
|
-
BaseListPageFields,
|
|
43
|
-
DefaultListFilters,
|
|
44
|
-
} from "@sealcode/sealgen";
|
|
45
|
-
import qs from "qs";
|
|
46
|
-
import type { FilePointer } from "@sealcode/file-manager";
|
|
47
|
-
import { imageRouter } from "${importPath(
|
|
48
|
-
target_locreq.resolve("src/back/image-router.ts")
|
|
49
|
-
)}";
|
|
50
|
-
|
|
51
|
-
export const actionName = "${action_name}";
|
|
52
|
-
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
54
|
-
const filterFields = [
|
|
55
|
-
${collection_fields.map(
|
|
56
|
-
(field) => ` { field: "${field.name}", ...${DefaultListFilters[field.type] ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback`} }`
|
|
57
|
-
).join(",\n")}
|
|
58
|
-
] as {
|
|
59
|
-
field: keyof (typeof ${uppercase_collection})["fields"];
|
|
60
|
-
render?: ListFilterRender;
|
|
61
|
-
prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
|
|
62
|
-
}[];
|
|
63
|
-
|
|
64
|
-
const displayFields = [
|
|
65
|
-
${collection_fields.map((field) => {
|
|
66
|
-
if (field.type == "boolean") {
|
|
67
|
-
return ` {
|
|
68
|
-
field: "${field.name}",
|
|
69
|
-
label: "${field.name}",
|
|
70
|
-
format: (v: boolean) => (v ? "YES" : "NO"),
|
|
71
|
-
}`;
|
|
72
|
-
}
|
|
73
|
-
if (field.type == "datetime") {
|
|
74
|
-
return ` {
|
|
75
|
-
field: "${field.name}",
|
|
76
|
-
label: "${field.name}",
|
|
77
|
-
format: (v: number) => {
|
|
78
|
-
const d = new Date();
|
|
79
|
-
d.setTime(v);
|
|
80
|
-
return d.toString().split(" ").slice(1, 5).join(" ");
|
|
81
|
-
},
|
|
82
|
-
}`;
|
|
83
|
-
}
|
|
84
|
-
if (field.type == "image") {
|
|
85
|
-
return `{
|
|
86
|
-
field: "${field.name}",
|
|
87
|
-
label: "${field.name}",
|
|
88
|
-
format: async (value: FilePointer) => {
|
|
89
|
-
return imageRouter.image(await value.getPath(), {
|
|
90
|
-
container: { width: 45, height: 45 },
|
|
91
|
-
alt: "",
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
}`;
|
|
95
|
-
}
|
|
96
|
-
return ` { field: "${field.name}", label: "${field.name}" }`;
|
|
97
|
-
}).join(",\n")}
|
|
98
|
-
] as {
|
|
99
|
-
field: string;
|
|
100
|
-
label: string;
|
|
101
|
-
format?: (value: unknown, item: CollectionItem<typeof Opinions>) => FlatTemplatable;
|
|
102
|
-
}[];
|
|
103
|
-
|
|
104
|
-
export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
105
|
-
typeof ${uppercase_collection},
|
|
106
|
-
typeof BaseListPageFields
|
|
107
|
-
> {
|
|
108
|
-
fields = BaseListPageFields;
|
|
109
|
-
|
|
110
|
-
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
|
111
|
-
const query_params = qs.parse(ctx.search.slice(1));
|
|
112
|
-
query_params.page = "1";
|
|
113
|
-
const filter_values = await super.getFilterValues(ctx);
|
|
114
|
-
return <form>
|
|
115
|
-
{Object.entries(query_params).map(([key, value]) => {
|
|
116
|
-
if (key == "filter") {
|
|
117
|
-
return "";
|
|
118
|
-
}
|
|
119
|
-
// this is necessary to not lose any query params when the user changes the filter values
|
|
120
|
-
return <input type="hidden" name={key} value={value}/>;
|
|
121
|
-
})}
|
|
122
|
-
{filterFields.map(({ field, render }) => {
|
|
123
|
-
if (!render) {
|
|
124
|
-
render = DefaultListFilters.fallback;
|
|
125
|
-
}
|
|
126
|
-
return (
|
|
127
|
-
render(filter_values[field] || "", this.collection.fields[field]) ||
|
|
128
|
-
""
|
|
129
|
-
);
|
|
130
|
-
})}
|
|
131
|
-
<input type="submit" />
|
|
132
|
-
</form>;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async getFilterValues(ctx: Context) {
|
|
136
|
-
// adding opportunity to adjust the values for a given field filter before it's sent to Sealious
|
|
137
|
-
const values = await super.getFilterValues(ctx);
|
|
138
|
-
for (const filterField of filterFields) {
|
|
139
|
-
const key = filterField.field as keyof typeof values;
|
|
140
|
-
if (key in values) {
|
|
141
|
-
const prepare_fn = filterField.prepareValue;
|
|
142
|
-
if (prepare_fn) {
|
|
143
|
-
values[key] = prepare_fn(values[key]) as any;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return values;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async renderItem(ctx: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
|
|
151
|
-
return <tr>
|
|
152
|
-
{displayFields.map(({ field, format }) => {
|
|
153
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
154
|
-
const value = item.get(field as any);
|
|
155
|
-
return <td>{format ? format(value, item) : value}</td>;
|
|
156
|
-
})}
|
|
157
|
-
</tr>;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
|
|
161
|
-
return (
|
|
162
|
-
<table class="sealious-list ${toKebabCase(action_name)}-table">
|
|
163
|
-
{this.renderTableHead(ctx, displayFields)}
|
|
164
|
-
<tbody>{content}</tbody>
|
|
165
|
-
</table>
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async render(ctx: Context) {
|
|
170
|
-
return html(
|
|
171
|
-
ctx,
|
|
172
|
-
"${action_name}",
|
|
173
|
-
<div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
|
|
174
|
-
<h2>${action_name} List</h2>
|
|
175
|
-
{super.render(ctx)}
|
|
176
|
-
</div>
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
})(${uppercase_collection});
|
|
181
|
-
`;
|
|
16
|
+
const result = await collectionListTemplate(
|
|
17
|
+
collection_name,
|
|
18
|
+
action_name,
|
|
19
|
+
newfilefullpath
|
|
20
|
+
);
|
|
182
21
|
return formatWithPrettier(result);
|
|
183
22
|
};
|
|
184
23
|
export {
|
|
@@ -1,84 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
2
2
|
import { formTemplate } from "../form.js";
|
|
3
3
|
import { curryImportPath } from "../../utils/import-path.js";
|
|
4
4
|
import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
|
|
5
|
-
|
|
6
|
-
name,
|
|
7
|
-
type,
|
|
8
|
-
is_required,
|
|
9
|
-
is_readonly
|
|
10
|
-
}) {
|
|
11
|
-
if (type == "deep-reverse-single-reference" || type == "jdd" || is_readonly) {
|
|
12
|
-
return "";
|
|
13
|
-
}
|
|
14
|
-
let fallback_value = `""`;
|
|
15
|
-
let main_value = `data.${name}`;
|
|
16
|
-
if (type == "image" || type == "file") {
|
|
17
|
-
fallback_value = `null`;
|
|
18
|
-
if (is_required) {
|
|
19
|
-
main_value = toCamelCase("prepared-" + name);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (type == "boolean") {
|
|
23
|
-
main_value = `!!data.${name}`;
|
|
24
|
-
fallback_value = "false";
|
|
25
|
-
}
|
|
26
|
-
if (type == "date") {
|
|
27
|
-
fallback_value == `undefined`;
|
|
28
|
-
}
|
|
29
|
-
if (is_required) {
|
|
30
|
-
return `${name}: ${main_value}`;
|
|
31
|
-
} else {
|
|
32
|
-
return `${name}: ${main_value} || ${fallback_value}`;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function renderFieldPreCreateAction({ name, type }) {
|
|
36
|
-
if (type == "image" || type == "file") {
|
|
37
|
-
const tempname = toCamelCase("prepared-" + name);
|
|
38
|
-
return `const ${tempname} = data.${name}.new || data.${name}.old;
|
|
39
|
-
if(!${tempname}){
|
|
40
|
-
throw new Error("Missing field: ${name}")
|
|
41
|
-
}`;
|
|
42
|
-
}
|
|
43
|
-
return "";
|
|
44
|
-
}
|
|
45
|
-
function renderFieldPostCreateAction({
|
|
46
|
-
name,
|
|
47
|
-
type,
|
|
48
|
-
referencing_collection,
|
|
49
|
-
referencing_field,
|
|
50
|
-
intermediary_field_that_points_there
|
|
51
|
-
}) {
|
|
52
|
-
if (type == "deep-reverse-single-reference" && referencing_collection && referencing_field && intermediary_field_that_points_there) {
|
|
53
|
-
return ` await (async function(){
|
|
54
|
-
// We iterate over all selected values and insert appropriate entries for the ${name} relation
|
|
55
|
-
const ids = Object.entries(
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
57
|
-
data.${name} as unknown as Record<string, string>
|
|
58
|
-
)
|
|
59
|
-
.filter(([, value]) => value === "on")
|
|
60
|
-
.map(([key]) => key);
|
|
61
|
-
|
|
62
|
-
const promises = ids.map(async (id) => {
|
|
63
|
-
await ctx.$app.collections["${referencing_collection}"].create(
|
|
64
|
-
new ctx.$app.SuperContext(),
|
|
65
|
-
{
|
|
66
|
-
"${referencing_field}": created.id,
|
|
67
|
-
"${intermediary_field_that_points_there}": id,
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
await Promise.all(promises);
|
|
73
|
-
})()`;
|
|
74
|
-
}
|
|
75
|
-
return "";
|
|
76
|
-
}
|
|
5
|
+
import { getFieldHandler } from "./shared-crud-form-fields.js";
|
|
77
6
|
async function createItemFormTemplate(create_action_name, newfilefullpath, collection_name, list_action) {
|
|
78
7
|
const collection_fields = await extract_fields_from_collection(
|
|
79
8
|
collection_name
|
|
80
9
|
);
|
|
81
10
|
const importPath = curryImportPath(newfilefullpath);
|
|
11
|
+
const field_handler_results = await Promise.all(
|
|
12
|
+
collection_fields.map(
|
|
13
|
+
(field) => getFieldHandler(collection_name, field, importPath)
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
const post_create_actions = field_handler_results.map(({ post_create }) => post_create).filter((e) => e != "");
|
|
82
17
|
return formTemplate(create_action_name, newfilefullpath, {
|
|
83
18
|
postimport: `import {${toPascalCase(
|
|
84
19
|
collection_name
|
|
@@ -105,6 +40,13 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
|
|
|
105
40
|
const response = await policy.check(ctx.$context);
|
|
106
41
|
return { canAccess: response?.allowed || false, message: response?.reason || "" };
|
|
107
42
|
};`,
|
|
43
|
+
get_initial_values: field_handler_results.filter((r) => !r.hide_initial_value_create).map(({ initial_value_create }) => initial_value_create).filter((e) => e != "").length ? `getInitialValues(ctx: Context){
|
|
44
|
+
return {
|
|
45
|
+
${field_handler_results.filter((r) => !r.hide_initial_value_create).map(
|
|
46
|
+
({ initial_value_create }) => initial_value_create
|
|
47
|
+
).join(",\n")}
|
|
48
|
+
}
|
|
49
|
+
}` : ``,
|
|
108
50
|
onsubmit: `
|
|
109
51
|
async onSubmit(ctx: Context) {
|
|
110
52
|
const data = await this.getParsedValues(ctx);
|
|
@@ -112,23 +54,22 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
|
|
|
112
54
|
throw new Error("Error when parsing the form values");
|
|
113
55
|
}
|
|
114
56
|
|
|
115
|
-
${
|
|
57
|
+
${field_handler_results.map(({ pre_create }) => pre_create).filter((e) => e !== "").join("\n")}
|
|
116
58
|
|
|
117
|
-
const created = await ctx.$app.collections["${toKebabCase(
|
|
118
|
-
collection_name
|
|
119
|
-
)}"].create(
|
|
59
|
+
${post_create_actions.length ? `const created = ` : ""} await ctx.$app.collections["${toKebabCase(collection_name)}"].create(
|
|
120
60
|
ctx.$context,
|
|
121
61
|
{
|
|
122
|
-
|
|
123
|
-
}
|
|
62
|
+
${field_handler_results.filter((result) => !result.hide_sealious_value).map((result) => result.sealious_value(result)).filter((e) => e != "").join(",\n")}
|
|
63
|
+
}
|
|
124
64
|
);
|
|
125
|
-
${
|
|
65
|
+
${post_create_actions.length ? `/* ==== POST CREATE ACTIONS ==== */` : ""}
|
|
66
|
+
${post_create_actions.join("\n")}
|
|
126
67
|
}
|
|
127
68
|
`,
|
|
128
69
|
render: ` async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
|
129
70
|
return html(
|
|
130
71
|
ctx,
|
|
131
|
-
"
|
|
72
|
+
"Create ${toKebabCase(collection_name)}",
|
|
132
73
|
tempstream/* HTML */ \` <div class="add-opinion-form">
|
|
133
74
|
<a class="" href="\${${list_action}URL}"
|
|
134
75
|
>\u2190 Back to ${collection_name} list</a
|
|
@@ -140,7 +81,6 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
|
|
|
140
81
|
});
|
|
141
82
|
}
|
|
142
83
|
export {
|
|
143
|
-
createItemFormTemplate
|
|
144
|
-
renderFieldForSealious
|
|
84
|
+
createItemFormTemplate
|
|
145
85
|
};
|
|
146
86
|
//# sourceMappingURL=collection-create-form.js.map
|