@riducms/admin 0.2.3 → 0.4.0
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/THIRD_PARTY_NOTICES.md +60 -37
- package/package.json +29 -15
- package/src/app/admin-core-view-route.svelte +38 -53
- package/src/app/admin-extension-route.svelte +19 -0
- package/src/app/admin-layout.scss +175 -0
- package/src/app/admin-layout.svelte +144 -410
- package/src/app/admin-navigation-progress.svelte +11 -77
- package/src/app/admin-page-title.svelte +17 -85
- package/src/app/admin-page-title.ts +136 -0
- package/src/app/admin-route-error.svelte +24 -0
- package/src/app/admin-router-provider.svelte +34 -0
- package/src/app/admin-router-root.svelte +41 -0
- package/src/app/admin-routes.svelte +56 -19
- package/src/app/loading-boundary.svelte +3 -1
- package/src/app-meta.ts +13 -0
- package/src/app.css +138 -496
- package/src/app.svelte +60 -26
- package/src/components/brand/ridu-logo.svelte +19 -5
- package/src/components/data/list-table.scss +63 -0
- package/src/components/icons/LICENSE.md +24 -0
- package/src/components/icons/chevron.svelte +8 -0
- package/src/components/icons/plus.svelte +12 -0
- package/src/components/icons/search.svelte +12 -0
- package/src/components/icons/x.svelte +8 -0
- package/src/components/json-tree/json-tree-node.svelte +61 -67
- package/src/components/json-tree/json-viewer.scss +153 -0
- package/src/components/json-tree/json-viewer.svelte +57 -96
- package/src/components/ui/banner/banner.scss +28 -0
- package/src/components/ui/banner/banner.svelte +9 -8
- package/src/components/ui/banner/index.ts +1 -5
- package/src/components/ui/code-editor/code-editor.scss +20 -0
- package/src/components/ui/code-editor/code-editor.svelte +71 -0
- package/src/components/ui/code-editor/code-editor.ts +151 -0
- package/src/components/ui/code-editor/code-languages.ts +29 -0
- package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
- package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
- package/src/components/ui/collapsible-panel/index.ts +1 -0
- package/src/components/ui/combobox/combobox.scss +163 -0
- package/src/components/ui/combobox/index.ts +22 -0
- package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
- package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
- package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
- package/src/components/ui/date-value-control/date-value-control.scss +223 -0
- package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
- package/src/components/ui/drawer/drawer-depth.ts +8 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
- package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
- package/src/components/ui/kbd/kbd.scss +17 -0
- package/src/components/ui/kbd/kbd.svelte +3 -10
- package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
- package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
- package/src/components/ui/popover/popover-content.svelte +3 -5
- package/src/components/ui/popover/popover.scss +18 -0
- package/src/components/ui/radio-group/index.ts +0 -1
- package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
- package/src/components/ui/radio-group/radio-group.svelte +2 -2
- package/src/components/ui/radio-group/radio.scss +45 -0
- package/src/components/ui/select/select-content.svelte +5 -6
- package/src/components/ui/select/select-item.svelte +8 -8
- package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
- package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
- package/src/components/ui/select/select-trigger.svelte +6 -7
- package/src/components/ui/select/select.scss +126 -0
- package/src/components/ui/sheet/sheet-content.svelte +6 -34
- package/src/components/ui/sheet/sheet-description.svelte +2 -2
- package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
- package/src/components/ui/sheet/sheet-title.svelte +2 -2
- package/src/components/ui/sheet/sheet.scss +221 -0
- package/src/components/ui/skeleton/skeleton.scss +20 -0
- package/src/components/ui/skeleton/skeleton.svelte +4 -16
- package/src/components/ui/sonner/sonner.scss +121 -0
- package/src/components/ui/sonner/sonner.svelte +11 -10
- package/src/components/ui/status-indicator/status-indicator.scss +48 -0
- package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
- package/src/components/ui/tabs/tabs-content.svelte +2 -2
- package/src/components/ui/tabs/tabs-list.svelte +7 -7
- package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
- package/src/components/ui/tabs/tabs.scss +86 -0
- package/src/components/ui/tabs/tabs.svelte +2 -2
- package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
- package/src/core/api/admin-client.ts +2 -2
- package/src/core/bootstrap/admin-bootstrap.ts +396 -0
- package/src/core/bootstrap/admin-prepared-state.ts +213 -0
- package/src/core/bootstrap/admin-route-modules.ts +66 -0
- package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
- package/src/core/forms/embedded-fields.ts +4 -8
- package/src/core/forms/field-editor-authoring.ts +8 -0
- package/src/core/forms/field-issue-focus.ts +4 -4
- package/src/core/forms/field-occurrence.ts +4 -7
- package/src/core/forms/form-controller.svelte.ts +14 -21
- package/src/core/forms/form-draft-recovery.ts +17 -4
- package/src/core/forms/form-issue-correlation.ts +13 -13
- package/src/core/forms/form-path.ts +17 -0
- package/src/core/forms/form-schema.ts +12 -43
- package/src/core/forms/form-validation.ts +7 -46
- package/src/core/forms/plugin-field-authoring.ts +6 -2
- package/src/core/forms/plugin-field-write.ts +2 -6
- package/src/core/i18n/admin-i18n.svelte.ts +18 -0
- package/src/core/i18n/format-admin-date-time.ts +19 -0
- package/src/core/i18n/humanize-identifier.ts +5 -0
- package/src/core/i18n/time-zone-label.ts +20 -0
- package/src/core/loaders/admin-view-host.svelte +72 -0
- package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
- package/src/core/notifications/validation-toast.svelte +2 -2
- package/src/core/plugins/field-registry.ts +7 -8
- package/src/core/routing/admin-paths.ts +53 -2
- package/src/core/routing/admin-scroll.svelte.ts +74 -0
- package/src/core/routing/admin-view-selection.ts +78 -0
- package/src/core/runtime/admin-runtime.svelte.ts +131 -135
- package/src/core/schema/field-default.ts +33 -0
- package/src/core/schema/field-localization.ts +15 -0
- package/src/core/schema/read-document-path.ts +11 -0
- package/src/features/account/account-menu.scss +37 -0
- package/src/features/account/account-menu.svelte +115 -0
- package/src/features/{auth → account}/account-navigation.svelte +16 -19
- package/src/features/{auth → account}/account-route.svelte +206 -49
- package/src/features/account/account-security-controller.svelte.ts +159 -0
- package/src/features/{auth → account}/account-security-route.svelte +140 -42
- package/src/features/api-reference/api-reference-code.svelte +114 -0
- package/src/features/api-reference/api-reference-content.svelte +258 -0
- package/src/features/api-reference/api-reference-drawer.scss +74 -0
- package/src/features/api-reference/api-reference-examples.ts +381 -0
- package/src/features/api-reference/api-reference-schema.ts +332 -0
- package/src/features/api-reference/api-reference.scss +190 -0
- package/src/features/api-reference/api-reference.svelte +74 -0
- package/src/features/auth/auth-brand.svelte +5 -3
- package/src/features/auth/auth-field.svelte +92 -0
- package/src/features/auth/auth-frame.svelte +5 -8
- package/src/features/auth/auth.scss +122 -0
- package/src/features/auth/create-first-user-controller.svelte.ts +30 -60
- package/src/features/auth/create-first-user-route.svelte +83 -122
- package/src/features/auth/forgot-password-route.svelte +32 -24
- package/src/features/auth/login-route.svelte +69 -117
- package/src/features/auth/logout-button.scss +34 -0
- package/src/features/auth/logout-button.svelte +52 -0
- package/src/features/auth/request-verification-route.svelte +28 -23
- package/src/features/auth/reset-password-route.svelte +111 -41
- package/src/features/auth/verify-email-route.svelte +52 -15
- package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
- package/src/features/bulk-edit/bulk-edit.scss +152 -0
- package/src/features/bulk-edit/field-picker.svelte +176 -0
- package/src/features/collections/bulk/bulk-actions.svelte +148 -0
- package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
- package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
- package/src/features/collections/bulk/bulk-editor.svelte +186 -0
- package/src/features/collections/collection-list-cell-values.ts +144 -0
- package/src/features/collections/collection-list-controller.svelte.ts +268 -379
- package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
- package/src/features/collections/collection-list-query.ts +223 -0
- package/src/features/collections/collection-list-results.svelte +205 -329
- package/src/features/collections/collection-list-route.svelte +143 -884
- package/src/features/collections/collection-list.scss +261 -0
- package/src/features/collections/collection-list.svelte.ts +362 -0
- package/src/features/collections/controls/column-picker.svelte +110 -0
- package/src/features/collections/controls/column-pill.svelte +62 -0
- package/src/features/collections/controls/filter-builder.svelte +193 -0
- package/src/features/collections/controls/filter-row.svelte +142 -0
- package/src/features/collections/controls/list-controls.scss +196 -0
- package/src/features/collections/controls/list-options.svelte +203 -0
- package/src/features/collections/controls/list-pagination.scss +78 -0
- package/src/features/collections/controls/list-pagination.svelte +115 -0
- package/src/features/collections/controls/list-toolbar.svelte +132 -0
- package/src/features/collections/controls/sort-heading.scss +52 -0
- package/src/features/collections/controls/sort-heading.svelte +78 -0
- package/src/features/collections/list-columns.ts +43 -0
- package/src/features/collections/list-workspace.ts +155 -54
- package/src/features/dashboard/dashboard-route.svelte +82 -86
- package/src/features/dashboard/dashboard.scss +131 -0
- package/src/features/development/development-tools.svelte +20 -36
- package/src/features/documents/document-action-bar.svelte +438 -0
- package/src/features/documents/document-api-view-loader.svelte +17 -6
- package/src/features/documents/document-api-view.scss +120 -0
- package/src/features/documents/document-api-view.svelte +169 -454
- package/src/features/documents/document-controller.svelte.ts +531 -617
- package/src/features/documents/document-credentials.scss +37 -0
- package/src/features/documents/document-credentials.svelte +72 -0
- package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
- package/src/features/documents/document-field-sections.scss +39 -0
- package/src/features/documents/document-field-sections.svelte +4 -13
- package/src/features/documents/document-loading.scss +24 -0
- package/src/features/documents/document-lock-controller.svelte.ts +187 -0
- package/src/features/documents/document-operation-dialogs.svelte +110 -0
- package/src/features/documents/document-route-loader.svelte +17 -5
- package/src/features/documents/document-route.svelte +305 -811
- package/src/features/documents/document-schedule.scss +127 -0
- package/src/features/documents/document-schedule.svelte +323 -0
- package/src/features/documents/document-title.ts +25 -0
- package/src/features/documents/document.scss +274 -0
- package/src/features/documents/live-preview-panel.scss +117 -0
- package/src/features/documents/live-preview-panel.svelte +167 -168
- package/src/features/documents/live-preview.ts +2 -10
- package/src/features/documents/upload-document-preview-loader.svelte +26 -23
- package/src/features/localization/content-locale-switcher.scss +43 -0
- package/src/features/localization/content-locale-switcher.svelte +24 -17
- package/src/features/navigation/admin-breadcrumbs.scss +56 -0
- package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
- package/src/features/navigation/admin-command-menu.svelte +38 -15
- package/src/features/navigation/admin-navigation.scss +133 -0
- package/src/features/navigation/admin-navigation.svelte +169 -0
- package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
- package/src/features/navigation/navigation-preference.svelte.ts +70 -0
- package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
- package/src/features/reference-browser/reference-browser-list.scss +113 -0
- package/src/features/reference-browser/reference-browser-list.svelte +370 -0
- package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +336 -239
- package/src/features/reference-browser/reference-browser.scss +217 -0
- package/src/features/reference-browser/reference-browser.svelte +154 -797
- package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
- package/src/features/reference-browser/reference-list-query.ts +35 -0
- package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
- package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
- package/src/features/uploads/bulk-upload-editor.svelte +191 -0
- package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
- package/src/features/uploads/bulk-upload-remote.svelte +100 -0
- package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
- package/src/features/uploads/bulk-upload-route.svelte +172 -592
- package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
- package/src/features/uploads/bulk-upload.scss +526 -0
- package/src/features/uploads/bulk-upload.ts +105 -14
- package/src/features/uploads/crop-surface.ts +150 -0
- package/src/features/uploads/image-editor.svelte +231 -0
- package/src/features/uploads/rendition-preview.svelte +64 -0
- package/src/features/uploads/upload-control.svelte +340 -0
- package/src/features/uploads/upload-document-contracts.ts +30 -0
- package/src/features/uploads/upload-draft.svelte.ts +294 -0
- package/src/features/uploads/upload-metadata-fields.scss +32 -0
- package/src/features/uploads/upload-metadata-fields.svelte +8 -7
- package/src/features/uploads/upload-thumbnail.svelte +47 -0
- package/src/features/uploads/upload.scss +361 -0
- package/src/features/versions/version-comparison.svelte +361 -0
- package/src/features/versions/version-diff-fields.svelte +136 -0
- package/src/features/versions/version-diff.ts +296 -41
- package/src/features/versions/version-history-controller.svelte.ts +311 -0
- package/src/features/versions/version-history-route-loader.svelte +32 -0
- package/src/features/versions/version-history-route.svelte +157 -522
- package/src/features/versions/version-history.scss +444 -0
- package/src/features/versions/version-history.ts +45 -0
- package/src/features/versions/version-table.svelte +91 -0
- package/src/fields/embedded-schema-draft-editor.scss +48 -0
- package/src/fields/embedded-schema-draft-editor.svelte +24 -16
- package/src/fields/field-collapsible.svelte +14 -8
- package/src/fields/field-layout.scss +171 -0
- package/src/fields/field-layout.svelte +3 -5
- package/src/fields/field-renderer.svelte +17 -29
- package/src/fields/field-shell.svelte +1 -1
- package/src/fields/field-tab-layout.svelte +25 -19
- package/src/fields/join/join-field-controller.svelte.ts +513 -0
- package/src/fields/join/join-field-values.ts +111 -0
- package/src/fields/join/join-field.scss +189 -0
- package/src/fields/join/join-field.svelte +164 -332
- package/src/fields/live-validation-feedback.svelte +4 -3
- package/src/fields/local-field-editor.svelte +2 -2
- package/src/fields/nested/block-name-input.svelte +11 -12
- package/src/fields/nested/block-picker.scss +128 -0
- package/src/fields/nested/block-picker.svelte +81 -0
- package/src/fields/nested/block-placeholder.svg +2 -0
- package/src/fields/nested/nested-field.scss +331 -0
- package/src/fields/nested/nested-field.svelte +265 -245
- package/src/fields/nested/sortable-row.svelte +9 -7
- package/src/fields/plugin-field.svelte +4 -6
- package/src/fields/point/point-field.svelte +51 -38
- package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
- package/src/fields/primitive-list/primitive-list.scss +109 -0
- package/src/fields/radio/radio-field.svelte +2 -1
- package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
- package/src/fields/relationship/relationship-field.svelte +144 -175
- package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
- package/src/fields/relationship/relationship-query.ts +2 -10
- package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
- package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
- package/src/fields/relationship/relationship-selection.svelte +120 -0
- package/src/fields/relationship/relationship-value.ts +46 -7
- package/src/fields/relationship/relationship.scss +182 -0
- package/src/fields/scalar/code-field.svelte +101 -0
- package/src/fields/scalar/date-control-value.ts +13 -0
- package/src/fields/scalar/scalar-field.svelte +25 -67
- package/src/fields/select/select-field.svelte +148 -94
- package/src/fields/text/slug-field.scss +28 -0
- package/src/fields/text/slug-field.svelte +19 -15
- package/src/fields/text/text-field.svelte +1 -2
- package/src/fields/ui/ui-field.svelte +7 -4
- package/src/fields/virtual/virtual-field.svelte +2 -5
- package/src/index.ts +1 -2
- package/src/routing.ts +2 -0
- package/src/app/deferred-redirect.svelte +0 -23
- package/src/components/code-block/code-block.svelte +0 -107
- package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
- package/src/components/ui/bulk-action-bar/index.ts +0 -1
- package/src/components/ui/button/index.ts +0 -8
- package/src/components/ui/calendar/calendar-caption.svelte +0 -72
- package/src/components/ui/calendar/calendar-cell.svelte +0 -20
- package/src/components/ui/calendar/calendar-day.svelte +0 -27
- package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
- package/src/components/ui/calendar/calendar-grid.svelte +0 -17
- package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
- package/src/components/ui/calendar/calendar-header.svelte +0 -20
- package/src/components/ui/calendar/calendar-heading.svelte +0 -17
- package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
- package/src/components/ui/calendar/calendar-month.svelte +0 -16
- package/src/components/ui/calendar/calendar-months.svelte +0 -20
- package/src/components/ui/calendar/calendar-nav.svelte +0 -20
- package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
- package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
- package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
- package/src/components/ui/calendar/calendar.svelte +0 -118
- package/src/components/ui/calendar/index.ts +0 -1
- package/src/components/ui/checkbox/checkbox.svelte +0 -35
- package/src/components/ui/checkbox/index.ts +0 -1
- package/src/components/ui/dialog/dialog-close.svelte +0 -11
- package/src/components/ui/dialog/dialog-content.svelte +0 -52
- package/src/components/ui/dialog/dialog-description.svelte +0 -20
- package/src/components/ui/dialog/dialog-footer.svelte +0 -20
- package/src/components/ui/dialog/dialog-header.svelte +0 -20
- package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
- package/src/components/ui/dialog/dialog-title.svelte +0 -17
- package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
- package/src/components/ui/dialog/dialog.svelte +0 -7
- package/src/components/ui/dialog/index.ts +0 -8
- package/src/components/ui/empty-state/empty-state.svelte +0 -34
- package/src/components/ui/empty-state/index.ts +0 -1
- package/src/components/ui/input/index.ts +0 -1
- package/src/components/ui/pagination/index.ts +0 -1
- package/src/components/ui/pagination/pagination.svelte +0 -88
- package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
- package/src/components/ui/slider/index.ts +0 -3
- package/src/components/ui/slider/slider-content.svelte +0 -59
- package/src/components/ui/slider/slider.svelte +0 -35
- package/src/components/ui/table/index.ts +0 -6
- package/src/components/ui/table/table-body.svelte +0 -20
- package/src/components/ui/table/table-cell.svelte +0 -23
- package/src/components/ui/table/table-head.svelte +0 -23
- package/src/components/ui/table/table-header.svelte +0 -20
- package/src/components/ui/table/table-row.svelte +0 -23
- package/src/components/ui/table/table.svelte +0 -22
- package/src/components/ui/textarea/index.ts +0 -1
- package/src/components/ui/tooltip/index.ts +0 -4
- package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
- package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
- package/src/components/ui/tooltip/tooltip.svelte +0 -7
- package/src/features/auth/account-menu.svelte +0 -159
- package/src/features/auth/account-security-controller.svelte.ts +0 -119
- package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
- package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
- package/src/features/collections/collection-list-filter-popover.svelte +0 -238
- package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
- package/src/features/collections/collection-table-skeleton.svelte +0 -44
- package/src/features/documents/document-heading.ts +0 -20
- package/src/features/documents/upload-document-preview.svelte +0 -525
|
@@ -5,21 +5,13 @@ import {
|
|
|
5
5
|
type ResolvedAdminConfig,
|
|
6
6
|
} from "@riducms/plugin/admin";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
type
|
|
10
|
-
type
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
type AdminShellComponent,
|
|
16
|
-
type AdminProvider,
|
|
17
|
-
type AdminDocumentAction,
|
|
18
|
-
type AdminDocumentView,
|
|
19
|
-
type AdminListCell,
|
|
20
|
-
type AdminRoute,
|
|
21
|
-
} from "@riducms/plugin";
|
|
22
|
-
import type { AuthSession, OperationCapabilities, SchemaManifest } from "@riducms/protocol";
|
|
8
|
+
bindSchemaManifest,
|
|
9
|
+
type AdminPreparedNavigationV1,
|
|
10
|
+
type AdminPreparedRuntimeV1,
|
|
11
|
+
type AuthSession,
|
|
12
|
+
type OperationCapabilities,
|
|
13
|
+
type SchemaManifest,
|
|
14
|
+
} from "@riducms/protocol";
|
|
23
15
|
import { RiduError } from "@riducms/sdk";
|
|
24
16
|
import { createContext } from "svelte";
|
|
25
17
|
|
|
@@ -61,9 +53,11 @@ export class AdminRuntime {
|
|
|
61
53
|
manifestRefreshError = $state<string>();
|
|
62
54
|
collectionOperations = $state.raw<Record<string, OperationCapabilities>>({});
|
|
63
55
|
globalOperations = $state.raw<Record<string, OperationCapabilities>>({});
|
|
56
|
+
preparedPreferences = $state.raw<Record<string, unknown>>({});
|
|
64
57
|
themePreference = $state<ThemePreference>("system");
|
|
65
58
|
resolvedTheme = $state<"light" | "dark">("dark");
|
|
66
59
|
contentLocale = $state<string>();
|
|
60
|
+
contentLocaleTransitioning = $state(false);
|
|
67
61
|
#confirmedThemePreference: ThemePreference = "system";
|
|
68
62
|
#confirmedThemeOwnerID = "";
|
|
69
63
|
#confirmedContentLocale?: string;
|
|
@@ -75,6 +69,10 @@ export class AdminRuntime {
|
|
|
75
69
|
#contentLocaleBlockers = new Map<symbol, () => boolean>();
|
|
76
70
|
#contentLocaleBlockerSequence = 0;
|
|
77
71
|
#contentLocaleBlockerRevision = $state(0);
|
|
72
|
+
#systemThemeQuery?: MediaQueryList;
|
|
73
|
+
readonly #handleSystemThemeChange = () => {
|
|
74
|
+
if (this.themePreference === "system") this.#applyTheme();
|
|
75
|
+
};
|
|
78
76
|
#preferenceReset?: {
|
|
79
77
|
sessionID: string | undefined;
|
|
80
78
|
ownerID: string;
|
|
@@ -141,58 +139,20 @@ export class AdminRuntime {
|
|
|
141
139
|
};
|
|
142
140
|
}
|
|
143
141
|
|
|
144
|
-
adoptContentLocale(locale: string) {
|
|
145
|
-
if (!this.contentLocales.some((candidate) => candidate.code === locale)) return false;
|
|
146
|
-
if (this.contentLocaleSwitchBlocked) return false;
|
|
147
|
-
if (this.contentLocale === locale) return true;
|
|
148
|
-
this.contentLocale = locale;
|
|
149
|
-
void this.refreshAccess(locale).catch(() => {
|
|
150
|
-
// Route adoption is best-effort; retain the last complete access snapshot on failure.
|
|
151
|
-
});
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
readonly pluginRoutes: readonly AdminRoute[];
|
|
156
|
-
readonly dashboardPanels: readonly AdminDashboardPanel[];
|
|
157
|
-
readonly loginComponents: readonly AdminLoginComponent[];
|
|
158
|
-
readonly accountComponents: readonly AdminAccountComponent[];
|
|
159
|
-
readonly navigationComponents: readonly AdminNavigationComponent[];
|
|
160
|
-
readonly logoutButton?: AdminLogoutButton;
|
|
161
|
-
readonly coreViews: readonly AdminCoreView[];
|
|
162
|
-
readonly brandComponents: readonly AdminBrandComponent[];
|
|
163
|
-
readonly shellComponents: readonly AdminShellComponent[];
|
|
164
|
-
readonly providers: readonly AdminProvider[];
|
|
165
|
-
readonly listCells: readonly AdminListCell[];
|
|
166
|
-
readonly documentActions: readonly AdminDocumentAction[];
|
|
167
|
-
readonly documentViews: readonly AdminDocumentView[];
|
|
168
142
|
readonly rowLabels: RowLabelRegistry;
|
|
169
143
|
readonly i18n: AdminI18nController;
|
|
170
144
|
|
|
171
145
|
readonly config: ResolvedAdminConfig;
|
|
172
146
|
readonly fields: FieldRegistry;
|
|
173
|
-
readonly editors: ResolvedAdminConfig["editors"];
|
|
174
147
|
|
|
175
148
|
constructor(
|
|
176
149
|
readonly client: AdminClient,
|
|
177
|
-
config: AdminConfig = {}
|
|
150
|
+
config: AdminConfig = {},
|
|
151
|
+
readonly adminBasePath = "/admin"
|
|
178
152
|
) {
|
|
179
153
|
this.config = resolveAdminConfig(config);
|
|
180
|
-
this.fields = createCoreFieldRegistry(this.config.
|
|
181
|
-
this.editors = this.config.editors;
|
|
154
|
+
this.fields = createCoreFieldRegistry(this.config.pluginFields);
|
|
182
155
|
const extensions = this.config.extensions;
|
|
183
|
-
this.pluginRoutes = extensions.routes;
|
|
184
|
-
this.dashboardPanels = extensions.dashboard;
|
|
185
|
-
this.loginComponents = extensions.login;
|
|
186
|
-
this.accountComponents = extensions.account;
|
|
187
|
-
this.navigationComponents = extensions.navigation;
|
|
188
|
-
this.logoutButton = extensions.logoutButton;
|
|
189
|
-
this.coreViews = extensions.views;
|
|
190
|
-
this.brandComponents = extensions.branding;
|
|
191
|
-
this.shellComponents = extensions.shell;
|
|
192
|
-
this.providers = extensions.providers;
|
|
193
|
-
this.listCells = extensions.listCells;
|
|
194
|
-
this.documentActions = extensions.documentActions;
|
|
195
|
-
this.documentViews = extensions.documentViews;
|
|
196
156
|
this.rowLabels = createRowLabelRegistry(extensions.rowLabels, this.config.rowLabels);
|
|
197
157
|
this.i18n = new AdminI18nController(
|
|
198
158
|
client,
|
|
@@ -203,10 +163,65 @@ export class AdminRuntime {
|
|
|
203
163
|
);
|
|
204
164
|
this.#applyTheme();
|
|
205
165
|
if (typeof window !== "undefined") {
|
|
206
|
-
window.matchMedia("(prefers-color-scheme: dark)")
|
|
207
|
-
|
|
208
|
-
|
|
166
|
+
this.#systemThemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
167
|
+
this.#systemThemeQuery.addEventListener("change", this.#handleSystemThemeChange);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
hardNavigate(path: string) {
|
|
172
|
+
const base = `/${this.adminBasePath.split("/").filter(Boolean).join("/")}`;
|
|
173
|
+
const route = path.startsWith("/") ? path : `/${path}`;
|
|
174
|
+
window.location.assign(base === "/" ? route : `${base}${route}`);
|
|
175
|
+
// Keep identity-changing handlers pending until the new document takes ownership.
|
|
176
|
+
return new Promise<never>(() => {});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
dispose() {
|
|
180
|
+
this.#systemThemeQuery?.removeEventListener("change", this.#handleSystemThemeChange);
|
|
181
|
+
this.#systemThemeQuery = undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
adoptPrepared(prepared: AdminPreparedRuntimeV1) {
|
|
185
|
+
this.#manifestRequest += 1;
|
|
186
|
+
this.error = undefined;
|
|
187
|
+
this.loading = true;
|
|
188
|
+
const manifest = bindSchemaManifest(prepared.manifest);
|
|
189
|
+
this.#installManifest(manifest);
|
|
190
|
+
this.session = prepared.session as AuthSession<AdminDocument> | undefined;
|
|
191
|
+
this.authBootstrapAvailable = prepared.authBootstrapAvailable;
|
|
192
|
+
this.collectionOperations = { ...prepared.collectionOperations };
|
|
193
|
+
this.globalOperations = { ...prepared.globalOperations };
|
|
194
|
+
this.preparedPreferences = { ...prepared.preferences };
|
|
195
|
+
this.#confirmedThemePreference = isThemePreference(prepared.theme) ? prepared.theme : "system";
|
|
196
|
+
this.#confirmedThemeOwnerID = preferenceOwnerID(this.session);
|
|
197
|
+
this.themePreference = this.#confirmedThemePreference;
|
|
198
|
+
this.#applyTheme();
|
|
199
|
+
if (
|
|
200
|
+
prepared.contentLocale !== undefined &&
|
|
201
|
+
this.contentLocales.some((candidate) => candidate.code === prepared.contentLocale)
|
|
202
|
+
) {
|
|
203
|
+
this.contentLocale = prepared.contentLocale;
|
|
204
|
+
this.#confirmedContentLocale = prepared.contentLocale;
|
|
205
|
+
this.#confirmedContentLocaleOwnerID = preferenceOwnerID(this.session);
|
|
209
206
|
}
|
|
207
|
+
this.i18n.adoptPreparedPreferences(prepared.adminLanguage, prepared.adminTimeZone);
|
|
208
|
+
this.loading = false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
adoptPreparedNavigation(prepared: AdminPreparedNavigationV1) {
|
|
212
|
+
this.collectionOperations = { ...prepared.collectionOperations };
|
|
213
|
+
this.globalOperations = { ...prepared.globalOperations };
|
|
214
|
+
if (
|
|
215
|
+
prepared.contentLocale !== undefined &&
|
|
216
|
+
this.contentLocales.some((candidate) => candidate.code === prepared.contentLocale)
|
|
217
|
+
) {
|
|
218
|
+
this.contentLocale = prepared.contentLocale;
|
|
219
|
+
this.#confirmedContentLocale = prepared.contentLocale;
|
|
220
|
+
this.#confirmedContentLocaleOwnerID = preferenceOwnerID(this.session);
|
|
221
|
+
}
|
|
222
|
+
// The context key guarantees this is the same identity. Preferences are
|
|
223
|
+
// adopted once from the cold document; a navigation response may have started
|
|
224
|
+
// before a local theme/language/time-zone write and must not roll it back.
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
async loadTheme() {
|
|
@@ -240,6 +255,15 @@ export class AdminRuntime {
|
|
|
240
255
|
return contentLocaleFromPath(path, this.contentLocales);
|
|
241
256
|
}
|
|
242
257
|
|
|
258
|
+
/** A supported URL locale wins; otherwise retain the session's selection or the default. */
|
|
259
|
+
resolveContentLocale(requested: string | null | undefined) {
|
|
260
|
+
return (
|
|
261
|
+
this.contentLocales.find((locale) => locale.code === requested)?.code ??
|
|
262
|
+
this.contentLocale ??
|
|
263
|
+
this.manifest?.application.localization?.defaultLocale
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
243
267
|
async loadContentLocale(activeLocale?: string) {
|
|
244
268
|
const request = ++this.#contentLocaleRequest;
|
|
245
269
|
const localization = this.manifest?.application.localization;
|
|
@@ -274,44 +298,24 @@ export class AdminRuntime {
|
|
|
274
298
|
: this.#confirmedContentLocale;
|
|
275
299
|
}
|
|
276
300
|
|
|
277
|
-
async
|
|
301
|
+
async persistContentLocalePreference(locale: string) {
|
|
278
302
|
if (!this.contentLocales.some((candidate) => candidate.code === locale)) return;
|
|
279
303
|
if (this.contentLocaleSwitchBlocked) throw new ContentLocaleSwitchBlockedError();
|
|
280
|
-
if (locale === this.contentLocale && locale === this.#confirmedContentLocale) return;
|
|
281
|
-
const request = ++this.#contentLocaleRequest;
|
|
282
304
|
const sessionID = this.session?.id;
|
|
283
305
|
const ownerID = preferenceOwnerID(this.session);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
JSON.stringify([ownerID, "content-locale"]),
|
|
295
|
-
ownerID,
|
|
296
|
-
() => this.session?.id === sessionID,
|
|
297
|
-
() => this.client.setPreference("content-locale", { locale })
|
|
298
|
-
);
|
|
299
|
-
if (!result.dispatched) throw new StalePreferenceSessionError();
|
|
300
|
-
}
|
|
301
|
-
if (request !== this.#contentLocaleRequest) return;
|
|
302
|
-
if (this.session?.id !== sessionID || preferenceOwnerID(this.session) !== ownerID) {
|
|
303
|
-
throw new StalePreferenceSessionError();
|
|
304
|
-
}
|
|
305
|
-
await accessRefresh;
|
|
306
|
-
this.#confirmedContentLocale = locale;
|
|
307
|
-
this.#confirmedContentLocaleOwnerID = ownerID;
|
|
308
|
-
} catch (error) {
|
|
309
|
-
if (request === this.#contentLocaleRequest) {
|
|
310
|
-
this.contentLocale = this.#confirmedContentLocale;
|
|
311
|
-
await this.refreshAccess(this.#confirmedContentLocale);
|
|
312
|
-
}
|
|
313
|
-
throw error;
|
|
306
|
+
if (sessionID === undefined || ownerID === "") {
|
|
307
|
+
await this.client.setPreference("content-locale", { locale });
|
|
308
|
+
} else {
|
|
309
|
+
const result = await getPreferenceWriteQueue().enqueue(
|
|
310
|
+
JSON.stringify([ownerID, "content-locale"]),
|
|
311
|
+
ownerID,
|
|
312
|
+
() => this.session?.id === sessionID,
|
|
313
|
+
() => this.client.setPreference("content-locale", { locale })
|
|
314
|
+
);
|
|
315
|
+
if (!result.dispatched) throw new StalePreferenceSessionError();
|
|
314
316
|
}
|
|
317
|
+
this.#confirmedContentLocale = locale;
|
|
318
|
+
this.#confirmedContentLocaleOwnerID = ownerID;
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
async setTheme(preference: ThemePreference) {
|
|
@@ -437,6 +441,27 @@ export class AdminRuntime {
|
|
|
437
441
|
}
|
|
438
442
|
}
|
|
439
443
|
|
|
444
|
+
#installManifest(manifest: SchemaManifest) {
|
|
445
|
+
validateAdminManifest(this.config, manifest);
|
|
446
|
+
this.manifest = manifest;
|
|
447
|
+
this.manifestRevision += 1;
|
|
448
|
+
this.#configureContentLocale();
|
|
449
|
+
this.i18n.configure(manifest.application.adminLocalization);
|
|
450
|
+
return manifest.application.admin;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async #refreshLocalizedAccess() {
|
|
454
|
+
await this.loadContentLocale(this.#browserRouteContentLocale());
|
|
455
|
+
const accessAuthoritative = await this.refreshAccess(this.contentLocale);
|
|
456
|
+
if (!accessAuthoritative || this.session === undefined || this.adminAccessAllowed) return;
|
|
457
|
+
try {
|
|
458
|
+
await this.client.auth.logout();
|
|
459
|
+
} catch {
|
|
460
|
+
// The local session is still denied even if server cleanup fails.
|
|
461
|
+
}
|
|
462
|
+
this.session = undefined;
|
|
463
|
+
}
|
|
464
|
+
|
|
440
465
|
async bootstrap() {
|
|
441
466
|
const request = ++this.#manifestRequest;
|
|
442
467
|
this.loading = true;
|
|
@@ -444,43 +469,26 @@ export class AdminRuntime {
|
|
|
444
469
|
try {
|
|
445
470
|
const manifest = await this.client.schema();
|
|
446
471
|
if (request !== this.#manifestRequest) return;
|
|
447
|
-
|
|
448
|
-
this.manifest = manifest;
|
|
449
|
-
this.manifestRevision += 1;
|
|
450
|
-
this.#configureContentLocale();
|
|
451
|
-
this.i18n.configure(manifest.application.adminLocalization);
|
|
452
|
-
const admin = manifest.application.admin;
|
|
472
|
+
const admin = this.#installManifest(manifest);
|
|
453
473
|
if (admin !== undefined) {
|
|
454
|
-
const bootstrap = await this.client.
|
|
474
|
+
const bootstrap = await this.client.auth.bootstrap({
|
|
475
|
+
collection: admin.userCollectionSlug,
|
|
476
|
+
});
|
|
455
477
|
if (request !== this.#manifestRequest) return;
|
|
456
478
|
this.authBootstrapAvailable = bootstrap.available;
|
|
457
479
|
if (bootstrap.available) {
|
|
458
480
|
this.session = undefined;
|
|
459
481
|
} else {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
} catch (error) {
|
|
465
|
-
if (request !== this.#manifestRequest) return;
|
|
466
|
-
if (!(error instanceof RiduError) || error.code !== "access_denied") throw error;
|
|
467
|
-
this.session = undefined;
|
|
468
|
-
}
|
|
482
|
+
// getSession resolves null without a session and rejects on an outage.
|
|
483
|
+
const session = await this.client.auth.getSession();
|
|
484
|
+
if (request !== this.#manifestRequest) return;
|
|
485
|
+
this.session = session?.collection === admin.userCollectionSlug ? session : undefined;
|
|
469
486
|
}
|
|
470
487
|
} else {
|
|
471
488
|
this.session = undefined;
|
|
472
489
|
this.authBootstrapAvailable = false;
|
|
473
490
|
}
|
|
474
|
-
await this
|
|
475
|
-
const accessAuthoritative = await this.refreshAccess(this.contentLocale);
|
|
476
|
-
if (accessAuthoritative && this.session !== undefined && !this.adminAccessAllowed) {
|
|
477
|
-
try {
|
|
478
|
-
await this.client.logout();
|
|
479
|
-
} catch {
|
|
480
|
-
// The local session is still denied even if server cleanup fails.
|
|
481
|
-
}
|
|
482
|
-
this.session = undefined;
|
|
483
|
-
}
|
|
491
|
+
await this.#refreshLocalizedAccess();
|
|
484
492
|
await Promise.all([this.loadTheme(), this.i18n.loadPreferences()]);
|
|
485
493
|
} catch (error) {
|
|
486
494
|
if (request !== this.#manifestRequest) return;
|
|
@@ -497,32 +505,20 @@ export class AdminRuntime {
|
|
|
497
505
|
try {
|
|
498
506
|
const manifest = await this.client.schema();
|
|
499
507
|
if (request !== this.#manifestRequest) return;
|
|
500
|
-
|
|
501
|
-
this.manifest = manifest;
|
|
502
|
-
this.manifestRevision += 1;
|
|
503
|
-
this.#configureContentLocale();
|
|
504
|
-
this.i18n.configure(manifest.application.adminLocalization);
|
|
505
|
-
const admin = manifest.application.admin;
|
|
508
|
+
const admin = this.#installManifest(manifest);
|
|
506
509
|
if (admin === undefined || this.session?.collection !== admin.userCollectionSlug) {
|
|
507
510
|
this.session = undefined;
|
|
508
511
|
}
|
|
509
512
|
if (admin !== undefined && this.session === undefined) {
|
|
510
|
-
const bootstrap = await this.client.
|
|
513
|
+
const bootstrap = await this.client.auth.bootstrap({
|
|
514
|
+
collection: admin.userCollectionSlug,
|
|
515
|
+
});
|
|
511
516
|
if (request !== this.#manifestRequest) return;
|
|
512
517
|
this.authBootstrapAvailable = bootstrap.available;
|
|
513
518
|
} else {
|
|
514
519
|
this.authBootstrapAvailable = false;
|
|
515
520
|
}
|
|
516
|
-
await this
|
|
517
|
-
const accessAuthoritative = await this.refreshAccess(this.contentLocale);
|
|
518
|
-
if (accessAuthoritative && this.session !== undefined && !this.adminAccessAllowed) {
|
|
519
|
-
try {
|
|
520
|
-
await this.client.logout();
|
|
521
|
-
} catch {
|
|
522
|
-
// The local session is still denied even if server cleanup fails.
|
|
523
|
-
}
|
|
524
|
-
this.session = undefined;
|
|
525
|
-
}
|
|
521
|
+
await this.#refreshLocalizedAccess();
|
|
526
522
|
await this.i18n.loadPreferences();
|
|
527
523
|
} catch (error) {
|
|
528
524
|
if (request !== this.#manifestRequest) return;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SchemaField } from "@riducms/protocol";
|
|
2
|
+
|
|
3
|
+
export type SchemaFieldDefault = { present: false } | { present: true; value: unknown };
|
|
4
|
+
|
|
5
|
+
/** Decode the literal-default representation used by the schema manifest. */
|
|
6
|
+
export function schemaFieldDefault(field: SchemaField): SchemaFieldDefault {
|
|
7
|
+
if (field.type === "select" && field.select?.hasMany === true) {
|
|
8
|
+
if ((field.select.defaultValues?.length ?? 0) === 0) return { present: false };
|
|
9
|
+
return { present: true, value: field.select.defaultValues };
|
|
10
|
+
}
|
|
11
|
+
if (field.default === undefined) return { present: false };
|
|
12
|
+
if (field.type === "text-list" || field.type === "number-list")
|
|
13
|
+
return { present: true, value: JSON.parse(field.default) };
|
|
14
|
+
if (field.type === "number") return { present: true, value: Number(field.default) };
|
|
15
|
+
if (field.type === "checkbox") return { present: true, value: field.default === "true" };
|
|
16
|
+
return { present: true, value: field.default };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Whether a generated create object requires this field when that object is supplied. */
|
|
20
|
+
export function schemaFieldRequiredOnCreate(field: SchemaField): boolean {
|
|
21
|
+
const inputRequired =
|
|
22
|
+
field.required ||
|
|
23
|
+
((field.type === "text-list" || field.type === "number-list") &&
|
|
24
|
+
(field.list?.minRows ?? 0) > 0);
|
|
25
|
+
if (!inputRequired) return false;
|
|
26
|
+
|
|
27
|
+
return !(
|
|
28
|
+
field.default !== undefined ||
|
|
29
|
+
field.dynamicDefault === true ||
|
|
30
|
+
(field.select?.hasMany === true && (field.select.defaultValues?.length ?? 0) > 0) ||
|
|
31
|
+
field.text?.slug !== undefined
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolveBlockTypes, type SchemaField } from "@riducms/protocol";
|
|
2
|
+
|
|
3
|
+
export function fieldsUseLocalization(fields: readonly SchemaField[]): boolean {
|
|
4
|
+
const visited = new Set<SchemaField>();
|
|
5
|
+
const usesLocalization = (field: SchemaField): boolean => {
|
|
6
|
+
if (visited.has(field)) return false;
|
|
7
|
+
visited.add(field);
|
|
8
|
+
return (
|
|
9
|
+
field.localized === true ||
|
|
10
|
+
field.nested?.fields.some(usesLocalization) === true ||
|
|
11
|
+
resolveBlockTypes(field.blocks).some((block) => block.fields.some(usesLocalization))
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
return fields.some(usesLocalization);
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
2
|
+
|
|
3
|
+
/** Read a document/group path without treating arrays as nested records. */
|
|
4
|
+
export function readDocumentPath(value: unknown, path: string) {
|
|
5
|
+
let current = value;
|
|
6
|
+
for (const segment of path.split(".")) {
|
|
7
|
+
if (!isRecord(current)) return undefined;
|
|
8
|
+
current = current[segment];
|
|
9
|
+
}
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
@use "@riducms/ui/styles.scss" as ui;
|
|
2
|
+
|
|
3
|
+
@layer ridu.components {
|
|
4
|
+
.ridu-account-menu__trigger {
|
|
5
|
+
display: grid;
|
|
6
|
+
place-items: center;
|
|
7
|
+
flex-shrink: 0;
|
|
8
|
+
width: 25px;
|
|
9
|
+
height: 25px;
|
|
10
|
+
border: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
background: transparent;
|
|
13
|
+
color: var(--foreground);
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
border-radius: 50%;
|
|
16
|
+
|
|
17
|
+
&:focus-visible {
|
|
18
|
+
@include ui.focus-outline;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
.ridu-account-menu__avatar {
|
|
22
|
+
fill: var(--nav-indicator-color);
|
|
23
|
+
overflow: visible;
|
|
24
|
+
}
|
|
25
|
+
.ridu-account-menu__avatar-background {
|
|
26
|
+
fill: var(--background-surface);
|
|
27
|
+
stroke: var(--nav-indicator-color);
|
|
28
|
+
stroke-width: 1px;
|
|
29
|
+
}
|
|
30
|
+
.ridu-account-menu__trigger:hover .ridu-account-menu__avatar {
|
|
31
|
+
fill: var(--foreground-muted);
|
|
32
|
+
}
|
|
33
|
+
.ridu-account-menu__trigger:hover .ridu-account-menu__avatar-background {
|
|
34
|
+
fill: var(--background-layer);
|
|
35
|
+
stroke: var(--foreground-muted);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Link } from "@hvniel/svelte-router";
|
|
3
|
+
import "@admin/features/account/account-menu.scss";
|
|
4
|
+
import SettingsIcon from "~icons/lucide/settings";
|
|
5
|
+
import UserRoundIcon from "~icons/lucide/user-round";
|
|
6
|
+
|
|
7
|
+
import { buttonVariants } from "@riducms/ui";
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@admin/components/ui/popover";
|
|
9
|
+
import { adminRoutePatterns } from "@admin/core/routing/admin-paths";
|
|
10
|
+
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
11
|
+
|
|
12
|
+
const runtime = getAdminRuntime();
|
|
13
|
+
const extensions = runtime.config.extensions;
|
|
14
|
+
|
|
15
|
+
let open = $state(false);
|
|
16
|
+
|
|
17
|
+
const accountAvatar = extensions.branding.find(
|
|
18
|
+
(component) => component.surface === "accountAvatar"
|
|
19
|
+
);
|
|
20
|
+
const settingsComponents = extensions.shellSlots.filter(
|
|
21
|
+
(component) => component.position === "settingsMenu"
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
function accountDetails() {
|
|
25
|
+
const user = runtime.session?.user;
|
|
26
|
+
const candidate = user?.name;
|
|
27
|
+
let displayName =
|
|
28
|
+
typeof candidate === "string" && candidate.trim().length > 0 ? candidate : undefined;
|
|
29
|
+
|
|
30
|
+
const identityField = runtime.authCollection?.authSettings?.identityField;
|
|
31
|
+
const identity = identityField === undefined ? undefined : user?.[identityField];
|
|
32
|
+
if (displayName === undefined && typeof identity === "string" && identity.trim().length > 0)
|
|
33
|
+
displayName = identity;
|
|
34
|
+
displayName ??= runtime.i18n.t("account:signedInUser");
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
user,
|
|
38
|
+
displayName,
|
|
39
|
+
identity: typeof identity === "string" && identity !== displayName ? identity : undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const account = $derived(accountDetails());
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<Popover bind:open>
|
|
47
|
+
<PopoverTrigger
|
|
48
|
+
class="ridu-account-menu__trigger"
|
|
49
|
+
aria-label={runtime.i18n.t("account:accountMenuLabel", { name: account.displayName })}
|
|
50
|
+
>
|
|
51
|
+
{#if accountAvatar !== undefined && runtime.manifest !== undefined}
|
|
52
|
+
<accountAvatar.component
|
|
53
|
+
manifest={runtime.manifest}
|
|
54
|
+
user={account.user}
|
|
55
|
+
surface="accountAvatar"
|
|
56
|
+
i18n={runtime.i18n}
|
|
57
|
+
/>
|
|
58
|
+
{:else}
|
|
59
|
+
<svg
|
|
60
|
+
class="ridu-account-menu__avatar"
|
|
61
|
+
width="25"
|
|
62
|
+
height="25"
|
|
63
|
+
viewBox="0 0 25 25"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
>
|
|
66
|
+
<circle class="ridu-account-menu__avatar-background" cx="12.5" cy="12.5" r="11.5" />
|
|
67
|
+
<circle cx="12.5" cy="10.73" r="3.98" />
|
|
68
|
+
<path
|
|
69
|
+
d="M12.5,24a11.44,11.44,0,0,0,7.66-2.94c-.5-2.71-3.73-4.8-7.66-4.8s-7.16,2.09-7.66,4.8A11.44,11.44,0,0,0,12.5,24Z"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
{/if}
|
|
73
|
+
</PopoverTrigger>
|
|
74
|
+
<PopoverContent side="bottom" align="end" sideOffset={8} class="w-[212px] gap-1 p-2">
|
|
75
|
+
<div class="border-b border-control-border px-2.5 py-2 pb-3">
|
|
76
|
+
<p class="truncate text-[13px] font-medium text-foreground">{account.displayName}</p>
|
|
77
|
+
{#if account.identity !== undefined}
|
|
78
|
+
<p class="mt-0.5 truncate text-[10.5px] text-foreground-faint">{account.identity}</p>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<Link
|
|
83
|
+
to={adminRoutePatterns.account}
|
|
84
|
+
class={buttonVariants({
|
|
85
|
+
variant: "ghost",
|
|
86
|
+
class: "h-9 w-full justify-start gap-2 px-2.5 text-[12.5px]",
|
|
87
|
+
})}
|
|
88
|
+
onclick={() => (open = false)}
|
|
89
|
+
>
|
|
90
|
+
<UserRoundIcon class="size-3.5" aria-hidden="true" />
|
|
91
|
+
{runtime.i18n.t("account:profileAndPreferences")}
|
|
92
|
+
</Link>
|
|
93
|
+
<Link
|
|
94
|
+
to={adminRoutePatterns.accountSecurity}
|
|
95
|
+
class={buttonVariants({
|
|
96
|
+
variant: "ghost",
|
|
97
|
+
class: "h-9 w-full justify-start gap-2 px-2.5 text-[12.5px]",
|
|
98
|
+
})}
|
|
99
|
+
onclick={() => (open = false)}
|
|
100
|
+
>
|
|
101
|
+
<SettingsIcon class="size-3.5" aria-hidden="true" />
|
|
102
|
+
{runtime.i18n.t("account:accountSecurity")}
|
|
103
|
+
</Link>
|
|
104
|
+
{#if runtime.manifest !== undefined}
|
|
105
|
+
{#each settingsComponents as component (component.key)}
|
|
106
|
+
<component.component
|
|
107
|
+
manifest={runtime.manifest}
|
|
108
|
+
user={account.user}
|
|
109
|
+
position="settingsMenu"
|
|
110
|
+
i18n={runtime.i18n}
|
|
111
|
+
/>
|
|
112
|
+
{/each}
|
|
113
|
+
{/if}
|
|
114
|
+
</PopoverContent>
|
|
115
|
+
</Popover>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Link } from "@hvniel/svelte-router";
|
|
3
3
|
|
|
4
|
-
import { buttonVariants } from "@
|
|
4
|
+
import { buttonVariants } from "@riducms/ui";
|
|
5
5
|
import { adminRoutePatterns } from "@admin/core/routing/admin-paths";
|
|
6
6
|
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
7
7
|
|
|
8
8
|
const runtime = getAdminRuntime();
|
|
9
|
+
|
|
9
10
|
let { active }: { active: "profile" | "security" } = $props();
|
|
10
11
|
</script>
|
|
11
12
|
|
|
@@ -15,29 +16,25 @@
|
|
|
15
16
|
>
|
|
16
17
|
<Link
|
|
17
18
|
to={adminRoutePatterns.account}
|
|
18
|
-
class={
|
|
19
|
-
variant: "ghost",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
],
|
|
26
|
-
})}
|
|
19
|
+
class={[
|
|
20
|
+
buttonVariants({ variant: "ghost" }),
|
|
21
|
+
"rounded-b-none border-b-2 px-3",
|
|
22
|
+
active === "profile"
|
|
23
|
+
? "border-primary text-foreground-strong"
|
|
24
|
+
: "border-transparent text-foreground-muted hover:bg-control",
|
|
25
|
+
]}
|
|
27
26
|
>
|
|
28
27
|
{runtime.i18n.t("account:profileAndPreferences")}
|
|
29
28
|
</Link>
|
|
30
29
|
<Link
|
|
31
30
|
to={adminRoutePatterns.accountSecurity}
|
|
32
|
-
class={
|
|
33
|
-
variant: "ghost",
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
],
|
|
40
|
-
})}
|
|
31
|
+
class={[
|
|
32
|
+
buttonVariants({ variant: "ghost" }),
|
|
33
|
+
"rounded-b-none border-b-2 px-3",
|
|
34
|
+
active === "security"
|
|
35
|
+
? "border-primary text-foreground-strong"
|
|
36
|
+
: "border-transparent text-foreground-muted hover:bg-control",
|
|
37
|
+
]}
|
|
41
38
|
>
|
|
42
39
|
{runtime.i18n.t("account:security")}
|
|
43
40
|
</Link>
|