From 703dac9f34a93cf244675a453fe8e49e6a1d7482 Mon Sep 17 00:00:00 2001 From: Jake Mannens Date: Mon, 22 Jun 2026 00:25:55 +1000 Subject: Added LinkAction Razor component --- Pages/Component/LinkAction.razor | 102 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Pages/Component/LinkAction.razor (limited to 'Pages/Component') diff --git a/Pages/Component/LinkAction.razor b/Pages/Component/LinkAction.razor new file mode 100644 index 0000000..ee6d189 --- /dev/null +++ b/Pages/Component/LinkAction.razor @@ -0,0 +1,102 @@ +@using System.Timers +@implements IDisposable +@inject IJSRuntime js + + + + @if(errorTimer.Enabled) { + @("Error!") + } else if(isLoading) { + @(LoadingText + "...") + } else { + @Text + } + + +@code { + [Parameter] + public Func OnClick { get; set; } + + [Parameter] + public string Text{ get; set; } + + [Parameter] + public string LoadingText { get; set; } + + private Dictionary Attributes => new() { + ["disabled"] = isLoading, + }; + + private Timer loadingTimer; + private Timer errorTimer; + + private object triggerLock = new(); + private bool isRunning = false; + private bool isLoading = false; + + protected override void OnInitialized() { + loadingTimer = new(50) { + AutoReset = false + }; + loadingTimer.Elapsed += LoadingTimerElapsed; + + errorTimer = new(1000) { + AutoReset = false + }; + errorTimer.Elapsed += ErrorTimerElapsed; + } + + // Most link-click actions (e.g. triggering dialogs, page + // navigation, etc) complete very quickly (under 50ms). + // Triggering a render cycle to show a loading animation is + // usually pointless as doing so would waste resources and + // appear less seemless to the user who will see brief + // flickering while the browser quickly recalculates it's + // layout twice. To that end, a timer is used to only render + // the loading animation on the link if the delegate task + // takes longer than 50ms + private void LoadingTimerElapsed(object? sender, EventArgs e) { + lock(triggerLock) { + if(isRunning) + isLoading = true; + InvokeAsync(() => StateHasChanged()); + } + } + + private void ErrorTimerElapsed(object? sender, EventArgs e) => + InvokeAsync(() => StateHasChanged()); + + private async Task Trigger() { + lock(triggerLock) { + if(isRunning) + return; + isRunning = true; + loadingTimer.Start(); + } + + bool error = false; + + try { + await Task.Run(OnClick); + } catch { + error = true; + } finally { + lock(triggerLock) { + loadingTimer.Stop(); + isRunning = false; + isLoading = false; + } + } + + if(error) + errorTimer.Start(); + + await InvokeAsync(() => StateHasChanged()); + } + + public void Dispose() => + loadingTimer.Dispose(); +} -- cgit v1.3