206 lines
No EOL
6.7 KiB
Text
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;
|
|
}
|
|
} |