[frontend] Add notification streaming to timeline

This commit is contained in:
Lilian 2024-06-27 00:27:12 +02:00 committed by Laura Hausmann
parent 671346a308
commit 3f6c241f59
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 132 additions and 98 deletions

View file

@ -1,92 +1,11 @@
@using Iceshrimp.Frontend.Core.Miscellaneous @if (_init)
@using Iceshrimp.Frontend.Core.Services {
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns <VirtualScroller @ref=VirtualScroller NoteResponseList = "State.Timeline" ReachedEnd="FetchOlder" ReachedStart="FetchNewer"/>
@using Iceshrimp.Shared.Schemas }
@using Ljbc1994.Blazor.IntersectionObserver else
@using Ljbc1994.Blazor.IntersectionObserver.API {
@using Ljbc1994.Blazor.IntersectionObserver.Components <div>Loading</div>
@inject ApiService ApiService }
@inject StateService StateService
@code {
@if (_init)
{
<VirtualScroller NoteResponseList="State.Timeline" ReachedEnd="FetchOlder" ReachedStart="FetchNewer" />
}
else
{
<div>Loading</div>
}
@code {
private TimelineState State { get; set; } = new()
{
Timeline = [],
MaxId = null,
MinId = null
};
private bool _init = false;
private bool LockFetch { get; set; }
private async Task Initialize()
{
var pq = new PaginationQuery() { Limit = 30 };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
State.MaxId = res[0].Id;
State.MinId = res.Last().Id;
State.Timeline = res;
}
private async Task FetchOlder()
{
if (LockFetch) return;
LockFetch = true;
var pq = new PaginationQuery() { Limit = 15, MaxId = State.MinId };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
if (res.Count > 0)
{
State.MinId = res.Last().Id;
State.Timeline.AddRange(res);
}
LockFetch = false;
}
private async Task FetchNewer()
{
if (LockFetch) return;
LockFetch = true;
var pq = new PaginationQuery() { Limit = 15, MinId = State.MaxId };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
if (res.Count > 0)
{
State.MinId = res.Last().Id;
State.Timeline.InsertRange(0, res);
}
LockFetch = false;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
var timeline = StateService.Timeline.GetState("home");
State = timeline;
_init = true;
StateHasChanged();
}
catch (ArgumentException)
{
await Initialize();
_init = true;
StateHasChanged();
}
}
StateService.Timeline.SetState("home", State);
}
} }

View file

@ -0,0 +1,100 @@
using Iceshrimp.Frontend.Core.Miscellaneous;
using Iceshrimp.Frontend.Core.Services;
using Iceshrimp.Frontend.Core.Services.StateServicePatterns;
using Iceshrimp.Shared.HubSchemas;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Components;
namespace Iceshrimp.Frontend.Components;
public partial class TimelineComponent : IAsyncDisposable
{
[Inject] private ApiService ApiService { get; set; } = null!;
[Inject] private StreamingService StreamingService { get; set; } = null!;
[Inject] private StateService StateService { get; set; } = null!;
private TimelineState State { get; set; } = new() { Timeline = [], MaxId = null, MinId = null };
private VirtualScroller VirtualScroller { get; set; } = null!;
private bool _init = false;
private bool LockFetch { get; set; }
private async Task Initialize()
{
var pq = new PaginationQuery() { Limit = 30 };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
State.MaxId = res[0].Id;
State.MinId = res.Last().Id;
State.Timeline = res;
}
private async Task FetchOlder()
{
if (LockFetch) return;
LockFetch = true;
var pq = new PaginationQuery() { Limit = 15, MaxId = State.MinId };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
if (res.Count > 0)
{
State.MinId = res.Last().Id;
State.Timeline.AddRange(res);
}
LockFetch = false;
}
private async Task FetchNewer()
{
if (LockFetch) return;
LockFetch = true;
var pq = new PaginationQuery() { Limit = 15, MinId = State.MaxId };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
if (res.Count > 0)
{
State.MaxId = res.Last().Id;
State.Timeline.InsertRange(0, res);
}
LockFetch = false;
}
private async void OnNotePublished(object? _, (StreamingTimeline timeline, NoteResponse note) data)
{
State.Timeline.Insert(0, data.note);
State.MaxId = data.note.Id;
StateHasChanged();
await VirtualScroller.OnNewNote();
}
protected override async Task OnInitializedAsync()
{
StreamingService.NotePublished += OnNotePublished;
await StreamingService.Connect();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
var timeline = StateService.Timeline.GetState("home");
State = timeline;
_init = true;
StateHasChanged();
}
catch (ArgumentException)
{
await Initialize();
_init = true;
StateHasChanged();
}
}
StateService.Timeline.SetState("home", State);
}
public async ValueTask DisposeAsync()
{
StreamingService.NotePublished -= OnNotePublished;
await StreamingService.DisposeAsync();
}
}

