[frontend] Make reply tree update when new replies are added (ISH-399)

This commit is contained in:
Lilian 2024-07-20 03:17:05 +02:00
parent 6dbde914a3
commit ee0ee919d6
No known key found for this signature in database
3 changed files with 109 additions and 60 deletions

View file

@ -10,45 +10,46 @@
@inject ComposeService ComposeService @inject ComposeService ComposeService
@inject SessionService SessionService @inject SessionService SessionService
@inject IStringLocalizer<Localization> Loc; @inject IStringLocalizer<Localization> Loc;
@inject GlobalComponentSvc GlobalComponentSvc @inject GlobalComponentSvc GlobalComponentSvc
@inject MessageService MessageService
<dialog class="dialog" @ref="Dialog"> <dialog class="dialog" @ref="Dialog">
<div class="compose"> <div class="compose">
<div class="header"> <div class="header">
<button @onclick="CloseDialog"> <button @onclick="CloseDialog">
<Icon Name="Icons.X"/> <Icon Name="Icons.X"/>
</button> </button>
<Dropdown TBind="NoteVisibility" Elements="@DropDownCreate()" @bind-Value="NoteDraft.Visibility"/> <Dropdown TBind="NoteVisibility" Elements="@DropDownCreate()" @bind-Value="NoteDraft.Visibility"/>
<button @onclick="SendNote" class="post-btn"> <button @onclick="SendNote" class="post-btn">
@Loc["ComposePost"]<Icon Name="Icons.PaperPlaneRight"/> @Loc["ComposePost"]<Icon Name="Icons.PaperPlaneRight"/>
</button> </button>
</div>
@if (ReplyOrQuote != null)
{
<div class="reply-or-quote">
<NoteComponent Note="ReplyOrQuote" AsQuote="true"/>
</div> </div>
} @if (ReplyOrQuote != null)
@if (NoteDraft.Cw != null) {
{ <div class="reply-or-quote">
<input @bind="NoteDraft.Cw" class="input cw-field" placeholder="Content Warning"/> <NoteComponent Note="ReplyOrQuote" AsQuote="true"/>
<hr class="separator"/> </div>
} }
<textarea @ref="Textarea" @bind="NoteDraft.Text" class="textarea" placeholder="@TextPlaceholder" rows="5" cols="35" autofocus="autofocus"></textarea> @if (NoteDraft.Cw != null)
<div class="footer"> {
<button class="footer-btn" @onclick="OpenUpload"> <input @bind="NoteDraft.Cw" class="input cw-field" placeholder="Content Warning"/>
<Icon Name="Icons.Upload" Size="1.3rem"></Icon> <hr class="separator"/>
</button> }
<button class="footer-btn" @onclick="ToggleCw"> <textarea @ref="Textarea" @bind="NoteDraft.Text" class="textarea" placeholder="@TextPlaceholder" rows="5" cols="35" autofocus="autofocus"></textarea>
<Icon Name="Icons.EyeSlash" Size="1.3rem"></Icon> <div class="footer">
</button> <button class="footer-btn" @onclick="OpenUpload">
<button @ref="EmojiButton" class="footer-btn positioned" @onclick="ToggleEmojiPicker"> <Icon Name="Icons.Upload" Size="1.3rem"></Icon>
<Icon Name="Icons.Smiley" Size="1.3rem"></Icon> </button>
</button> <button class="footer-btn" @onclick="ToggleCw">
<div class="file-input"> <Icon Name="Icons.EyeSlash" Size="1.3rem"></Icon>
<InputFile @ref="UploadInput" OnChange="Upload">Upload!</InputFile> </button>
<button @ref="EmojiButton" class="footer-btn positioned" @onclick="ToggleEmojiPicker">
<Icon Name="Icons.Smiley" Size="1.3rem"></Icon>
</button>
<div class="file-input">
<InputFile @ref="UploadInput" OnChange="Upload">Upload!</InputFile>
</div>
</div> </div>
</div> <div @onclick="CloseDialog" class="backdrop"></div>
<div @onclick="CloseDialog" class="backdrop"></div>
</div> </div>
</dialog> </dialog>
@ -70,12 +71,7 @@
Cw = null Cw = null
}; };
private Dictionary<string, string> AvailablePlaceholders { get; set; } = new() private Dictionary<string, string> AvailablePlaceholders { get; set; } = new() { { "default", "What's on your mind?" }, { "reply", "Reply goes here!" }, { "quote", "Quote this post!" } };
{
{ "default", "What's on your mind?" },
{ "reply", "Reply goes here!" },
{ "quote", "Quote this post!" }
};
RenderFragment DropdownIcon(NoteVisibility vis) RenderFragment DropdownIcon(NoteVisibility vis)
{ {
@ -108,9 +104,7 @@
new DropdownElement<NoteVisibility> new DropdownElement<NoteVisibility>
{ {
#pragma warning disable BL0005 // Setting this outside the component is fine until this is reworked #pragma warning disable BL0005 // Setting this outside the component is fine until this is reworked
Icon = DropdownIcon(vis), Icon = DropdownIcon(vis), Content = DropdownContent(vis), Selection = vis
Content = DropdownContent(vis),
Selection = vis
#pragma warning restore BL0005 #pragma warning restore BL0005
}) })
.ToList(); .ToList();
@ -203,14 +197,9 @@
private void ResetState() private void ResetState()
{ {
ReplyOrQuote = null; ReplyOrQuote = null;
Attachments = new List<DriveFileResponse>(); Attachments = new List<DriveFileResponse>();
NoteDraft = new NoteCreateRequest NoteDraft = new NoteCreateRequest { Text = "", Visibility = NoteVisibility.Followers, Cw = null };
{
Text = "",
Visibility = NoteVisibility.Followers,
Cw = null
};
TextPlaceholder = AvailablePlaceholders["default"]; TextPlaceholder = AvailablePlaceholders["default"];
} }
@ -227,7 +216,14 @@
} }
await ApiService.Notes.CreateNote(NoteDraft); await ApiService.Notes.CreateNote(NoteDraft);
if (ReplyOrQuote != null)
{
var res = await ApiService.Notes.GetNote(ReplyOrQuote.Id);
if (res != null) _ = MessageService.UpdateNote(res);
}
await CloseDialog(); await CloseDialog();
// FIXME: Implement timeline refresh and call it here. // FIXME: Implement timeline refresh and call it here.
} }

