@jskit-ai/users-web 0.1.36 → 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 +8 -22
- package/package.json +16 -11
- package/src/client/components/MembersAdminClientElement.vue +5 -5
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +14 -25
- package/src/client/components/WorkspaceMembersClientElement.vue +19 -19
- package/src/client/components/WorkspaceProfileClientElement.vue +1 -1
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +1 -1
- package/src/client/components/WorkspacesClientElement.vue +4 -4
- 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/{accountSettingsRuntimeConstants.js → account-settings/accountSettingsRuntimeConstants.js} +0 -4
- package/src/client/composables/{accountSettingsRuntimeHelpers.js → account-settings/accountSettingsRuntimeHelpers.js} +2 -2
- package/src/client/composables/crud/crudBindingSupport.js +75 -0
- package/src/client/composables/{crudLookupFieldLabelSupport.js → crud/crudLookupFieldLabelSupport.js} +37 -5
- package/src/client/composables/{crudLookupFieldRuntime.js → crud/crudLookupFieldRuntime.js} +11 -4
- package/src/client/composables/{crudSchemaFormHelpers.js → crud/crudSchemaFormHelpers.js} +178 -5
- 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} +18 -8
- 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/records/useList.js +482 -0
- package/src/client/composables/{useView.js → records/useView.js} +7 -7
- package/src/client/composables/{addEditUiRuntime.js → runtime/addEditUiRuntime.js} +13 -4
- package/src/client/composables/{listUiRuntime.js → runtime/listUiRuntime.js} +20 -8
- 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} +13 -4
- package/src/client/composables/support/listQueryParamSupport.js +459 -0
- package/src/client/composables/{routeTemplateHelpers.js → support/routeTemplateHelpers.js} +122 -0
- 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 +58 -7
- package/src/client/composables/useScopeRuntime.js +1 -1
- package/src/client/lib/bootstrap.js +1 -1
- package/src/client/lib/menuIcons.js +27 -6
- package/src/client/support/menuLinkTarget.js +93 -0
- package/templates/src/components/WorkspaceNotFoundCard.vue +2 -1
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +1 -1
- package/test/addEditUiRuntime.test.js +19 -1
- package/test/crudBindingSupport.test.js +110 -0
- package/test/crudLookupFieldRuntime.test.js +52 -2
- package/test/errorMessageHelpers.test.js +1 -1
- package/test/exportsContract.test.js +10 -1
- package/test/listQueryParamSupport.test.js +190 -0
- package/test/listUiRuntime.test.js +22 -1
- package/test/menuIcons.test.js +2 -0
- 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 +57 -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/usePagedCollection.test.js +53 -0
- package/test/viewCoreLoading.test.js +1 -1
- package/test/viewUiRuntime.test.js +36 -1
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +0 -241
- package/src/client/composables/useList.js +0 -268
- /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/{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/{scopeHelpers.js → support/scopeHelpers.js} +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { useRoute } from "vue-router";
|
|
3
|
+
import { resolveLookupFieldDisplayValue } from "../crud/crudLookupFieldLabelSupport.js";
|
|
4
|
+
import { resolveCrudBoundValues } from "../crud/crudBindingSupport.js";
|
|
5
|
+
import { resolveCrudListParentDescriptor } from "../internal/crudListParentTitleSupport.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveRouteParamsSource,
|
|
8
|
+
toRouteParamValue
|
|
9
|
+
} from "../support/routeTemplateHelpers.js";
|
|
10
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
11
|
+
import { useList } from "./useList.js";
|
|
12
|
+
|
|
13
|
+
function resolveRequestQueryParamsInput(requestQueryParams, context = {}) {
|
|
14
|
+
if (typeof requestQueryParams === "function") {
|
|
15
|
+
return asPlainObject(requestQueryParams(context));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return asPlainObject(unref(requestQueryParams));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveCrudParentRequestQueryParams({ resource = {}, route = null, recordIdParam = "recordId" } = {}) {
|
|
22
|
+
const descriptor = resolveCrudListParentDescriptor({
|
|
23
|
+
resource,
|
|
24
|
+
route,
|
|
25
|
+
recordIdParam
|
|
26
|
+
});
|
|
27
|
+
if (!descriptor?.fieldKey || !descriptor?.routeParamKey) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const routeParams = resolveRouteParamsSource(route?.params || {});
|
|
32
|
+
const routeParamValue = toRouteParamValue(routeParams[descriptor.routeParamKey]);
|
|
33
|
+
if (!routeParamValue) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
[descriptor.fieldKey]: routeParamValue
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function useCrudList({
|
|
43
|
+
resource = null,
|
|
44
|
+
requestQueryParams = null,
|
|
45
|
+
parentBinding = null,
|
|
46
|
+
recordIdParam = "recordId",
|
|
47
|
+
route = null,
|
|
48
|
+
...listOptions
|
|
49
|
+
} = {}) {
|
|
50
|
+
const sourceRoute = route && typeof route === "object" ? route : useRoute();
|
|
51
|
+
const boundParentRequestQueryParams = computed(() => {
|
|
52
|
+
return resolveCrudBoundValues({
|
|
53
|
+
binding: parentBinding,
|
|
54
|
+
routeValues: resolveCrudParentRequestQueryParams({
|
|
55
|
+
resource,
|
|
56
|
+
route: sourceRoute,
|
|
57
|
+
recordIdParam
|
|
58
|
+
}),
|
|
59
|
+
context: Object.freeze({
|
|
60
|
+
route: sourceRoute,
|
|
61
|
+
resource,
|
|
62
|
+
recordIdParam
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
const records = useList({
|
|
67
|
+
...listOptions,
|
|
68
|
+
recordIdParam,
|
|
69
|
+
requestQueryParams(context = {}) {
|
|
70
|
+
const baseRequestQueryParams = resolveRequestQueryParamsInput(requestQueryParams, context);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...baseRequestQueryParams,
|
|
74
|
+
...boundParentRequestQueryParams.value
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
records.resolveFieldDisplay = resolveLookupFieldDisplayValue;
|
|
80
|
+
return records;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { useCrudList };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { useRoute } from "vue-router";
|
|
3
|
+
import {
|
|
4
|
+
resolveLookupFieldDisplayValue,
|
|
5
|
+
resolveRecordTitle
|
|
6
|
+
} from "../crud/crudLookupFieldLabelSupport.js";
|
|
7
|
+
import { resolveCrudBoundValues } from "../crud/crudBindingSupport.js";
|
|
8
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
9
|
+
import { useView } from "./useView.js";
|
|
10
|
+
|
|
11
|
+
function useCrudView({
|
|
12
|
+
paramBinding = null,
|
|
13
|
+
route = null,
|
|
14
|
+
...viewOptions
|
|
15
|
+
} = {}) {
|
|
16
|
+
const sourceRoute = route && typeof route === "object" ? route : useRoute();
|
|
17
|
+
const boundRouteParams = computed(() => {
|
|
18
|
+
return resolveCrudBoundValues({
|
|
19
|
+
binding: paramBinding,
|
|
20
|
+
routeValues: asPlainObject(sourceRoute?.params || {}),
|
|
21
|
+
context: Object.freeze({
|
|
22
|
+
route: sourceRoute
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const view = useView({
|
|
27
|
+
...viewOptions,
|
|
28
|
+
routeParams: boundRouteParams
|
|
29
|
+
});
|
|
30
|
+
view.resolveFieldDisplay = resolveLookupFieldDisplayValue;
|
|
31
|
+
view.resolveRecordTitle = resolveRecordTitle;
|
|
32
|
+
return view;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { useCrudView };
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { computed, onScopeDispose, proxyRefs, ref, watch } from "vue";
|
|
2
|
+
import { useRouter } from "vue-router";
|
|
3
|
+
import { appendQueryString } from "@jskit-ai/kernel/shared/support";
|
|
4
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
6
|
+
import { useListCore } from "../runtime/useListCore.js";
|
|
7
|
+
import { resolveOperationAdapter } from "../runtime/operationAdapters.js";
|
|
8
|
+
import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
9
|
+
import { createListUiRuntime } from "../runtime/listUiRuntime.js";
|
|
10
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
11
|
+
import {
|
|
12
|
+
normalizeListSearchConfig,
|
|
13
|
+
matchesLocalSearch
|
|
14
|
+
} from "../support/listSearchSupport.js";
|
|
15
|
+
import {
|
|
16
|
+
normalizeListSyncToRouteConfig,
|
|
17
|
+
resolveQueryParamDescriptors,
|
|
18
|
+
resolveActiveQueryParamEntries,
|
|
19
|
+
resolveWritableQueryParamBindings,
|
|
20
|
+
buildQueryParamEntriesToken,
|
|
21
|
+
parseRouteBindingValue,
|
|
22
|
+
areQueryParamBindingValuesEqual,
|
|
23
|
+
buildRouteQueryCompareToken,
|
|
24
|
+
mergeManagedQueryParamKeyHistory,
|
|
25
|
+
resolveRouteSyncManagedKeys
|
|
26
|
+
} from "../support/listQueryParamSupport.js";
|
|
27
|
+
import {
|
|
28
|
+
resolveRouteParamNamesInOrder,
|
|
29
|
+
} from "../support/routeTemplateHelpers.js";
|
|
30
|
+
|
|
31
|
+
const EMPTY_ROUTE_SYNC_QUERY_PARAM_BLACKLIST = Object.freeze([]);
|
|
32
|
+
|
|
33
|
+
function useList({
|
|
34
|
+
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
35
|
+
surfaceId = "",
|
|
36
|
+
access = "auto",
|
|
37
|
+
apiSuffix = "",
|
|
38
|
+
queryKeyFactory = null,
|
|
39
|
+
viewPermissions = [],
|
|
40
|
+
readEnabled = true,
|
|
41
|
+
placementSource = "users-web.list",
|
|
42
|
+
fallbackLoadError = "Unable to load list.",
|
|
43
|
+
initialPageParam = null,
|
|
44
|
+
getNextPageParam,
|
|
45
|
+
selectItems,
|
|
46
|
+
requestOptions,
|
|
47
|
+
queryOptions,
|
|
48
|
+
realtime = null,
|
|
49
|
+
adapter = null,
|
|
50
|
+
recordIdParam = "recordId",
|
|
51
|
+
recordIdSelector = null,
|
|
52
|
+
viewUrlTemplate = "",
|
|
53
|
+
editUrlTemplate = "",
|
|
54
|
+
search = null,
|
|
55
|
+
queryParams = null,
|
|
56
|
+
requestQueryParams = null,
|
|
57
|
+
syncToRoute = false
|
|
58
|
+
} = {}) {
|
|
59
|
+
const searchConfig = normalizeListSearchConfig(search);
|
|
60
|
+
const routeSyncConfig = normalizeListSyncToRouteConfig(syncToRoute, {
|
|
61
|
+
defaultSearchParam: searchConfig.queryParam
|
|
62
|
+
});
|
|
63
|
+
const router = routeSyncConfig.enabled === true ? useRouter() : null;
|
|
64
|
+
const searchQuery = ref(searchConfig.initialQuery);
|
|
65
|
+
const debouncedSearchQuery = ref(searchConfig.initialQuery);
|
|
66
|
+
let searchDebounceTimer = null;
|
|
67
|
+
const isSearchDebouncing = ref(false);
|
|
68
|
+
|
|
69
|
+
watch(searchQuery, (value) => {
|
|
70
|
+
const normalizedValue = normalizeText(value);
|
|
71
|
+
if (searchDebounceTimer) {
|
|
72
|
+
clearTimeout(searchDebounceTimer);
|
|
73
|
+
}
|
|
74
|
+
if (searchConfig.enabled !== true) {
|
|
75
|
+
debouncedSearchQuery.value = normalizedValue;
|
|
76
|
+
isSearchDebouncing.value = false;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
isSearchDebouncing.value = true;
|
|
80
|
+
searchDebounceTimer = setTimeout(() => {
|
|
81
|
+
debouncedSearchQuery.value = normalizedValue;
|
|
82
|
+
isSearchDebouncing.value = false;
|
|
83
|
+
searchDebounceTimer = null;
|
|
84
|
+
}, searchConfig.debounceMs);
|
|
85
|
+
}, { immediate: true });
|
|
86
|
+
|
|
87
|
+
onScopeDispose(() => {
|
|
88
|
+
if (!searchDebounceTimer) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
clearTimeout(searchDebounceTimer);
|
|
92
|
+
searchDebounceTimer = null;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const operationAdapter = resolveOperationAdapter(adapter, {
|
|
96
|
+
context: "useList adapter"
|
|
97
|
+
});
|
|
98
|
+
const operationScope = operationAdapter.useOperationScope({
|
|
99
|
+
ownershipFilter,
|
|
100
|
+
surfaceId,
|
|
101
|
+
access,
|
|
102
|
+
placementSource,
|
|
103
|
+
apiSuffix,
|
|
104
|
+
readEnabled,
|
|
105
|
+
queryKeyFactory,
|
|
106
|
+
permissionSets: {
|
|
107
|
+
view: viewPermissions
|
|
108
|
+
},
|
|
109
|
+
realtime
|
|
110
|
+
});
|
|
111
|
+
if (
|
|
112
|
+
routeSyncConfig.enabled === true &&
|
|
113
|
+
routeSyncConfig.hydrateFromRoute === true &&
|
|
114
|
+
routeSyncConfig.syncSearch === true &&
|
|
115
|
+
searchConfig.enabled === true
|
|
116
|
+
) {
|
|
117
|
+
const routeQuerySource = asPlainObject(operationScope.routeContext.route?.query || {});
|
|
118
|
+
const routeSearchValue = routeQuerySource[routeSyncConfig.searchParam];
|
|
119
|
+
const hydratedSearch = normalizeText(Array.isArray(routeSearchValue) ? routeSearchValue[0] : routeSearchValue);
|
|
120
|
+
|
|
121
|
+
if (searchDebounceTimer) {
|
|
122
|
+
clearTimeout(searchDebounceTimer);
|
|
123
|
+
searchDebounceTimer = null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
searchQuery.value = hydratedSearch;
|
|
127
|
+
debouncedSearchQuery.value = hydratedSearch;
|
|
128
|
+
isSearchDebouncing.value = false;
|
|
129
|
+
}
|
|
130
|
+
const canView = operationScope.permissionGate("view");
|
|
131
|
+
const queryParamsContext = computed(() => {
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
surfaceId: operationScope.routeContext.currentSurfaceId.value,
|
|
134
|
+
workspaceSlug: operationScope.workspaceSlugFromRoute.value,
|
|
135
|
+
ownershipFilter: operationScope.normalizedOwnershipFilter
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
const queryParamDescriptors = computed(() => {
|
|
139
|
+
return resolveQueryParamDescriptors(queryParams, queryParamsContext.value);
|
|
140
|
+
});
|
|
141
|
+
const requestQueryParamDescriptors = computed(() => {
|
|
142
|
+
return resolveQueryParamDescriptors(requestQueryParams, queryParamsContext.value);
|
|
143
|
+
});
|
|
144
|
+
const declaredQueryParamKeys = computed(() => {
|
|
145
|
+
return queryParamDescriptors.value.map((descriptor) => descriptor.key);
|
|
146
|
+
});
|
|
147
|
+
const activeQueryParamEntries = computed(() => {
|
|
148
|
+
return resolveActiveQueryParamEntries(queryParamDescriptors.value);
|
|
149
|
+
});
|
|
150
|
+
const activeRequestQueryParamEntries = computed(() => {
|
|
151
|
+
return resolveActiveQueryParamEntries(requestQueryParamDescriptors.value);
|
|
152
|
+
});
|
|
153
|
+
const activeQueryParamsToken = computed(() => buildQueryParamEntriesToken(activeQueryParamEntries.value));
|
|
154
|
+
const activeRequestQueryParamsToken = computed(() => {
|
|
155
|
+
return buildQueryParamEntriesToken(activeRequestQueryParamEntries.value);
|
|
156
|
+
});
|
|
157
|
+
const writableQueryParamBindings = computed(() => {
|
|
158
|
+
return resolveWritableQueryParamBindings(queryParamDescriptors.value);
|
|
159
|
+
});
|
|
160
|
+
const activeSearchQuery = computed(() => {
|
|
161
|
+
if (searchConfig.enabled !== true) {
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
const normalized = normalizeText(debouncedSearchQuery.value);
|
|
165
|
+
if (!normalized || normalized.length < searchConfig.minLength) {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
return normalized;
|
|
169
|
+
});
|
|
170
|
+
const querySearchEnabled = computed(() => searchConfig.enabled === true && searchConfig.mode === "query");
|
|
171
|
+
const listPath = computed(() => {
|
|
172
|
+
const basePath = normalizeText(operationScope.apiPath.value);
|
|
173
|
+
if (!basePath) {
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const searchParams = new URLSearchParams();
|
|
178
|
+
if (querySearchEnabled.value) {
|
|
179
|
+
const queryValue = activeSearchQuery.value;
|
|
180
|
+
if (queryValue) {
|
|
181
|
+
searchParams.set(searchConfig.queryParam, queryValue);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const entry of activeRequestQueryParamEntries.value) {
|
|
186
|
+
for (const value of entry.values) {
|
|
187
|
+
searchParams.append(entry.key, value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const entry of activeQueryParamEntries.value) {
|
|
191
|
+
for (const value of entry.values) {
|
|
192
|
+
searchParams.append(entry.key, value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const serializedSearch = searchParams.toString();
|
|
197
|
+
if (!serializedSearch) {
|
|
198
|
+
return basePath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return appendQueryString(basePath, serializedSearch);
|
|
202
|
+
});
|
|
203
|
+
const listQueryKey = computed(() => {
|
|
204
|
+
const sourceQueryKey = operationScope.queryKey.value;
|
|
205
|
+
const baseQueryKey = Array.isArray(sourceQueryKey)
|
|
206
|
+
? [...sourceQueryKey]
|
|
207
|
+
: sourceQueryKey == null
|
|
208
|
+
? []
|
|
209
|
+
: [sourceQueryKey];
|
|
210
|
+
if (activeRequestQueryParamsToken.value) {
|
|
211
|
+
baseQueryKey.push("__request_query__", activeRequestQueryParamsToken.value);
|
|
212
|
+
}
|
|
213
|
+
if (querySearchEnabled.value) {
|
|
214
|
+
baseQueryKey.push("__search__", searchConfig.queryParam, activeSearchQuery.value);
|
|
215
|
+
}
|
|
216
|
+
if (activeQueryParamsToken.value) {
|
|
217
|
+
baseQueryKey.push("__query__", activeQueryParamsToken.value);
|
|
218
|
+
}
|
|
219
|
+
return baseQueryKey;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const list = useListCore({
|
|
223
|
+
queryKey: listQueryKey,
|
|
224
|
+
path: listPath,
|
|
225
|
+
enabled: operationScope.queryCanRun(canView),
|
|
226
|
+
initialPageParam,
|
|
227
|
+
getNextPageParam,
|
|
228
|
+
selectItems,
|
|
229
|
+
requestOptions,
|
|
230
|
+
queryOptions,
|
|
231
|
+
fallbackLoadError
|
|
232
|
+
});
|
|
233
|
+
const routeSyncHydrated = ref(routeSyncConfig.enabled !== true);
|
|
234
|
+
const routeSyncApplying = ref(false);
|
|
235
|
+
const routeSyncManagedKeyHistory = ref([]);
|
|
236
|
+
const routeSyncQueryParamBlacklist = computed(() => {
|
|
237
|
+
if (routeSyncConfig.enabled !== true || routeSyncConfig.syncQueryParams !== true) {
|
|
238
|
+
return EMPTY_ROUTE_SYNC_QUERY_PARAM_BLACKLIST;
|
|
239
|
+
}
|
|
240
|
+
return routeSyncConfig.queryParamBlacklist;
|
|
241
|
+
});
|
|
242
|
+
const routeSyncQueryParamBlacklistSet = computed(() => {
|
|
243
|
+
return new Set(routeSyncQueryParamBlacklist.value);
|
|
244
|
+
});
|
|
245
|
+
if (routeSyncConfig.enabled === true && routeSyncConfig.syncQueryParams === true) {
|
|
246
|
+
watch(declaredQueryParamKeys, (nextKeys) => {
|
|
247
|
+
routeSyncManagedKeyHistory.value = mergeManagedQueryParamKeyHistory(
|
|
248
|
+
routeSyncManagedKeyHistory.value,
|
|
249
|
+
nextKeys
|
|
250
|
+
);
|
|
251
|
+
}, { immediate: true });
|
|
252
|
+
}
|
|
253
|
+
const routeSyncManagedKeys = computed(() => {
|
|
254
|
+
const managedKeys = resolveRouteSyncManagedKeys({
|
|
255
|
+
searchEnabled: searchConfig.enabled,
|
|
256
|
+
searchParam: routeSyncConfig.searchParam,
|
|
257
|
+
syncSearch: routeSyncConfig.enabled === true && routeSyncConfig.syncSearch === true,
|
|
258
|
+
syncQueryParams: routeSyncConfig.enabled === true && routeSyncConfig.syncQueryParams === true,
|
|
259
|
+
declaredKeys: declaredQueryParamKeys.value,
|
|
260
|
+
keyHistory: routeSyncManagedKeyHistory.value
|
|
261
|
+
});
|
|
262
|
+
if (routeSyncConfig.enabled !== true || routeSyncConfig.syncQueryParams !== true) {
|
|
263
|
+
return managedKeys;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const output = new Set(managedKeys);
|
|
267
|
+
for (const key of routeSyncQueryParamBlacklist.value) {
|
|
268
|
+
output.add(key);
|
|
269
|
+
}
|
|
270
|
+
return [...output].sort((left, right) => left.localeCompare(right));
|
|
271
|
+
});
|
|
272
|
+
const routeSyncDesiredQuery = computed(() => {
|
|
273
|
+
if (routeSyncConfig.enabled !== true) {
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const desiredQuery = {};
|
|
278
|
+
if (routeSyncConfig.syncSearch === true && searchConfig.enabled === true) {
|
|
279
|
+
const normalizedSearch = normalizeText(searchQuery.value);
|
|
280
|
+
if (normalizedSearch) {
|
|
281
|
+
desiredQuery[routeSyncConfig.searchParam] = normalizedSearch;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (routeSyncConfig.syncQueryParams === true) {
|
|
285
|
+
for (const entry of activeQueryParamEntries.value) {
|
|
286
|
+
if (routeSyncQueryParamBlacklistSet.value.has(entry.key)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (entry.values.length === 1) {
|
|
290
|
+
desiredQuery[entry.key] = entry.values[0];
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
desiredQuery[entry.key] = [...entry.values];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return desiredQuery;
|
|
298
|
+
});
|
|
299
|
+
if (routeSyncConfig.enabled === true) {
|
|
300
|
+
watch(
|
|
301
|
+
() => operationScope.routeContext.route?.query || {},
|
|
302
|
+
(routeQuery) => {
|
|
303
|
+
if (routeSyncConfig.hydrateFromRoute !== true || routeSyncApplying.value === true) {
|
|
304
|
+
routeSyncHydrated.value = true;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const routeQuerySource = asPlainObject(routeQuery);
|
|
309
|
+
if (routeSyncConfig.syncSearch === true && searchConfig.enabled === true) {
|
|
310
|
+
const routeSearchValue = routeQuerySource[routeSyncConfig.searchParam];
|
|
311
|
+
const nextSearch = normalizeText(Array.isArray(routeSearchValue) ? routeSearchValue[0] : routeSearchValue);
|
|
312
|
+
if (nextSearch !== searchQuery.value) {
|
|
313
|
+
searchQuery.value = nextSearch;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (routeSyncConfig.syncQueryParams === true) {
|
|
317
|
+
for (const binding of writableQueryParamBindings.value) {
|
|
318
|
+
if (routeSyncQueryParamBlacklistSet.value.has(binding.key)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const nextValue = parseRouteBindingValue(binding, routeQuerySource[binding.key]);
|
|
322
|
+
const currentValue = typeof binding.get === "function" ? binding.get() : undefined;
|
|
323
|
+
if (areQueryParamBindingValuesEqual(currentValue, nextValue)) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
binding.set(nextValue);
|
|
328
|
+
} catch {
|
|
329
|
+
// Ignore non-writable query param bindings.
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
routeSyncHydrated.value = true;
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
immediate: true
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
watch(
|
|
342
|
+
[routeSyncDesiredQuery, routeSyncManagedKeys],
|
|
343
|
+
async ([desiredQuery, managedKeys]) => {
|
|
344
|
+
if (routeSyncHydrated.value !== true || routeSyncApplying.value === true) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const managedKeySet = new Set(Array.isArray(managedKeys) ? managedKeys : []);
|
|
349
|
+
const currentQuery = asPlainObject(operationScope.routeContext.route?.query || {});
|
|
350
|
+
const nextQuery = {};
|
|
351
|
+
|
|
352
|
+
for (const [key, value] of Object.entries(currentQuery)) {
|
|
353
|
+
if (managedKeySet.has(key)) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
nextQuery[key] = value;
|
|
357
|
+
}
|
|
358
|
+
for (const [key, value] of Object.entries(asPlainObject(desiredQuery))) {
|
|
359
|
+
nextQuery[key] = value;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (buildRouteQueryCompareToken(currentQuery) === buildRouteQueryCompareToken(nextQuery)) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
routeSyncApplying.value = true;
|
|
367
|
+
try {
|
|
368
|
+
if (routeSyncConfig.mode === "push") {
|
|
369
|
+
await router.push({
|
|
370
|
+
query: nextQuery
|
|
371
|
+
});
|
|
372
|
+
} else {
|
|
373
|
+
await router.replace({
|
|
374
|
+
query: nextQuery
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
} finally {
|
|
378
|
+
routeSyncApplying.value = false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
watch(activeSearchQuery, (nextValue, previousValue) => {
|
|
385
|
+
if (!querySearchEnabled.value) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (nextValue === previousValue) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
list.trimToFirstPage();
|
|
393
|
+
});
|
|
394
|
+
watch(activeQueryParamsToken, (nextValue, previousValue) => {
|
|
395
|
+
if (nextValue === previousValue) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
list.trimToFirstPage();
|
|
400
|
+
});
|
|
401
|
+
watch(activeRequestQueryParamsToken, (nextValue, previousValue) => {
|
|
402
|
+
if (nextValue === previousValue) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
list.trimToFirstPage();
|
|
407
|
+
});
|
|
408
|
+
const filteredItems = computed(() => {
|
|
409
|
+
const sourceItems = Array.isArray(list.items.value) ? list.items.value : [];
|
|
410
|
+
if (searchConfig.enabled !== true || searchConfig.mode !== "local") {
|
|
411
|
+
return sourceItems;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const queryValue = activeSearchQuery.value;
|
|
415
|
+
if (!queryValue) {
|
|
416
|
+
return sourceItems;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return sourceItems.filter((item) => matchesLocalSearch(item, queryValue, searchConfig.fields));
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const isInitialLoading = operationScope.isLoading(list.isInitialLoading);
|
|
423
|
+
const isFetching = operationScope.isLoading(list.isFetching);
|
|
424
|
+
const isRefetching = computed(() => Boolean(isFetching.value && !isInitialLoading.value));
|
|
425
|
+
const loadError = operationScope.loadError(list.loadError);
|
|
426
|
+
const isLoading = operationScope.isLoading(list.isLoading);
|
|
427
|
+
const listUiRuntime = createListUiRuntime({
|
|
428
|
+
items: filteredItems,
|
|
429
|
+
isInitialLoading,
|
|
430
|
+
recordIdParam,
|
|
431
|
+
recordIdSelector,
|
|
432
|
+
routeParams: computed(() => operationScope.routeContext.route?.params || {}),
|
|
433
|
+
routeParamNames: computed(() => resolveRouteParamNamesInOrder(operationScope.routeContext.route)),
|
|
434
|
+
routePath: computed(() => operationScope.routeContext.route?.path || ""),
|
|
435
|
+
viewUrlTemplate,
|
|
436
|
+
editUrlTemplate
|
|
437
|
+
});
|
|
438
|
+
setupOperationErrorReporting({
|
|
439
|
+
source: `${placementSource}.load`,
|
|
440
|
+
loadError,
|
|
441
|
+
dedupeWindowMs: 0,
|
|
442
|
+
loadActionFactory: () => ({
|
|
443
|
+
label: "Retry",
|
|
444
|
+
dismissOnRun: true,
|
|
445
|
+
handler() {
|
|
446
|
+
void list.reload();
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return proxyRefs({
|
|
452
|
+
canView,
|
|
453
|
+
isInitialLoading,
|
|
454
|
+
isFetching,
|
|
455
|
+
isRefetching,
|
|
456
|
+
isLoading,
|
|
457
|
+
isLoadingMore: list.isLoadingMore,
|
|
458
|
+
hasMore: list.hasMore,
|
|
459
|
+
loadError,
|
|
460
|
+
pages: list.pages,
|
|
461
|
+
items: filteredItems,
|
|
462
|
+
reload: list.reload,
|
|
463
|
+
loadMore: list.loadMore,
|
|
464
|
+
hasViewUrl: listUiRuntime.hasViewUrl,
|
|
465
|
+
hasEditUrl: listUiRuntime.hasEditUrl,
|
|
466
|
+
actionColumnCount: listUiRuntime.actionColumnCount,
|
|
467
|
+
showListSkeleton: listUiRuntime.showListSkeleton,
|
|
468
|
+
resolveRowKey: listUiRuntime.resolveRowKey,
|
|
469
|
+
resolveParams: listUiRuntime.resolveParams,
|
|
470
|
+
resolveViewUrl: listUiRuntime.resolveViewUrl,
|
|
471
|
+
resolveEditUrl: listUiRuntime.resolveEditUrl,
|
|
472
|
+
searchEnabled: searchConfig.enabled,
|
|
473
|
+
searchMode: searchConfig.mode,
|
|
474
|
+
searchQuery,
|
|
475
|
+
searchLabel: searchConfig.label,
|
|
476
|
+
searchPlaceholder: searchConfig.placeholder,
|
|
477
|
+
isSearchDebouncing,
|
|
478
|
+
activeQueryParamsToken
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export { useList };
|
|
@@ -1,12 +1,12 @@
|
|
|
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 { useViewCore } from "
|
|
5
|
-
import { useEndpointResource } from "
|
|
6
|
-
import { resolveOperationAdapter } from "
|
|
7
|
-
import { setupOperationErrorReporting } from "
|
|
8
|
-
import { createViewUiRuntime } from "
|
|
9
|
-
import {
|
|
4
|
+
import { useViewCore } from "../runtime/useViewCore.js";
|
|
5
|
+
import { useEndpointResource } from "../runtime/useEndpointResource.js";
|
|
6
|
+
import { resolveOperationAdapter } from "../runtime/operationAdapters.js";
|
|
7
|
+
import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
8
|
+
import { createViewUiRuntime } from "../runtime/viewUiRuntime.js";
|
|
9
|
+
import { resolveRouteParamNamesInOrder } from "../support/routeTemplateHelpers.js";
|
|
10
10
|
|
|
11
11
|
function useView({
|
|
12
12
|
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
@@ -37,6 +37,7 @@ function useView({
|
|
|
37
37
|
const viewUiRuntime = createViewUiRuntime({
|
|
38
38
|
recordIdParam,
|
|
39
39
|
routeParams: routeParams ?? computed(() => route?.params || {}),
|
|
40
|
+
routeParamNames: computed(() => resolveRouteParamNamesInOrder(route)),
|
|
40
41
|
routePath: computed(() => route?.path || ""),
|
|
41
42
|
routeRecordId,
|
|
42
43
|
apiUrlTemplate,
|
|
@@ -113,7 +114,6 @@ function useView({
|
|
|
113
114
|
listUrl: viewUiRuntime.listUrl,
|
|
114
115
|
editUrl: viewUiRuntime.editUrl,
|
|
115
116
|
resolveParams: viewUiRuntime.resolveParams,
|
|
116
|
-
resolveFieldDisplay: resolveLookupFieldDisplayValue,
|
|
117
117
|
canView,
|
|
118
118
|
isLoading,
|
|
119
119
|
isFetching,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { computed, unref } from "vue";
|
|
2
|
-
import { asPlainObject } from "
|
|
2
|
+
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
3
3
|
import {
|
|
4
4
|
normalizeRouteParamName,
|
|
5
5
|
resolveRouteParamsSource,
|
|
6
|
-
|
|
6
|
+
resolveScopedRoutePathname,
|
|
7
7
|
resolveRouteTemplateLocation,
|
|
8
8
|
toRouteParamValue
|
|
9
|
-
} from "
|
|
9
|
+
} from "../support/routeTemplateHelpers.js";
|
|
10
10
|
|
|
11
11
|
function toResolvedRecordId({ routeParams, recordIdParam, routeRecordId }) {
|
|
12
12
|
const explicitRecordId = toRouteParamValue(
|
|
@@ -31,6 +31,7 @@ function resolveSavedRecordId(payload, saveRecordIdSelector) {
|
|
|
31
31
|
function createAddEditUiRuntime({
|
|
32
32
|
recordIdParam = "recordId",
|
|
33
33
|
routeParams = null,
|
|
34
|
+
routeParamNames = null,
|
|
34
35
|
routePath = "",
|
|
35
36
|
routeRecordId = null,
|
|
36
37
|
apiUrlTemplate = "",
|
|
@@ -63,10 +64,18 @@ function createAddEditUiRuntime({
|
|
|
63
64
|
routeRecordId
|
|
64
65
|
});
|
|
65
66
|
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
67
|
+
const currentPathname = resolveScopedRoutePathname({
|
|
68
|
+
currentPathname: routePath,
|
|
69
|
+
params: currentRouteParams,
|
|
70
|
+
orderedParamNames: routeParamNames,
|
|
71
|
+
anchorParamName: normalizedRecordIdParam,
|
|
72
|
+
anchorParamValue: resolvedRecordId,
|
|
73
|
+
anchorMode: "after"
|
|
74
|
+
});
|
|
66
75
|
|
|
67
76
|
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
68
77
|
params: sourceParams,
|
|
69
|
-
currentPathname
|
|
78
|
+
currentPathname
|
|
70
79
|
});
|
|
71
80
|
}
|
|
72
81
|
|