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>
<MudGrid>
<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>
</MudGrid>
<AddTorrentOptions @ref="TorrentOptions" ShowCookieOption="false" />

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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()

View File

@@ -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<bool> 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)

View File

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

View File

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

View File

@@ -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<KeyboardService>? _dotNetObjectReference;
private bool _disposedValue;
private readonly ConcurrentDictionary<string, Func<KeyboardEvent, Task>> _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);
}
}
}

View File

@@ -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');
}