@rendermode InteractiveAuto @using System.Linq @using Prefab.Web.Client.Models.Shared @using Prefab.Web.Client.Services @implements IDisposable @code { private const string ProductsMenuCacheKey = "nav-menu/products"; [Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private INavMenuService NavMenuService { get; set; } = default!; [Inject] private IServiceProvider ServiceProvider { get; set; } = default!; private bool _productsMenuOpen; private bool _productsFlyoutOpen; private ElementReference _panel; private NavMenuModel? _menu; private bool _menuLoaded; private string? _errorMessage; private IDisposable? _persistRegistration; private CancellationTokenSource? _loadCancellationSource; private string ProductsMenuItemClass => $"main-nav__item main-nav__item--with--menu {(_productsMenuOpen ? "main-nav__item--open" : string.Empty)}"; private string ProductsFlyoutItemClass => $"main-nav__item main-nav__item--with--menu {(_productsFlyoutOpen ? "main-nav__item--open" : string.Empty)}"; private PersistentComponentState? AppState => ServiceProvider.GetService(); protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; var appState = AppState; if (appState is not null) { _persistRegistration = appState.RegisterOnPersisting(PersistAsync); } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender || _menuLoaded) { return; } if (AppState is not null && AppState.TryTakeFromJson(ProductsMenuCacheKey, out NavMenuModel? cached) && cached is not null) { _menu = cached; } else { await LoadAsync(forceReload: true); } _menuLoaded = true; StateHasChanged(); } private async Task ToggleProductsMenuAsync() { if (_productsMenuOpen) { await CloseAsync(); return; } await OpenAsync(focusPanel: true); } private Task OnProductsMenuLinkClick(MouseEventArgs _) => ToggleProductsMenuAsync(); private Task HandleMouseEnter() => OpenAsync(focusPanel: false); private Task HandleMouseLeave() => CloseAsync(); private async Task HandleFlyoutMouseEnter() { await LoadAsync(forceReload: false); _productsFlyoutOpen = true; StateHasChanged(); } private Task HandleFlyoutMouseLeave() { if (!_productsFlyoutOpen) { return Task.CompletedTask; } _productsFlyoutOpen = false; StateHasChanged(); return Task.CompletedTask; } private Task OnProductsFlyoutLinkClick(MouseEventArgs _) { if (_productsFlyoutOpen) { _productsFlyoutOpen = false; StateHasChanged(); return Task.CompletedTask; } return HandleFlyoutMouseEnter(); } private async Task OpenAsync(bool focusPanel) { if (_productsMenuOpen) { return; } await LoadAsync(forceReload: false); _productsMenuOpen = true; StateHasChanged(); if (focusPanel) { await Task.Yield(); await _panel.FocusAsync(); } } private Task CloseAsync() { if (!_productsMenuOpen) { return Task.CompletedTask; } _productsMenuOpen = false; StateHasChanged(); return Task.CompletedTask; } private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { _ = InvokeAsync(async () => { await CloseAsync(); _productsFlyoutOpen = false; await LoadAsync(forceReload: true); StateHasChanged(); }); } private async Task LoadAsync(bool forceReload) { if (!forceReload && _menu is not null) { return; } _loadCancellationSource?.Cancel(); _loadCancellationSource?.Dispose(); _loadCancellationSource = new CancellationTokenSource(); var token = _loadCancellationSource.Token; try { var result = await NavMenuService.Get(2, token); if (result.IsSuccess && result.Value is { } model) { _menu = model; _errorMessage = null; } else { _menu = null; _errorMessage = result.Problem?.Detail ?? "Navigation unavailable."; } } catch (OperationCanceledException) { // Swallow expected cancellation when a new request supersedes the current one. } catch (Exception exception) { _menu = null; _errorMessage = exception.Message; } } private Task PersistAsync() { var appState = AppState; if (_menu is not null && appState is not null) { appState.PersistAsJson(ProductsMenuCacheKey, _menu); } return Task.CompletedTask; } public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; _persistRegistration?.Dispose(); _loadCancellationSource?.Cancel(); _loadCancellationSource?.Dispose(); } private IEnumerable GetFlyoutColumns() { if (_menu is null || _menu.Columns.Count == 0) { return Array.Empty(); } return _menu.Columns; } }