@sealcode/sealgen 0.15.33 → 0.15.35

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.
Files changed (33) hide show
  1. package/@types/src/forms/controls/controls.d.ts +1 -0
  2. package/@types/src/forms/controls/table.d.ts +15 -0
  3. package/@types/src/forms/fields/fields.d.ts +1 -0
  4. package/@types/src/forms/fields/table.d.ts +12 -0
  5. package/@types/src/templates/shared/collection-create-form.d.ts +2 -0
  6. package/@types/src/templates/shared/collection-edit-form.d.ts +1 -0
  7. package/@types/src/templates/shared/collection-list.d.ts +1 -0
  8. package/@types/src/templates/shared/confirm-item-delete.d.ts +1 -0
  9. package/@types/src/templates/shared/item-delete.d.ts +1 -0
  10. package/@types/src/templates/shared/shared-crud-form-fields.d.ts +44 -0
  11. package/@types/src/utils/utils.d.ts +1 -0
  12. package/@types/src/utils/with-fallback.d.ts +2 -0
  13. package/lib/add-crud.js +131 -0
  14. package/lib/forms/controls/controls.js +1 -0
  15. package/lib/forms/controls/table.js +62 -0
  16. package/lib/forms/fields/fields.js +1 -0
  17. package/lib/forms/fields/table.js +58 -0
  18. package/lib/templates/shared/collection-create-form.js +146 -0
  19. package/lib/templates/shared/collection-edit-form.js +200 -0
  20. package/lib/templates/shared/collection-list.js +211 -0
  21. package/lib/templates/shared/confirm-item-delete.js +72 -0
  22. package/lib/templates/shared/item-delete.js +41 -0
  23. package/lib/templates/shared/shared-crud-form-fields.js +118 -0
  24. package/lib/utils/utils.js +1 -0
  25. package/lib/utils/with-fallback.js +21 -0
  26. package/lib/utils/write-file.js +11 -0
  27. package/package.json +1 -1
  28. package/src/forms/controls/controls.ts +1 -0
  29. package/src/forms/controls/table.ts +78 -0
  30. package/src/forms/fields/fields.ts +1 -0
  31. package/src/forms/fields/table.ts +72 -0
  32. package/src/utils/utils.ts +1 -0
  33. package/src/utils/with-fallback.ts +47 -0
