@jskit-ai/users-web 0.1.4
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 +507 -0
- package/package.json +31 -0
- package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
- package/src/client/components/MembersAdminClientElement.vue +404 -0
- package/src/client/components/ProfileClientElement.vue +242 -0
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
- package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
- package/src/client/components/UsersWorkspaceSelector.vue +237 -0
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
- package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
- package/src/client/components/WorkspacesClientElement.vue +514 -0
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
- package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
- package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
- package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
- package/src/client/composables/errorMessageHelpers.js +66 -0
- package/src/client/composables/internal/useOperationScope.js +144 -0
- package/src/client/composables/modelStateHelpers.js +49 -0
- package/src/client/composables/operationUiHelpers.js +121 -0
- package/src/client/composables/operationValidationHelpers.js +52 -0
- package/src/client/composables/refValueHelpers.js +19 -0
- package/src/client/composables/scopeHelpers.js +145 -0
- package/src/client/composables/useAccess.js +109 -0
- package/src/client/composables/useAccountSettingsRuntime.js +533 -0
- package/src/client/composables/useAddEdit.js +135 -0
- package/src/client/composables/useAddEditCore.js +137 -0
- package/src/client/composables/useBootstrapQuery.js +52 -0
- package/src/client/composables/useCommand.js +112 -0
- package/src/client/composables/useCommandCore.js +130 -0
- package/src/client/composables/useEndpointResource.js +104 -0
- package/src/client/composables/useFieldErrorBag.js +61 -0
- package/src/client/composables/useList.js +85 -0
- package/src/client/composables/useListCore.js +65 -0
- package/src/client/composables/usePagedCollection.js +125 -0
- package/src/client/composables/usePaths.js +108 -0
- package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
- package/src/client/composables/useScopeRuntime.js +107 -0
- package/src/client/composables/useSurfaceRouteContext.js +31 -0
- package/src/client/composables/useUiFeedback.js +96 -0
- package/src/client/composables/useView.js +89 -0
- package/src/client/composables/useViewCore.js +104 -0
- package/src/client/composables/useWorkspaceRouteContext.js +28 -0
- package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
- package/src/client/index.js +7 -0
- package/src/client/lib/bootstrap.js +95 -0
- package/src/client/lib/httpClient.js +67 -0
- package/src/client/lib/menuIcons.js +192 -0
- package/src/client/lib/permissions.js +34 -0
- package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
- package/src/client/lib/surfaceAccessPolicy.js +350 -0
- package/src/client/lib/theme.js +99 -0
- package/src/client/lib/workspaceLinkResolver.js +207 -0
- package/src/client/lib/workspaceSurfaceContext.js +82 -0
- package/src/client/lib/workspaceSurfacePaths.js +163 -0
- package/src/client/providers/UsersWebClientProvider.js +85 -0
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
- package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
- package/src/client/support/contractGuards.js +34 -0
- package/src/client/support/realtimeWorkspace.js +12 -0
- package/src/client/support/runtimeNormalization.js +27 -0
- package/src/client/support/workspaceQueryKeys.js +15 -0
- package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
- package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
- package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
- package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
- package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
- package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
- package/templates/src/pages/account/index.vue +17 -0
- package/templates/src/pages/admin/members/index.vue +7 -0
- package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
- package/templates/src/pages/console/settings/index.vue +16 -0
- package/templates/src/surfaces/admin/index.vue +29 -0
- package/templates/src/surfaces/admin/root.vue +20 -0
- package/templates/src/surfaces/app/index.vue +27 -0
- package/templates/src/surfaces/app/root.vue +20 -0
- package/test/bootstrap.test.js +38 -0
- package/test/bootstrapPlacementRuntime.test.js +991 -0
- package/test/errorMessageHelpers.test.js +28 -0
- package/test/exportsContract.test.js +39 -0
- package/test/menuIcons.test.js +33 -0
- package/test/permissions.test.js +35 -0
- package/test/profileSurfaceMenuLinks.test.js +207 -0
- package/test/refValueHelpers.test.js +14 -0
- package/test/scopeHelpers.test.js +57 -0
- package/test/surfaceAccessPolicy.test.js +129 -0
- package/test/theme.test.js +95 -0
- package/test/workspaceLinkResolver.test.js +61 -0
- package/test/workspaceSurfacePaths.test.js +39 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
3
|
+
import { useViewCore } from "./useViewCore.js";
|
|
4
|
+
import { useEndpointResource } from "./useEndpointResource.js";
|
|
5
|
+
import { useOperationScope } from "./internal/useOperationScope.js";
|
|
6
|
+
import { setupOperationErrorReporting } from "./operationUiHelpers.js";
|
|
7
|
+
|
|
8
|
+
function useView({
|
|
9
|
+
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
10
|
+
surfaceId = "",
|
|
11
|
+
access = "auto",
|
|
12
|
+
apiSuffix = "",
|
|
13
|
+
queryKeyFactory = null,
|
|
14
|
+
viewPermissions = [],
|
|
15
|
+
readMethod = "GET",
|
|
16
|
+
readEnabled = true,
|
|
17
|
+
placementSource = "users-web.view",
|
|
18
|
+
fallbackLoadError = "Unable to load resource.",
|
|
19
|
+
notFoundStatuses = [404],
|
|
20
|
+
notFoundMessage = "Record not found.",
|
|
21
|
+
model,
|
|
22
|
+
mapLoadedToModel,
|
|
23
|
+
realtime = null
|
|
24
|
+
} = {}) {
|
|
25
|
+
const operationScope = useOperationScope({
|
|
26
|
+
ownershipFilter,
|
|
27
|
+
surfaceId,
|
|
28
|
+
access,
|
|
29
|
+
placementSource,
|
|
30
|
+
apiSuffix,
|
|
31
|
+
readEnabled,
|
|
32
|
+
queryKeyFactory,
|
|
33
|
+
permissionSets: {
|
|
34
|
+
view: viewPermissions
|
|
35
|
+
},
|
|
36
|
+
realtime
|
|
37
|
+
});
|
|
38
|
+
const canView = operationScope.permissionGate("view");
|
|
39
|
+
|
|
40
|
+
const resource = useEndpointResource({
|
|
41
|
+
queryKey: operationScope.queryKey,
|
|
42
|
+
path: operationScope.apiPath,
|
|
43
|
+
enabled: operationScope.queryCanRun(canView),
|
|
44
|
+
readMethod,
|
|
45
|
+
fallbackLoadError
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const view = useViewCore({
|
|
49
|
+
resource,
|
|
50
|
+
model,
|
|
51
|
+
canView,
|
|
52
|
+
mapLoadedToModel,
|
|
53
|
+
notFoundStatuses,
|
|
54
|
+
notFoundMessage
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const loadError = operationScope.loadError(view.loadError);
|
|
58
|
+
const isLoading = operationScope.isLoading(view.isLoading);
|
|
59
|
+
const isFetching = operationScope.isLoading(view.isFetching);
|
|
60
|
+
const isRefetching = computed(() => Boolean(isFetching.value && !isLoading.value));
|
|
61
|
+
setupOperationErrorReporting({
|
|
62
|
+
source: `${placementSource}.load`,
|
|
63
|
+
loadError,
|
|
64
|
+
notFoundError: view.notFoundError,
|
|
65
|
+
dedupeWindowMs: 0,
|
|
66
|
+
loadActionFactory: () => ({
|
|
67
|
+
label: "Retry",
|
|
68
|
+
dismissOnRun: true,
|
|
69
|
+
handler() {
|
|
70
|
+
void view.refresh();
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
record: view.record,
|
|
77
|
+
canView,
|
|
78
|
+
isLoading,
|
|
79
|
+
isFetching,
|
|
80
|
+
isRefetching,
|
|
81
|
+
isNotFound: view.isNotFound,
|
|
82
|
+
notFoundError: view.notFoundError,
|
|
83
|
+
loadError,
|
|
84
|
+
refresh: view.refresh,
|
|
85
|
+
resource
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { useView };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { computed, watch } from "vue";
|
|
2
|
+
import { captureModelSnapshot, restoreModelSnapshot } from "./modelStateHelpers.js";
|
|
3
|
+
|
|
4
|
+
function normalizeStatusList(value) {
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return value.map((entry) => Number(entry)).filter((entry) => Number.isInteger(entry) && entry > 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return [404];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function useViewCore({
|
|
13
|
+
resource,
|
|
14
|
+
model,
|
|
15
|
+
canView,
|
|
16
|
+
mapLoadedToModel,
|
|
17
|
+
notFoundStatuses = [404],
|
|
18
|
+
notFoundMessage = "Record not found."
|
|
19
|
+
} = {}) {
|
|
20
|
+
const statusList = normalizeStatusList(notFoundStatuses);
|
|
21
|
+
const modelSnapshot = captureModelSnapshot(model);
|
|
22
|
+
|
|
23
|
+
watch(
|
|
24
|
+
() => resource?.query?.isPending?.value,
|
|
25
|
+
(isPending) => {
|
|
26
|
+
if (!isPending || !modelSnapshot) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
restoreModelSnapshot(model, modelSnapshot);
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
immediate: true
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
watch(
|
|
38
|
+
() => resource?.data?.value,
|
|
39
|
+
(payload) => {
|
|
40
|
+
if (!payload || typeof mapLoadedToModel !== "function") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
mapLoadedToModel(model, payload, {
|
|
45
|
+
resource
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
immediate: true
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const data = resource?.data;
|
|
54
|
+
const record = computed(() => (model !== undefined ? model : data?.value));
|
|
55
|
+
const isLoading = computed(() => Boolean(resource?.query?.isPending?.value));
|
|
56
|
+
const isFetching = computed(() => Boolean(resource?.query?.isFetching?.value));
|
|
57
|
+
const error = computed(() => resource?.query?.error?.value || null);
|
|
58
|
+
|
|
59
|
+
const isNotFound = computed(() => {
|
|
60
|
+
const status = Number(error.value?.status || 0);
|
|
61
|
+
return statusList.includes(status);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const notFoundError = computed(() => {
|
|
65
|
+
if (!isNotFound.value) {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return String(notFoundMessage || "Record not found.");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const loadError = computed(() => {
|
|
73
|
+
if (isNotFound.value) {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return String(resource?.loadError?.value || "").trim();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const resolvedCanView = computed(() => {
|
|
81
|
+
if (canView === undefined) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Boolean(canView?.value);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
async function refresh() {
|
|
89
|
+
return resource?.reload?.();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
record,
|
|
94
|
+
isLoading,
|
|
95
|
+
isFetching,
|
|
96
|
+
isNotFound,
|
|
97
|
+
notFoundError,
|
|
98
|
+
loadError,
|
|
99
|
+
canView: resolvedCanView,
|
|
100
|
+
refresh
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { useViewCore };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
extractWorkspaceSlugFromSurfacePathname
|
|
4
|
+
} from "../lib/workspaceSurfacePaths.js";
|
|
5
|
+
import { useSurfaceRouteContext } from "./useSurfaceRouteContext.js";
|
|
6
|
+
|
|
7
|
+
function useWorkspaceRouteContext() {
|
|
8
|
+
const { route, routePath, placementContext, mergePlacementContext, currentSurfaceId } = useSurfaceRouteContext();
|
|
9
|
+
const workspaceSlugFromRoute = computed(() => {
|
|
10
|
+
const workspaceSlug = extractWorkspaceSlugFromSurfacePathname(
|
|
11
|
+
placementContext.value,
|
|
12
|
+
currentSurfaceId.value,
|
|
13
|
+
routePath.value
|
|
14
|
+
);
|
|
15
|
+
return String(workspaceSlug || "").trim();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
route,
|
|
20
|
+
routePath,
|
|
21
|
+
placementContext,
|
|
22
|
+
mergePlacementContext,
|
|
23
|
+
currentSurfaceId,
|
|
24
|
+
workspaceSlugFromRoute
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { useWorkspaceRouteContext };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { resolveSurfaceIdFromPlacementPathname } from "@jskit-ai/shell-web/client/placement";
|
|
3
|
+
import {
|
|
4
|
+
resolveSurfaceSwitchTargetsFromPlacementContext,
|
|
5
|
+
surfaceRequiresWorkspaceFromPlacementContext
|
|
6
|
+
} from "../lib/workspaceSurfaceContext.js";
|
|
7
|
+
|
|
8
|
+
function resolveCurrentPathname(route = null) {
|
|
9
|
+
const routePath = String(route?.path || "").trim();
|
|
10
|
+
if (routePath) {
|
|
11
|
+
return routePath;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof window === "object" && window?.location?.pathname) {
|
|
15
|
+
return String(window.location.pathname);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return "/";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function useWorkspaceSurfaceId({ route = null, placementContext = null } = {}) {
|
|
22
|
+
const currentSurfaceId = computed(() =>
|
|
23
|
+
resolveSurfaceIdFromPlacementPathname(unref(placementContext), resolveCurrentPathname(route))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const workspaceSurfaceId = computed(() => {
|
|
27
|
+
const contextValue = unref(placementContext);
|
|
28
|
+
const surfaceId = String(currentSurfaceId.value || "").trim().toLowerCase();
|
|
29
|
+
if (surfaceId && surfaceRequiresWorkspaceFromPlacementContext(contextValue, surfaceId)) {
|
|
30
|
+
return surfaceId;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const targets = resolveSurfaceSwitchTargetsFromPlacementContext(contextValue, surfaceId);
|
|
34
|
+
return String(targets.workspaceSurfaceId || "").trim().toLowerCase();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
currentSurfaceId,
|
|
39
|
+
workspaceSurfaceId
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { useWorkspaceSurfaceId };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
function buildBootstrapApiPath(workspaceSlug = "") {
|
|
2
|
+
const normalizedWorkspaceSlug = String(workspaceSlug || "").trim();
|
|
3
|
+
if (!normalizedWorkspaceSlug) {
|
|
4
|
+
return "/api/bootstrap";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const query = new URLSearchParams({
|
|
8
|
+
workspaceSlug: normalizedWorkspaceSlug
|
|
9
|
+
});
|
|
10
|
+
return `/api/bootstrap?${query.toString()}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeWorkspaceEntry(entry) {
|
|
14
|
+
if (!entry || typeof entry !== "object") {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const id = Number(entry.id);
|
|
19
|
+
const slug = String(entry.slug || "").trim();
|
|
20
|
+
if (!Number.isInteger(id) || id < 1 || !slug) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
id,
|
|
26
|
+
slug,
|
|
27
|
+
name: String(entry.name || slug).trim() || slug,
|
|
28
|
+
color: String(entry.color || "").trim(),
|
|
29
|
+
avatarUrl: String(entry.avatarUrl || "").trim(),
|
|
30
|
+
roleId: String(entry.roleId || "member").trim().toLowerCase() || "member",
|
|
31
|
+
isAccessible: entry.isAccessible !== false
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeWorkspaceList(list) {
|
|
36
|
+
const source = Array.isArray(list) ? list : [];
|
|
37
|
+
return source.map(normalizeWorkspaceEntry).filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findWorkspaceBySlug(list, workspaceSlug) {
|
|
41
|
+
const normalizedWorkspaceSlug = String(workspaceSlug || "").trim();
|
|
42
|
+
if (!normalizedWorkspaceSlug) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const source = Array.isArray(list) ? list : [];
|
|
47
|
+
for (const entry of source) {
|
|
48
|
+
const normalizedEntry = normalizeWorkspaceEntry(entry);
|
|
49
|
+
if (normalizedEntry && normalizedEntry.slug === normalizedWorkspaceSlug) {
|
|
50
|
+
return normalizedEntry;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolvePlacementUserFromBootstrapPayload(payload = {}, currentUser = null) {
|
|
58
|
+
const source = payload && typeof payload === "object" ? payload : {};
|
|
59
|
+
const session = source.session && typeof source.session === "object" ? source.session : {};
|
|
60
|
+
if (session.authenticated !== true) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const profile = source.profile && typeof source.profile === "object" ? source.profile : {};
|
|
65
|
+
const profileAvatar = profile.avatar && typeof profile.avatar === "object" ? profile.avatar : {};
|
|
66
|
+
const fallbackUser = currentUser && typeof currentUser === "object" ? currentUser : {};
|
|
67
|
+
const nextUser = {};
|
|
68
|
+
|
|
69
|
+
const userId = Number(session.userId || fallbackUser.id || 0);
|
|
70
|
+
if (Number.isInteger(userId) && userId > 0) {
|
|
71
|
+
nextUser.id = userId;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const displayName = String(profile.displayName || fallbackUser.displayName || fallbackUser.name || "").trim();
|
|
75
|
+
if (displayName) {
|
|
76
|
+
nextUser.displayName = displayName;
|
|
77
|
+
nextUser.name = displayName;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const email = String(profile.email || fallbackUser.email || "").trim().toLowerCase();
|
|
81
|
+
if (email) {
|
|
82
|
+
nextUser.email = email;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
nextUser.avatarUrl = String(profileAvatar.effectiveUrl || fallbackUser.avatarUrl || "").trim();
|
|
86
|
+
return Object.freeze(nextUser);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
buildBootstrapApiPath,
|
|
91
|
+
normalizeWorkspaceEntry,
|
|
92
|
+
normalizeWorkspaceList,
|
|
93
|
+
findWorkspaceBySlug,
|
|
94
|
+
resolvePlacementUserFromBootstrapPayload
|
|
95
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHttpClient } from "@jskit-ai/http-runtime/client";
|
|
2
|
+
import {
|
|
3
|
+
isTransientQueryError,
|
|
4
|
+
transientQueryRetryDelay
|
|
5
|
+
} from "@jskit-ai/kernel/shared/support";
|
|
6
|
+
|
|
7
|
+
const SAFE_RETRY_METHODS = Object.freeze(new Set(["GET", "HEAD"]));
|
|
8
|
+
const MAX_TRANSIENT_HTTP_RETRIES = 2;
|
|
9
|
+
|
|
10
|
+
const baseUsersWebHttpClient = createHttpClient({
|
|
11
|
+
credentials: "include",
|
|
12
|
+
csrf: {
|
|
13
|
+
sessionPath: "/api/session"
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function sleep(delayMs) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
setTimeout(resolve, delayMs);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function shouldRetryTransientHttpFailure(error, method, attemptIndex) {
|
|
24
|
+
if (!SAFE_RETRY_METHODS.has(String(method || "GET").toUpperCase())) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (!isTransientQueryError(error)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return Number(attemptIndex) < MAX_TRANSIENT_HTTP_RETRIES;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function requestWithTransientRetry(executor, method) {
|
|
34
|
+
let attemptIndex = 0;
|
|
35
|
+
|
|
36
|
+
while (true) {
|
|
37
|
+
try {
|
|
38
|
+
return await executor();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (!shouldRetryTransientHttpFailure(error, method, attemptIndex)) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
attemptIndex += 1;
|
|
44
|
+
await sleep(transientQueryRetryDelay(attemptIndex));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const usersWebHttpClient = Object.freeze({
|
|
50
|
+
...baseUsersWebHttpClient,
|
|
51
|
+
request(url, requestOptions = {}, state = null) {
|
|
52
|
+
const method = String(requestOptions?.method || "GET").toUpperCase();
|
|
53
|
+
return requestWithTransientRetry(
|
|
54
|
+
() => baseUsersWebHttpClient.request(url, requestOptions, state),
|
|
55
|
+
method
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
requestStream(url, requestOptions = {}, handlers = {}, state = null) {
|
|
59
|
+
const method = String(requestOptions?.method || "GET").toUpperCase();
|
|
60
|
+
return requestWithTransientRetry(
|
|
61
|
+
() => baseUsersWebHttpClient.requestStream(url, requestOptions, handlers, state),
|
|
62
|
+
method
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export { usersWebHttpClient };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mdiAccountCircleOutline,
|
|
3
|
+
mdiAccountCogOutline,
|
|
4
|
+
mdiAccountGroupOutline,
|
|
5
|
+
mdiArrowRightCircleOutline,
|
|
6
|
+
mdiClipboardListOutline,
|
|
7
|
+
mdiCogOutline,
|
|
8
|
+
mdiConsoleNetworkOutline,
|
|
9
|
+
mdiFolderOutline,
|
|
10
|
+
mdiHomeVariantOutline,
|
|
11
|
+
mdiLogin,
|
|
12
|
+
mdiLogout,
|
|
13
|
+
mdiRobotOutline,
|
|
14
|
+
mdiShieldCrownOutline,
|
|
15
|
+
mdiViewDashboardOutline,
|
|
16
|
+
mdiViewListOutline
|
|
17
|
+
} from "@mdi/js";
|
|
18
|
+
import { isExternalLinkTarget, splitPathQueryHash } from "@jskit-ai/kernel/shared/support/linkPath";
|
|
19
|
+
import { normalizePathname as normalizeKernelPathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
20
|
+
|
|
21
|
+
const SURFACE_SWITCH_ICON_BY_ID = Object.freeze({
|
|
22
|
+
home: mdiHomeVariantOutline,
|
|
23
|
+
app: mdiViewDashboardOutline,
|
|
24
|
+
admin: mdiShieldCrownOutline,
|
|
25
|
+
console: mdiConsoleNetworkOutline
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function normalizeText(value) {
|
|
29
|
+
return String(value || "").trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizePathname(value) {
|
|
33
|
+
const normalizedValue = normalizeText(value);
|
|
34
|
+
if (!normalizedValue) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isExternalLinkTarget(normalizedValue)) {
|
|
39
|
+
const isHttpTarget = normalizedValue.startsWith("http://") || normalizedValue.startsWith("https://");
|
|
40
|
+
if (!isHttpTarget) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let parsedPathname = "";
|
|
45
|
+
try {
|
|
46
|
+
parsedPathname = String(new URL(normalizedValue).pathname || "");
|
|
47
|
+
} catch {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const normalizedPathname = normalizeText(parsedPathname);
|
|
52
|
+
if (!normalizedPathname) {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
return normalizeKernelPathname(normalizedPathname).toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { pathname } = splitPathQueryHash(normalizedValue);
|
|
59
|
+
const normalizedPathname = normalizeText(pathname);
|
|
60
|
+
if (!normalizedPathname) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return normalizeKernelPathname(normalizedPathname).toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveSurfaceSwitchIdFromLabel(label = "") {
|
|
68
|
+
const normalizedLabel = normalizeText(label).toLowerCase();
|
|
69
|
+
if (!normalizedLabel.startsWith("go to ")) {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
return normalizeText(normalizedLabel.slice("go to ".length));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveSurfaceSwitchIcon(surfaceId = "", explicitIcon = "") {
|
|
76
|
+
const normalizedExplicitIcon = normalizeText(explicitIcon);
|
|
77
|
+
if (normalizedExplicitIcon) {
|
|
78
|
+
return normalizedExplicitIcon;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const normalizedSurfaceId = normalizeText(surfaceId).toLowerCase();
|
|
82
|
+
return SURFACE_SWITCH_ICON_BY_ID[normalizedSurfaceId] || mdiArrowRightCircleOutline;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveMenuLinkIcon({ icon = "", label = "", to = "" } = {}) {
|
|
86
|
+
const normalizedIcon = normalizeText(icon);
|
|
87
|
+
if (normalizedIcon) {
|
|
88
|
+
return normalizedIcon;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const normalizedLabel = normalizeText(label).toLowerCase();
|
|
92
|
+
const normalizedPathname = normalizePathname(to);
|
|
93
|
+
if (!normalizedLabel && !normalizedPathname) {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const surfaceSwitchSurfaceId = resolveSurfaceSwitchIdFromLabel(normalizedLabel);
|
|
98
|
+
if (surfaceSwitchSurfaceId) {
|
|
99
|
+
return resolveSurfaceSwitchIcon(surfaceSwitchSurfaceId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
normalizedLabel.includes("sign in") ||
|
|
104
|
+
normalizedPathname.includes("/auth/login")
|
|
105
|
+
) {
|
|
106
|
+
return mdiLogin;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
normalizedLabel.includes("sign out") ||
|
|
111
|
+
normalizedPathname.includes("/auth/signout")
|
|
112
|
+
) {
|
|
113
|
+
return mdiLogout;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
normalizedLabel.includes("account") ||
|
|
118
|
+
normalizedPathname.includes("/account")
|
|
119
|
+
) {
|
|
120
|
+
if (
|
|
121
|
+
normalizedLabel.includes("settings") ||
|
|
122
|
+
normalizedPathname.includes("/settings") ||
|
|
123
|
+
normalizedPathname === "/account"
|
|
124
|
+
) {
|
|
125
|
+
return mdiAccountCogOutline;
|
|
126
|
+
}
|
|
127
|
+
return mdiAccountCircleOutline;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
normalizedLabel.includes("members") ||
|
|
132
|
+
normalizedLabel.includes("team") ||
|
|
133
|
+
normalizedPathname.includes("/members")
|
|
134
|
+
) {
|
|
135
|
+
return mdiAccountGroupOutline;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (normalizedLabel.includes("assistant") || normalizedPathname.includes("/assistant")) {
|
|
139
|
+
return mdiRobotOutline;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
normalizedLabel.includes("console") ||
|
|
144
|
+
normalizedPathname.startsWith("/console")
|
|
145
|
+
) {
|
|
146
|
+
return mdiConsoleNetworkOutline;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
normalizedLabel.includes("admin") ||
|
|
151
|
+
normalizedPathname.includes("/admin")
|
|
152
|
+
) {
|
|
153
|
+
return mdiShieldCrownOutline;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (normalizedLabel.includes("settings") || normalizedPathname.includes("/settings")) {
|
|
157
|
+
return mdiCogOutline;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
normalizedLabel.includes("home") ||
|
|
162
|
+
normalizedPathname === "/"
|
|
163
|
+
) {
|
|
164
|
+
return mdiHomeVariantOutline;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
normalizedLabel.includes("workspace") ||
|
|
169
|
+
normalizedLabel.includes("dashboard") ||
|
|
170
|
+
normalizedPathname.includes("/w/")
|
|
171
|
+
) {
|
|
172
|
+
return mdiViewDashboardOutline;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (normalizedPathname) {
|
|
176
|
+
const segments = normalizedPathname.split("/").filter(Boolean);
|
|
177
|
+
if (segments.length === 1) {
|
|
178
|
+
return mdiFolderOutline;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (normalizedLabel.includes("list")) {
|
|
183
|
+
return mdiClipboardListOutline;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return mdiViewListOutline;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
resolveMenuLinkIcon,
|
|
191
|
+
resolveSurfaceSwitchIcon
|
|
192
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasPermission,
|
|
3
|
+
normalizePermissionList
|
|
4
|
+
} from "@jskit-ai/kernel/shared/support";
|
|
5
|
+
|
|
6
|
+
function toPermissionSet(values) {
|
|
7
|
+
const normalized = normalizePermissionList(values);
|
|
8
|
+
if (normalized.length < 1) {
|
|
9
|
+
return new Set();
|
|
10
|
+
}
|
|
11
|
+
return new Set(normalized);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function arePermissionListsEqual(left, right) {
|
|
15
|
+
const leftSet = toPermissionSet(left);
|
|
16
|
+
const rightSet = toPermissionSet(right);
|
|
17
|
+
if (leftSet.size !== rightSet.size) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const permission of leftSet) {
|
|
22
|
+
if (!rightSet.has(permission)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
normalizePermissionList,
|
|
32
|
+
arePermissionListsEqual,
|
|
33
|
+
hasPermission
|
|
34
|
+
};
|