287 lines
No EOL
9.3 KiB
Text
287 lines
No EOL
9.3 KiB
Text
@using System.Collections.Specialized
|
|
@using System.Runtime.InteropServices.JavaScript
|
|
@using Iceshrimp.Frontend.Core.Miscellaneous
|
|
@using Iceshrimp.Frontend.Core.Services
|
|
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns
|
|
@using Iceshrimp.Shared.Schemas
|
|
@using Ljbc1994.Blazor.IntersectionObserver
|
|
@using Ljbc1994.Blazor.IntersectionObserver.API
|
|
@inject IIntersectionObserverService ObserverService
|
|
@inject IJSRuntime Js
|
|
@inject StateService StateService
|
|
|
|
<div @ref="@Scroller" class="scroller">
|
|
<div @ref="@padTopRef" class="padding top" style="height: @(State.PadTop + "px")"></div>
|
|
@if(loadingTop){
|
|
<div class="target">
|
|
Loading!
|
|
</div>
|
|
}
|
|
@foreach (var el in State.RenderedList)
|
|
{
|
|
|
|
<div class="target" @ref="@Ref">
|
|
<TimelineNote @key="el.Id" Note="el"></TimelineNote>
|
|
</div>
|
|
}
|
|
@if(loadingBottom){
|
|
<div class="target">
|
|
Loading!
|
|
</div>
|
|
}
|
|
<div @ref="@padBotRef" class="padding bottom" style="height: @(State.PadBottom + "px")"></div>
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter, EditorRequired] public required List<NoteResponse> NoteResponseList { get; set; }
|
|
[Parameter, EditorRequired] public required EventCallback ReachedEnd { get; set; }
|
|
[Parameter, EditorRequired] public required EventCallback ReachedStart { get; set; }
|
|
private VirtualScrollerState State { get; set; } = new();
|
|
private int UpdateCount { get; set; } = 15;
|
|
private int _count = 30;
|
|
private List<ElementReference> _refs = [];
|
|
private IntersectionObserver? ObserverTop { get; set; }
|
|
private IntersectionObserver? ObserverBottom { get; set; }
|
|
private IntersectionObserver? OvrscrlObsvTop { get; set; }
|
|
private IntersectionObserver? OvrscrlObsvBottom { get; set; }
|
|
private bool _overscrollTop = false;
|
|
private bool _overscrollBottom = false;
|
|
private ElementReference padTopRef;
|
|
private ElementReference padBotRef;
|
|
private ElementReference Scroller;
|
|
private bool loadingTop = false;
|
|
private bool loadingBottom = false;
|
|
private bool setScroll = false;
|
|
|
|
private ElementReference Ref
|
|
{
|
|
set => _refs.Add(value);
|
|
}
|
|
|
|
private bool interlock = false;
|
|
private IJSObjectReference Module { get; set; }
|
|
|
|
private void InitialRender(string? id)
|
|
{
|
|
var a = new List<NoteResponse>();
|
|
a = NoteResponseList.GetRange(0, _count);
|
|
State.RenderedList = a;
|
|
}
|
|
|
|
private async Task LoadOlder()
|
|
{
|
|
loadingBottom = true;
|
|
StateHasChanged();
|
|
await ReachedEnd.InvokeAsync();
|
|
loadingBottom = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task LoadNewer()
|
|
{
|
|
loadingTop = true;
|
|
StateHasChanged();
|
|
await ReachedStart.InvokeAsync();
|
|
loadingTop = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task SaveState()
|
|
{
|
|
await GetScrollTop();
|
|
StateService.VirtualScroller.SetState("home", State);
|
|
}
|
|
|
|
|
|
private async Task RemoveAbove(int amount)
|
|
{
|
|
for (int i = 0; i < amount; i++)
|
|
{
|
|
var height = await Module.InvokeAsync<int>("GetHeight", _refs[i]);
|
|
State.PadTop += height;
|
|
State.Height[State.RenderedList[i].Id] = height;
|
|
}
|
|
|
|
State.RenderedList.RemoveRange(0, amount);
|
|
|
|
}
|
|
|
|
private async Task Down()
|
|
{
|
|
if (OvrscrlObsvBottom is null) throw new Exception("Tried to use observer that does not exist");
|
|
await OvrscrlObsvBottom.Disconnect();
|
|
|
|
var index = NoteResponseList.IndexOf(State.RenderedList.Last());
|
|
Console.WriteLine($"Index: {index}");
|
|
if (index >= NoteResponseList.Count - (1 + UpdateCount))
|
|
{
|
|
Console.WriteLine("end of data, requesting more");
|
|
await LoadOlder();
|
|
}
|
|
else
|
|
{
|
|
var a = NoteResponseList.GetRange(index + 1, UpdateCount);
|
|
var heightChange = 0;
|
|
foreach (var el in a)
|
|
{
|
|
if (State.Height.ContainsKey(el.Id))
|
|
{
|
|
heightChange += State.Height[el.Id];
|
|
Console.WriteLine("found height");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("Did not find height");
|
|
}
|
|
}
|
|
|
|
if (State.PadBottom > 0) State.PadBottom -= heightChange;
|
|
Console.WriteLine($"Pad bottom height: {State.PadBottom}");
|
|
|
|
State.RenderedList.AddRange(a);
|
|
await RemoveAbove(UpdateCount);
|
|
interlock = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
await OvrscrlObsvBottom.Observe(padBotRef);
|
|
}
|
|
|
|
private async Task Up()
|
|
{
|
|
if (OvrscrlObsvTop is null) throw new Exception("Tried to use observer that does not exist");
|
|
await OvrscrlObsvTop.Disconnect();
|
|
for (int i = 0; i < UpdateCount; i++)
|
|
{
|
|
var height = await Module.InvokeAsync<int>("GetHeight", _refs[i]);
|
|
State.PadBottom += height;
|
|
State.Height[State.RenderedList[i].Id] = height;
|
|
}
|
|
|
|
var index = NoteResponseList.IndexOf(State.RenderedList.First());
|
|
var a = NoteResponseList.GetRange(index - UpdateCount, UpdateCount);
|
|
var heightChange = 0;
|
|
foreach (var el in a)
|
|
{
|
|
heightChange += State.Height[el.Id];
|
|
}
|
|
|
|
State.PadTop -= heightChange;
|
|
State.RenderedList.InsertRange(0, a);
|
|
State.RenderedList.RemoveRange(State.RenderedList.Count - UpdateCount, UpdateCount);
|
|
StateHasChanged();
|
|
interlock = false;
|
|
await OvrscrlObsvTop.Observe(padTopRef);
|
|
}
|
|
|
|
private async Task SetupObservers()
|
|
{
|
|
var options = new IntersectionObserverOptions
|
|
{
|
|
RootMargin = "100%"
|
|
};
|
|
OvrscrlObsvTop = await ObserverService.Create(OverscrollCallbackTop);
|
|
OvrscrlObsvBottom = await ObserverService.Create(OverscrollCallbackBottom);
|
|
|
|
await OvrscrlObsvTop.Observe(padTopRef);
|
|
await OvrscrlObsvBottom.Observe(padBotRef);
|
|
}
|
|
|
|
|
|
private async void OverscrollCallbackTop(IList<IntersectionObserverEntry> list)
|
|
{
|
|
Console.WriteLine("Top callback fired");
|
|
var entry = list.First();
|
|
_overscrollTop = entry.IsIntersecting;
|
|
|
|
if (interlock == false)
|
|
{
|
|
var index = NoteResponseList.IndexOf(State.RenderedList.First());
|
|
if (index == 0)
|
|
{
|
|
Console.WriteLine("Can't go up further");
|
|
await LoadNewer();
|
|
return;
|
|
}
|
|
|
|
interlock = true;
|
|
Console.WriteLine("first observed");
|
|
if (list.First().IsIntersecting)
|
|
|
|
{
|
|
Console.WriteLine("Shifting up");
|
|
await Up();
|
|
}
|
|
|
|
interlock = false;
|
|
}
|
|
}
|
|
|
|
private async void OverscrollCallbackBottom(IList<IntersectionObserverEntry> list)
|
|
{
|
|
Console.WriteLine("Bottom callback fired");
|
|
var entry = list.First();
|
|
_overscrollBottom = entry.IsIntersecting;
|
|
if (interlock == false)
|
|
{
|
|
interlock = true;
|
|
Console.WriteLine("last observerd");
|
|
if (list.First().IsIntersecting)
|
|
{
|
|
Console.WriteLine("Shifting down");
|
|
await Down();
|
|
}
|
|
|
|
interlock = false;
|
|
}
|
|
}
|
|
|
|
private async Task GetScrollTop()
|
|
{
|
|
var scrollTop = await Module.InvokeAsync<float>("GetScrollTop", Scroller);
|
|
State.ScrollTop = scrollTop;
|
|
}
|
|
|
|
private async Task SetScrollTop()
|
|
{
|
|
await Module.InvokeVoidAsync("SetScrollTop", State.ScrollTop, Scroller);
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
try
|
|
{
|
|
var virtualScrollerState = StateService.VirtualScroller.GetState("home");
|
|
State = virtualScrollerState;
|
|
setScroll = true;
|
|
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
InitialRender(null);
|
|
}
|
|
}
|
|
|
|
protected override void OnParametersSet()
|
|
{
|
|
StateHasChanged();
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
Module = await Js.InvokeAsync<IJSObjectReference>("import", "./Components/VirtualScroller.razor.js");
|
|
await SetupObservers();
|
|
}
|
|
|
|
if (setScroll)
|
|
{
|
|
await SetScrollTop();
|
|
setScroll = false;
|
|
}
|
|
|
|
await SaveState();
|
|
}
|
|
|
|
} |