[frontend] Add note deletion to VirtualScroller and Timeline (ISH-404)
This commit is contained in:
parent
854979b359
commit
ba42e19beb
5 changed files with 114 additions and 38 deletions
|
@ -11,12 +11,13 @@ namespace Iceshrimp.Frontend.Components;
|
|||
public partial class VirtualScroller : IAsyncDisposable
|
||||
{
|
||||
[Inject] private IIntersectionObserverService ObserverService { get; set; } = null!;
|
||||
[Inject] private IJSRuntime Js { get; set; } = null!;
|
||||
[Inject] private StateService StateService { get; set; } = null!;
|
||||
[Inject] private IJSRuntime Js { get; set; } = null!;
|
||||
[Inject] private StateService StateService { get; set; } = null!;
|
||||
[Inject] private MessageService MessageService { get; set; } = null!;
|
||||
[Parameter] [EditorRequired] public required List<NoteResponse> NoteResponseList { get; set; }
|
||||
[Parameter] [EditorRequired] public required Func<Task<bool>> ReachedEnd { get; set; }
|
||||
[Parameter] [EditorRequired] public required EventCallback ReachedStart { get; set; }
|
||||
private VirtualScrollerState State { get; set; } = new();
|
||||
private VirtualScrollerState State { get; set; } = null!;
|
||||
private int UpdateCount { get; set; } = 15;
|
||||
private int _count = 30;
|
||||
private List<ElementReference> _refs = [];
|
||||
|
@ -47,6 +48,7 @@ public partial class VirtualScroller : IAsyncDisposable
|
|||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await SaveState();
|
||||
MessageService.AnyNoteDeleted -= OnNoteDeleted;
|
||||
}
|
||||
|
||||
private async Task LoadOlder()
|
||||
|
@ -225,8 +227,17 @@ public partial class VirtualScroller : IAsyncDisposable
|
|||
{
|
||||
await Module.InvokeVoidAsync("SetScrollTop", State.ScrollTop, _scroller);
|
||||
}
|
||||
|
||||
private void OnNoteDeleted(object? _, NoteResponse note)
|
||||
{
|
||||
State.RenderedList.Remove(note);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
State = StateService.VirtualScroller.CreateStateObject();
|
||||
MessageService.AnyNoteDeleted += OnNoteDeleted;
|
||||
try
|
||||
{
|
||||
var virtualScrollerState = StateService.VirtualScroller.GetState("home");
|
||||
|
@ -257,6 +268,5 @@ public partial class VirtualScroller : IAsyncDisposable
|
|||
await SetScrollTop();
|
||||
_setScroll = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace Iceshrimp.Frontend.Core.Services;
|
|||
|
||||
internal class StateService(MessageService messageService)
|
||||
{
|
||||
public VirtualScroller VirtualScroller { get; } = new();
|
||||
public VirtualScroller VirtualScroller { get; } = new(messageService);
|
||||
public Timeline Timeline { get; } = new(messageService);
|
||||
public SingleNote SingleNote { get; } = new();
|
||||
}
|
|
@ -6,7 +6,7 @@ namespace Iceshrimp.Frontend.Core.Services.StateServicePatterns;
|
|||
internal class Timeline(MessageService messageService)
|
||||
{
|
||||
private MessageService MessageService { get; set; } = messageService;
|
||||
private Dictionary<string, TimelineState> States { get; } = new();
|
||||
private Dictionary<string, TimelineState> States { get; } = new();
|
||||
|
||||
public void SetState(string id, TimelineState state)
|
||||
{
|
||||
|
@ -27,13 +27,13 @@ internal class Timeline(MessageService messageService)
|
|||
|
||||
internal class TimelineState : IDisposable
|
||||
{
|
||||
private MessageService MessageService { get; set; }
|
||||
public required string? MaxId;
|
||||
public required string? MinId;
|
||||
public required List<NoteResponse> Timeline;
|
||||
private MessageService MessageService { get; set; }
|
||||
public required string? MaxId;
|
||||
public required string? MinId;
|
||||
public required List<NoteResponse> Timeline;
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public TimelineState(List<NoteResponse> timeline, string? maxId, string? minId, MessageService messageService)
|
||||
internal TimelineState(List<NoteResponse> timeline, string? maxId, string? minId, MessageService messageService)
|
||||
{
|
||||
MaxId = maxId;
|
||||
MinId = minId;
|
||||
|
@ -54,7 +54,10 @@ internal class TimelineState : IDisposable
|
|||
|
||||
private void OnNoteDeleted(object? _, NoteResponse note)
|
||||
{
|
||||
Timeline.Remove(note);
|
||||
var i = Timeline.FindIndex(p => p.Id == note.Id);
|
||||
if (i == 0) MaxId = Timeline[1].Id;
|
||||
if (i == Timeline.Count - 1) MinId = Timeline[^2].Id;
|
||||
Timeline.RemoveAt(i);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
@ -2,10 +2,15 @@ using Iceshrimp.Shared.Schemas.Web;
|
|||
|
||||
namespace Iceshrimp.Frontend.Core.Services.StateServicePatterns;
|
||||
|
||||
public class VirtualScroller
|
||||
internal class VirtualScroller(MessageService messageService)
|
||||
{
|
||||
private Dictionary<string, VirtualScrollerState> States { get; } = new();
|
||||
|
||||
public VirtualScrollerState CreateStateObject()
|
||||
{
|
||||
return new VirtualScrollerState(messageService);
|
||||
}
|
||||
|
||||
public void SetState(string id, VirtualScrollerState state)
|
||||
{
|
||||
States[id] = state;
|
||||
|
@ -18,11 +23,37 @@ public class VirtualScroller
|
|||
}
|
||||
}
|
||||
|
||||
public class VirtualScrollerState
|
||||
internal class VirtualScrollerState : IDisposable
|
||||
{
|
||||
public Dictionary<string, int> Height = new();
|
||||
public int PadBottom = 0;
|
||||
public int PadTop = 0;
|
||||
public List<NoteResponse> RenderedList = [];
|
||||
public float ScrollTop = 0;
|
||||
internal VirtualScrollerState(MessageService messageService)
|
||||
{
|
||||
_messageService = messageService;
|
||||
_messageService.AnyNoteChanged += OnNoteChanged;
|
||||
_messageService.AnyNoteDeleted += OnNoteDeleted;
|
||||
}
|
||||
|
||||
public Dictionary<string, int> Height = new();
|
||||
public int PadBottom = 0;
|
||||
public int PadTop = 0;
|
||||
public List<NoteResponse> RenderedList = [];
|
||||
public float ScrollTop = 0;
|
||||
private MessageService _messageService;
|
||||
|
||||
private void OnNoteChanged(object? _, NoteResponse note)
|
||||
{
|
||||
var i = RenderedList.FindIndex(p => p.Id == note.Id);
|
||||
if (i >= 0) RenderedList[i] = note;
|
||||
}
|
||||
|
||||
private void OnNoteDeleted(object? _, NoteResponse note)
|
||||
{
|
||||
var i = RenderedList.FindIndex(p => p.Id == note.Id);
|
||||
if (i >= 0) RenderedList.RemoveAt(i);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messageService.AnyNoteChanged -= OnNoteChanged;
|
||||
_messageService.AnyNoteDeleted -= OnNoteDeleted;
|
||||
}
|
||||
}
|
|
@ -3,15 +3,19 @@
|
|||
@using Iceshrimp.Frontend.Components.Note
|
||||
@using Iceshrimp.Frontend.Core.Services
|
||||
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns
|
||||
@using Iceshrimp.Frontend.Localization
|
||||
@using Iceshrimp.Shared.Schemas.Web
|
||||
@inject ApiService ApiService
|
||||
@inject IJSRuntime Js
|
||||
@inject MessageService MessageService
|
||||
@inject StateService State
|
||||
@inject NavigationManager Navigation
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject ApiService ApiService
|
||||
@inject IJSRuntime Js
|
||||
@inject MessageService MessageService
|
||||
@inject StateService State
|
||||
@inject NavigationManager Navigation
|
||||
@inject IStringLocalizer<Localization> Loc;
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@if (_init)
|
||||
@if (_componentState == LoadState.Init)
|
||||
{
|
||||
<div @ref="Scroller" class="scroller">
|
||||
<div class="wrapper">
|
||||
|
@ -41,14 +45,18 @@
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@if (_componentState == LoadState.Loading)
|
||||
{
|
||||
<div>Loading</div>
|
||||
}
|
||||
@if (_error)
|
||||
@if (_componentState == LoadState.Error)
|
||||
{
|
||||
<div>This note does not exist!</div>
|
||||
}
|
||||
@if (_componentState == LoadState.Deleted)
|
||||
{
|
||||
<div>@Loc["This post has been deleted"]</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string? NoteId { get; set; }
|
||||
|
@ -57,32 +65,39 @@ else
|
|||
private IList<NoteResponse>? Ascendants { get; set; }
|
||||
private IJSInProcessObjectReference Module { get; set; } = null!;
|
||||
private ElementReference RootNoteRef { get; set; }
|
||||
private bool _init;
|
||||
private bool _error;
|
||||
private int _depth = 20;
|
||||
private ElementReference Scroller { get; set; }
|
||||
private IDisposable? _locationChangingHandlerDisposable;
|
||||
private IDisposable? _noteChangedHandler;
|
||||
private LoadState _componentState;
|
||||
|
||||
private enum LoadState
|
||||
{
|
||||
Loading,
|
||||
Init,
|
||||
Error,
|
||||
Deleted
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_init = false;
|
||||
_componentState = LoadState.Loading;
|
||||
if (NoteId == null)
|
||||
{
|
||||
_error = true;
|
||||
_componentState = LoadState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
RootNote = await ApiService.Notes.GetNote(NoteId);
|
||||
if (RootNote == null)
|
||||
{
|
||||
_error = true;
|
||||
_componentState = LoadState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
Descendants = await ApiService.Notes.GetNoteDescendants(NoteId, _depth);
|
||||
Ascendants = await ApiService.Notes.GetNoteAscendants(NoteId, default);
|
||||
_init = true;
|
||||
Descendants = await ApiService.Notes.GetNoteDescendants(NoteId, _depth);
|
||||
Ascendants = await ApiService.Notes.GetNoteAscendants(NoteId, default);
|
||||
_componentState = LoadState.Init;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
@ -90,7 +105,8 @@ else
|
|||
protected override void OnInitialized()
|
||||
{
|
||||
if (NoteId != null) _noteChangedHandler = MessageService.Register(NoteId, OnNoteChanged, MessageService.Type.Updated);
|
||||
_locationChangingHandlerDisposable = Navigation.RegisterLocationChangingHandler(LocationChangeHandler);
|
||||
_locationChangingHandlerDisposable = Navigation.RegisterLocationChangingHandler(LocationChangeHandler);
|
||||
MessageService.AnyNoteDeleted += OnNoteDeleted;
|
||||
}
|
||||
|
||||
private void OnNoteChanged(object? _, NoteResponse note)
|
||||
|
@ -98,6 +114,21 @@ else
|
|||
var __ = Refresh();
|
||||
}
|
||||
|
||||
private void OnNoteDeleted(object? _, NoteResponse note)
|
||||
{
|
||||
if (NoteId == note.Id)
|
||||
{
|
||||
_componentState = LoadState.Deleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ascendants?.Remove(note);
|
||||
Descendants?.Remove(note);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
if (NoteId == null) throw new InvalidOperationException("RefreshNote called under impossible circumstances");
|
||||
|
@ -119,7 +150,7 @@ else
|
|||
Module = (IJSInProcessObjectReference)await Js.InvokeAsync<IJSObjectReference>("import", "/Pages/SingleNote.razor.js");
|
||||
}
|
||||
|
||||
if (_init)
|
||||
if (_componentState == LoadState.Init)
|
||||
{
|
||||
var state = State.SingleNote.GetState(NoteId!);
|
||||
if (state != null)
|
||||
|
@ -135,7 +166,7 @@ else
|
|||
|
||||
private void SaveState()
|
||||
{
|
||||
if (NoteId == null) return;
|
||||
if (NoteId == null || _componentState != LoadState.Init) return;
|
||||
var scrollTop = Module.Invoke<float>("GetScrollTop", Scroller);
|
||||
var state = new SingleNoteState { ScrollTop = scrollTop };
|
||||
State.SingleNote.SetState(NoteId, state);
|
||||
|
@ -146,5 +177,6 @@ else
|
|||
if (_noteChangedHandler != null) _noteChangedHandler.Dispose();
|
||||
SaveState();
|
||||
_locationChangingHandlerDisposable?.Dispose();
|
||||
MessageService.AnyNoteDeleted -= OnNoteDeleted;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue