Ensure keyboard events can be scoped to dialogs only.

This commit is contained in:
ahjephson
2024-08-17 15:14:11 +01:00
parent f3aec1cc0b
commit 1dba943927
9 changed files with 107 additions and 28 deletions

View File

@@ -1,10 +1,10 @@
@inherits SubmittableDialog <MudDialog>
<MudDialog>
<DialogContent> <DialogContent>
<MudGrid> <MudGrid>
<MudItem xs="12"> <MudItem xs="12">
<MudTextField @ref="UrlsTextField" Label="Urls" Lines="10" @bind-Value="Urls" Variant="Variant.Outlined" AutoFocus="true" /> <MudFocusTrap>
<MudTextField @ref="UrlsTextField" Label="Urls" Lines="10" @bind-Value="Urls" Variant="Variant.Outlined" AutoFocus="true" />
</MudFocusTrap>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
<AddTorrentOptions @ref="TorrentOptions" ShowCookieOption="false" /> <AddTorrentOptions @ref="TorrentOptions" ShowCookieOption="false" />

View File

@@ -1,11 +1,21 @@
using Lantean.QBTMudBlade.Models; using Lantean.QBTMudBlade.Models;
using Lantean.QBTMudBlade.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using MudBlazor; using MudBlazor;
namespace Lantean.QBTMudBlade.Components.Dialogs 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] [CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!; public MudDialogInstance MudDialog { get; set; } = default!;
@@ -15,6 +25,19 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
protected AddTorrentOptions TorrentOptions { get; set; } = default!; 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() protected void Cancel()
{ {
MudDialog.Cancel(); MudDialog.Cancel();
@@ -31,19 +54,25 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
MudDialog.Close(DialogResult.Ok(options)); 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) // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
{ await DisposeAsync(disposing: true);
await UrlsTextField.FocusAsync(); GC.SuppressFinalize(this);
}
} }
} }
} }

View File

@@ -16,6 +16,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
if (firstRender) if (firstRender)
{ {
await KeyboardService.RegisterKeypressEvent("Enter", k => Submit(k)); await KeyboardService.RegisterKeypressEvent("Enter", k => Submit(k));
await KeyboardService.Focus();
} }
} }
@@ -28,6 +29,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
if (disposing) if (disposing)
{ {
await KeyboardService.UnregisterKeypressEvent("Enter"); await KeyboardService.UnregisterKeypressEvent("Enter");
await KeyboardService.UnFocus();
} }
_disposedValue = true; _disposedValue = true;

View File

@@ -161,9 +161,12 @@ namespace Lantean.QBTMudBlade.Components
protected async Task Remove() 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() protected async Task SetLocation()

View File

@@ -95,11 +95,11 @@ namespace Lantean.QBTMudBlade.Helpers
options.DownloadFirstAndLastPiecesFirst); options.DownloadFirstAndLastPiecesFirst);
} }
public static async Task InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes) public static async Task<bool> InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
{ {
if (hashes.Length == 0) if (hashes.Length == 0)
{ {
return; return false;
} }
var parameters = new DialogParameters var parameters = new DialogParameters
@@ -111,10 +111,12 @@ namespace Lantean.QBTMudBlade.Helpers
var dialogResult = await reference.Result; var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null) if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{ {
return; return false;
} }
await apiClient.DeleteTorrents(hashes, (bool)dialogResult.Data); await apiClient.DeleteTorrents(hashes, (bool)dialogResult.Data);
return true;
} }
public static async Task InvokeRenameFilesDialog(this IDialogService dialogService, IApiClient apiClient, string hash) public static async Task InvokeRenameFilesDialog(this IDialogService dialogService, IApiClient apiClient, string hash)

View File

@@ -42,7 +42,7 @@ namespace Lantean.QBTMudBlade
builder.Services.AddSingleton<IDataManager, DataManager>(); builder.Services.AddSingleton<IDataManager, DataManager>();
builder.Services.AddBlazoredLocalStorage(); builder.Services.AddBlazoredLocalStorage();
builder.Services.AddSingleton<IClipboardService, ClipboardService>(); builder.Services.AddSingleton<IClipboardService, ClipboardService>();
builder.Services.AddSingleton<IKeyboardService, KeyboardService>(); builder.Services.AddTransient<IKeyboardService, KeyboardService>();
#if DEBUG #if DEBUG
builder.Logging.SetMinimumLevel(LogLevel.Information); builder.Logging.SetMinimumLevel(LogLevel.Information);

