diff --git a/admin/src/components/shell/AppShell.vue b/admin/src/components/shell/AppShell.vue index 444ff833..c573f4ba 100644 --- a/admin/src/components/shell/AppShell.vue +++ b/admin/src/components/shell/AppShell.vue @@ -3,16 +3,20 @@ import { computed, onBeforeUnmount } from 'vue' import { useRoute } from 'vue-router' import PluginRail from './PluginRail.vue' import SectionPanel from './SectionPanel.vue' +import SettingsPanel from './SettingsPanel.vue' import Breadcrumbs from './Breadcrumbs.vue' import UserMenu from './UserMenu.vue' import Toast from '../ui/Toast.vue' import { ExternalLink } from '@lucide/vue' +import { controllerIdFromPath } from '../../app/controllerRoutes' import { activeEntry } from '../../state/useNavigation' +import { settingsEntry, settingsLinkFor, settingsSectionOf } from '../../state/useSettings' import { useSidebar } from '../../state/useSidebar' import { t } from '../../app/i18n' // Navigation shell (design: Navigation shell and Top header; D-06, D-10): -// the plugin rail, the active plugin's section panel (hidden when the +// the plugin rail, the section panel (the active plugin's side menu or, on a +// settings page, the settings panel of its category; hidden when the // sidebar is collapsed by the viewport or by the admin), and the 64px header // with breadcrumbs and the user menu. The toast queue is mounted here so // toasts survive navigation. @@ -28,11 +32,27 @@ const siteURL = `${window.location.origin}/` const active = computed(() => activeEntry(String(route.params.vendor ?? ''), String(route.params.plugin ?? ''))) +// The settings panel shows on a singleton settings form and on a controller a +// settings link entry opens, unless that controller's plugin has a navigation +// entry of its own (it keeps its plugin panel, as the rail's Settings rule +// does). The /settings index has no controller ID and resolves to null. +const settingsSection = computed(() => { + if (active.value) { + return null + } + if (route.name === 'settings-form') { + return settingsSectionOf(settingsEntry(String(route.params.code ?? ''))) + } + return settingsSectionOf(settingsLinkFor(controllerIdFromPath(route.path))) +}) + // --shell-offset is the width the sidebar occupies (the rail, plus the // section panel when it shows). Fixed bottom save bars start there, so the // rail's Settings item is never covered. const shellOffset = computed(() => - active.value && !collapsed.value ? 'calc(var(--spacing-rail) + var(--spacing-panel))' : 'var(--spacing-rail)', + (active.value || settingsSection.value) && !collapsed.value + ? 'calc(var(--spacing-rail) + var(--spacing-panel))' + : 'var(--spacing-rail)', ) @@ -40,6 +60,7 @@ const shellOffset = computed(() =>