@sealcode/sealgen 0.19.17 → 0.19.20
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/add-crud.test.d.ts +1 -0
- package/@types/controllers/sealgen-table.stimulus.d.ts +13 -0
- package/@types/forms/controls/table.d.ts +2 -0
- package/@types/index.d.ts +1 -0
- package/lib/controllers/sealgen-table.stimulus.js +98 -0
- package/lib/forms/controls/table.js +49 -43
- package/lib/generate-routes.js +11 -2
- package/lib/mount.js +8 -10
- package/lib/templates/form.js +2 -2
- package/lib/templates/jdd-editor.js +2 -0
- package/lib/templates/long-running-process.js +2 -0
- package/lib/templates/multiform.js +2 -0
- package/lib/templates/page.js +2 -0
- package/lib/templates/post.js +2 -0
- package/lib/templates/redirect.js +2 -0
- package/lib/templates/shared/collection-list.js +2 -1
- package/lib/templates/shared/item-delete.js +2 -0
- package/lib/templates/stateful-page.js +2 -0
- package/package.json +1 -1
- package/src/add-crud.test.ts +44 -0
- package/src/add-crud.ts +1 -1
- package/src/controllers/sealgen-table.stimulus.ts +115 -0
- package/src/forms/controls/table.ts +94 -81
- package/src/generate-routes.ts +13 -2
- package/src/index.ts +2 -0
- package/src/mount.ts +8 -12
- package/src/templates/form.ts +2 -2
- package/src/templates/jdd-editor.ts +2 -0
- package/src/templates/long-running-process.ts +2 -0
- package/src/templates/multiform.ts +2 -0
- package/src/templates/page.ts +2 -0
- package/src/templates/post.ts +2 -0
- package/src/templates/redirect.ts +2 -0
- package/src/templates/shared/collection-list.ts +2 -1
- package/src/templates/shared/item-delete.ts +2 -0
- package/src/templates/stateful-page.ts +2 -0
- package/lib/controllers/table-add-button.stimulus.js +0 -35
- package/src/controllers/table-add-button.stimulus.ts +0 -35
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Controller } from "stimulus";
|
|
2
|
+
export default class TableController extends Controller {
|
|
3
|
+
static targets: string[];
|
|
4
|
+
tableTarget: HTMLTableElement;
|
|
5
|
+
templateTarget: HTMLTemplateElement;
|
|
6
|
+
getTbody(): HTMLTableSectionElement;
|
|
7
|
+
getRowIndex(tr: HTMLTableRowElement): number;
|
|
8
|
+
addRow(): void;
|
|
9
|
+
swapRows(index_a: number, index_b: number): void;
|
|
10
|
+
moveUp(e: MouseEvent): void;
|
|
11
|
+
moveDown(e: MouseEvent): void;
|
|
12
|
+
reindex(): void;
|
|
13
|
+
}
|
|
@@ -10,6 +10,7 @@ export type TableControlOptions<F extends Record<string, FormField>> = {
|
|
|
10
10
|
[field_name in keyof F]: FormControl;
|
|
11
11
|
};
|
|
12
12
|
allow_removing?: boolean;
|
|
13
|
+
allow_reordering?: boolean;
|
|
13
14
|
label_add?: string;
|
|
14
15
|
label: string;
|
|
15
16
|
label_remove?: string;
|
|
@@ -36,4 +37,5 @@ export declare class Table<F extends Record<string, FormField>> extends FormCont
|
|
|
36
37
|
setLabel(value: string): this;
|
|
37
38
|
setRemoveLabel(value: string): this;
|
|
38
39
|
setAddLabel(value: string): this;
|
|
40
|
+
setAllowReordering(value: boolean): this;
|
|
39
41
|
}
|
package/@types/index.d.ts
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Controller } from "stimulus";
|
|
2
|
+
class TableController extends Controller {
|
|
3
|
+
static {
|
|
4
|
+
this.targets = ["table", "template"];
|
|
5
|
+
}
|
|
6
|
+
getTbody() {
|
|
7
|
+
const tbody = this.element.closest("turbo-frame")?.querySelector("tbody");
|
|
8
|
+
if (!tbody) {
|
|
9
|
+
throw new Error("tbody not found");
|
|
10
|
+
}
|
|
11
|
+
return tbody;
|
|
12
|
+
}
|
|
13
|
+
getRowIndex(tr) {
|
|
14
|
+
let result = 0;
|
|
15
|
+
for (const child of Array.from(this.getTbody().children)) {
|
|
16
|
+
if (child == tr) {
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
result++;
|
|
20
|
+
}
|
|
21
|
+
return -1;
|
|
22
|
+
}
|
|
23
|
+
addRow() {
|
|
24
|
+
const tbody = this.getTbody();
|
|
25
|
+
const orig_template = this.templateTarget.innerHTML;
|
|
26
|
+
const placeholder = this.element.getAttribute("data-table-placeholder");
|
|
27
|
+
if (!placeholder) {
|
|
28
|
+
throw new Error("Could not find placeholder attribute");
|
|
29
|
+
}
|
|
30
|
+
this.templateTarget.innerHTML = orig_template.replaceAll(
|
|
31
|
+
placeholder,
|
|
32
|
+
String(tbody.querySelectorAll("tr").length)
|
|
33
|
+
);
|
|
34
|
+
tbody.appendChild(this.templateTarget.cloneNode(true).content);
|
|
35
|
+
this.templateTarget.innerHTML = orig_template;
|
|
36
|
+
this.reindex();
|
|
37
|
+
}
|
|
38
|
+
swapRows(index_a, index_b) {
|
|
39
|
+
const tbody = this.getTbody();
|
|
40
|
+
const [a, b] = [index_a, index_b].sort().reverse();
|
|
41
|
+
tbody.insertBefore(tbody.children[a], tbody.children[b]);
|
|
42
|
+
this.reindex();
|
|
43
|
+
}
|
|
44
|
+
moveUp(e) {
|
|
45
|
+
const target = e.target;
|
|
46
|
+
const tr = target.closest("tr");
|
|
47
|
+
if (!tr) {
|
|
48
|
+
console.error("Not within a tr");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const index_a = this.getRowIndex(tr);
|
|
52
|
+
if (index_a < 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const index_b = index_a - 1;
|
|
56
|
+
this.swapRows(index_a, index_b);
|
|
57
|
+
}
|
|
58
|
+
moveDown(e) {
|
|
59
|
+
const target = e.target;
|
|
60
|
+
const tr = target.closest("tr");
|
|
61
|
+
if (!tr) {
|
|
62
|
+
console.error("Not within a tr");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const index_a = this.getRowIndex(tr);
|
|
66
|
+
if (index_a < 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const index_b = index_a + 1;
|
|
70
|
+
this.swapRows(index_a, index_b);
|
|
71
|
+
}
|
|
72
|
+
reindex() {
|
|
73
|
+
let result = 0;
|
|
74
|
+
for (const tr of Array.from(this.getTbody().children)) {
|
|
75
|
+
tr.querySelectorAll("input").forEach((input) => {
|
|
76
|
+
input.setAttribute(
|
|
77
|
+
"name",
|
|
78
|
+
(input.getAttribute("name") || "").replace(
|
|
79
|
+
/[(\d+)](?!.*\d)/,
|
|
80
|
+
// last number in a regex
|
|
81
|
+
result.toString()
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
result++;
|
|
86
|
+
}
|
|
87
|
+
this.element.querySelectorAll(
|
|
88
|
+
`[data-action="sealgen-table#moveUp"], [data-action="sealgen-table#moveDown"]`
|
|
89
|
+
).forEach((e) => e.disabled = false);
|
|
90
|
+
this.element.querySelectorAll(
|
|
91
|
+
'tr:first-child [data-action="sealgen-table#moveUp"], tr:last-child [data-action="sealgen-table#moveDown"]'
|
|
92
|
+
).forEach((e) => e.disabled = true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
TableController as default
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=sealgen-table.stimulus.js.map
|
|
@@ -49,19 +49,17 @@ class Table extends FormControl {
|
|
|
49
49
|
${this.options.allow_removing ? (
|
|
50
50
|
/* HTML */
|
|
51
51
|
`<td>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
formnovalidate
|
|
64
|
-
formaction="${this.getActionURL(
|
|
52
|
+
<button onclick="this.closest('tr').remove()">
|
|
53
|
+
remove
|
|
54
|
+
</button>
|
|
55
|
+
<noscript>
|
|
56
|
+
<input
|
|
57
|
+
type="submit"
|
|
58
|
+
data-turbo-frame="${this.getFrameID()}"
|
|
59
|
+
value="${this.options.label_remove || "remove"}"
|
|
60
|
+
form="${fctx.form_id}"
|
|
61
|
+
formnovalidate
|
|
62
|
+
formaction="${this.getActionURL(
|
|
65
63
|
this.table_field.name,
|
|
66
64
|
{
|
|
67
65
|
remove: {
|
|
@@ -69,9 +67,19 @@ class Table extends FormControl {
|
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
)}"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
/>
|
|
71
|
+
</noscript>
|
|
72
|
+
</td>`
|
|
73
|
+
) : ""}${this.options.allow_reordering ? (
|
|
74
|
+
/* HTML */
|
|
75
|
+
`<td>
|
|
76
|
+
<button data-action="sealgen-table#moveUp">
|
|
77
|
+
\u2191
|
|
78
|
+
</button>
|
|
79
|
+
<button data-action="sealgen-table#moveDown">
|
|
80
|
+
\u2193
|
|
81
|
+
</button>
|
|
82
|
+
</td>`
|
|
75
83
|
) : ""}
|
|
76
84
|
</tr>`
|
|
77
85
|
);
|
|
@@ -89,13 +97,17 @@ class Table extends FormControl {
|
|
|
89
97
|
].join(" ")}"
|
|
90
98
|
>
|
|
91
99
|
<label>${this.options.label || this.table_field.label}</label>
|
|
92
|
-
<div
|
|
93
|
-
|
|
100
|
+
<div
|
|
101
|
+
class="table__wrapper"
|
|
102
|
+
data-controller="sealgen-table"
|
|
103
|
+
data-table-placeholder="${TABLE_COLUMN_FIELD_INDEX_PLACEHOLDER}"
|
|
104
|
+
>
|
|
105
|
+
<table data-sealgen-table-target="table">
|
|
94
106
|
<tbody>
|
|
95
107
|
${rows?.map((row, index) => make_row(row, index))}
|
|
96
108
|
</tbody>
|
|
97
109
|
</table>
|
|
98
|
-
<template>
|
|
110
|
+
<template data-sealgen-table-target="template">
|
|
99
111
|
${make_row(null, TABLE_COLUMN_FIELD_INDEX_PLACEHOLDER)}
|
|
100
112
|
</template>
|
|
101
113
|
|
|
@@ -104,19 +116,17 @@ class Table extends FormControl {
|
|
|
104
116
|
this.options.allow_adding ? (
|
|
105
117
|
/* HTML */
|
|
106
118
|
`<button
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
formnovalidate
|
|
119
|
-
formaction="${this.getActionURL(
|
|
119
|
+
data-action="click->sealgen-table#addRow"
|
|
120
|
+
>
|
|
121
|
+
${this.options.label_add || "add"}
|
|
122
|
+
</button>
|
|
123
|
+
<noscript>
|
|
124
|
+
<input
|
|
125
|
+
type="submit"
|
|
126
|
+
value="add"
|
|
127
|
+
form="${fctx.form_id}"
|
|
128
|
+
formnovalidate
|
|
129
|
+
formaction="${this.getActionURL(
|
|
120
130
|
this.table_field.name,
|
|
121
131
|
{
|
|
122
132
|
insert: {
|
|
@@ -125,8 +135,8 @@ class Table extends FormControl {
|
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
)}"
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
/>
|
|
139
|
+
</noscript>`
|
|
130
140
|
) : ""}
|
|
131
141
|
</div>
|
|
132
142
|
</turbo-frame>`;
|
|
@@ -144,7 +154,7 @@ class Table extends FormControl {
|
|
|
144
154
|
router.post("/", async (ctx, next) => {
|
|
145
155
|
const action = ctx.$body.action;
|
|
146
156
|
const field_name = ctx.$body.field_name;
|
|
147
|
-
if (!is(action, predicates.object) || !is(field_name, predicates.string)) {
|
|
157
|
+
if (!is(action, predicates.object) || !is(field_name, predicates.string) || field_name !== this.table_field.name) {
|
|
148
158
|
await next();
|
|
149
159
|
return;
|
|
150
160
|
}
|
|
@@ -154,10 +164,6 @@ class Table extends FormControl {
|
|
|
154
164
|
},
|
|
155
165
|
action
|
|
156
166
|
)) {
|
|
157
|
-
if (field_name !== this.table_field.name) {
|
|
158
|
-
await next();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
167
|
if (!ctx.$body[this.table_field.name]) {
|
|
162
168
|
ctx.$body[this.table_field.name] = {};
|
|
163
169
|
}
|
|
@@ -169,10 +175,6 @@ class Table extends FormControl {
|
|
|
169
175
|
},
|
|
170
176
|
action
|
|
171
177
|
)) {
|
|
172
|
-
if (field_name !== this.table_field.name) {
|
|
173
|
-
await next();
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
178
|
if (!ctx.$body[this.table_field.name]) {
|
|
177
179
|
ctx.$body[this.table_field.name] = {};
|
|
178
180
|
}
|
|
@@ -221,6 +223,10 @@ class Table extends FormControl {
|
|
|
221
223
|
this.options.label_add = value;
|
|
222
224
|
return this;
|
|
223
225
|
}
|
|
226
|
+
setAllowReordering(value) {
|
|
227
|
+
this.options.allow_reordering = value;
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
224
230
|
}
|
|
225
231
|
export {
|
|
226
232
|
TABLE_COLUMN_FIELD_INDEX_PLACEHOLDER,
|
package/lib/generate-routes.js
CHANGED
|
@@ -5,8 +5,8 @@ import { walkDir } from "./utils/walk.js";
|
|
|
5
5
|
import { importPath } from "./utils/import-path.js";
|
|
6
6
|
import { assertType, predicates } from "@sealcode/ts-predicates";
|
|
7
7
|
import { unescape_url_params } from "./utils/escape-url-params.js";
|
|
8
|
-
import { formatWithPrettier } from "./utils/prettier.js";
|
|
9
8
|
import { Templates } from "./templates/templates.js";
|
|
9
|
+
import { formatWithPrettier } from "./utils/prettier.js";
|
|
10
10
|
const target_locreq = _locreq(process.cwd());
|
|
11
11
|
async function extractActionName(full_file_path) {
|
|
12
12
|
const file_content = await fs.readFile(full_file_path, "utf-8");
|
|
@@ -49,6 +49,12 @@ function sortRoutes(urls) {
|
|
|
49
49
|
return encoded1 < encoded2 ? -1 : 1;
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
const getPathToBuiltFile = (filePath) => {
|
|
53
|
+
const rootPath = target_locreq.resolve("");
|
|
54
|
+
const relativePath = relative(rootPath, filePath);
|
|
55
|
+
const distPath = relativePath.replace(/^src[\\/]/, "dist/");
|
|
56
|
+
return distPath.replace(/\.(ts|tsx)$/, ".js");
|
|
57
|
+
};
|
|
52
58
|
async function generateRoutes() {
|
|
53
59
|
const files = await Promise.all(
|
|
54
60
|
(await walkDir(target_locreq.resolve("src/back/routes"))).filter(
|
|
@@ -58,6 +64,7 @@ async function generateRoutes() {
|
|
|
58
64
|
).map(async (fullpath) => ({
|
|
59
65
|
fullpath,
|
|
60
66
|
actionName: await extractActionName(fullpath),
|
|
67
|
+
module_path: getPathToBuiltFile(fullpath),
|
|
61
68
|
// trailing slash is important here, as it enables to use the entire path while building relative URLs. For example, while visiting /users/123, the path ./add-photo leads to /users/add-photo. While visiting /users/123/ (note the trailing slash), the path ./add-photo leads to /users/123/add-photo
|
|
62
69
|
url: unescape_url_params(
|
|
63
70
|
"/" + relative(
|
|
@@ -71,7 +78,7 @@ async function generateRoutes() {
|
|
|
71
78
|
}))
|
|
72
79
|
);
|
|
73
80
|
const url_tree = { children: {} };
|
|
74
|
-
sortRoutes(files).forEach(({ actionName, url }) => {
|
|
81
|
+
sortRoutes(files).forEach(({ actionName, url, module_path }) => {
|
|
75
82
|
const elements = url.split("/").filter((e) => e != "");
|
|
76
83
|
let pointer = url_tree;
|
|
77
84
|
for (const [index, element] of elements.entries()) {
|
|
@@ -81,6 +88,7 @@ async function generateRoutes() {
|
|
|
81
88
|
pointer = pointer.children[element];
|
|
82
89
|
if (index == elements.length - 1) {
|
|
83
90
|
pointer.actionName = actionName;
|
|
91
|
+
pointer.module_path = module_path;
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
});
|
|
@@ -88,6 +96,7 @@ async function generateRoutes() {
|
|
|
88
96
|
|
|
89
97
|
export type URLTree = {
|
|
90
98
|
actionName?: string;
|
|
99
|
+
module_path?: string;
|
|
91
100
|
children: { [key: string]: URLTree };
|
|
92
101
|
};
|
|
93
102
|
export const url_tree = ${JSON.stringify(url_tree)};
|
package/lib/mount.js
CHANGED
|
@@ -10,19 +10,17 @@ async function handleHtmlPromise(ctx, next) {
|
|
|
10
10
|
}
|
|
11
11
|
function mount(router, url, mountable, use_dummy_app = false, file_manager = use_dummy_app ? new FileManager("/tmp", "/uploaded_files") : void 0) {
|
|
12
12
|
const raw_url = typeof url === "string" ? url : url.rawURL;
|
|
13
|
-
const imageRouter = new KoaResponsiveImageRouter({
|
|
14
|
-
staticPath: "/tmp",
|
|
15
|
-
thumbnailSize: 10,
|
|
16
|
-
cacheManagerResolutionThreshold: 10,
|
|
17
|
-
imageStoragePath: "/tmp",
|
|
18
|
-
smartCropStoragePath: "/tmp"
|
|
19
|
-
});
|
|
20
|
-
const fileManager = new FileManager("/tmp", "/uploaded_files");
|
|
21
13
|
const args = use_dummy_app ? [
|
|
22
14
|
async (ctx, next) => {
|
|
23
15
|
ctx.$app = {
|
|
24
|
-
fileManager,
|
|
25
|
-
imageRouter
|
|
16
|
+
fileManager: new FileManager("/tmp", "/uploaded_files"),
|
|
17
|
+
imageRouter: new KoaResponsiveImageRouter({
|
|
18
|
+
staticPath: "/tmp",
|
|
19
|
+
thumbnailSize: 10,
|
|
20
|
+
cacheManagerResolutionThreshold: 10,
|
|
21
|
+
imageStoragePath: "/tmp",
|
|
22
|
+
smartCropStoragePath: "/tmp"
|
|
23
|
+
}),
|
|
26
24
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
27
25
|
Logger: new Proxy(
|
|
28
26
|
{},
|
package/lib/templates/form.js
CHANGED
|
@@ -41,13 +41,13 @@ const defaults = {
|
|
|
41
41
|
async function formTemplate(action_name, _params = defaults) {
|
|
42
42
|
const params = { ...defaults, ..._params };
|
|
43
43
|
const content = `import type { Context } from "koa";
|
|
44
|
-
import type { FormData } from "@sealcode/sealgen";
|
|
44
|
+
import type { FormData, BreadcrumbLabel } from "@sealcode/sealgen";
|
|
45
45
|
import { Form, Controls } from "@sealcode/sealgen";
|
|
46
46
|
import html from "src/back/html.js";
|
|
47
|
-
|
|
48
47
|
${params.postimport}
|
|
49
48
|
|
|
50
49
|
export const actionName = "${action_name}";
|
|
50
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
51
51
|
|
|
52
52
|
const getFields = ${params.get_fields};
|
|
53
53
|
|
|
@@ -48,12 +48,14 @@ import type { FieldNames } from "sealious";
|
|
|
48
48
|
import { TempstreamJSX } from "tempstream";
|
|
49
49
|
import type ${collectionClassName} from "src/back/collections/${collection_name}.js";
|
|
50
50
|
import { EditJDDField } from "@sealcode/jdd-editor";
|
|
51
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
51
52
|
import html from "src/back/html.js";
|
|
52
53
|
import { registry } from "src/back/jdd-components/registry.js";
|
|
53
54
|
import { makeJDDContext } from "src/back/jdd-context.js";
|
|
54
55
|
import { defaultHead } from "src/back/defaultHead.js";
|
|
55
56
|
|
|
56
57
|
export const actionName = "${actionName}";
|
|
58
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${actionName}";
|
|
57
59
|
|
|
58
60
|
export default new (class JDDCreatePreviewPage extends EditJDDField<${collectionClassName}> {
|
|
59
61
|
getCollection(ctx: Context) {
|
|
@@ -5,10 +5,12 @@ async function LongRunningProcessTemplate(action_name, newfilefullpath) {
|
|
|
5
5
|
return `import { Context } from "koa";
|
|
6
6
|
import { TempstreamJSX } from "tempstream";
|
|
7
7
|
import { Page } from "@sealcode/sealgen";
|
|
8
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
8
9
|
import html from "${rel("src/back/html.js")}";
|
|
9
10
|
import { LongRunningProcess } from "sealious";
|
|
10
11
|
|
|
11
12
|
export const actionName = "${action_name}";
|
|
13
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
12
14
|
|
|
13
15
|
export default new (class PatroniteAutoImportStatusPage extends Page {
|
|
14
16
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -4,9 +4,11 @@ async function multiformTemplate(action_name, newfilefullpath) {
|
|
|
4
4
|
return `import { Context } from "koa";
|
|
5
5
|
import html from "${rel("src/back/html.js")}";
|
|
6
6
|
import { Controls, Fields, Form, FormMessage, Multiform } from "@sealcode/sealgen";
|
|
7
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
7
8
|
import { FlatTemplatable } from "tempstream";
|
|
8
9
|
|
|
9
10
|
export const actionName = "${action_name}";
|
|
11
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
10
12
|
|
|
11
13
|
const fields1 = {
|
|
12
14
|
email: new Fields.TextBasedSimpleField(true),
|
package/lib/templates/page.js
CHANGED
|
@@ -2,9 +2,11 @@ async function pageTemplate(action_name) {
|
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { TempstreamJSX } from "tempstream";
|
|
4
4
|
import { Page } from "@sealcode/sealgen";
|
|
5
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
5
6
|
import html from "src/back/html.js";
|
|
6
7
|
|
|
7
8
|
export const actionName = "${action_name}";
|
|
9
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
8
10
|
|
|
9
11
|
export default new (class ${action_name}Page extends Page {
|
|
10
12
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
package/lib/templates/post.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
async function postTemplate(action_name) {
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { Mountable } from "@sealcode/sealgen";
|
|
4
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
4
5
|
import type Router from "@koa/router";
|
|
5
6
|
|
|
6
7
|
export const actionName = "${action_name}";
|
|
8
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
7
9
|
|
|
8
10
|
export default new (class ${action_name}Redirect extends Mountable {
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
async function redirectTemplate(action_name) {
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { Mountable } from "@sealcode/sealgen";
|
|
4
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
4
5
|
import type Router from "@koa/router";
|
|
5
6
|
|
|
6
7
|
export const actionName = "${action_name}";
|
|
8
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
7
9
|
|
|
8
10
|
export default new (class ${action_name}Redirect extends Mountable {
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -91,7 +91,7 @@ import type { FlatTemplatable, Templatable } from "tempstream";
|
|
|
91
91
|
import { TempstreamJSX, tempstream } from "tempstream";
|
|
92
92
|
import { ${uppercase_collection} } from "src/back/collections/collections.js";
|
|
93
93
|
import html from "src/back/html.js";
|
|
94
|
-
import type { ListFilterRender, FormDisplayInfo } from "@sealcode/sealgen";
|
|
94
|
+
import type { ListFilterRender, FormDisplayInfo, BreadcrumbLabel } from "@sealcode/sealgen";
|
|
95
95
|
import {
|
|
96
96
|
SealiousItemListPage,
|
|
97
97
|
BaseListPageFields,
|
|
@@ -103,6 +103,7 @@ ${hooks.post_import_js}
|
|
|
103
103
|
${field_import_string}
|
|
104
104
|
|
|
105
105
|
export const actionName = "${action_name}";
|
|
106
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
106
107
|
|
|
107
108
|
const filterFields:{
|
|
108
109
|
field: keyof (typeof ${uppercase_collection})["fields"];
|
|
@@ -4,6 +4,7 @@ function itemDeleteTemplate(delete_action_name, collection_name, list_action_nam
|
|
|
4
4
|
const result = `import type { Context } from "koa";
|
|
5
5
|
import { Mountable } from "@sealcode/sealgen";
|
|
6
6
|
import type Router from "@koa/router";
|
|
7
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
7
8
|
|
|
8
9
|
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
9
10
|
"src/back/collections/collections.js"
|
|
@@ -14,6 +15,7 @@ import { ${list_action_name}URL } from "${importPath(
|
|
|
14
15
|
)}";
|
|
15
16
|
|
|
16
17
|
export const actionName = "${delete_action_name}";
|
|
18
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${delete_action_name}";
|
|
17
19
|
|
|
18
20
|
export default new (class ${delete_action_name}Redirect extends Mountable {
|
|
19
21
|
canAccess = async (ctx: Context) => {
|
|
@@ -5,9 +5,11 @@ async function statefulPageTemplate(action_name, newfilefullpath) {
|
|
|
5
5
|
return formatWithPrettier(`import { TempstreamJSX, Templatable } from "tempstream";
|
|
6
6
|
import { Context } from "koa";
|
|
7
7
|
import { StatefulPage, StatefulPageActionArgument } from "@sealcode/sealgen";
|
|
8
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
8
9
|
import html from "${rel("src/back/html.js")}";
|
|
9
10
|
|
|
10
11
|
export const actionName = "${action_name}";
|
|
12
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
11
13
|
|
|
12
14
|
const actions = {
|
|
13
15
|
add: ({ state, inputs }: StatefulPageActionArgument<State>) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Locator, Page } from "playwright";
|
|
2
|
+
import { RealAppTest } from "./test_utils/test-on-real-app.js";
|
|
3
|
+
import { getBrowser } from "./utils/browser-creator.js";
|
|
4
|
+
|
|
5
|
+
describe("add crud test", () => {
|
|
6
|
+
let page: Page;
|
|
7
|
+
before(async () => {
|
|
8
|
+
const browser = await getBrowser();
|
|
9
|
+
const context = await browser.newContext();
|
|
10
|
+
page = await context.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
after(async () => {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("Generate simple sealious crud", async function () {
|
|
18
|
+
const test_app = await RealAppTest.init();
|
|
19
|
+
|
|
20
|
+
const path = "/sample";
|
|
21
|
+
await test_app.runSealgenCommand(`add-collection --collection_name=sealcode-test`);
|
|
22
|
+
await test_app.runSealgenCommand(`add-crud --url=${path} --collection=sealcode-test`);
|
|
23
|
+
await test_app.start();
|
|
24
|
+
|
|
25
|
+
await page.goto(test_app.fullURL(`sample/create/`));
|
|
26
|
+
|
|
27
|
+
const contentInput: Locator = page.getByLabel("Content");
|
|
28
|
+
|
|
29
|
+
await contentInput.focus();
|
|
30
|
+
await contentInput.fill("Hello");
|
|
31
|
+
|
|
32
|
+
await page.locator("[type=submit]").click();
|
|
33
|
+
await page.locator('[href="/sample/"]').click();
|
|
34
|
+
|
|
35
|
+
const dataFieldNameAttr: string | null = await page
|
|
36
|
+
.getByText("Hello")
|
|
37
|
+
.getAttribute("data-field-name");
|
|
38
|
+
|
|
39
|
+
if (dataFieldNameAttr !== "content") {
|
|
40
|
+
throw new Error("Generated td element does not have correct data-field-name attribute");
|
|
41
|
+
}
|
|
42
|
+
await test_app.close();
|
|
43
|
+
}).timeout(100 * 1000);
|
|
44
|
+
});
|
package/src/add-crud.ts
CHANGED
|
@@ -71,7 +71,7 @@ export async function addCRUD(
|
|
|
71
71
|
{displayFields.map(({ field, format }) => {
|
|
72
72
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
73
73
|
const value = item.get(field as any);
|
|
74
|
-
return <td>{format ? format(value, item) : value}</td>;
|
|
74
|
+
return <td data-field-name={field}>{format ? format(value, item) : value}</td>;
|
|
75
75
|
})}
|
|
76
76
|
<td><div class="sealious-list__actions">
|
|
77
77
|
<a href={${edit_action_name}URL(item.id)}>Edit</a>
|