[frontend/components] Make virtual scroller gracefully handle no content
This commit is contained in:
parent
13495a692a
commit
652df49326
4 changed files with 49 additions and 19 deletions
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.Routing;
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Iceshrimp.Frontend.Components;
|
namespace Iceshrimp.Frontend.Components;
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
[Inject] private NavigationManager Navigation { get; set; } = null!;
|
[Inject] private NavigationManager Navigation { get; set; } = null!;
|
||||||
[Inject] private ILogger<NewVirtualScroller> Logger { get; set; } = null!;
|
[Inject] private ILogger<NewVirtualScroller> Logger { get; set; } = null!;
|
||||||
[Inject] private IJSRuntime Js { get; set; } = null!;
|
[Inject] private IJSRuntime Js { get; set; } = null!;
|
||||||
|
[Inject] private IStringLocalizer<Iceshrimp.Frontend.Localization.Localization> Loc { get; set; } = null!;
|
||||||
|
|
||||||
[Parameter] [EditorRequired] public required RenderFragment<T> ItemTemplate { get; set; } = default!;
|
[Parameter] [EditorRequired] public required RenderFragment<T> ItemTemplate { get; set; } = default!;
|
||||||
[Parameter] [EditorRequired] public required IReadOnlyList<T> InitialItems { get; set; } = default!;
|
[Parameter] [EditorRequired] public required IReadOnlyList<T> InitialItems { get; set; } = default!;
|
||||||
|
@ -41,6 +43,7 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
private bool _shouldRender = false;
|
private bool _shouldRender = false;
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private bool _hideBefore = true;
|
private bool _hideBefore = true;
|
||||||
|
private bool _hideAfter = true;
|
||||||
|
|
||||||
private IDisposable? _locationChangeHandlerDisposable;
|
private IDisposable? _locationChangeHandlerDisposable;
|
||||||
|
|
||||||
|
@ -120,7 +123,17 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
builder.CloseRegion();
|
builder.CloseRegion();
|
||||||
|
|
||||||
|
if (Items.Count == 0)
|
||||||
|
{
|
||||||
builder.OpenRegion(2);
|
builder.OpenRegion(2);
|
||||||
|
builder.OpenElement(1, "div");
|
||||||
|
builder.AddAttribute(2, "class", "placeholder");
|
||||||
|
builder.AddContent(3,@Loc["Nothing here, yet!"]);
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.CloseRegion();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.OpenRegion(3);
|
||||||
foreach (var item in Items)
|
foreach (var item in Items)
|
||||||
{
|
{
|
||||||
builder.OpenElement(2, "div");
|
builder.OpenElement(2, "div");
|
||||||
|
@ -147,13 +160,14 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
|
|
||||||
builder.CloseRegion();
|
builder.CloseRegion();
|
||||||
|
|
||||||
builder.OpenRegion(3);
|
builder.OpenRegion(4);
|
||||||
builder.OpenComponent<ScrollEnd>(1);
|
builder.OpenComponent<ScrollEnd>(1);
|
||||||
builder.AddComponentParameter(2, "IntersectionChange", new EventCallback(this, CallbackAfterAsync));
|
builder.AddComponentParameter(2, "IntersectionChange", new EventCallback(this, CallbackAfterAsync));
|
||||||
builder.AddComponentParameter(3, "ManualLoad", new EventCallback(this, CallbackAfterAsync));
|
builder.AddComponentParameter(3, "ManualLoad", new EventCallback(this, CallbackAfterAsync));
|
||||||
builder.AddComponentParameter(4, "RequireReset", true);
|
builder.AddComponentParameter(4, "RequireReset", true);
|
||||||
builder.AddComponentParameter(5, "Class", "virtual-scroller-button");
|
builder.AddComponentParameter(5, "Class", "virtual-scroller-button");
|
||||||
builder.AddComponentReferenceCapture(6,
|
builder.AddComponentParameter(6, "Hide", _hideAfter);
|
||||||
|
builder.AddComponentReferenceCapture(7,
|
||||||
reference =>
|
reference =>
|
||||||
After = reference as ScrollEnd
|
After = reference as ScrollEnd
|
||||||
?? throw new InvalidOperationException());
|
?? throw new InvalidOperationException());
|
||||||
|
@ -163,7 +177,8 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
|
|
||||||
private async Task CallbackBeforeAsync()
|
private async Task CallbackBeforeAsync()
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
// Cannot call item provider when there are no existing items.
|
||||||
|
if (Items.Count == 0 || !_initialized)
|
||||||
{
|
{
|
||||||
Before.Reset();
|
Before.Reset();
|
||||||
return;
|
return;
|
||||||
|
@ -181,6 +196,7 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
ReRender();
|
ReRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = await resTask;
|
var res = await resTask;
|
||||||
if (res is not null && res.Count > 0)
|
if (res is not null && res.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -220,13 +236,26 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
|
||||||
|
|
||||||
private async Task CallbackAfterAsync()
|
private async Task CallbackAfterAsync()
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
// Cannot call item provider when there are no existing items.
|
||||||
|
if (Items.Count == 0 || !_initialized)
|
||||||
{
|
{
|
||||||
After.Reset();
|
After.Reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = await ItemProvider(DirectionEnum.Older, Items.Last().Value);
|
var resTask = ItemProvider(DirectionEnum.Older, Items.Last().Value);
|
||||||
|
// Only show the spinner if ItemProvider takes more than 500ms to load, and the spinner is actually visible on screen.
|
||||||
|
await Task.WhenAny(Task.Delay(TimeSpan.FromMilliseconds(500)), resTask);
|
||||||
|
if (resTask.IsCompleted == false)
|
||||||
|
{
|
||||||
|
if (After.Visible)
|
||||||
|
{
|
||||||
|
_hideAfter = false;
|
||||||
|
ReRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await resTask;
|
||||||
if (res is not null && res.Count > 0)
|
if (res is not null && res.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var el in res)
|
foreach (var el in res)
|
||||||
|
|
|
@ -5,6 +5,4 @@ namespace Iceshrimp.Frontend.Core.Services.NoteStore;
|
||||||
internal class TimelineState
|
internal class TimelineState
|
||||||
{
|
{
|
||||||
public SortedList<string, NoteResponse> Timeline { get; } = new(Comparer<string>.Create((x, y) => String.Compare(y, x, StringComparison.Ordinal)));
|
public SortedList<string, NoteResponse> Timeline { get; } = new(Comparer<string>.Create((x, y) => String.Compare(y, x, StringComparison.Ordinal)));
|
||||||
public string? MaxId { get; set; }
|
|
||||||
public string? MinId { get; set; }
|
|
||||||
}
|
}
|
|
@ -81,8 +81,6 @@ internal class TimelineStore : NoteMessageProvider, IAsyncDisposable, IStreaming
|
||||||
if (add is false) _logger.LogError($"Duplicate note: {note.Id}");
|
if (add is false) _logger.LogError($"Duplicate note: {note.Id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Timelines[timeline].MaxId = Timelines[timeline].Timeline.First().Value.Id;
|
|
||||||
Timelines[timeline].MinId = Timelines[timeline].Timeline.Last().Value.Id;
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
|
|
|
@ -309,9 +309,9 @@ pre:has(code) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-button.hidden {
|
.virtual-scroller-button.hidden {
|
||||||
height: 0;
|
height: 1px;
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
/*transition: height ease-out 250ms;*/
|
transition: height ease-out 250ms;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,10 +319,15 @@ pre:has(code) {
|
||||||
/*noinspection CssInvalidAtRule*/
|
/*noinspection CssInvalidAtRule*/
|
||||||
@starting-style {
|
@starting-style {
|
||||||
.virtual-scroller-button {
|
.virtual-scroller-button {
|
||||||
height: 0;
|
height: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes spinner {
|
@keyframes spinner {
|
||||||
0% {
|
0% {
|
||||||
|
|
Loading…
Add table
Reference in a new issue