@riducms/admin 0.2.2 → 0.3.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 +27 -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 +121 -124
- 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 +157 -0
- package/src/features/{auth → account}/account-security-route.svelte +140 -42
- package/src/features/api-reference/api-reference-code.svelte +64 -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 +20 -51
- package/src/features/auth/create-first-user-route.svelte +83 -122
- package/src/features/auth/forgot-password-route.svelte +31 -23
- package/src/features/auth/login-route.svelte +68 -116
- 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 +27 -22
- package/src/features/auth/reset-password-route.svelte +108 -41
- package/src/features/auth/verify-email-route.svelte +49 -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 +526 -613
- 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 +335 -236
- 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 +132 -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.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,12 +469,7 @@ 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
474
|
const bootstrap = await this.client.authBootstrap(admin.userCollectionSlug);
|
|
455
475
|
if (request !== this.#manifestRequest) return;
|
|
@@ -471,16 +491,7 @@ export class AdminRuntime {
|
|
|
471
491
|
this.session = undefined;
|
|
472
492
|
this.authBootstrapAvailable = false;
|
|
473
493
|
}
|
|
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
|
-
}
|
|
494
|
+
await this.#refreshLocalizedAccess();
|
|
484
495
|
await Promise.all([this.loadTheme(), this.i18n.loadPreferences()]);
|
|
485
496
|
} catch (error) {
|
|
486
497
|
if (request !== this.#manifestRequest) return;
|
|
@@ -497,12 +508,7 @@ export class AdminRuntime {
|
|
|
497
508
|
try {
|
|
498
509
|
const manifest = await this.client.schema();
|
|
499
510
|
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;
|
|
511
|
+
const admin = this.#installManifest(manifest);
|
|
506
512
|
if (admin === undefined || this.session?.collection !== admin.userCollectionSlug) {
|
|
507
513
|
this.session = undefined;
|
|
508
514
|
}
|
|
@@ -513,16 +519,7 @@ export class AdminRuntime {
|
|
|
513
519
|
} else {
|
|
514
520
|
this.authBootstrapAvailable = false;
|
|
515
521
|
}
|
|
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
|
-
}
|
|
522
|
+
await this.#refreshLocalizedAccess();
|
|
526
523
|
await this.i18n.loadPreferences();
|
|
527
524
|
} catch (error) {
|
|
528
525
|
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>
|