@using System.Linq @using ScadaLink.Security @using Microsoft.AspNetCore.Components.Routing @using Microsoft.JSInterop @implements IDisposable @inject NavigationManager Navigation @inject IJSRuntime JS @code { // Expanded-section state persists in the "scadabridge_nav" cookie, written // by navState.set / read by navState.get (wwwroot/js/nav-state.js) — a // comma-separated list of section ids. // Every collapsible section id. Also the allow-list for parsing the cookie. private static readonly string[] SectionIds = { "admin", "design", "deployment", "notifications", "sitecalls", "monitoring", "audit" }; // The currently-expanded sections. Populated from the cookie on first // render; mutated by ToggleAsync and by navigating into a section. private readonly HashSet _expanded = new(StringComparer.Ordinal); protected override void OnInitialized() { Navigation.LocationChanged += OnLocationChanged; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) { return; } // Hydrate from the cookie. Until this completes the sidebar paints // collapsed (the "collapsed by default" state) — matching how TreeView // hydrates its expand state in OnAfterRenderAsync(firstRender). string saved; try { saved = await JS.InvokeAsync("navState.get") ?? string.Empty; } catch (JSDisconnectedException) { return; } foreach (var id in saved.Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { if (Array.IndexOf(SectionIds, id) >= 0) { _expanded.Add(id); } } // The section of the page we loaded on is always expanded. if (EnsureCurrentSectionExpanded()) { await PersistAsync(); } StateHasChanged(); } private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { // Navigating into a collapsed section expands it (and remembers it). if (EnsureCurrentSectionExpanded()) { _ = PersistAsync(); _ = InvokeAsync(StateHasChanged); } } private async Task ToggleAsync(string id) { if (!_expanded.Remove(id)) { _expanded.Add(id); } await PersistAsync(); } // Adds the current page's section to _expanded; returns true if it changed. private bool EnsureCurrentSectionExpanded() { var section = CurrentSection(); return section is not null && _expanded.Add(section); } // Maps the current URL's first path segment to a section id, or null for // sectionless pages (Dashboard, Login). private string? CurrentSection() { var relative = Navigation.ToBaseRelativePath(Navigation.Uri); var firstSegment = relative.Split('?', '#')[0] .Split('/', StringSplitOptions.RemoveEmptyEntries) .FirstOrDefault(); return firstSegment switch { "admin" => "admin", "design" => "design", "deployment" => "deployment", "notifications" => "notifications", "site-calls" => "sitecalls", "monitoring" => "monitoring", "audit" => "audit", _ => null, }; } private async Task PersistAsync() { try { await JS.InvokeVoidAsync("navState.set", string.Join(',', _expanded)); } catch (JSDisconnectedException) { // The circuit is gone — nothing to persist to. } } public void Dispose() { Navigation.LocationChanged -= OnLocationChanged; } }