@jskit-ai/users-web 0.1.37 → 0.1.40
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 +13 -244
- package/package.json +17 -13
- package/src/client/components/ConsoleSettingsClientElement.vue +1 -1
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +14 -25
- package/src/client/components/WorkspaceMembersClientElement.vue +3 -3
- package/src/client/components/WorkspaceProfileClientElement.vue +1 -1
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +1 -1
- package/src/client/components/WorkspacesClientElement.vue +2 -2
- package/src/client/composables/account-settings/accountSettingsAvatarUploadRuntime.js +61 -0
- package/src/client/composables/{accountSettingsInvitesRuntime.js → account-settings/accountSettingsInvitesRuntime.js} +1 -1
- package/src/client/composables/{accountSettingsRuntimeHelpers.js → account-settings/accountSettingsRuntimeHelpers.js} +1 -1
- package/src/client/composables/crud/crudBindingSupport.js +75 -0
- package/src/client/composables/{crudLookupFieldLabelSupport.js → crud/crudLookupFieldLabelSupport.js} +1 -1
- package/src/client/composables/{crudLookupFieldRuntime.js → crud/crudLookupFieldRuntime.js} +6 -2
- package/src/client/composables/{crudSchemaFormHelpers.js → crud/crudSchemaFormHelpers.js} +155 -2
- package/src/client/composables/internal/crudListParentTitleSupport.js +168 -0
- package/src/client/composables/internal/useOperationScope.js +1 -1
- package/src/client/composables/{useAddEdit.js → records/useAddEdit.js} +9 -9
- package/src/client/composables/{useCrudSchemaForm.js → records/useCrudAddEdit.js} +32 -15
- package/src/client/composables/records/useCrudList.js +83 -0
- package/src/client/composables/records/useCrudView.js +35 -0
- package/src/client/composables/{useList.js → records/useList.js} +31 -57
- package/src/client/composables/{useView.js → records/useView.js} +6 -9
- package/src/client/composables/{addEditUiRuntime.js → runtime/addEditUiRuntime.js} +2 -2
- package/src/client/composables/{listUiRuntime.js → runtime/listUiRuntime.js} +2 -2
- package/src/client/composables/{operationAdapters.js → runtime/operationAdapters.js} +1 -1
- package/src/client/composables/{useEndpointResource.js → runtime/useEndpointResource.js} +5 -5
- package/src/client/composables/{useListCore.js → runtime/useListCore.js} +4 -4
- package/src/client/composables/{useUiFeedback.js → runtime/useUiFeedback.js} +1 -1
- package/src/client/composables/{viewUiRuntime.js → runtime/viewUiRuntime.js} +2 -2
- package/src/client/composables/useAccess.js +2 -2
- package/src/client/composables/useAccountSettingsRuntime.js +6 -6
- package/src/client/composables/useBootstrapQuery.js +1 -1
- package/src/client/composables/useCommand.js +5 -5
- package/src/client/composables/useCrudListParentTitle.js +131 -0
- package/src/client/composables/usePagedCollection.js +3 -3
- package/src/client/composables/useScopeRuntime.js +1 -1
- package/src/client/index.js +1 -0
- package/src/client/providers/UsersWebClientProvider.js +0 -12
- package/src/client/providers/UsersWorkspacesClientProvider.js +26 -0
- package/src/client/support/menuLinkTarget.js +93 -0
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +16 -9
- package/templates/src/pages/console/settings/index.vue +1 -0
- package/test/addEditUiRuntime.test.js +1 -1
- package/test/crudBindingSupport.test.js +110 -0
- package/test/crudLookupFieldRuntime.test.js +1 -1
- package/test/errorMessageHelpers.test.js +1 -1
- package/test/exportsContract.test.js +10 -1
- package/test/listQueryParamSupport.test.js +1 -1
- package/test/listUiRuntime.test.js +1 -1
- package/test/menuLinkTarget.test.js +116 -0
- package/test/permissions.test.js +2 -2
- package/test/refValueHelpers.test.js +1 -1
- package/test/resourceLoadStateHelpers.test.js +1 -1
- package/test/routeTemplateHelpers.test.js +1 -1
- package/test/scopeHelpers.test.js +1 -1
- package/test/settingsPlacementContract.test.js +44 -0
- package/test/{useCrudSchemaForm.test.js → useCrudAddEdit.test.js} +81 -1
- package/test/useCrudListParentTitle.test.js +143 -0
- package/test/useListSearchSupport.test.js +1 -1
- package/test/viewCoreLoading.test.js +1 -1
- package/test/viewUiRuntime.test.js +1 -1
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +0 -95
- package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +0 -162
- package/templates/src/components/WorkspaceNotFoundCard.vue +0 -34
- package/templates/src/composables/useWorkspaceNotFoundState.js +0 -41
- package/templates/src/pages/admin/members/index.vue +0 -7
- package/templates/src/pages/admin/workspace/settings/index.vue +0 -16
- package/templates/src/surfaces/admin/index.vue +0 -29
- package/templates/src/surfaces/admin/root.vue +0 -20
- package/templates/src/surfaces/app/index.vue +0 -27
- package/templates/src/surfaces/app/root.vue +0 -20
- /package/src/client/composables/{accountSettingsRuntimeConstants.js → account-settings/accountSettingsRuntimeConstants.js} +0 -0
- /package/src/client/composables/{modelStateHelpers.js → runtime/modelStateHelpers.js} +0 -0
- /package/src/client/composables/{operationUiHelpers.js → runtime/operationUiHelpers.js} +0 -0
- /package/src/client/composables/{operationValidationHelpers.js → runtime/operationValidationHelpers.js} +0 -0
- /package/src/client/composables/{useAddEditCore.js → runtime/useAddEditCore.js} +0 -0
- /package/src/client/composables/{useCommandCore.js → runtime/useCommandCore.js} +0 -0
- /package/src/client/composables/{useFieldErrorBag.js → runtime/useFieldErrorBag.js} +0 -0
- /package/src/client/composables/{useViewCore.js → runtime/useViewCore.js} +0 -0
- /package/src/client/composables/{errorMessageHelpers.js → support/errorMessageHelpers.js} +0 -0
- /package/src/client/composables/{listQueryParamSupport.js → support/listQueryParamSupport.js} +0 -0
- /package/src/client/composables/{listSearchSupport.js → support/listSearchSupport.js} +0 -0
- /package/src/client/composables/{refValueHelpers.js → support/refValueHelpers.js} +0 -0
- /package/src/client/composables/{resourceLoadStateHelpers.js → support/resourceLoadStateHelpers.js} +0 -0
- /package/src/client/composables/{routeTemplateHelpers.js → support/routeTemplateHelpers.js} +0 -0
- /package/src/client/composables/{scopeHelpers.js → support/scopeHelpers.js} +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { unref } from "vue";
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
4
|
+
|
|
5
|
+
const CRUD_BINDING_MODE_ROUTE = "route";
|
|
6
|
+
const CRUD_BINDING_MODE_MERGE = "merge";
|
|
7
|
+
const CRUD_BINDING_MODE_EXPLICIT = "explicit";
|
|
8
|
+
const CRUD_BINDING_MODE_NONE = "none";
|
|
9
|
+
|
|
10
|
+
function normalizeCrudBindingMode(value = "") {
|
|
11
|
+
const normalizedValue = normalizeText(value).toLowerCase();
|
|
12
|
+
if (normalizedValue === CRUD_BINDING_MODE_MERGE) {
|
|
13
|
+
return CRUD_BINDING_MODE_MERGE;
|
|
14
|
+
}
|
|
15
|
+
if (normalizedValue === CRUD_BINDING_MODE_EXPLICIT) {
|
|
16
|
+
return CRUD_BINDING_MODE_EXPLICIT;
|
|
17
|
+
}
|
|
18
|
+
if (normalizedValue === CRUD_BINDING_MODE_NONE) {
|
|
19
|
+
return CRUD_BINDING_MODE_NONE;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return CRUD_BINDING_MODE_ROUTE;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeCrudBindingConfig(binding = {}) {
|
|
26
|
+
const source = asPlainObject(unref(binding));
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
mode: normalizeCrudBindingMode(source.mode),
|
|
29
|
+
values: source.values ?? null
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveCrudBindingValues(values, context = {}) {
|
|
34
|
+
if (typeof values === "function") {
|
|
35
|
+
return asPlainObject(values(context));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return asPlainObject(unref(values));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveCrudBoundValues({
|
|
42
|
+
binding = {},
|
|
43
|
+
routeValues = {},
|
|
44
|
+
context = {}
|
|
45
|
+
} = {}) {
|
|
46
|
+
const normalizedBinding = normalizeCrudBindingConfig(binding);
|
|
47
|
+
const normalizedRouteValues = asPlainObject(routeValues);
|
|
48
|
+
const explicitValues = resolveCrudBindingValues(normalizedBinding.values, context);
|
|
49
|
+
|
|
50
|
+
if (normalizedBinding.mode === CRUD_BINDING_MODE_NONE) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
if (normalizedBinding.mode === CRUD_BINDING_MODE_EXPLICIT) {
|
|
54
|
+
return explicitValues;
|
|
55
|
+
}
|
|
56
|
+
if (normalizedBinding.mode === CRUD_BINDING_MODE_MERGE) {
|
|
57
|
+
return {
|
|
58
|
+
...normalizedRouteValues,
|
|
59
|
+
...explicitValues
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return normalizedRouteValues;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
CRUD_BINDING_MODE_ROUTE,
|
|
68
|
+
CRUD_BINDING_MODE_MERGE,
|
|
69
|
+
CRUD_BINDING_MODE_EXPLICIT,
|
|
70
|
+
CRUD_BINDING_MODE_NONE,
|
|
71
|
+
normalizeCrudBindingMode,
|
|
72
|
+
normalizeCrudBindingConfig,
|
|
73
|
+
resolveCrudBindingValues,
|
|
74
|
+
resolveCrudBoundValues
|
|
75
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import { normalizeCrudLookupContainerKey } from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
3
|
-
import { asPlainObject } from "
|
|
3
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
4
4
|
|
|
5
5
|
const LOOKUP_LABEL_COMPOSITION_CANDIDATES = Object.freeze([
|
|
6
6
|
Object.freeze(["name", "surname"]),
|
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
normalizeCrudLookupContainerKey,
|
|
6
6
|
resolveCrudLookupApiPathFromNamespace
|
|
7
7
|
} from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
8
|
-
import {
|
|
8
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
9
|
+
import { useList } from "../records/useList.js";
|
|
9
10
|
import {
|
|
10
11
|
resolveLookupItemLabel,
|
|
11
12
|
resolveLookupFieldDisplayValue
|
|
12
13
|
} from "./crudLookupFieldLabelSupport.js";
|
|
13
|
-
import { asPlainObject } from "
|
|
14
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
14
15
|
|
|
15
16
|
function normalizeQueryKeyPrefix(value) {
|
|
16
17
|
const source = Array.isArray(value) ? value : [];
|
|
@@ -109,12 +110,14 @@ function createCrudLookupFieldRuntime({
|
|
|
109
110
|
defaultValue: defaultLookupContainerKey,
|
|
110
111
|
context: `createCrudLookupFieldRuntime formFields["${key}"].relation.containerKey`
|
|
111
112
|
});
|
|
113
|
+
const relationSurfaceId = normalizeSurfaceId(rawRelation.surfaceId);
|
|
112
114
|
if (!valueKey) {
|
|
113
115
|
continue;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
const runtime = useList({
|
|
117
119
|
adapter: adapter || undefined,
|
|
120
|
+
...(relationSurfaceId ? { surfaceId: relationSurfaceId } : {}),
|
|
118
121
|
apiSuffix: apiPath,
|
|
119
122
|
queryKeyFactory: (surfaceId = "", workspaceSlug = "") => [
|
|
120
123
|
...normalizedQueryKeyPrefix,
|
|
@@ -147,6 +150,7 @@ function createCrudLookupFieldRuntime({
|
|
|
147
150
|
kind: "lookup",
|
|
148
151
|
namespace,
|
|
149
152
|
...(explicitApiPath ? { apiPath: explicitApiPath } : {}),
|
|
153
|
+
...(relationSurfaceId ? { surfaceId: relationSurfaceId } : {}),
|
|
150
154
|
containerKey: relationLookupContainerKey,
|
|
151
155
|
valueKey,
|
|
152
156
|
...(labelKey ? { labelKey } : {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
2
|
-
import { asPlainObject } from "
|
|
3
|
-
import { toRouteParamValue } from "
|
|
2
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
3
|
+
import { toRouteParamValue } from "../support/routeTemplateHelpers.js";
|
|
4
4
|
|
|
5
5
|
const EMPTY_FIELD_ERROR_LIST = Object.freeze([]);
|
|
6
6
|
const fieldErrorListCache = new Map();
|
|
@@ -44,6 +44,84 @@ function resolveFormFieldType(field = {}) {
|
|
|
44
44
|
return String(field.type || "").trim().toLowerCase();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function resolveFormFieldFormat(field = {}) {
|
|
48
|
+
return String(field.format || "").trim().toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function padDateTimePart(value) {
|
|
52
|
+
return String(value).padStart(2, "0");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeTimeWhitespace(value) {
|
|
56
|
+
return String(value ?? "").replaceAll(/\s+/gu, " ").trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toTimeInputValue(value) {
|
|
60
|
+
const normalized = normalizeTimeWhitespace(value);
|
|
61
|
+
if (!normalized) {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const twentyFourHourMatch = normalized.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/u);
|
|
66
|
+
if (twentyFourHourMatch) {
|
|
67
|
+
const hours = Number(twentyFourHourMatch[1]);
|
|
68
|
+
const minutes = Number(twentyFourHourMatch[2]);
|
|
69
|
+
if (hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59) {
|
|
70
|
+
return `${padDateTimePart(hours)}:${padDateTimePart(minutes)}`;
|
|
71
|
+
}
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const meridiemMatch = normalized.match(/^(\d{1,2}):(\d{2})\s*([ap]\.?m\.?)$/iu);
|
|
76
|
+
if (!meridiemMatch) {
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rawHours = Number(meridiemMatch[1]);
|
|
81
|
+
const minutes = Number(meridiemMatch[2]);
|
|
82
|
+
if (rawHours < 1 || rawHours > 12 || minutes < 0 || minutes > 59) {
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let hours = rawHours % 12;
|
|
87
|
+
if (String(meridiemMatch[3] || "").toLowerCase().startsWith("p")) {
|
|
88
|
+
hours += 12;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return `${padDateTimePart(hours)}:${padDateTimePart(minutes)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function toDateTimeLocalInputValue(value) {
|
|
95
|
+
if (value == null || value === "") {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
100
|
+
if (Number.isNaN(date.getTime())) {
|
|
101
|
+
return String(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [
|
|
105
|
+
date.getFullYear(),
|
|
106
|
+
padDateTimePart(date.getMonth() + 1),
|
|
107
|
+
padDateTimePart(date.getDate())
|
|
108
|
+
].join("-") + `T${padDateTimePart(date.getHours())}:${padDateTimePart(date.getMinutes())}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function toIsoUtcDateTimeValue(value) {
|
|
112
|
+
const normalized = String(value ?? "").trim();
|
|
113
|
+
if (!normalized) {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const date = new Date(normalized);
|
|
118
|
+
if (Number.isNaN(date.getTime())) {
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return date.toISOString();
|
|
123
|
+
}
|
|
124
|
+
|
|
47
125
|
function resolveFormFieldInitialValue(field = {}) {
|
|
48
126
|
if (Object.prototype.hasOwnProperty.call(field, "initialValue")) {
|
|
49
127
|
return field.initialValue;
|
|
@@ -60,6 +138,23 @@ function resolveFormFieldInitialValue(field = {}) {
|
|
|
60
138
|
return "";
|
|
61
139
|
}
|
|
62
140
|
|
|
141
|
+
function shouldSerializeClearedFieldAsNull(field = {}) {
|
|
142
|
+
if (field?.nullable !== true) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const fieldType = resolveFormFieldType(field);
|
|
147
|
+
const fieldFormat = resolveFormFieldFormat(field);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
fieldType === "integer" ||
|
|
151
|
+
fieldType === "number" ||
|
|
152
|
+
fieldFormat === "date" ||
|
|
153
|
+
fieldFormat === "date-time" ||
|
|
154
|
+
fieldFormat === "time"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
63
158
|
function createCrudFormModel(fields = []) {
|
|
64
159
|
const model = {};
|
|
65
160
|
for (const field of normalizeCrudFormFields(fields)) {
|
|
@@ -76,6 +171,8 @@ function buildCrudFormPayload(fields = [], model = {}) {
|
|
|
76
171
|
for (const field of normalizeCrudFormFields(fields)) {
|
|
77
172
|
const fieldKey = field.key;
|
|
78
173
|
const fieldType = resolveFormFieldType(field);
|
|
174
|
+
const fieldFormat = resolveFormFieldFormat(field);
|
|
175
|
+
const clearAsNull = shouldSerializeClearedFieldAsNull(field);
|
|
79
176
|
const rawValue = sourceModel[fieldKey];
|
|
80
177
|
|
|
81
178
|
if (fieldType === "boolean") {
|
|
@@ -86,6 +183,9 @@ function buildCrudFormPayload(fields = [], model = {}) {
|
|
|
86
183
|
if (fieldType === "integer" || fieldType === "number") {
|
|
87
184
|
const normalizedValue = String(rawValue ?? "").trim();
|
|
88
185
|
if (!normalizedValue) {
|
|
186
|
+
if (clearAsNull) {
|
|
187
|
+
payload[fieldKey] = null;
|
|
188
|
+
}
|
|
89
189
|
continue;
|
|
90
190
|
}
|
|
91
191
|
|
|
@@ -97,6 +197,48 @@ function buildCrudFormPayload(fields = [], model = {}) {
|
|
|
97
197
|
}
|
|
98
198
|
|
|
99
199
|
if (rawValue == null) {
|
|
200
|
+
if (clearAsNull) {
|
|
201
|
+
payload[fieldKey] = null;
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (fieldFormat === "date") {
|
|
207
|
+
const normalizedValue = String(rawValue).trim();
|
|
208
|
+
if (!normalizedValue) {
|
|
209
|
+
if (clearAsNull) {
|
|
210
|
+
payload[fieldKey] = null;
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
payload[fieldKey] = normalizedValue;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (fieldFormat === "date-time") {
|
|
220
|
+
const normalizedValue = toIsoUtcDateTimeValue(rawValue);
|
|
221
|
+
if (!normalizedValue) {
|
|
222
|
+
if (clearAsNull) {
|
|
223
|
+
payload[fieldKey] = null;
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
payload[fieldKey] = normalizedValue;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (fieldFormat === "time") {
|
|
233
|
+
const normalizedValue = toTimeInputValue(rawValue);
|
|
234
|
+
if (!normalizedValue) {
|
|
235
|
+
if (clearAsNull) {
|
|
236
|
+
payload[fieldKey] = null;
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
payload[fieldKey] = normalizedValue;
|
|
100
242
|
continue;
|
|
101
243
|
}
|
|
102
244
|
|
|
@@ -112,6 +254,7 @@ function applyCrudPayloadToForm(fields = [], model = {}, payload = {}) {
|
|
|
112
254
|
for (const field of normalizeCrudFormFields(fields)) {
|
|
113
255
|
const fieldKey = field.key;
|
|
114
256
|
const fieldType = resolveFormFieldType(field);
|
|
257
|
+
const fieldFormat = resolveFormFieldFormat(field);
|
|
115
258
|
const rawValue = sourcePayload[fieldKey];
|
|
116
259
|
|
|
117
260
|
if (fieldType === "boolean") {
|
|
@@ -124,6 +267,16 @@ function applyCrudPayloadToForm(fields = [], model = {}, payload = {}) {
|
|
|
124
267
|
continue;
|
|
125
268
|
}
|
|
126
269
|
|
|
270
|
+
if (fieldFormat === "date-time") {
|
|
271
|
+
targetModel[fieldKey] = toDateTimeLocalInputValue(rawValue);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (fieldFormat === "time") {
|
|
276
|
+
targetModel[fieldKey] = toTimeInputValue(rawValue);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
127
280
|
targetModel[fieldKey] = rawValue == null ? "" : String(rawValue);
|
|
128
281
|
}
|
|
129
282
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeCrudLookupContainerKey,
|
|
3
|
+
resolveCrudLookupApiPathFromNamespace,
|
|
4
|
+
resolveCrudLookupFieldKeyFromRouteParam
|
|
5
|
+
} from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
6
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
|
+
import { resolveLookupFieldDisplayValue, resolveRecordTitle } from "../crud/crudLookupFieldLabelSupport.js";
|
|
8
|
+
import { resolveRouteParamNamesInOrder, toRouteParamValue } from "../support/routeTemplateHelpers.js";
|
|
9
|
+
|
|
10
|
+
function singularizeLabel(value = "") {
|
|
11
|
+
const normalizedValue = normalizeText(value);
|
|
12
|
+
if (!normalizedValue) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
if (normalizedValue.endsWith("ies")) {
|
|
16
|
+
return `${normalizedValue.slice(0, -3)}y`;
|
|
17
|
+
}
|
|
18
|
+
if (normalizedValue.endsWith("s") && !normalizedValue.endsWith("ss")) {
|
|
19
|
+
return normalizedValue.slice(0, -1);
|
|
20
|
+
}
|
|
21
|
+
return normalizedValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toTitleLabel(value = "") {
|
|
25
|
+
const normalizedValue = normalizeText(value)
|
|
26
|
+
.replace(/Id$/u, "")
|
|
27
|
+
.replace(/[_-]+/gu, " ")
|
|
28
|
+
.replace(/([a-z0-9])([A-Z])/gu, "$1 $2")
|
|
29
|
+
.trim();
|
|
30
|
+
if (!normalizedValue) {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return normalizedValue
|
|
35
|
+
.split(/\s+/u)
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.map((entry) => entry.charAt(0).toUpperCase() + entry.slice(1))
|
|
38
|
+
.join(" ");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveEntityLabel(routeParamKey = "", relationNamespace = "") {
|
|
42
|
+
const routeLabel = singularizeLabel(toTitleLabel(routeParamKey));
|
|
43
|
+
if (routeLabel) {
|
|
44
|
+
return routeLabel;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const namespaceLabel = singularizeLabel(toTitleLabel(relationNamespace));
|
|
48
|
+
if (namespaceLabel) {
|
|
49
|
+
return namespaceLabel;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return "Record";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveLookupFieldMeta(resource = {}, fieldKey = "") {
|
|
56
|
+
const normalizedFieldKey = normalizeText(fieldKey);
|
|
57
|
+
if (!normalizedFieldKey) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const entries = Array.isArray(resource?.fieldMeta) ? resource.fieldMeta : [];
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (normalizeText(entry?.key) !== normalizedFieldKey) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const relation = entry?.relation;
|
|
68
|
+
if (!relation || normalizeText(relation.kind).toLowerCase() !== "lookup") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return entry;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveCrudListParentDescriptor({ resource = {}, route = null, recordIdParam = "recordId" } = {}) {
|
|
79
|
+
const orderedRouteParamNames = resolveRouteParamNamesInOrder(route);
|
|
80
|
+
if (orderedRouteParamNames.length < 1) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const normalizedRecordIdParam = normalizeText(recordIdParam) || "recordId";
|
|
85
|
+
for (const routeParamKey of [...orderedRouteParamNames].reverse()) {
|
|
86
|
+
if (routeParamKey === "workspaceSlug" || routeParamKey === normalizedRecordIdParam) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const fieldKey = resolveCrudLookupFieldKeyFromRouteParam(resource, routeParamKey);
|
|
91
|
+
if (!fieldKey) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const fieldMeta = resolveLookupFieldMeta(resource, fieldKey);
|
|
96
|
+
if (!fieldMeta) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const relation = fieldMeta.relation || {};
|
|
101
|
+
const relationNamespace = normalizeText(relation.namespace);
|
|
102
|
+
const containerKey = normalizeCrudLookupContainerKey(relation.containerKey, {
|
|
103
|
+
defaultValue: resource?.contract?.lookup?.containerKey || "lookups"
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
fieldKey,
|
|
108
|
+
routeParamKey,
|
|
109
|
+
relationNamespace,
|
|
110
|
+
entityLabel: resolveEntityLabel(routeParamKey, relationNamespace),
|
|
111
|
+
labelKey: normalizeText(relation.labelKey),
|
|
112
|
+
fieldDescriptor: Object.freeze({
|
|
113
|
+
key: fieldKey,
|
|
114
|
+
relation: Object.freeze({
|
|
115
|
+
kind: "lookup",
|
|
116
|
+
valueKey: normalizeText(relation.valueKey) || "id",
|
|
117
|
+
labelKey: normalizeText(relation.labelKey),
|
|
118
|
+
containerKey
|
|
119
|
+
})
|
|
120
|
+
}),
|
|
121
|
+
apiUrlTemplate: relationNamespace
|
|
122
|
+
? `${resolveCrudLookupApiPathFromNamespace(relationNamespace)}/:${routeParamKey}`
|
|
123
|
+
: ""
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveCrudListParentTitleFromItems(items = [], descriptor = null) {
|
|
131
|
+
const sourceItems = Array.isArray(items) ? items : [];
|
|
132
|
+
if (!descriptor?.fieldDescriptor) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const item of sourceItems) {
|
|
137
|
+
const resolvedTitle = normalizeText(resolveLookupFieldDisplayValue(item, descriptor.fieldDescriptor));
|
|
138
|
+
if (resolvedTitle) {
|
|
139
|
+
return resolvedTitle;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveCrudListParentRecordTitle(record = {}, descriptor = null) {
|
|
147
|
+
const resolvedTitle = resolveRecordTitle(record, {
|
|
148
|
+
fallbackKey: normalizeText(descriptor?.labelKey),
|
|
149
|
+
defaultValue: ""
|
|
150
|
+
});
|
|
151
|
+
if (resolvedTitle && resolvedTitle !== "-") {
|
|
152
|
+
return resolvedTitle;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const entityLabel = normalizeText(descriptor?.entityLabel) || "Record";
|
|
156
|
+
const recordId = toRouteParamValue(record?.id);
|
|
157
|
+
if (recordId) {
|
|
158
|
+
return `${entityLabel} #${recordId}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export {
|
|
165
|
+
resolveCrudListParentDescriptor,
|
|
166
|
+
resolveCrudListParentRecordTitle,
|
|
167
|
+
resolveCrudListParentTitleFromItems
|
|
168
|
+
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
resolvePermissionAccess,
|
|
8
8
|
resolveEnabled,
|
|
9
9
|
resolveQueryKey
|
|
10
|
-
} from "../scopeHelpers.js";
|
|
10
|
+
} from "../support/scopeHelpers.js";
|
|
11
11
|
|
|
12
12
|
function normalizePermissionSets(permissionSets = {}) {
|
|
13
13
|
const source = permissionSets && typeof permissionSets === "object" && !Array.isArray(permissionSets)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { computed, proxyRefs } from "vue";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
3
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
4
|
-
import { useAddEditCore } from "
|
|
5
|
-
import { useEndpointResource } from "
|
|
6
|
-
import { resolveOperationAdapter } from "
|
|
7
|
-
import { createAddEditUiRuntime } from "
|
|
8
|
-
import { useUiFeedback } from "
|
|
9
|
-
import { useFieldErrorBag } from "
|
|
4
|
+
import { useAddEditCore } from "../runtime/useAddEditCore.js";
|
|
5
|
+
import { useEndpointResource } from "../runtime/useEndpointResource.js";
|
|
6
|
+
import { resolveOperationAdapter } from "../runtime/operationAdapters.js";
|
|
7
|
+
import { createAddEditUiRuntime } from "../runtime/addEditUiRuntime.js";
|
|
8
|
+
import { useUiFeedback } from "../runtime/useUiFeedback.js";
|
|
9
|
+
import { useFieldErrorBag } from "../runtime/useFieldErrorBag.js";
|
|
10
10
|
import {
|
|
11
11
|
setupRouteChangeCleanup,
|
|
12
12
|
setupOperationErrorReporting
|
|
13
|
-
} from "
|
|
13
|
+
} from "../runtime/operationUiHelpers.js";
|
|
14
14
|
import {
|
|
15
15
|
resolveResourceMessages
|
|
16
|
-
} from "
|
|
17
|
-
import { resolveRouteParamNamesInOrder } from "
|
|
16
|
+
} from "../support/scopeHelpers.js";
|
|
17
|
+
import { resolveRouteParamNamesInOrder } from "../support/routeTemplateHelpers.js";
|
|
18
18
|
|
|
19
19
|
function useAddEdit({
|
|
20
20
|
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { computed, proxyRefs, reactive, watch } from "vue";
|
|
2
2
|
import { useRoute, useRouter } from "vue-router";
|
|
3
|
-
import { asPlainObject } from "
|
|
3
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
4
4
|
import { useAddEdit } from "./useAddEdit.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveCrudBoundValues,
|
|
7
|
+
} from "../crud/crudBindingSupport.js";
|
|
5
8
|
import {
|
|
6
9
|
normalizeCrudFormFields,
|
|
7
10
|
createCrudFormModel,
|
|
8
11
|
buildCrudFormPayload,
|
|
9
12
|
applyCrudPayloadToForm,
|
|
10
|
-
|
|
13
|
+
resolveCrudRouteBoundFieldValues,
|
|
11
14
|
resolveCrudFieldErrors,
|
|
12
15
|
parseCrudResourceOperationInput
|
|
13
|
-
} from "
|
|
14
|
-
import { hasResolvedQueryData } from "
|
|
16
|
+
} from "../crud/crudSchemaFormHelpers.js";
|
|
17
|
+
import { hasResolvedQueryData } from "../support/resourceLoadStateHelpers.js";
|
|
15
18
|
|
|
16
19
|
function normalizeFieldErrorKeys(keys = []) {
|
|
17
20
|
return Array.isArray(keys)
|
|
@@ -36,12 +39,13 @@ function normalizeSaveSuccessOptions(options = {}) {
|
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
function
|
|
42
|
+
function useCrudAddEdit({
|
|
40
43
|
resource = null,
|
|
41
44
|
operationName = "",
|
|
42
45
|
formFields = [],
|
|
43
46
|
addEditOptions = {},
|
|
44
47
|
saveSuccess = {},
|
|
48
|
+
fieldBinding = null,
|
|
45
49
|
createModel = null,
|
|
46
50
|
buildPayload = null,
|
|
47
51
|
mapPayloadToModel = null,
|
|
@@ -61,18 +65,31 @@ function useCrudSchemaForm({
|
|
|
61
65
|
? asPlainObject(createModel(normalizedFields))
|
|
62
66
|
: createCrudFormModel(normalizedFields);
|
|
63
67
|
const form = hasProvidedModel ? providedModel : reactive(defaultModel);
|
|
68
|
+
const boundFieldValues = computed(() => {
|
|
69
|
+
return resolveCrudBoundValues({
|
|
70
|
+
binding: fieldBinding,
|
|
71
|
+
routeValues: resolveCrudRouteBoundFieldValues(normalizedFields, route?.params || {}),
|
|
72
|
+
context: Object.freeze({
|
|
73
|
+
route,
|
|
74
|
+
fields: normalizedFields
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
});
|
|
64
78
|
|
|
65
|
-
function
|
|
66
|
-
|
|
79
|
+
function applyBoundFieldValues(target = {}) {
|
|
80
|
+
Object.assign(target, boundFieldValues.value);
|
|
81
|
+
return target;
|
|
67
82
|
}
|
|
68
83
|
|
|
69
|
-
applyRouteBoundValues(form);
|
|
70
84
|
watch(
|
|
71
|
-
|
|
85
|
+
boundFieldValues,
|
|
72
86
|
() => {
|
|
73
|
-
|
|
87
|
+
applyBoundFieldValues(form);
|
|
74
88
|
},
|
|
75
|
-
{
|
|
89
|
+
{
|
|
90
|
+
immediate: true,
|
|
91
|
+
deep: true
|
|
92
|
+
}
|
|
76
93
|
);
|
|
77
94
|
const parseInputOverride = typeof parseInput === "function"
|
|
78
95
|
? parseInput
|
|
@@ -109,7 +126,7 @@ function useCrudSchemaForm({
|
|
|
109
126
|
fields: normalizedFields
|
|
110
127
|
})
|
|
111
128
|
: buildCrudFormPayload(normalizedFields, model);
|
|
112
|
-
|
|
129
|
+
applyBoundFieldValues(payload);
|
|
113
130
|
|
|
114
131
|
return payload;
|
|
115
132
|
}
|
|
@@ -120,12 +137,12 @@ function useCrudSchemaForm({
|
|
|
120
137
|
...context,
|
|
121
138
|
fields: normalizedFields
|
|
122
139
|
});
|
|
123
|
-
|
|
140
|
+
applyBoundFieldValues(model);
|
|
124
141
|
}
|
|
125
142
|
: (shouldApplyDefaultMapPayload
|
|
126
143
|
? (model = {}, payload = {}) => {
|
|
127
144
|
applyCrudPayloadToForm(normalizedFields, model, payload);
|
|
128
|
-
|
|
145
|
+
applyBoundFieldValues(model);
|
|
129
146
|
}
|
|
130
147
|
: undefined);
|
|
131
148
|
|
|
@@ -200,4 +217,4 @@ function useCrudSchemaForm({
|
|
|
200
217
|
});
|
|
201
218
|
}
|
|
202
219
|
|
|
203
|
-
export {
|
|
220
|
+
export { useCrudAddEdit };
|