@datalayer/core 1.0.25 → 1.1.43
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/README.md +11 -134
- package/lib/api/index.d.ts +0 -9
- package/lib/api/index.js +0 -9
- package/lib/cli-login.js +167 -0
- package/lib/client/auth/AuthenticationManager.d.ts +3 -3
- package/lib/client/auth/AuthenticationManager.js +13 -13
- package/lib/client/auth/strategies.d.ts +2 -2
- package/lib/client/auth/strategies.js +6 -6
- package/lib/client/base.d.ts +9 -12
- package/lib/client/base.js +17 -19
- package/lib/client/index.d.ts +10 -98
- package/lib/client/index.js +12 -21
- package/lib/client/mixins/IAMMixin.js +26 -26
- package/lib/components/auth/Login.js +2 -2
- package/lib/components/avatars/UserAvatar.d.ts +16 -0
- package/lib/components/avatars/UserAvatar.js +47 -0
- package/lib/components/avatars/index.d.ts +1 -0
- package/lib/components/avatars/index.js +1 -0
- package/lib/components/billing/BillableAccountSelect.js +4 -0
- package/lib/components/billing/index.js +4 -0
- package/lib/components/context/index.d.ts +0 -1
- package/lib/components/context/index.js +0 -1
- package/lib/components/display/DisplayHandle.d.ts +12 -0
- package/lib/components/display/DisplayHandle.js +11 -0
- package/lib/components/display/LiveRelativeTime.js +4 -0
- package/lib/components/display/index.d.ts +1 -0
- package/lib/components/display/index.js +1 -0
- package/lib/components/flashes/index.d.ts +0 -1
- package/lib/components/flashes/index.js +0 -1
- package/lib/components/index.d.ts +0 -1
- package/lib/components/index.js +0 -1
- package/lib/components/principal/Principal.d.ts +7 -0
- package/lib/components/principal/Principal.js +153 -16
- package/lib/components/principal/PrincipalAvatar.d.ts +1 -1
- package/lib/components/principal/PrincipalAvatar.js +4 -28
- package/lib/components/principal/PrincipalBadge.js +4 -4
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +4 -2
- package/lib/components/principal/PrincipalDetailsOverlay.js +27 -7
- package/lib/components/principal/PrincipalSwitcherMenu.js +79 -42
- package/lib/components/progress/index.d.ts +0 -1
- package/lib/components/progress/index.js +0 -1
- package/lib/components/sharing/ShareAccessComponent.d.ts +3 -2
- package/lib/components/sharing/ShareAccessComponent.js +194 -147
- package/lib/components/sharing/SharingEditor.js +4 -0
- package/lib/components/snippets/index.d.ts +1 -1
- package/lib/components/snippets/index.js +1 -1
- package/lib/components/students/StudentResultCircle.d.ts +21 -0
- package/lib/components/students/StudentResultCircle.js +59 -0
- package/lib/components/students/index.d.ts +1 -1
- package/lib/components/students/index.js +1 -1
- package/lib/components/time/LiveRelativeTime.js +4 -0
- package/lib/components/time/index.js +4 -0
- package/lib/components/timeline/index.js +4 -0
- package/lib/components/tokens/index.d.ts +1 -1
- package/lib/components/tokens/index.js +1 -1
- package/lib/components/toolbars/index.d.ts +1 -3
- package/lib/components/toolbars/index.js +1 -3
- package/lib/config/Configuration.d.ts +13 -13
- package/lib/config/Configuration.js +5 -5
- package/lib/hooks/cacheConverters.d.ts +21 -0
- package/lib/hooks/cacheConverters.js +110 -0
- package/lib/hooks/index.d.ts +0 -2
- package/lib/hooks/index.js +0 -2
- package/lib/hooks/useCache.d.ts +64 -35
- package/lib/hooks/useCache.js +661 -306
- package/lib/hooks/useDatalayer.js +3 -1
- package/lib/hooks/useIAM.js +2 -4
- package/lib/hooks/useLocationHandles.d.ts +3 -1
- package/lib/hooks/useLocationHandles.js +2 -1
- package/lib/hooks/usePrincipalStore.d.ts +2 -2
- package/lib/hooks/usePrincipalStore.js +97 -14
- package/lib/hooks/useSelectedPrincipal.js +1 -1
- package/lib/hooks/useUser.js +19 -2
- package/lib/index.d.ts +1 -6
- package/lib/index.js +1 -13
- package/lib/main.js +3 -3
- package/lib/mocks/index.js +1 -1
- package/lib/mocks/models/index.d.ts +0 -1
- package/lib/mocks/models/index.js +0 -1
- package/lib/models/Datasource.d.ts +2 -2
- package/lib/models/IAM.d.ts +1 -1
- package/lib/models/School.d.ts +12 -3
- package/lib/models/Secret.d.ts +2 -2
- package/lib/models/Student.d.ts +13 -3
- package/lib/models/Usage.d.ts +11 -1
- package/lib/models/UserDTO.d.ts +2 -2
- package/lib/models/UserSettings.d.ts +1 -1
- package/lib/models/UserSettings.js +1 -1
- package/lib/models/index.d.ts +0 -30
- package/lib/models/index.js +0 -30
- package/lib/otel/client/OtelClient.d.ts +2 -2
- package/lib/otel/client/OtelClient.js +2 -2
- package/lib/otel/hooks/index.js +5 -5
- package/lib/otel/types.d.ts +1 -1
- package/lib/otel/views/OtelLive.js +1 -1
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/index.js +1 -0
- package/lib/routes/publicPaths.d.ts +23 -0
- package/lib/routes/publicPaths.js +74 -0
- package/lib/state/State.d.ts +0 -12
- package/lib/state/State.js +0 -12
- package/lib/state/substates/CoreState.js +13 -13
- package/lib/state/substates/IAMState.d.ts +1 -1
- package/lib/state/substates/IAMState.js +14 -14
- package/lib/state/substates/LayoutState.d.ts +12 -1
- package/lib/state/substates/SurveysState.d.ts +1 -1
- package/lib/state/substates/SurveysState.js +10 -10
- package/lib/state/substates/index.d.ts +0 -7
- package/lib/state/substates/index.js +0 -7
- package/lib/utils/Handles.js +3 -1
- package/lib/utils/index.d.ts +0 -1
- package/lib/utils/index.js +0 -1
- package/lib/views/api-keys/APIKeys.js +12 -3
- package/lib/views/datasources/DatasourceDetail.d.ts +1 -1
- package/lib/views/datasources/DatasourceNew.d.ts +1 -1
- package/lib/views/datasources/Datasources.d.ts +1 -1
- package/lib/views/datasources/Datasources.js +12 -3
- package/lib/views/iam/SignInSimple.d.ts +37 -1
- package/lib/views/iam/SignInSimple.js +111 -13
- package/package.json +5 -3
- package/lib/App.css +0 -47
- package/lib/App.d.ts +0 -3
- package/lib/App.js +0 -21
- package/lib/api/runtimes/checkpoints.d.ts +0 -122
- package/lib/api/runtimes/checkpoints.js +0 -118
- package/lib/api/runtimes/environments.d.ts +0 -9
- package/lib/api/runtimes/environments.js +0 -29
- package/lib/api/runtimes/healthz.d.ts +0 -15
- package/lib/api/runtimes/healthz.js +0 -44
- package/lib/api/runtimes/index.d.ts +0 -12
- package/lib/api/runtimes/index.js +0 -16
- package/lib/api/runtimes/runtimes.d.ts +0 -138
- package/lib/api/runtimes/runtimes.js +0 -230
- package/lib/api/runtimes/snapshots.d.ts +0 -38
- package/lib/api/runtimes/snapshots.js +0 -82
- package/lib/api/scheduler/index.d.ts +0 -6
- package/lib/api/scheduler/index.js +0 -10
- package/lib/api/scheduler/schedules.d.ts +0 -85
- package/lib/api/scheduler/schedules.js +0 -78
- package/lib/api/spacer/documents.d.ts +0 -12
- package/lib/api/spacer/documents.js +0 -43
- package/lib/api/spacer/healthz.d.ts +0 -15
- package/lib/api/spacer/healthz.js +0 -44
- package/lib/api/spacer/index.d.ts +0 -15
- package/lib/api/spacer/index.js +0 -19
- package/lib/api/spacer/items.d.ts +0 -25
- package/lib/api/spacer/items.js +0 -55
- package/lib/api/spacer/lexicals.d.ts +0 -34
- package/lib/api/spacer/lexicals.js +0 -89
- package/lib/api/spacer/notebooks.d.ts +0 -34
- package/lib/api/spacer/notebooks.js +0 -89
- package/lib/api/spacer/spaces.d.ts +0 -84
- package/lib/api/spacer/spaces.js +0 -170
- package/lib/api/spacer/users.d.ts +0 -9
- package/lib/api/spacer/users.js +0 -29
- package/lib/client/mixins/RuntimesMixin.d.ts +0 -93
- package/lib/client/mixins/RuntimesMixin.js +0 -229
- package/lib/client/mixins/SpacerMixin.d.ts +0 -210
- package/lib/client/mixins/SpacerMixin.js +0 -615
- package/lib/client/utils/spacerUtils.d.ts +0 -18
- package/lib/client/utils/spacerUtils.js +0 -32
- package/lib/collaboration/DatalayerCollaboration.d.ts +0 -13
- package/lib/collaboration/DatalayerCollaboration.js +0 -28
- package/lib/collaboration/DatalayerCollaborationProvider.d.ts +0 -60
- package/lib/collaboration/DatalayerCollaborationProvider.js +0 -163
- package/lib/collaboration/index.d.ts +0 -2
- package/lib/collaboration/index.js +0 -6
- package/lib/components/context/SpaceSelect.d.ts +0 -2
- package/lib/components/context/SpaceSelect.js +0 -42
- package/lib/components/flashes/FlashDisclaimer.d.ts +0 -2
- package/lib/components/flashes/FlashDisclaimer.js +0 -17
- package/lib/components/icons/ArtifactIcon.d.ts +0 -10
- package/lib/components/icons/ArtifactIcon.js +0 -58
- package/lib/components/icons/index.d.ts +0 -1
- package/lib/components/icons/index.js +0 -5
- package/lib/components/nbgrader/NbGradesDetails.d.ts +0 -5
- package/lib/components/nbgrader/NbGradesDetails.js +0 -14
- package/lib/components/nbgrader/index.d.ts +0 -1
- package/lib/components/nbgrader/index.js +0 -5
- package/lib/components/progress/CreditsIndicator.d.ts +0 -33
- package/lib/components/progress/CreditsIndicator.js +0 -23
- package/lib/components/runtimes/RuntimeCellVariables.d.ts +0 -39
- package/lib/components/runtimes/RuntimeCellVariables.js +0 -172
- package/lib/components/runtimes/RuntimeCellVariablesDialog.d.ts +0 -37
- package/lib/components/runtimes/RuntimeCellVariablesDialog.js +0 -101
- package/lib/components/runtimes/RuntimeLauncherDialog.d.ts +0 -57
- package/lib/components/runtimes/RuntimeLauncherDialog.js +0 -275
- package/lib/components/runtimes/RuntimePickerBase.d.ts +0 -69
- package/lib/components/runtimes/RuntimePickerBase.js +0 -122
- package/lib/components/runtimes/RuntimePickerCell.d.ts +0 -34
- package/lib/components/runtimes/RuntimePickerCell.js +0 -104
- package/lib/components/runtimes/RuntimePickerNotebook.d.ts +0 -42
- package/lib/components/runtimes/RuntimePickerNotebook.js +0 -232
- package/lib/components/runtimes/RuntimeReservationControl.d.ts +0 -47
- package/lib/components/runtimes/RuntimeReservationControl.js +0 -42
- package/lib/components/runtimes/RuntimeSimplePicker.d.ts +0 -38
- package/lib/components/runtimes/RuntimeSimplePicker.js +0 -155
- package/lib/components/runtimes/RuntimeTransfer.d.ts +0 -16
- package/lib/components/runtimes/RuntimeTransfer.js +0 -5
- package/lib/components/runtimes/RuntimeUtils.d.ts +0 -17
- package/lib/components/runtimes/RuntimeUtils.js +0 -178
- package/lib/components/runtimes/RuntimeVariables.d.ts +0 -38
- package/lib/components/runtimes/RuntimeVariables.js +0 -75
- package/lib/components/runtimes/index.d.ts +0 -11
- package/lib/components/runtimes/index.js +0 -15
- package/lib/components/scheduler/ScheduleMenu.d.ts +0 -54
- package/lib/components/scheduler/ScheduleMenu.js +0 -154
- package/lib/components/scheduler/index.d.ts +0 -1
- package/lib/components/scheduler/index.js +0 -5
- package/lib/components/snapshots/SandboxSnapshotMenu.d.ts +0 -33
- package/lib/components/snapshots/SandboxSnapshotMenu.js +0 -169
- package/lib/components/snapshots/index.d.ts +0 -1
- package/lib/components/snapshots/index.js +0 -5
- package/lib/components/snippets/SnippetDialog.d.ts +0 -42
- package/lib/components/snippets/SnippetDialog.js +0 -42
- package/lib/components/students/StudentItemStatus.d.ts +0 -7
- package/lib/components/students/StudentItemStatus.js +0 -66
- package/lib/components/tokens/SpaceVariantToken.d.ts +0 -5
- package/lib/components/tokens/SpaceVariantToken.js +0 -23
- package/lib/components/toolbars/AssignmentEditorToolbar.d.ts +0 -4
- package/lib/components/toolbars/AssignmentEditorToolbar.js +0 -18
- package/lib/components/toolbars/DocumentEditorToolbar.d.ts +0 -2
- package/lib/components/toolbars/DocumentEditorToolbar.js +0 -12
- package/lib/components/toolbars/NotebookEditorToolbar.d.ts +0 -29
- package/lib/components/toolbars/NotebookEditorToolbar.js +0 -30
- package/lib/examples/CellExample.d.ts +0 -6
- package/lib/examples/CellExample.js +0 -20
- package/lib/examples/NotebookExample.d.ts +0 -6
- package/lib/examples/NotebookExample.js +0 -19
- package/lib/examples/lexical-theme.css +0 -436
- package/lib/examples/notebooks/IPyWidgetsExample.ipynb.json +0 -101
- package/lib/examples/notebooks/IPyWidgetsExampleWithState.ipynb.json +0 -112
- package/lib/examples/notebooks/Lite.ipynb.json +0 -128
- package/lib/examples/notebooks/Matplotlib.ipynb.json +0 -137
- package/lib/examples/notebooks/NotebookExample1.ipynb.json +0 -126
- package/lib/examples/notebooks/NotebookExample2.ipynb.json +0 -48
- package/lib/examples/notebooks/NotebookOutputs.ipynb.json +0 -49
- package/lib/examples/notebooks/NotebookToCExample.ipynb.json +0 -102
- package/lib/examples/notebooks/PyGWalker.ipynb.json +0 -55
- package/lib/hooks/useCellOutputshot.d.ts +0 -4
- package/lib/hooks/useCellOutputshot.js +0 -25
- package/lib/hooks/useProjects.d.ts +0 -628
- package/lib/hooks/useProjects.js +0 -190
- package/lib/hooks/useRuntimes.d.ts +0 -5
- package/lib/hooks/useRuntimes.js +0 -34
- package/lib/mocks/jupyter/index.js +0 -5
- package/lib/mocks/models/CourseMock.d.ts +0 -6
- package/lib/mocks/models/CourseMock.js +0 -29
- package/lib/mocks/models/SpaceMock.d.ts +0 -6
- package/lib/mocks/models/SpaceMock.js +0 -25
- package/lib/models/Assignment.d.ts +0 -7
- package/lib/models/Assignment.js +0 -5
- package/lib/models/Cell.d.ts +0 -8
- package/lib/models/Cell.js +0 -5
- package/lib/models/CodeBlock.d.ts +0 -10
- package/lib/models/CodeBlock.js +0 -5
- package/lib/models/CodeSandboxSnapshot.d.ts +0 -92
- package/lib/models/CodeSandboxSnapshot.js +0 -13
- package/lib/models/CodeSandboxSnapshotDTO.d.ts +0 -173
- package/lib/models/CodeSandboxSnapshotDTO.js +0 -139
- package/lib/models/CodefeedBlocks.d.ts +0 -7
- package/lib/models/CodefeedBlocks.js +0 -5
- package/lib/models/Content.d.ts +0 -5
- package/lib/models/Content.js +0 -5
- package/lib/models/Course.d.ts +0 -17
- package/lib/models/Course.js +0 -5
- package/lib/models/Dataset.d.ts +0 -16
- package/lib/models/Dataset.js +0 -5
- package/lib/models/Document.d.ts +0 -8
- package/lib/models/Document.js +0 -5
- package/lib/models/Environment.d.ts +0 -86
- package/lib/models/Environment.js +0 -5
- package/lib/models/EnvironmentDTO.d.ts +0 -125
- package/lib/models/EnvironmentDTO.js +0 -88
- package/lib/models/Exercise.d.ts +0 -16
- package/lib/models/Exercise.js +0 -5
- package/lib/models/Item.d.ts +0 -20
- package/lib/models/Item.js +0 -5
- package/lib/models/ItemDTO.d.ts +0 -74
- package/lib/models/ItemDTO.js +0 -186
- package/lib/models/ItemType.d.ts +0 -2
- package/lib/models/ItemType.js +0 -5
- package/lib/models/Lesson.d.ts +0 -5
- package/lib/models/Lesson.js +0 -5
- package/lib/models/LexicalDTO.d.ts +0 -155
- package/lib/models/LexicalDTO.js +0 -157
- package/lib/models/Library.d.ts +0 -10
- package/lib/models/Library.js +0 -5
- package/lib/models/Notebook.d.ts +0 -11
- package/lib/models/Notebook.js +0 -5
- package/lib/models/NotebookDTO.d.ts +0 -96
- package/lib/models/NotebookDTO.js +0 -153
- package/lib/models/Page.d.ts +0 -41
- package/lib/models/Page.js +0 -25
- package/lib/models/PageTag.d.ts +0 -19
- package/lib/models/PageTag.js +0 -94
- package/lib/models/ProjectDTO.d.ts +0 -89
- package/lib/models/ProjectDTO.js +0 -131
- package/lib/models/Runtime.d.ts +0 -123
- package/lib/models/Runtime.js +0 -18
- package/lib/models/RuntimeDTO.d.ts +0 -191
- package/lib/models/RuntimeDTO.js +0 -204
- package/lib/models/Space.d.ts +0 -35
- package/lib/models/Space.js +0 -66
- package/lib/models/SpaceDTO.d.ts +0 -334
- package/lib/models/SpaceDTO.js +0 -244
- package/lib/models/SpaceItem.d.ts +0 -10
- package/lib/models/SpaceItem.js +0 -5
- package/lib/models/SpaceMember.d.ts +0 -6
- package/lib/models/SpaceMember.js +0 -5
- package/lib/models/StudentItem.d.ts +0 -21
- package/lib/models/StudentItem.js +0 -5
- package/lib/services/DatalayerServiceManager.d.ts +0 -22
- package/lib/services/DatalayerServiceManager.js +0 -69
- package/lib/services/index.d.ts +0 -4
- package/lib/services/index.js +0 -7
- package/lib/services/reconnectToRuntime.d.ts +0 -32
- package/lib/services/reconnectToRuntime.js +0 -59
- package/lib/state/substates/CellState.d.ts +0 -12
- package/lib/state/substates/CellState.js +0 -22
- package/lib/state/substates/DocumentState.d.ts +0 -10
- package/lib/state/substates/DocumentState.js +0 -14
- package/lib/state/substates/GradeState.d.ts +0 -10
- package/lib/state/substates/GradeState.js +0 -14
- package/lib/state/substates/JupyterLabState.d.ts +0 -16
- package/lib/state/substates/JupyterLabState.js +0 -17
- package/lib/state/substates/NbformatState.d.ts +0 -13
- package/lib/state/substates/NbformatState.js +0 -16
- package/lib/state/substates/RuntimesState.d.ts +0 -79
- package/lib/state/substates/RuntimesState.js +0 -180
- package/lib/state/substates/SpaceState.d.ts +0 -11
- package/lib/state/substates/SpaceState.js +0 -16
- package/lib/stateful/index.d.ts +0 -2
- package/lib/stateful/index.js +0 -6
- package/lib/stateful/jupyter/exec/Python.d.ts +0 -5
- package/lib/stateful/jupyter/exec/Python.js +0 -100
- package/lib/stateful/jupyter/exec/Snippets.d.ts +0 -105
- package/lib/stateful/jupyter/exec/Snippets.js +0 -101
- package/lib/stateful/jupyter/exec/index.d.ts +0 -2
- package/lib/stateful/jupyter/exec/index.js +0 -6
- package/lib/stateful/jupyter/index.d.ts +0 -2
- package/lib/stateful/jupyter/index.js +0 -6
- package/lib/stateful/jupyter/kernelsHandler.d.ts +0 -8
- package/lib/stateful/jupyter/kernelsHandler.js +0 -39
- package/lib/stateful/runtimes/actions.d.ts +0 -102
- package/lib/stateful/runtimes/actions.js +0 -249
- package/lib/stateful/runtimes/apis.d.ts +0 -273
- package/lib/stateful/runtimes/apis.js +0 -5
- package/lib/stateful/runtimes/index.d.ts +0 -5
- package/lib/stateful/runtimes/index.js +0 -9
- package/lib/stateful/runtimes/settings.d.ts +0 -15
- package/lib/stateful/runtimes/settings.js +0 -31
- package/lib/stateful/runtimes/snapshots.d.ts +0 -25
- package/lib/stateful/runtimes/snapshots.js +0 -150
- package/lib/stateful/runtimes/utils.d.ts +0 -7
- package/lib/stateful/runtimes/utils.js +0 -14
- package/lib/utils/Cells.d.ts +0 -2
- package/lib/utils/Cells.js +0 -28
- /package/lib/{mocks/jupyter/index.d.ts → cli-login.d.ts} +0 -0
|
@@ -1,18 +1,139 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Principal – common, tunable display for an actor (user / team /
|
|
8
|
+
* organization). Combines a {@link PrincipalAvatar} with a
|
|
9
|
+
* {@link PrincipalDetailsOverlay} so all spots that need to show
|
|
10
|
+
* "avatar + clickable name with details overlay" can share a single
|
|
11
|
+
* component.
|
|
12
|
+
*/
|
|
13
|
+
import * as React from 'react';
|
|
2
14
|
import { Box } from '@datalayer/primer-addons';
|
|
3
15
|
import { useCache } from '../../hooks/index.js';
|
|
16
|
+
import { useIAMStore } from '../../state/index.js';
|
|
4
17
|
import { PrincipalAvatar } from './PrincipalAvatar.js';
|
|
5
18
|
import { PrincipalDetailsOverlay } from './PrincipalDetailsOverlay.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Default maximum length applied to the placeholder shown before a principal's
|
|
21
|
+
* real display name has been resolved. The placeholder (derived from the given
|
|
22
|
+
* handle or uid) is truncated to this many characters.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_PLACEHOLDER_MAX_LENGTH = 10;
|
|
25
|
+
/**
|
|
26
|
+
* Generic, non-informative display names that callers pass while the real
|
|
27
|
+
* entity is still loading. When the provided display name is one of these we
|
|
28
|
+
* prefer a handle/uid-derived placeholder instead.
|
|
29
|
+
*/
|
|
30
|
+
const GENERIC_PLACEHOLDER_DISPLAY_NAMES = new Set([
|
|
31
|
+
'unknown',
|
|
32
|
+
'principal',
|
|
33
|
+
'personal',
|
|
34
|
+
'team',
|
|
35
|
+
'organization',
|
|
36
|
+
'billable account',
|
|
37
|
+
]);
|
|
38
|
+
/** Truncate a placeholder string to `maxLength`, appending an ellipsis. */
|
|
39
|
+
function truncatePlaceholder(value, maxLength) {
|
|
40
|
+
const trimmed = value.trim();
|
|
41
|
+
if (maxLength <= 0 || trimmed.length <= maxLength) {
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
44
|
+
return `${trimmed.slice(0, maxLength)}\u2026`;
|
|
45
|
+
}
|
|
46
|
+
export const Principal = ({ principal, isAdmin = false, avatarSize = 20, gap = 2, square = false, placeholderMaxLength = DEFAULT_PLACEHOLDER_MAX_LENGTH, sx, }) => {
|
|
47
|
+
const { useUser, useOrganizationByHandle, useTeam, useUserPublicProfileByHandle, useOrganizationPublicProfileByHandle, } = useCache();
|
|
48
|
+
// When no user is authenticated (anonymous visitor, e.g. public pages), the
|
|
49
|
+
// authenticated `useUser` / `useOrganization` endpoints return 401, which
|
|
50
|
+
// triggers a global logout + redirect to sign-in. For anonymous visitors we
|
|
51
|
+
// resolve the principal through the public-by-handle endpoints instead, which
|
|
52
|
+
// are anonymous-accessible and therefore never redirect.
|
|
53
|
+
const { user: authenticatedUser } = useIAMStore();
|
|
54
|
+
const isAnonymous = !authenticatedUser;
|
|
55
|
+
const normalizedUid = String(principal.uid || '').trim();
|
|
56
|
+
const normalizedProvidedHandle = String(principal.handle || '').trim();
|
|
57
|
+
const normalizedProvidedAccountHandle = String(principal.accountHandle || '').trim();
|
|
58
|
+
const providedHandleIsUidPlaceholder = !!normalizedProvidedHandle &&
|
|
59
|
+
!!normalizedUid &&
|
|
60
|
+
normalizedProvidedHandle === normalizedUid;
|
|
61
|
+
const providedAccountHandleIsUidPlaceholder = !!normalizedProvidedAccountHandle &&
|
|
62
|
+
!!normalizedUid &&
|
|
63
|
+
normalizedProvidedAccountHandle === normalizedUid;
|
|
64
|
+
const principalHandle = String((!providedHandleIsUidPlaceholder ? principal.handle : '') ||
|
|
65
|
+
(!providedAccountHandleIsUidPlaceholder ? principal.accountHandle : '') ||
|
|
66
|
+
'').trim();
|
|
67
|
+
const teamPathParts = principal.kind === 'team' ? principalHandle.split('/') : [];
|
|
68
|
+
const teamOrganizationHandleFromPath = String(teamPathParts[0] || '').trim();
|
|
69
|
+
const teamOrganizationHandleFromAccount = principal.kind === 'team' && !providedAccountHandleIsUidPlaceholder
|
|
70
|
+
? String(principal.accountHandle || '').trim()
|
|
71
|
+
: '';
|
|
72
|
+
const teamOrganizationHandle = teamOrganizationHandleFromPath || teamOrganizationHandleFromAccount;
|
|
73
|
+
const hydratedUserQuery = useUser(!isAnonymous && principal.kind === 'personal'
|
|
74
|
+
? String(principal.uid || '')
|
|
75
|
+
: '');
|
|
76
|
+
// Authenticated organizations are fetched by handle via the accounts
|
|
77
|
+
// endpoint (`/api/iam/v1/accounts/{handle}`). The by-uid organization
|
|
78
|
+
// endpoint (`/api/iam/v1/organizations/{uid}`) is not served and 404s, so we
|
|
79
|
+
// never look an organization up by uid here.
|
|
80
|
+
const organizationLookupHandle = String(principalHandle || '')
|
|
81
|
+
.replace(/^@+/, '')
|
|
82
|
+
.trim();
|
|
83
|
+
const hydratedOrgQuery = useOrganizationByHandle(!isAnonymous && principal.kind === 'organization'
|
|
84
|
+
? organizationLookupHandle
|
|
85
|
+
: '');
|
|
86
|
+
const teamOrganizationQuery = useOrganizationByHandle(principal.kind === 'team' ? teamOrganizationHandle : '');
|
|
87
|
+
const hydratedTeamQuery = useTeam(principal.kind === 'team' ? String(principal.uid || '') : '', principal.kind === 'team'
|
|
88
|
+
? String(teamOrganizationQuery.data?.id || '')
|
|
89
|
+
: '');
|
|
90
|
+
const hydratedPublicUserQuery = useUserPublicProfileByHandle(isAnonymous && principal.kind === 'personal' ? principalHandle : '');
|
|
91
|
+
const hydratedPublicOrgQuery = useOrganizationPublicProfileByHandle(isAnonymous && principal.kind === 'organization' ? principalHandle : '');
|
|
92
|
+
// Normalise the public (snake_case) profile payloads into the same camelCase
|
|
93
|
+
// shape the authenticated entities expose, so downstream logic is uniform.
|
|
94
|
+
const normalizedPublicUser = React.useMemo(() => {
|
|
95
|
+
const profile = hydratedPublicUserQuery.data;
|
|
96
|
+
if (!profile) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const displayName = String(profile.display_name ||
|
|
100
|
+
[profile.first_name, profile.last_name].filter(Boolean).join(' ') ||
|
|
101
|
+
'').trim();
|
|
102
|
+
return {
|
|
103
|
+
displayName,
|
|
104
|
+
firstName: profile.first_name,
|
|
105
|
+
lastName: profile.last_name,
|
|
106
|
+
handle: profile.handle,
|
|
107
|
+
avatarUrl: profile.avatar_url,
|
|
108
|
+
origin: profile.origin,
|
|
109
|
+
email: profile.email,
|
|
110
|
+
};
|
|
111
|
+
}, [hydratedPublicUserQuery.data]);
|
|
112
|
+
const normalizedPublicOrg = React.useMemo(() => {
|
|
113
|
+
const profile = hydratedPublicOrgQuery.data;
|
|
114
|
+
if (!profile) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
displayName: String(profile.display_name || profile.name || '').trim(),
|
|
119
|
+
name: profile.name,
|
|
120
|
+
description: profile.description || profile.description_t,
|
|
121
|
+
handle: profile.handle,
|
|
122
|
+
avatarUrl: profile.avatar_url,
|
|
123
|
+
};
|
|
124
|
+
}, [hydratedPublicOrgQuery.data]);
|
|
125
|
+
const hydratedEntity = principal.kind === 'personal'
|
|
126
|
+
? isAnonymous
|
|
127
|
+
? normalizedPublicUser
|
|
128
|
+
: hydratedUserQuery.data
|
|
12
129
|
: principal.kind === 'organization'
|
|
13
|
-
?
|
|
14
|
-
|
|
15
|
-
|
|
130
|
+
? isAnonymous
|
|
131
|
+
? normalizedPublicOrg
|
|
132
|
+
: hydratedOrgQuery.data
|
|
133
|
+
: principal.kind === 'team'
|
|
134
|
+
? hydratedTeamQuery.data
|
|
135
|
+
: undefined;
|
|
136
|
+
const hydratedDisplayName = principal.kind === 'personal'
|
|
16
137
|
? String(hydratedEntity?.displayName ||
|
|
17
138
|
[
|
|
18
139
|
hydratedEntity?.firstName,
|
|
@@ -27,16 +148,32 @@ export const Principal = ({ principal, isAdmin = false, avatarSize = 20, gap = 2
|
|
|
27
148
|
const resolvedPrincipal = {
|
|
28
149
|
...principal,
|
|
29
150
|
displayName: hydratedDisplayName ||
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
151
|
+
(() => {
|
|
152
|
+
const providedDisplayName = String(principal.displayName || '').trim();
|
|
153
|
+
const providedIsGenericPlaceholder = !providedDisplayName ||
|
|
154
|
+
GENERIC_PLACEHOLDER_DISPLAY_NAMES.has(providedDisplayName.toLowerCase()) ||
|
|
155
|
+
(!!normalizedUid && providedDisplayName === normalizedUid);
|
|
156
|
+
// While the real display name is unresolved, prefer the given handle
|
|
157
|
+
// (or uid) as a placeholder, truncated to a maximum length. Only fall
|
|
158
|
+
// back to the caller-provided display name when it is meaningful.
|
|
159
|
+
const placeholder = truncatePlaceholder(principalHandle || normalizedProvidedHandle || normalizedUid || '', placeholderMaxLength);
|
|
160
|
+
if (providedIsGenericPlaceholder) {
|
|
161
|
+
return placeholder || providedDisplayName || 'Unknown';
|
|
162
|
+
}
|
|
163
|
+
return providedDisplayName || placeholder || 'Unknown';
|
|
164
|
+
})(),
|
|
165
|
+
handle: (!providedHandleIsUidPlaceholder ? principal.handle : undefined) ||
|
|
35
166
|
String(hydratedEntity?.handle || '').trim() ||
|
|
36
167
|
undefined,
|
|
37
|
-
accountHandle: principal.accountHandle ||
|
|
168
|
+
accountHandle: (!providedAccountHandleIsUidPlaceholder ? principal.accountHandle : undefined) ||
|
|
38
169
|
String(hydratedEntity?.handle || '').trim() ||
|
|
39
170
|
undefined,
|
|
171
|
+
name: principal.name ||
|
|
172
|
+
String(hydratedEntity?.name || '').trim() ||
|
|
173
|
+
undefined,
|
|
174
|
+
description: principal.description ||
|
|
175
|
+
String(hydratedEntity?.description || '').trim() ||
|
|
176
|
+
undefined,
|
|
40
177
|
avatarUrl: principal.avatarUrl || hydratedEntity?.avatarUrl || undefined,
|
|
41
178
|
firstName: principal.firstName || hydratedEntity?.firstName || undefined,
|
|
42
179
|
lastName: principal.lastName || hydratedEntity?.lastName || undefined,
|
|
@@ -49,6 +186,6 @@ export const Principal = ({ principal, isAdmin = false, avatarSize = 20, gap = 2
|
|
|
49
186
|
gap,
|
|
50
187
|
minWidth: 0,
|
|
51
188
|
...sx,
|
|
52
|
-
}, children: [_jsx(PrincipalAvatar, { kind: resolvedPrincipal.kind, avatarUrl: resolvedPrincipal.avatarUrl, alt: resolvedPrincipal.displayName, size: avatarSize, square: square }), _jsx(PrincipalDetailsOverlay, { kind: resolvedPrincipal.kind, uid: resolvedPrincipal.uid, displayName: resolvedPrincipal.displayName, handle: resolvedPrincipal.handle, accountHandle: resolvedPrincipal.accountHandle, firstName: resolvedPrincipal.firstName, lastName: resolvedPrincipal.lastName, email: resolvedPrincipal.email, origin: resolvedPrincipal.origin, avatarUrl: resolvedPrincipal.avatarUrl, isAdmin: isAdmin })] }));
|
|
189
|
+
}, children: [_jsx(PrincipalAvatar, { kind: resolvedPrincipal.kind, avatarUrl: resolvedPrincipal.avatarUrl, alt: resolvedPrincipal.displayName, size: avatarSize, square: square }), _jsx(PrincipalDetailsOverlay, { kind: resolvedPrincipal.kind, uid: resolvedPrincipal.uid, displayName: resolvedPrincipal.displayName, name: resolvedPrincipal.name, description: resolvedPrincipal.description, handle: resolvedPrincipal.handle, accountHandle: resolvedPrincipal.accountHandle, firstName: resolvedPrincipal.firstName, lastName: resolvedPrincipal.lastName, email: resolvedPrincipal.email, origin: resolvedPrincipal.origin, avatarUrl: resolvedPrincipal.avatarUrl, isAdmin: isAdmin })] }));
|
|
53
190
|
};
|
|
54
191
|
export default Principal;
|
|
@@ -3,42 +3,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { Box
|
|
6
|
+
import { Box } from '@datalayer/primer-addons';
|
|
7
7
|
import { OrganizationIcon, PeopleIcon } from '@primer/octicons-react';
|
|
8
|
-
import {
|
|
9
|
-
import { DLAvatar } from '../avatars/index.js';
|
|
10
|
-
function hasRealAvatar(url) {
|
|
11
|
-
if (!url) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
if (url.startsWith('https://www.gravatar.com/avatar')) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
8
|
+
import { UserAvatar } from '../avatars/index.js';
|
|
19
9
|
function getFallbackIconSize(size) {
|
|
20
10
|
return Math.max(12, Math.round(size * 0.62));
|
|
21
11
|
}
|
|
22
12
|
export function PrincipalAvatar({ kind, avatarUrl, alt, size = 20, square = false, }) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return (_jsx(DLAvatar, { src: avatarUrl, alt: alt || 'User', size: size, square: square }));
|
|
13
|
+
if (kind === 'personal') {
|
|
14
|
+
return (_jsx(UserAvatar, { avatarUrl: avatarUrl, size: size, square: square, iconSize: getFallbackIconSize(size) }));
|
|
26
15
|
}
|
|
27
16
|
const iconSize = getFallbackIconSize(size);
|
|
28
17
|
const borderRadius = square ? 2 : '50%';
|
|
29
|
-
if (kind === 'user') {
|
|
30
|
-
return (_jsx(Box, { sx: {
|
|
31
|
-
width: size,
|
|
32
|
-
height: size,
|
|
33
|
-
display: 'inline-flex',
|
|
34
|
-
alignItems: 'center',
|
|
35
|
-
justifyContent: 'center',
|
|
36
|
-
bg: 'accent.subtle',
|
|
37
|
-
borderRadius,
|
|
38
|
-
overflow: 'hidden',
|
|
39
|
-
'--datalayer-icon-fg': palette.primary,
|
|
40
|
-
}, "aria-label": alt || 'User', children: _jsx(AlienIcon, { size: iconSize }) }));
|
|
41
|
-
}
|
|
42
18
|
const Icon = kind === 'team' ? PeopleIcon : OrganizationIcon;
|
|
43
19
|
return (_jsx(Box, { sx: {
|
|
44
20
|
width: size,
|
|
@@ -71,7 +71,7 @@ export const PrincipalBadge = ({ principal: providedPrincipal, showPrincipalLabe
|
|
|
71
71
|
kind: 'team',
|
|
72
72
|
uid: selectedPrincipalUid,
|
|
73
73
|
handle: `${orgHandle}/${teamHandle}`,
|
|
74
|
-
accountHandle:
|
|
74
|
+
accountHandle: orgHandle,
|
|
75
75
|
displayName: `@${formatFriendlyHandle(orgHandle)}/${formatFriendlyHandle(teamHandle)}`,
|
|
76
76
|
origin: 'Datalayer',
|
|
77
77
|
};
|
|
@@ -85,7 +85,7 @@ export const PrincipalBadge = ({ principal: providedPrincipal, showPrincipalLabe
|
|
|
85
85
|
? `@${formatFriendlyHandle(resolvedHandle)}`
|
|
86
86
|
: '@me';
|
|
87
87
|
return {
|
|
88
|
-
kind: '
|
|
88
|
+
kind: 'personal',
|
|
89
89
|
uid: user?.id || selectedPrincipalUid,
|
|
90
90
|
displayName: fullName || fallbackHandle,
|
|
91
91
|
handle: resolvedHandle,
|
|
@@ -110,7 +110,7 @@ export const PrincipalBadge = ({ principal: providedPrincipal, showPrincipalLabe
|
|
|
110
110
|
user?.email,
|
|
111
111
|
user?.avatarUrl,
|
|
112
112
|
]);
|
|
113
|
-
const userLookupUid = basePrincipal.kind === '
|
|
113
|
+
const userLookupUid = basePrincipal.kind === 'personal' ? String(basePrincipal.uid || '') : '';
|
|
114
114
|
const organizationLookupUid = basePrincipal.kind === 'organization'
|
|
115
115
|
? String(basePrincipal.uid || '')
|
|
116
116
|
: '';
|
|
@@ -168,7 +168,7 @@ export const PrincipalBadge = ({ principal: providedPrincipal, showPrincipalLabe
|
|
|
168
168
|
fallbackHandle;
|
|
169
169
|
const origin = normalizeUserOrigin(resolvedUser?.origin || basePrincipal.origin);
|
|
170
170
|
return {
|
|
171
|
-
kind: '
|
|
171
|
+
kind: 'personal',
|
|
172
172
|
uid: resolvedUser?.uid || basePrincipal.uid,
|
|
173
173
|
displayName: resolvedDisplayName,
|
|
174
174
|
handle: resolvedHandle,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export type PrincipalKind = '
|
|
1
|
+
export type PrincipalKind = 'personal' | 'team' | 'organization';
|
|
2
2
|
export type PrincipalDetailsOverlayProps = {
|
|
3
3
|
kind: PrincipalKind;
|
|
4
4
|
uid?: string;
|
|
5
5
|
displayName: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
description?: string;
|
|
6
8
|
handle?: string;
|
|
7
9
|
accountHandle?: string;
|
|
8
10
|
firstName?: string;
|
|
@@ -19,5 +21,5 @@ export declare function buildPrincipalProfilePath({ kind, uid, handle, accountHa
|
|
|
19
21
|
accountHandle?: string;
|
|
20
22
|
isAdmin?: boolean;
|
|
21
23
|
}): string | null;
|
|
22
|
-
export declare function PrincipalDetailsOverlay({ kind, uid, displayName, handle, accountHandle, firstName, lastName, email, origin, avatarUrl, isAdmin, }: PrincipalDetailsOverlayProps): JSX.Element;
|
|
24
|
+
export declare function PrincipalDetailsOverlay({ kind, uid, displayName, name, description, handle, accountHandle, firstName, lastName, email, origin, avatarUrl, isAdmin, }: PrincipalDetailsOverlayProps): JSX.Element;
|
|
23
25
|
export default PrincipalDetailsOverlay;
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { ActionMenu, Box, Button, Text } from '@primer/react';
|
|
6
|
+
import { ActionMenu, Box, Button, Label, Text } from '@primer/react';
|
|
7
7
|
import { useNavigate } from '../../hooks/index.js';
|
|
8
8
|
import { PrincipalAvatar } from './PrincipalAvatar.js';
|
|
9
9
|
function normalize(value) {
|
|
@@ -16,7 +16,7 @@ export function buildPrincipalProfilePath({ kind, uid, handle, accountHandle, is
|
|
|
16
16
|
const safeHandle = normalizedHandle && normalizedHandle !== normalizedUid
|
|
17
17
|
? normalizedHandle
|
|
18
18
|
: '';
|
|
19
|
-
if (kind === '
|
|
19
|
+
if (kind === 'personal') {
|
|
20
20
|
if (isAdmin && normalizedUid) {
|
|
21
21
|
return `/admin/management/iam/users/${normalizedUid}`;
|
|
22
22
|
}
|
|
@@ -45,7 +45,7 @@ export function buildPrincipalProfilePath({ kind, uid, handle, accountHandle, is
|
|
|
45
45
|
}
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
|
-
export function PrincipalDetailsOverlay({ kind, uid, displayName, handle, accountHandle, firstName, lastName, email, origin, avatarUrl, isAdmin = false, }) {
|
|
48
|
+
export function PrincipalDetailsOverlay({ kind, uid, displayName, name, description, handle, accountHandle, firstName, lastName, email, origin, avatarUrl, isAdmin = false, }) {
|
|
49
49
|
const navigate = useNavigate();
|
|
50
50
|
const normalizedDisplayName = normalize(displayName) ||
|
|
51
51
|
normalize(handle) ||
|
|
@@ -53,10 +53,16 @@ export function PrincipalDetailsOverlay({ kind, uid, displayName, handle, accoun
|
|
|
53
53
|
'Principal';
|
|
54
54
|
const normalizedHandle = normalize(handle);
|
|
55
55
|
const normalizedUid = normalize(uid);
|
|
56
|
+
const normalizedName = normalize(name);
|
|
57
|
+
const normalizedDescription = normalize(description);
|
|
58
|
+
const normalizedOrigin = normalize(origin) || 'Datalayer';
|
|
59
|
+
const resolvedHandle = normalizedHandle && normalizedHandle !== normalizedUid
|
|
60
|
+
? normalizedHandle
|
|
61
|
+
: '';
|
|
56
62
|
const targetPath = buildPrincipalProfilePath({
|
|
57
63
|
kind,
|
|
58
64
|
uid: normalizedUid,
|
|
59
|
-
handle:
|
|
65
|
+
handle: resolvedHandle,
|
|
60
66
|
accountHandle,
|
|
61
67
|
isAdmin,
|
|
62
68
|
});
|
|
@@ -72,15 +78,29 @@ export function PrincipalDetailsOverlay({ kind, uid, displayName, handle, accoun
|
|
|
72
78
|
':hover': {
|
|
73
79
|
textDecoration: 'underline',
|
|
74
80
|
},
|
|
75
|
-
}, children: normalizedDisplayName }) }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsxs(Box, { sx: { display: 'grid', gap: 3, p: 4, minWidth: 420 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(PrincipalAvatar, { kind: kind, avatarUrl: avatarUrl, alt: normalizedDisplayName, size: 40 }), _jsxs(Box, { sx: { display: 'grid', gap: 0.5 }, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: normalizedDisplayName }),
|
|
81
|
+
}, children: normalizedDisplayName }) }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsxs(Box, { sx: { display: 'grid', gap: 3, p: 4, minWidth: 420 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(PrincipalAvatar, { kind: kind, avatarUrl: avatarUrl, alt: normalizedDisplayName, size: 40 }), _jsxs(Box, { sx: { display: 'grid', gap: 0.5 }, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: normalizedDisplayName }), resolvedHandle ? (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["@", resolvedHandle] })) : null] })] }), _jsxs(Box, { sx: {
|
|
76
82
|
display: 'grid',
|
|
77
83
|
gridTemplateColumns: '110px 1fr',
|
|
78
84
|
gap: 1,
|
|
79
85
|
alignItems: 'baseline',
|
|
80
|
-
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "
|
|
86
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Kind" }), _jsx(Text, { sx: { fontSize: 1 }, children: kind }), resolvedHandle ? (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Handle" }), _jsxs(Text, { sx: { fontSize: 1 }, children: ["@", resolvedHandle] })] })) : null, normalizedUid ? (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "UID" }), _jsx(Text, { sx: { fontSize: 1 }, children: normalizedUid })] })) : null, kind === 'personal' ? (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "First name" }), _jsx(Text, { sx: { fontSize: 1 }, children: firstName || 'N/A' }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Last name" }), _jsx(Text, { sx: { fontSize: 1 }, children: lastName || 'N/A' }), email ? (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Email" }), _jsx(Text, { sx: { fontSize: 1 }, children: email })] })) : null, _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Origin" }), _jsx(Label, { size: "small", variant: "secondary", title: normalizedOrigin, sx: {
|
|
87
|
+
justifySelf: 'start',
|
|
88
|
+
width: 'fit-content',
|
|
89
|
+
maxWidth: '100%',
|
|
90
|
+
overflow: 'hidden',
|
|
91
|
+
textOverflow: 'ellipsis',
|
|
92
|
+
whiteSpace: 'nowrap',
|
|
93
|
+
}, children: normalizedOrigin })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Name" }), _jsx(Text, { sx: { fontSize: 1 }, children: normalizedName || normalizedDisplayName }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Description" }), _jsx(Text, { sx: { fontSize: 1 }, children: normalizedDescription || 'N/A' }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Origin" }), _jsx(Label, { size: "small", variant: "secondary", title: normalizedOrigin, sx: {
|
|
94
|
+
justifySelf: 'start',
|
|
95
|
+
width: 'fit-content',
|
|
96
|
+
maxWidth: '100%',
|
|
97
|
+
overflow: 'hidden',
|
|
98
|
+
textOverflow: 'ellipsis',
|
|
99
|
+
whiteSpace: 'nowrap',
|
|
100
|
+
}, children: normalizedOrigin })] }))] }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: _jsx(Button, { size: "small", onClick: () => {
|
|
81
101
|
if (targetPath) {
|
|
82
102
|
navigate(targetPath);
|
|
83
103
|
}
|
|
84
|
-
}, disabled: !targetPath, children: "View
|
|
104
|
+
}, disabled: !targetPath, children: "View Profile" }) })] }) })] }));
|
|
85
105
|
}
|
|
86
106
|
export default PrincipalDetailsOverlay;
|
|
@@ -4,8 +4,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
6
|
import { useEffect, useMemo, useState } from 'react';
|
|
7
|
-
import { ActionList, ActionMenu, Box, Text } from '@primer/react';
|
|
8
|
-
import { OrganizationIcon, PeopleIcon, PersonIcon, } from '@primer/octicons-react';
|
|
7
|
+
import { ActionList, ActionMenu, Box, Button, Label, Text } from '@primer/react';
|
|
8
|
+
import { OrganizationIcon, PeopleIcon, PersonIcon, TriangleDownIcon, } from '@primer/octicons-react';
|
|
9
9
|
import { useCache, useAuthorization } from '../../hooks/index.js';
|
|
10
10
|
import { useCoreStore } from '../../state/index.js';
|
|
11
11
|
import { useIAMStore } from '../../state/substates/index.js';
|
|
@@ -13,7 +13,7 @@ import { memberships as fetchMemberships } from '../../api/iam/profile.js';
|
|
|
13
13
|
import { usePrincipalStore } from '../../hooks/usePrincipalStore.js';
|
|
14
14
|
import { useBillableAccountStore } from '../../hooks/useBillableAccountStore.js';
|
|
15
15
|
import { useSelectedPrincipal } from '../../hooks/useSelectedPrincipal.js';
|
|
16
|
-
import {
|
|
16
|
+
import { DisplayHandle, displayHandleText } from '../display/DisplayHandle.js';
|
|
17
17
|
function truncatePrincipalLabel(label, maxChars) {
|
|
18
18
|
const trimmed = (label || '').trim();
|
|
19
19
|
if (!trimmed) {
|
|
@@ -32,7 +32,7 @@ function truncatePrincipalLabel(label, maxChars) {
|
|
|
32
32
|
* - selecting a team principal → billable account = the team's parent org
|
|
33
33
|
*/
|
|
34
34
|
export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, showClosedBorder = true, }) {
|
|
35
|
-
const { user, token,
|
|
35
|
+
const { user, token, iamUrl } = useIAMStore();
|
|
36
36
|
const { configuration } = useCoreStore();
|
|
37
37
|
const { checkIsPlatformAdmin } = useAuthorization();
|
|
38
38
|
const { useUserOrganizations } = useCache();
|
|
@@ -42,7 +42,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
42
42
|
const isPlatformAdmin = user ? checkIsPlatformAdmin(user) : false;
|
|
43
43
|
const [teams, setTeams] = useState([]);
|
|
44
44
|
const [teamsLoading, setTeamsLoading] = useState(false);
|
|
45
|
-
const
|
|
45
|
+
const selectPersonalPrincipal = usePrincipalStore(state => state.selectPersonalPrincipal);
|
|
46
46
|
const selectOrganizationPrincipal = usePrincipalStore(state => state.selectOrganizationPrincipal);
|
|
47
47
|
const selectTeamPrincipal = usePrincipalStore(state => state.selectTeamPrincipal);
|
|
48
48
|
const setBillableAccount = useBillableAccountStore(state => state.setBillableAccount);
|
|
@@ -50,7 +50,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
50
50
|
const personalUid = user?.uid || user?.id || '';
|
|
51
51
|
const personalHandle = user?.handle || '';
|
|
52
52
|
const selectUser = (uid, handle) => {
|
|
53
|
-
|
|
53
|
+
selectPersonalPrincipal(uid, handle);
|
|
54
54
|
setBillableAccount({ kind: 'user', uid, handle });
|
|
55
55
|
};
|
|
56
56
|
const selectOrganization = (uid, handle) => {
|
|
@@ -85,7 +85,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
85
85
|
}
|
|
86
86
|
setTeamsLoading(true);
|
|
87
87
|
try {
|
|
88
|
-
const baseUrl =
|
|
88
|
+
const baseUrl = iamUrl || configuration.iamUrl;
|
|
89
89
|
const response = await fetchMemberships(token, baseUrl);
|
|
90
90
|
const rawMemberships = Array.isArray(response?.memberships)
|
|
91
91
|
? response.memberships
|
|
@@ -120,7 +120,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
120
120
|
return () => {
|
|
121
121
|
cancelled = true;
|
|
122
122
|
};
|
|
123
|
-
}, [token,
|
|
123
|
+
}, [token, iamUrl, configuration.iamUrl]);
|
|
124
124
|
useEffect(() => {
|
|
125
125
|
if (!personalUid || !personalHandle) {
|
|
126
126
|
return;
|
|
@@ -129,21 +129,13 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
129
129
|
selectUser(personalUid, personalHandle);
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
// Keep explicit organization/team selections stable across route transitions,
|
|
133
|
+
// even while memberships/organizations are still loading or temporarily
|
|
134
|
+
// unavailable for a given view.
|
|
135
|
+
if (selectedPrincipalKind === 'organization' || selectedPrincipalKind === 'team') {
|
|
133
136
|
return;
|
|
134
137
|
}
|
|
135
|
-
if (selectedPrincipalKind === '
|
|
136
|
-
selectUser(personalUid, personalHandle);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (selectedPrincipalKind === 'team' && teamsLoading) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (selectedPrincipalKind === 'team' && !selectedTeam) {
|
|
143
|
-
selectUser(personalUid, personalHandle);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (selectedPrincipalKind === 'user' &&
|
|
138
|
+
if (selectedPrincipalKind === 'personal' &&
|
|
147
139
|
selectedPrincipalUid !== personalUid) {
|
|
148
140
|
selectUser(personalUid, personalHandle);
|
|
149
141
|
}
|
|
@@ -195,10 +187,10 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
195
187
|
''
|
|
196
188
|
: '';
|
|
197
189
|
const selectedPrincipalLabel = selectedPrincipalKind === 'team'
|
|
198
|
-
?
|
|
199
|
-
:
|
|
190
|
+
? `${displayHandleText(effectiveOrganizationHandle || personalHandle || 'organization')}/${displayHandleText(effectiveHandle, { withAt: false })}`
|
|
191
|
+
: displayHandleText(effectiveHandle);
|
|
200
192
|
const selectedPrincipalLabelClosed = truncatePrincipalLabel(selectedPrincipalLabel, maxLabelChars);
|
|
201
|
-
const isCurrentUserPrincipal = selectedPrincipalKind === '
|
|
193
|
+
const isCurrentUserPrincipal = selectedPrincipalKind === 'personal';
|
|
202
194
|
const selectedItemSx = {
|
|
203
195
|
bg: 'accent.subtle',
|
|
204
196
|
borderColor: 'accent.muted',
|
|
@@ -206,30 +198,37 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
206
198
|
fontWeight: 'semibold',
|
|
207
199
|
};
|
|
208
200
|
const adminBadgeSx = {
|
|
209
|
-
ml: 'auto',
|
|
210
|
-
px: 1,
|
|
211
|
-
py: '2px',
|
|
212
|
-
borderRadius: 999,
|
|
213
201
|
bg: 'attention.subtle',
|
|
214
202
|
color: 'attention.fg',
|
|
215
|
-
fontSize: 0,
|
|
216
|
-
fontWeight: 'semibold',
|
|
217
|
-
lineHeight: 1.2,
|
|
218
203
|
textTransform: 'lowercase',
|
|
204
|
+
lineHeight: 1.2,
|
|
219
205
|
};
|
|
220
|
-
return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsxs(Box, { as: "button", type: "button", "aria-label": "Switch principal", sx: {
|
|
206
|
+
return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: showClosedBorder ? (_jsxs(Box, { as: "button", type: "button", "aria-label": "Switch principal", sx: {
|
|
221
207
|
width: fullWidth ? '100%' : 'auto',
|
|
222
|
-
|
|
208
|
+
boxSizing: 'border-box',
|
|
209
|
+
px: 2,
|
|
210
|
+
py: 2,
|
|
211
|
+
fontSize: 0,
|
|
223
212
|
display: 'flex',
|
|
224
213
|
alignItems: 'center',
|
|
225
214
|
justifyContent: 'space-between',
|
|
226
215
|
gap: 2,
|
|
227
|
-
bg:
|
|
228
|
-
border:
|
|
229
|
-
borderColor:
|
|
216
|
+
bg: 'canvas.subtle',
|
|
217
|
+
border: '1px solid',
|
|
218
|
+
borderColor: 'border.default',
|
|
230
219
|
borderRadius: 2,
|
|
231
220
|
cursor: 'pointer',
|
|
232
221
|
textAlign: 'left',
|
|
222
|
+
transition: 'background-color 120ms ease',
|
|
223
|
+
':hover': {
|
|
224
|
+
bg: 'canvas.subtle',
|
|
225
|
+
},
|
|
226
|
+
':focus-visible': {
|
|
227
|
+
bg: 'canvas.subtle',
|
|
228
|
+
outline: '2px solid',
|
|
229
|
+
outlineColor: 'accent.emphasis',
|
|
230
|
+
outlineOffset: '2px',
|
|
231
|
+
},
|
|
233
232
|
}, children: [_jsx(Box, { sx: {
|
|
234
233
|
display: 'inline-flex',
|
|
235
234
|
alignItems: 'center',
|
|
@@ -239,24 +238,50 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
239
238
|
display: 'block',
|
|
240
239
|
color: 'accent.fg',
|
|
241
240
|
fontWeight: 'semibold',
|
|
242
|
-
fontSize:
|
|
241
|
+
fontSize: 'inherit',
|
|
243
242
|
whiteSpace: 'nowrap',
|
|
244
243
|
overflow: 'hidden',
|
|
245
244
|
textOverflow: 'ellipsis',
|
|
246
245
|
maxWidth: '100%',
|
|
247
246
|
}, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Box, { sx: {
|
|
248
|
-
ml: 'auto',
|
|
249
247
|
flexShrink: 0,
|
|
250
248
|
display: 'inline-flex',
|
|
251
249
|
alignItems: 'center',
|
|
252
|
-
}, children: _jsx(
|
|
250
|
+
}, children: _jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" }) })) : null, _jsx(Box, { sx: {
|
|
251
|
+
display: 'inline-flex',
|
|
252
|
+
alignItems: 'center',
|
|
253
|
+
color: 'fg.muted',
|
|
254
|
+
flexShrink: 0,
|
|
255
|
+
ml: isPlatformAdmin && isCurrentUserPrincipal ? 1 : 0,
|
|
256
|
+
}, children: _jsx(TriangleDownIcon, { size: 12 }) })] })) : (_jsx(Button, { variant: "invisible", size: "small", "aria-label": "Switch principal", trailingVisual: TriangleDownIcon, sx: {
|
|
257
|
+
maxWidth: fullWidth ? '100%' : ['200px', '260px', '360px'],
|
|
258
|
+
width: fullWidth ? '100%' : 'auto',
|
|
259
|
+
}, children: _jsxs(Box, { sx: {
|
|
260
|
+
display: 'inline-flex',
|
|
261
|
+
alignItems: 'center',
|
|
262
|
+
gap: 1,
|
|
263
|
+
minWidth: 0,
|
|
264
|
+
}, children: [_jsx(Box, { sx: {
|
|
265
|
+
display: 'inline-flex',
|
|
266
|
+
alignItems: 'center',
|
|
267
|
+
color: 'fg.muted',
|
|
268
|
+
flexShrink: 0,
|
|
269
|
+
}, children: selectedPrincipalKind === 'organization' ? (_jsx(OrganizationIcon, { size: 16 })) : selectedPrincipalKind === 'team' ? (_jsx(PeopleIcon, { size: 16 })) : (_jsx(PersonIcon, { size: 16 })) }), _jsx(Box, { sx: {
|
|
270
|
+
minWidth: 0,
|
|
271
|
+
overflow: 'hidden',
|
|
272
|
+
textOverflow: 'ellipsis',
|
|
273
|
+
whiteSpace: 'nowrap',
|
|
274
|
+
}, children: _jsx(Text, { sx: {
|
|
275
|
+
color: 'accent.fg',
|
|
276
|
+
fontWeight: 'semibold',
|
|
277
|
+
}, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" })) : null] }) })) }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Personal" }), _jsxs(ActionList.Item, { disabled: isCurrentUserPrincipal, selected: isCurrentUserPrincipal, sx: isCurrentUserPrincipal ? selectedItemSx : undefined, onSelect: () => {
|
|
253
278
|
if (isCurrentUserPrincipal) {
|
|
254
279
|
return;
|
|
255
280
|
}
|
|
256
281
|
if (personalUid && personalHandle) {
|
|
257
282
|
selectUser(personalUid, personalHandle);
|
|
258
283
|
}
|
|
259
|
-
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PersonIcon, {}) }),
|
|
284
|
+
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PersonIcon, {}) }), _jsx(DisplayHandle, { handle: personalHandle || 'me' }), isPlatformAdmin ? (_jsx(ActionList.TrailingVisual, { children: _jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" }) })) : null] })] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Organizations" }), organizations.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No organizations" })) : (organizations.map((organization) => {
|
|
260
285
|
const organizationUid = getOrganizationUid(organization);
|
|
261
286
|
const isCurrentOrganizationPrincipal = selectedPrincipalKind === 'organization' &&
|
|
262
287
|
selectedPrincipalUid === organizationUid;
|
|
@@ -269,7 +294,13 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
269
294
|
if (organizationUid && organization.handle) {
|
|
270
295
|
selectOrganization(organizationUid, organization.handle);
|
|
271
296
|
}
|
|
272
|
-
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(OrganizationIcon, {}) }), "
|
|
297
|
+
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(OrganizationIcon, {}) }), _jsx(Box, { as: "span", title: displayHandleText(organization.handle), sx: {
|
|
298
|
+
display: 'block',
|
|
299
|
+
minWidth: 0,
|
|
300
|
+
overflow: 'hidden',
|
|
301
|
+
textOverflow: 'ellipsis',
|
|
302
|
+
whiteSpace: 'nowrap',
|
|
303
|
+
}, children: _jsx(DisplayHandle, { handle: organization.handle }) })] }, organizationUid));
|
|
273
304
|
}))] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Teams" }), teams.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No teams" })) : (teams.map(team => {
|
|
274
305
|
const isCurrentTeamPrincipal = selectedPrincipalKind === 'team' &&
|
|
275
306
|
selectedPrincipalUid === team.uid;
|
|
@@ -281,7 +312,13 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
281
312
|
return;
|
|
282
313
|
}
|
|
283
314
|
selectTeam(team, orgHandle);
|
|
284
|
-
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PeopleIcon, {}) }),
|
|
315
|
+
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PeopleIcon, {}) }), _jsxs(Box, { as: "span", title: `${displayHandleText(orgHandle)}/${displayHandleText(team.handle, { withAt: false })}`, sx: {
|
|
316
|
+
display: 'block',
|
|
317
|
+
minWidth: 0,
|
|
318
|
+
overflow: 'hidden',
|
|
319
|
+
textOverflow: 'ellipsis',
|
|
320
|
+
whiteSpace: 'nowrap',
|
|
321
|
+
}, children: [_jsx(DisplayHandle, { handle: orgHandle }), "/", _jsx(DisplayHandle, { handle: team.handle, withAt: false })] })] }, team.uid));
|
|
285
322
|
}))] })] }) })] }));
|
|
286
323
|
}
|
|
287
324
|
export default PrincipalSwitcherMenu;
|