View file

@ -145,28 +145,28 @@
await OvrscrlObsvBottom.Observe(_padBotRef); await OvrscrlObsvBottom.Observe(_padBotRef);
} }
private async Task Up() private async Task Up(int updateCount)
{ {
if (OvrscrlObsvTop is null) throw new Exception("Tried to use observer that does not exist"); if (OvrscrlObsvTop is null) throw new Exception("Tried to use observer that does not exist");
await OvrscrlObsvTop.Disconnect(); await OvrscrlObsvTop.Disconnect();
for (int i = 0; i < UpdateCount; i++) for (int i = 0; i < updateCount; i++)
{ {
var height = await Module.InvokeAsync<int>("GetHeight", _refs[i]); var height = await Module.InvokeAsync<int>("GetHeight", _refs[i]);
State.PadBottom += height; State.PadBottom += height;
State.Height[State.RenderedList[i].Id] = height; State.Height[State.RenderedList[i].Id] = height;
} }
var index = NoteResponseList.IndexOf(State.RenderedList.First()); var index = NoteResponseList.IndexOf(State.RenderedList.First());
var a = NoteResponseList.GetRange(index - UpdateCount, UpdateCount); var a = NoteResponseList.GetRange(index - updateCount, updateCount);
var heightChange = 0; var heightChange = 0;
foreach (var el in a) foreach (var el in a)
{ {
heightChange += State.Height[el.Id]; State.Height.TryGetValue(el.Id, out var height);
heightChange += height;
} }
State.PadTop -= heightChange; State.PadTop -= heightChange;
State.RenderedList.InsertRange(0, a); State.RenderedList.InsertRange(0, a);
State.RenderedList.RemoveRange(State.RenderedList.Count - UpdateCount, UpdateCount); State.RenderedList.RemoveRange(State.RenderedList.Count - updateCount, updateCount);
StateHasChanged(); StateHasChanged();
_interlock = false; _interlock = false;
await OvrscrlObsvTop.Observe(_padTopRef); await OvrscrlObsvTop.Observe(_padTopRef);
@ -183,6 +183,15 @@
await OvrscrlObsvBottom.Observe(_padBotRef); await OvrscrlObsvBottom.Observe(_padBotRef);
} }
public async Task OnNewNote()
{
if (_overscrollTop && _interlock == false)
{
_interlock = true;
await Up(1);
}
}
private async void OverscrollCallbackTop(IList<IntersectionObserverEntry> list) private async void OverscrollCallbackTop(IList<IntersectionObserverEntry> list)
{ {
@ -198,11 +207,17 @@
return; return;
} }
var updateCount = UpdateCount;
if (index < UpdateCount)
{
updateCount = index;
}
_interlock = true; _interlock = true;
if (list.First().IsIntersecting) if (list.First().IsIntersecting)
{ {
await Up(); await Up(updateCount);
} }
_interlock = false; _interlock = false;