From 3f6c241f5961e2e9461786995bf78d80253359f2 Mon Sep 17 00:00:00 2001 From: Lilian Date: Thu, 27 Jun 2024 00:27:12 +0200 Subject: [PATCH] [frontend] Add notification streaming to timeline --- .../Components/TimelineComponent.razor | 101 ++---------------- .../Components/TimelineComponent.razor.cs | 100 +++++++++++++++++ .../Components/VirtualScroller.razor | 29 +++-- 3 files changed, 132 insertions(+), 98 deletions(-) create mode 100644 Iceshrimp.Frontend/Components/TimelineComponent.razor.cs diff --git a/Iceshrimp.Frontend/Components/TimelineComponent.razor b/Iceshrimp.Frontend/Components/TimelineComponent.razor index 200acedd..1328c08e 100644 --- a/Iceshrimp.Frontend/Components/TimelineComponent.razor +++ b/Iceshrimp.Frontend/Components/TimelineComponent.razor @@ -1,92 +1,11 @@ -@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) - { - - } - else - { -
Loading
- } - - @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); - } - +@if (_init) +{ + } +else +{ +
Loading
+} + +@code { +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Components/TimelineComponent.razor.cs b/Iceshrimp.Frontend/Components/TimelineComponent.razor.cs new file mode 100644 index 00000000..ec79c043 --- /dev/null +++ b/Iceshrimp.Frontend/Components/TimelineComponent.razor.cs @@ -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(); + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Components/VirtualScroller.razor b/Iceshrimp.Frontend/Components/VirtualScroller.razor index ac7b7b49..4d58b276 100644 --- a/Iceshrimp.Frontend/Components/VirtualScroller.razor +++ b/Iceshrimp.Frontend/Components/VirtualScroller.razor @@ -145,28 +145,28 @@ 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"); await OvrscrlObsvTop.Disconnect(); - for (int i = 0; i < UpdateCount; i++) + for (int i = 0; i < updateCount; i++) { var height = await Module.InvokeAsync("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 a = NoteResponseList.GetRange(index - updateCount, updateCount); var heightChange = 0; foreach (var el in a) { - heightChange += State.Height[el.Id]; + State.Height.TryGetValue(el.Id, out var height); + heightChange += height; } State.PadTop -= heightChange; State.RenderedList.InsertRange(0, a); - State.RenderedList.RemoveRange(State.RenderedList.Count - UpdateCount, UpdateCount); + State.RenderedList.RemoveRange(State.RenderedList.Count - updateCount, updateCount); StateHasChanged(); _interlock = false; await OvrscrlObsvTop.Observe(_padTopRef); @@ -183,6 +183,15 @@ await OvrscrlObsvBottom.Observe(_padBotRef); } + public async Task OnNewNote() + { + if (_overscrollTop && _interlock == false) + { + _interlock = true; + await Up(1); + } + } + private async void OverscrollCallbackTop(IList list) { @@ -198,11 +207,17 @@ return; } + var updateCount = UpdateCount; + if (index < UpdateCount) + { + updateCount = index; + } + _interlock = true; if (list.First().IsIntersecting) { - await Up(); + await Up(updateCount); } _interlock = false;