From e0e8fe8b7225235d6be5184058b301d090281a3e Mon Sep 17 00:00:00 2001 From: pancakes Date: Tue, 4 Feb 2025 00:52:06 +1000 Subject: [PATCH] [frontend/pages] Add upload and import actions to emoji management page --- .../ControllerModels/EmojiControllerModel.cs | 9 +- .../Pages/Moderation/CustomEmojis.razor | 82 +++++++++++++++++++ .../Pages/Moderation/CustomEmojis.razor.css | 4 + .../Pages/Moderation/CustomEmojis.razor.js | 3 + 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.js diff --git a/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs b/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs index d3975c54..13c5133d 100644 --- a/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs +++ b/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs @@ -1,3 +1,4 @@ +using Iceshrimp.Frontend.Core.Miscellaneous; using Iceshrimp.Frontend.Core.Services; using Iceshrimp.Shared.Schemas.Web; using Microsoft.AspNetCore.Components.Forms; @@ -12,12 +13,16 @@ internal class EmojiControllerModel(ApiClient api) public Task> GetRemoteEmojiAsync() => api.CallAsync>(HttpMethod.Get, "/emoji/remote"); - public Task UploadEmojiAsync(IBrowserFile file) => - api.CallAsync(HttpMethod.Post, "/emoji", data: file); + public Task UploadEmojiAsync(IBrowserFile file, string? name) => + api.CallAsync(HttpMethod.Post, "/emoji", + name != null ? QueryString.Create("name", name) : QueryString.Empty, file); public Task CloneEmojiAsync(string name, string host) => api.CallNullableAsync(HttpMethod.Post, $"/emoji/clone/{name}@{host}"); + public Task ImportEmojiAsync(IBrowserFile file) => + api.CallNullableAsync(HttpMethod.Post, "/emoji/import", data: file); + public Task UpdateEmojiAsync(string id, UpdateEmojiRequest request) => api.CallNullableAsync(HttpMethod.Patch, $"/emoji/{id}", data: request); diff --git a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor index a64026a9..e31eb592 100644 --- a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor +++ b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor @@ -11,11 +11,22 @@ @attribute [Authorize(Roles = "moderator")] @layout ModerationLayout @inject ApiService Api; +@inject GlobalComponentSvc Global; +@inject IJSRuntime Js; @inject IStringLocalizer Loc; @Loc["Custom Emojis"] + @if (State is State.Empty or State.Loaded && Source == "local") + { + + + + + + + } @if (State is State.Loaded) @@ -59,6 +70,11 @@ } +
+ Upload + Import +
+ @code { private List Emojis { get; set; } = []; private Dictionary> Categories { get; set; } = new(); @@ -66,6 +82,12 @@ private string HostFilter { get; set; } = ""; private string Source { get; set; } = "local"; 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() { @@ -96,8 +118,68 @@ } } + // The 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(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(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() { + _module = await Js.InvokeAsync("import", + "./Pages/Moderation/CustomEmojis.razor.js"); await GetEmojis(); } } diff --git a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css index df8f466c..15c0cbc7 100644 --- a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css +++ b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css @@ -31,6 +31,10 @@ margin-top: 5rem; } +.file-input { + display: none; +} + @media (max-width: 1000px) { .emoji-list { grid-template-columns: 1fr; diff --git a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.js b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.js new file mode 100644 index 00000000..5eb113bd --- /dev/null +++ b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.js @@ -0,0 +1,3 @@ +export function openUpload(element) { + element.click(); +} \ No newline at end of file