@jskit-ai/crud-ui-generator 0.1.40 → 0.1.42
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/package.descriptor.mjs +16 -4
- package/package.json +4 -4
- package/src/server/buildTemplateContext.js +70 -0
- package/templates/src/pages/admin/ui-generator/EditElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/EditWrapperElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/ListElement.vue +2 -26
- package/templates/src/pages/admin/ui-generator/NewElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/NewWrapperElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/ViewElement.vue +2 -6
- package/test/buildTemplateContext.test.js +60 -18
- package/test/packageDescriptor.test.js +17 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/crud-ui-generator",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.42",
|
|
5
5
|
kind: "generator",
|
|
6
6
|
description: "Generate CRUD route trees from resource validators at an explicit route root relative to src/pages/.",
|
|
7
7
|
options: {
|
|
@@ -43,6 +43,15 @@ export default Object.freeze({
|
|
|
43
43
|
promptLabel: "Route id param",
|
|
44
44
|
promptHint: "Route param used by view and edit pages (default: recordId)."
|
|
45
45
|
},
|
|
46
|
+
"parent-title": {
|
|
47
|
+
required: false,
|
|
48
|
+
inputType: "text",
|
|
49
|
+
defaultValue: "contextual",
|
|
50
|
+
validationType: "enum",
|
|
51
|
+
allowedValues: ["contextual", "none"],
|
|
52
|
+
promptLabel: "Parent title mode",
|
|
53
|
+
promptHint: "Whether list pages should show a parent-aware heading: contextual | none."
|
|
54
|
+
},
|
|
46
55
|
force: {
|
|
47
56
|
required: false,
|
|
48
57
|
inputType: "flag",
|
|
@@ -108,6 +117,7 @@ export default Object.freeze({
|
|
|
108
117
|
"operations",
|
|
109
118
|
"display-fields",
|
|
110
119
|
"id-param",
|
|
120
|
+
"parent-title",
|
|
111
121
|
"link-placement",
|
|
112
122
|
"link-component-token",
|
|
113
123
|
"namespace",
|
|
@@ -130,7 +140,8 @@ export default Object.freeze({
|
|
|
130
140
|
lines: [
|
|
131
141
|
"npx jskit generate crud-ui-generator crud \\",
|
|
132
142
|
" admin/catalog/index/products \\",
|
|
133
|
-
" --resource-file packages/products/src/shared/productResource.js"
|
|
143
|
+
" --resource-file packages/products/src/shared/productResource.js \\",
|
|
144
|
+
" --parent-title contextual"
|
|
134
145
|
]
|
|
135
146
|
},
|
|
136
147
|
{
|
|
@@ -141,6 +152,7 @@ export default Object.freeze({
|
|
|
141
152
|
" --resource-file packages/pets/src/shared/petResource.js \\",
|
|
142
153
|
" --id-param petId \\",
|
|
143
154
|
" --display-fields name,breedId,sex \\",
|
|
155
|
+
" --parent-title none \\",
|
|
144
156
|
" --force"
|
|
145
157
|
]
|
|
146
158
|
}
|
|
@@ -168,7 +180,7 @@ export default Object.freeze({
|
|
|
168
180
|
mutations: {
|
|
169
181
|
dependencies: {
|
|
170
182
|
runtime: {
|
|
171
|
-
"@jskit-ai/users-web": "0.1.
|
|
183
|
+
"@jskit-ai/users-web": "0.1.74"
|
|
172
184
|
},
|
|
173
185
|
dev: {}
|
|
174
186
|
},
|
|
@@ -353,7 +365,7 @@ export default Object.freeze({
|
|
|
353
365
|
position: "bottom",
|
|
354
366
|
skipIfContains: "__JSKIT_UI_MENU_MARKER__",
|
|
355
367
|
value:
|
|
356
|
-
"\n// __JSKIT_UI_MENU_MARKER__\n{\n addPlacement({\n id: \"__JSKIT_UI_MENU_PLACEMENT_ID__\",\n target: \"__JSKIT_UI_MENU_PLACEMENT_TARGET__\",\n surfaces: [\"__JSKIT_UI_SURFACE_ID__\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"__JSKIT_UI_MENU_LABEL__\",\n surface: \"__JSKIT_UI_SURFACE_ID__\",\n scopedSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n unscopedSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n__JSKIT_UI_MENU_WHEN_LINE__ });\n}\n",
|
|
368
|
+
"\n// __JSKIT_UI_MENU_MARKER__\n{\n addPlacement({\n id: \"__JSKIT_UI_MENU_PLACEMENT_ID__\",\n target: \"__JSKIT_UI_MENU_PLACEMENT_TARGET__\",\n surfaces: [\"__JSKIT_UI_SURFACE_ID__\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"__JSKIT_UI_MENU_LABEL__\",\n icon: \"__JSKIT_UI_MENU_ICON__\",\n surface: \"__JSKIT_UI_SURFACE_ID__\",\n scopedSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n unscopedSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n__JSKIT_UI_MENU_WHEN_LINE__ });\n}\n",
|
|
357
369
|
reason: "Append generated CRUD list-page placement.",
|
|
358
370
|
category: "crud-ui-generator",
|
|
359
371
|
id: "crud-ui-placement-menu",
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/crud-ui-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@jskit-ai/crud-core": "0.1.
|
|
10
|
-
"@jskit-ai/kernel": "0.1.
|
|
11
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
9
|
+
"@jskit-ai/crud-core": "0.1.67",
|
|
10
|
+
"@jskit-ai/kernel": "0.1.59",
|
|
11
|
+
"@jskit-ai/resource-crud-core": "0.1.4"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
"./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import descriptor from "../../package.descriptor.mjs";
|
|
33
33
|
|
|
34
34
|
const DEFAULT_ALLOWED_OPERATIONS = Object.freeze(["list", "view", "new", "edit"]);
|
|
35
|
+
const DEFAULT_ALLOWED_PARENT_TITLE_VALUES = Object.freeze(["contextual", "none"]);
|
|
35
36
|
function resolveAllowedValues(schema = {}, fallbackValues = []) {
|
|
36
37
|
const resolvedValues = [];
|
|
37
38
|
const seen = new Set();
|
|
@@ -56,9 +57,18 @@ function resolveAllowedValues(schema = {}, fallbackValues = []) {
|
|
|
56
57
|
const OPERATION_VALUES = resolveAllowedValues(descriptor?.options?.operations, DEFAULT_ALLOWED_OPERATIONS);
|
|
57
58
|
const ALLOWED_OPERATIONS = new Set(OPERATION_VALUES);
|
|
58
59
|
const DEFAULT_OPERATIONS = normalizeText(descriptor?.options?.operations?.defaultValue) || OPERATION_VALUES.join(",");
|
|
60
|
+
const PARENT_TITLE_VALUES = resolveAllowedValues(
|
|
61
|
+
descriptor?.options?.["parent-title"],
|
|
62
|
+
DEFAULT_ALLOWED_PARENT_TITLE_VALUES
|
|
63
|
+
);
|
|
64
|
+
const ALLOWED_PARENT_TITLE_VALUES = new Set(PARENT_TITLE_VALUES);
|
|
65
|
+
const DEFAULT_PARENT_TITLE_MODE = normalizeText(descriptor?.options?.["parent-title"]?.defaultValue).toLowerCase()
|
|
66
|
+
|| PARENT_TITLE_VALUES[0]
|
|
67
|
+
|| "contextual";
|
|
59
68
|
const DEFAULT_LIST_HIDDEN_FIELD_KEYS = new Set(["createdAt", "updatedAt"]);
|
|
60
69
|
const DEFAULT_FORM_COMPONENT_FILE = "CrudAddEditForm.vue";
|
|
61
70
|
const DEFAULT_FORM_FIELDS_FILE = "CrudAddEditFormFields.js";
|
|
71
|
+
const DEFAULT_GENERATED_LINK_ICON = "mdi-view-list-outline";
|
|
62
72
|
|
|
63
73
|
function splitTextIntoWords(value = "") {
|
|
64
74
|
const normalized = String(value || "")
|
|
@@ -221,6 +231,17 @@ function parseDisplayFieldsOption(options) {
|
|
|
221
231
|
return Object.freeze(unique);
|
|
222
232
|
}
|
|
223
233
|
|
|
234
|
+
function parseParentTitleOption(options) {
|
|
235
|
+
const parentTitleMode = normalizeText(options?.["parent-title"]).toLowerCase() || DEFAULT_PARENT_TITLE_MODE;
|
|
236
|
+
if (!ALLOWED_PARENT_TITLE_VALUES.has(parentTitleMode)) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`crud-ui-generator option "parent-title" supports only: ${PARENT_TITLE_VALUES.join(", ")}.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return parentTitleMode;
|
|
243
|
+
}
|
|
244
|
+
|
|
224
245
|
function validateDisplayFieldsForOperation(selectedFieldKeys, fields, operationName) {
|
|
225
246
|
const selectedFields = Array.isArray(selectedFieldKeys) ? selectedFieldKeys : [];
|
|
226
247
|
if (selectedFields.length < 1) {
|
|
@@ -460,11 +481,52 @@ function resolveCrudRelativePath(namespace = "") {
|
|
|
460
481
|
})}`;
|
|
461
482
|
}
|
|
462
483
|
|
|
484
|
+
function buildListParentTitleImportLine(parentTitleMode = "contextual") {
|
|
485
|
+
if (parentTitleMode !== "contextual") {
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return 'import { useCrudListParentTitle } from "@jskit-ai/users-web/client/composables/useCrudListParentTitle";';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function buildListHeadingTitleSetup({
|
|
493
|
+
parentTitleMode = "contextual",
|
|
494
|
+
resourceNamespace = "",
|
|
495
|
+
routeTitle = "Records"
|
|
496
|
+
} = {}) {
|
|
497
|
+
const normalizedRouteTitle = normalizeText(routeTitle) || "Records";
|
|
498
|
+
const routeTitleLiteral = JSON.stringify(normalizedRouteTitle);
|
|
499
|
+
if (parentTitleMode !== "contextual") {
|
|
500
|
+
return `const listHeadingTitle = computed(() => ${routeTitleLiteral});`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return `const parentTitle = useCrudListParentTitle({
|
|
504
|
+
listRuntime: records,
|
|
505
|
+
resource: uiResource,
|
|
506
|
+
adapter: UI_OPERATION_ADAPTER || undefined,
|
|
507
|
+
recordIdParam: UI_RECORD_ID_PARAM,
|
|
508
|
+
queryKeyPrefix: ["ui-generator", "${resourceNamespace}", "list", "parent-title"],
|
|
509
|
+
placementSource: "ui-generator.${resourceNamespace}.list.parent-title",
|
|
510
|
+
fallbackLoadError: "Unable to load parent record.",
|
|
511
|
+
notFoundMessage: "Parent record not found."
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const listHeadingTitle = computed(() => {
|
|
515
|
+
const resolvedParentTitle = String(parentTitle.title || "").trim();
|
|
516
|
+
if (!resolvedParentTitle) {
|
|
517
|
+
return ${routeTitleLiteral};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return ${JSON.stringify(`${normalizedRouteTitle} for `)} + resolvedParentTitle;
|
|
521
|
+
});`;
|
|
522
|
+
}
|
|
523
|
+
|
|
463
524
|
async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
464
525
|
const targetRoot = requireTargetRootOption(options);
|
|
465
526
|
const listTargetFile = resolveListTargetFile(targetRoot);
|
|
466
527
|
const selectedOperations = parseOperationsOption(options);
|
|
467
528
|
const selectedDisplayFields = parseDisplayFieldsOption(options);
|
|
529
|
+
const parentTitleMode = parseParentTitleOption(options);
|
|
468
530
|
const pageTarget = await resolvePageTargetDetails({
|
|
469
531
|
appRoot,
|
|
470
532
|
targetFile: listTargetFile,
|
|
@@ -606,6 +668,13 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
606
668
|
__JSKIT_UI_RESOURCE_SINGULAR_TITLE__: resourceLabels.singularTitle,
|
|
607
669
|
__JSKIT_UI_RESOURCE_PLURAL_TITLE__: resourceLabels.pluralTitle,
|
|
608
670
|
__JSKIT_UI_ROUTE_TITLE__: pageTarget.defaultName,
|
|
671
|
+
__JSKIT_UI_PARENT_TITLE_MODE__: parentTitleMode,
|
|
672
|
+
__JSKIT_UI_LIST_PARENT_TITLE_IMPORT_LINE__: buildListParentTitleImportLine(parentTitleMode),
|
|
673
|
+
__JSKIT_UI_LIST_HEADING_TITLE_SETUP__: buildListHeadingTitleSetup({
|
|
674
|
+
parentTitleMode,
|
|
675
|
+
resourceNamespace,
|
|
676
|
+
routeTitle: pageTarget.defaultName
|
|
677
|
+
}),
|
|
609
678
|
__JSKIT_UI_FORM_COMPONENT_FILE__: DEFAULT_FORM_COMPONENT_FILE,
|
|
610
679
|
__JSKIT_UI_FORM_FIELDS_FILE__: DEFAULT_FORM_FIELDS_FILE,
|
|
611
680
|
__JSKIT_UI_SURFACE_ID__: pageTarget.surfaceId,
|
|
@@ -659,6 +728,7 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
659
728
|
__JSKIT_UI_MENU_PLACEMENT_ID__: String(pageLinkTarget?.pageTarget?.placementId || ""),
|
|
660
729
|
__JSKIT_UI_MENU_PLACEMENT_TARGET__: String(pageLinkTarget?.placementTarget?.id || ""),
|
|
661
730
|
__JSKIT_UI_MENU_COMPONENT_TOKEN__: String(pageLinkTarget?.componentToken || ""),
|
|
731
|
+
__JSKIT_UI_MENU_ICON__: DEFAULT_GENERATED_LINK_ICON,
|
|
662
732
|
__JSKIT_UI_MENU_WORKSPACE_SUFFIX__: String(pageLinkTarget?.pageTarget?.routeUrlSuffix || ""),
|
|
663
733
|
__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__: String(pageLinkTarget?.pageTarget?.routeUrlSuffix || ""),
|
|
664
734
|
__JSKIT_UI_MENU_WHEN_LINE__: String(pageLinkTarget?.whenLine || ""),
|
|
@@ -63,12 +63,6 @@ const UI_VIEW_URL = __JSKIT_UI_EDIT_PAGE_VIEW_URL__;
|
|
|
63
63
|
const UI_CANCEL_URL = UI_VIEW_URL || UI_LIST_URL;
|
|
64
64
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
65
65
|
const UI_EDIT_FORM_FIELDS = [];
|
|
66
|
-
const UI_EDIT_TRANSPORT = Object.freeze({
|
|
67
|
-
kind: "jsonapi-resource",
|
|
68
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
69
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
70
|
-
responseKind: "record"
|
|
71
|
-
});
|
|
72
66
|
|
|
73
67
|
// @jskit-contract crud.ui.form-fields.__JSKIT_UI_RESOURCE_NAMESPACE__.edit.v1
|
|
74
68
|
void UI_EDIT_FORM_FIELDS;
|
|
@@ -105,7 +99,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
105
99
|
routeRecordId.value
|
|
106
100
|
],
|
|
107
101
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.edit",
|
|
108
|
-
transport: UI_EDIT_TRANSPORT,
|
|
109
102
|
writeMethod: "PATCH",
|
|
110
103
|
fallbackLoadError: "Unable to load record.",
|
|
111
104
|
fallbackSaveError: "Unable to save record.",
|
|
@@ -26,12 +26,6 @@ const UI_LIST_URL = __JSKIT_UI_EDIT_PAGE_LIST_URL__;
|
|
|
26
26
|
const UI_VIEW_URL = __JSKIT_UI_EDIT_PAGE_VIEW_URL__;
|
|
27
27
|
const UI_CANCEL_URL = UI_VIEW_URL || UI_LIST_URL;
|
|
28
28
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
29
|
-
const UI_EDIT_TRANSPORT = Object.freeze({
|
|
30
|
-
kind: "jsonapi-resource",
|
|
31
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
32
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
33
|
-
responseKind: "record"
|
|
34
|
-
});
|
|
35
29
|
const route = useRoute();
|
|
36
30
|
|
|
37
31
|
// jskit:crud-ui-fields-target ../_components/__JSKIT_UI_FORM_COMPONENT_FILE__
|
|
@@ -64,7 +58,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
64
58
|
routeRecordId.value
|
|
65
59
|
],
|
|
66
60
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.edit",
|
|
67
|
-
transport: UI_EDIT_TRANSPORT,
|
|
68
61
|
writeMethod: "PATCH",
|
|
69
62
|
fallbackLoadError: "Unable to load record.",
|
|
70
63
|
fallbackSaveError: "Unable to save record.",
|
|
@@ -89,7 +89,7 @@ __JSKIT_UI_LIST_ROW_COLUMNS__
|
|
|
89
89
|
|
|
90
90
|
<script setup>
|
|
91
91
|
import { computed } from "vue";
|
|
92
|
-
|
|
92
|
+
__JSKIT_UI_LIST_PARENT_TITLE_IMPORT_LINE__
|
|
93
93
|
import { useCrudList } from "@jskit-ai/users-web/client/composables/useCrudList";
|
|
94
94
|
import { resource as uiResource } from "__JSKIT_UI_RESOURCE_IMPORT_PATH__";
|
|
95
95
|
|
|
@@ -101,11 +101,6 @@ const UI_EDIT_URL = __JSKIT_UI_LIST_PAGE_EDIT_URL__;
|
|
|
101
101
|
const UI_NEW_URL = __JSKIT_UI_LIST_PAGE_NEW_URL__;
|
|
102
102
|
const UI_RECORD_CHANGED_EVENTS = __JSKIT_UI_LIST_REALTIME_EVENTS__;
|
|
103
103
|
const UI_ROUTE_QUERY_BLACKLIST = Object.freeze(["include", "cursor", "limit"]);
|
|
104
|
-
const UI_LIST_TRANSPORT = Object.freeze({
|
|
105
|
-
kind: "jsonapi-resource",
|
|
106
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
107
|
-
responseKind: "collection"
|
|
108
|
-
});
|
|
109
104
|
|
|
110
105
|
const records = useCrudList({
|
|
111
106
|
adapter: UI_OPERATION_ADAPTER || undefined,
|
|
@@ -131,7 +126,6 @@ const records = useCrudList({
|
|
|
131
126
|
},
|
|
132
127
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.list",
|
|
133
128
|
fallbackLoadError: "Unable to load records.",
|
|
134
|
-
transport: UI_LIST_TRANSPORT,
|
|
135
129
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
136
130
|
recordIdSelector: (item = {}) => __JSKIT_UI_LIST_RECORD_ID_EXPR__,
|
|
137
131
|
viewUrlTemplate: UI_VIEW_URL,
|
|
@@ -143,25 +137,7 @@ const records = useCrudList({
|
|
|
143
137
|
: null
|
|
144
138
|
});
|
|
145
139
|
|
|
146
|
-
|
|
147
|
-
listRuntime: records,
|
|
148
|
-
resource: uiResource,
|
|
149
|
-
adapter: UI_OPERATION_ADAPTER || undefined,
|
|
150
|
-
recordIdParam: UI_RECORD_ID_PARAM,
|
|
151
|
-
queryKeyPrefix: ["ui-generator", "__JSKIT_UI_RESOURCE_NAMESPACE__", "list", "parent-title"],
|
|
152
|
-
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.list.parent-title",
|
|
153
|
-
fallbackLoadError: "Unable to load parent record.",
|
|
154
|
-
notFoundMessage: "Parent record not found."
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const listHeadingTitle = computed(() => {
|
|
158
|
-
const resolvedParentTitle = String(parentTitle.title || "").trim();
|
|
159
|
-
if (!resolvedParentTitle) {
|
|
160
|
-
return "__JSKIT_UI_ROUTE_TITLE__";
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return `__JSKIT_UI_ROUTE_TITLE__ for ${resolvedParentTitle}`;
|
|
164
|
-
});
|
|
140
|
+
__JSKIT_UI_LIST_HEADING_TITLE_SETUP__
|
|
165
141
|
</script>
|
|
166
142
|
|
|
167
143
|
<style scoped>
|
|
@@ -50,12 +50,6 @@ const UI_LIST_URL = __JSKIT_UI_NEW_PAGE_LIST_URL__;
|
|
|
50
50
|
const UI_VIEW_URL = __JSKIT_UI_NEW_PAGE_VIEW_URL__;
|
|
51
51
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
52
52
|
const UI_CREATE_FORM_FIELDS = [];
|
|
53
|
-
const UI_CREATE_TRANSPORT = Object.freeze({
|
|
54
|
-
kind: "jsonapi-resource",
|
|
55
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
56
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
57
|
-
responseKind: "record"
|
|
58
|
-
});
|
|
59
53
|
|
|
60
54
|
// @jskit-contract crud.ui.form-fields.__JSKIT_UI_RESOURCE_NAMESPACE__.new.v1
|
|
61
55
|
void UI_CREATE_FORM_FIELDS;
|
|
@@ -81,7 +75,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
81
75
|
],
|
|
82
76
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.new",
|
|
83
77
|
readEnabled: false,
|
|
84
|
-
transport: UI_CREATE_TRANSPORT,
|
|
85
78
|
writeMethod: "POST",
|
|
86
79
|
fallbackSaveError: "Unable to save record.",
|
|
87
80
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
@@ -23,12 +23,6 @@ const UI_LIST_URL = __JSKIT_UI_NEW_PAGE_LIST_URL__;
|
|
|
23
23
|
const UI_VIEW_URL = __JSKIT_UI_NEW_PAGE_VIEW_URL__;
|
|
24
24
|
const UI_CANCEL_URL = UI_LIST_URL;
|
|
25
25
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
26
|
-
const UI_CREATE_TRANSPORT = Object.freeze({
|
|
27
|
-
kind: "jsonapi-resource",
|
|
28
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
29
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
30
|
-
responseKind: "record"
|
|
31
|
-
});
|
|
32
26
|
|
|
33
27
|
// jskit:crud-ui-fields-target ./_components/__JSKIT_UI_FORM_COMPONENT_FILE__
|
|
34
28
|
// jskit:crud-ui-form-fields-target ./_components/__JSKIT_UI_FORM_FIELDS_FILE__
|
|
@@ -51,7 +45,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
51
45
|
],
|
|
52
46
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.new",
|
|
53
47
|
readEnabled: false,
|
|
54
|
-
transport: UI_CREATE_TRANSPORT,
|
|
55
48
|
writeMethod: "POST",
|
|
56
49
|
fallbackSaveError: "Unable to save record.",
|
|
57
50
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
@@ -57,6 +57,7 @@ __JSKIT_UI_VIEW_COLUMNS__
|
|
|
57
57
|
|
|
58
58
|
<script setup>
|
|
59
59
|
import { useCrudView } from "@jskit-ai/users-web/client/composables/useCrudView";
|
|
60
|
+
import { resource as uiResource } from "__JSKIT_UI_RESOURCE_IMPORT_PATH__";
|
|
60
61
|
|
|
61
62
|
const UI_OPERATION_ADAPTER = null;
|
|
62
63
|
const UI_RECORD_ID_PARAM = "__JSKIT_UI_RECORD_ID_PARAM__";
|
|
@@ -66,14 +67,10 @@ const UI_LIST_URL = __JSKIT_UI_VIEW_PAGE_LIST_URL__;
|
|
|
66
67
|
const UI_EDIT_URL = __JSKIT_UI_VIEW_PAGE_EDIT_URL__;
|
|
67
68
|
const UI_VIEW_TITLE_FALLBACK_FIELD_KEY = __JSKIT_UI_VIEW_TITLE_FALLBACK_FIELD_KEY__;
|
|
68
69
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
69
|
-
const UI_VIEW_TRANSPORT = Object.freeze({
|
|
70
|
-
kind: "jsonapi-resource",
|
|
71
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
72
|
-
responseKind: "record"
|
|
73
|
-
});
|
|
74
70
|
|
|
75
71
|
const view = useCrudView({
|
|
76
72
|
adapter: UI_OPERATION_ADAPTER || undefined,
|
|
73
|
+
resource: uiResource,
|
|
77
74
|
apiUrlTemplate: UI_VIEW_API_URL,
|
|
78
75
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
79
76
|
includeRecordIdInQueryKey: true,
|
|
@@ -85,7 +82,6 @@ const view = useCrudView({
|
|
|
85
82
|
String(workspaceSlug || "")
|
|
86
83
|
],
|
|
87
84
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.view",
|
|
88
|
-
transport: UI_VIEW_TRANSPORT,
|
|
89
85
|
fallbackLoadError: "Unable to load record.",
|
|
90
86
|
notFoundMessage: "Record not found.",
|
|
91
87
|
listUrlTemplate: UI_LIST_URL,
|
|
@@ -320,6 +320,9 @@ test("buildUiTemplateContext derives CRUD placeholders from the explicit target-
|
|
|
320
320
|
assert.equal(context.__JSKIT_UI_RESOURCE_SINGULAR_TITLE__, "Customer");
|
|
321
321
|
assert.equal(context.__JSKIT_UI_RESOURCE_PLURAL_TITLE__, "Customers");
|
|
322
322
|
assert.equal(context.__JSKIT_UI_ROUTE_TITLE__, "Customers");
|
|
323
|
+
assert.equal(context.__JSKIT_UI_PARENT_TITLE_MODE__, "contextual");
|
|
324
|
+
assert.match(context.__JSKIT_UI_LIST_PARENT_TITLE_IMPORT_LINE__, /useCrudListParentTitle/);
|
|
325
|
+
assert.match(context.__JSKIT_UI_LIST_HEADING_TITLE_SETUP__, /Customers for /);
|
|
323
326
|
assert.equal(context.__JSKIT_UI_FORM_COMPONENT_FILE__, "CrudAddEditForm.vue");
|
|
324
327
|
assert.equal(context.__JSKIT_UI_FORM_FIELDS_FILE__, "CrudAddEditFormFields.js");
|
|
325
328
|
assert.equal(context.__JSKIT_UI_SURFACE_ID__, "admin");
|
|
@@ -350,6 +353,24 @@ test("buildUiTemplateContext derives CRUD placeholders from the explicit target-
|
|
|
350
353
|
});
|
|
351
354
|
});
|
|
352
355
|
|
|
356
|
+
test("buildUiTemplateContext can suppress parent-title heading generation with parent-title none", async () => {
|
|
357
|
+
await withTempApp(async (appRoot) => {
|
|
358
|
+
await writeResource(appRoot, RESOURCE_FILE, FULL_RESOURCE_SOURCE);
|
|
359
|
+
|
|
360
|
+
const context = await buildUiTemplateContext({
|
|
361
|
+
appRoot,
|
|
362
|
+
options: createOptions({
|
|
363
|
+
"parent-title": "none"
|
|
364
|
+
})
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
assert.equal(context.__JSKIT_UI_PARENT_TITLE_MODE__, "none");
|
|
368
|
+
assert.equal(context.__JSKIT_UI_LIST_PARENT_TITLE_IMPORT_LINE__, "");
|
|
369
|
+
assert.doesNotMatch(context.__JSKIT_UI_LIST_HEADING_TITLE_SETUP__, /useCrudListParentTitle/);
|
|
370
|
+
assert.match(context.__JSKIT_UI_LIST_HEADING_TITLE_SETUP__, /computed\(\(\) => "Customers"\)/);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
353
374
|
test("buildUiTemplateContext keeps non-nullable booleans as switches", async () => {
|
|
354
375
|
await withTempApp(async (appRoot) => {
|
|
355
376
|
await writeResource(appRoot, RESOURCE_FILE, FULL_RESOURCE_SOURCE);
|
|
@@ -628,7 +649,8 @@ test("buildUiTemplateContext infers tab placement and relative link-to from the
|
|
|
628
649
|
});
|
|
629
650
|
|
|
630
651
|
assert.equal(context.__JSKIT_UI_MENU_PLACEMENT_TARGET__, "catalog:sub-pages");
|
|
631
|
-
assert.equal(context.__JSKIT_UI_MENU_COMPONENT_TOKEN__, "local.main.ui.
|
|
652
|
+
assert.equal(context.__JSKIT_UI_MENU_COMPONENT_TOKEN__, "local.main.ui.surface-aware-menu-link-item");
|
|
653
|
+
assert.equal(context.__JSKIT_UI_MENU_ICON__, "mdi-view-list-outline");
|
|
632
654
|
assert.equal(context.__JSKIT_UI_MENU_TO_PROP_LINE__, " to: \"./products\",\n");
|
|
633
655
|
assert.equal(context.__JSKIT_UI_MENU_WORKSPACE_SUFFIX__, "/catalog/products");
|
|
634
656
|
});
|
|
@@ -714,7 +736,24 @@ test("buildUiTemplateContext validates operations against the supported CRUD set
|
|
|
714
736
|
});
|
|
715
737
|
});
|
|
716
738
|
|
|
717
|
-
test("
|
|
739
|
+
test("buildUiTemplateContext validates parent-title against the supported modes", async () => {
|
|
740
|
+
await withTempApp(async (appRoot) => {
|
|
741
|
+
await writeResource(appRoot, RESOURCE_FILE, FULL_RESOURCE_SOURCE);
|
|
742
|
+
|
|
743
|
+
await assert.rejects(
|
|
744
|
+
() =>
|
|
745
|
+
buildUiTemplateContext({
|
|
746
|
+
appRoot,
|
|
747
|
+
options: createOptions({
|
|
748
|
+
"parent-title": "always"
|
|
749
|
+
})
|
|
750
|
+
}),
|
|
751
|
+
/parent-title" supports only: contextual, none/
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
test("crud ui templates derive JSON:API transport from the shared CRUD resource", async () => {
|
|
718
757
|
const testDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
719
758
|
const templateRoot = path.resolve(testDirectory, "..", "templates", "src", "pages", "admin", "ui-generator");
|
|
720
759
|
|
|
@@ -725,25 +764,28 @@ test("crud ui templates opt into shared JSON:API client transport", async () =>
|
|
|
725
764
|
const newWrapperTemplateSource = await readFile(path.join(templateRoot, "NewWrapperElement.vue"), "utf8");
|
|
726
765
|
const editWrapperTemplateSource = await readFile(path.join(templateRoot, "EditWrapperElement.vue"), "utf8");
|
|
727
766
|
|
|
728
|
-
assert.match(listTemplateSource, /
|
|
729
|
-
assert.
|
|
730
|
-
assert.
|
|
767
|
+
assert.match(listTemplateSource, /resource: uiResource,/);
|
|
768
|
+
assert.doesNotMatch(listTemplateSource, /const UI_LIST_TRANSPORT = Object\.freeze\(\{/);
|
|
769
|
+
assert.doesNotMatch(listTemplateSource, /transport:\s*UI_LIST_TRANSPORT,/);
|
|
731
770
|
|
|
732
|
-
assert.match(viewTemplateSource, /
|
|
733
|
-
assert.match(viewTemplateSource, /
|
|
734
|
-
assert.
|
|
771
|
+
assert.match(viewTemplateSource, /import \{ resource as uiResource \} from/);
|
|
772
|
+
assert.match(viewTemplateSource, /resource: uiResource,/);
|
|
773
|
+
assert.doesNotMatch(viewTemplateSource, /const UI_VIEW_TRANSPORT = Object\.freeze\(\{/);
|
|
774
|
+
assert.doesNotMatch(viewTemplateSource, /transport:\s*UI_VIEW_TRANSPORT,/);
|
|
735
775
|
|
|
736
|
-
assert.match(newTemplateSource, /
|
|
737
|
-
assert.
|
|
738
|
-
assert.
|
|
776
|
+
assert.match(newTemplateSource, /resource: uiResource,/);
|
|
777
|
+
assert.doesNotMatch(newTemplateSource, /const UI_CREATE_TRANSPORT = Object\.freeze\(\{/);
|
|
778
|
+
assert.doesNotMatch(newTemplateSource, /transport:\s*UI_CREATE_TRANSPORT,/);
|
|
739
779
|
|
|
740
|
-
assert.match(editTemplateSource, /
|
|
741
|
-
assert.
|
|
742
|
-
assert.
|
|
780
|
+
assert.match(editTemplateSource, /resource: uiResource,/);
|
|
781
|
+
assert.doesNotMatch(editTemplateSource, /const UI_EDIT_TRANSPORT = Object\.freeze\(\{/);
|
|
782
|
+
assert.doesNotMatch(editTemplateSource, /transport:\s*UI_EDIT_TRANSPORT,/);
|
|
743
783
|
|
|
744
|
-
assert.match(newWrapperTemplateSource, /
|
|
745
|
-
assert.
|
|
784
|
+
assert.match(newWrapperTemplateSource, /resource: uiResource,/);
|
|
785
|
+
assert.doesNotMatch(newWrapperTemplateSource, /const UI_CREATE_TRANSPORT = Object\.freeze\(\{/);
|
|
786
|
+
assert.doesNotMatch(newWrapperTemplateSource, /transport:\s*UI_CREATE_TRANSPORT,/);
|
|
746
787
|
|
|
747
|
-
assert.match(editWrapperTemplateSource, /
|
|
748
|
-
assert.
|
|
788
|
+
assert.match(editWrapperTemplateSource, /resource: uiResource,/);
|
|
789
|
+
assert.doesNotMatch(editWrapperTemplateSource, /const UI_EDIT_TRANSPORT = Object\.freeze\(\{/);
|
|
790
|
+
assert.doesNotMatch(editWrapperTemplateSource, /transport:\s*UI_EDIT_TRANSPORT,/);
|
|
749
791
|
});
|
|
@@ -12,3 +12,20 @@ test("crud-ui-generator operations option exposes structured csv-enum metadata",
|
|
|
12
12
|
assert.equal(descriptor.options?.operations?.defaultValue, "list,view,new,edit");
|
|
13
13
|
assert.equal(descriptor.metadata?.generatorSubcommands?.crud?.optionNames?.includes("operations"), true);
|
|
14
14
|
});
|
|
15
|
+
|
|
16
|
+
test("crud-ui-generator parent-title option exposes structured enum metadata", () => {
|
|
17
|
+
assert.equal(descriptor.options?.["parent-title"]?.validationType, "enum");
|
|
18
|
+
assert.deepEqual(
|
|
19
|
+
descriptor.options?.["parent-title"]?.allowedValues,
|
|
20
|
+
["contextual", "none"]
|
|
21
|
+
);
|
|
22
|
+
assert.equal(descriptor.options?.["parent-title"]?.defaultValue, "contextual");
|
|
23
|
+
assert.equal(descriptor.metadata?.generatorSubcommands?.crud?.optionNames?.includes("parent-title"), true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("crud-ui-generator placement scaffold includes an explicit stock icon prop", () => {
|
|
27
|
+
const placementMutation = descriptor?.mutations?.text?.find(
|
|
28
|
+
(entry) => String(entry?.id || "").trim() === "crud-ui-placement-menu"
|
|
29
|
+
);
|
|
30
|
+
assert.match(String(placementMutation?.value || ""), /icon: "__JSKIT_UI_MENU_ICON__"/);
|
|
31
|
+
});
|