@jskit-ai/users-web 0.1.37 → 0.1.38
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 -7
- package/package.json +16 -12
- 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/support/menuLinkTarget.js +93 -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/{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/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
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.38",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Users web module: workspace selector shell element plus workspace/profile/members UI elements.",
|
|
7
7
|
dependsOn: [
|
|
@@ -231,12 +231,12 @@ export default Object.freeze({
|
|
|
231
231
|
runtime: {
|
|
232
232
|
"@tanstack/vue-query": "5.92.12",
|
|
233
233
|
"@mdi/js": "^7.4.47",
|
|
234
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
235
|
-
"@jskit-ai/realtime": "0.1.
|
|
236
|
-
"@jskit-ai/kernel": "0.1.
|
|
237
|
-
"@jskit-ai/shell-web": "0.1.
|
|
238
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
239
|
-
"@jskit-ai/users-core": "0.1.
|
|
234
|
+
"@jskit-ai/http-runtime": "0.1.23",
|
|
235
|
+
"@jskit-ai/realtime": "0.1.23",
|
|
236
|
+
"@jskit-ai/kernel": "0.1.24",
|
|
237
|
+
"@jskit-ai/shell-web": "0.1.23",
|
|
238
|
+
"@jskit-ai/uploads-image-web": "0.1.2",
|
|
239
|
+
"@jskit-ai/users-core": "0.1.33",
|
|
240
240
|
"vuetify": "^4.0.0"
|
|
241
241
|
},
|
|
242
242
|
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.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -8,26 +8,30 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
"./client": "./src/client/index.js",
|
|
10
10
|
"./client/components/WorkspaceMembersClientElement": "./src/client/components/WorkspaceMembersClientElement.vue",
|
|
11
|
-
"./client/composables/useAddEdit": "./src/client/composables/useAddEdit.js",
|
|
12
|
-
"./client/composables/
|
|
13
|
-
"./client/composables/crudLookupFieldRuntime": "./src/client/composables/crudLookupFieldRuntime.js",
|
|
14
|
-
"./client/composables/useList": "./src/client/composables/useList.js",
|
|
15
|
-
"./client/composables/
|
|
11
|
+
"./client/composables/useAddEdit": "./src/client/composables/records/useAddEdit.js",
|
|
12
|
+
"./client/composables/useCrudAddEdit": "./src/client/composables/records/useCrudAddEdit.js",
|
|
13
|
+
"./client/composables/crudLookupFieldRuntime": "./src/client/composables/crud/crudLookupFieldRuntime.js",
|
|
14
|
+
"./client/composables/useList": "./src/client/composables/records/useList.js",
|
|
15
|
+
"./client/composables/useCrudList": "./src/client/composables/records/useCrudList.js",
|
|
16
|
+
"./client/composables/useCrudListParentTitle": "./src/client/composables/useCrudListParentTitle.js",
|
|
17
|
+
"./client/composables/useView": "./src/client/composables/records/useView.js",
|
|
18
|
+
"./client/composables/useCrudView": "./src/client/composables/records/useCrudView.js",
|
|
16
19
|
"./client/composables/usePagedCollection": "./src/client/composables/usePagedCollection.js",
|
|
17
20
|
"./client/composables/useAccountSettingsRuntime": "./src/client/composables/useAccountSettingsRuntime.js",
|
|
18
21
|
"./client/composables/usePaths": "./src/client/composables/usePaths.js",
|
|
19
22
|
"./client/composables/useWorkspaceRouteContext": "./src/client/composables/useWorkspaceRouteContext.js",
|
|
23
|
+
"./client/support/menuLinkTarget": "./src/client/support/menuLinkTarget.js",
|
|
20
24
|
"./client/support/realtimeWorkspace": "./src/client/support/realtimeWorkspace.js"
|
|
21
25
|
},
|
|
22
26
|
"dependencies": {
|
|
23
27
|
"@tanstack/vue-query": "5.92.12",
|
|
24
28
|
"@mdi/js": "^7.4.47",
|
|
25
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
26
|
-
"@jskit-ai/kernel": "0.1.
|
|
27
|
-
"@jskit-ai/realtime": "0.1.
|
|
28
|
-
"@jskit-ai/shell-web": "0.1.
|
|
29
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
30
|
-
"@jskit-ai/users-core": "0.1.
|
|
29
|
+
"@jskit-ai/http-runtime": "0.1.23",
|
|
30
|
+
"@jskit-ai/kernel": "0.1.24",
|
|
31
|
+
"@jskit-ai/realtime": "0.1.23",
|
|
32
|
+
"@jskit-ai/shell-web": "0.1.23",
|
|
33
|
+
"@jskit-ai/uploads-image-web": "0.1.2",
|
|
34
|
+
"@jskit-ai/users-core": "0.1.33",
|
|
31
35
|
"vuetify": "^4.0.0"
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { useRoute } from "vue-router";
|
|
3
4
|
import { useWebPlacementContext } from "@jskit-ai/shell-web/client/placement";
|
|
4
5
|
import { usePaths } from "../composables/usePaths.js";
|
|
5
|
-
import { surfaceRequiresWorkspaceFromPlacementContext } from "../lib/workspaceSurfaceContext.js";
|
|
6
6
|
import { resolveMenuLinkIcon } from "../lib/menuIcons.js";
|
|
7
|
+
import { resolveMenuLinkTarget } from "../support/menuLinkTarget.js";
|
|
7
8
|
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
label: {
|
|
@@ -36,34 +37,22 @@ const props = defineProps({
|
|
|
36
37
|
}
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
const route = useRoute();
|
|
39
41
|
const paths = usePaths();
|
|
40
42
|
const { context: placementContext } = useWebPlacementContext();
|
|
41
43
|
|
|
42
|
-
const targetSurfaceId = computed(() => {
|
|
43
|
-
const explicitSurface = String(props.surface || "").trim().toLowerCase();
|
|
44
|
-
if (explicitSurface && explicitSurface !== "*") {
|
|
45
|
-
return explicitSurface;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return String(paths.currentSurfaceId.value || "").trim().toLowerCase();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
44
|
const resolvedTo = computed(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return paths.page(normalizedSuffix, {
|
|
65
|
-
surface: targetSurfaceId.value,
|
|
66
|
-
mode: "auto"
|
|
45
|
+
return resolveMenuLinkTarget({
|
|
46
|
+
to: props.to,
|
|
47
|
+
surface: props.surface,
|
|
48
|
+
currentSurfaceId: paths.currentSurfaceId.value,
|
|
49
|
+
placementContext: placementContext.value,
|
|
50
|
+
workspaceSuffix: props.workspaceSuffix,
|
|
51
|
+
nonWorkspaceSuffix: props.nonWorkspaceSuffix,
|
|
52
|
+
routeParams: route.params || {},
|
|
53
|
+
resolvePagePath(relativePath, options = {}) {
|
|
54
|
+
return paths.page(relativePath, options);
|
|
55
|
+
}
|
|
67
56
|
});
|
|
68
57
|
});
|
|
69
58
|
|
|
@@ -23,11 +23,11 @@ import { computed, reactive, ref, watch } from "vue";
|
|
|
23
23
|
import { formatDateTime } from "@jskit-ai/kernel/shared/support";
|
|
24
24
|
import MembersAdminClientElement from "./MembersAdminClientElement.vue";
|
|
25
25
|
import { useCommand } from "../composables/useCommand.js";
|
|
26
|
-
import { useList } from "../composables/useList.js";
|
|
27
|
-
import { useView } from "../composables/useView.js";
|
|
26
|
+
import { useList } from "../composables/records/useList.js";
|
|
27
|
+
import { useView } from "../composables/records/useView.js";
|
|
28
28
|
import { usePaths } from "../composables/usePaths.js";
|
|
29
29
|
import { useAccess } from "../composables/useAccess.js";
|
|
30
|
-
import { useUiFeedback } from "../composables/useUiFeedback.js";
|
|
30
|
+
import { useUiFeedback } from "../composables/runtime/useUiFeedback.js";
|
|
31
31
|
import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
|
|
32
32
|
import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
33
33
|
import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
|
|
@@ -71,7 +71,7 @@ import { computed, reactive } from "vue";
|
|
|
71
71
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
72
72
|
import { workspaceResource } from "@jskit-ai/users-core/shared/resources/workspaceResource";
|
|
73
73
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
74
|
-
import { useAddEdit } from "../composables/useAddEdit.js";
|
|
74
|
+
import { useAddEdit } from "../composables/records/useAddEdit.js";
|
|
75
75
|
import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
|
|
76
76
|
|
|
77
77
|
const emit = defineEmits(["saved"]);
|
|
@@ -176,7 +176,7 @@ import {
|
|
|
176
176
|
DEFAULT_WORKSPACE_LIGHT_PALETTE,
|
|
177
177
|
resolveWorkspaceThemePalettes
|
|
178
178
|
} from "@jskit-ai/users-core/shared/settings";
|
|
179
|
-
import { useAddEdit } from "../composables/useAddEdit.js";
|
|
179
|
+
import { useAddEdit } from "../composables/records/useAddEdit.js";
|
|
180
180
|
import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
|
|
181
181
|
import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
|
|
182
182
|
import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
9
9
|
import { normalizeWorkspaceList } from "../lib/bootstrap.js";
|
|
10
10
|
import { useCommand } from "../composables/useCommand.js";
|
|
11
|
-
import { useView } from "../composables/useView.js";
|
|
11
|
+
import { useView } from "../composables/records/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
15
|
import { USERS_ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
16
|
-
import { normalizePendingInvite } from "../composables/accountSettingsRuntimeHelpers.js";
|
|
16
|
+
import { normalizePendingInvite } from "../composables/account-settings/accountSettingsRuntimeHelpers.js";
|
|
17
17
|
|
|
18
18
|
const route = useRoute();
|
|
19
19
|
const router = useRouter();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import "@jskit-ai/uploads-image-web/client/styles";
|
|
2
|
+
import { createManagedImageAssetRuntime } from "@jskit-ai/uploads-image-web/client/composables/createManagedImageAssetRuntime";
|
|
3
|
+
import { resolveFieldErrors } from "@jskit-ai/http-runtime/client";
|
|
4
|
+
import { usersWebHttpClient } from "../../lib/httpClient.js";
|
|
5
|
+
|
|
6
|
+
function createAccountSettingsAvatarUploadRuntime({
|
|
7
|
+
queryClient,
|
|
8
|
+
sessionQueryKey,
|
|
9
|
+
accountSettingsQueryKey,
|
|
10
|
+
selectedAvatarFileName,
|
|
11
|
+
applySettingsData,
|
|
12
|
+
reportAccountFeedback
|
|
13
|
+
} = {}) {
|
|
14
|
+
async function resolveCsrfToken() {
|
|
15
|
+
const sessionPayload = await queryClient.fetchQuery({
|
|
16
|
+
queryKey: sessionQueryKey,
|
|
17
|
+
queryFn: () =>
|
|
18
|
+
usersWebHttpClient.request("/api/session", {
|
|
19
|
+
method: "GET"
|
|
20
|
+
}),
|
|
21
|
+
staleTime: 60_000
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const csrfToken = String(sessionPayload?.csrfToken || "");
|
|
25
|
+
if (!csrfToken) {
|
|
26
|
+
throw new Error("Unable to prepare secure avatar upload request.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return csrfToken;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return createManagedImageAssetRuntime({
|
|
33
|
+
uploadEndpoint: "/api/settings/profile/avatar",
|
|
34
|
+
fieldName: "avatar",
|
|
35
|
+
selectedFileName: selectedAvatarFileName,
|
|
36
|
+
resolveRequestHeaders: async () => ({
|
|
37
|
+
"csrf-token": await resolveCsrfToken()
|
|
38
|
+
}),
|
|
39
|
+
onUploadSuccess: ({ data }) => {
|
|
40
|
+
applySettingsData(data);
|
|
41
|
+
queryClient.setQueryData(accountSettingsQueryKey, data);
|
|
42
|
+
},
|
|
43
|
+
reportFeedback: reportAccountFeedback,
|
|
44
|
+
resolveUploadErrorMessage: ({ error, response, defaultMessage }) => {
|
|
45
|
+
const body = response?.body && typeof response.body === "object" ? response.body : {};
|
|
46
|
+
const fieldErrors = resolveFieldErrors(body);
|
|
47
|
+
return String(fieldErrors.avatar || body?.error || error?.message || defaultMessage);
|
|
48
|
+
},
|
|
49
|
+
messages: {
|
|
50
|
+
uploadSuccess: "Avatar uploaded.",
|
|
51
|
+
uploadInvalidResponse: "Avatar uploaded, but the response payload was invalid.",
|
|
52
|
+
uploadError: "Unable to upload avatar.",
|
|
53
|
+
uploadRestriction: "Selected avatar file does not meet upload restrictions.",
|
|
54
|
+
editorUnavailable: "Avatar editor is unavailable in this environment.",
|
|
55
|
+
changeError: "Avatar uploaded, but the settings view could not refresh."
|
|
56
|
+
},
|
|
57
|
+
source: "users-web.account-settings-runtime:avatar"
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { createAccountSettingsAvatarUploadRuntime };
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
normalizeReturnToPath as normalizeSharedReturnToPath,
|
|
4
4
|
resolveAllowedOriginsFromPlacementContext
|
|
5
5
|
} from "@jskit-ai/kernel/shared/support";
|
|
6
|
-
import { normalizeRecord } from "
|
|
6
|
+
import { normalizeRecord } from "../../support/runtimeNormalization.js";
|
|
7
7
|
|
|
8
8
|
function normalizeReturnToPath(value, { fallback = "/", accountSettingsPath = "/account", allowedOrigins = [] } = {}) {
|
|
9
9
|
return normalizeSharedReturnToPath(value, {
|
|
@@ -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
|
}
|