[frontend] Add note deletion to VirtualScroller and Timeline (ISH-404)

This commit is contained in:
Lilian 2024-07-21 22:27:54 +02:00
parent 854979b359
commit ba42e19beb
No known key found for this signature in database
5 changed files with 114 additions and 38 deletions

View file

@ -13,10 +13,11 @@ 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 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;
}
}
}

View file

@ -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();
}

View file

@ -33,7 +33,7 @@ internal class TimelineState : IDisposable
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()

View file

@ -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
{
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;
}
}

View file

@ -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
@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;
_componentState = LoadState.Init;
StateHasChanged();
}
@ -91,6 +106,7 @@ else
{
if (NoteId != null) _noteChangedHandler = MessageService.Register(NoteId, OnNoteChanged, MessageService.Type.Updated);
_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;
}
}