diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor index a88862c1..71136240 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor @@ -1,28 +1,55 @@ @inherits LayoutComponentBase +@using Microsoft.AspNetCore.Components.Routing -@* Layout chrome ported from ScadaLink CentralUI: no separate top bar — brand sits - at the top of the side rail. The sidebar itself is the interactive island - (); MainLayout stays statically rendered so the Body RenderFragment - doesn't have to cross an interactive boundary. *@ +@* Thin delegation to the shared ZB.MOM.WW.Theme side-rail chassis. ThemeShell owns + the brand bar, the CSS-only narrow-viewport hamburger, and the responsive collapse, + so MainLayout no longer carries its own .app-shell / hamburger wrapper. Nav sections + are static
(NavRailSection) whose expand state is persisted to localStorage + by the kit's (emitted in App.razor) — replacing the old interactive + NavSidebar island + cookie/URL auto-expand. *@ -
- @* Hamburger toggle: visible only on viewports
+ + + + + +
Session
+ @context.User.Identity?.Name +
+ @string.Join(", ", context.User.Claims + .Where(c => c.Type.EndsWith("/role")).Select(c => c.Value)) +
+
+ + + +
+ +
Session
+ Sign in +
+
+
+ @Body +
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSection.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSection.razor deleted file mode 100644 index 5c697c75..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSection.razor +++ /dev/null @@ -1,36 +0,0 @@ -@* A collapsible sidebar nav section: an uppercase-eyebrow button that toggles - the visibility of its child nav items. Mirrors the ScadaLink NavSection at - /Users/dohertj2/Desktop/scadalink-design/src/ScadaLink.CentralUI/Components/Layout/NavSection.razor - but uses OtOpcUa's rail-eyebrow + rail-link classes. *@ - - -@if (Expanded) -{ -
- @ChildContent -
-} - -@code { - /// Section label shown in the eyebrow (e.g. "Scripting"). - [Parameter, EditorRequired] - public string Title { get; set; } = string.Empty; - - /// Whether the section is expanded — its child links rendered. - [Parameter] - public bool Expanded { get; set; } - - /// Raised when the eyebrow button is clicked. - [Parameter] - public EventCallback OnToggle { get; set; } - - /// The section's child nav links, rendered only while expanded. - [Parameter] - public RenderFragment? ChildContent { get; set; } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSidebar.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSidebar.razor deleted file mode 100644 index 28f9f77d..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSidebar.razor +++ /dev/null @@ -1,160 +0,0 @@ -@rendermode InteractiveServer -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.JSInterop -@implements IDisposable -@inject NavigationManager Navigation -@inject IJSRuntime JS - -@* Interactive sidebar — extracted from MainLayout so the layout itself can stay - statically rendered (layouts can't take RenderFragment Body across an interactive - boundary). Hosts the collapsible NavSection groups and cookie persistence. *@ - - - -@code { - // Expanded-section state persists in the `otopcua_nav` cookie via - // wwwroot/js/nav-state.js (window.navState.get/.set). Same pattern as - // ScadaLink CentralUI's NavMenu. - - private static readonly string[] SectionIds = { "nav", "scripting", "live" }; - - private readonly HashSet _expanded = new(StringComparer.Ordinal); - - protected override void OnInitialized() - { - Navigation.LocationChanged += OnLocationChanged; - // Seed from the URL so the current page's section is expanded on the - // initial render — works even before JS interop is ready. - EnsureCurrentSectionExpanded(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender) return; - - string saved; - try - { - saved = await JS.InvokeAsync("navState.get") ?? string.Empty; - } - catch (JSDisconnectedException) { return; } - catch (InvalidOperationException) { return; } - - foreach (var id in saved.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) - { - if (Array.IndexOf(SectionIds, id) >= 0) - _expanded.Add(id); - } - - if (EnsureCurrentSectionExpanded()) - await PersistAsync(); - - StateHasChanged(); - } - - private void OnLocationChanged(object? sender, LocationChangedEventArgs e) - { - if (EnsureCurrentSectionExpanded()) - { - _ = PersistAsync(); - _ = InvokeAsync(StateHasChanged); - } - } - - private async Task ToggleAsync(string id) - { - if (!_expanded.Remove(id)) - _expanded.Add(id); - await PersistAsync(); - } - - private bool EnsureCurrentSectionExpanded() - { - var section = CurrentSection(); - return section is not null && _expanded.Add(section); - } - - private string? CurrentSection() - { - var relative = Navigation.ToBaseRelativePath(Navigation.Uri); - var firstSegment = relative.Split('?', '#')[0] - .Split('/', StringSplitOptions.RemoveEmptyEntries) - .FirstOrDefault(); - - return firstSegment switch - { - null or "" => "nav", - "fleet" or "hosts" or "clusters" or "reservations" or "certificates" or "role-grants" => "nav", - "virtual-tags" or "scripted-alarms" or "scripts" or "script-log" => "scripting", - "deployments" or "alerts" or "alarms-historian" => "live", - _ => null, - }; - } - - private async Task PersistAsync() - { - try - { - await JS.InvokeVoidAsync("navState.set", string.Join(',', _expanded)); - } - catch (JSDisconnectedException) { } - catch (InvalidOperationException) { } - } - - public void Dispose() - { - Navigation.LocationChanged -= OnLocationChanged; - } -}