From 1dba94392786ef049fcf6e4421a3bde09d2de199 Mon Sep 17 00:00:00 2001 From: ahjephson Date: Sat, 17 Aug 2024 15:14:11 +0100 Subject: [PATCH] Ensure keyboard events can be scoped to dialogs only. --- .../Dialogs/AddTorrentLinkDialog.razor | 8 ++-- .../Dialogs/AddTorrentLinkDialog.razor.cs | 47 +++++++++++++++---- .../Components/Dialogs/SubmittableDialog.cs | 2 + .../Components/TorrentActions.razor.cs | 7 ++- Lantean.QBTMudBlade/Helpers/DialogHelper.cs | 8 ++-- Lantean.QBTMudBlade/Program.cs | 2 +- .../Services/IKeyboardService.cs | 2 + .../Services/KeyboardService.cs | 40 +++++++++++++++- Lantean.QBTMudBlade/wwwroot/js/interop.js | 19 +++++--- 9 files changed, 107 insertions(+), 28 deletions(-) diff --git a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor index 913d0ab..edd5185 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor +++ b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor @@ -1,10 +1,10 @@ -@inherits SubmittableDialog - - + - + + + diff --git a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor.cs b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor.cs index 499735e..b5ed575 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor.cs +++ b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor.cs @@ -1,11 +1,21 @@ using Lantean.QBTMudBlade.Models; +using Lantean.QBTMudBlade.Services; using Microsoft.AspNetCore.Components; using MudBlazor; namespace Lantean.QBTMudBlade.Components.Dialogs { - public partial class AddTorrentLinkDialog + public partial class AddTorrentLinkDialog : IAsyncDisposable { + private bool _disposedValue; + private readonly KeyboardEvent _ctrlEnterKey = new KeyboardEvent("Enter") + { + CtrlKey = true, + }; + + [Inject] + protected IKeyboardService KeyboardService { get; set; } = default!; + [CascadingParameter] public MudDialogInstance MudDialog { get; set; } = default!; @@ -15,6 +25,19 @@ namespace Lantean.QBTMudBlade.Components.Dialogs protected AddTorrentOptions TorrentOptions { get; set; } = default!; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await KeyboardService.RegisterKeypressEvent(_ctrlEnterKey, k => + { + Submit(); + return Task.CompletedTask; + }); + await KeyboardService.Focus(); + } + } + protected void Cancel() { MudDialog.Cancel(); @@ -31,19 +54,25 @@ namespace Lantean.QBTMudBlade.Components.Dialogs MudDialog.Close(DialogResult.Ok(options)); } - protected override Task Submit(KeyboardEvent keyboardEvent) + protected virtual async ValueTask DisposeAsync(bool disposing) { - Submit(); + if (!_disposedValue) + { + if (disposing) + { + await KeyboardService.UnregisterKeypressEvent(_ctrlEnterKey); + await KeyboardService.UnFocus(); + } - return Task.CompletedTask; + _disposedValue = true; + } } - protected override async Task OnAfterRenderAsync(bool firstRender) + public async ValueTask DisposeAsync() { - if (firstRender && UrlsTextField is not null) - { - await UrlsTextField.FocusAsync(); - } + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + await DisposeAsync(disposing: true); + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/Dialogs/SubmittableDialog.cs b/Lantean.QBTMudBlade/Components/Dialogs/SubmittableDialog.cs index d00ff93..7334cae 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/SubmittableDialog.cs +++ b/Lantean.QBTMudBlade/Components/Dialogs/SubmittableDialog.cs @@ -16,6 +16,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs if (firstRender) { await KeyboardService.RegisterKeypressEvent("Enter", k => Submit(k)); + await KeyboardService.Focus(); } } @@ -28,6 +29,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs if (disposing) { await KeyboardService.UnregisterKeypressEvent("Enter"); + await KeyboardService.UnFocus(); } _disposedValue = true; diff --git a/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs b/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs index 27c44ab..b1467cd 100644 --- a/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs +++ b/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs @@ -161,9 +161,12 @@ namespace Lantean.QBTMudBlade.Components protected async Task Remove() { - await DialogService.InvokeDeleteTorrentDialog(ApiClient, Hashes.ToArray()); + var deleted = await DialogService.InvokeDeleteTorrentDialog(ApiClient, Hashes.ToArray()); - NavigationManager.NavigateTo("/"); + if (deleted) + { + NavigationManager.NavigateTo("/"); + } } protected async Task SetLocation() diff --git a/Lantean.QBTMudBlade/Helpers/DialogHelper.cs b/Lantean.QBTMudBlade/Helpers/DialogHelper.cs index b2da3a5..7d665f0 100644 --- a/Lantean.QBTMudBlade/Helpers/DialogHelper.cs +++ b/Lantean.QBTMudBlade/Helpers/DialogHelper.cs @@ -95,11 +95,11 @@ namespace Lantean.QBTMudBlade.Helpers options.DownloadFirstAndLastPiecesFirst); } - public static async Task InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes) + public static async Task InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes) { if (hashes.Length == 0) { - return; + return false; } var parameters = new DialogParameters @@ -111,10 +111,12 @@ namespace Lantean.QBTMudBlade.Helpers var dialogResult = await reference.Result; if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null) { - return; + return false; } await apiClient.DeleteTorrents(hashes, (bool)dialogResult.Data); + + return true; } public static async Task InvokeRenameFilesDialog(this IDialogService dialogService, IApiClient apiClient, string hash) diff --git a/Lantean.QBTMudBlade/Program.cs b/Lantean.QBTMudBlade/Program.cs index a250a2d..a6fc983 100644 --- a/Lantean.QBTMudBlade/Program.cs +++ b/Lantean.QBTMudBlade/Program.cs @@ -42,7 +42,7 @@ namespace Lantean.QBTMudBlade builder.Services.AddSingleton(); builder.Services.AddBlazoredLocalStorage(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddTransient(); #if DEBUG builder.Logging.SetMinimumLevel(LogLevel.Information); diff --git a/Lantean.QBTMudBlade/Services/IKeyboardService.cs b/Lantean.QBTMudBlade/Services/IKeyboardService.cs index a0463d1..6b860cb 100644 --- a/Lantean.QBTMudBlade/Services/IKeyboardService.cs +++ b/Lantean.QBTMudBlade/Services/IKeyboardService.cs @@ -4,6 +4,8 @@ namespace Lantean.QBTMudBlade.Services { public interface IKeyboardService { + Task Focus(); + Task UnFocus(); Task RegisterKeypressEvent(KeyboardEvent criteria, Func onKeyPress); Task UnregisterKeypressEvent(KeyboardEvent criteria); diff --git a/Lantean.QBTMudBlade/Services/KeyboardService.cs b/Lantean.QBTMudBlade/Services/KeyboardService.cs index 4b20876..e61488a 100644 --- a/Lantean.QBTMudBlade/Services/KeyboardService.cs +++ b/Lantean.QBTMudBlade/Services/KeyboardService.cs @@ -4,12 +4,12 @@ using System.Collections.Concurrent; namespace Lantean.QBTMudBlade.Services { - public class KeyboardService : IKeyboardService + public class KeyboardService : IKeyboardService, IAsyncDisposable { private readonly IJSRuntime _jSRuntime; private DotNetObjectReference? _dotNetObjectReference; - + private bool _disposedValue; private readonly ConcurrentDictionary> _keyboardHandlers = new(); public KeyboardService(IJSRuntime jSRuntime) @@ -46,5 +46,41 @@ namespace Lantean.QBTMudBlade.Services await _jSRuntime.InvokeVoidAsync("qbt.unregisterKeypressEvent", criteria, GetObjectReference()); _keyboardHandlers.Remove(criteria, out var _); } + + public async Task Focus() + { + await _jSRuntime.InvokeVoidAsync("qbt.keyPressFocusInstance", GetObjectReference()); + } + + public async Task UnFocus() + { + await _jSRuntime.InvokeVoidAsync("qbt.keyPressUnFocusInstance", GetObjectReference()); + } + + protected async virtual ValueTask DisposeAsync(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + await UnFocus(); + foreach (var key in _keyboardHandlers.Keys) + { + await _jSRuntime.InvokeVoidAsync("qbt.unregisterKeypressEvent", key, GetObjectReference()); + } + + _keyboardHandlers.Clear(); + } + + _disposedValue = true; + } + } + + public async ValueTask DisposeAsync() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + await DisposeAsync(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/Lantean.QBTMudBlade/wwwroot/js/interop.js b/Lantean.QBTMudBlade/wwwroot/js/interop.js index 8af37b0..3bdceff 100644 --- a/Lantean.QBTMudBlade/wwwroot/js/interop.js +++ b/Lantean.QBTMudBlade/wwwroot/js/interop.js @@ -103,21 +103,20 @@ window.qbt.clearSelection = () => { } let supportedEvents = new Map(); +let focusInstance = null; document.addEventListener('keyup', event => { const key = getKey(event); - console.log(key); - console.log(event); - const references = supportedEvents.get(key); if (!references) { return; } - console.log(references); - references.forEach(dotNetObjectReference => { + if (focusInstance && dotNetObjectReference !== focusInstance) { + return; + } dotNetObjectReference.invokeMethodAsync('HandleKeyPressEvent', { key: event.key, code: event.code, @@ -143,8 +142,6 @@ window.qbt.registerKeypressEvent = (keyboardEventArgs, dotNetObjectReference) => references.set(dotNetObjectReference._id, dotNetObjectReference); supportedEvents.set(key, references); } - - } window.qbt.unregisterKeypressEvent = (keyboardEventArgs, dotNetObjectReference) => { @@ -158,6 +155,14 @@ window.qbt.unregisterKeypressEvent = (keyboardEventArgs, dotNetObjectReference) references.delete(dotNetObjectReference._id); } +window.qbt.keyPressFocusInstance = dotNetObjectReference => { + focusInstance = dotNetObjectReference; +} + +window.qbt.keyPressUnFocusInstance = dotNetObjectReference => { + focusInstance = null; +} + function getKey(keyboardEvent) { return keyboardEvent.key + (keyboardEvent.ctrlKey ? '1' : '0') + (keyboardEvent.shiftKey ? '1' : '0') + (keyboardEvent.altKey ? '1' : '0') + (keyboardEvent.metaKey ? '1' : '0') + (keyboardEvent.repeat ? '1' : '0'); }