diff --git a/Iceshrimp.Frontend/Components/VirtualScroller.cs b/Iceshrimp.Frontend/Components/VirtualScroller.cs index 111bd698..aa125757 100644 --- a/Iceshrimp.Frontend/Components/VirtualScroller.cs +++ b/Iceshrimp.Frontend/Components/VirtualScroller.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; using Microsoft.JSInterop; +using Microsoft.Extensions.Localization; namespace Iceshrimp.Frontend.Components; @@ -15,10 +16,11 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi { [Inject] private IIntersectionObserverService ObserverService { get; set; } = null!; - [Inject] private StateService State { get; set; } = null!; - [Inject] private NavigationManager Navigation { get; set; } = null!; - [Inject] private ILogger Logger { get; set; } = null!; - [Inject] private IJSRuntime Js { get; set; } = null!; + [Inject] private StateService State { get; set; } = null!; + [Inject] private NavigationManager Navigation { get; set; } = null!; + [Inject] private ILogger Logger { get; set; } = null!; + [Inject] private IJSRuntime Js { get; set; } = null!; + [Inject] private IStringLocalizer Loc { get; set; } = null!; [Parameter] [EditorRequired] public required RenderFragment ItemTemplate { get; set; } = default!; [Parameter] [EditorRequired] public required IReadOnlyList InitialItems { get; set; } = default!; @@ -41,6 +43,7 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi private bool _shouldRender = false; private bool _initialized = false; private bool _hideBefore = true; + private bool _hideAfter = true; private IDisposable? _locationChangeHandlerDisposable; @@ -120,7 +123,17 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi builder.CloseComponent(); builder.CloseRegion(); - builder.OpenRegion(2); + if (Items.Count == 0) + { + 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) { builder.OpenElement(2, "div"); @@ -147,13 +160,14 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi builder.CloseRegion(); - builder.OpenRegion(3); + builder.OpenRegion(4); builder.OpenComponent(1); builder.AddComponentParameter(2, "IntersectionChange", new EventCallback(this, CallbackAfterAsync)); builder.AddComponentParameter(3, "ManualLoad", new EventCallback(this, CallbackAfterAsync)); builder.AddComponentParameter(4, "RequireReset", true); builder.AddComponentParameter(5, "Class", "virtual-scroller-button"); - builder.AddComponentReferenceCapture(6, + builder.AddComponentParameter(6, "Hide", _hideAfter); + builder.AddComponentReferenceCapture(7, reference => After = reference as ScrollEnd ?? throw new InvalidOperationException()); @@ -163,14 +177,15 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi private async Task CallbackBeforeAsync() { - if (!_initialized) + // Cannot call item provider when there are no existing items. + if (Items.Count == 0 || !_initialized) { Before.Reset(); return; } - + var heightBefore = _module.Invoke("GetDocumentHeight"); - var resTask = ItemProvider(DirectionEnum.Newer, Items.First().Value); + var resTask = ItemProvider(DirectionEnum.Newer, Items.First().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) @@ -181,6 +196,7 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi ReRender(); } } + var res = await resTask; if (res is not null && res.Count > 0) { @@ -220,13 +236,26 @@ public class VirtualScroller : ComponentBase, IDisposable where T : IIdentifi private async Task CallbackAfterAsync() { - if (!_initialized) + // Cannot call item provider when there are no existing items. + if (Items.Count == 0 || !_initialized) { After.Reset(); 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) { foreach (var el in res) diff --git a/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineState.cs b/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineState.cs index 543445ca..68b7efc6 100644 --- a/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineState.cs +++ b/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineState.cs @@ -5,6 +5,4 @@ namespace Iceshrimp.Frontend.Core.Services.NoteStore; internal class TimelineState { public SortedList Timeline { get; } = new(Comparer.Create((x, y) => String.Compare(y, x, StringComparison.Ordinal))); - public string? MaxId { get; set; } - public string? MinId { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineStore.cs b/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineStore.cs index 1aaa54ca..0814baf1 100644 --- a/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineStore.cs +++ b/Iceshrimp.Frontend/Core/Services/NoteStore/TimelineStore.cs @@ -81,8 +81,6 @@ internal class TimelineStore : NoteMessageProvider, IAsyncDisposable, IStreaming 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; } catch (ApiException e) diff --git a/Iceshrimp.Frontend/wwwroot/css/app.css b/Iceshrimp.Frontend/wwwroot/css/app.css index de2df49f..5b6ad736 100644 --- a/Iceshrimp.Frontend/wwwroot/css/app.css +++ b/Iceshrimp.Frontend/wwwroot/css/app.css @@ -309,9 +309,9 @@ pre:has(code) { } .virtual-scroller-button.hidden { - height: 0; + height: 1px; overflow: clip; - /*transition: height ease-out 250ms;*/ + transition: height ease-out 250ms; opacity: 0; } @@ -319,10 +319,15 @@ pre:has(code) { /*noinspection CssInvalidAtRule*/ @starting-style { .virtual-scroller-button { - height: 0; + height: 1px; } } +.placeholder { + text-align: center; + padding-top: 5rem; +} + @keyframes spinner { 0% {