@page "/notes/{NoteId}" @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.Extensions.Localization @using Microsoft.AspNetCore.Components.Sections @using Iceshrimp.Assets.PhosphorIcons @inject ApiService ApiService @inject IJSRuntime Js @inject MessageService MessageService @inject StateService State @inject NavigationManager Navigation @inject IStringLocalizer Loc @inject ILogger Logger; @implements IDisposable @if (_componentState == LoadState.Init) { @Loc["Post by {0}", (RootNote?.User.DisplayName ?? RootNote?.User.Username) ?? string.Empty]
@if (Ascendants != null) {
@foreach (var note in Ascendants) { }
}
@if (Descendants != null) {
@foreach (var element in Descendants) { }
}
} @if (_componentState == LoadState.Loading) {
Loading
} @if (_componentState == LoadState.Error) {
This note does not exist!
} @if (_componentState == LoadState.Deleted) {
@Loc["This post has been deleted"]
} @code { [Parameter] public string? NoteId { get; set; } public NoteResponse? RootNote { get; set; } private IList? Descendants { get; set; } private IList? 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("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("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; } }