@using RackPeek.Domain @using Shared.Rcl.Components @inherits LayoutComponentBase @implements IAsyncDisposable @inject NavigationManager Nav @inject IJSRuntime JS
rackpeek @RpkConstants.Version
@if (_dropdownOpen) { }
@if (RpkConstants.HasGitServices) {
}
@Body
@code { private const string _outsideDismissId = "rpk-mobile-nav"; private const string _containerSelector = "#rpk-mobile-nav"; private bool _dropdownOpen; private bool _listenerRegistered; private DotNetObjectReference? _selfRef; protected override void OnInitialized() { _selfRef = DotNetObjectReference.Create(this); Nav.LocationChanged += HandleLocationChanged; } private void HandleLocationChanged(object? sender, LocationChangedEventArgs e) => InvokeAsync(StateHasChanged); private void ToggleDropdown() => _dropdownOpen = !_dropdownOpen; private void CloseDropdown() => _dropdownOpen = false; [JSInvokable] public Task DismissDropdownFromJs() { if (!_dropdownOpen) return Task.CompletedTask; _dropdownOpen = false; return InvokeAsync(StateHasChanged); } protected override async Task OnAfterRenderAsync(bool firstRender) { // Mirror the dropdown state into the JS dismiss-listener registration // so the listener only exists while it's needed. if (_dropdownOpen && !_listenerRegistered) { await JS.InvokeVoidAsync( "rackpeekUi.registerOutsideDismiss", _outsideDismissId, _containerSelector, _selfRef, nameof(DismissDropdownFromJs)); _listenerRegistered = true; } else if (!_dropdownOpen && _listenerRegistered) { await JS.InvokeVoidAsync("rackpeekUi.unregisterOutsideDismiss", _outsideDismissId); _listenerRegistered = false; } } public async ValueTask DisposeAsync() { Nav.LocationChanged -= HandleLocationChanged; if (_listenerRegistered) { try { await JS.InvokeVoidAsync("rackpeekUi.unregisterOutsideDismiss", _outsideDismissId); } catch { /* page unloading */ } } _selfRef?.Dispose(); } private string CurrentPageLabel { get { var path = Nav.ToBaseRelativePath(Nav.Uri).Split('?')[0].Split('#')[0].Trim('/'); NavItem? best = null; var bestLength = -1; foreach (NavItem item in NavItems) { if (!Matches(path, item)) continue; if (item.Href.Length > bestLength) { best = item; bestLength = item.Href.Length; } } return best?.Label ?? "Menu"; } } private static bool Matches(string path, NavItem item) { if (item.Match == NavLinkMatch.All) return string.Equals(path, item.Href, StringComparison.OrdinalIgnoreCase); // Prefix match — but never let the empty-string "Home" entry win as // a prefix of every route. if (string.IsNullOrEmpty(item.Href)) return false; return path.Equals(item.Href, StringComparison.OrdinalIgnoreCase) || path.StartsWith(item.Href + "/", StringComparison.OrdinalIgnoreCase); } private static readonly NavItem[] NavItems = { new("", "home", "Home", NavLinkMatch.All), new("cli", "cli", "CLI"), new("yaml", "yaml", "Yaml"), new("hardware/tree", "hardware", "Hardware"), new("systems/list", "systems", "Systems"), new("services/list", "services", "Services"), new("visualise", "visualise", "Visualise"), new("docs", "docs", "Docs") }; private sealed record NavItem(string Href, string TestId, string Label, NavLinkMatch Match = NavLinkMatch.Prefix); }