View file

@ -1,7 +1,11 @@
@* ReSharper disable once RedundantUsingDirective *@ @* ReSharper disable once RedundantUsingDirective *@
@using Iceshrimp.Frontend.Components.Note @using Iceshrimp.Frontend.Components.Note
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Shared.Schemas.Web @using Iceshrimp.Shared.Schemas.Web
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject MessageService MessageService
@inject ApiService Api
@implements IDisposable
<div class="@(Depth > 0 ? "descendant" : "root-note")"> <div class="@(Depth > 0 ? "descendant" : "root-note")">
<div class="note-container"> <div class="note-container">
@ -44,13 +48,13 @@
</div> </div>
} }
</div> </div>
<RecursiveNote Note="note" Depth="Depth + 1"/> <RecursiveNote Note="note" Depth="Depth + 1" MaxDepth="MaxDepth"/>
</div> </div>
} }
else else
{ {
<RecursiveNote Note="note" Depth="Depth + 1"/> <RecursiveNote Note="note" Depth="Depth + 1" MaxDepth="MaxDepth"/>
} }
} }
</div> </div>
@ -58,20 +62,43 @@
</div> </div>
@code { @code {
[Parameter] [EditorRequired] public required NoteResponse Note { get; set; } [Parameter] [EditorRequired] public required NoteResponse Note { get; set; }
[Parameter] [EditorRequired] public required int Depth { get; set; } [Parameter] [EditorRequired] public required int Depth { get; set; }
[Parameter] [EditorRequired] public required int MaxDepth { get; set; }
private bool _indented = false; private bool _indented = false;
protected override void OnInitialized() protected override void OnInitialized()
{ {
MessageService.Register(Note.Id, OnNoteChanged);
if (Depth > 0 || Note.Descendants?.Count > 0) if (Depth > 0 || Note.Descendants?.Count > 0)
{ {
_indented = true; _indented = true;
} }
} }
private void OnNoteChanged(object? _, NoteResponse note)
{
var __ = Refresh();
}
private async Task Refresh()
{
if (Depth < MaxDepth)
{
var res = await Api.Notes.GetNoteDescendants(Note.Id, MaxDepth - Depth);
Note.Descendants = res;
StateHasChanged();
}
}
private void OpenNote() private void OpenNote()
{ {
NavigationManager.NavigateTo($"/notes/{Note.Id}"); NavigationManager.NavigateTo($"/notes/{Note.Id}");
} }
public void Dispose()
{
MessageService.Unregister(Note.Id, OnNoteChanged);
}
} }

View file

@ -5,6 +5,8 @@
@using Iceshrimp.Shared.Schemas.Web @using Iceshrimp.Shared.Schemas.Web
@inject ApiService ApiService @inject ApiService ApiService
@inject IJSRuntime Js @inject IJSRuntime Js
@inject MessageService MessageService
@implements IDisposable
@if (_init) @if (_init)
{ {
@ -28,7 +30,7 @@
<div class="descendants"> <div class="descendants">
@foreach (var element in Descendants) @foreach (var element in Descendants)
{ {
<RecursiveNote Note="element" Depth="0"/> <RecursiveNote Note="element" Depth="0" MaxDepth="_depth"/>
} }
</div> </div>
} }
@ -54,6 +56,7 @@ else
private ElementReference RootNoteRef { get; set; } private ElementReference RootNoteRef { get; set; }
private bool _init; private bool _init;
private bool _error; private bool _error;
private int _depth = 20;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
@ -71,13 +74,31 @@ else
return; return;
} }
Descendants = await ApiService.Notes.GetNoteDescendants(NoteId, default); Descendants = await ApiService.Notes.GetNoteDescendants(NoteId, _depth);
Ascendants = await ApiService.Notes.GetNoteAscendants(NoteId, default); Ascendants = await ApiService.Notes.GetNoteAscendants(NoteId, default);
_init = true; _init = true;
StateHasChanged(); StateHasChanged();
} }
protected override void OnInitialized()
{
if (NoteId != null) MessageService.Register(NoteId, OnNoteChanged);
}
private void OnNoteChanged(object? _, NoteResponse note)
{
var __ = Refresh();
}
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();
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
@ -91,4 +112,9 @@ else
.InvokeVoidAsync("ScrollIntoView", RootNoteRef); .InvokeVoidAsync("ScrollIntoView", RootNoteRef);
} }
} }
public void Dispose()
{
if (NoteId != null) MessageService.Unregister(NoteId, OnNoteChanged);
}
} }