@page "/mod/emojis" @using Iceshrimp.Frontend.Core.Services @using Iceshrimp.Frontend.Localization @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization @using Microsoft.AspNetCore.Components.Sections @using Iceshrimp.Assets.PhosphorIcons @using Iceshrimp.Frontend.Core.Miscellaneous @using Iceshrimp.Shared.Schemas.Web @using Iceshrimp.Frontend.Components @attribute [Authorize(Roles = "moderator")] @layout ModerationLayout @inject ApiService Api; @inject GlobalComponentSvc Global; @inject IJSRuntime Js; @inject IStringLocalizer Loc; @inject ILogger Logger; @Loc["Custom Emojis"] @if (State is State.Empty or State.Loaded && Source == "local") { }
@if (State is State.Empty or State.Error or State.Loaded) { } @if (State is State.Loaded) { @if (_displayType is DisplayType.All ) {
@foreach (var emoji in DisplayedEmojis) { }
@if (PaginationData is { Next: not null } && Source == "remote") { } } @if (_displayType is DisplayType.Categories && Source == "local") { foreach (var el in LocalEmojiCategories) { @el.Key
@foreach (var emoji in el.Value) { }
} } @if (_displayType is DisplayType.Categories && Source == "remote") { @foreach (var category in DisplayedCategories) { } } } @if (State is State.Empty) { This instance has no emojis } @if (State is State.Loading) {
}
Upload Import
@code { private List DisplayedEmojis { get; set; } = []; private List StoredLocalEmojis { get; set; } = []; private List StoredRemoteEmojis { get; set; } = []; private Dictionary> LocalEmojiCategories { get; set; } = new(); private HashSet Categories { get; set; } = []; private HashSet DisplayedCategories { get; set; } = []; private string EmojiFilter { 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 InputFile ImportInput { get; set; } = null!; private IBrowserFile ImportFile { get; set; } = null!; private PaginationData? PaginationData { get; set; } private IJSObjectReference _module = null!; private DisplayType _displayType = DisplayType.All; private void FilterEmojis() { if (Source == "remote" && _displayType == DisplayType.All || Source == "local") { DisplayedEmojis = StoredRemoteEmojis.Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) > 0).ToList(); } if (Source == "remote" && _displayType == DisplayType.Categories) { DisplayedCategories = Categories.Where(p => p.Contains(EmojiFilter.Trim())).ToHashSet(); } if (Source == "local" && _displayType == DisplayType.Categories) { LocalEmojiCategories = StoredLocalEmojis .Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) != 0) .OrderBy(p => p.Name) .ThenBy(p => p.Id) .GroupBy(p => p.Category) .OrderBy(p => string.IsNullOrEmpty(p.Key)) .ThenBy(p => p.Key) .ToDictionary(p => p.Key ?? "Other", p => p.ToList()); } } private enum DisplayType { Categories, All } private async Task GetEmojis() { if (Source == "local") { State = State.Loading; StoredLocalEmojis = await Api.Emoji.GetAllEmojiAsync(); FilterEmojis(); State = StoredLocalEmojis.Count == 0 ? State.Empty : State.Loaded; } if (Source == "remote") { if (_displayType is DisplayType.Categories) { await FetchHosts(); } else { var res = await Api.Emoji.GetRemoteEmojiAsync(new PaginationQuery()); PaginationData = res.Links; StoredRemoteEmojis = res.Data; } } if (EmojiFilter.Length == 0) { switch (Source) { case "remote": DisplayedEmojis = StoredRemoteEmojis; break; case "local": DisplayedEmojis = StoredLocalEmojis; break; } } } private async Task FetchHosts() { // Due to query weirdness, MinID and MaxID are reversed here var pq = new PaginationQuery { MinId = Categories.LastOrDefault(), Limit = 250 }; var res = await Api.Emoji.GetRemoteEmojiHostsAsync(pq); foreach (var el in res) { var op = Categories.Add(el.Entity); if (!op) Logger.LogWarning($"Duplicate host entry: {el.Entity}"); } if (EmojiFilter.Length == 0) DisplayedCategories = Categories; } private async Task FetchMore() { if (PaginationData?.Next == null) return; var pq = new PaginationQuery { MaxId = PaginationData.Next?.Split('=')[1], Limit = PaginationData.Limit }; var res = await Api.Emoji.GetRemoteEmojiAsync(pq); PaginationData = res.Links; StoredRemoteEmojis.AddRange(res.Data); if (EmojiFilter.Length == 0) { DisplayedEmojis = StoredRemoteEmojis; } StateHasChanged(); } // 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(); } }