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;