@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,109 +1,37 @@
|
|
|
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
|
-
import {
|
|
6
|
-
function renderFieldForInitialValues({
|
|
7
|
-
name,
|
|
8
|
-
type,
|
|
9
|
-
is_required,
|
|
10
|
-
is_readonly
|
|
11
|
-
}) {
|
|
12
|
-
if (type == "deep-reverse-single-reference") {
|
|
13
|
-
return `${name}: Object.fromEntries((item.get("${name}") || []).map(id=>[id, "on"]))`;
|
|
14
|
-
}
|
|
15
|
-
if (type == "jdd" || is_readonly) {
|
|
16
|
-
return "";
|
|
17
|
-
}
|
|
18
|
-
if (type == "image" || type == "file") {
|
|
19
|
-
return `${name}: {old: item.get("${name}")}`;
|
|
20
|
-
}
|
|
21
|
-
let fallback_value = `""`;
|
|
22
|
-
let main_value = `item.get("${name}")`;
|
|
23
|
-
if (type == "boolean") {
|
|
24
|
-
fallback_value = `"false"`;
|
|
25
|
-
main_value = `String(item.get("${name}"))`;
|
|
26
|
-
}
|
|
27
|
-
if (is_required) {
|
|
28
|
-
return `${name}: ${main_value}`;
|
|
29
|
-
} else {
|
|
30
|
-
return `${name}: ${main_value} || ${fallback_value}`;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function renderFieldPreEditAction({ name, type }) {
|
|
34
|
-
if (type == "image" || type == "file") {
|
|
35
|
-
const tempname = toCamelCase("prepared-" + name);
|
|
36
|
-
return `const ${tempname} = data.${name}.new || data.${name}.old;
|
|
37
|
-
if(!${tempname}){
|
|
38
|
-
throw new Error("Missing field: ${name}")
|
|
39
|
-
}`;
|
|
40
|
-
}
|
|
41
|
-
return "";
|
|
42
|
-
}
|
|
43
|
-
function renderFieldPostEditAction({
|
|
44
|
-
name,
|
|
45
|
-
type,
|
|
46
|
-
referencing_collection,
|
|
47
|
-
referencing_field,
|
|
48
|
-
intermediary_field_that_points_there
|
|
49
|
-
}) {
|
|
50
|
-
if (type == "deep-reverse-single-reference" && referencing_collection && referencing_field && intermediary_field_that_points_there) {
|
|
51
|
-
return `{
|
|
52
|
-
const item_id = await this.getID(ctx);
|
|
53
|
-
// First, we remove links refering to current item
|
|
54
|
-
const {items:to_delete} = (await ctx.$app.collections["${referencing_collection}"]
|
|
55
|
-
.list(ctx.$context)
|
|
56
|
-
.filter({"${referencing_field}": item_id})
|
|
57
|
-
.fetch()
|
|
58
|
-
)
|
|
59
|
-
await Promise.all(
|
|
60
|
-
to_delete.map((item) => item.remove(ctx.$context))
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// Then, we iterate over all selected values and insert appropriate entries for the ${name} relation
|
|
64
|
-
const ids = Object.entries(
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
66
|
-
data.${name} as unknown as Record<string, string>
|
|
67
|
-
)
|
|
68
|
-
.filter(([, value]) => value === "on")
|
|
69
|
-
.map(([key]) => key);
|
|
70
|
-
|
|
71
|
-
const promises = ids.map(async (id) => {
|
|
72
|
-
await ctx.$app.collections["${referencing_collection}"].create(
|
|
73
|
-
new ctx.$app.SuperContext(),
|
|
74
|
-
{
|
|
75
|
-
"${referencing_field}": item_id,
|
|
76
|
-
"${intermediary_field_that_points_there}": id,
|
|
77
|
-
}
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
await Promise.all(promises);
|
|
82
|
-
}`;
|
|
83
|
-
}
|
|
84
|
-
return "";
|
|
85
|
-
}
|
|
5
|
+
import { getFieldHandler } from "./shared-crud-form-fields.js";
|
|
86
6
|
async function editItemFormTemplate(edit_action_name, newfilefullpath, collection_name, list_action) {
|
|
87
7
|
const collection_fields = await extract_fields_from_collection(
|
|
88
8
|
collection_name
|
|
89
9
|
);
|
|
90
10
|
const importPath = curryImportPath(newfilefullpath);
|
|
11
|
+
const field_handler_results = await Promise.all(
|
|
12
|
+
collection_fields.map(async (field) => {
|
|
13
|
+
return getFieldHandler(collection_name, field, importPath);
|
|
14
|
+
})
|
|
15
|
+
);
|
|
91
16
|
return formTemplate(edit_action_name, newfilefullpath, {
|
|
92
17
|
postimport: `import {${toPascalCase(
|
|
93
18
|
collection_name
|
|
94
19
|
)}FormFields, ${toPascalCase(
|
|
95
20
|
collection_name
|
|
96
21
|
)}FormControls }from "../shared.js";
|
|
97
|
-
|
|
98
|
-
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
22
|
+
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
99
23
|
"src/back/collections/collections.js"
|
|
100
24
|
)}";
|
|
25
|
+
import { ${list_action}URL, ${edit_action_name}URL } from "${importPath(
|
|
26
|
+
"src/back/routes/urls.js"
|
|
27
|
+
)}";
|
|
28
|
+
import {tempstream} from "tempstream";
|
|
101
29
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
30
|
+
${Array.from(
|
|
31
|
+
new Set(field_handler_results.map(({ imports }) => imports.edit))
|
|
32
|
+
).join("\n")}
|
|
105
33
|
|
|
106
|
-
import {
|
|
34
|
+
${collection_fields.filter((e) => !e.is_required).length ? `import {withFallback} from "@sealcode/sealgen";` : ""}
|
|
107
35
|
`,
|
|
108
36
|
fields: `...${toPascalCase(collection_name)}FormFields`,
|
|
109
37
|
controls: `
|
|
@@ -130,18 +58,18 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
130
58
|
throw new Error("Error when parsing the form values");
|
|
131
59
|
}
|
|
132
60
|
|
|
133
|
-
${
|
|
61
|
+
${field_handler_results.map((result) => result.pre_edit).filter((e) => e !== "").join("\n")}
|
|
134
62
|
|
|
135
63
|
item.setMultiple(
|
|
136
64
|
{
|
|
137
|
-
${
|
|
65
|
+
${field_handler_results.filter((r) => !r.hide_sealious_value).map((result) => result.sealious_value(result)).filter((e) => e != "").join(",\n")}
|
|
138
66
|
}
|
|
139
67
|
);
|
|
140
68
|
await item.save(ctx.$context);
|
|
141
69
|
|
|
142
70
|
|
|
143
|
-
|
|
144
|
-
${
|
|
71
|
+
${field_handler_results.map((result) => result.post_edit).filter((e) => e != "").length ? `// #### POST-EDIT ACTIONS ############` : ""}
|
|
72
|
+
${field_handler_results.map((result) => result.post_edit).filter((e) => e != "").join("\n")}
|
|
145
73
|
}
|
|
146
74
|
`,
|
|
147
75
|
other_methods: `
|
|
@@ -150,9 +78,9 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
150
78
|
// of the resource to edit.
|
|
151
79
|
// If sealgen got it wrong, just return the param name that should be used here instead
|
|
152
80
|
const param_name =
|
|
153
|
-
${
|
|
81
|
+
${edit_action_name}URL.params
|
|
154
82
|
.find(param => param.toLowerCase().includes("id"))
|
|
155
|
-
|| ${
|
|
83
|
+
|| ${edit_action_name}URL.params[0]
|
|
156
84
|
const id = ctx.params[param_name];
|
|
157
85
|
if (!id) {
|
|
158
86
|
throw new Error("Missing URL parameter: " + param_name);
|
|
@@ -176,14 +104,43 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
|
|
|
176
104
|
}
|
|
177
105
|
|
|
178
106
|
return {
|
|
179
|
-
${
|
|
107
|
+
${field_handler_results.filter((r) => !r.hide_initial_value_edit).map(({ initial_value_edit, is_required, name }) => {
|
|
108
|
+
if (typeof initial_value_edit == "string") {
|
|
109
|
+
if (initial_value_edit == "") {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
return `${name}: ${initial_value_edit}`;
|
|
113
|
+
}
|
|
114
|
+
const main_value = initial_value_edit.main_value;
|
|
115
|
+
if (!main_value) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Missing main_value for initial_value_edit"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const { parsed_value, fallback_value } = initial_value_edit;
|
|
121
|
+
if (is_required) {
|
|
122
|
+
return `${name}: ${parsed_value || main_value}`;
|
|
123
|
+
}
|
|
124
|
+
if (main_value && parsed_value && fallback_value) {
|
|
125
|
+
if (main_value == parsed_value) {
|
|
126
|
+
return `${name}: withFallback(${main_value}, ${fallback_value})`;
|
|
127
|
+
} else {
|
|
128
|
+
return `${name}: withFallback(${main_value}, ${parsed_value}, ${fallback_value})`;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
console.error(initial_value_edit);
|
|
132
|
+
throw new Error(
|
|
133
|
+
"Missing field info for generating initial value"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}).filter((e) => e.trim() !== "").join(",\n")}
|
|
180
137
|
};
|
|
181
138
|
}
|
|
182
139
|
`,
|
|
183
140
|
render: ` async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
|
184
141
|
return html(
|
|
185
142
|
ctx,
|
|
186
|
-
"
|
|
143
|
+
"Edit ${toPascalCase(collection_name)}",
|
|
187
144
|
tempstream/* HTML */ \` <div class="add-opinion-form">
|
|
188
145
|
<a class="" href="\${${list_action}URL}"
|
|
189
146
|
>\u2190 Back to ${collection_name} list</a
|
|
@@ -6,6 +6,15 @@ import { DefaultListFilters } from "../../page/default-list-filters.js";
|
|
|
6
6
|
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
7
7
|
import { formatWithPrettier } from "../../utils/prettier.js";
|
|
8
8
|
const target_locreq = _locreq(process.cwd());
|
|
9
|
+
function fieldImport({ type }, importPath) {
|
|
10
|
+
if (type == "image") {
|
|
11
|
+
return `
|
|
12
|
+
import type { FilePointer } from "@sealcode/file-manager";
|
|
13
|
+
import { imageRouter } from "${importPath("src/back/image-router.js")}";
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
9
18
|
function makeDefaultFilterFields(collection_fields) {
|
|
10
19
|
return collection_fields.map(
|
|
11
20
|
(field) => ` { field: "${field.name}", ...${DefaultListFilters[field.type] ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback`} }`
|
|
@@ -51,7 +60,7 @@ const default_hooks = {
|
|
|
51
60
|
pre_header_html: "",
|
|
52
61
|
post_header_html: "",
|
|
53
62
|
post_import_js: "",
|
|
54
|
-
render_item: (collection_name) => ` async renderItem(
|
|
63
|
+
render_item: (collection_name) => ` async renderItem(_ctx: Context, item: CollectionItem<typeof ${toPascalCase(
|
|
55
64
|
collection_name
|
|
56
65
|
)}>) {
|
|
57
66
|
return <tr>
|
|
@@ -74,8 +83,13 @@ async function collectionListTemplate(collection_name, action_name, newfilefullp
|
|
|
74
83
|
extract_fields_from_collection(collection_name)
|
|
75
84
|
]);
|
|
76
85
|
const importPath = curryImportPath(newfilefullpath);
|
|
86
|
+
const field_import_string = Array.from(
|
|
87
|
+
new Set(
|
|
88
|
+
collection_fields.map((field) => fieldImport(field, importPath))
|
|
89
|
+
)
|
|
90
|
+
).join("\n");
|
|
77
91
|
const result = `import type { Context } from "koa";
|
|
78
|
-
import type { CollectionItem
|
|
92
|
+
import type { CollectionItem } from "sealious";
|
|
79
93
|
import type { FlatTemplatable, Templatable } from "tempstream";
|
|
80
94
|
import { TempstreamJSX, tempstream } from "tempstream";
|
|
81
95
|
import { ${uppercase_collection} } from "${importPath(
|
|
@@ -89,12 +103,9 @@ import {
|
|
|
89
103
|
DefaultListFilters,
|
|
90
104
|
} from "@sealcode/sealgen";
|
|
91
105
|
import qs from "qs";
|
|
92
|
-
import type { FilePointer } from "@sealcode/file-manager";
|
|
93
|
-
import { imageRouter } from "${importPath(
|
|
94
|
-
target_locreq.resolve("src/back/image-router.ts")
|
|
95
|
-
)}";
|
|
96
106
|
|
|
97
107
|
${hooks.post_import_js}
|
|
108
|
+
${field_import_string}
|
|
98
109
|
|
|
99
110
|
export const actionName = "${action_name}";
|
|
100
111
|
|
|
@@ -107,6 +118,8 @@ const filterFields = [
|
|
|
107
118
|
prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
|
|
108
119
|
}[];
|
|
109
120
|
|
|
121
|
+
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
110
123
|
const displayFields = [
|
|
111
124
|
${makeDefaultDisplayFields(collection_fields)}
|
|
112
125
|
] as {
|
|
@@ -152,10 +165,12 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
152
165
|
// adding opportunity to adjust the values for a given field filter before it's sent to Sealious
|
|
153
166
|
const values = await super.getFilterValues(ctx);
|
|
154
167
|
for (const filterField of filterFields) {
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
155
169
|
const key = filterField.field as keyof typeof values;
|
|
156
170
|
if (key in values) {
|
|
157
171
|
const prepare_fn = filterField.prepareValue;
|
|
158
172
|
if (prepare_fn) {
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
|
159
174
|
values[key] = prepare_fn(values[key]) as any;
|
|
160
175
|
}
|
|
161
176
|
}
|
|
@@ -11,22 +11,24 @@ import { ${delete_action_name}URL } from "${importPath(
|
|
|
11
11
|
"src/back/routes/urls.js"
|
|
12
12
|
)}";
|
|
13
13
|
import html from "${importPath("src/back/html.ts")}";
|
|
14
|
-
import { ${toPascalCase(collection_name)}
|
|
15
|
-
|
|
16
|
-
)}";
|
|
14
|
+
import { ${toPascalCase(collection_name)} as ${toPascalCase(
|
|
15
|
+
collection_name
|
|
16
|
+
)}Collection } from "${importPath("src/back/collections/collections.js")}";
|
|
17
17
|
|
|
18
18
|
export const actionName = "${ask_delete_action_name}";
|
|
19
19
|
|
|
20
20
|
export default new (class ${ask_delete_action_name}Page extends Page {
|
|
21
21
|
canAccess = async (ctx: Context) => {
|
|
22
|
-
const policy = ${toPascalCase(
|
|
22
|
+
const policy = ${toPascalCase(
|
|
23
|
+
collection_name
|
|
24
|
+
)}Collection.getPolicy("edit");
|
|
23
25
|
const response = await policy.check(ctx.$context);
|
|
24
26
|
return { canAccess: response?.allowed || false, message: response?.reason || "" };
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
getItemTitle(_ctx: Context, item: CollectionItem<typeof ${toPascalCase(
|
|
28
30
|
collection_name
|
|
29
|
-
)}>){
|
|
31
|
+
)}Collection>){
|
|
30
32
|
return item.get("name")
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const boolean_handler = async (_, { name }, ___, { is_required_expr, fields_var }) => ({
|
|
2
|
+
field: `${name}: new Fields.Boolean(${is_required_expr})`,
|
|
3
|
+
controls: `new Controls.Checkbox(${fields_var}.${name}, { label: "${name}" })`,
|
|
4
|
+
initial_value_edit: {
|
|
5
|
+
fallback_value: `"false"`,
|
|
6
|
+
parsed_value: `String(item.get("${name}"))`
|
|
7
|
+
},
|
|
8
|
+
main_value_sealious: `!!data.${name}`,
|
|
9
|
+
fallback_value_sealious: "false"
|
|
10
|
+
});
|
|
11
|
+
export {
|
|
12
|
+
boolean_handler
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=boolean.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const date_handler = async (_, { name }, ___, { fields_var }) => ({
|
|
2
|
+
controls: `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}", type: "date"})`,
|
|
3
|
+
fallback_value_sealious: `undefined`
|
|
4
|
+
});
|
|
5
|
+
export {
|
|
6
|
+
date_handler
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=date.js.map
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
2
|
+
import extract_fields_from_collection from "../../../utils/extract-fields-from-collection.js";
|
|
3
|
+
const deep_reverse_single_reference_handler = async (collection_name, {
|
|
4
|
+
name,
|
|
5
|
+
target,
|
|
6
|
+
referencing_collection,
|
|
7
|
+
intermediary_field_that_points_there,
|
|
8
|
+
referencing_field
|
|
9
|
+
}, ___, { is_required_expr, fields_var }) => {
|
|
10
|
+
if (!referencing_collection || !target || !intermediary_field_that_points_there || !referencing_field) {
|
|
11
|
+
console.warn(
|
|
12
|
+
"missing referencing_collection, target, intermediary_field_that_points_there, or referencing_field member in extracted field info"
|
|
13
|
+
);
|
|
14
|
+
return { field: "" };
|
|
15
|
+
}
|
|
16
|
+
const referencing_fields = await extract_fields_from_collection(
|
|
17
|
+
referencing_collection
|
|
18
|
+
);
|
|
19
|
+
if (referencing_fields.length == 2) {
|
|
20
|
+
const post_create = (get_id) => `{
|
|
21
|
+
const item_id = ${get_id};
|
|
22
|
+
// First, we remove links refering to current item
|
|
23
|
+
const {items: existing_links } = (await ctx.$app.collections["${referencing_collection}"]
|
|
24
|
+
.list(ctx.$context)
|
|
25
|
+
.filter({"${referencing_field}": item_id})
|
|
26
|
+
.fetch()
|
|
27
|
+
)
|
|
28
|
+
const should_exist_ids = Object.entries(
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
30
|
+
(data["${name}"] as unknown as Record<string, string>) || {}
|
|
31
|
+
)
|
|
32
|
+
.filter(([, value]) => value === "on")
|
|
33
|
+
.map(([key]) => key);
|
|
34
|
+
const to_delete = existing_links.filter(
|
|
35
|
+
(link) => !should_exist_ids.includes(link.get("${intermediary_field_that_points_there}"))
|
|
36
|
+
);
|
|
37
|
+
const to_create_ids = should_exist_ids.filter(
|
|
38
|
+
(id) => !existing_links.map(({ id }) => id).includes(id)
|
|
39
|
+
);
|
|
40
|
+
await Promise.all(to_delete.map((item) => item.remove(ctx.$context)));
|
|
41
|
+
// Then, we iterate over all selected values and insert appropriate missing links
|
|
42
|
+
const promises = to_create_ids.map(async (id) => {
|
|
43
|
+
await ctx.$app.collections["${toKebabCase(referencing_collection)}"].create(
|
|
44
|
+
ctx.$context,
|
|
45
|
+
{
|
|
46
|
+
"${referencing_field}": item_id,
|
|
47
|
+
"${intermediary_field_that_points_there}": id,
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
await Promise.all(promises);
|
|
52
|
+
}`;
|
|
53
|
+
return {
|
|
54
|
+
field: `${name}: new Fields.CheckboxedListField(${is_required_expr}, async (ctx) => {
|
|
55
|
+
const { items } = await ctx.$app.collections["${target}"]
|
|
56
|
+
.list(ctx.$context)
|
|
57
|
+
.fetch();
|
|
58
|
+
|
|
59
|
+
return items.map((s) => ({
|
|
60
|
+
label: s.get("name"),
|
|
61
|
+
value: s.id,
|
|
62
|
+
}));
|
|
63
|
+
})`,
|
|
64
|
+
controls: `new Controls.CheckboxedListInput(${fields_var}.${name}, {
|
|
65
|
+
label: "${name}",
|
|
66
|
+
})`,
|
|
67
|
+
post_edit: post_create(`await this.getID(ctx)`),
|
|
68
|
+
post_create: post_create(`created.id`),
|
|
69
|
+
initial_value_edit: `Object.fromEntries((item.get("${name}") || []).map(id=>[id, "on"]))`,
|
|
70
|
+
sealious_value: () => ""
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
const initial_value_function_name = `get${toPascalCase(
|
|
74
|
+
name
|
|
75
|
+
)}TableInitialValues`;
|
|
76
|
+
const post_create = (get_id) => `{
|
|
77
|
+
const item_id = ${get_id};
|
|
78
|
+
// First, we remove links refering to current item
|
|
79
|
+
const { items: to_delete } = await ctx.$app.collections[
|
|
80
|
+
"${toKebabCase(referencing_collection)}"
|
|
81
|
+
]
|
|
82
|
+
.list(ctx.$context)
|
|
83
|
+
.filter({ "${referencing_field}": item_id })
|
|
84
|
+
.fetch();
|
|
85
|
+
await Promise.all(to_delete.map((item) => item.remove(ctx.$context)));
|
|
86
|
+
|
|
87
|
+
// Then, we iterate over all selected values and insert appropriate entries for the subcategories relation
|
|
88
|
+
|
|
89
|
+
const promises = data["${name}"]
|
|
90
|
+
.filter((s) => s.selected == true)
|
|
91
|
+
.map(async (link_info) => {
|
|
92
|
+
await ctx.$app.collections["${toKebabCase(referencing_collection)}"].create(
|
|
93
|
+
ctx.$context,
|
|
94
|
+
{
|
|
95
|
+
"${referencing_field}": item_id,
|
|
96
|
+
"${intermediary_field_that_points_there}": String(link_info["${intermediary_field_that_points_there}"]),
|
|
97
|
+
${referencing_fields.filter(
|
|
98
|
+
({ type }) => type !== "single-reference"
|
|
99
|
+
).map(
|
|
100
|
+
(field) => `"${field.name}": link_info["${field.name}"]`
|
|
101
|
+
).join(",\n")}
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await Promise.all(promises);
|
|
107
|
+
}`;
|
|
108
|
+
const initial_value = `(await ${initial_value_function_name}(ctx,item)) as unknown as FormDataValue`;
|
|
109
|
+
return {
|
|
110
|
+
imports: {
|
|
111
|
+
shared: `import type { CollectionItem } from "sealious"; import type { Context } from "koa";`,
|
|
112
|
+
edit: `import {${initial_value_function_name}} from "../shared.js"; import type { FormDataValue } from "@sealcode/sealgen";`
|
|
113
|
+
},
|
|
114
|
+
field: `
|
|
115
|
+
${name}: new Fields.Table({
|
|
116
|
+
selected: new Fields.Boolean(false),
|
|
117
|
+
"${referencing_field}": new Fields.SimpleFormField(true),
|
|
118
|
+
"${intermediary_field_that_points_there}": new Fields.SimpleFormField(true),
|
|
119
|
+
${referencing_fields.filter(({ type }) => type !== "single-reference").map(
|
|
120
|
+
(field) => `${field.name}: new Fields.SimpleFormField(true)`
|
|
121
|
+
).join(",\n")}
|
|
122
|
+
})`,
|
|
123
|
+
controls: `
|
|
124
|
+
new Controls.Table(${fields_var}.${name}, {
|
|
125
|
+
render_fields: {
|
|
126
|
+
selected: (fctx, name, value) => /* HTML */ \`<input
|
|
127
|
+
type="checkbox"
|
|
128
|
+
name=\${name}
|
|
129
|
+
\${value?.toString() == "true" ? "checked" : ""}
|
|
130
|
+
form="\${fctx.form_id}"
|
|
131
|
+
autocomplete="off"
|
|
132
|
+
/>\`,
|
|
133
|
+
"${referencing_field}": (fctx, name, value) => /* HTML */ \`<input
|
|
134
|
+
type="hidden"
|
|
135
|
+
name=\${name}
|
|
136
|
+
value="\${value}"
|
|
137
|
+
form="\${fctx.form_id}"
|
|
138
|
+
/>\`,
|
|
139
|
+
"${intermediary_field_that_points_there}": (fctx, name, value) => /* HTML */ \`<input
|
|
140
|
+
type="hidden"
|
|
141
|
+
name=\${name}
|
|
142
|
+
value="\${value}"
|
|
143
|
+
form="\${fctx.form_id}"
|
|
144
|
+
/>\`,
|
|
145
|
+
${referencing_fields.filter(
|
|
146
|
+
({ type }) => type !== "single-reference"
|
|
147
|
+
).map(
|
|
148
|
+
(field) => `${field.name}: (fctx, name, value) => /* HTML */ \`<input
|
|
149
|
+
name=\${name}
|
|
150
|
+
value="\${value}"
|
|
151
|
+
form="\${fctx.form_id}"
|
|
152
|
+
autocomplete="off"
|
|
153
|
+
/>\``
|
|
154
|
+
).join(",\n")}
|
|
155
|
+
},
|
|
156
|
+
allow_adding: false,
|
|
157
|
+
})`,
|
|
158
|
+
top_level_shared: `export async function ${initial_value_function_name}(
|
|
159
|
+
ctx: Context,
|
|
160
|
+
item: CollectionItem<typeof ${toPascalCase(
|
|
161
|
+
collection_name
|
|
162
|
+
)}>
|
|
163
|
+
) {
|
|
164
|
+
// convert from an array to array-like object
|
|
165
|
+
return Object.fromEntries(
|
|
166
|
+
await Promise.all([
|
|
167
|
+
ctx.$app.collections["${toKebabCase(target)}"].list(ctx.$context).fetch(),
|
|
168
|
+
ctx.$app.collections["${toKebabCase(
|
|
169
|
+
referencing_collection
|
|
170
|
+
)}"].list(ctx.$context).fetch(),
|
|
171
|
+
]).then(([{ items: targets }, { items: links }]) =>
|
|
172
|
+
targets
|
|
173
|
+
.map((target) => {
|
|
174
|
+
return {
|
|
175
|
+
selected: (item.get("${name}") || []).includes(
|
|
176
|
+
target.id
|
|
177
|
+
)
|
|
178
|
+
? "on"
|
|
179
|
+
: "off",
|
|
180
|
+
"${referencing_field}": item.id,
|
|
181
|
+
"${intermediary_field_that_points_there}": target.id,
|
|
182
|
+
${referencing_fields.filter(
|
|
183
|
+
({ type }) => type !== "single-reference"
|
|
184
|
+
).map(
|
|
185
|
+
(field) => `"${field.name}": links
|
|
186
|
+
.find(
|
|
187
|
+
(e) =>
|
|
188
|
+
e.get("${intermediary_field_that_points_there}") == target.id &&
|
|
189
|
+
e.get("${referencing_field}") == item.id
|
|
190
|
+
)
|
|
191
|
+
?.get("rank") || 0,`
|
|
192
|
+
).join(",\n")}
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.entries()
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
}`,
|
|
199
|
+
initial_value_edit: initial_value,
|
|
200
|
+
initial_value_create: initial_value,
|
|
201
|
+
post_edit: post_create(`await this.getID(ctx)`),
|
|
202
|
+
post_create: post_create(`created.id`)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
export {
|
|
207
|
+
deep_reverse_single_reference_handler
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=deep-reverse-single-reference.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { toPascalCase } from "js-convert-case";
|
|
2
|
+
const default_handler = async (collection_name, { name, is_readonly, is_required }) => {
|
|
3
|
+
const required_s = `${toPascalCase(
|
|
4
|
+
collection_name
|
|
5
|
+
)}.fields.${name}.required`;
|
|
6
|
+
const fields_var = `${toPascalCase(collection_name)}FormFields`;
|
|
7
|
+
const fallback_value_initial = `""`;
|
|
8
|
+
const fallback_value_sealious = `""`;
|
|
9
|
+
const main_value_initial = `item.get("${name}")`;
|
|
10
|
+
const main_value_sealious = `data["${name}"]`;
|
|
11
|
+
const parsed_value = main_value_initial;
|
|
12
|
+
return {
|
|
13
|
+
is_required,
|
|
14
|
+
name,
|
|
15
|
+
post_create: "",
|
|
16
|
+
imports: { shared: "", edit: "" },
|
|
17
|
+
hide_field: is_readonly,
|
|
18
|
+
field: `${name}: new Fields.CollectionField(${required_s}, ${toPascalCase(
|
|
19
|
+
collection_name
|
|
20
|
+
)}.fields.${name})`,
|
|
21
|
+
hide_control: is_readonly,
|
|
22
|
+
controls: `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}"})`,
|
|
23
|
+
is_required_expr: `${toPascalCase(
|
|
24
|
+
collection_name
|
|
25
|
+
)}.fields.${name}.required`,
|
|
26
|
+
fields_var,
|
|
27
|
+
top_level_shared: "",
|
|
28
|
+
hide_initial_value_edit: is_readonly,
|
|
29
|
+
initial_value_edit: {
|
|
30
|
+
fallback_value: fallback_value_initial,
|
|
31
|
+
main_value: main_value_initial,
|
|
32
|
+
parsed_value
|
|
33
|
+
},
|
|
34
|
+
hide_initial_value_create: is_readonly,
|
|
35
|
+
initial_value_create: "",
|
|
36
|
+
pre_edit: "",
|
|
37
|
+
post_edit: "",
|
|
38
|
+
pre_create: "",
|
|
39
|
+
main_value_sealious,
|
|
40
|
+
fallback_value_sealious,
|
|
41
|
+
sealious_value: (result) => {
|
|
42
|
+
return result.is_required ? `${result.name}: ${result.main_value_sealious}` : `${result.name}: ${result.main_value_sealious} != null ? ${result.main_value_sealious} : ${result.fallback_value_sealious}`;
|
|
43
|
+
},
|
|
44
|
+
hide_sealious_value: is_readonly
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
default_handler
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=default.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { toPascalCase } from "js-convert-case";
|
|
2
|
+
const enum_handler = async (collection_name, { name }, _, { fields_var }) => ({
|
|
3
|
+
// this assertion and import is only necessary because required fields
|
|
4
|
+
// in Sealious lose the type information. Once this is fixed, we can remove this import here
|
|
5
|
+
imports: { shared: `import type { FieldTypes } from "sealious";` },
|
|
6
|
+
field: `${name}: new Fields.PickFromListField(
|
|
7
|
+
${toPascalCase(collection_name)}.fields["${name}"].required,
|
|
8
|
+
async (ctx) =>
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
10
|
+
(${toPascalCase(collection_name)}.fields["${name}"])
|
|
11
|
+
.getAllowedValues(ctx.$app)
|
|
12
|
+
.map((value) => ({ value, label: value }))
|
|
13
|
+
)`,
|
|
14
|
+
controls: `new Controls.Dropdown(${fields_var}.${name}, { label: "${name}"})`
|
|
15
|
+
});
|
|
16
|
+
export {
|
|
17
|
+
enum_handler
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=enum.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { toCamelCase } from "js-convert-case";
|
|
2
|
+
const file_handler = async (_, { name, is_required }, __, { is_required_expr }) => {
|
|
3
|
+
const tempname = toCamelCase("prepared-" + name);
|
|
4
|
+
return {
|
|
5
|
+
field: `${name}: new Fields.File(${is_required_expr}, TheFileManager)`,
|
|
6
|
+
initial_value_edit: `${name}: {old: item.get("${name}")}`,
|
|
7
|
+
pre_edit: `const ${tempname} = data.${name}.new || data.${name}.old;
|
|
8
|
+
if(!${tempname}){
|
|
9
|
+
throw new Error("Missing field: ${name}")
|
|
10
|
+
}`,
|
|
11
|
+
fallback_value_sealious: "null",
|
|
12
|
+
...is_required ? { main_value_sealious: tempname } : {}
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
file_handler
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { toCamelCase } from "js-convert-case";
|
|
2
|
+
const image_handler = async (_, { name, is_required }, importPath, { is_required_expr, fields_var }) => {
|
|
3
|
+
const tempname = toCamelCase("prepared-" + name);
|
|
4
|
+
return {
|
|
5
|
+
imports: {
|
|
6
|
+
shared: `import { imageRouter } from "${importPath(
|
|
7
|
+
"src/back/image-router.js"
|
|
8
|
+
)}";
|
|
9
|
+
import { TheFileManager } from "${importPath(
|
|
10
|
+
"src/back/file-manager.js"
|
|
11
|
+
)}";`
|
|
12
|
+
},
|
|
13
|
+
field: `${name}: new Fields.File(${is_required_expr}, TheFileManager)`,
|
|
14
|
+
controls: `new Controls.Photo(${fields_var}.${name}, imageRouter, {
|
|
15
|
+
label: "${name}",
|
|
16
|
+
})`,
|
|
17
|
+
initial_value_edit: `{old: item.get("${name}")}`,
|
|
18
|
+
pre_edit: `const ${tempname} = data.${name}.new || data.${name}.old ` + (is_required ? `; if(!${tempname}){
|
|
19
|
+
throw new Error("Missing field: ${name}")
|
|
20
|
+
}` : " || undefined;"),
|
|
21
|
+
pre_create: `const ${tempname} = data.${name}.new || data.${name}.old;
|
|
22
|
+
if(!${tempname}){
|
|
23
|
+
throw new Error("Missing field: ${name}")
|
|
24
|
+
}`,
|
|
25
|
+
fallback_value_sealious: "null",
|
|
26
|
+
sealious_value: () => `${name}: ${tempname}`
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
image_handler
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=image.js.map
|