@@ -0,0 +1,200 @@
1
+ import { toCamelCase, toKebabCase, toPascalCase } from "js-convert-case";
2
+ import { formTemplate } from "../form.js";
3
+ import { curryImportPath } from "../../utils/import-path.js";
4
+ import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
5
+ import { renderFieldForSealious } from "./collection-create-form.js";
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
+ }
86
+ async function editItemFormTemplate(edit_action_name, newfilefullpath, collection_name, list_action) {
87
+ const collection_fields = await extract_fields_from_collection(
88
+ collection_name
89
+ );
90
+ const importPath = curryImportPath(newfilefullpath);
91
+ return formTemplate(edit_action_name, newfilefullpath, {
92
+ postimport: `import {${toPascalCase(
93
+ collection_name
94
+ )}FormFields, ${toPascalCase(
95
+ collection_name
96
+ )}FormControls }from "../shared.js";
97
+
98
+ import { ${toPascalCase(collection_name)} } from "${importPath(
99
+ "src/back/collections/collections.js"
100
+ )}";
101
+
102
+ import { ${list_action}URL, ${toPascalCase(
103
+ edit_action_name
104
+ )}URL } from "${importPath("src/back/routes/urls.js")}";
105
+
106
+ import {tempstream} from "tempstream";
107
+ `,
108
+ fields: `...${toPascalCase(collection_name)}FormFields`,
109
+ controls: `
110
+ new Controls.FormHeader("Edit ${toPascalCase(
111
+ collection_name
112
+ )}"),
113
+ ...${toPascalCase(collection_name)}FormControls`,
114
+ can_access: `canAccess = async (ctx: Context) => {
115
+ const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
116
+ const response = await policy.check(ctx.$context);
117
+ return { canAccess: response?.allowed || false, message: response?.reason || "" };
118
+ };`,
119
+ onsubmit: `
120
+ async onSubmit(ctx: Context) {
121
+ const data = await this.getParsedValues(ctx);
122
+ const id = await this.getID(ctx);
123
+ const {items: [item]} = await ctx.$app.collections["${toKebabCase(
124
+ collection_name
125
+ )}"]
126
+ .list(ctx.$context)
127
+ .ids([id])
128
+ .fetch();
129
+ if (!data) {
130
+ throw new Error("Error when parsing the form values");
131
+ }
132
+
133
+ ${collection_fields.map((field) => renderFieldPreEditAction(field)).filter((e) => e !== "").join("\n")}
134
+
135
+ item.setMultiple(
136
+ {
137
+ ${collection_fields.map((field) => renderFieldForSealious(field)).filter((e) => e != "").join(",\n")}
138
+ }
139
+ );
140
+ await item.save(ctx.$context);
141
+
142
+
143
+ // ## POST-EDIT ACTIONS ############
144
+ ${collection_fields.map((field) => renderFieldPostEditAction(field)).filter((e) => e != "").join("\n")}
145
+ }
146
+ `,
147
+ other_methods: `
148
+ async getID(ctx: Context): Promise<string>{
149
+ // trying to automatically guess which param is the one that represents the ID
150
+ // of the resource to edit.
151
+ // If sealgen got it wrong, just return the param name that should be used here instead
152
+ const param_name =
153
+ ${toPascalCase(edit_action_name)}URL.params
154
+ .find(param => param.toLowerCase().includes("id"))
155
+ || ${toPascalCase(edit_action_name)}URL.params[0]
156
+ const id = ctx.params[param_name];
157
+ if (!id) {
158
+ throw new Error("Missing URL parameter: " + param_name);
159
+ }
160
+ return id
161
+ }
162
+ `,
163
+ get_initial_values: `async getInitialValues(ctx: Context) {
164
+ const id = await this.getID(ctx);
165
+
166
+ const {
167
+ items: [item],
168
+ } = await ctx.$app.collections["${toKebabCase(collection_name)}"]
169
+ .list(ctx.$context)
170
+ .ids([id])
171
+ .attach({})
172
+ .fetch();
173
+
174
+ if (!item) {
175
+ throw new Error("Item with given id not found: " + id);
176
+ }
177
+
178
+ return {
179
+ ${collection_fields.map((field) => renderFieldForInitialValues(field)).filter((e) => e !== "").join(",\n")}
180
+ };
181
+ }
182
+ `,
183
+ render: ` async render(ctx: Context, data: FormData, show_field_errors: boolean) {
184
+ return html(
185
+ ctx,
186
+ "AddBlogArticle",
187
+ tempstream/* HTML */ \` <div class="add-opinion-form">
188
+ <a class="" href="\${${list_action}URL}"
189
+ >\u2190 Back to ${collection_name} list</a
190
+ >
191
+ \${await super.render(ctx, data, show_field_errors)}
192
+ </div>\`
193
+ );
194
+ }`
195
+ });
196
+ }
197
+ export {
198
+ editItemFormTemplate
199
+ };
200
+ //# sourceMappingURL=collection-edit-form.js.map
@@ -0,0 +1,211 @@
1
+ import { extractCollectionClassname } from "../../generate-collections.js";
2
+ import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
3
+ import _locreq from "locreq";
4
+ import { curryImportPath } from "../../utils/import-path.js";
5
+ import { DefaultListFilters } from "../../page/default-list-filters.js";
6
+ import { toKebabCase, toPascalCase } from "js-convert-case";
7
+ import { formatWithPrettier } from "../../utils/prettier.js";
8
+ const target_locreq = _locreq(process.cwd());
9
+ function makeDefaultFilterFields(collection_fields) {
10
+ return collection_fields.map(
11
+ (field) => ` { field: "${field.name}", ...${DefaultListFilters[field.type] ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback`} }`
12
+ ).join(",\n");
13
+ }
14
+ function makeDefaultDisplayFields(collection_fields) {
15
+ return collection_fields.map((field) => {
16
+ if (field.type == "boolean") {
17
+ return ` {
18
+ field: "${field.name}",
19
+ label: "${field.name}",
20
+ format: (v: boolean) => (v ? "YES" : "NO"),
21
+ }`;
22
+ }
23
+ if (field.type == "datetime") {
24
+ return ` {
25
+ field: "${field.name}",
26
+ label: "${field.name}",
27
+ format: (v: number) => {
28
+ const d = new Date();
29
+ d.setTime(v);
30
+ return d.toString().split(" ").slice(1, 5).join(" ");
31
+ },
32
+ }`;
33
+ }
34
+ if (field.type == "image") {
35
+ return `{
36
+ field: "${field.name}",
37
+ label: "${field.name}",
38
+ format: async (value: FilePointer) => {
39
+ return imageRouter.image(await value.getPath(), {
40
+ container: { width: 45, height: 45 },
41
+ crop: { width: 45, height: 45 },
42
+ alt: "",
43
+ });
44
+ },
45
+ }`;
46
+ }
47
+ return ` { field: "${field.name}", label: "${field.name}" }`;
48
+ }).join(",\n");
49
+ }
50
+ const default_hooks = {
51
+ pre_header_html: "",
52
+ post_header_html: "",
53
+ post_import_js: "",
54
+ render_item: (collection_name) => ` async renderItem(ctx: Context, item: CollectionItem<typeof ${toPascalCase(
55
+ collection_name
56
+ )}>) {
57
+ return <tr>
58
+ {displayFields.map(({ field, format }) => {
59
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
60
+ const value = item.get(field as any);
61
+ return <td>{format ? format(value, item) : value}</td>;
62
+ })}
63
+ </tr>;
64
+ }`
65
+ };
66
+ async function collectionListTemplate(collection_name, action_name, newfilefullpath, _hooks = {}) {
67
+ const hooks = { ...default_hooks, ..._hooks };
68
+ const [uppercase_collection, collection_fields] = await Promise.all([
69
+ extractCollectionClassname(
70
+ target_locreq.resolve(
71
+ "src/back/collections/" + collection_name + ".ts"
72
+ )
73
+ ),
74
+ extract_fields_from_collection(collection_name)
75
+ ]);
76
+ const importPath = curryImportPath(newfilefullpath);
77
+ const result = `import type { Context } from "koa";
78
+ import type { CollectionItem, ItemListResult } from "sealious";
79
+ import type { FlatTemplatable, Templatable } from "tempstream";
80
+ import { TempstreamJSX, tempstream } from "tempstream";
81
+ import { ${uppercase_collection} } from "${importPath(
82
+ target_locreq.resolve("src/back/collections/collections.ts")
83
+ )}";
84
+ import html from "${importPath(target_locreq.resolve("src/back/html.ts"))}";
85
+ import type { ListFilterRender } from "@sealcode/sealgen";
86
+ import {
87
+ SealiousItemListPage,
88
+ BaseListPageFields,
89
+ DefaultListFilters,
90
+ } from "@sealcode/sealgen";
91
+ 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
+
97
+ ${hooks.post_import_js}
98
+
99
+ export const actionName = "${action_name}";
100
+
101
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
102
+ const filterFields = [
103
+ ${makeDefaultFilterFields(collection_fields)}
104
+ ] as {
105
+ field: keyof (typeof ${uppercase_collection})["fields"];
106
+ render?: ListFilterRender;
107
+ prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
108
+ }[];
109
+
110
+ const displayFields = [
111
+ ${makeDefaultDisplayFields(collection_fields)}
112
+ ] as {
113
+ field: string;
114
+ label: string;
115
+ format?: (value: unknown, item: CollectionItem<typeof ${toPascalCase(
116
+ collection_name
117
+ )}>) => FlatTemplatable;
118
+ }[];
119
+
120
+ export default new (class ${action_name}Page extends SealiousItemListPage<
121
+ typeof ${uppercase_collection},
122
+ typeof BaseListPageFields
123
+ > {
124
+ fields = BaseListPageFields;
125
+
126
+ async renderFilters(ctx: Context): Promise<FlatTemplatable> {
127
+ const query_params = qs.parse(ctx.search.slice(1));
128
+ query_params.page = "1";
129
+ const filter_values = await super.getFilterValues(ctx);
130
+ return <form>
131
+ {Object.entries(query_params).map(([key, value]) => {
132
+ if (key == "filter") {
133
+ return "";
134
+ }
135
+ // this is necessary to not lose any query params when the user changes the filter values
136
+ return <input type="hidden" name={key} value={value}/>;
137
+ })}
138
+ {filterFields.map(({ field, render }) => {
139
+ if (!render) {
140
+ render = DefaultListFilters.fallback.render;
141
+ }
142
+ return (
143
+ render(filter_values[field] || "", this.collection.fields[field]) ||
144
+ ""
145
+ );
146
+ })}
147
+ <input type="submit" />
148
+ </form>;
149
+ }
150
+
151
+ async getFilterValues(ctx: Context) {
152
+ // adding opportunity to adjust the values for a given field filter before it's sent to Sealious
153
+ const values = await super.getFilterValues(ctx);
154
+ for (const filterField of filterFields) {
155
+ const key = filterField.field as keyof typeof values;
156
+ if (key in values) {
157
+ const prepare_fn = filterField.prepareValue;
158
+ if (prepare_fn) {
159
+ values[key] = prepare_fn(values[key]) as any;
160
+ }
161
+ }
162
+ }
163
+ return values;
164
+ }
165
+
166
+ ${hooks.render_item(collection_name)}
167
+
168
+ renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
169
+ return (
170
+ <table class="sealious-list ${toKebabCase(action_name)}-table">
171
+ {this.renderTableHead(ctx, displayFields)}
172
+ <tbody>{content}</tbody>
173
+ </table>
174
+ );
175
+ }
176
+
177
+ renderTableHead(
178
+ ctx: Context,
179
+ fields: { field: string; label?: string }[]
180
+ ): FlatTemplatable {
181
+ return tempstream/* HTML */ \`<thead>
182
+ <tr>
183
+ \${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))}
184
+ <th>Actions</th>
185
+ </tr>
186
+ </thead>\`;
187
+ }
188
+
189
+ async render(ctx: Context) {
190
+ return html(
191
+ ctx,
192
+ "${action_name}",
193
+ <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
194
+ ${hooks.pre_header_html}
195
+ <h2>${action_name} List</h2>
196
+ ${hooks.post_header_html}
197
+ {super.render(ctx)}
198
+ </div>
199
+ );
200
+ }
201
+
202
+ })(${uppercase_collection});
203
+ `;
204
+ return await formatWithPrettier(result);
205
+ }
206
+ export {
207
+ collectionListTemplate,
208
+ makeDefaultDisplayFields,
209
+ makeDefaultFilterFields
210
+ };
211
+ //# sourceMappingURL=collection-list.js.map
@@ -0,0 +1,72 @@
1
+ import { toKebabCase, toPascalCase } from "js-convert-case";
2
+ import { curryImportPath } from "../../utils/import-path.js";
3
+ import { formatWithPrettier } from "../../utils/prettier.js";
4
+ async function askItemDeleteTemplate(ask_delete_action_name, collection_name, newfilefullpath, list_action_name, delete_action_name) {
5
+ const importPath = curryImportPath(newfilefullpath);
6
+ const result = `import type { Context } from "koa";
7
+ import { Page } from "@sealcode/sealgen";
8
+ import type { CollectionItem } from "sealious";
9
+ import { TempstreamJSX } from "tempstream";
10
+ import { ${delete_action_name}URL } from "${importPath(
11
+ "src/back/routes/urls.js"
12
+ )}";
13
+ import html from "${importPath("src/back/html.ts")}";
14
+ import { ${toPascalCase(collection_name)} } from "${importPath(
15
+ "src/back/collections/collections.js"
16
+ )}";
17
+
18
+ export const actionName = "${ask_delete_action_name}";
19
+
20
+ export default new (class ${ask_delete_action_name}Page extends Page {
21
+ canAccess = async (ctx: Context) => {
22
+ const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
23
+ const response = await policy.check(ctx.$context);
24
+ return { canAccess: response?.allowed || false, message: response?.reason || "" };
25
+ };
26
+
27
+ getItemTitle(_ctx: Context, item: CollectionItem<typeof ${toPascalCase(
28
+ collection_name
29
+ )}>){
30
+ return item.get("name")
31
+ }
32
+
33
+ async render(ctx: Context) {
34
+ const id = ctx.params.id
35
+ if(!id){throw new Error("Could not read the id param from the URL")}
36
+ const { items: [item] } =
37
+ await ctx.$app.collections["${toKebabCase(
38
+ collection_name
39
+ )}"].list(ctx.$context).ids([id]).fetch();
40
+
41
+ if (!item) {
42
+ throw new Error("Item with given id not found");
43
+ }
44
+ return html(
45
+ ctx,
46
+ "Delete?",
47
+ <div>
48
+ <div class="confirm-delete-page confirm-delete-page--${toKebabCase(
49
+ collection_name
50
+ )}">
51
+ <span>
52
+ Are you sure you want to remove: {" "}
53
+ <b>{this.getItemTitle(ctx, item)}</b>
54
+ </span>
55
+ <form action={${delete_action_name}URL(item.id)}>
56
+ <button type="submit">
57
+ remove
58
+ </button>
59
+ <input type="hidden" name="id" value={item?.id} />
60
+ </form>
61
+ </div>
62
+ </div>
63
+ );
64
+ }
65
+ })();
66
+ `;
67
+ return await formatWithPrettier(result);
68
+ }
69
+ export {
70
+ askItemDeleteTemplate
71
+ };
72
+ //# sourceMappingURL=confirm-item-delete.js.map
@@ -0,0 +1,41 @@
1
+ import { toKebabCase, toPascalCase } from "js-convert-case";
2
+ import { curryImportPath } from "../../utils/import-path.js";
3
+ function itemDeleteTemplate(delete_action_name, collection_name, newfilefullpath, list_action_name) {
4
+ const importPath = curryImportPath(newfilefullpath);
5
+ return `import { Context } from "koa";
6
+ import { Mountable } from "@sealcode/sealgen";
7
+ import Router from "@koa/router";
8
+
9
+ import { ${toPascalCase(collection_name)} } from "${importPath(
10
+ "src/back/collections/collections.js"
11
+ )}";
12
+
13
+ import { ${list_action_name}URL } from "${importPath(
14
+ "src/back/routes/urls.js"
15
+ )}";
16
+
17
+ export const actionName = "${delete_action_name}";
18
+
19
+ export default new (class ${delete_action_name}Redirect extends Mountable {
20
+ canAccess = async (ctx: Context) => {
21
+ const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
22
+ const response = await policy.check(ctx.$context);
23
+ return { canAccess: response?.allowed || false, message: response?.reason || "" };
24
+ };
25
+
26
+ mount(router: Router, path: string) {
27
+ router.get(path, async (ctx) => {
28
+ ctx.$app.collections["${toKebabCase(
29
+ collection_name
30
+ )}"].removeByID(ctx.$context, ctx.params.id);
31
+ ctx.status = 302;
32
+ ctx.redirect(${list_action_name}URL);
33
+ });
34
+ }
35
+ })();
36
+ `;
37
+ }
38
+ export {
39
+ itemDeleteTemplate
40
+ };
41
+ //# sourceMappingURL=item-delete.js.map
@@ -0,0 +1,118 @@
1
+ import { toPascalCase } from "js-convert-case";
2
+ import { extractCollectionClassname } from "../../generate-collections.js";
3
+ import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
4
+ import _locreq from "locreq";
5
+ import { curryImportPath } from "../../utils/import-path.js";
6
+ import { formatWithPrettier } from "../../utils/prettier.js";
7
+ const target_locreq = _locreq(process.cwd());
8
+ function renderFieldImports(field) {
9
+ if (field.type == "enum") {
10
+ return `import type { FieldTypes } from "sealious";`;
11
+ }
12
+ }
13
+ function renderFormField(collection_name, { name, type, target, is_readonly }) {
14
+ const required_s = `${toPascalCase(
15
+ collection_name
16
+ )}.fields.${name}.required`;
17
+ if (type == "image" || type == "file") {
18
+ return `${name}: new Fields.File(${required_s}, TheFileManager)`;
19
+ }
20
+ if (type == "int") {
21
+ return `${name}: new Fields.NumberField(${required_s}, 0)`;
22
+ }
23
+ if (type == "boolean") {
24
+ return `${name}: new Fields.Boolean(${required_s})`;
25
+ }
26
+ if (type == "enum") {
27
+ return `${name}: new Fields.PickFromListField(
28
+ ${toPascalCase(collection_name)}.fields["${name}"].required,
29
+ async (ctx) =>
30
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
31
+ (${toPascalCase(collection_name)}.fields["${name}"] as FieldTypes.Enum<string>)
32
+ .getAllowedValues(ctx.$app)
33
+ .map((value) => ({ value, label: value }))
34
+ )`;
35
+ }
36
+ if (is_readonly || type == "jdd") {
37
+ return "";
38
+ }
39
+ if (type == "deep-reverse-single-reference" && target) {
40
+ return `${name}: new Fields.CheckboxedListField(${required_s}, async (ctx) => {
41
+ const { items } = await ctx.$app.collections["${target}"]
42
+ .list(ctx.$context)
43
+ .fetch();
44
+
45
+ return items.map((s) => ({
46
+ label: s.get("name"),
47
+ value: s.id,
48
+ }));
49
+ })`;
50
+ }
51
+ return `${name}: new Fields.CollectionField(${required_s}, ${toPascalCase(
52
+ collection_name
53
+ )}.fields.${name})`;
54
+ }
55
+ function renderFormControl(collection_name, { name, type, target, is_readonly }) {
56
+ const fields_var = `${toPascalCase(collection_name)}FormFields`;
57
+ if (is_readonly || type == "jdd") {
58
+ return "";
59
+ }
60
+ if (type == "int") {
61
+ return `new Controls.Number(${fields_var}.${name}, { label: "${name}" })`;
62
+ }
63
+ if (type == "deep-reverse-single-reference" && target) {
64
+ return `new Controls.CheckboxedListInput(${fields_var}.${name}, {
65
+ label: "${name}",
66
+ })`;
67
+ }
68
+ if (type == "boolean") {
69
+ return `new Controls.Checkbox(${fields_var}.${name}, { label: "${name}" })`;
70
+ }
71
+ if (type == "date") {
72
+ return `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}", type: "date"})`;
73
+ }
74
+ if (type == "enum") {
75
+ return `new Controls.Dropdown(${fields_var}.${name}, { label: "${name}"})`;
76
+ }
77
+ if (type == "image") {
78
+ return `new Controls.Photo(${fields_var}.${name}, imageRouter, {
79
+ label: "${name}",
80
+ })`;
81
+ }
82
+ return `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}"})`;
83
+ }
84
+ async function sharedCrudFormFields(collection_name, newfilefullpath) {
85
+ const [uppercase_collection, collection_fields] = await Promise.all([
86
+ extractCollectionClassname(
87
+ target_locreq.resolve(
88
+ "src/back/collections/" + collection_name + ".ts"
89
+ )
90
+ ),
91
+ extract_fields_from_collection(collection_name)
92
+ ]);
93
+ const importPath = curryImportPath(newfilefullpath);
94
+ const result = `import { Controls, Fields } from "@sealcode/sealgen";
95
+ import { ${uppercase_collection} } from "${importPath(
96
+ "src/back/collections/collections.js"
97
+ )}";
98
+ import { TheFileManager } from "${importPath("src/back/file-manager.js")}";
99
+ import { imageRouter } from "${importPath("src/back/image-router.js")}";
100
+
101
+ ${Array.from(
102
+ new Set(collection_fields.map((field) => renderFieldImports(field)))
103
+ ).join(";\n")}
104
+
105
+ export const ${toPascalCase(collection_name)}FormFields = <const>{
106
+ ${collection_fields.map((field) => renderFormField(collection_name, field)).filter((e) => e != "").join(",\n")}
107
+ };
108
+
109
+ export const ${toPascalCase(collection_name)}FormControls = [
110
+ ${collection_fields.map((field) => renderFormControl(collection_name, field)).filter((e) => e != "").join(",\n")}
111
+ ];
112
+ `;
113
+ return formatWithPrettier(result);
114
+ }
115
+ export {
116
+ sharedCrudFormFields
117
+ };
118
+ //# sourceMappingURL=shared-crud-form-fields.js.map
@@ -13,6 +13,7 @@ export * from "./make-slug.js";
13
13
  export * from "./render-attributes.js";
14
14
  export * from "./format-external-url.js";
15
15
  export * from "./base64.js";
16
+ export * from "./with-fallback.js";
16
17
  export {
17
18
  UrlWithNewParams,
18
19
  assertThrowsAsync,
@@ -0,0 +1,21 @@
1
+ function withFallback(value1, value2, value3) {
2
+ if (value3 !== void 0) {
3
+ if (value1 !== null && value1 !== void 0 && value2 !== null && value2 !== void 0) {
4
+ return value2;
5
+ } else {
6
+ return value3;
7
+ }
8
+ } else {
9
+ if (value1 !== null && value1 !== void 0) {
10
+ return value1;
11
+ } else if (value2 !== void 0 && value2 !== null) {
12
+ return value2;
13
+ } else {
14
+ throw new Error("No suitable fallback: ${arguments}");
15
+ }
16
+ }
17
+ }
18
+ export {
19
+ withFallback
20
+ };
21
+ //# sourceMappingURL=with-fallback.js.map
@@ -0,0 +1,11 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { resolve } from "path";
3
+ async function writeFile(path, content) {
4
+ await fs.mkdir(resolve(path, "../"), { recursive: true });
5
+ await fs.writeFile(path, content);
6
+ console.log(`${path} created`);
7
+ }
8
+ export {
9
+ writeFile
10
+ };
11
+ //# sourceMappingURL=write-file.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.15.33",
3
+ "version": "0.15.35",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",