[frontend/components] Hide virtual scroller ScrollEnd buttons when not needed

This commit is contained in:
Lilian 2025-02-09 22:30:05 +01:00
parent cd1973e000
commit 13495a692a
No known key found for this signature in database
3 changed files with 54 additions and 9 deletions

View file

@ -2,7 +2,7 @@
@using Ljbc1994.Blazor.IntersectionObserver.Components @using Ljbc1994.Blazor.IntersectionObserver.Components
@using Iceshrimp.Assets.PhosphorIcons @using Iceshrimp.Assets.PhosphorIcons
<IntersectionObserve OnChange="entry => OnChange(entry)"> <IntersectionObserve OnChange="entry => OnChange(entry)">
<div @ref="context.Ref.Current" class="@Class" style="@Style"> <div @ref="context.Ref.Current" class="@Class @(Hide ? "hidden" : "")" style="@Style">
@if (_state is State.Loading or State.Init) @if (_state is State.Loading or State.Init)
{ {
<LoadingSpinner /> <LoadingSpinner />
@ -20,12 +20,15 @@
[Parameter] public string? Class { get; set; } [Parameter] public string? Class { get; set; }
[Parameter] public bool RequireReset { get; init; } [Parameter] public bool RequireReset { get; init; }
[Parameter] public string? Style { get; set; } [Parameter] public string? Style { get; set; }
[Parameter] public bool Hide { get; set; } = false;
public bool Visible { get; private set; }
private State _state; private State _state;
private async Task OnChange(IntersectionObserverEntry entry) private async Task OnChange(IntersectionObserverEntry entry)
{ {
if (entry.IsIntersecting) if (entry.IsIntersecting)
{ {
Visible = true;
switch (_state) switch (_state)
{ {
case State.Loading: case State.Loading:
@ -40,6 +43,9 @@
{ {
_state = State.Waiting; _state = State.Waiting;
} }
} else if (entry.IsIntersecting == false)
{
Visible = false;
} }
} }

View file

@ -40,6 +40,7 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
private bool _setScroll = false; private bool _setScroll = false;
private bool _shouldRender = false; private bool _shouldRender = false;
private bool _initialized = false; private bool _initialized = false;
private bool _hideBefore = true;
private IDisposable? _locationChangeHandlerDisposable; private IDisposable? _locationChangeHandlerDisposable;
@ -111,7 +112,8 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
builder.AddComponentParameter(3, "ManualLoad", new EventCallback(this, CallbackBeforeAsync)); builder.AddComponentParameter(3, "ManualLoad", new EventCallback(this, CallbackBeforeAsync));
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", _hideBefore);
builder.AddComponentReferenceCapture(7,
reference => reference =>
Before = reference as ScrollEnd Before = reference as ScrollEnd
?? throw new InvalidOperationException()); ?? throw new InvalidOperationException());
@ -168,7 +170,18 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
} }
var heightBefore = _module.Invoke<float>("GetDocumentHeight"); var heightBefore = _module.Invoke<float>("GetDocumentHeight");
var res = await 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)
{
if (Before.Visible)
{
_hideBefore = 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)
@ -183,6 +196,12 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
var scroll = _module.Invoke<float>("GetScrollY"); var scroll = _module.Invoke<float>("GetScrollY");
_module.InvokeVoid("SetScrollY", scroll + diff); _module.InvokeVoid("SetScrollY", scroll + diff);
} }
else if (res?.Count == 0)
{
_hideBefore = true;
Logger.LogInformation("Hiding before");
ReRender();
}
Before.Reset(); Before.Reset();
} }
@ -195,6 +214,7 @@ public class VirtualScroller<T> : ComponentBase, IDisposable where T : IIdentifi
var add = Items.TryAdd(item.Id, item); var add = Items.TryAdd(item.Id, item);
if (add is false) Logger.LogError($"Duplicate notification: {item.Id}"); if (add is false) Logger.LogError($"Duplicate notification: {item.Id}");
} }
ReRender(); ReRender();
} }

View file

@ -115,6 +115,7 @@ button {
padding-inline: 1em; padding-inline: 1em;
align-items: center; align-items: center;
} }
.button:hover { .button:hover {
background-color: var(--hover-color); background-color: var(--hover-color);
} }
@ -304,8 +305,25 @@ pre:has(code){
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
height: 3rem; height: 3rem;
transition: height ease-out 250ms;
} }
.virtual-scroller-button.hidden {
height: 0;
overflow: clip;
/*transition: height ease-out 250ms;*/
opacity: 0;
}
/*@Is supported in all browsers*/
/*noinspection CssInvalidAtRule*/
@starting-style {
.virtual-scroller-button {
height: 0;
}
}
@keyframes spinner { @keyframes spinner {
0% { 0% {
rotate: 0deg; rotate: 0deg;
@ -330,6 +348,7 @@ pre:has(code){
.lazy-component-target-internal { .lazy-component-target-internal {
min-height: var(--height); min-height: var(--height);
} }
/*@Is supported in all browsers*/ /*@Is supported in all browsers*/
/*noinspection CssInvalidAtRule*/ /*noinspection CssInvalidAtRule*/
@starting-style { @starting-style {