[frontend] Add notification streaming to timeline
This commit is contained in:
parent
671346a308
commit
3f6c241f59
3 changed files with 132 additions and 98 deletions
|
@ -1,17 +1,6 @@
|
||||||
@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
|
|
||||||
@using Ljbc1994.Blazor.IntersectionObserver.Components
|
|
||||||
@inject ApiService ApiService
|
|
||||||
@inject StateService StateService
|
|
||||||
|
|
||||||
|
|
||||||
@if (_init)
|
@if (_init)
|
||||||
{
|
{
|
||||||
<VirtualScroller NoteResponseList="State.Timeline" ReachedEnd="FetchOlder" ReachedStart="FetchNewer" />
|
<VirtualScroller @ref=VirtualScroller NoteResponseList = "State.Timeline" ReachedEnd="FetchOlder" ReachedStart="FetchNewer"/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -19,74 +8,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
100
Iceshrimp.Frontend/Components/TimelineComponent.razor.cs
Normal file
100
Iceshrimp.Frontend/Components/TimelineComponent.razor.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue