Iceshrimp.NET/Iceshrimp.Frontend/Pages/SingleNote.razor
2024-09-13 21:44:31 +02:00

206 lines
No EOL
6.7 KiB
Text

@page "/notes/{NoteId}"
@attribute [Authorize]
@inject ApiService ApiService
@inject IJSRuntime Js
@inject MessageService MessageService
@inject StateService State
@inject NavigationManager Navigation
@inject IStringLocalizer<Localization> Loc
@inject ILogger<SingleNote> Logger;
@using Iceshrimp.Assets.PhosphorIcons
@using Iceshrimp.Frontend.Components
@using Iceshrimp.Frontend.Components.Note
@using Iceshrimp.Frontend.Core.Miscellaneous
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns
@using Iceshrimp.Frontend.Localization
@using Iceshrimp.Shared.Schemas.Web
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Sections
@using Microsoft.Extensions.Localization
@implements IDisposable
@if (_componentState == LoadState.Init)
{
<SectionContent SectionName="top-bar">
<Icon Name="Icons.Signpost"></Icon>
@Loc["Post by {0}", (RootNote?.User.DisplayName ?? RootNote?.User.Username) ?? string.Empty]
</SectionContent>
<div @ref="Scroller" class="scroller">
<div class="wrapper">
<div class="container">
@if (Ascendants != null)
{
<div class="ascendants">
@foreach (var note in Ascendants)
{
<AscendedNote Note="note"/>
}
</div>
}
<div @ref="RootNoteRef" class="root-note">
<Note NoteResponse="RootNote"></Note>
</div>
@if (Descendants != null)
{
<div class="descendants">
@foreach (var element in Descendants)
{
<RecursiveNote Note="element" Depth="0" MaxDepth="_depth"/>
}
</div>
}
</div>
</div>
</div>
}
@if (_componentState == LoadState.Loading)
{
<div>Loading</div>
}
@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; }
public NoteResponse? RootNote { get; set; }
private IList<NoteResponse>? Descendants { get; set; }
private IList<NoteResponse>? Ascendants { get; set; }
private IJSInProcessObjectReference Module { get; set; } = null!;
private ElementReference RootNoteRef { get; set; }
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()
{
Logger.LogTrace($"Opening NoteID: {NoteId}");
_componentState = LoadState.Loading;
if (NoteId == null)
{
_componentState = LoadState.Error;
return;
}
try
{
var rootNoteTask = ApiService.Notes.GetNote(NoteId);
var descendantsTask = ApiService.Notes.GetNoteDescendants(NoteId, _depth);
var ascendantsTask = ApiService.Notes.GetNoteAscendants(NoteId, default);
RootNote = await rootNoteTask;
Descendants = await descendantsTask;
Ascendants = await ascendantsTask;
}
catch (ApiException e)
{
Logger.LogWarning($"Failed to load ID '{NoteId}' due to API Exception: {e.Message}");
_componentState = LoadState.Error;
return;
}
if (RootNote == null)
{
_componentState = LoadState.Error;
return;
}
_componentState = LoadState.Init;
StateHasChanged();
}
protected override void OnInitialized()
{
if (NoteId != null) _noteChangedHandler = MessageService.Register(NoteId, OnNoteChanged, MessageService.Type.Updated);
_locationChangingHandlerDisposable = Navigation.RegisterLocationChangingHandler(LocationChangeHandler);
MessageService.AnyNoteDeleted += OnNoteDeleted;
}
private void OnNoteChanged(object? _, NoteResponse note)
{
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");
Descendants = await ApiService.Notes.GetNoteDescendants(NoteId, default);
Ascendants = await ApiService.Notes.GetNoteAscendants(NoteId, default);
StateHasChanged();
}
private ValueTask LocationChangeHandler(LocationChangingContext arg)
{
SaveState();
return ValueTask.CompletedTask;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Module = (IJSInProcessObjectReference)await Js.InvokeAsync<IJSObjectReference>("import", "/Pages/SingleNote.razor.js");
}
if (_componentState == LoadState.Init)
{
var state = State.SingleNote.GetState(NoteId!);
if (state != null)
{
Module.InvokeVoid("SetScrollTop", Scroller, state.ScrollTop);
}
else
{
Module.InvokeVoid("ScrollIntoView", RootNoteRef);
}
}
}
private void SaveState()
{
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);
}
public void Dispose()
{
if (_noteChangedHandler != null) _noteChangedHandler.Dispose();
SaveState();
_locationChangingHandlerDisposable?.Dispose();
MessageService.AnyNoteDeleted -= OnNoteDeleted;
}
}