@jskit-ai/users-web 0.1.31 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.descriptor.mjs +7 -6
- package/package.json +7 -7
- package/src/client/components/WorkspaceMembersClientElement.vue +3 -8
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +1 -2
- package/src/client/components/WorkspacesClientElement.vue +3 -8
- package/src/client/composables/addEditUiRuntime.js +120 -0
- package/src/client/composables/crudSchemaFormHelpers.js +152 -0
- package/src/client/composables/listUiRuntime.js +111 -0
- package/src/client/composables/operationAdapters.js +34 -0
- package/src/client/composables/routeTemplateHelpers.js +110 -0
- package/src/client/composables/useAccountSettingsRuntime.js +10 -14
- package/src/client/composables/useAddEdit.js +37 -5
- package/src/client/composables/useCrudSchemaForm.js +177 -0
- package/src/client/composables/useList.js +31 -4
- package/src/client/composables/useView.js +43 -5
- package/src/client/composables/viewUiRuntime.js +87 -0
- package/src/client/lib/theme.js +2 -3
- package/src/client/providers/UsersWebClientProvider.js +14 -39
- package/src/client/runtime/bootstrapPlacementRuntime.js +14 -24
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +0 -4
- package/test/addEditUiRuntime.test.js +75 -0
- package/test/bootstrapPlacementRuntime.test.js +45 -52
- package/test/listUiRuntime.test.js +81 -0
- package/test/useCrudSchemaForm.test.js +141 -0
- package/test/viewUiRuntime.test.js +60 -0
|
@@ -23,10 +23,6 @@ import { useAddEdit } from "./useAddEdit.js";
|
|
|
23
23
|
import { useCommand } from "./useCommand.js";
|
|
24
24
|
import { useView } from "./useView.js";
|
|
25
25
|
import { usePaths } from "./usePaths.js";
|
|
26
|
-
import {
|
|
27
|
-
ACCOUNT_SETTINGS_CHANGED_EVENT,
|
|
28
|
-
WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
|
|
29
|
-
} from "@jskit-ai/users-core/shared/events/usersEvents";
|
|
30
26
|
import { resolveAccountSettingsPathFromPlacementContext } from "../lib/workspaceSurfacePaths.js";
|
|
31
27
|
import {
|
|
32
28
|
ACCOUNT_SETTINGS_DEFAULTS,
|
|
@@ -206,22 +202,22 @@ function useAccountSettingsRuntime() {
|
|
|
206
202
|
|
|
207
203
|
const settingsView = useView({
|
|
208
204
|
ownershipFilter: OWNERSHIP_PUBLIC,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
205
|
+
apiSuffix: "/settings",
|
|
206
|
+
queryKeyFactory: () => accountSettingsQueryKey,
|
|
207
|
+
realtime: {
|
|
208
|
+
event: "account.settings.changed"
|
|
209
|
+
},
|
|
214
210
|
fallbackLoadError: "Unable to load settings.",
|
|
215
211
|
mapLoadedToModel: mapAccountSettingsPayload
|
|
216
212
|
});
|
|
217
213
|
|
|
218
214
|
const pendingInvitesView = useView({
|
|
219
215
|
ownershipFilter: OWNERSHIP_PUBLIC,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
216
|
+
apiSuffix: "/bootstrap",
|
|
217
|
+
queryKeyFactory: () => pendingInvitesQueryKey,
|
|
218
|
+
realtime: {
|
|
219
|
+
event: "workspace.invitations.pending.changed"
|
|
220
|
+
},
|
|
225
221
|
fallbackLoadError: "Unable to load invitations.",
|
|
226
222
|
model: pendingInvitesModel,
|
|
227
223
|
mapLoadedToModel: (model, payload = {}) => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { computed, proxyRefs } from "vue";
|
|
2
|
+
import { useRoute } from "vue-router";
|
|
2
3
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
3
4
|
import { useAddEditCore } from "./useAddEditCore.js";
|
|
4
5
|
import { useEndpointResource } from "./useEndpointResource.js";
|
|
5
|
-
import {
|
|
6
|
+
import { resolveOperationAdapter } from "./operationAdapters.js";
|
|
7
|
+
import { createAddEditUiRuntime } from "./addEditUiRuntime.js";
|
|
6
8
|
import { useUiFeedback } from "./useUiFeedback.js";
|
|
7
9
|
import { useFieldErrorBag } from "./useFieldErrorBag.js";
|
|
8
10
|
import {
|
|
@@ -36,15 +38,39 @@ function useAddEdit({
|
|
|
36
38
|
buildRawPayload,
|
|
37
39
|
buildSavePayload,
|
|
38
40
|
onSaveSuccess,
|
|
41
|
+
recordIdParam = "recordId",
|
|
42
|
+
routeParams = null,
|
|
43
|
+
routeRecordId = null,
|
|
44
|
+
apiUrlTemplate = "",
|
|
45
|
+
viewUrlTemplate = "",
|
|
46
|
+
listUrlTemplate = "",
|
|
47
|
+
saveRecordIdSelector = null,
|
|
39
48
|
messages = {},
|
|
40
|
-
realtime = null
|
|
49
|
+
realtime = null,
|
|
50
|
+
adapter = null
|
|
41
51
|
} = {}) {
|
|
42
|
-
const
|
|
52
|
+
const route = useRoute();
|
|
53
|
+
const addEditUiRuntime = createAddEditUiRuntime({
|
|
54
|
+
recordIdParam,
|
|
55
|
+
routeParams: routeParams ?? computed(() => route?.params || {}),
|
|
56
|
+
routePath: computed(() => route?.path || ""),
|
|
57
|
+
routeRecordId,
|
|
58
|
+
apiUrlTemplate,
|
|
59
|
+
viewUrlTemplate,
|
|
60
|
+
listUrlTemplate,
|
|
61
|
+
saveRecordIdSelector
|
|
62
|
+
});
|
|
63
|
+
const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
|
|
64
|
+
const effectiveApiSuffix = normalizedApiUrlTemplate ? addEditUiRuntime.apiSuffix : apiSuffix;
|
|
65
|
+
const operationAdapter = resolveOperationAdapter(adapter, {
|
|
66
|
+
context: "useAddEdit adapter"
|
|
67
|
+
});
|
|
68
|
+
const operationScope = operationAdapter.useOperationScope({
|
|
43
69
|
ownershipFilter,
|
|
44
70
|
surfaceId,
|
|
45
71
|
access,
|
|
46
72
|
placementSource,
|
|
47
|
-
apiSuffix,
|
|
73
|
+
apiSuffix: effectiveApiSuffix,
|
|
48
74
|
model,
|
|
49
75
|
readEnabled,
|
|
50
76
|
queryKeyFactory,
|
|
@@ -128,7 +154,13 @@ function useAddEdit({
|
|
|
128
154
|
messageType: addEdit.messageType,
|
|
129
155
|
submit: addEdit.submit,
|
|
130
156
|
refresh: endpointResource.reload,
|
|
131
|
-
resource: endpointResource
|
|
157
|
+
resource: endpointResource,
|
|
158
|
+
recordId: addEditUiRuntime.recordId,
|
|
159
|
+
listUrl: addEditUiRuntime.listUrl,
|
|
160
|
+
cancelUrl: addEditUiRuntime.cancelUrl,
|
|
161
|
+
resolveParams: addEditUiRuntime.resolveParams,
|
|
162
|
+
resolveViewUrl: addEditUiRuntime.resolveViewUrl,
|
|
163
|
+
resolveSavedViewUrl: addEditUiRuntime.resolveSavedViewUrl
|
|
132
164
|
});
|
|
133
165
|
}
|
|
134
166
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { computed, reactive } from "vue";
|
|
2
|
+
import { useRouter } from "vue-router";
|
|
3
|
+
import { asPlainObject } from "./scopeHelpers.js";
|
|
4
|
+
import { useAddEdit } from "./useAddEdit.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizeCrudFormFields,
|
|
7
|
+
createCrudFormModel,
|
|
8
|
+
buildCrudFormPayload,
|
|
9
|
+
applyCrudPayloadToForm,
|
|
10
|
+
resolveCrudFieldErrors,
|
|
11
|
+
parseCrudResourceOperationInput
|
|
12
|
+
} from "./crudSchemaFormHelpers.js";
|
|
13
|
+
|
|
14
|
+
function normalizeFieldErrorKeys(keys = []) {
|
|
15
|
+
return Array.isArray(keys)
|
|
16
|
+
? keys.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
17
|
+
: [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeSaveSuccessOptions(options = {}) {
|
|
21
|
+
const source = asPlainObject(options);
|
|
22
|
+
const invalidateQueryKey = Array.isArray(source.invalidateQueryKey)
|
|
23
|
+
? source.invalidateQueryKey
|
|
24
|
+
: null;
|
|
25
|
+
const listUrlTemplate = String(source.listUrlTemplate || "").trim();
|
|
26
|
+
const navigateToView = source.navigateToView !== false;
|
|
27
|
+
const navigateToList = source.navigateToList !== false;
|
|
28
|
+
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
invalidateQueryKey,
|
|
31
|
+
listUrlTemplate,
|
|
32
|
+
navigateToView,
|
|
33
|
+
navigateToList
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function useCrudSchemaForm({
|
|
38
|
+
resource = null,
|
|
39
|
+
operationName = "",
|
|
40
|
+
formFields = [],
|
|
41
|
+
addEditOptions = {},
|
|
42
|
+
saveSuccess = {},
|
|
43
|
+
createModel = null,
|
|
44
|
+
buildPayload = null,
|
|
45
|
+
mapPayloadToModel = null,
|
|
46
|
+
parseInput = null
|
|
47
|
+
} = {}) {
|
|
48
|
+
const router = useRouter();
|
|
49
|
+
const normalizedFields = normalizeCrudFormFields(formFields);
|
|
50
|
+
const normalizedAddEditOptions = asPlainObject(addEditOptions);
|
|
51
|
+
const saveSuccessOptions = normalizeSaveSuccessOptions(saveSuccess);
|
|
52
|
+
const defaultFieldErrorKeys = normalizedFields.map((field) => field.key);
|
|
53
|
+
const providedFieldErrorKeys = normalizeFieldErrorKeys(normalizedAddEditOptions.fieldErrorKeys);
|
|
54
|
+
const fieldErrorKeys = providedFieldErrorKeys.length > 0 ? providedFieldErrorKeys : defaultFieldErrorKeys;
|
|
55
|
+
const providedModel = normalizedAddEditOptions.model;
|
|
56
|
+
const hasProvidedModel = Boolean(providedModel && typeof providedModel === "object" && !Array.isArray(providedModel));
|
|
57
|
+
const defaultModel = typeof createModel === "function"
|
|
58
|
+
? asPlainObject(createModel(normalizedFields))
|
|
59
|
+
: createCrudFormModel(normalizedFields);
|
|
60
|
+
const form = hasProvidedModel ? providedModel : reactive(defaultModel);
|
|
61
|
+
const parseInputOverride = typeof parseInput === "function"
|
|
62
|
+
? parseInput
|
|
63
|
+
: (typeof normalizedAddEditOptions.parseInput === "function" ? normalizedAddEditOptions.parseInput : null);
|
|
64
|
+
const buildPayloadOverride = typeof buildPayload === "function"
|
|
65
|
+
? buildPayload
|
|
66
|
+
: (typeof normalizedAddEditOptions.buildRawPayload === "function" ? normalizedAddEditOptions.buildRawPayload : null);
|
|
67
|
+
const mapPayloadToModelOverride = typeof mapPayloadToModel === "function"
|
|
68
|
+
? mapPayloadToModel
|
|
69
|
+
: (typeof normalizedAddEditOptions.mapLoadedToModel === "function" ? normalizedAddEditOptions.mapLoadedToModel : null);
|
|
70
|
+
const onSaveSuccessOverride = typeof normalizedAddEditOptions.onSaveSuccess === "function"
|
|
71
|
+
? normalizedAddEditOptions.onSaveSuccess
|
|
72
|
+
: null;
|
|
73
|
+
const shouldApplyDefaultMapPayload = normalizedAddEditOptions.readEnabled !== false;
|
|
74
|
+
const resolvedResource = normalizedAddEditOptions.resource || resource;
|
|
75
|
+
|
|
76
|
+
function resolveParseInput(rawPayload = {}, context = {}) {
|
|
77
|
+
if (parseInputOverride) {
|
|
78
|
+
return parseInputOverride(rawPayload, context);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return parseCrudResourceOperationInput({
|
|
82
|
+
resource: resolvedResource,
|
|
83
|
+
operationName,
|
|
84
|
+
rawPayload,
|
|
85
|
+
context
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveBuildRawPayload(model = {}, context = {}) {
|
|
90
|
+
if (buildPayloadOverride) {
|
|
91
|
+
return buildPayloadOverride(model, {
|
|
92
|
+
...context,
|
|
93
|
+
fields: normalizedFields
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return buildCrudFormPayload(normalizedFields, model);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const effectiveMapLoadedToModel = mapPayloadToModelOverride
|
|
101
|
+
? (model = {}, payload = {}, context = {}) => {
|
|
102
|
+
mapPayloadToModelOverride(model, payload, {
|
|
103
|
+
...context,
|
|
104
|
+
fields: normalizedFields
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
: (shouldApplyDefaultMapPayload
|
|
108
|
+
? (model = {}, payload = {}) => {
|
|
109
|
+
applyCrudPayloadToForm(normalizedFields, model, payload);
|
|
110
|
+
}
|
|
111
|
+
: undefined);
|
|
112
|
+
|
|
113
|
+
let addEditRuntime = null;
|
|
114
|
+
|
|
115
|
+
async function handleSaveSuccess(payload, context = {}) {
|
|
116
|
+
if (onSaveSuccessOverride) {
|
|
117
|
+
await onSaveSuccessOverride(payload, context);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const queryClient = context?.queryClient;
|
|
122
|
+
if (queryClient && saveSuccessOptions.invalidateQueryKey?.length > 0) {
|
|
123
|
+
await queryClient.invalidateQueries({
|
|
124
|
+
queryKey: saveSuccessOptions.invalidateQueryKey
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (saveSuccessOptions.navigateToView) {
|
|
129
|
+
const viewUrl = addEditRuntime?.resolveSavedViewUrl(payload) ||
|
|
130
|
+
addEditRuntime?.resolveViewUrl(addEditRuntime?.recordId);
|
|
131
|
+
if (viewUrl) {
|
|
132
|
+
await router.push(viewUrl);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!saveSuccessOptions.navigateToList) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const listUrlTemplate = saveSuccessOptions.listUrlTemplate ||
|
|
142
|
+
String(normalizedAddEditOptions.listUrlTemplate || "").trim();
|
|
143
|
+
const listUrl = addEditRuntime?.resolveParams(listUrlTemplate);
|
|
144
|
+
if (listUrl) {
|
|
145
|
+
await router.push(listUrl);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const addEdit = useAddEdit({
|
|
150
|
+
...normalizedAddEditOptions,
|
|
151
|
+
resource: resolvedResource,
|
|
152
|
+
model: form,
|
|
153
|
+
fieldErrorKeys,
|
|
154
|
+
parseInput: resolveParseInput,
|
|
155
|
+
buildRawPayload: resolveBuildRawPayload,
|
|
156
|
+
mapLoadedToModel: effectiveMapLoadedToModel,
|
|
157
|
+
onSaveSuccess: handleSaveSuccess
|
|
158
|
+
});
|
|
159
|
+
addEditRuntime = addEdit;
|
|
160
|
+
|
|
161
|
+
function resolveFieldErrors(fieldKey = "") {
|
|
162
|
+
return resolveCrudFieldErrors(addEdit.fieldErrors, fieldKey);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const showFormSkeleton = computed(() => Boolean(addEdit.isInitialLoading));
|
|
166
|
+
|
|
167
|
+
return Object.freeze({
|
|
168
|
+
formFields: normalizedFields,
|
|
169
|
+
fieldErrorKeys,
|
|
170
|
+
form,
|
|
171
|
+
addEdit,
|
|
172
|
+
showFormSkeleton,
|
|
173
|
+
resolveFieldErrors
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { useCrudSchemaForm };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
3
3
|
import { useListCore } from "./useListCore.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveOperationAdapter } from "./operationAdapters.js";
|
|
5
5
|
import { setupOperationErrorReporting } from "./operationUiHelpers.js";
|
|
6
|
+
import { createListUiRuntime } from "./listUiRuntime.js";
|
|
6
7
|
|
|
7
8
|
function useList({
|
|
8
9
|
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
@@ -19,9 +20,17 @@ function useList({
|
|
|
19
20
|
selectItems,
|
|
20
21
|
requestOptions,
|
|
21
22
|
queryOptions,
|
|
22
|
-
realtime = null
|
|
23
|
+
realtime = null,
|
|
24
|
+
adapter = null,
|
|
25
|
+
recordIdParam = "recordId",
|
|
26
|
+
recordIdSelector = null,
|
|
27
|
+
viewUrlTemplate = "",
|
|
28
|
+
editUrlTemplate = ""
|
|
23
29
|
} = {}) {
|
|
24
|
-
const
|
|
30
|
+
const operationAdapter = resolveOperationAdapter(adapter, {
|
|
31
|
+
context: "useList adapter"
|
|
32
|
+
});
|
|
33
|
+
const operationScope = operationAdapter.useOperationScope({
|
|
25
34
|
ownershipFilter,
|
|
26
35
|
surfaceId,
|
|
27
36
|
access,
|
|
@@ -53,6 +62,16 @@ function useList({
|
|
|
53
62
|
const isRefetching = computed(() => Boolean(isFetching.value && !isInitialLoading.value));
|
|
54
63
|
const loadError = operationScope.loadError(list.loadError);
|
|
55
64
|
const isLoading = operationScope.isLoading(list.isLoading);
|
|
65
|
+
const listUiRuntime = createListUiRuntime({
|
|
66
|
+
items: list.items,
|
|
67
|
+
isInitialLoading,
|
|
68
|
+
recordIdParam,
|
|
69
|
+
recordIdSelector,
|
|
70
|
+
routeParams: computed(() => operationScope.routeContext.route?.params || {}),
|
|
71
|
+
routePath: computed(() => operationScope.routeContext.route?.path || ""),
|
|
72
|
+
viewUrlTemplate,
|
|
73
|
+
editUrlTemplate
|
|
74
|
+
});
|
|
56
75
|
setupOperationErrorReporting({
|
|
57
76
|
source: `${placementSource}.load`,
|
|
58
77
|
loadError,
|
|
@@ -78,7 +97,15 @@ function useList({
|
|
|
78
97
|
pages: list.pages,
|
|
79
98
|
items: list.items,
|
|
80
99
|
reload: list.reload,
|
|
81
|
-
loadMore: list.loadMore
|
|
100
|
+
loadMore: list.loadMore,
|
|
101
|
+
hasViewUrl: listUiRuntime.hasViewUrl,
|
|
102
|
+
hasEditUrl: listUiRuntime.hasEditUrl,
|
|
103
|
+
actionColumnCount: listUiRuntime.actionColumnCount,
|
|
104
|
+
showListSkeleton: listUiRuntime.showListSkeleton,
|
|
105
|
+
resolveRowKey: listUiRuntime.resolveRowKey,
|
|
106
|
+
resolveParams: listUiRuntime.resolveParams,
|
|
107
|
+
resolveViewUrl: listUiRuntime.resolveViewUrl,
|
|
108
|
+
resolveEditUrl: listUiRuntime.resolveEditUrl
|
|
82
109
|
});
|
|
83
110
|
}
|
|
84
111
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
|
+
import { useRoute } from "vue-router";
|
|
2
3
|
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
3
4
|
import { useViewCore } from "./useViewCore.js";
|
|
4
5
|
import { useEndpointResource } from "./useEndpointResource.js";
|
|
5
|
-
import {
|
|
6
|
+
import { resolveOperationAdapter } from "./operationAdapters.js";
|
|
6
7
|
import { setupOperationErrorReporting } from "./operationUiHelpers.js";
|
|
8
|
+
import { createViewUiRuntime } from "./viewUiRuntime.js";
|
|
7
9
|
|
|
8
10
|
function useView({
|
|
9
11
|
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
@@ -20,14 +22,37 @@ function useView({
|
|
|
20
22
|
notFoundMessage = "Record not found.",
|
|
21
23
|
model,
|
|
22
24
|
mapLoadedToModel,
|
|
23
|
-
|
|
25
|
+
recordIdParam = "recordId",
|
|
26
|
+
routeParams = null,
|
|
27
|
+
routeRecordId = null,
|
|
28
|
+
apiUrlTemplate = "",
|
|
29
|
+
listUrlTemplate = "",
|
|
30
|
+
editUrlTemplate = "",
|
|
31
|
+
includeRecordIdInQueryKey = false,
|
|
32
|
+
realtime = null,
|
|
33
|
+
adapter = null
|
|
24
34
|
} = {}) {
|
|
25
|
-
const
|
|
35
|
+
const route = useRoute();
|
|
36
|
+
const viewUiRuntime = createViewUiRuntime({
|
|
37
|
+
recordIdParam,
|
|
38
|
+
routeParams: routeParams ?? computed(() => route?.params || {}),
|
|
39
|
+
routePath: computed(() => route?.path || ""),
|
|
40
|
+
routeRecordId,
|
|
41
|
+
apiUrlTemplate,
|
|
42
|
+
listUrlTemplate,
|
|
43
|
+
editUrlTemplate
|
|
44
|
+
});
|
|
45
|
+
const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
|
|
46
|
+
const effectiveApiSuffix = normalizedApiUrlTemplate ? viewUiRuntime.apiSuffix : apiSuffix;
|
|
47
|
+
const operationAdapter = resolveOperationAdapter(adapter, {
|
|
48
|
+
context: "useView adapter"
|
|
49
|
+
});
|
|
50
|
+
const operationScope = operationAdapter.useOperationScope({
|
|
26
51
|
ownershipFilter,
|
|
27
52
|
surfaceId,
|
|
28
53
|
access,
|
|
29
54
|
placementSource,
|
|
30
|
-
apiSuffix,
|
|
55
|
+
apiSuffix: effectiveApiSuffix,
|
|
31
56
|
readEnabled,
|
|
32
57
|
queryKeyFactory,
|
|
33
58
|
permissionSets: {
|
|
@@ -35,10 +60,19 @@ function useView({
|
|
|
35
60
|
},
|
|
36
61
|
realtime
|
|
37
62
|
});
|
|
63
|
+
const queryKey = computed(() => {
|
|
64
|
+
const source = Array.isArray(operationScope.queryKey.value) ? operationScope.queryKey.value : [];
|
|
65
|
+
if (!includeRecordIdInQueryKey) {
|
|
66
|
+
return source;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const recordIdToken = String(viewUiRuntime.recordId.value || "").trim();
|
|
70
|
+
return [...source, recordIdToken];
|
|
71
|
+
});
|
|
38
72
|
const canView = operationScope.permissionGate("view");
|
|
39
73
|
|
|
40
74
|
const resource = useEndpointResource({
|
|
41
|
-
queryKey
|
|
75
|
+
queryKey,
|
|
42
76
|
path: operationScope.apiPath,
|
|
43
77
|
enabled: operationScope.queryCanRun(canView),
|
|
44
78
|
readMethod,
|
|
@@ -74,6 +108,10 @@ function useView({
|
|
|
74
108
|
|
|
75
109
|
return Object.freeze({
|
|
76
110
|
record: view.record,
|
|
111
|
+
recordId: viewUiRuntime.recordId,
|
|
112
|
+
listUrl: viewUiRuntime.listUrl,
|
|
113
|
+
editUrl: viewUiRuntime.editUrl,
|
|
114
|
+
resolveParams: viewUiRuntime.resolveParams,
|
|
77
115
|
canView,
|
|
78
116
|
isLoading,
|
|
79
117
|
isFetching,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { asPlainObject } from "./scopeHelpers.js";
|
|
3
|
+
import {
|
|
4
|
+
normalizeRouteParamName,
|
|
5
|
+
resolveRouteParamsSource,
|
|
6
|
+
resolveRoutePathnameSource,
|
|
7
|
+
resolveRouteTemplateLocation,
|
|
8
|
+
toRouteParamValue
|
|
9
|
+
} from "./routeTemplateHelpers.js";
|
|
10
|
+
|
|
11
|
+
function resolveRecordId({ routeParams, recordIdParam, routeRecordId }) {
|
|
12
|
+
const explicitRecordId = toRouteParamValue(
|
|
13
|
+
typeof routeRecordId === "function" ? routeRecordId() : unref(routeRecordId)
|
|
14
|
+
);
|
|
15
|
+
if (explicitRecordId) {
|
|
16
|
+
return explicitRecordId;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return toRouteParamValue(routeParams[recordIdParam]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createViewUiRuntime({
|
|
23
|
+
recordIdParam = "recordId",
|
|
24
|
+
routeParams = null,
|
|
25
|
+
routePath = "",
|
|
26
|
+
routeRecordId = null,
|
|
27
|
+
apiUrlTemplate = "",
|
|
28
|
+
listUrlTemplate = "",
|
|
29
|
+
editUrlTemplate = ""
|
|
30
|
+
} = {}) {
|
|
31
|
+
const normalizedRecordIdParam = normalizeRouteParamName(recordIdParam, {
|
|
32
|
+
context: "useView recordIdParam"
|
|
33
|
+
});
|
|
34
|
+
const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
|
|
35
|
+
const normalizedListUrlTemplate = String(listUrlTemplate || "").trim();
|
|
36
|
+
const normalizedEditUrlTemplate = String(editUrlTemplate || "").trim();
|
|
37
|
+
|
|
38
|
+
function resolveTemplatePath(urlTemplate = "", extraParams = {}) {
|
|
39
|
+
const normalizedTemplate = String(urlTemplate || "").trim();
|
|
40
|
+
if (!normalizedTemplate) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const currentRouteParams = resolveRouteParamsSource(routeParams);
|
|
45
|
+
const sourceParams = {
|
|
46
|
+
...currentRouteParams,
|
|
47
|
+
...asPlainObject(extraParams)
|
|
48
|
+
};
|
|
49
|
+
const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
|
|
50
|
+
resolveRecordId({
|
|
51
|
+
routeParams: currentRouteParams,
|
|
52
|
+
recordIdParam: normalizedRecordIdParam,
|
|
53
|
+
routeRecordId
|
|
54
|
+
});
|
|
55
|
+
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
56
|
+
|
|
57
|
+
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
58
|
+
params: sourceParams,
|
|
59
|
+
currentPathname: resolveRoutePathnameSource(routePath)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const recordId = computed(() =>
|
|
64
|
+
resolveRecordId({
|
|
65
|
+
routeParams: resolveRouteParamsSource(routeParams),
|
|
66
|
+
recordIdParam: normalizedRecordIdParam,
|
|
67
|
+
routeRecordId
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
const apiSuffix = computed(() => resolveTemplatePath(normalizedApiUrlTemplate));
|
|
71
|
+
const listUrl = computed(() => resolveTemplatePath(normalizedListUrlTemplate));
|
|
72
|
+
const editUrl = computed(() => resolveTemplatePath(normalizedEditUrlTemplate));
|
|
73
|
+
|
|
74
|
+
function resolveParams(urlTemplate = "", extraParams = {}) {
|
|
75
|
+
return resolveTemplatePath(urlTemplate, extraParams);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
recordId,
|
|
80
|
+
apiSuffix,
|
|
81
|
+
listUrl,
|
|
82
|
+
editUrl,
|
|
83
|
+
resolveParams
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { createViewUiRuntime };
|
package/src/client/lib/theme.js
CHANGED
|
@@ -4,7 +4,6 @@ import { resolveWorkspaceThemePalette } from "@jskit-ai/users-core/shared/settin
|
|
|
4
4
|
const THEME_PREFERENCE_LIGHT = "light";
|
|
5
5
|
const THEME_PREFERENCE_DARK = "dark";
|
|
6
6
|
const THEME_PREFERENCE_SYSTEM = "system";
|
|
7
|
-
const THEME_PREFERENCE_STORAGE_KEY = "jskit.themePreference";
|
|
8
7
|
const HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
9
8
|
const WORKSPACE_THEME_NAME_LIGHT = "workspace-light";
|
|
10
9
|
const WORKSPACE_THEME_NAME_DARK = "workspace-dark";
|
|
@@ -71,7 +70,7 @@ function readPersistedThemePreference(options = {}) {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
try {
|
|
74
|
-
const value = storage.getItem(
|
|
73
|
+
const value = storage.getItem("jskit.themePreference");
|
|
75
74
|
return normalizeThemePreference(value);
|
|
76
75
|
} catch {
|
|
77
76
|
return THEME_PREFERENCE_SYSTEM;
|
|
@@ -86,7 +85,7 @@ function persistThemePreference(themePreference, options = {}) {
|
|
|
86
85
|
|
|
87
86
|
const normalizedPreference = normalizeThemePreference(themePreference);
|
|
88
87
|
try {
|
|
89
|
-
storage.setItem(
|
|
88
|
+
storage.setItem("jskit.themePreference", normalizedPreference);
|
|
90
89
|
return true;
|
|
91
90
|
} catch {
|
|
92
91
|
return false;
|
|
@@ -9,21 +9,9 @@ import ProfileClientElement from "../components/ProfileClientElement.vue";
|
|
|
9
9
|
import MembersAdminClientElement from "../components/MembersAdminClientElement.vue";
|
|
10
10
|
import WorkspaceSettingsClientElement from "../components/WorkspaceSettingsClientElement.vue";
|
|
11
11
|
import {
|
|
12
|
-
USERS_WEB_BOOTSTRAP_PLACEMENT_RUNTIME_TOKEN,
|
|
13
12
|
createBootstrapPlacementRuntime
|
|
14
13
|
} from "../runtime/bootstrapPlacementRuntime.js";
|
|
15
14
|
|
|
16
|
-
const USERS_WEB_WORKSPACE_SELECTOR_TOKEN = "users.web.workspace.selector";
|
|
17
|
-
const USERS_WEB_WORKSPACE_TOOLS_WIDGET_TOKEN = "users.web.workspace.tools.widget";
|
|
18
|
-
const USERS_WEB_SHELL_MENU_LINK_ITEM_TOKEN = "users.web.shell.menu-link-item";
|
|
19
|
-
const USERS_WEB_SURFACE_AWARE_MENU_LINK_ITEM_TOKEN = "users.web.shell.surface-aware-menu-link-item";
|
|
20
|
-
const USERS_WEB_PROFILE_SURFACE_SWITCH_MENU_ITEM_TOKEN = "users.web.profile.menu.surface-switch-item";
|
|
21
|
-
const USERS_WEB_WORKSPACE_SETTINGS_MENU_ITEM_TOKEN = "users.web.workspace-settings.menu-item";
|
|
22
|
-
const USERS_WEB_WORKSPACE_MEMBERS_MENU_ITEM_TOKEN = "users.web.workspace-members.menu-item";
|
|
23
|
-
const USERS_WEB_PROFILE_ELEMENT_TOKEN = "users.web.profile.element";
|
|
24
|
-
const USERS_WEB_MEMBERS_ADMIN_ELEMENT_TOKEN = "users.web.members-admin.element";
|
|
25
|
-
const USERS_WEB_WORKSPACE_SETTINGS_ELEMENT_TOKEN = "users.web.workspace-settings.element";
|
|
26
|
-
|
|
27
15
|
class UsersWebClientProvider {
|
|
28
16
|
static id = "users.web.client";
|
|
29
17
|
static dependsOn = ["shell.web.client"];
|
|
@@ -33,17 +21,17 @@ class UsersWebClientProvider {
|
|
|
33
21
|
throw new Error("UsersWebClientProvider requires application singleton().");
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
app.singleton(
|
|
37
|
-
app.singleton(
|
|
38
|
-
app.singleton(
|
|
39
|
-
app.singleton(
|
|
40
|
-
app.singleton(
|
|
41
|
-
app.singleton(
|
|
42
|
-
app.singleton(
|
|
43
|
-
app.singleton(
|
|
44
|
-
app.singleton(
|
|
45
|
-
app.singleton(
|
|
46
|
-
app.singleton(
|
|
24
|
+
app.singleton("users.web.workspace.selector", () => UsersWorkspaceSelector);
|
|
25
|
+
app.singleton("users.web.workspace.tools.widget", () => UsersWorkspaceToolsWidget);
|
|
26
|
+
app.singleton("users.web.shell.menu-link-item", () => UsersShellMenuLinkItem);
|
|
27
|
+
app.singleton("users.web.shell.surface-aware-menu-link-item", () => UsersSurfaceAwareMenuLinkItem);
|
|
28
|
+
app.singleton("users.web.profile.menu.surface-switch-item", () => UsersProfileSurfaceSwitchMenuItem);
|
|
29
|
+
app.singleton("users.web.workspace-settings.menu-item", () => UsersWorkspaceSettingsMenuItem);
|
|
30
|
+
app.singleton("users.web.workspace-members.menu-item", () => UsersWorkspaceMembersMenuItem);
|
|
31
|
+
app.singleton("users.web.profile.element", () => ProfileClientElement);
|
|
32
|
+
app.singleton("users.web.members-admin.element", () => MembersAdminClientElement);
|
|
33
|
+
app.singleton("users.web.workspace-settings.element", () => WorkspaceSettingsClientElement);
|
|
34
|
+
app.singleton("users.web.bootstrap-placement.runtime", (scope) => createBootstrapPlacementRuntime({ app: scope }));
|
|
47
35
|
}
|
|
48
36
|
|
|
49
37
|
async boot(app) {
|
|
@@ -51,7 +39,7 @@ class UsersWebClientProvider {
|
|
|
51
39
|
throw new Error("UsersWebClientProvider requires application make().");
|
|
52
40
|
}
|
|
53
41
|
|
|
54
|
-
const runtime = app.make(
|
|
42
|
+
const runtime = app.make("users.web.bootstrap-placement.runtime");
|
|
55
43
|
if (runtime && typeof runtime.initialize === "function") {
|
|
56
44
|
await runtime.initialize();
|
|
57
45
|
}
|
|
@@ -62,24 +50,11 @@ class UsersWebClientProvider {
|
|
|
62
50
|
return;
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
const runtime = app.make(
|
|
53
|
+
const runtime = app.make("users.web.bootstrap-placement.runtime");
|
|
66
54
|
if (runtime && typeof runtime.shutdown === "function") {
|
|
67
55
|
runtime.shutdown();
|
|
68
56
|
}
|
|
69
57
|
}
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
export {
|
|
73
|
-
UsersWebClientProvider,
|
|
74
|
-
USERS_WEB_WORKSPACE_SELECTOR_TOKEN,
|
|
75
|
-
USERS_WEB_WORKSPACE_TOOLS_WIDGET_TOKEN,
|
|
76
|
-
USERS_WEB_SHELL_MENU_LINK_ITEM_TOKEN,
|
|
77
|
-
USERS_WEB_SURFACE_AWARE_MENU_LINK_ITEM_TOKEN,
|
|
78
|
-
USERS_WEB_PROFILE_SURFACE_SWITCH_MENU_ITEM_TOKEN,
|
|
79
|
-
USERS_WEB_WORKSPACE_SETTINGS_MENU_ITEM_TOKEN,
|
|
80
|
-
USERS_WEB_WORKSPACE_MEMBERS_MENU_ITEM_TOKEN,
|
|
81
|
-
USERS_WEB_PROFILE_ELEMENT_TOKEN,
|
|
82
|
-
USERS_WEB_MEMBERS_ADMIN_ELEMENT_TOKEN,
|
|
83
|
-
USERS_WEB_WORKSPACE_SETTINGS_ELEMENT_TOKEN,
|
|
84
|
-
USERS_WEB_BOOTSTRAP_PLACEMENT_RUNTIME_TOKEN
|
|
85
|
-
};
|
|
60
|
+
export { UsersWebClientProvider };
|