@page "/reader" @page "/reader/{BookId:guid}" @layout ReaderLayout @attribute [Authorize] @using NexusReader.UI.Shared.Services @implements IAsyncDisposable @inject IQuizStateService QuizState @inject IFocusModeService FocusMode @inject IJSRuntime JS @inject NavigationManager NavManager @inject IReaderNavigationService NavService @inject IIdentityService IdentityService Nexus E-Reader
@code { [Parameter] public Guid? BookId { get; set; } private ReaderCanvas? readerCanvas; private string? _activeQuizBlockId; private IJSObjectReference? _interopModule; private IJSObjectReference? _keydownHandler; private DotNetObjectReference? _dotNetRef; protected override void OnInitialized() { QuizState.OnQuizRequested += HandleQuizRequestedAsync; FocusMode.OnFocusModeChanged += HandleUpdate; } protected override async Task OnParametersSetAsync() { var uri = NavManager.ToAbsoluteUri(NavManager.Uri); int chapterIndex = 0; if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("chapter", out var chapterValue)) { int.TryParse(chapterValue, out chapterIndex); } if (BookId.HasValue && BookId.Value != Guid.Empty) { if (NavService.CurrentEbookId != BookId.Value || NavService.CurrentChapterIndex != chapterIndex) { NavService.SetBook(BookId.Value, chapterIndex); } } else if (NavService.CurrentEbookId == Guid.Empty) { // If no BookId in URL and no book currently selected, try to load last read book var profileResult = await IdentityService.GetProfileAsync(); if (profileResult.IsSuccess && profileResult.Value.LastReadBook != null) { NavService.SetBook(profileResult.Value.LastReadBook.Id, chapterIndex > 0 ? chapterIndex : profileResult.Value.LastReadBook.LastChapterIndex); } } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await FocusMode.InitializeAsync(); try { _interopModule = await JS.InvokeAsync("import", "./_content/NexusReader.UI.Shared/js/focusInterop.js"); _dotNetRef = DotNetObjectReference.Create(this); _keydownHandler = await _interopModule.InvokeAsync("attachKeyboardListener", _dotNetRef); } catch { } /* ignored dynamically */ StateHasChanged(); } } [JSInvokable] public async Task OnFocusKeypressed() { await FocusMode.ToggleAsync(); StateHasChanged(); } private async Task HandleNodeSelected(string nodeId) { if (readerCanvas != null) { await readerCanvas.ScrollToNodeAsync(nodeId); } } private async Task HandleQuizRequestedAsync(string blockId) { _activeQuizBlockId = blockId; await InvokeAsync(StateHasChanged); } private Task HandleUpdate() => InvokeAsync(StateHasChanged); public async ValueTask DisposeAsync() { QuizState.OnQuizRequested -= HandleQuizRequestedAsync; FocusMode.OnFocusModeChanged -= HandleUpdate; if (_interopModule != null && _keydownHandler != null) { try { await _interopModule.InvokeVoidAsync("detachKeyboardListener", _keydownHandler); await _interopModule.DisposeAsync(); await _keydownHandler.DisposeAsync(); } catch { } // Circuit disconnected catch explicitly } _dotNetRef?.Dispose(); } }