diff --git a/Iceshrimp.Frontend/Components/TimelineComponent.razor b/Iceshrimp.Frontend/Components/TimelineComponent.razor
index 4bb226ea..200acedd 100644
--- a/Iceshrimp.Frontend/Components/TimelineComponent.razor
+++ b/Iceshrimp.Frontend/Components/TimelineComponent.razor
@@ -1,47 +1,66 @@
@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 List Timeline { get; set; } = [];
- private bool _init = false;
- private string? MaxId { get; set; }
- private string? MinId { get; set; }
- private bool LockFetch { get; set; }
+ @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);
- MaxId = res[0].Id;
- MinId = res.Last().Id;
- Timeline = res;
+ 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 = 10, MaxId = MinId };
+ var pq = new PaginationQuery() { Limit = 15, MaxId = State.MinId };
var res = await ApiService.Timelines.GetHomeTimeline(pq);
if (res.Count > 0)
{
- MinId = res.Last().Id;
- Timeline.AddRange(res);
+ 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;
}
@@ -50,10 +69,24 @@
{
if (firstRender)
{
- await Initialize();
- _init = true;
- StateHasChanged();
+ try
+ {
+ var timeline = StateService.Timeline.GetState("home");
+ State = timeline;
+ _init = true;
+ StateHasChanged();
+ }
+ catch (ArgumentException)
+ {
+ await Initialize();
+ _init = true;
+ StateHasChanged();
+ }
+
+
}
+
+ StateService.Timeline.SetState("home", State);
}
}
diff --git a/Iceshrimp.Frontend/Components/VirtualScroller.razor b/Iceshrimp.Frontend/Components/VirtualScroller.razor
index c7cd2961..c891bc99 100644
--- a/Iceshrimp.Frontend/Components/VirtualScroller.razor
+++ b/Iceshrimp.Frontend/Components/VirtualScroller.razor
@@ -1,45 +1,57 @@
@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
@code {
- [Parameter, EditorRequired] public required List NoteResponseList { get; set; }
- [Parameter, EditorRequired] public required EventCallback ReachedEnd { get; set; }
- [Parameter, EditorRequired] public required EventCallback ReachedStart { get; set; }
- private List RenderedList { get; set; } = new();
- private Dictionary Height { get; set; } = new();
- private string MaxId { get; set; }
- private string MinId { get; set; }
- private int RenderIndex { get; set; }
- private int PadTop { get; set; } = 0;
- private int PadBottom { get; set; } = 0;
- private int UpdateCount { get; set; } = 5;
- private int _count = 15;
- private List _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;
+ [Parameter, EditorRequired] public required List 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 _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
{
@@ -52,35 +64,45 @@
private void InitialRender(string? id)
{
var a = new List();
- if (id != null)
- {
- RenderIndex = NoteResponseList.IndexOf(NoteResponseList.First(el => el.Id == id));
- a = NoteResponseList.GetRange(RenderIndex, RenderIndex + _count);
- }
- else
- {
- a = NoteResponseList.GetRange(0, _count);
- }
-
-
- RenderedList = a;
+ a = NoteResponseList.GetRange(0, _count);
+ State.RenderedList = a;
}
- private async Task LoadMore()
+ 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("GetHeight", _refs[i]);
- PadTop += height;
- Height[RenderedList[i].Id] = height;
+ State.PadTop += height;
+ State.Height[State.RenderedList[i].Id] = height;
}
- RenderedList.RemoveRange(0, amount);
+ State.RenderedList.RemoveRange(0, amount);
}
@@ -89,22 +111,22 @@
if (OvrscrlObsvBottom is null) throw new Exception("Tried to use observer that does not exist");
await OvrscrlObsvBottom.Disconnect();
- var index = NoteResponseList.IndexOf(RenderedList.Last());
+ var index = NoteResponseList.IndexOf(State.RenderedList.Last());
Console.WriteLine($"Index: {index}");
- if (index >= NoteResponseList.Count - 1)
+ if (index >= NoteResponseList.Count - (1 + UpdateCount))
{
Console.WriteLine("end of data, requesting more");
- await LoadMore();
+ await LoadOlder();
}
else
{
- var a = NoteResponseList.GetRange(index + 1, 5);
+ var a = NoteResponseList.GetRange(index + 1, UpdateCount);
var heightChange = 0;
foreach (var el in a)
{
- if (Height.ContainsKey(el.Id))
+ if (State.Height.ContainsKey(el.Id))
{
- heightChange += Height[el.Id];
+ heightChange += State.Height[el.Id];
Console.WriteLine("found height");
}
else
@@ -113,10 +135,10 @@
}
}
- if (PadBottom > 0) PadBottom -= heightChange;
- Console.WriteLine($"Pad bottom height: {PadBottom}");
+ if (State.PadBottom > 0) State.PadBottom -= heightChange;
+ Console.WriteLine($"Pad bottom height: {State.PadBottom}");
- RenderedList.AddRange(a);
+ State.RenderedList.AddRange(a);
await RemoveAbove(UpdateCount);
interlock = false;
StateHasChanged();
@@ -132,21 +154,21 @@
for (int i = 0; i < UpdateCount; i++)
{
var height = await Module.InvokeAsync("GetHeight", _refs[i]);
- PadBottom += height;
- Height[RenderedList[i].Id] = height;
+ State.PadBottom += height;
+ State.Height[State.RenderedList[i].Id] = height;
}
- var index = NoteResponseList.IndexOf(RenderedList.First());
+ var index = NoteResponseList.IndexOf(State.RenderedList.First());
var a = NoteResponseList.GetRange(index - UpdateCount, UpdateCount);
var heightChange = 0;
foreach (var el in a)
{
- heightChange += Height[el.Id];
+ heightChange += State.Height[el.Id];
}
- PadTop -= heightChange;
- RenderedList.InsertRange(0, a);
- RenderedList.RemoveRange(RenderedList.Count - UpdateCount, UpdateCount);
+ State.PadTop -= heightChange;
+ State.RenderedList.InsertRange(0, a);
+ State.RenderedList.RemoveRange(State.RenderedList.Count - UpdateCount, UpdateCount);
StateHasChanged();
interlock = false;
await OvrscrlObsvTop.Observe(padTopRef);
@@ -174,10 +196,11 @@
if (interlock == false)
{
- var index = NoteResponseList.IndexOf(RenderedList.First());
+ var index = NoteResponseList.IndexOf(State.RenderedList.First());
if (index == 0)
{
Console.WriteLine("Can't go up further");
+ await LoadNewer();
return;
}
@@ -213,15 +236,36 @@
}
}
- protected override async Task OnInitializedAsync()
+ private async Task GetScrollTop()
{
- InitialRender(null);
+ var scrollTop = await Module.InvokeAsync("GetScrollTop", Scroller);
+ State.ScrollTop = scrollTop;
}
- // protected override Task OnParametersSetAsync()
- // {
- // return;
- // }
+ 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)
{
@@ -230,6 +274,14 @@
Module = await Js.InvokeAsync("import", "./Components/VirtualScroller.razor.js");
await SetupObservers();
}
+
+ if (setScroll)
+ {
+ await SetScrollTop();
+ setScroll = false;
+ }
+
+ await SaveState();
}
}
\ No newline at end of file
diff --git a/Iceshrimp.Frontend/Components/VirtualScroller.razor.js b/Iceshrimp.Frontend/Components/VirtualScroller.razor.js
index 3145f4bc..954d8cb9 100644
--- a/Iceshrimp.Frontend/Components/VirtualScroller.razor.js
+++ b/Iceshrimp.Frontend/Components/VirtualScroller.razor.js
@@ -5,4 +5,12 @@ export function GetHeight(ref){
} else {
return 0;
}
-}
\ No newline at end of file
+}
+
+export function GetScrollTop(ref) {
+ return ref.scrollTop;
+}
+
+export function SetScrollTop(number, ref) {
+ ref.scrollTop = number;
+}
\ No newline at end of file
diff --git a/Iceshrimp.Frontend/Core/Services/StateService.cs b/Iceshrimp.Frontend/Core/Services/StateService.cs
new file mode 100644
index 00000000..80c1bb88
--- /dev/null
+++ b/Iceshrimp.Frontend/Core/Services/StateService.cs
@@ -0,0 +1,11 @@
+
+using Iceshrimp.Frontend.Core.Services.StateServicePatterns;
+
+namespace Iceshrimp.Frontend.Core.Services;
+
+public class StateService
+{
+ public VirtualScroller VirtualScroller { get; } = new();
+ public Timeline Timeline { get; } = new();
+
+}
\ No newline at end of file
diff --git a/Iceshrimp.Frontend/Core/Services/StateServicePatterns/TimelineState.cs b/Iceshrimp.Frontend/Core/Services/StateServicePatterns/TimelineState.cs
new file mode 100644
index 00000000..d9d0f6da
--- /dev/null
+++ b/Iceshrimp.Frontend/Core/Services/StateServicePatterns/TimelineState.cs
@@ -0,0 +1,25 @@
+using Iceshrimp.Shared.Schemas;
+
+namespace Iceshrimp.Frontend.Core.Services.StateServicePatterns;
+
+public class Timeline
+{
+ private Dictionary States { get; } = new();
+
+ public void SetState(string id, TimelineState state)
+ {
+ States[id] = state;
+ }
+
+ public TimelineState GetState(string id)
+ {
+ States.TryGetValue(id, out var state);
+ return state ?? throw new ArgumentException($"Requested state '{id}' does not exist.");
+ }
+}
+public class TimelineState
+{
+ public required List Timeline = [];
+ public required string? MaxId;
+ public required string? MinId;
+}
\ No newline at end of file
diff --git a/Iceshrimp.Frontend/Core/Services/StateServicePatterns/VirtualScrollerState.cs b/Iceshrimp.Frontend/Core/Services/StateServicePatterns/VirtualScrollerState.cs
new file mode 100644
index 00000000..de308cef
--- /dev/null
+++ b/Iceshrimp.Frontend/Core/Services/StateServicePatterns/VirtualScrollerState.cs
@@ -0,0 +1,29 @@
+using Iceshrimp.Shared.Schemas;
+
+namespace Iceshrimp.Frontend.Core.Services.StateServicePatterns;
+
+public class VirtualScroller
+{
+ private Dictionary States { get; } = new();
+
+ public void SetState(string id, VirtualScrollerState state)
+ {
+ States[id] = state;
+ }
+
+ public VirtualScrollerState GetState(string id)
+ {
+ States.TryGetValue(id, out var state);
+ return state ?? throw new ArgumentException($"Requested state '{id}' does not exist.");
+ }
+}
+
+public class VirtualScrollerState
+{
+ public List RenderedList = [];
+ public Dictionary Height = new();
+ public int PadTop = 0;
+ public int PadBottom = 0;
+ public float ScrollTop = 0;
+
+}
\ No newline at end of file
diff --git a/Iceshrimp.Frontend/Startup.cs b/Iceshrimp.Frontend/Startup.cs
index 7ec6ece6..d3dbd38f 100644
--- a/Iceshrimp.Frontend/Startup.cs
+++ b/Iceshrimp.Frontend/Startup.cs
@@ -21,6 +21,7 @@ builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddScoped();
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddBlazoredLocalStorageAsSingleton();