Iceshrimp.NET/Iceshrimp.Frontend/Components/LazyComponent.cs

108 lines
3.2 KiB
C#

using Ljbc1994.Blazor.IntersectionObserver;
using Ljbc1994.Blazor.IntersectionObserver.API;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.JSInterop;
namespace Iceshrimp.Frontend.Components;
public class LazyComponent : ComponentBase, IAsyncDisposable
{
[Inject] private IIntersectionObserverService ObserverService { get; set; } = null!;
[Inject] private IJSInProcessRuntime Js { get; set; } = null!;
[Inject] private ILogger<LazyComponent> Logger { get; set; } = null!;
[Parameter] [EditorRequired] public required RenderFragment ChildContent { get; set; } = default!;
[Parameter] public float? InitialHeight { get; set; }
private IntersectionObserver? Observer { get; set; }
public ElementReference Target { get; private set; }
public bool Visible { get; private set; }
private float? Height { get; set; }
// private bool minHeightSet = false;
protected override void OnInitialized()
{
if (InitialHeight is not null)
{
Height = InitialHeight;
}
}
public float? GetHeight()
{
if (Height != null) return Height.Value;
if (Visible) return Js.Invoke<float>("getHeight", Target);
else
{
Logger.LogWarning("Invisible, no height available");
return null;
}
}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(1, "div");
var classnames = "target lazy-component-target-internal";
if (Visible) classnames = "target lazy-component-target-internal visible";
builder.AddAttribute(2, "class", classnames);
if (Height is not null)
{
builder.AddAttribute(4, "style", $"--height: {Height}px");
}
builder.AddElementReferenceCapture(5, elementReference => Target = elementReference);
builder.OpenRegion(10);
if (Visible)
{
builder.AddContent(1, ChildContent);
}
else
{
builder.OpenElement(2, "div");
builder.AddAttribute(3, "class", "placeholder");
if (Height is not null)
{
builder.AddAttribute(4, "style", $"height: {Height}px");
}
else
{
builder.AddAttribute(5, "style", "height: 5rem");
}
builder.CloseElement();
}
builder.CloseRegion();
builder.CloseElement();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Observer = await ObserverService.Create(OnIntersect);
await Observer.Observe(Target);
}
}
private void OnIntersect(IList<IntersectionObserverEntry> entries)
{
var entry = entries.First();
if (Visible && entry.IsIntersecting is false)
{
Height = Js.Invoke<float>("getHeight", Target);
Visible = false;
StateHasChanged();
}
if (Visible is false && entry.IsIntersecting)
{
Visible = true;
StateHasChanged();
}
}
public async ValueTask DisposeAsync()
{
if (Observer != null) await Observer.Dispose();
}
}