[frontend/pages] Add upload and import actions to emoji management page

This commit is contained in:
pancakes 2025-02-04 00:52:06 +10:00 committed by Laura Hausmann
parent fd5ced1c59
commit e0e8fe8b72
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 96 additions and 2 deletions

View file

@ -1,3 +1,4 @@
using Iceshrimp.Frontend.Core.Miscellaneous;
using Iceshrimp.Frontend.Core.Services; using Iceshrimp.Frontend.Core.Services;
using Iceshrimp.Shared.Schemas.Web; using Iceshrimp.Shared.Schemas.Web;
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
@ -12,12 +13,16 @@ internal class EmojiControllerModel(ApiClient api)
public Task<List<EmojiResponse>> GetRemoteEmojiAsync() => public Task<List<EmojiResponse>> GetRemoteEmojiAsync() =>
api.CallAsync<List<EmojiResponse>>(HttpMethod.Get, "/emoji/remote"); api.CallAsync<List<EmojiResponse>>(HttpMethod.Get, "/emoji/remote");
public Task<EmojiResponse> UploadEmojiAsync(IBrowserFile file) => public Task<EmojiResponse> UploadEmojiAsync(IBrowserFile file, string? name) =>
api.CallAsync<EmojiResponse>(HttpMethod.Post, "/emoji", data: file); api.CallAsync<EmojiResponse>(HttpMethod.Post, "/emoji",
name != null ? QueryString.Create("name", name) : QueryString.Empty, file);
public Task<EmojiResponse?> CloneEmojiAsync(string name, string host) => public Task<EmojiResponse?> CloneEmojiAsync(string name, string host) =>
api.CallNullableAsync<EmojiResponse>(HttpMethod.Post, $"/emoji/clone/{name}@{host}"); api.CallNullableAsync<EmojiResponse>(HttpMethod.Post, $"/emoji/clone/{name}@{host}");
public Task<bool> ImportEmojiAsync(IBrowserFile file) =>
api.CallNullableAsync(HttpMethod.Post, "/emoji/import", data: file);
public Task<EmojiResponse?> UpdateEmojiAsync(string id, UpdateEmojiRequest request) => public Task<EmojiResponse?> UpdateEmojiAsync(string id, UpdateEmojiRequest request) =>
api.CallNullableAsync<EmojiResponse>(HttpMethod.Patch, $"/emoji/{id}", data: request); api.CallNullableAsync<EmojiResponse>(HttpMethod.Patch, $"/emoji/{id}", data: request);

View file

@ -11,11 +11,22 @@
@attribute [Authorize(Roles = "moderator")] @attribute [Authorize(Roles = "moderator")]
@layout ModerationLayout @layout ModerationLayout
@inject ApiService Api; @inject ApiService Api;
@inject GlobalComponentSvc Global;
@inject IJSRuntime Js;
@inject IStringLocalizer<Localization> Loc; @inject IStringLocalizer<Localization> Loc;
<SectionContent SectionName="top-bar"> <SectionContent SectionName="top-bar">
<Icon Name="Icons.Smiley"></Icon> <Icon Name="Icons.Smiley"></Icon>
@Loc["Custom Emojis"] @Loc["Custom Emojis"]
@if (State is State.Empty or State.Loaded && Source == "local")
{
<span class="action btn" @onclick="OpenUpload" title="@Loc["Upload emoji"]">
<Icon Name="Icons.Upload"/>
</span>
<span class="action btn" @onclick="OpenImport" title="@Loc["Import emoji pack"]">
<Icon Name="Icons.FileArrowUp"/>
</span>
}
</SectionContent> </SectionContent>
@if (State is State.Loaded) @if (State is State.Loaded)
@ -59,6 +70,11 @@
</div> </div>
} }
<div class="file-input">
<InputFile @ref="UploadInput" OnChange="Upload" accept="image/*">Upload</InputFile>
<InputFile @ref="ImportInput" OnChange="Import" accept=".zip">Import</InputFile>
</div>
@code { @code {
private List<EmojiResponse> Emojis { get; set; } = []; private List<EmojiResponse> Emojis { get; set; } = [];
private Dictionary<string, List<EmojiResponse>> Categories { get; set; } = new(); private Dictionary<string, List<EmojiResponse>> Categories { get; set; } = new();
@ -66,6 +82,12 @@
private string HostFilter { get; set; } = ""; private string HostFilter { get; set; } = "";
private string Source { get; set; } = "local"; private string Source { get; set; } = "local";
private State State { get; set; } private State State { get; set; }
private InputFile UploadInput { get; set; } = null!;
private IBrowserFile UploadFile { get; set; } = null!;
private string UploadName { get; set; } = "";
private InputFile ImportInput { get; set; } = null!;
private IBrowserFile ImportFile { get; set; } = null!;
private IJSObjectReference _module = null!;
private void FilterEmojis() private void FilterEmojis()
{ {
@ -96,8 +118,68 @@
} }
} }
// The <InputFile> Component is hidden, and triggered by a sepperate button.
// That way we get it's functionality, without the styling limitations of the InputFile component
private async Task OpenUpload()
{
await _module.InvokeVoidAsync("openUpload", UploadInput.Element);
}
private async Task OpenImport()
{
await _module.InvokeVoidAsync("openUpload", ImportInput.Element);
}
private async Task Upload(InputFileChangeEventArgs e)
{
if (!e.File.ContentType.StartsWith("image/")) return;
UploadFile = e.File;
await Global.PromptDialog?.Prompt(new EventCallback<string?>(this, UploadCallback), Loc["Set emoji name"], "", e.File.Name.Split(".")[0], buttonText: Loc["Upload"])!;
}
private async Task UploadCallback(string? name)
{
if (name == null) return;
try
{
await Api.Emoji.UploadEmojiAsync(UploadFile, name);
await Global.NoticeDialog?.Display(Loc["Successfully uploaded {0}", name])!;
await GetEmojis();
}
catch (ApiException e)
{
await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!;
}
}
private async Task Import(InputFileChangeEventArgs e)
{
if (e.File.ContentType != "application/zip") return;
ImportFile = e.File;
await Global.ConfirmDialog?.Confirm(new EventCallback<bool>(this, ImportCallback), Loc["Import {0}?", e.File.Name], Icons.FileArrowUp, Loc["Import"])!;
}
private async Task ImportCallback(bool import)
{
if (!import) return;
try
{
await Api.Emoji.ImportEmojiAsync(ImportFile);
await Global.NoticeDialog?.Display(Loc["Successfully imported emoji pack"])!;
await GetEmojis();
}
catch (ApiException e)
{
await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!;
}
}
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_module = await Js.InvokeAsync<IJSObjectReference>("import",
"./Pages/Moderation/CustomEmojis.razor.js");
await GetEmojis(); await GetEmojis();
} }
} }

View file

@ -31,6 +31,10 @@
margin-top: 5rem; margin-top: 5rem;
} }
.file-input {
display: none;
}
@media (max-width: 1000px) { @media (max-width: 1000px) {
.emoji-list { .emoji-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View file

@ -0,0 +1,3 @@
export function openUpload(element) {
element.click();
}