@jskit-ai/users-web 0.1.53 → 0.1.55
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 +15 -53
- package/package.json +16 -11
- package/src/client/account-settings/sections.js +74 -0
- package/src/client/composables/account-settings/accountSettingsRuntimeHelpers.js +2 -38
- package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -2
- package/src/client/composables/internal/crudListParentTitleSupport.js +1 -1
- package/src/client/composables/internal/useOperationScope.js +12 -12
- package/src/client/composables/records/useAddEdit.js +2 -2
- package/src/client/composables/records/useList.js +3 -3
- package/src/client/composables/records/useView.js +2 -2
- package/src/client/composables/support/scopeHelpers.js +19 -19
- package/src/client/composables/useAccess.js +3 -3
- package/src/client/composables/useAccountSettingsRuntime.js +8 -156
- package/src/client/composables/useCommand.js +2 -2
- package/src/client/composables/useCrudListParentTitle.js +2 -2
- package/src/client/composables/usePaths.js +50 -38
- package/src/client/composables/useScopeRuntime.js +55 -27
- package/src/client/composables/useSurfaceRouteContext.js +1 -7
- package/src/client/index.js +0 -1
- package/src/client/lib/bootstrap.js +0 -63
- package/src/client/lib/httpClient.js +2 -59
- package/src/client/lib/theme.js +12 -189
- package/src/client/providers/UsersWebClientProvider.js +2 -25
- package/src/client/providers/bootUsersWebClientProvider.js +28 -0
- package/src/shared/toolsOutletContracts.js +1 -8
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +33 -21
- package/test/accountSettingsSections.test.js +79 -0
- package/test/exportsContract.test.js +2 -2
- package/test/scopeHelpers.test.js +6 -6
- package/test/settingsPlacementContract.test.js +4 -11
- package/test/theme.test.js +0 -56
- package/src/client/components/MembersAdminClientElement.vue +0 -400
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +0 -39
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +0 -36
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +0 -90
- package/src/client/components/UsersWorkspaceSelector.vue +0 -248
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +0 -39
- package/src/client/components/UsersWorkspaceToolsWidget.vue +0 -12
- package/src/client/components/WorkspaceMembersClientElement.vue +0 -655
- package/src/client/components/WorkspaceProfileClientElement.vue +0 -116
- package/src/client/components/WorkspaceSettingsClientElement.vue +0 -102
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +0 -265
- package/src/client/components/WorkspacesClientElement.vue +0 -509
- package/src/client/composables/account-settings/accountSettingsInvitesRuntime.js +0 -88
- package/src/client/composables/useBootstrapQuery.js +0 -52
- package/src/client/composables/useWorkspaceRouteContext.js +0 -28
- package/src/client/composables/useWorkspaceSurfaceId.js +0 -43
- package/src/client/lib/menuIcons.js +0 -201
- package/src/client/lib/profileSurfaceMenuLinks.js +0 -142
- package/src/client/lib/surfaceAccessPolicy.js +0 -350
- package/src/client/lib/workspaceLinkResolver.js +0 -207
- package/src/client/lib/workspaceSurfaceContext.js +0 -82
- package/src/client/lib/workspaceSurfacePaths.js +0 -163
- package/src/client/providers/UsersWorkspacesClientProvider.js +0 -24
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +0 -371
- package/src/client/runtime/bootstrapPlacementRuntime.js +0 -463
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +0 -28
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +0 -147
- package/src/client/support/menuLinkTarget.js +0 -93
- package/src/client/support/realtimeWorkspace.js +0 -21
- package/src/client/support/runtimeNormalization.js +0 -27
- package/src/client/support/workspaceQueryKeys.js +0 -15
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +0 -77
- package/test/bootstrapPlacementRuntime.test.js +0 -1095
- package/test/menuIcons.test.js +0 -34
- package/test/menuLinkTarget.test.js +0 -116
- package/test/profileSurfaceMenuLinks.test.js +0 -208
- package/test/surfaceAccessPolicy.test.js +0 -129
- package/test/workspaceLinkResolver.test.js +0 -61
- package/test/workspaceSurfacePaths.test.js +0 -39
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
|
2
2
|
import { useQueryClient } from "@tanstack/vue-query";
|
|
3
3
|
import { useTheme } from "vuetify";
|
|
4
|
-
import { useRoute
|
|
4
|
+
import { useRoute } from "vue-router";
|
|
5
5
|
import {
|
|
6
6
|
useWebPlacementContext,
|
|
7
7
|
resolveSurfaceNavigationTargetFromPlacementContext
|
|
8
8
|
} from "@jskit-ai/shell-web/client/placement";
|
|
9
9
|
import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
10
10
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
11
|
+
import { ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/kernel/shared/support/visibility";
|
|
11
12
|
import { userProfileResource } from "@jskit-ai/users-core/shared/resources/userProfileResource";
|
|
12
13
|
import { userSettingsResource } from "@jskit-ai/users-core/shared/resources/userSettingsResource";
|
|
13
|
-
import { USERS_ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
14
14
|
import {
|
|
15
15
|
persistThemePreference,
|
|
16
16
|
resolveThemeNameForPreference,
|
|
17
17
|
setVuetifyThemeName
|
|
18
18
|
} from "../lib/theme.js";
|
|
19
|
-
import {
|
|
20
|
-
useWorkspaceSurfaceId
|
|
21
|
-
} from "./useWorkspaceSurfaceId.js";
|
|
22
19
|
import { useAddEdit } from "./records/useAddEdit.js";
|
|
23
20
|
import { useCommand } from "./useCommand.js";
|
|
24
21
|
import { useView } from "./records/useView.js";
|
|
25
|
-
import {
|
|
26
|
-
import { resolveAccountSettingsPathFromPlacementContext } from "../lib/workspaceSurfacePaths.js";
|
|
22
|
+
import { resolveSurfacePathFromPlacementContext } from "@jskit-ai/shell-web/client/placement";
|
|
27
23
|
import {
|
|
28
24
|
ACCOUNT_SETTINGS_DEFAULTS,
|
|
29
25
|
AVATAR_DEFAULT_SIZE,
|
|
@@ -37,19 +33,15 @@ import {
|
|
|
37
33
|
} from "./account-settings/accountSettingsRuntimeConstants.js";
|
|
38
34
|
import {
|
|
39
35
|
normalizeAvatarSize,
|
|
40
|
-
normalizePendingInvite,
|
|
41
36
|
normalizeReturnToPath,
|
|
42
37
|
normalizeSettingsPayload,
|
|
43
38
|
resolveAllowedReturnToOrigins
|
|
44
39
|
} from "./account-settings/accountSettingsRuntimeHelpers.js";
|
|
45
40
|
import { createAccountSettingsAvatarUploadRuntime } from "./account-settings/accountSettingsAvatarUploadRuntime.js";
|
|
46
|
-
import { createAccountSettingsInvitesRuntime } from "./account-settings/accountSettingsInvitesRuntime.js";
|
|
47
41
|
|
|
48
42
|
function useAccountSettingsRuntime() {
|
|
49
43
|
const route = useRoute();
|
|
50
|
-
const router = useRouter();
|
|
51
44
|
const { context: placementContext } = useWebPlacementContext();
|
|
52
|
-
const paths = usePaths();
|
|
53
45
|
const queryClient = useQueryClient();
|
|
54
46
|
const errorRuntime = useShellWebErrorRuntime();
|
|
55
47
|
const vuetifyTheme = useTheme();
|
|
@@ -58,11 +50,12 @@ function useAccountSettingsRuntime() {
|
|
|
58
50
|
const accountProfileWriteQueryKey = ["users-web", "settings", "account-profile-write"];
|
|
59
51
|
const accountPreferencesWriteQueryKey = ["users-web", "settings", "account-preferences-write"];
|
|
60
52
|
const accountNotificationsWriteQueryKey = ["users-web", "settings", "account-notifications-write"];
|
|
61
|
-
const pendingInvitesQueryKey = ["users-web", "settings", "pending-invites"];
|
|
62
53
|
const sessionQueryKey = Object.freeze(["users-web", "session", "csrf"]);
|
|
63
|
-
const OWNERSHIP_PUBLIC =
|
|
54
|
+
const OWNERSHIP_PUBLIC = ROUTE_VISIBILITY_PUBLIC;
|
|
64
55
|
|
|
65
|
-
const accountSettingsPath = computed(() =>
|
|
56
|
+
const accountSettingsPath = computed(() =>
|
|
57
|
+
resolveSurfacePathFromPlacementContext(placementContext.value, "account", "/")
|
|
58
|
+
);
|
|
66
59
|
const allowedReturnToOrigins = computed(() => resolveAllowedReturnToOrigins(placementContext.value));
|
|
67
60
|
const backTarget = computed(() =>
|
|
68
61
|
normalizeReturnToPath(route?.query?.returnTo, {
|
|
@@ -107,20 +100,7 @@ function useAccountSettingsRuntime() {
|
|
|
107
100
|
version: null
|
|
108
101
|
});
|
|
109
102
|
|
|
110
|
-
const pendingInvitesModel = reactive({
|
|
111
|
-
pendingInvites: [],
|
|
112
|
-
workspaceInvitesEnabled: false
|
|
113
|
-
});
|
|
114
|
-
|
|
115
103
|
const selectedAvatarFileName = ref("");
|
|
116
|
-
const inviteAction = ref({
|
|
117
|
-
token: "",
|
|
118
|
-
decision: ""
|
|
119
|
-
});
|
|
120
|
-
const redeemInviteModel = reactive({
|
|
121
|
-
token: "",
|
|
122
|
-
decision: ""
|
|
123
|
-
});
|
|
124
104
|
|
|
125
105
|
const profileInitials = computed(() => {
|
|
126
106
|
const source = String(profileForm.displayName || profileForm.email || "U").trim();
|
|
@@ -211,41 +191,6 @@ function useAccountSettingsRuntime() {
|
|
|
211
191
|
mapLoadedToModel: mapAccountSettingsPayload
|
|
212
192
|
});
|
|
213
193
|
|
|
214
|
-
const pendingInvitesView = useView({
|
|
215
|
-
ownershipFilter: OWNERSHIP_PUBLIC,
|
|
216
|
-
apiSuffix: "/bootstrap",
|
|
217
|
-
queryKeyFactory: () => pendingInvitesQueryKey,
|
|
218
|
-
realtime: {
|
|
219
|
-
event: "workspace.invitations.pending.changed"
|
|
220
|
-
},
|
|
221
|
-
fallbackLoadError: "Unable to load invitations.",
|
|
222
|
-
model: pendingInvitesModel,
|
|
223
|
-
mapLoadedToModel: (model, payload = {}) => {
|
|
224
|
-
model.workspaceInvitesEnabled = payload?.app?.features?.workspaceInvites === true;
|
|
225
|
-
model.pendingInvites = model.workspaceInvitesEnabled
|
|
226
|
-
? (Array.isArray(payload?.pendingInvites) ? payload.pendingInvites : [])
|
|
227
|
-
.map(normalizePendingInvite)
|
|
228
|
-
.filter(Boolean)
|
|
229
|
-
: [];
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const redeemInviteCommand = useCommand({
|
|
234
|
-
ownershipFilter: OWNERSHIP_PUBLIC,
|
|
235
|
-
apiSuffix: "/workspace/invitations/redeem",
|
|
236
|
-
writeMethod: "POST",
|
|
237
|
-
fallbackRunError: "Unable to respond to invitation.",
|
|
238
|
-
suppressSuccessMessage: true,
|
|
239
|
-
model: redeemInviteModel,
|
|
240
|
-
buildRawPayload: (model) => ({
|
|
241
|
-
token: String(model.token || "").trim(),
|
|
242
|
-
decision: String(model.decision || "").trim().toLowerCase()
|
|
243
|
-
}),
|
|
244
|
-
messages: {
|
|
245
|
-
error: "Unable to respond to invitation."
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
194
|
const profileAddEdit = useAddEdit({
|
|
250
195
|
ownershipFilter: OWNERSHIP_PUBLIC,
|
|
251
196
|
resource: userProfileResource,
|
|
@@ -349,91 +294,10 @@ function useAccountSettingsRuntime() {
|
|
|
349
294
|
|
|
350
295
|
const loadingSettings = computed(() => Boolean(settingsView.isLoading));
|
|
351
296
|
const refreshingSettings = computed(() => Boolean(settingsView.isRefetching));
|
|
352
|
-
const invitesAvailable = computed(() => pendingInvitesModel.workspaceInvitesEnabled === true);
|
|
353
|
-
const loadingInvites = computed(() => Boolean(pendingInvitesView.isLoading));
|
|
354
|
-
const refreshingInvites = computed(() => Boolean(pendingInvitesView.isRefetching));
|
|
355
|
-
const pendingInvites = computed(() => {
|
|
356
|
-
return Array.isArray(pendingInvitesModel.pendingInvites) ? pendingInvitesModel.pendingInvites : [];
|
|
357
|
-
});
|
|
358
|
-
const isResolvingInvite = computed(() => Boolean(redeemInviteCommand.isRunning.value));
|
|
359
|
-
|
|
360
|
-
const { workspaceSurfaceId } = useWorkspaceSurfaceId({
|
|
361
|
-
route,
|
|
362
|
-
placementContext
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
function workspaceHomePath(workspaceSlug) {
|
|
366
|
-
const normalizedSlug = String(workspaceSlug || "").trim();
|
|
367
|
-
if (!normalizedSlug || !workspaceSurfaceId.value) {
|
|
368
|
-
return "";
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return paths.page("/", {
|
|
372
|
-
surface: workspaceSurfaceId.value,
|
|
373
|
-
workspaceSlug: normalizedSlug,
|
|
374
|
-
mode: "workspace"
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
297
|
async function submitProfile() {
|
|
379
298
|
await profileAddEdit.submit();
|
|
380
299
|
}
|
|
381
300
|
|
|
382
|
-
async function openWorkspace(workspaceSlug) {
|
|
383
|
-
const targetPath = workspaceHomePath(workspaceSlug);
|
|
384
|
-
if (!targetPath) {
|
|
385
|
-
reportAccountFeedback({
|
|
386
|
-
message: "Workspace surface is not configured.",
|
|
387
|
-
severity: "error",
|
|
388
|
-
channel: "banner",
|
|
389
|
-
dedupeKey: "users-web.account-settings-runtime:workspace-surface-missing"
|
|
390
|
-
});
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
try {
|
|
395
|
-
const navigationTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
396
|
-
path: targetPath,
|
|
397
|
-
surfaceId: workspaceSurfaceId.value
|
|
398
|
-
});
|
|
399
|
-
if (navigationTarget.sameOrigin) {
|
|
400
|
-
await router.push(navigationTarget.href);
|
|
401
|
-
} else if (typeof window === "object" && window?.location && typeof window.location.assign === "function") {
|
|
402
|
-
window.location.assign(navigationTarget.href);
|
|
403
|
-
} else {
|
|
404
|
-
throw new Error("Cross-origin navigation is unavailable in this environment.");
|
|
405
|
-
}
|
|
406
|
-
} catch (error) {
|
|
407
|
-
reportAccountFeedback({
|
|
408
|
-
message: String(error?.message || "Unable to open workspace."),
|
|
409
|
-
severity: "error",
|
|
410
|
-
channel: "banner",
|
|
411
|
-
dedupeKey: `users-web.account-settings-runtime:open-workspace:${String(workspaceSlug || "").trim()}`
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const invitesRuntime = createAccountSettingsInvitesRuntime({
|
|
417
|
-
invitesAvailable,
|
|
418
|
-
isResolvingInvite,
|
|
419
|
-
inviteAction,
|
|
420
|
-
redeemInviteModel,
|
|
421
|
-
redeemInviteCommand,
|
|
422
|
-
pendingInvites,
|
|
423
|
-
pendingInvitesModel,
|
|
424
|
-
pendingInvitesView,
|
|
425
|
-
openWorkspace,
|
|
426
|
-
reportAccountFeedback
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
function acceptInvite(invite) {
|
|
430
|
-
return invitesRuntime.accept(invite);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function refuseInvite(invite) {
|
|
434
|
-
return invitesRuntime.refuse(invite);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
301
|
function openAvatarEditor() {
|
|
438
302
|
avatarUploadRuntime.openEditor();
|
|
439
303
|
}
|
|
@@ -508,17 +372,6 @@ function useAccountSettingsRuntime() {
|
|
|
508
372
|
submit: submitNotifications
|
|
509
373
|
});
|
|
510
374
|
|
|
511
|
-
const invites = Object.freeze({
|
|
512
|
-
isAvailable: invitesAvailable,
|
|
513
|
-
items: pendingInvites,
|
|
514
|
-
isLoading: loadingInvites,
|
|
515
|
-
isRefetching: refreshingInvites,
|
|
516
|
-
isResolving: isResolvingInvite,
|
|
517
|
-
action: inviteAction,
|
|
518
|
-
accept: acceptInvite,
|
|
519
|
-
refuse: refuseInvite
|
|
520
|
-
});
|
|
521
|
-
|
|
522
375
|
return Object.freeze({
|
|
523
376
|
backTarget,
|
|
524
377
|
backNavigationTarget,
|
|
@@ -526,8 +379,7 @@ function useAccountSettingsRuntime() {
|
|
|
526
379
|
refreshingSettings,
|
|
527
380
|
profile,
|
|
528
381
|
preferences,
|
|
529
|
-
notifications
|
|
530
|
-
invites
|
|
382
|
+
notifications
|
|
531
383
|
});
|
|
532
384
|
}
|
|
533
385
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { proxyRefs } from "vue";
|
|
2
|
-
import {
|
|
2
|
+
import { ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/kernel/shared/support/visibility";
|
|
3
3
|
import { useCommandCore } from "./runtime/useCommandCore.js";
|
|
4
4
|
import { useEndpointResource } from "./runtime/useEndpointResource.js";
|
|
5
5
|
import { useOperationScope } from "./internal/useOperationScope.js";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./runtime/operationUiHelpers.js";
|
|
12
12
|
|
|
13
13
|
function useCommand({
|
|
14
|
-
ownershipFilter =
|
|
14
|
+
ownershipFilter = ROUTE_VISIBILITY_WORKSPACE,
|
|
15
15
|
surfaceId = "",
|
|
16
16
|
access = "auto",
|
|
17
17
|
apiSuffix = "",
|
|
@@ -79,12 +79,12 @@ function useCrudListParentTitle({
|
|
|
79
79
|
readEnabled: shouldLoadParentRecord,
|
|
80
80
|
recordIdParam: normalizeText(initialParentDescriptor.routeParamKey) || "recordId",
|
|
81
81
|
includeRecordIdInQueryKey: true,
|
|
82
|
-
queryKeyFactory: (surfaceId = "",
|
|
82
|
+
queryKeyFactory: (surfaceId = "", scopeParamValue = "") => [
|
|
83
83
|
...normalizedQueryKeyPrefix,
|
|
84
84
|
normalizeText(initialParentDescriptor.relationNamespace),
|
|
85
85
|
normalizeText(initialParentDescriptor.routeParamKey),
|
|
86
86
|
String(surfaceId || ""),
|
|
87
|
-
String(
|
|
87
|
+
String(scopeParamValue || "")
|
|
88
88
|
],
|
|
89
89
|
placementSource,
|
|
90
90
|
fallbackLoadError,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { computed, unref } from "vue";
|
|
2
2
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { resolveScopedApiBasePath } from "@jskit-ai/kernel/shared/surface";
|
|
4
|
+
import { resolveSurfaceDefinitionFromPlacementContext } from "@jskit-ai/shell-web/client/placement";
|
|
5
|
+
import { useShellLinkResolver } from "@jskit-ai/shell-web/client/navigation/linkResolver";
|
|
6
|
+
import { useSurfaceRouteContext } from "./useSurfaceRouteContext.js";
|
|
7
7
|
|
|
8
8
|
function normalizePathSuffix(value = "") {
|
|
9
9
|
const raw = normalizeText(unref(value));
|
|
@@ -28,23 +28,43 @@ function resolveSurfaceId(value, fallback = "") {
|
|
|
28
28
|
return "";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
function resolveDefaultSurfaceIdFromPlacementContext(placementContext = null) {
|
|
32
|
+
return resolveSurfaceId(placementContext?.surfaceConfig?.defaultSurfaceId, "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRouteParams(params = null) {
|
|
36
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
37
|
+
return {};
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
const output = {};
|
|
41
|
+
for (const [rawKey, rawValue] of Object.entries(params)) {
|
|
42
|
+
const key = normalizeText(rawKey);
|
|
43
|
+
if (!key) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
48
|
+
const normalizedValue = normalizeText(value);
|
|
49
|
+
if (!normalizedValue) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
output[key] = normalizedValue;
|
|
53
|
+
}
|
|
54
|
+
return output;
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
function
|
|
41
|
-
return
|
|
57
|
+
function resolveRouteParams(baseParams = {}, overrideParams = null) {
|
|
58
|
+
return {
|
|
59
|
+
...normalizeRouteParams(baseParams),
|
|
60
|
+
...normalizeRouteParams(overrideParams)
|
|
61
|
+
};
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
function usePaths({ routeContext: sourceRouteContext = null } = {}) {
|
|
45
|
-
const routeContext = sourceRouteContext ||
|
|
46
|
-
const
|
|
47
|
-
const
|
|
65
|
+
const routeContext = sourceRouteContext || useSurfaceRouteContext();
|
|
66
|
+
const shellLinkResolver = useShellLinkResolver();
|
|
67
|
+
const routeParams = computed(() => normalizeRouteParams(routeContext.route?.params));
|
|
48
68
|
|
|
49
69
|
function page(relativePath = "/", options = {}) {
|
|
50
70
|
const source = options && typeof options === "object" && !Array.isArray(options) ? options : {};
|
|
@@ -54,13 +74,12 @@ function usePaths({ routeContext: sourceRouteContext = null } = {}) {
|
|
|
54
74
|
if (!surface) {
|
|
55
75
|
return "";
|
|
56
76
|
}
|
|
57
|
-
|
|
58
|
-
const mode = normalizeText(source.mode).toLowerCase() || "auto";
|
|
59
|
-
|
|
60
|
-
return workspaceLinkResolver.resolve(relativePath, {
|
|
77
|
+
return shellLinkResolver.resolve(relativePath, {
|
|
61
78
|
surface,
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
explicitTo: source.explicitTo,
|
|
80
|
+
surfaceRelativePath: source.surfaceRelativePath,
|
|
81
|
+
params: resolveRouteParams(routeParams.value, source.params),
|
|
82
|
+
strictParams: source.strictParams !== false
|
|
64
83
|
});
|
|
65
84
|
}
|
|
66
85
|
|
|
@@ -70,36 +89,29 @@ function usePaths({ routeContext: sourceRouteContext = null } = {}) {
|
|
|
70
89
|
resolveSurfaceId(source.surface, routeContext.currentSurfaceId.value) ||
|
|
71
90
|
resolveDefaultSurfaceIdFromPlacementContext(routeContext.placementContext.value);
|
|
72
91
|
const suffix = normalizePathSuffix(relativePath);
|
|
73
|
-
const workspaceScoped = surfaceRequiresWorkspaceFromPlacementContext(routeContext.placementContext.value, surface);
|
|
74
92
|
|
|
75
93
|
if (!suffix) {
|
|
76
94
|
throw new TypeError("usePaths().api(relativePath) requires a non-empty relativePath.");
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (workspaceScoped) {
|
|
85
|
-
const nextWorkspaceSlug = resolveWorkspaceSlug(source.workspaceSlug, workspaceSlug.value);
|
|
86
|
-
if (!nextWorkspaceSlug) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
`usePaths().api(${suffix}) requires workspace slug for workspace surface "${surface || "<unknown>"}".`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return templatePath.replace(":workspaceSlug", nextWorkspaceSlug);
|
|
93
|
-
}
|
|
97
|
+
const routeBase = resolveSurfaceDefinitionFromPlacementContext(
|
|
98
|
+
routeContext.placementContext.value,
|
|
99
|
+
surface
|
|
100
|
+
)?.routeBase || "/";
|
|
94
101
|
|
|
95
|
-
return
|
|
102
|
+
return resolveScopedApiBasePath({
|
|
103
|
+
routeBase,
|
|
104
|
+
relativePath: suffix,
|
|
105
|
+
params: resolveRouteParams(routeParams.value, source.params),
|
|
106
|
+
strictParams: source.strictParams !== false
|
|
107
|
+
});
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
return Object.freeze({
|
|
99
111
|
route: routeContext.route,
|
|
100
112
|
placementContext: routeContext.placementContext,
|
|
101
113
|
currentSurfaceId: routeContext.currentSurfaceId,
|
|
102
|
-
|
|
114
|
+
routeParams,
|
|
103
115
|
page,
|
|
104
116
|
api
|
|
105
117
|
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
|
-
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface
|
|
3
|
-
import {
|
|
2
|
+
import { normalizeSurfaceId, resolveScopedRouteBase } from "@jskit-ai/kernel/shared/surface";
|
|
3
|
+
import {
|
|
4
|
+
ROUTE_VISIBILITY_WORKSPACE
|
|
5
|
+
} from "@jskit-ai/kernel/shared/support/visibility";
|
|
4
6
|
import { useAccess } from "./useAccess.js";
|
|
5
|
-
import {
|
|
7
|
+
import { useSurfaceRouteContext } from "./useSurfaceRouteContext.js";
|
|
6
8
|
import { usePaths } from "./usePaths.js";
|
|
7
|
-
import {
|
|
9
|
+
import { resolveSurfaceDefinitionFromPlacementContext } from "@jskit-ai/shell-web/client/placement";
|
|
8
10
|
import {
|
|
9
11
|
asPlainObject,
|
|
10
12
|
ensureAccessModeCompatibility,
|
|
@@ -12,9 +14,15 @@ import {
|
|
|
12
14
|
normalizeOwnershipFilter,
|
|
13
15
|
resolveApiSuffix
|
|
14
16
|
} from "./support/scopeHelpers.js";
|
|
17
|
+
import { extractRouteParamNames, toRouteParamValue } from "./support/routeTemplateHelpers.js";
|
|
18
|
+
|
|
19
|
+
function resolveScopedRouteParamNames(placementContext = null, surfaceId = "") {
|
|
20
|
+
const routeBase = resolveSurfaceDefinitionFromPlacementContext(placementContext, surfaceId)?.routeBase || "/";
|
|
21
|
+
return extractRouteParamNames(resolveScopedRouteBase(routeBase));
|
|
22
|
+
}
|
|
15
23
|
|
|
16
24
|
function useScopeRuntime({
|
|
17
|
-
ownershipFilter =
|
|
25
|
+
ownershipFilter = ROUTE_VISIBILITY_WORKSPACE,
|
|
18
26
|
surfaceId = "",
|
|
19
27
|
accessMode = "auto",
|
|
20
28
|
hasPermissionRequirements = false,
|
|
@@ -29,12 +37,11 @@ function useScopeRuntime({
|
|
|
29
37
|
const accessRequired = resolveAccessModeEnabled(normalizedAccessMode, {
|
|
30
38
|
hasPermissionRequirements
|
|
31
39
|
});
|
|
32
|
-
const routeContext =
|
|
40
|
+
const routeContext = useSurfaceRouteContext();
|
|
33
41
|
const paths = usePaths({
|
|
34
42
|
routeContext
|
|
35
43
|
});
|
|
36
44
|
|
|
37
|
-
const workspaceSlugFromRoute = routeContext.workspaceSlugFromRoute;
|
|
38
45
|
const resolvedSurfaceId = computed(() => {
|
|
39
46
|
const explicitSurfaceId = normalizeSurfaceId(surfaceId);
|
|
40
47
|
if (explicitSurfaceId) {
|
|
@@ -43,64 +50,85 @@ function useScopeRuntime({
|
|
|
43
50
|
|
|
44
51
|
return normalizeSurfaceId(routeContext.currentSurfaceId.value);
|
|
45
52
|
});
|
|
46
|
-
const
|
|
47
|
-
|
|
53
|
+
const scopedRouteParamNames = computed(() =>
|
|
54
|
+
resolveScopedRouteParamNames(routeContext.placementContext.value, resolvedSurfaceId.value)
|
|
48
55
|
);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
const routeScopeParams = computed(() => {
|
|
57
|
+
const source = paths.routeParams.value;
|
|
58
|
+
const next = {};
|
|
59
|
+
for (const paramName of scopedRouteParamNames.value) {
|
|
60
|
+
const paramValue = toRouteParamValue(source[paramName]);
|
|
61
|
+
if (paramValue) {
|
|
62
|
+
next[paramName] = paramValue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return Object.freeze(next);
|
|
66
|
+
});
|
|
67
|
+
const missingScopedRouteParamNames = computed(() =>
|
|
68
|
+
scopedRouteParamNames.value.filter((paramName) => !routeScopeParams.value[paramName])
|
|
69
|
+
);
|
|
70
|
+
const requiresScopedRouteParams = computed(() => scopedRouteParamNames.value.length > 0);
|
|
71
|
+
const hasRequiredRouteScope = computed(() => missingScopedRouteParamNames.value.length < 1);
|
|
72
|
+
const scopeParamValue = computed(() => {
|
|
73
|
+
const [primaryScopeParamName = ""] = scopedRouteParamNames.value;
|
|
74
|
+
return primaryScopeParamName ? routeScopeParams.value[primaryScopeParamName] || "" : "";
|
|
75
|
+
});
|
|
76
|
+
const routeScopeError = computed(() => {
|
|
77
|
+
if (!requiresScopedRouteParams.value || hasRequiredRouteScope.value) {
|
|
52
78
|
return "";
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
|
|
81
|
+
const missingParams = missingScopedRouteParamNames.value.join(", ");
|
|
82
|
+
return `Route parameters ${missingParams} are required for surface "${resolvedSurfaceId.value || "<unknown>"}".`;
|
|
56
83
|
});
|
|
57
84
|
|
|
58
85
|
const accessRuntime = useAccess({
|
|
59
|
-
|
|
60
|
-
enabled: computed(() => accessRequired &&
|
|
86
|
+
scopeParamValue,
|
|
87
|
+
enabled: computed(() => accessRequired && hasRequiredRouteScope.value),
|
|
61
88
|
access: normalizedAccessMode,
|
|
62
89
|
hasPermissionRequirements,
|
|
63
|
-
mergePlacementContext: accessRequired ? routeContext.mergePlacementContext : null,
|
|
64
90
|
placementSource: String(placementSource || "users-web.scope-runtime")
|
|
65
91
|
});
|
|
66
92
|
|
|
67
93
|
function resolveApiPath(apiSuffix = "", context = {}) {
|
|
68
|
-
if (
|
|
94
|
+
if (routeScopeError.value) {
|
|
69
95
|
return "";
|
|
70
96
|
}
|
|
71
97
|
|
|
72
98
|
const suffix = resolveApiSuffix(apiSuffix, {
|
|
73
99
|
surfaceId: routeContext.currentSurfaceId.value,
|
|
74
|
-
|
|
100
|
+
scopeParamValue: scopeParamValue.value,
|
|
75
101
|
ownershipFilter: normalizedOwnershipFilter,
|
|
76
102
|
...asPlainObject(context)
|
|
77
103
|
});
|
|
78
104
|
|
|
79
105
|
return paths.api(suffix, {
|
|
80
106
|
surface: resolvedSurfaceId.value,
|
|
81
|
-
|
|
107
|
+
params: routeScopeParams.value
|
|
82
108
|
});
|
|
83
109
|
}
|
|
84
110
|
|
|
85
|
-
function
|
|
86
|
-
if (
|
|
87
|
-
throw new Error(`${caller}: ${
|
|
111
|
+
function requireRouteScope(caller = "useScopeRuntime") {
|
|
112
|
+
if (routeScopeError.value) {
|
|
113
|
+
throw new Error(`${caller}: ${routeScopeError.value}`);
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
|
|
91
117
|
return Object.freeze({
|
|
92
118
|
normalizedOwnershipFilter,
|
|
93
|
-
|
|
119
|
+
requiresScopedRouteParams: requiresScopedRouteParams.value,
|
|
94
120
|
resolvedSurfaceId,
|
|
95
121
|
accessMode: normalizedAccessMode,
|
|
96
122
|
accessRequired,
|
|
97
123
|
routeContext,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
scopedRouteParamNames,
|
|
125
|
+
routeScopeParams,
|
|
126
|
+
scopeParamValue,
|
|
127
|
+
hasRequiredRouteScope,
|
|
128
|
+
routeScopeError,
|
|
101
129
|
access: accessRuntime,
|
|
102
130
|
resolveApiPath,
|
|
103
|
-
|
|
131
|
+
requireRouteScope
|
|
104
132
|
});
|
|
105
133
|
}
|
|
106
134
|
|
|
@@ -5,19 +5,13 @@ import {
|
|
|
5
5
|
resolveSurfaceIdFromPlacementPathname,
|
|
6
6
|
useWebPlacementContext
|
|
7
7
|
} from "@jskit-ai/shell-web/client/placement";
|
|
8
|
-
import { resolveWorkspaceSurfaceIdFromPlacementPathname } from "../lib/workspaceSurfacePaths.js";
|
|
9
8
|
|
|
10
9
|
function useSurfaceRouteContext() {
|
|
11
10
|
const route = useRoute();
|
|
12
11
|
const { context: placementContext, mergeContext: mergePlacementContext } = useWebPlacementContext();
|
|
13
12
|
const routePath = computed(() => resolveRuntimePathname(route?.path));
|
|
14
13
|
|
|
15
|
-
const currentSurfaceId = computed(() =>
|
|
16
|
-
return (
|
|
17
|
-
resolveWorkspaceSurfaceIdFromPlacementPathname(placementContext.value, routePath.value) ||
|
|
18
|
-
resolveSurfaceIdFromPlacementPathname(placementContext.value, routePath.value)
|
|
19
|
-
);
|
|
20
|
-
});
|
|
14
|
+
const currentSurfaceId = computed(() => resolveSurfaceIdFromPlacementPathname(placementContext.value, routePath.value));
|
|
21
15
|
|
|
22
16
|
return Object.freeze({
|
|
23
17
|
route,
|
package/src/client/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { UsersWebClientProvider } from "./providers/UsersWebClientProvider.js";
|
|
2
2
|
|
|
3
3
|
export { UsersWebClientProvider } from "./providers/UsersWebClientProvider.js";
|
|
4
|
-
export { UsersWorkspacesClientProvider } from "./providers/UsersWorkspacesClientProvider.js";
|
|
5
4
|
|
|
6
5
|
const clientProviders = Object.freeze([UsersWebClientProvider]);
|
|
7
6
|
|
|
@@ -1,64 +1,5 @@
|
|
|
1
1
|
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
|
|
3
|
-
function buildBootstrapApiPath(workspaceSlug = "") {
|
|
4
|
-
const normalizedWorkspaceSlug = String(workspaceSlug || "").trim();
|
|
5
|
-
if (!normalizedWorkspaceSlug) {
|
|
6
|
-
return "/api/bootstrap";
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const query = new URLSearchParams({
|
|
10
|
-
workspaceSlug: normalizedWorkspaceSlug
|
|
11
|
-
});
|
|
12
|
-
return `/api/bootstrap?${query.toString()}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function normalizeWorkspaceEntry(entry) {
|
|
16
|
-
if (!entry || typeof entry !== "object") {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const id = normalizeRecordId(entry.id, { fallback: null });
|
|
21
|
-
const slug = String(entry.slug || "").trim();
|
|
22
|
-
if (!id || !slug) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return Object.freeze({
|
|
27
|
-
id,
|
|
28
|
-
slug,
|
|
29
|
-
name: String(entry.name || slug).trim() || slug,
|
|
30
|
-
color: String(entry.color || "").trim(),
|
|
31
|
-
secondaryColor: String(entry.secondaryColor || "").trim(),
|
|
32
|
-
surfaceColor: String(entry.surfaceColor || "").trim(),
|
|
33
|
-
surfaceVariantColor: String(entry.surfaceVariantColor || "").trim(),
|
|
34
|
-
avatarUrl: String(entry.avatarUrl || "").trim(),
|
|
35
|
-
roleSid: String(entry.roleSid || "member").trim().toLowerCase() || "member",
|
|
36
|
-
isAccessible: entry.isAccessible !== false
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function normalizeWorkspaceList(list) {
|
|
41
|
-
const source = Array.isArray(list) ? list : [];
|
|
42
|
-
return source.map(normalizeWorkspaceEntry).filter(Boolean);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function findWorkspaceBySlug(list, workspaceSlug) {
|
|
46
|
-
const normalizedWorkspaceSlug = String(workspaceSlug || "").trim();
|
|
47
|
-
if (!normalizedWorkspaceSlug) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const source = Array.isArray(list) ? list : [];
|
|
52
|
-
for (const entry of source) {
|
|
53
|
-
const normalizedEntry = normalizeWorkspaceEntry(entry);
|
|
54
|
-
if (normalizedEntry && normalizedEntry.slug === normalizedWorkspaceSlug) {
|
|
55
|
-
return normalizedEntry;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
3
|
function resolvePlacementUserFromBootstrapPayload(payload = {}, currentUser = null) {
|
|
63
4
|
const source = payload && typeof payload === "object" ? payload : {};
|
|
64
5
|
const session = source.session && typeof source.session === "object" ? source.session : {};
|
|
@@ -92,9 +33,5 @@ function resolvePlacementUserFromBootstrapPayload(payload = {}, currentUser = nu
|
|
|
92
33
|
}
|
|
93
34
|
|
|
94
35
|
export {
|
|
95
|
-
buildBootstrapApiPath,
|
|
96
|
-
normalizeWorkspaceEntry,
|
|
97
|
-
normalizeWorkspaceList,
|
|
98
|
-
findWorkspaceBySlug,
|
|
99
36
|
resolvePlacementUserFromBootstrapPayload
|
|
100
37
|
};
|