From f1a5597380a80395b743b2022c18251babb0e9d5 Mon Sep 17 00:00:00 2001 From: Jake Mannens Date: Thu, 4 Jun 2026 13:59:07 +1000 Subject: Added Razor components for loadable remote content --- Pages/Component/ErrorState.razor | 23 ++++++++++++++ Pages/Component/LoadableContent.razor | 59 +++++++++++++++++++++++++++++++++++ Pages/Component/LoadedState.razor | 23 ++++++++++++++ Pages/Component/LoadingState.razor | 23 ++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 Pages/Component/ErrorState.razor create mode 100644 Pages/Component/LoadableContent.razor create mode 100644 Pages/Component/LoadedState.razor create mode 100644 Pages/Component/LoadingState.razor diff --git a/Pages/Component/ErrorState.razor b/Pages/Component/ErrorState.razor new file mode 100644 index 0000000..92baac6 --- /dev/null +++ b/Pages/Component/ErrorState.razor @@ -0,0 +1,23 @@ +@implements IDisposable +@typeparam T + +@if(Parent.ComponentState == ComponentState.Error) { + @ChildContent +} + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [CascadingParameter] + public LoadableContent Parent { get; set; } + + protected override void OnInitialized() => + Parent.OnStateChanged += OnStateChanged; + + public void OnStateChanged(object? sender, EventArgs e) => + InvokeAsync(StateHasChanged); + + public void Dispose() => + Parent.OnStateChanged -= OnStateChanged; +} diff --git a/Pages/Component/LoadableContent.razor b/Pages/Component/LoadableContent.razor new file mode 100644 index 0000000..570317f --- /dev/null +++ b/Pages/Component/LoadableContent.razor @@ -0,0 +1,59 @@ +@attribute [CascadingTypeParameter(nameof(T))] +@typeparam T + + + @ChildContent + + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public required Func> DataSource { private get; set; } + + public T Data { get; private set; } + + public ComponentState ComponentState { get; private set; } = ComponentState.Loading; + + public event EventHandler? OnStateChanged; + + private bool reloadRequested = false; + private Task? loadTask = null; + private object loadLock = new(); + + protected override void OnInitialized() => Load(); + + public void Load() { + lock(loadLock) { + reloadRequested = true; + if(loadTask is null || loadTask.IsCompleted) { + //loadTask = Task.Run(LoadInternalAsync); + loadTask = LoadInternalAsync(); + } + } + } + + private async Task LoadInternalAsync() { + while(true) { + lock(loadLock) { + if(!reloadRequested) + break; + reloadRequested = false; + } + + //ComponentState = ComponentState.Loading; + //await InvokeAsync(() => OnStateChanged?.Invoke(this, EventArgs.Empty)); + //await InvokeAsync(() => StateHasChanged()); + try { + Data = await DataSource.Invoke(); + ComponentState = ComponentState.Loaded; + } catch { + ComponentState = ComponentState.Error; + } + + await InvokeAsync(() => OnStateChanged?.Invoke(this, EventArgs.Empty)); + await InvokeAsync(() => StateHasChanged()); + } + } +} diff --git a/Pages/Component/LoadedState.razor b/Pages/Component/LoadedState.razor new file mode 100644 index 0000000..fce18d8 --- /dev/null +++ b/Pages/Component/LoadedState.razor @@ -0,0 +1,23 @@ +@implements IDisposable +@typeparam T + +@if(Parent.ComponentState == ComponentState.Loaded) { + @ChildContent +} + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [CascadingParameter] + public LoadableContent Parent { get; set; } + + protected override void OnInitialized() => + Parent.OnStateChanged += OnStateChanged; + + public void OnStateChanged(object? sender, EventArgs e) => + InvokeAsync(StateHasChanged); + + public void Dispose() => + Parent.OnStateChanged -= OnStateChanged; +} diff --git a/Pages/Component/LoadingState.razor b/Pages/Component/LoadingState.razor new file mode 100644 index 0000000..0a7cffc --- /dev/null +++ b/Pages/Component/LoadingState.razor @@ -0,0 +1,23 @@ +@implements IDisposable +@typeparam T + +@if(Parent.ComponentState == ComponentState.Loading) { + @ChildContent +} + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [CascadingParameter] + public LoadableContent Parent { get; set; } + + protected override void OnInitialized() => + Parent.OnStateChanged += OnStateChanged; + + public void OnStateChanged(object? sender, EventArgs e) => + InvokeAsync(StateHasChanged); + + public void Dispose() => + Parent.OnStateChanged -= OnStateChanged; +} -- cgit v1.3