@jskit-ai/users-web 0.1.30 → 0.1.32
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 +7 -6
- package/package.json +7 -7
- package/src/client/components/WorkspaceMembersClientElement.vue +3 -8
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +1 -2
- package/src/client/components/WorkspacesClientElement.vue +3 -8
- package/src/client/composables/addEditUiRuntime.js +120 -0
- package/src/client/composables/crudSchemaFormHelpers.js +152 -0
- package/src/client/composables/listUiRuntime.js +111 -0
- package/src/client/composables/operationAdapters.js +34 -0
- package/src/client/composables/routeTemplateHelpers.js +110 -0
- package/src/client/composables/useAccountSettingsRuntime.js +10 -14
- package/src/client/composables/useAddEdit.js +37 -5
- package/src/client/composables/useCrudSchemaForm.js +177 -0
- package/src/client/composables/useList.js +31 -4
- package/src/client/composables/useView.js +43 -5
- package/src/client/composables/viewUiRuntime.js +87 -0
- package/src/client/lib/theme.js +2 -3
- package/src/client/providers/UsersWebClientProvider.js +14 -39
- package/src/client/runtime/bootstrapPlacementRuntime.js +14 -24
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +0 -4
- package/test/addEditUiRuntime.test.js +75 -0
- package/test/bootstrapPlacementRuntime.test.js +45 -52
- package/test/listUiRuntime.test.js +81 -0
- package/test/useCrudSchemaForm.test.js +141 -0
- package/test/viewUiRuntime.test.js +60 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/users-web",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.32",
|
|
5
|
+
kind: "runtime",
|
|
5
6
|
description: "Users web module: workspace selector shell element plus workspace/profile/members UI elements.",
|
|
6
7
|
dependsOn: [
|
|
7
8
|
"@jskit-ai/http-runtime",
|
|
@@ -241,11 +242,11 @@ export default Object.freeze({
|
|
|
241
242
|
"@uppy/dashboard": "^5.1.1",
|
|
242
243
|
"@uppy/image-editor": "^4.2.0",
|
|
243
244
|
"@uppy/xhr-upload": "^5.1.1",
|
|
244
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
245
|
-
"@jskit-ai/realtime": "0.1.
|
|
246
|
-
"@jskit-ai/kernel": "0.1.
|
|
247
|
-
"@jskit-ai/shell-web": "0.1.
|
|
248
|
-
"@jskit-ai/users-core": "0.1.
|
|
245
|
+
"@jskit-ai/http-runtime": "0.1.17",
|
|
246
|
+
"@jskit-ai/realtime": "0.1.17",
|
|
247
|
+
"@jskit-ai/kernel": "0.1.18",
|
|
248
|
+
"@jskit-ai/shell-web": "0.1.17",
|
|
249
|
+
"@jskit-ai/users-core": "0.1.27",
|
|
249
250
|
"vuetify": "^4.0.0"
|
|
250
251
|
},
|
|
251
252
|
dev: {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"./client": "./src/client/index.js",
|
|
10
10
|
"./client/components/WorkspaceMembersClientElement": "./src/client/components/WorkspaceMembersClientElement.vue",
|
|
11
11
|
"./client/composables/useAddEdit": "./src/client/composables/useAddEdit.js",
|
|
12
|
+
"./client/composables/useCrudSchemaForm": "./src/client/composables/useCrudSchemaForm.js",
|
|
12
13
|
"./client/composables/useList": "./src/client/composables/useList.js",
|
|
13
|
-
"./client/composables/useCommand": "./src/client/composables/useCommand.js",
|
|
14
14
|
"./client/composables/useView": "./src/client/composables/useView.js",
|
|
15
15
|
"./client/composables/usePagedCollection": "./src/client/composables/usePagedCollection.js",
|
|
16
16
|
"./client/composables/useAccountSettingsRuntime": "./src/client/composables/useAccountSettingsRuntime.js",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@tanstack/vue-query": "5.92.12",
|
|
23
23
|
"@mdi/js": "^7.4.47",
|
|
24
|
-
"@jskit-ai/users-core": "0.1.
|
|
25
|
-
"@jskit-ai/realtime": "0.1.
|
|
26
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
27
|
-
"@jskit-ai/kernel": "0.1.
|
|
28
|
-
"@jskit-ai/shell-web": "0.1.
|
|
24
|
+
"@jskit-ai/users-core": "0.1.27",
|
|
25
|
+
"@jskit-ai/realtime": "0.1.17",
|
|
26
|
+
"@jskit-ai/http-runtime": "0.1.17",
|
|
27
|
+
"@jskit-ai/kernel": "0.1.18",
|
|
28
|
+
"@jskit-ai/shell-web": "0.1.17",
|
|
29
29
|
"vuetify": "^4.0.0"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -32,11 +32,6 @@ import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContex
|
|
|
32
32
|
import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
33
33
|
import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
|
|
34
34
|
import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
|
|
35
|
-
import {
|
|
36
|
-
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
37
|
-
WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
38
|
-
WORKSPACE_INVITES_CHANGED_EVENT
|
|
39
|
-
} from "@jskit-ai/users-core/shared/events/usersEvents";
|
|
40
35
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
41
36
|
|
|
42
37
|
const forms = reactive({
|
|
@@ -274,7 +269,7 @@ const workspaceSettingsView = useView({
|
|
|
274
269
|
buildWorkspaceQueryKey("settings", surfaceId, workspaceSlug),
|
|
275
270
|
viewPermissions: ["workspace.members.invite"],
|
|
276
271
|
realtime: {
|
|
277
|
-
event:
|
|
272
|
+
event: "workspace.settings.changed",
|
|
278
273
|
matches: matchesWorkspaceRealtime
|
|
279
274
|
},
|
|
280
275
|
fallbackLoadError: "Unable to load workspace settings."
|
|
@@ -295,7 +290,7 @@ const workspaceMembersList = useList({
|
|
|
295
290
|
buildWorkspaceQueryKey("members", surfaceId, workspaceSlug),
|
|
296
291
|
viewPermissions: ["workspace.members.view", "workspace.members.manage"],
|
|
297
292
|
realtime: {
|
|
298
|
-
event:
|
|
293
|
+
event: "workspace.members.changed",
|
|
299
294
|
matches: matchesWorkspaceRealtime
|
|
300
295
|
},
|
|
301
296
|
selectItems: (payload) => normalizeMembers(payload?.members),
|
|
@@ -309,7 +304,7 @@ const workspaceInvitesList = useList({
|
|
|
309
304
|
buildWorkspaceQueryKey("invites", surfaceId, workspaceSlug),
|
|
310
305
|
viewPermissions: ["workspace.members.view", "workspace.members.manage"],
|
|
311
306
|
realtime: {
|
|
312
|
-
event:
|
|
307
|
+
event: "workspace.invites.changed",
|
|
313
308
|
matches: matchesWorkspaceRealtime
|
|
314
309
|
},
|
|
315
310
|
selectItems: (payload) => normalizeInvites(payload?.invites),
|
|
@@ -170,7 +170,6 @@
|
|
|
170
170
|
import { computed, reactive } from "vue";
|
|
171
171
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
172
172
|
import { workspaceSettingsResource } from "@jskit-ai/users-core/shared/resources/workspaceSettingsResource";
|
|
173
|
-
import { WORKSPACE_SETTINGS_CHANGED_EVENT } from "@jskit-ai/users-core/shared/events/usersEvents";
|
|
174
173
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
175
174
|
import {
|
|
176
175
|
DEFAULT_WORKSPACE_DARK_PALETTE,
|
|
@@ -221,7 +220,7 @@ const addEdit = useAddEdit({
|
|
|
221
220
|
"darkSurfaceVariantColor"
|
|
222
221
|
],
|
|
223
222
|
realtime: {
|
|
224
|
-
event:
|
|
223
|
+
event: "workspace.settings.changed",
|
|
225
224
|
matches: matchesWorkspaceRealtime
|
|
226
225
|
},
|
|
227
226
|
model: workspaceSettingsForm,
|
|
@@ -12,11 +12,6 @@ import { useView } from "../composables/useView.js";
|
|
|
12
12
|
import { usePaths } from "../composables/usePaths.js";
|
|
13
13
|
import { useRealtimeQueryInvalidation } from "../composables/useRealtimeQueryInvalidation.js";
|
|
14
14
|
import { useWorkspaceSurfaceId } from "../composables/useWorkspaceSurfaceId.js";
|
|
15
|
-
import {
|
|
16
|
-
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
17
|
-
WORKSPACES_CHANGED_EVENT,
|
|
18
|
-
WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
|
|
19
|
-
} from "@jskit-ai/users-core/shared/events/usersEvents";
|
|
20
15
|
import { USERS_ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
21
16
|
import { normalizePendingInvite } from "../composables/accountSettingsRuntimeHelpers.js";
|
|
22
17
|
|
|
@@ -55,7 +50,7 @@ const bootstrapView = useView({
|
|
|
55
50
|
apiSuffix: "/bootstrap",
|
|
56
51
|
queryKeyFactory: () => bootstrapQueryKey,
|
|
57
52
|
realtime: {
|
|
58
|
-
event:
|
|
53
|
+
event: "workspace.settings.changed"
|
|
59
54
|
},
|
|
60
55
|
fallbackLoadError: "Unable to load workspaces.",
|
|
61
56
|
model: bootstrapModel,
|
|
@@ -112,12 +107,12 @@ const createWorkspaceCommand = useCommand({
|
|
|
112
107
|
});
|
|
113
108
|
|
|
114
109
|
useRealtimeQueryInvalidation({
|
|
115
|
-
event:
|
|
110
|
+
event: "workspaces.changed",
|
|
116
111
|
queryKey: bootstrapQueryKey
|
|
117
112
|
});
|
|
118
113
|
|
|
119
114
|
useRealtimeQueryInvalidation({
|
|
120
|
-
event:
|
|
115
|
+
event: "workspace.invitations.pending.changed",
|
|
121
116
|
queryKey: bootstrapQueryKey
|
|
122
117
|
});
|
|
123
118
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { asPlainObject } from "./scopeHelpers.js";
|
|
3
|
+
import {
|
|
4
|
+
normalizeRouteParamName,
|
|
5
|
+
resolveRouteParamsSource,
|
|
6
|
+
resolveRoutePathnameSource,
|
|
7
|
+
resolveRouteTemplateLocation,
|
|
8
|
+
toRouteParamValue
|
|
9
|
+
} from "./routeTemplateHelpers.js";
|
|
10
|
+
|
|
11
|
+
function toResolvedRecordId({ routeParams, recordIdParam, routeRecordId }) {
|
|
12
|
+
const explicitRecordId = toRouteParamValue(
|
|
13
|
+
typeof routeRecordId === "function" ? routeRecordId() : unref(routeRecordId)
|
|
14
|
+
);
|
|
15
|
+
if (explicitRecordId) {
|
|
16
|
+
return explicitRecordId;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return toRouteParamValue(routeParams[recordIdParam]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveSavedRecordId(payload, saveRecordIdSelector) {
|
|
23
|
+
const sourcePayload = asPlainObject(payload);
|
|
24
|
+
if (typeof saveRecordIdSelector === "function") {
|
|
25
|
+
return toRouteParamValue(saveRecordIdSelector(sourcePayload));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return toRouteParamValue(sourcePayload.id);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createAddEditUiRuntime({
|
|
32
|
+
recordIdParam = "recordId",
|
|
33
|
+
routeParams = null,
|
|
34
|
+
routePath = "",
|
|
35
|
+
routeRecordId = null,
|
|
36
|
+
apiUrlTemplate = "",
|
|
37
|
+
viewUrlTemplate = "",
|
|
38
|
+
listUrlTemplate = "",
|
|
39
|
+
saveRecordIdSelector = null
|
|
40
|
+
} = {}) {
|
|
41
|
+
const normalizedRecordIdParam = normalizeRouteParamName(recordIdParam, {
|
|
42
|
+
context: "useAddEdit recordIdParam"
|
|
43
|
+
});
|
|
44
|
+
const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
|
|
45
|
+
const normalizedViewUrlTemplate = String(viewUrlTemplate || "").trim();
|
|
46
|
+
const normalizedListUrlTemplate = String(listUrlTemplate || "").trim();
|
|
47
|
+
|
|
48
|
+
function resolveTemplatePath(urlTemplate = "", extraParams = {}) {
|
|
49
|
+
const normalizedTemplate = String(urlTemplate || "").trim();
|
|
50
|
+
if (!normalizedTemplate) {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const currentRouteParams = resolveRouteParamsSource(routeParams);
|
|
55
|
+
const sourceParams = {
|
|
56
|
+
...currentRouteParams,
|
|
57
|
+
...asPlainObject(extraParams)
|
|
58
|
+
};
|
|
59
|
+
const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
|
|
60
|
+
toResolvedRecordId({
|
|
61
|
+
routeParams: currentRouteParams,
|
|
62
|
+
recordIdParam: normalizedRecordIdParam,
|
|
63
|
+
routeRecordId
|
|
64
|
+
});
|
|
65
|
+
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
66
|
+
|
|
67
|
+
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
68
|
+
params: sourceParams,
|
|
69
|
+
currentPathname: resolveRoutePathnameSource(routePath)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const recordId = computed(() =>
|
|
74
|
+
toResolvedRecordId({
|
|
75
|
+
routeParams: resolveRouteParamsSource(routeParams),
|
|
76
|
+
recordIdParam: normalizedRecordIdParam,
|
|
77
|
+
routeRecordId
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const apiSuffix = computed(() => resolveTemplatePath(normalizedApiUrlTemplate));
|
|
82
|
+
const listUrl = computed(() => resolveTemplatePath(normalizedListUrlTemplate));
|
|
83
|
+
const cancelUrl = computed(() => resolveTemplatePath(normalizedViewUrlTemplate) || listUrl.value);
|
|
84
|
+
|
|
85
|
+
function resolveParams(urlTemplate = "", extraParams = {}) {
|
|
86
|
+
return resolveTemplatePath(urlTemplate, extraParams);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveViewUrl(recordIdLike = "") {
|
|
90
|
+
const targetRecordId = toRouteParamValue(recordIdLike);
|
|
91
|
+
if (!targetRecordId) {
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return resolveTemplatePath(normalizedViewUrlTemplate, {
|
|
96
|
+
[normalizedRecordIdParam]: targetRecordId
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolveSavedViewUrl(payload = {}) {
|
|
101
|
+
const targetRecordId = resolveSavedRecordId(payload, saveRecordIdSelector);
|
|
102
|
+
if (!targetRecordId) {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return resolveViewUrl(targetRecordId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
recordId,
|
|
111
|
+
apiSuffix,
|
|
112
|
+
listUrl,
|
|
113
|
+
cancelUrl,
|
|
114
|
+
resolveParams,
|
|
115
|
+
resolveViewUrl,
|
|
116
|
+
resolveSavedViewUrl
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { createAddEditUiRuntime };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
2
|
+
import { asPlainObject } from "./scopeHelpers.js";
|
|
3
|
+
|
|
4
|
+
function normalizeCrudFormFields(fields = []) {
|
|
5
|
+
const normalizedFields = [];
|
|
6
|
+
const seenKeys = new Set();
|
|
7
|
+
for (const field of Array.isArray(fields) ? fields : []) {
|
|
8
|
+
const source = asPlainObject(field);
|
|
9
|
+
const key = String(source.key || "").trim();
|
|
10
|
+
if (!key || seenKeys.has(key)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
seenKeys.add(key);
|
|
15
|
+
normalizedFields.push({
|
|
16
|
+
...source,
|
|
17
|
+
key
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return Object.freeze(normalizedFields);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveFormFieldType(field = {}) {
|
|
25
|
+
return String(field.type || "").trim().toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveFormFieldInitialValue(field = {}) {
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(field, "initialValue")) {
|
|
30
|
+
return field.initialValue;
|
|
31
|
+
}
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(field, "defaultValue")) {
|
|
33
|
+
return field.defaultValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fieldType = resolveFormFieldType(field);
|
|
37
|
+
if (fieldType === "boolean") {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createCrudFormModel(fields = []) {
|
|
45
|
+
const model = {};
|
|
46
|
+
for (const field of normalizeCrudFormFields(fields)) {
|
|
47
|
+
model[field.key] = resolveFormFieldInitialValue(field);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return model;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildCrudFormPayload(fields = [], model = {}) {
|
|
54
|
+
const payload = {};
|
|
55
|
+
const sourceModel = asPlainObject(model);
|
|
56
|
+
|
|
57
|
+
for (const field of normalizeCrudFormFields(fields)) {
|
|
58
|
+
const fieldKey = field.key;
|
|
59
|
+
const fieldType = resolveFormFieldType(field);
|
|
60
|
+
const rawValue = sourceModel[fieldKey];
|
|
61
|
+
|
|
62
|
+
if (fieldType === "boolean") {
|
|
63
|
+
payload[fieldKey] = Boolean(rawValue);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (fieldType === "integer" || fieldType === "number") {
|
|
68
|
+
const normalizedValue = String(rawValue ?? "").trim();
|
|
69
|
+
if (!normalizedValue) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const parsedNumber = Number(normalizedValue);
|
|
74
|
+
payload[fieldKey] = Number.isFinite(parsedNumber)
|
|
75
|
+
? (fieldType === "integer" ? Math.trunc(parsedNumber) : parsedNumber)
|
|
76
|
+
: rawValue;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (rawValue == null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
payload[fieldKey] = rawValue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return payload;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function applyCrudPayloadToForm(fields = [], model = {}, payload = {}) {
|
|
91
|
+
const targetModel = asPlainObject(model);
|
|
92
|
+
const sourcePayload = asPlainObject(payload);
|
|
93
|
+
for (const field of normalizeCrudFormFields(fields)) {
|
|
94
|
+
const fieldKey = field.key;
|
|
95
|
+
const fieldType = resolveFormFieldType(field);
|
|
96
|
+
const rawValue = sourcePayload[fieldKey];
|
|
97
|
+
|
|
98
|
+
if (fieldType === "boolean") {
|
|
99
|
+
targetModel[fieldKey] = Boolean(rawValue);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (fieldType === "integer" || fieldType === "number") {
|
|
104
|
+
targetModel[fieldKey] = rawValue == null ? "" : String(rawValue);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
targetModel[fieldKey] = rawValue == null ? "" : String(rawValue);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveCrudFieldErrors(fieldErrors = {}, fieldKey = "") {
|
|
113
|
+
const key = String(fieldKey || "").trim();
|
|
114
|
+
if (!key) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const source = asPlainObject(fieldErrors);
|
|
119
|
+
const message = String(source[key] || "").trim();
|
|
120
|
+
if (!message) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return [message];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseCrudResourceOperationInput({
|
|
128
|
+
resource = null,
|
|
129
|
+
operationName = "",
|
|
130
|
+
rawPayload = {},
|
|
131
|
+
context = {}
|
|
132
|
+
} = {}) {
|
|
133
|
+
const normalizedOperationName = String(operationName || "").trim();
|
|
134
|
+
const operations = asPlainObject(asPlainObject(resource).operations);
|
|
135
|
+
const operation = asPlainObject(operations[normalizedOperationName]);
|
|
136
|
+
|
|
137
|
+
return validateOperationSection({
|
|
138
|
+
operation,
|
|
139
|
+
section: "bodyValidator",
|
|
140
|
+
value: rawPayload,
|
|
141
|
+
context
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
normalizeCrudFormFields,
|
|
147
|
+
createCrudFormModel,
|
|
148
|
+
buildCrudFormPayload,
|
|
149
|
+
applyCrudPayloadToForm,
|
|
150
|
+
resolveCrudFieldErrors,
|
|
151
|
+
parseCrudResourceOperationInput
|
|
152
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { asPlainObject } from "./scopeHelpers.js";
|
|
3
|
+
import {
|
|
4
|
+
normalizeRouteParamName,
|
|
5
|
+
toRouteParamValue,
|
|
6
|
+
resolveRouteParamsSource,
|
|
7
|
+
resolveRoutePathnameSource,
|
|
8
|
+
resolveRouteTemplateLocation
|
|
9
|
+
} from "./routeTemplateHelpers.js";
|
|
10
|
+
|
|
11
|
+
function resolveRecordId(record, recordIdSelector) {
|
|
12
|
+
const item = asPlainObject(record);
|
|
13
|
+
if (typeof recordIdSelector === "function") {
|
|
14
|
+
return toRouteParamValue(recordIdSelector(item));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return toRouteParamValue(item.id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createListUiRuntime({
|
|
21
|
+
items,
|
|
22
|
+
isInitialLoading,
|
|
23
|
+
recordIdParam = "recordId",
|
|
24
|
+
recordIdSelector = null,
|
|
25
|
+
routeParams = null,
|
|
26
|
+
routePath = "",
|
|
27
|
+
viewUrlTemplate = "",
|
|
28
|
+
editUrlTemplate = ""
|
|
29
|
+
} = {}) {
|
|
30
|
+
const normalizedRecordIdParam = normalizeRouteParamName(recordIdParam, {
|
|
31
|
+
context: "useList recordIdParam"
|
|
32
|
+
});
|
|
33
|
+
const normalizedViewUrlTemplate = String(viewUrlTemplate || "").trim();
|
|
34
|
+
const normalizedEditUrlTemplate = String(editUrlTemplate || "").trim();
|
|
35
|
+
const hasViewUrl = Boolean(normalizedViewUrlTemplate);
|
|
36
|
+
const hasEditUrl = Boolean(normalizedEditUrlTemplate);
|
|
37
|
+
const actionColumnCount = (hasViewUrl ? 1 : 0) + (hasEditUrl ? 1 : 0);
|
|
38
|
+
const normalizedItems = computed(() => (Array.isArray(items?.value) ? items.value : []));
|
|
39
|
+
const showListSkeleton = computed(() => Boolean(isInitialLoading?.value && normalizedItems.value.length < 1));
|
|
40
|
+
|
|
41
|
+
function resolveTemplatePath(urlTemplate = "", extraParams = {}) {
|
|
42
|
+
const normalizedTemplate = String(urlTemplate || "").trim();
|
|
43
|
+
if (!normalizedTemplate) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
48
|
+
params: {
|
|
49
|
+
...resolveRouteParamsSource(routeParams),
|
|
50
|
+
...asPlainObject(extraParams)
|
|
51
|
+
},
|
|
52
|
+
currentPathname: resolveRoutePathnameSource(routePath)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveParams(urlTemplate = "", extraParams = {}) {
|
|
57
|
+
return resolveTemplatePath(urlTemplate, extraParams);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveRowKey(record, index) {
|
|
61
|
+
const recordId = resolveRecordId(record, recordIdSelector);
|
|
62
|
+
if (recordId) {
|
|
63
|
+
return recordId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `row-${index}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveViewUrl(record) {
|
|
70
|
+
if (!hasViewUrl) {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const recordId = resolveRecordId(record, recordIdSelector);
|
|
75
|
+
if (!recordId) {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return resolveTemplatePath(normalizedViewUrlTemplate, {
|
|
80
|
+
[normalizedRecordIdParam]: recordId
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveEditUrl(record) {
|
|
85
|
+
if (!hasEditUrl) {
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const recordId = resolveRecordId(record, recordIdSelector);
|
|
90
|
+
if (!recordId) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return resolveTemplatePath(normalizedEditUrlTemplate, {
|
|
95
|
+
[normalizedRecordIdParam]: recordId
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
hasViewUrl,
|
|
101
|
+
hasEditUrl,
|
|
102
|
+
actionColumnCount,
|
|
103
|
+
showListSkeleton,
|
|
104
|
+
resolveParams,
|
|
105
|
+
resolveRowKey,
|
|
106
|
+
resolveViewUrl,
|
|
107
|
+
resolveEditUrl
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { createListUiRuntime };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useOperationScope } from "./internal/useOperationScope.js";
|
|
2
|
+
|
|
3
|
+
const USERS_OPERATION_ADAPTER_ID = "users-default";
|
|
4
|
+
|
|
5
|
+
function createUsersOperationAdapter() {
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
id: USERS_OPERATION_ADAPTER_ID,
|
|
8
|
+
useOperationScope(options = {}) {
|
|
9
|
+
return useOperationScope(options);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const defaultUsersOperationAdapter = createUsersOperationAdapter();
|
|
15
|
+
|
|
16
|
+
function resolveOperationAdapter(adapter, { context = "users-web operation adapter" } = {}) {
|
|
17
|
+
if (adapter == null) {
|
|
18
|
+
return defaultUsersOperationAdapter;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!adapter || typeof adapter !== "object" || Array.isArray(adapter)) {
|
|
22
|
+
throw new TypeError(`${context} must be an object when provided.`);
|
|
23
|
+
}
|
|
24
|
+
if (typeof adapter.useOperationScope !== "function") {
|
|
25
|
+
throw new TypeError(`${context} must expose useOperationScope(options).`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return adapter;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
createUsersOperationAdapter,
|
|
33
|
+
resolveOperationAdapter
|
|
34
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { unref } from "vue";
|
|
2
|
+
import { asPlainObject } from "./scopeHelpers.js";
|
|
3
|
+
|
|
4
|
+
const ROUTE_PARAM_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
5
|
+
|
|
6
|
+
function normalizeRouteParamName(value = "", { context = "users-web route param" } = {}) {
|
|
7
|
+
const normalizedValue = String(value || "").trim();
|
|
8
|
+
if (!normalizedValue) {
|
|
9
|
+
throw new TypeError(`${context} must be a non-empty route parameter name.`);
|
|
10
|
+
}
|
|
11
|
+
if (!ROUTE_PARAM_NAME_PATTERN.test(normalizedValue)) {
|
|
12
|
+
throw new TypeError(
|
|
13
|
+
`${context} "${normalizedValue}" is invalid. Use letters, numbers, and underscores only.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return normalizedValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function toRouteParamValue(value) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return toRouteParamValue(value[0]);
|
|
23
|
+
}
|
|
24
|
+
if (value == null) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return String(value).trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveRouteSourceValue(source = null) {
|
|
32
|
+
if (typeof source === "function") {
|
|
33
|
+
return source();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return unref(source);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveRouteParamsSource(source = null) {
|
|
40
|
+
return asPlainObject(resolveRouteSourceValue(source));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeRoutePathname(value = "") {
|
|
44
|
+
const rawPathname = String(value || "").trim();
|
|
45
|
+
const sanitizedPathname = rawPathname.split(/[?#]/u, 1)[0] || "";
|
|
46
|
+
if (!sanitizedPathname) {
|
|
47
|
+
return "/";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return sanitizedPathname.startsWith("/") ? sanitizedPathname : `/${sanitizedPathname}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveRoutePathnameSource(source = "") {
|
|
54
|
+
return normalizeRoutePathname(resolveRouteSourceValue(source));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveRouteTemplatePath(routeTemplate = "", params = {}) {
|
|
58
|
+
const normalizedTemplate = String(routeTemplate || "").trim();
|
|
59
|
+
if (!normalizedTemplate) {
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const source = asPlainObject(params);
|
|
64
|
+
const missingParams = [];
|
|
65
|
+
const resolvedPath = normalizedTemplate.replace(/:([A-Za-z][A-Za-z0-9_]*)/g, (_, key) => {
|
|
66
|
+
const value = toRouteParamValue(source[key]);
|
|
67
|
+
if (!value) {
|
|
68
|
+
missingParams.push(key);
|
|
69
|
+
return `:${key}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return encodeURIComponent(value);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (missingParams.length > 0) {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return resolvedPath;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveRouteTemplateLocation(routeTemplate = "", { params = {}, currentPathname = "/" } = {}) {
|
|
83
|
+
const resolvedTemplatePath = resolveRouteTemplatePath(routeTemplate, params);
|
|
84
|
+
if (!resolvedTemplatePath) {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
if (resolvedTemplatePath.startsWith("/")) {
|
|
88
|
+
return resolvedTemplatePath;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const normalizedCurrentPathname = resolveRoutePathnameSource(currentPathname);
|
|
92
|
+
const basePathname = normalizedCurrentPathname.endsWith("/")
|
|
93
|
+
? normalizedCurrentPathname
|
|
94
|
+
: `${normalizedCurrentPathname}/`;
|
|
95
|
+
const resolvedPathname = new URL(resolvedTemplatePath, `https://jskit.local${basePathname}`).pathname;
|
|
96
|
+
if (resolvedPathname.length > 1 && resolvedPathname.endsWith("/")) {
|
|
97
|
+
return resolvedPathname.slice(0, -1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return resolvedPathname;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
normalizeRouteParamName,
|
|
105
|
+
toRouteParamValue,
|
|
106
|
+
resolveRouteParamsSource,
|
|
107
|
+
resolveRoutePathnameSource,
|
|
108
|
+
resolveRouteTemplatePath,
|
|
109
|
+
resolveRouteTemplateLocation
|
|
110
|
+
};
|