@jskit-ai/users-web 0.1.81 → 0.1.83
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 +51 -9
- package/package.json +21 -10
- package/src/client/bulkActions.js +47 -0
- package/src/client/components/AccountSettingsClientElement.vue +69 -24
- package/src/client/components/CrudAddEditScreen.vue +186 -0
- package/src/client/components/CrudListBulkActionSurface.vue +126 -0
- package/src/client/components/CrudListFilterSurface.vue +377 -0
- package/src/client/components/CrudListScreen.vue +434 -0
- package/src/client/components/CrudViewScreen.vue +186 -0
- package/src/client/components/ProfileClientElement.vue +19 -12
- package/src/client/composables/records/useAddEdit.js +23 -2
- package/src/client/composables/records/useCrudAddEdit.js +8 -0
- package/src/client/composables/records/useCrudList.js +11 -1
- package/src/client/composables/records/useCrudView.js +1 -0
- package/src/client/composables/records/useView.js +9 -2
- package/src/client/composables/runtime/operationUiHelpers.js +7 -3
- package/src/client/composables/runtime/useEndpointResource.js +20 -2
- package/src/client/composables/runtime/useUiFeedback.js +4 -2
- package/src/client/composables/support/resourceLoadStateHelpers.js +33 -1
- package/src/client/composables/useAccountSettingsRuntime.js +10 -1
- package/src/client/composables/useCrudAddEditScreen.js +88 -0
- package/src/client/composables/useCrudListBulkActions.js +147 -0
- package/src/client/composables/useCrudListScreen.js +107 -0
- package/src/client/composables/useCrudViewScreen.js +67 -0
- package/src/client/composables/usePagedCollection.js +6 -1
- package/src/client/composables/useRealtimeQueryInvalidation.js +26 -0
- package/src/client/filters.js +15 -0
- package/src/client/index.js +5 -0
- package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +34 -8
- package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +34 -8
- package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +34 -8
- package/test/crudListBulkActionSurface.test.js +27 -0
- package/test/crudListFilterSurface.test.js +45 -0
- package/test/crudScreenComponents.test.js +62 -0
- package/test/errorIntentContract.test.js +31 -0
- package/test/exportsContract.test.js +11 -0
- package/test/requestTransportOptions.test.js +110 -1
- package/test/resourceLoadStateHelpers.test.js +35 -1
- package/test/settingsPlacementContract.test.js +61 -0
- package/test/useCrudListBulkActions.test.js +65 -0
|
@@ -163,9 +163,29 @@ function useAddEdit({
|
|
|
163
163
|
);
|
|
164
164
|
const loadError = operationScope.loadError(endpointResource.loadError);
|
|
165
165
|
const isLoading = operationScope.isLoading(endpointResource.isLoading);
|
|
166
|
+
const canRetryLoad = computed(() => Boolean(readEnabled !== false && endpointResource?.reload));
|
|
167
|
+
|
|
168
|
+
async function refresh() {
|
|
169
|
+
if (!canRetryLoad.value) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return endpointResource.reload();
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
setupOperationErrorReporting({
|
|
167
177
|
source: `${placementSource}.load`,
|
|
168
|
-
loadError
|
|
178
|
+
loadError,
|
|
179
|
+
dedupeWindowMs: 0,
|
|
180
|
+
loadActionFactory: () => canRetryLoad.value
|
|
181
|
+
? {
|
|
182
|
+
label: "Retry",
|
|
183
|
+
dismissOnRun: true,
|
|
184
|
+
handler() {
|
|
185
|
+
void refresh();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
: null
|
|
169
189
|
});
|
|
170
190
|
|
|
171
191
|
return proxyRefs({
|
|
@@ -177,13 +197,14 @@ function useAddEdit({
|
|
|
177
197
|
isRefetching,
|
|
178
198
|
isFieldLocked,
|
|
179
199
|
isSubmitDisabled,
|
|
200
|
+
canRetryLoad,
|
|
180
201
|
isLoading,
|
|
181
202
|
isSaving: addEdit.saving,
|
|
182
203
|
fieldErrors: addEdit.fieldErrors,
|
|
183
204
|
message: addEdit.message,
|
|
184
205
|
messageType: addEdit.messageType,
|
|
185
206
|
submit: addEdit.submit,
|
|
186
|
-
refresh
|
|
207
|
+
refresh,
|
|
187
208
|
resource: endpointResource,
|
|
188
209
|
recordId: addEditUiRuntime.recordId,
|
|
189
210
|
listUrl: addEditUiRuntime.listUrl,
|
|
@@ -6,6 +6,7 @@ import { useAddEdit } from "./useAddEdit.js";
|
|
|
6
6
|
import {
|
|
7
7
|
resolveCrudBoundValues,
|
|
8
8
|
} from "../crud/crudBindingSupport.js";
|
|
9
|
+
import { resolveOperationRealtimeOptions } from "../useRealtimeQueryInvalidation.js";
|
|
9
10
|
import {
|
|
10
11
|
normalizeCrudFormFields,
|
|
11
12
|
createCrudFormModel,
|
|
@@ -64,6 +65,12 @@ function useCrudAddEdit({
|
|
|
64
65
|
operationName
|
|
65
66
|
}
|
|
66
67
|
);
|
|
68
|
+
const resolvedRealtime = resolveOperationRealtimeOptions({
|
|
69
|
+
realtime: normalizedAddEditOptions.realtime,
|
|
70
|
+
fallbackRealtime: resolvedResource?.operations?.[operationName]?.realtime ||
|
|
71
|
+
resolvedResource?.operations?.list?.realtime ||
|
|
72
|
+
null
|
|
73
|
+
});
|
|
67
74
|
const saveSuccessOptions = normalizeSaveSuccessOptions(saveSuccess);
|
|
68
75
|
const defaultFieldErrorKeys = normalizedFields.map((field) => field.key);
|
|
69
76
|
const providedFieldErrorKeys = normalizeFieldErrorKeys(normalizedAddEditOptions.fieldErrorKeys);
|
|
@@ -185,6 +192,7 @@ function useCrudAddEdit({
|
|
|
185
192
|
input: resolvedInput,
|
|
186
193
|
buildRawPayload: resolveBuildRawPayload,
|
|
187
194
|
mapLoadedToModel: effectiveMapLoadedToModel,
|
|
195
|
+
realtime: resolvedRealtime,
|
|
188
196
|
onSaveSuccess: handleSaveSuccess
|
|
189
197
|
});
|
|
190
198
|
addEditRuntime = addEdit;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { computed, unref } from "vue";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
3
|
import { resolveCrudJsonApiTransport } from "../crud/crudJsonApiTransportSupport.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
resolveLookupFieldDisplayValue,
|
|
6
|
+
resolveRecordTitle
|
|
7
|
+
} from "../crud/crudLookupFieldLabelSupport.js";
|
|
5
8
|
import { resolveCrudBoundValues } from "../crud/crudBindingSupport.js";
|
|
6
9
|
import { resolveCrudListParentDescriptor } from "../internal/crudListParentTitleSupport.js";
|
|
7
10
|
import {
|
|
@@ -9,6 +12,7 @@ import {
|
|
|
9
12
|
toRouteParamValue
|
|
10
13
|
} from "../support/routeTemplateHelpers.js";
|
|
11
14
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
15
|
+
import { resolveOperationRealtimeOptions } from "../useRealtimeQueryInvalidation.js";
|
|
12
16
|
import { useList } from "./useList.js";
|
|
13
17
|
|
|
14
18
|
function resolveRequestQueryParamsInput(requestQueryParams, context = {}) {
|
|
@@ -46,6 +50,7 @@ function useCrudList({
|
|
|
46
50
|
parentBinding = null,
|
|
47
51
|
recordIdParam = "recordId",
|
|
48
52
|
route = null,
|
|
53
|
+
realtime = undefined,
|
|
49
54
|
...listOptions
|
|
50
55
|
} = {}) {
|
|
51
56
|
const sourceRoute = route && typeof route === "object" ? route : useRoute();
|
|
@@ -69,6 +74,10 @@ function useCrudList({
|
|
|
69
74
|
transport: resolveCrudJsonApiTransport(listOptions.transport, resource, {
|
|
70
75
|
mode: "list"
|
|
71
76
|
}),
|
|
77
|
+
realtime: resolveOperationRealtimeOptions({
|
|
78
|
+
realtime,
|
|
79
|
+
fallbackRealtime: resource?.operations?.list?.realtime || null
|
|
80
|
+
}),
|
|
72
81
|
recordIdParam,
|
|
73
82
|
requestQueryParams(context = {}) {
|
|
74
83
|
const baseRequestQueryParams = resolveRequestQueryParamsInput(requestQueryParams, context);
|
|
@@ -81,6 +90,7 @@ function useCrudList({
|
|
|
81
90
|
});
|
|
82
91
|
|
|
83
92
|
records.resolveFieldDisplay = resolveLookupFieldDisplayValue;
|
|
93
|
+
records.resolveRecordTitle = resolveRecordTitle;
|
|
84
94
|
return records;
|
|
85
95
|
}
|
|
86
96
|
|
|
@@ -8,6 +8,7 @@ import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
|
8
8
|
import { createViewUiRuntime } from "../runtime/viewUiRuntime.js";
|
|
9
9
|
import { createRequestQueryRuntime } from "../support/requestQueryRuntimeSupport.js";
|
|
10
10
|
import { resolveRouteParamNamesInOrder } from "../support/routeTemplateHelpers.js";
|
|
11
|
+
import { resolveOperationRealtimeOptions } from "../useRealtimeQueryInvalidation.js";
|
|
11
12
|
|
|
12
13
|
function useView({
|
|
13
14
|
ownershipFilter = ROUTE_VISIBILITY_WORKSPACE,
|
|
@@ -33,7 +34,7 @@ function useView({
|
|
|
33
34
|
listUrlTemplate = "",
|
|
34
35
|
editUrlTemplate = "",
|
|
35
36
|
includeRecordIdInQueryKey = false,
|
|
36
|
-
realtime =
|
|
37
|
+
realtime = undefined,
|
|
37
38
|
adapter = null
|
|
38
39
|
} = {}) {
|
|
39
40
|
const route = useRoute();
|
|
@@ -63,7 +64,12 @@ function useView({
|
|
|
63
64
|
permissionSets: {
|
|
64
65
|
view: viewPermissions
|
|
65
66
|
},
|
|
66
|
-
realtime
|
|
67
|
+
realtime: resolveOperationRealtimeOptions({
|
|
68
|
+
realtime,
|
|
69
|
+
fallbackRealtime: resource?.operations?.view?.realtime ||
|
|
70
|
+
resource?.operations?.list?.realtime ||
|
|
71
|
+
null
|
|
72
|
+
})
|
|
67
73
|
});
|
|
68
74
|
const queryParamsContext = computed(() => {
|
|
69
75
|
return Object.freeze({
|
|
@@ -97,6 +103,7 @@ function useView({
|
|
|
97
103
|
readMethod,
|
|
98
104
|
readQuery: requestQueryRuntime.requestQuery,
|
|
99
105
|
transport,
|
|
106
|
+
refreshOnPull: true,
|
|
100
107
|
fallbackLoadError
|
|
101
108
|
});
|
|
102
109
|
|
|
@@ -12,8 +12,8 @@ function setupOperationErrorReporting({
|
|
|
12
12
|
notFoundError = null,
|
|
13
13
|
loadActionFactory = null,
|
|
14
14
|
notFoundActionFactory = null,
|
|
15
|
-
loadChannel = "
|
|
16
|
-
notFoundChannel = "
|
|
15
|
+
loadChannel = "",
|
|
16
|
+
notFoundChannel = "",
|
|
17
17
|
loadSeverity = "error",
|
|
18
18
|
notFoundSeverity = "warning",
|
|
19
19
|
dedupeWindowMs = 2000
|
|
@@ -27,7 +27,8 @@ function setupOperationErrorReporting({
|
|
|
27
27
|
|
|
28
28
|
function watchMessage(value, {
|
|
29
29
|
kind = "load",
|
|
30
|
-
channel = "
|
|
30
|
+
channel = "",
|
|
31
|
+
intent = "resource-load",
|
|
31
32
|
severity = "error",
|
|
32
33
|
actionFactory = null
|
|
33
34
|
} = {}) {
|
|
@@ -60,6 +61,7 @@ function setupOperationErrorReporting({
|
|
|
60
61
|
const reportResult = runtime.report({
|
|
61
62
|
source: normalizedSource,
|
|
62
63
|
message: nextMessage,
|
|
64
|
+
intent,
|
|
63
65
|
severity,
|
|
64
66
|
channel,
|
|
65
67
|
action,
|
|
@@ -83,6 +85,7 @@ function setupOperationErrorReporting({
|
|
|
83
85
|
watchMessage(loadError, {
|
|
84
86
|
kind: "load",
|
|
85
87
|
channel: loadChannel,
|
|
88
|
+
intent: "resource-load",
|
|
86
89
|
severity: loadSeverity,
|
|
87
90
|
actionFactory: loadActionFactory
|
|
88
91
|
});
|
|
@@ -92,6 +95,7 @@ function setupOperationErrorReporting({
|
|
|
92
95
|
watchMessage(notFoundError, {
|
|
93
96
|
kind: "not-found",
|
|
94
97
|
channel: notFoundChannel,
|
|
98
|
+
intent: "resource-load",
|
|
95
99
|
severity: notFoundSeverity,
|
|
96
100
|
actionFactory: notFoundActionFactory
|
|
97
101
|
});
|
|
@@ -4,7 +4,11 @@ import { usersWebHttpClient } from "../../lib/httpClient.js";
|
|
|
4
4
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
5
5
|
import { resolveEnabledRef, resolveTextRef } from "../support/refValueHelpers.js";
|
|
6
6
|
import { toQueryErrorMessage } from "../support/errorMessageHelpers.js";
|
|
7
|
-
import {
|
|
7
|
+
import { useOperationRealtime } from "../useRealtimeQueryInvalidation.js";
|
|
8
|
+
import {
|
|
9
|
+
hasResolvedQueryData,
|
|
10
|
+
mergeQueryMeta
|
|
11
|
+
} from "../support/resourceLoadStateHelpers.js";
|
|
8
12
|
|
|
9
13
|
function buildEndpointReadRequestOptions({
|
|
10
14
|
method = "GET",
|
|
@@ -67,6 +71,8 @@ function useEndpointResource({
|
|
|
67
71
|
writeMethod = "PATCH",
|
|
68
72
|
readQuery = null,
|
|
69
73
|
transport = null,
|
|
74
|
+
refreshOnPull = false,
|
|
75
|
+
realtime = null,
|
|
70
76
|
queryOptions = null,
|
|
71
77
|
mutationOptions = null,
|
|
72
78
|
fallbackLoadError = "Unable to load resource.",
|
|
@@ -96,7 +102,18 @@ function useEndpointResource({
|
|
|
96
102
|
});
|
|
97
103
|
},
|
|
98
104
|
enabled: queryEnabled,
|
|
99
|
-
...(
|
|
105
|
+
...(refreshOnPull
|
|
106
|
+
? mergeQueryMeta(asPlainObject(queryOptions), {
|
|
107
|
+
jskit: {
|
|
108
|
+
refreshOnPull: true
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
: asPlainObject(queryOptions))
|
|
112
|
+
});
|
|
113
|
+
const realtimeBinding = useOperationRealtime({
|
|
114
|
+
realtime,
|
|
115
|
+
queryKey,
|
|
116
|
+
enabled: queryEnabled
|
|
100
117
|
});
|
|
101
118
|
|
|
102
119
|
const mutation = useMutation({
|
|
@@ -158,6 +175,7 @@ function useEndpointResource({
|
|
|
158
175
|
isSaving,
|
|
159
176
|
loadError,
|
|
160
177
|
saveError,
|
|
178
|
+
realtime: realtimeBinding,
|
|
161
179
|
reload,
|
|
162
180
|
save
|
|
163
181
|
});
|
|
@@ -5,8 +5,8 @@ import { toUiErrorMessage } from "../support/errorMessageHelpers.js";
|
|
|
5
5
|
function useUiFeedback({
|
|
6
6
|
initialType = "success",
|
|
7
7
|
source = "users-web.ui-feedback",
|
|
8
|
-
successChannel = "
|
|
9
|
-
errorChannel = "
|
|
8
|
+
successChannel = "",
|
|
9
|
+
errorChannel = "",
|
|
10
10
|
dedupeWindowMs = 2000
|
|
11
11
|
} = {}) {
|
|
12
12
|
const message = ref("");
|
|
@@ -55,6 +55,7 @@ function useUiFeedback({
|
|
|
55
55
|
errorRuntime.report({
|
|
56
56
|
source: normalizedSource,
|
|
57
57
|
message: normalizedMessage,
|
|
58
|
+
intent: "action-feedback",
|
|
58
59
|
severity: "success",
|
|
59
60
|
channel: successChannel,
|
|
60
61
|
dedupeKey: `${normalizedSource}:success:${normalizedMessage}`,
|
|
@@ -73,6 +74,7 @@ function useUiFeedback({
|
|
|
73
74
|
source: normalizedSource,
|
|
74
75
|
message: message.value,
|
|
75
76
|
cause: errorValue || null,
|
|
77
|
+
intent: "action-feedback",
|
|
76
78
|
severity: "error",
|
|
77
79
|
channel: errorChannel,
|
|
78
80
|
dedupeKey: `${normalizedSource}:error:${message.value}`,
|
|
@@ -7,4 +7,36 @@ function hasResolvedQueryData({ query = null, data = null } = {}) {
|
|
|
7
7
|
return querySucceeded || hasDataPayload;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function mergeQueryMeta(queryOptions = null, meta = {}) {
|
|
11
|
+
const sourceOptions =
|
|
12
|
+
queryOptions && typeof queryOptions === "object" && !Array.isArray(queryOptions) ? queryOptions : {};
|
|
13
|
+
const sourceMeta =
|
|
14
|
+
sourceOptions.meta && typeof sourceOptions.meta === "object" && !Array.isArray(sourceOptions.meta)
|
|
15
|
+
? sourceOptions.meta
|
|
16
|
+
: {};
|
|
17
|
+
const sourceJskitMeta =
|
|
18
|
+
sourceMeta.jskit && typeof sourceMeta.jskit === "object" && !Array.isArray(sourceMeta.jskit)
|
|
19
|
+
? sourceMeta.jskit
|
|
20
|
+
: {};
|
|
21
|
+
const nextJskitMeta =
|
|
22
|
+
meta.jskit && typeof meta.jskit === "object" && !Array.isArray(meta.jskit)
|
|
23
|
+
? meta.jskit
|
|
24
|
+
: {};
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...sourceOptions,
|
|
28
|
+
meta: {
|
|
29
|
+
...sourceMeta,
|
|
30
|
+
...meta,
|
|
31
|
+
jskit: {
|
|
32
|
+
...sourceJskitMeta,
|
|
33
|
+
...nextJskitMeta
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
hasResolvedQueryData,
|
|
41
|
+
mergeQueryMeta
|
|
42
|
+
};
|
|
@@ -127,7 +127,7 @@ function useAccountSettingsRuntime() {
|
|
|
127
127
|
function reportAccountFeedback({
|
|
128
128
|
message,
|
|
129
129
|
severity = "error",
|
|
130
|
-
channel = "
|
|
130
|
+
channel = "",
|
|
131
131
|
dedupeKey = ""
|
|
132
132
|
} = {}) {
|
|
133
133
|
const normalizedMessage = String(message || "").trim();
|
|
@@ -138,6 +138,7 @@ function useAccountSettingsRuntime() {
|
|
|
138
138
|
errorRuntime.report({
|
|
139
139
|
source: "users-web.account-settings-runtime",
|
|
140
140
|
message: normalizedMessage,
|
|
141
|
+
intent: "action-feedback",
|
|
141
142
|
severity,
|
|
142
143
|
channel,
|
|
143
144
|
dedupeKey: dedupeKey || `users-web.account-settings-runtime:${severity}:${normalizedMessage}`,
|
|
@@ -301,6 +302,12 @@ function useAccountSettingsRuntime() {
|
|
|
301
302
|
|
|
302
303
|
const loadingSettings = computed(() => Boolean(settingsView.isLoading));
|
|
303
304
|
const refreshingSettings = computed(() => Boolean(settingsView.isRefetching));
|
|
305
|
+
const settingsLoadError = computed(() => String(settingsView.loadError || "").trim());
|
|
306
|
+
|
|
307
|
+
async function refreshSettings() {
|
|
308
|
+
return settingsView.refresh();
|
|
309
|
+
}
|
|
310
|
+
|
|
304
311
|
async function submitProfile() {
|
|
305
312
|
await profileAddEdit.submit();
|
|
306
313
|
}
|
|
@@ -384,6 +391,8 @@ function useAccountSettingsRuntime() {
|
|
|
384
391
|
backNavigationTarget,
|
|
385
392
|
loadingSettings,
|
|
386
393
|
refreshingSettings,
|
|
394
|
+
settingsLoadError,
|
|
395
|
+
refreshSettings,
|
|
387
396
|
profile,
|
|
388
397
|
preferences,
|
|
389
398
|
notifications
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { useRoute } from "vue-router";
|
|
3
|
+
import { useCrudAddEdit } from "./records/useCrudAddEdit.js";
|
|
4
|
+
|
|
5
|
+
function normalizeProvidedScreen(screen = null) {
|
|
6
|
+
return screen && typeof screen === "object" && !Array.isArray(screen)
|
|
7
|
+
? screen
|
|
8
|
+
: null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function useCrudAddEditScreen({
|
|
12
|
+
screen = null,
|
|
13
|
+
mode = "new",
|
|
14
|
+
title = "",
|
|
15
|
+
subtitle = "",
|
|
16
|
+
saveLabel = "Save",
|
|
17
|
+
cancelTo = "",
|
|
18
|
+
resource = null,
|
|
19
|
+
operationName = "",
|
|
20
|
+
formFields = [],
|
|
21
|
+
addEditOptions = {},
|
|
22
|
+
saveSuccess = {},
|
|
23
|
+
fieldBinding = null,
|
|
24
|
+
createModel = null,
|
|
25
|
+
buildPayload = null,
|
|
26
|
+
mapPayloadToModel = null,
|
|
27
|
+
input = null,
|
|
28
|
+
preserveCancelQuery = false
|
|
29
|
+
} = {}) {
|
|
30
|
+
const providedScreen = normalizeProvidedScreen(screen);
|
|
31
|
+
if (providedScreen) {
|
|
32
|
+
return providedScreen;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const route = useRoute();
|
|
36
|
+
const formRuntime = useCrudAddEdit({
|
|
37
|
+
resource,
|
|
38
|
+
operationName,
|
|
39
|
+
formFields,
|
|
40
|
+
addEditOptions,
|
|
41
|
+
saveSuccess,
|
|
42
|
+
fieldBinding,
|
|
43
|
+
createModel,
|
|
44
|
+
buildPayload,
|
|
45
|
+
mapPayloadToModel,
|
|
46
|
+
input
|
|
47
|
+
});
|
|
48
|
+
const resolvedMode = computed(() => String(unref(mode) || "new").trim() || "new");
|
|
49
|
+
const resolvedTitle = computed(() => String(unref(title) || "").trim());
|
|
50
|
+
const resolvedSubtitle = computed(() => String(unref(subtitle) || "").trim());
|
|
51
|
+
const resolvedSaveLabel = computed(() => String(unref(saveLabel) || "Save").trim() || "Save");
|
|
52
|
+
const resolvedCancelTo = computed(() => unref(cancelTo));
|
|
53
|
+
|
|
54
|
+
function resolveCancelTo(target = resolvedCancelTo.value) {
|
|
55
|
+
const resolvedTarget = unref(target);
|
|
56
|
+
if (!resolvedTarget) {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof resolvedTarget === "string") {
|
|
61
|
+
const resolvedPath = formRuntime.addEdit.resolveParams(resolvedTarget);
|
|
62
|
+
if (!preserveCancelQuery || !resolvedPath) {
|
|
63
|
+
return resolvedPath;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
path: resolvedPath,
|
|
67
|
+
query: route.query
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return resolvedTarget;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
mode: resolvedMode,
|
|
76
|
+
title: resolvedTitle,
|
|
77
|
+
subtitle: resolvedSubtitle,
|
|
78
|
+
saveLabel: resolvedSaveLabel,
|
|
79
|
+
cancelTo: resolvedCancelTo,
|
|
80
|
+
formRuntime,
|
|
81
|
+
addEdit: formRuntime.addEdit,
|
|
82
|
+
formState: formRuntime.form,
|
|
83
|
+
resolveFieldErrors: formRuntime.resolveFieldErrors,
|
|
84
|
+
resolveCancelTo
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { useCrudAddEditScreen };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { defineCrudListBulkActions } from "../bulkActions.js";
|
|
4
|
+
|
|
5
|
+
function normalizeSelectedId(value = "") {
|
|
6
|
+
return normalizeText(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function resolveActionKey(actionOrKey = "") {
|
|
10
|
+
return normalizeText(
|
|
11
|
+
actionOrKey && typeof actionOrKey === "object"
|
|
12
|
+
? actionOrKey.key
|
|
13
|
+
: actionOrKey
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function useCrudListBulkActions(actions = [], {
|
|
18
|
+
resolveRecordId = null,
|
|
19
|
+
resolveContext = null
|
|
20
|
+
} = {}) {
|
|
21
|
+
const normalizedActions = defineCrudListBulkActions(actions);
|
|
22
|
+
const selectedIds = ref([]);
|
|
23
|
+
const selectedRecordMap = new Map();
|
|
24
|
+
const executingActionKey = ref("");
|
|
25
|
+
|
|
26
|
+
const selectedCount = computed(() => selectedIds.value.length);
|
|
27
|
+
const hasSelection = computed(() => selectedCount.value > 0);
|
|
28
|
+
const hasActions = computed(() => normalizedActions.length > 0);
|
|
29
|
+
|
|
30
|
+
function getRecordId(record = {}, index = 0) {
|
|
31
|
+
const resolvedId = typeof resolveRecordId === "function"
|
|
32
|
+
? resolveRecordId(record, index)
|
|
33
|
+
: record?.id ?? record?.attributes?.id ?? index;
|
|
34
|
+
return normalizeSelectedId(resolvedId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setRecordSelected(record = {}, index = 0, selected = true) {
|
|
38
|
+
const recordId = getRecordId(record, index);
|
|
39
|
+
if (!recordId) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const nextIds = new Set(selectedIds.value);
|
|
44
|
+
if (selected) {
|
|
45
|
+
nextIds.add(recordId);
|
|
46
|
+
selectedRecordMap.set(recordId, record);
|
|
47
|
+
} else {
|
|
48
|
+
nextIds.delete(recordId);
|
|
49
|
+
selectedRecordMap.delete(recordId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
selectedIds.value = Array.from(nextIds);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isRecordSelected(record = {}, index = 0) {
|
|
56
|
+
const recordId = getRecordId(record, index);
|
|
57
|
+
return recordId ? selectedIds.value.includes(recordId) : false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setVisibleSelected(records = [], selected = true) {
|
|
61
|
+
for (const [index, record] of (Array.isArray(records) ? records : []).entries()) {
|
|
62
|
+
setRecordSelected(record, index, selected);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function allVisibleSelected(records = []) {
|
|
67
|
+
const visibleRecords = Array.isArray(records) ? records : [];
|
|
68
|
+
return visibleRecords.length > 0 && visibleRecords.every((record, index) => isRecordSelected(record, index));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function someVisibleSelected(records = []) {
|
|
72
|
+
return (Array.isArray(records) ? records : []).some((record, index) => isRecordSelected(record, index));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clearSelection() {
|
|
76
|
+
selectedIds.value = [];
|
|
77
|
+
selectedRecordMap.clear();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findAction(actionOrKey = "") {
|
|
81
|
+
const actionKey = resolveActionKey(actionOrKey);
|
|
82
|
+
return normalizedActions.find((action) => action.key === actionKey) || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isActionExecuting(actionOrKey = "") {
|
|
86
|
+
const actionKey = resolveActionKey(actionOrKey);
|
|
87
|
+
return Boolean(actionKey && executingActionKey.value === actionKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isActionDisabled(actionOrKey = "") {
|
|
91
|
+
const action = findAction(actionOrKey);
|
|
92
|
+
if (!action || !hasSelection.value || executingActionKey.value) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (typeof action.disabled === "function") {
|
|
96
|
+
return Boolean(action.disabled({
|
|
97
|
+
selectedIds: selectedIds.value.slice(),
|
|
98
|
+
selectedRecords: Array.from(selectedRecordMap.values()),
|
|
99
|
+
action
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
return Boolean(action.disabled);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function execute(actionOrKey = "") {
|
|
106
|
+
const action = findAction(actionOrKey);
|
|
107
|
+
if (!action || typeof action.run !== "function" || isActionDisabled(action)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
executingActionKey.value = action.key;
|
|
112
|
+
try {
|
|
113
|
+
const baseContext = typeof resolveContext === "function" ? resolveContext() : {};
|
|
114
|
+
return await action.run({
|
|
115
|
+
...(baseContext && typeof baseContext === "object" && !Array.isArray(baseContext) ? baseContext : {}),
|
|
116
|
+
action,
|
|
117
|
+
ids: selectedIds.value.slice(),
|
|
118
|
+
selectedIds: selectedIds.value.slice(),
|
|
119
|
+
selectedRecords: Array.from(selectedRecordMap.values()),
|
|
120
|
+
clearSelection
|
|
121
|
+
});
|
|
122
|
+
} finally {
|
|
123
|
+
executingActionKey.value = "";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
actions: normalizedActions,
|
|
129
|
+
selectedIds,
|
|
130
|
+
selectedCount,
|
|
131
|
+
hasActions,
|
|
132
|
+
hasSelection,
|
|
133
|
+
executingActionKey,
|
|
134
|
+
setRecordSelected,
|
|
135
|
+
isRecordSelected,
|
|
136
|
+
setVisibleSelected,
|
|
137
|
+
allVisibleSelected,
|
|
138
|
+
someVisibleSelected,
|
|
139
|
+
clearSelection,
|
|
140
|
+
findAction,
|
|
141
|
+
isActionDisabled,
|
|
142
|
+
isActionExecuting,
|
|
143
|
+
execute
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { useCrudListBulkActions };
|