View File

@@ -4,6 +4,8 @@ namespace Lantean.QBTMudBlade.Services
{ {
public interface IKeyboardService public interface IKeyboardService
{ {
Task Focus();
Task UnFocus();
Task RegisterKeypressEvent(KeyboardEvent criteria, Func<KeyboardEvent, Task> onKeyPress); Task RegisterKeypressEvent(KeyboardEvent criteria, Func<KeyboardEvent, Task> onKeyPress);
Task UnregisterKeypressEvent(KeyboardEvent criteria); Task UnregisterKeypressEvent(KeyboardEvent criteria);

View File

@@ -4,12 +4,12 @@ using System.Collections.Concurrent;
namespace Lantean.QBTMudBlade.Services namespace Lantean.QBTMudBlade.Services
{ {
public class KeyboardService : IKeyboardService public class KeyboardService : IKeyboardService, IAsyncDisposable
{ {
private readonly IJSRuntime _jSRuntime; private readonly IJSRuntime _jSRuntime;
private DotNetObjectReference<KeyboardService>? _dotNetObjectReference; private DotNetObjectReference<KeyboardService>? _dotNetObjectReference;
private bool _disposedValue;
private readonly ConcurrentDictionary<string, Func<KeyboardEvent, Task>> _keyboardHandlers = new(); private readonly ConcurrentDictionary<string, Func<KeyboardEvent, Task>> _keyboardHandlers = new();
public KeyboardService(IJSRuntime jSRuntime) public KeyboardService(IJSRuntime jSRuntime)
@@ -46,5 +46,41 @@ namespace Lantean.QBTMudBlade.Services
await _jSRuntime.InvokeVoidAsync("qbt.unregisterKeypressEvent", criteria, GetObjectReference()); await _jSRuntime.InvokeVoidAsync("qbt.unregisterKeypressEvent", criteria, GetObjectReference());
_keyboardHandlers.Remove(criteria, out var _); _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);
}
} }
} }

View File

@@ -103,21 +103,20 @@ window.qbt.clearSelection = () => {
} }
let supportedEvents = new Map(); let supportedEvents = new Map();
let focusInstance = null;
document.addEventListener('keyup', event => { document.addEventListener('keyup', event => {
const key = getKey(event); const key = getKey(event);
console.log(key);
console.log(event);
const references = supportedEvents.get(key); const references = supportedEvents.get(key);
if (!references) { if (!references) {
return; return;
} }
console.log(references);
references.forEach(dotNetObjectReference => { references.forEach(dotNetObjectReference => {
if (focusInstance && dotNetObjectReference !== focusInstance) {
return;
}
dotNetObjectReference.invokeMethodAsync('HandleKeyPressEvent', { dotNetObjectReference.invokeMethodAsync('HandleKeyPressEvent', {
key: event.key, key: event.key,
code: event.code, code: event.code,
@@ -143,8 +142,6 @@ window.qbt.registerKeypressEvent = (keyboardEventArgs, dotNetObjectReference) =>
references.set(dotNetObjectReference._id, dotNetObjectReference); references.set(dotNetObjectReference._id, dotNetObjectReference);
supportedEvents.set(key, references); supportedEvents.set(key, references);
} }
} }
window.qbt.unregisterKeypressEvent = (keyboardEventArgs, dotNetObjectReference) => { window.qbt.unregisterKeypressEvent = (keyboardEventArgs, dotNetObjectReference) => {
@@ -158,6 +155,14 @@ window.qbt.unregisterKeypressEvent = (keyboardEventArgs, dotNetObjectReference)
references.delete(dotNetObjectReference._id); references.delete(dotNetObjectReference._id);
} }
window.qbt.keyPressFocusInstance = dotNetObjectReference => {
focusInstance = dotNetObjectReference;
}
window.qbt.keyPressUnFocusInstance = dotNetObjectReference => {
focusInstance = null;
}
function getKey(keyboardEvent) { 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'); return keyboardEvent.key + (keyboardEvent.ctrlKey ? '1' : '0') + (keyboardEvent.shiftKey ? '1' : '0') + (keyboardEvent.altKey ? '1' : '0') + (keyboardEvent.metaKey ? '1' : '0') + (keyboardEvent.repeat ? '1' : '0');
} }