@jskit-ai/http-web 0.1.91 → 0.1.93
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.json +6 -6
- package/patterns/crud-screen-set/PATTERN.md +11 -1
- package/src/client/bulkActions.js +13 -40
- package/src/client/components/CrudAddEditScreen.vue +1 -9
- package/src/client/components/CrudListBulkActionSurface.vue +2 -5
- package/src/client/components/CrudListFilterControls.vue +175 -0
- package/src/client/components/CrudListFilterSurface.vue +5 -248
- package/src/client/components/CrudListScreen.vue +1 -28
- package/src/client/components/CrudViewScreen.vue +1 -9
- package/src/client/composables/crud/crudJsonApiTransportSupport.js +20 -42
- package/src/client/composables/crud/crudLookupFieldLabelSupport.js +2 -13
- package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -27
- package/src/client/composables/crud/crudSchemaFormHelpers.js +1 -7
- package/src/client/composables/internal/crudListFilterLookupSupport.js +2 -1
- package/src/client/composables/internal/useOperationScope.js +5 -7
- package/src/client/composables/records/useAddEdit.js +12 -11
- package/src/client/composables/records/useCrudAddEdit.js +74 -22
- package/src/client/composables/records/useCrudList.js +0 -6
- package/src/client/composables/records/useCrudView.js +0 -6
- package/src/client/composables/records/useList.js +2 -8
- package/src/client/composables/records/useView.js +3 -8
- package/src/client/composables/runtime/addEditUiRuntime.js +12 -60
- package/src/client/composables/runtime/listUiRuntime.js +11 -36
- package/src/client/composables/runtime/useFieldErrorBag.js +3 -3
- package/src/client/composables/runtime/viewUiRuntime.js +14 -62
- package/src/client/composables/support/errorMessageHelpers.js +6 -48
- package/src/client/composables/support/resourceLoadStateHelpers.js +67 -156
- package/src/client/composables/support/routeTemplateHelpers.js +57 -0
- package/src/client/composables/useAccess.js +12 -17
- package/src/client/composables/useCommand.js +0 -1
- package/src/client/composables/useCrudDeleteAction.js +7 -5
- package/src/client/composables/useCrudListBulkActions.js +9 -21
- package/src/client/composables/useCrudListFilterLookups.js +1 -3
- package/src/client/composables/useCrudListParentTitle.js +0 -2
- package/src/client/composables/useCrudListRowActions.js +8 -24
- package/src/client/composables/useCrudListScreen.js +4 -5
- package/src/client/composables/useCrudViewScreen.js +2 -3
- package/src/client/composables/useRealtimeQueryInvalidation.js +3 -10
- package/src/client/composables/useScopeRuntime.js +2 -5
- package/src/client/rowActions.js +13 -40
- package/src/client/support/crudListActionSupport.js +54 -0
- package/test/crudAddEditActions.test.js +59 -19
- package/test/crudListFilterSurface.test.js +7 -4
- package/test/listUiRuntime.test.js +3 -1
- package/test/requestTransportOptions.test.js +24 -0
- package/test/resourceLoadStateHelpers.test.js +32 -0
- package/test/useAccess.test.js +62 -0
- package/test/useCrudDeleteAction.test.js +57 -2
- package/test/useCrudListScreen.test.js +45 -0
- package/test/useListRouteInitialization.test.js +5 -44
- package/src/client/composables/runtime/operationAdapters.js +0 -34
|
@@ -93,14 +93,6 @@ const editLocation = computed(() => unref(props.screen?.editLocation) || null);
|
|
|
93
93
|
</template>
|
|
94
94
|
|
|
95
95
|
<style scoped>
|
|
96
|
-
.crud-screen {
|
|
97
|
-
--crud-screen-state-padding: 2.5rem 1.25rem;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.crud-screen--operator {
|
|
101
|
-
--crud-screen-state-padding: 2rem 1rem;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
96
|
.crud-view-actions {
|
|
105
97
|
display: flex;
|
|
106
98
|
flex-wrap: wrap;
|
|
@@ -115,7 +107,7 @@ const editLocation = computed(() => unref(props.screen?.editLocation) || null);
|
|
|
115
107
|
.crud-view-state {
|
|
116
108
|
margin-inline: auto;
|
|
117
109
|
max-width: 30rem;
|
|
118
|
-
padding:
|
|
110
|
+
padding: 2rem 1rem;
|
|
119
111
|
text-align: center;
|
|
120
112
|
}
|
|
121
113
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import { normalizeCrudLookupNamespace } from "@jskit-ai/resource-crud-core/shared/crudLookup";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const CRUD_RESPONSE_KIND_BY_MODE = Object.freeze({
|
|
5
|
+
list: "collection",
|
|
6
|
+
view: "record",
|
|
7
|
+
delete: "record",
|
|
8
|
+
"add-edit": "record"
|
|
9
|
+
});
|
|
7
10
|
|
|
8
11
|
function resolveSchemaFieldDefinitions(definition = null) {
|
|
9
12
|
const schema = definition?.schema;
|
|
@@ -73,41 +76,20 @@ function inferCrudJsonApiTransport(resource = null, { mode = "", operationName =
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
const normalizedMode = normalizeText(mode).toLowerCase();
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
responseKind: "collection"
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (normalizedMode === "view") {
|
|
86
|
-
return Object.freeze({
|
|
87
|
-
kind: "jsonapi-resource",
|
|
88
|
-
responseType: resourceType,
|
|
89
|
-
responseKind: "record"
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (normalizedMode === "delete") {
|
|
94
|
-
return Object.freeze({
|
|
95
|
-
kind: "jsonapi-resource",
|
|
96
|
-
responseType: resourceType,
|
|
97
|
-
responseKind: "record"
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (normalizedMode === "add-edit") {
|
|
102
|
-
return Object.freeze({
|
|
103
|
-
kind: "jsonapi-resource",
|
|
104
|
-
...(normalizedOperationName ? { requestType: resourceType } : {}),
|
|
105
|
-
responseType: resourceType,
|
|
106
|
-
responseKind: "record"
|
|
107
|
-
});
|
|
79
|
+
const responseKind = Object.hasOwn(CRUD_RESPONSE_KIND_BY_MODE, normalizedMode)
|
|
80
|
+
? CRUD_RESPONSE_KIND_BY_MODE[normalizedMode]
|
|
81
|
+
: "";
|
|
82
|
+
if (!responseKind) {
|
|
83
|
+
return null;
|
|
108
84
|
}
|
|
109
85
|
|
|
110
|
-
|
|
86
|
+
const hasRequestType = normalizedMode === "add-edit" && Boolean(normalizeText(operationName));
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
kind: "jsonapi-resource",
|
|
89
|
+
...(hasRequestType ? { requestType: resourceType } : {}),
|
|
90
|
+
responseType: resourceType,
|
|
91
|
+
responseKind
|
|
92
|
+
});
|
|
111
93
|
}
|
|
112
94
|
|
|
113
95
|
function resolveCrudJsonApiTransport(transport = null, resource = null, options = {}) {
|
|
@@ -126,14 +108,10 @@ function resolveCrudJsonApiTransport(transport = null, resource = null, options
|
|
|
126
108
|
const lookupContainerKey = normalizeText(resource?.contract?.lookup?.containerKey, {
|
|
127
109
|
fallback: "lookups"
|
|
128
110
|
});
|
|
129
|
-
if (Object.keys(lookupFieldMap).length < 1 && !lookupContainerKey) {
|
|
130
|
-
return baseTransport;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
111
|
return Object.freeze({
|
|
134
112
|
...baseTransport,
|
|
135
113
|
...(Object.keys(lookupFieldMap).length > 0 ? { lookupFieldMap } : {}),
|
|
136
|
-
|
|
114
|
+
lookupContainerKey
|
|
137
115
|
});
|
|
138
116
|
}
|
|
139
117
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { hasValue, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import { normalizeCrudLookupContainerKey } from "@jskit-ai/resource-crud-core/shared/crudLookup";
|
|
3
3
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
4
4
|
|
|
@@ -12,17 +12,6 @@ const LOOKUP_LABEL_COMPOSITION_CANDIDATES = Object.freeze([
|
|
|
12
12
|
]);
|
|
13
13
|
const DEFAULT_RECORD_TITLE = "-";
|
|
14
14
|
|
|
15
|
-
function hasDisplayValue(value) {
|
|
16
|
-
if (value == null) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
if (typeof value === "string") {
|
|
20
|
-
return normalizeText(value).length > 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
15
|
function resolveComposedLabel(source = {}, candidates = LOOKUP_LABEL_COMPOSITION_CANDIDATES) {
|
|
27
16
|
for (const candidate of candidates) {
|
|
28
17
|
const parts = [];
|
|
@@ -137,7 +126,7 @@ function resolveLookupFieldDisplayValue(record = {}, field = {}, relationKind =
|
|
|
137
126
|
}
|
|
138
127
|
|
|
139
128
|
const lookupValue = lookupRecord[descriptor.relation.valueKey];
|
|
140
|
-
if (
|
|
129
|
+
if (hasValue(lookupValue)) {
|
|
141
130
|
return lookupValue;
|
|
142
131
|
}
|
|
143
132
|
|
|
@@ -21,14 +21,6 @@ function normalizeQueryKeyPrefix(value) {
|
|
|
21
21
|
.filter(Boolean);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function isSameLookupValue(left, right) {
|
|
25
|
-
if (left === right) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return String(left ?? "") === String(right ?? "");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
24
|
function normalizeLookupValue(value) {
|
|
33
25
|
if (value == null) {
|
|
34
26
|
return "";
|
|
@@ -75,7 +67,6 @@ function createSelectedLookupItem(selectedValue, selectedRecord = {}, entry = {}
|
|
|
75
67
|
|
|
76
68
|
function createCrudLookupFieldRuntime({
|
|
77
69
|
formFields = [],
|
|
78
|
-
adapter = null,
|
|
79
70
|
recordIdParam = "recordId",
|
|
80
71
|
queryKeyPrefix = [],
|
|
81
72
|
placementSourcePrefix = "",
|
|
@@ -121,7 +112,6 @@ function createCrudLookupFieldRuntime({
|
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
const runtime = useList({
|
|
124
|
-
adapter: adapter || undefined,
|
|
125
115
|
...(relationSurfaceId ? { surfaceId: relationSurfaceId } : {}),
|
|
126
116
|
apiSuffix: apiPath,
|
|
127
117
|
...(transport ? { transport } : {}),
|
|
@@ -192,22 +182,7 @@ function createCrudLookupFieldRuntime({
|
|
|
192
182
|
return items;
|
|
193
183
|
}
|
|
194
184
|
|
|
195
|
-
|
|
196
|
-
return items;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const matchingItem = items.find((item) => isSameLookupValue(item?.value, selectedItem.value));
|
|
200
|
-
if (matchingItem) {
|
|
201
|
-
return [
|
|
202
|
-
{
|
|
203
|
-
...selectedItem,
|
|
204
|
-
label: String(matchingItem.label ?? selectedItem.label ?? "")
|
|
205
|
-
},
|
|
206
|
-
...items
|
|
207
|
-
];
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return [selectedItem, ...items];
|
|
185
|
+
return items.some((item) => item.value === selectedItem.value) ? items : [selectedItem, ...items];
|
|
211
186
|
}
|
|
212
187
|
|
|
213
188
|
function resolveLookupLoading(fieldKey = "") {
|
|
@@ -217,7 +192,7 @@ function createCrudLookupFieldRuntime({
|
|
|
217
192
|
return false;
|
|
218
193
|
}
|
|
219
194
|
|
|
220
|
-
return Boolean(entry.runtime.isInitialLoading || entry.runtime.isFetching
|
|
195
|
+
return Boolean(entry.runtime.isInitialLoading || entry.runtime.isFetching);
|
|
221
196
|
}
|
|
222
197
|
|
|
223
198
|
function resolveLookupSearch(fieldKey = "") {
|
|
@@ -443,13 +443,7 @@ function resolveCrudFieldErrors(fieldErrors = {}, fieldKey = "") {
|
|
|
443
443
|
return [];
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
const
|
|
447
|
-
const message = String(source[key] || "").trim();
|
|
448
|
-
|
|
449
|
-
if (!message) {
|
|
450
|
-
return resolveStableFieldErrorList(key, "");
|
|
451
|
-
}
|
|
452
|
-
|
|
446
|
+
const message = String(asPlainObject(fieldErrors)[key] || "").trim();
|
|
453
447
|
return resolveStableFieldErrorList(key, message);
|
|
454
448
|
}
|
|
455
449
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeText, normalizeUniqueTextList } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
import { CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY } from "@jskit-ai/resource-crud-core/shared/crudListFilters";
|
|
2
3
|
import { resolveLookupItemLabel } from "../crud/crudLookupFieldLabelSupport.js";
|
|
3
4
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
4
5
|
|
|
@@ -58,7 +59,7 @@ function normalizeLookupRequestQueryParamsMap(value = {}) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function resolveLookupSelectedValues(filter = {}, rawValue = undefined) {
|
|
61
|
-
if (filter?.type ===
|
|
62
|
+
if (filter?.type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY) {
|
|
62
63
|
return normalizeUniqueTextList(rawValue, {
|
|
63
64
|
acceptSingle: true
|
|
64
65
|
});
|
|
@@ -36,7 +36,6 @@ function useOperationScope({
|
|
|
36
36
|
ownershipFilter = ROUTE_VISIBILITY_WORKSPACE,
|
|
37
37
|
surfaceId = "",
|
|
38
38
|
access = "auto",
|
|
39
|
-
placementSource = "http-web.operation",
|
|
40
39
|
apiSuffix = "",
|
|
41
40
|
model,
|
|
42
41
|
readEnabled = true,
|
|
@@ -49,8 +48,7 @@ function useOperationScope({
|
|
|
49
48
|
ownershipFilter,
|
|
50
49
|
surfaceId,
|
|
51
50
|
accessMode: access,
|
|
52
|
-
hasPermissionRequirements: hasAnyPermissions(normalizedPermissionSets)
|
|
53
|
-
placementSource
|
|
51
|
+
hasPermissionRequirements: hasAnyPermissions(normalizedPermissionSets)
|
|
54
52
|
});
|
|
55
53
|
const normalizedOwnershipFilter = scopeRuntime.normalizedOwnershipFilter;
|
|
56
54
|
const routeContext = scopeRuntime.routeContext;
|
|
@@ -106,9 +104,9 @@ function useOperationScope({
|
|
|
106
104
|
return scopeRuntime.routeScopeError.value;
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
return
|
|
107
|
+
const permissionsError = String(accessRuntime.permissionsUnavailableError.value || "").trim();
|
|
108
|
+
if (permissionsError) {
|
|
109
|
+
return permissionsError;
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
if (baseError === undefined || baseError === null || baseError === "") {
|
|
@@ -120,7 +118,7 @@ function useOperationScope({
|
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
function isLoading(baseLoading = false) {
|
|
123
|
-
return computed(() => Boolean(unref(baseLoading)) || accessRuntime.
|
|
121
|
+
return computed(() => Boolean(unref(baseLoading)) || accessRuntime.awaitingPlacementPermissions.value);
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
return Object.freeze({
|
|
@@ -3,7 +3,7 @@ import { useRoute } from "vue-router";
|
|
|
3
3
|
import { ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/kernel/shared/support/visibility";
|
|
4
4
|
import { useAddEditCore } from "../runtime/useAddEditCore.js";
|
|
5
5
|
import { useEndpointResource } from "../runtime/useEndpointResource.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useOperationScope } from "../internal/useOperationScope.js";
|
|
7
7
|
import { createAddEditUiRuntime } from "../runtime/addEditUiRuntime.js";
|
|
8
8
|
import { useUiFeedback } from "../runtime/useUiFeedback.js";
|
|
9
9
|
import { useFieldErrorBag } from "../runtime/useFieldErrorBag.js";
|
|
@@ -35,7 +35,7 @@ function useAddEdit({
|
|
|
35
35
|
requestRecoveryLabel = "Resource",
|
|
36
36
|
placementSource = "http-web.add-edit",
|
|
37
37
|
fallbackLoadError = "Unable to load resource.",
|
|
38
|
-
fallbackSaveError = "
|
|
38
|
+
fallbackSaveError = "",
|
|
39
39
|
fieldErrorKeys = [],
|
|
40
40
|
clearOnRouteChange = true,
|
|
41
41
|
model,
|
|
@@ -54,8 +54,7 @@ function useAddEdit({
|
|
|
54
54
|
listUrlTemplate = "",
|
|
55
55
|
saveRecordIdSelector = null,
|
|
56
56
|
messages = {},
|
|
57
|
-
realtime = null
|
|
58
|
-
adapter = null
|
|
57
|
+
realtime = null
|
|
59
58
|
} = {}) {
|
|
60
59
|
const route = useRoute();
|
|
61
60
|
const addEditUiRuntime = createAddEditUiRuntime({
|
|
@@ -71,14 +70,10 @@ function useAddEdit({
|
|
|
71
70
|
});
|
|
72
71
|
const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
|
|
73
72
|
const effectiveApiSuffix = normalizedApiUrlTemplate ? addEditUiRuntime.apiSuffix : apiSuffix;
|
|
74
|
-
const
|
|
75
|
-
context: "useAddEdit adapter"
|
|
76
|
-
});
|
|
77
|
-
const operationScope = operationAdapter.useOperationScope({
|
|
73
|
+
const operationScope = useOperationScope({
|
|
78
74
|
ownershipFilter,
|
|
79
75
|
surfaceId,
|
|
80
76
|
access,
|
|
81
|
-
placementSource,
|
|
82
77
|
apiSuffix: effectiveApiSuffix,
|
|
83
78
|
model,
|
|
84
79
|
readEnabled,
|
|
@@ -90,7 +85,7 @@ function useAddEdit({
|
|
|
90
85
|
realtime
|
|
91
86
|
});
|
|
92
87
|
const routeContext = operationScope.routeContext;
|
|
93
|
-
const
|
|
88
|
+
const resolvedMessages = {
|
|
94
89
|
...resolveResourceMessages(resource, {
|
|
95
90
|
validation: "Fix invalid values and try again.",
|
|
96
91
|
saveSuccess: "Saved.",
|
|
@@ -98,6 +93,12 @@ function useAddEdit({
|
|
|
98
93
|
}),
|
|
99
94
|
...(messages && typeof messages === "object" ? messages : {})
|
|
100
95
|
};
|
|
96
|
+
// One save-error string for the endpoint and its feedback: an explicit
|
|
97
|
+
// fallbackSaveError, otherwise the resolved resource/messages saveError.
|
|
98
|
+
const effectiveMessages = {
|
|
99
|
+
...resolvedMessages,
|
|
100
|
+
saveError: String(fallbackSaveError || "").trim() || String(resolvedMessages.saveError || "").trim() || "Unable to save."
|
|
101
|
+
};
|
|
101
102
|
|
|
102
103
|
const canView = operationScope.permissionGate("view");
|
|
103
104
|
const canSave = operationScope.permissionGate("save");
|
|
@@ -129,7 +130,7 @@ function useAddEdit({
|
|
|
129
130
|
requestRecovery,
|
|
130
131
|
requestRecoveryLabel,
|
|
131
132
|
fallbackLoadError,
|
|
132
|
-
fallbackSaveError:
|
|
133
|
+
fallbackSaveError: effectiveMessages.saveError
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
const feedback = useUiFeedback({
|
|
@@ -4,6 +4,7 @@ import { asPlainObject } from "../support/scopeHelpers.js";
|
|
|
4
4
|
import { resolveCrudJsonApiTransport } from "../crud/crudJsonApiTransportSupport.js";
|
|
5
5
|
import { resolveCrudHttpClient } from "../crud/crudHttpClientSupport.js";
|
|
6
6
|
import { useAddEdit } from "./useAddEdit.js";
|
|
7
|
+
import { normalizeFieldErrorKeys } from "../runtime/useFieldErrorBag.js";
|
|
7
8
|
import {
|
|
8
9
|
resolveCrudBoundValues,
|
|
9
10
|
} from "../crud/crudBindingSupport.js";
|
|
@@ -18,10 +19,68 @@ import {
|
|
|
18
19
|
} from "../crud/crudSchemaFormHelpers.js";
|
|
19
20
|
import { hasResolvedQueryData } from "../support/resourceLoadStateHelpers.js";
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// addEditOptions that pass unchanged to useAddEdit. client, transport, realtime,
|
|
23
|
+
// fieldErrorKeys, model and onSaveSuccess are read here first. Resource, input,
|
|
24
|
+
// payload building and model mapping have one home each: the top-level options.
|
|
25
|
+
const PASSED_ADD_EDIT_OPTIONS = Object.freeze([
|
|
26
|
+
"ownershipFilter",
|
|
27
|
+
"surfaceId",
|
|
28
|
+
"access",
|
|
29
|
+
"apiSuffix",
|
|
30
|
+
"queryKeyFactory",
|
|
31
|
+
"viewPermissions",
|
|
32
|
+
"savePermissions",
|
|
33
|
+
"readMethod",
|
|
34
|
+
"readEnabled",
|
|
35
|
+
"writeMethod",
|
|
36
|
+
"requestRecovery",
|
|
37
|
+
"requestRecoveryLabel",
|
|
38
|
+
"placementSource",
|
|
39
|
+
"fallbackLoadError",
|
|
40
|
+
"fallbackSaveError",
|
|
41
|
+
"clearOnRouteChange",
|
|
42
|
+
"buildSavePayload",
|
|
43
|
+
"validationFeedback",
|
|
44
|
+
"requestQueryParams",
|
|
45
|
+
"recordIdParam",
|
|
46
|
+
"routeParams",
|
|
47
|
+
"routeRecordId",
|
|
48
|
+
"apiUrlTemplate",
|
|
49
|
+
"viewUrlTemplate",
|
|
50
|
+
"listUrlTemplate",
|
|
51
|
+
"saveRecordIdSelector",
|
|
52
|
+
"messages"
|
|
53
|
+
]);
|
|
54
|
+
const CONSUMED_ADD_EDIT_OPTIONS = Object.freeze([
|
|
55
|
+
"client",
|
|
56
|
+
"transport",
|
|
57
|
+
"realtime",
|
|
58
|
+
"fieldErrorKeys",
|
|
59
|
+
"model",
|
|
60
|
+
"onSaveSuccess"
|
|
61
|
+
]);
|
|
62
|
+
const TOP_LEVEL_ADD_EDIT_OPTIONS = Object.freeze({
|
|
63
|
+
resource: "resource",
|
|
64
|
+
input: "input",
|
|
65
|
+
buildRawPayload: "buildPayload",
|
|
66
|
+
mapLoadedToModel: "mapPayloadToModel"
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function resolvePassedAddEditOptions(addEditOptions = {}) {
|
|
70
|
+
const passed = {};
|
|
71
|
+
for (const [key, value] of Object.entries(addEditOptions)) {
|
|
72
|
+
if (Object.hasOwn(TOP_LEVEL_ADD_EDIT_OPTIONS, key)) {
|
|
73
|
+
throw new TypeError(
|
|
74
|
+
`useCrudAddEdit takes ${TOP_LEVEL_ADD_EDIT_OPTIONS[key]} as a top-level option, not addEditOptions.${key}.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (PASSED_ADD_EDIT_OPTIONS.includes(key)) {
|
|
78
|
+
passed[key] = value;
|
|
79
|
+
} else if (!CONSUMED_ADD_EDIT_OPTIONS.includes(key)) {
|
|
80
|
+
throw new TypeError(`useCrudAddEdit does not support addEditOptions.${key}.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return passed;
|
|
25
84
|
}
|
|
26
85
|
|
|
27
86
|
function normalizeSaveSuccessOptions(options = {}) {
|
|
@@ -57,10 +116,10 @@ function useCrudAddEdit({
|
|
|
57
116
|
const route = useRoute();
|
|
58
117
|
const normalizedFields = normalizeCrudFormFields(formFields);
|
|
59
118
|
const normalizedAddEditOptions = asPlainObject(addEditOptions);
|
|
60
|
-
const
|
|
119
|
+
const passedAddEditOptions = resolvePassedAddEditOptions(normalizedAddEditOptions);
|
|
61
120
|
const resolvedTransport = resolveCrudJsonApiTransport(
|
|
62
121
|
normalizedAddEditOptions.transport,
|
|
63
|
-
|
|
122
|
+
resource,
|
|
64
123
|
{
|
|
65
124
|
mode: "add-edit",
|
|
66
125
|
operationName
|
|
@@ -68,8 +127,8 @@ function useCrudAddEdit({
|
|
|
68
127
|
);
|
|
69
128
|
const resolvedRealtime = resolveOperationRealtimeOptions({
|
|
70
129
|
realtime: normalizedAddEditOptions.realtime,
|
|
71
|
-
fallbackRealtime:
|
|
72
|
-
|
|
130
|
+
fallbackRealtime: resource?.operations?.[operationName]?.realtime ||
|
|
131
|
+
resource?.operations?.list?.realtime ||
|
|
73
132
|
null
|
|
74
133
|
});
|
|
75
134
|
const saveSuccessOptions = normalizeSaveSuccessOptions(saveSuccess);
|
|
@@ -108,18 +167,13 @@ function useCrudAddEdit({
|
|
|
108
167
|
deep: true
|
|
109
168
|
}
|
|
110
169
|
);
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
? buildPayload
|
|
114
|
-
: (typeof normalizedAddEditOptions.buildRawPayload === "function" ? normalizedAddEditOptions.buildRawPayload : null);
|
|
115
|
-
const mapPayloadToModelOverride = typeof mapPayloadToModel === "function"
|
|
116
|
-
? mapPayloadToModel
|
|
117
|
-
: (typeof normalizedAddEditOptions.mapLoadedToModel === "function" ? normalizedAddEditOptions.mapLoadedToModel : null);
|
|
170
|
+
const buildPayloadOverride = typeof buildPayload === "function" ? buildPayload : null;
|
|
171
|
+
const mapPayloadToModelOverride = typeof mapPayloadToModel === "function" ? mapPayloadToModel : null;
|
|
118
172
|
const onSaveSuccessOverride = typeof normalizedAddEditOptions.onSaveSuccess === "function"
|
|
119
173
|
? normalizedAddEditOptions.onSaveSuccess
|
|
120
174
|
: null;
|
|
121
175
|
const shouldApplyDefaultMapPayload = normalizedAddEditOptions.readEnabled !== false;
|
|
122
|
-
const resolvedInput =
|
|
176
|
+
const resolvedInput = input || resource?.operations?.[operationName]?.body || null;
|
|
123
177
|
|
|
124
178
|
function resolveBuildRawPayload(model = {}, context = {}) {
|
|
125
179
|
const payload = buildPayloadOverride
|
|
@@ -176,18 +230,16 @@ function useCrudAddEdit({
|
|
|
176
230
|
return;
|
|
177
231
|
}
|
|
178
232
|
|
|
179
|
-
const
|
|
180
|
-
String(normalizedAddEditOptions.listUrlTemplate || "").trim();
|
|
181
|
-
const listUrl = addEditRuntime?.resolveParams(listUrlTemplate);
|
|
233
|
+
const listUrl = addEditRuntime?.resolveParams(saveSuccessOptions.listUrlTemplate);
|
|
182
234
|
if (listUrl) {
|
|
183
235
|
await router.push(listUrl);
|
|
184
236
|
}
|
|
185
237
|
}
|
|
186
238
|
|
|
187
239
|
const addEdit = useAddEdit({
|
|
188
|
-
...
|
|
189
|
-
resource
|
|
190
|
-
client: resolveCrudHttpClient(
|
|
240
|
+
...passedAddEditOptions,
|
|
241
|
+
resource,
|
|
242
|
+
client: resolveCrudHttpClient(resource, {
|
|
191
243
|
client: normalizedAddEditOptions.client
|
|
192
244
|
}),
|
|
193
245
|
transport: resolvedTransport,
|
|
@@ -2,10 +2,6 @@ import { computed, unref } from "vue";
|
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
3
|
import { resolveCrudJsonApiTransport } from "../crud/crudJsonApiTransportSupport.js";
|
|
4
4
|
import { resolveCrudHttpClient } from "../crud/crudHttpClientSupport.js";
|
|
5
|
-
import {
|
|
6
|
-
resolveLookupFieldDisplayValue,
|
|
7
|
-
resolveRecordTitle
|
|
8
|
-
} from "../crud/crudLookupFieldLabelSupport.js";
|
|
9
5
|
import { resolveCrudBoundValues } from "../crud/crudBindingSupport.js";
|
|
10
6
|
import { resolveCrudListParentDescriptor } from "../internal/crudListParentTitleSupport.js";
|
|
11
7
|
import {
|
|
@@ -93,8 +89,6 @@ function useCrudList({
|
|
|
93
89
|
}
|
|
94
90
|
});
|
|
95
91
|
|
|
96
|
-
records.resolveFieldDisplay = resolveLookupFieldDisplayValue;
|
|
97
|
-
records.resolveRecordTitle = resolveRecordTitle;
|
|
98
92
|
return records;
|
|
99
93
|
}
|
|
100
94
|
|
|
@@ -4,10 +4,6 @@ import {
|
|
|
4
4
|
resolveCrudJsonApiTransport
|
|
5
5
|
} from "../crud/crudJsonApiTransportSupport.js";
|
|
6
6
|
import { resolveCrudHttpClient } from "../crud/crudHttpClientSupport.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveLookupFieldDisplayValue,
|
|
9
|
-
resolveRecordTitle
|
|
10
|
-
} from "../crud/crudLookupFieldLabelSupport.js";
|
|
11
7
|
import { resolveCrudBoundValues } from "../crud/crudBindingSupport.js";
|
|
12
8
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
13
9
|
import { useView } from "./useView.js";
|
|
@@ -39,8 +35,6 @@ function useCrudView({
|
|
|
39
35
|
}),
|
|
40
36
|
routeParams: boundRouteParams
|
|
41
37
|
});
|
|
42
|
-
view.resolveFieldDisplay = resolveLookupFieldDisplayValue;
|
|
43
|
-
view.resolveRecordTitle = resolveRecordTitle;
|
|
44
38
|
return view;
|
|
45
39
|
}
|
|
46
40
|
|
|
@@ -3,7 +3,7 @@ import { useRouter } from "vue-router";
|
|
|
3
3
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
import { ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/kernel/shared/support/visibility";
|
|
5
5
|
import { useListCore } from "../runtime/useListCore.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useOperationScope } from "../internal/useOperationScope.js";
|
|
7
7
|
import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
8
8
|
import { createListUiRuntime } from "../runtime/listUiRuntime.js";
|
|
9
9
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
@@ -53,7 +53,6 @@ function useList({
|
|
|
53
53
|
requestRecovery,
|
|
54
54
|
requestRecoveryLabel = "List",
|
|
55
55
|
realtime = null,
|
|
56
|
-
adapter = null,
|
|
57
56
|
recordIdParam = "recordId",
|
|
58
57
|
recordIdSelector = null,
|
|
59
58
|
viewUrlTemplate = "",
|
|
@@ -104,14 +103,10 @@ function useList({
|
|
|
104
103
|
searchDebounceTimer = null;
|
|
105
104
|
});
|
|
106
105
|
|
|
107
|
-
const
|
|
108
|
-
context: "useList adapter"
|
|
109
|
-
});
|
|
110
|
-
const operationScope = operationAdapter.useOperationScope({
|
|
106
|
+
const operationScope = useOperationScope({
|
|
111
107
|
ownershipFilter,
|
|
112
108
|
surfaceId,
|
|
113
109
|
access,
|
|
114
|
-
placementSource,
|
|
115
110
|
apiSuffix,
|
|
116
111
|
readEnabled,
|
|
117
112
|
queryKeyFactory,
|
|
@@ -481,7 +476,6 @@ function useList({
|
|
|
481
476
|
loadMore: list.loadMore,
|
|
482
477
|
hasViewUrl: listUiRuntime.hasViewUrl,
|
|
483
478
|
hasEditUrl: listUiRuntime.hasEditUrl,
|
|
484
|
-
actionColumnCount: listUiRuntime.actionColumnCount,
|
|
485
479
|
showListSkeleton: listUiRuntime.showListSkeleton,
|
|
486
480
|
resolveRowKey: listUiRuntime.resolveRowKey,
|
|
487
481
|
resolveParams: listUiRuntime.resolveParams,
|
|
@@ -3,7 +3,7 @@ import { useRoute } from "vue-router";
|
|
|
3
3
|
import { ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/kernel/shared/support/visibility";
|
|
4
4
|
import { useViewCore } from "../runtime/useViewCore.js";
|
|
5
5
|
import { useEndpointResource } from "../runtime/useEndpointResource.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useOperationScope } from "../internal/useOperationScope.js";
|
|
7
7
|
import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
8
8
|
import { createViewUiRuntime } from "../runtime/viewUiRuntime.js";
|
|
9
9
|
import { createRequestQueryRuntime } from "../support/requestQueryRuntimeSupport.js";
|
|
@@ -39,8 +39,7 @@ function useView({
|
|
|
39
39
|
listUrlTemplate = "",
|
|
40
40
|
editUrlTemplate = "",
|
|
41
41
|
includeRecordIdInQueryKey = false,
|
|
42
|
-
realtime = undefined
|
|
43
|
-
adapter = null
|
|
42
|
+
realtime = undefined
|
|
44
43
|
} = {}) {
|
|
45
44
|
const route = useRoute();
|
|
46
45
|
const viewUiRuntime = createViewUiRuntime({
|
|
@@ -55,14 +54,10 @@ function useView({
|
|
|
55
54
|
});
|
|
56
55
|
const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
|
|
57
56
|
const effectiveApiSuffix = normalizedApiUrlTemplate ? viewUiRuntime.apiSuffix : apiSuffix;
|
|
58
|
-
const
|
|
59
|
-
context: "useView adapter"
|
|
60
|
-
});
|
|
61
|
-
const operationScope = operationAdapter.useOperationScope({
|
|
57
|
+
const operationScope = useOperationScope({
|
|
62
58
|
ownershipFilter,
|
|
63
59
|
surfaceId,
|
|
64
60
|
access,
|
|
65
|
-
placementSource,
|
|
66
61
|
apiSuffix: effectiveApiSuffix,
|
|
67
62
|
readEnabled,
|
|
68
63
|
queryKeyFactory,
|