Iceshrimp.NET/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor

185 lines
6.9 KiB
Text

@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<Localization> Loc;
<SectionContent SectionName="top-bar">
<Icon Name="Icons.Smiley"></Icon>
@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>
@if (State is State.Loaded)
{
<div class="body">
<div class="emoji-search">
<input @bind="EmojiFilter" @bind:event="oninput" @bind:after="FilterEmojis" class="search" type="text" placeholder="Search" aria-label="search"/>
@if (Source == "remote")
{
<input @bind="HostFilter" @bind:event="oninput" @bind:after="FilterEmojis" class="search" type="text" placeholder="Host" aria-label="host"/>
}
<select class="search-from" @bind="Source" @bind:after="GetEmojis">
<option value="local">@Loc["Local"]</option>
<option value="remote">@Loc["Remote"]</option>
</select>
</div>
@foreach (var category in Categories)
{
<span class="category-name">@category.Key</span>
<div class="emoji-list">
@foreach (var emoji in category.Value)
{
<EmojiManagementEntry Emoji="@emoji" Source="@Source" GetEmojis="GetEmojis"/>
}
</div>
}
</div>
}
@if (State is State.Empty)
{
<div class="body">
<i>This instance has no emojis</i>
</div>
}
@if (State is State.Loading)
{
<div class="body">
<div class="loading">
<Icon Name="Icons.Spinner" Size="3rem"/>
</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 {
private List<EmojiResponse> Emojis { get; set; } = [];
private Dictionary<string, List<EmojiResponse>> Categories { get; set; } = new();
private string EmojiFilter { get; set; } = "";
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()
{
Categories = Emojis
.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)
.Where(p => Source == "local" || (Source == "remote" && (p.Key?.Contains(HostFilter) ?? false)))
.OrderBy(p => string.IsNullOrEmpty(p.Key))
.ThenBy(p => p.Key)
.ToDictionary(p => p.Key ?? "Other", p => p.ToList());
}
private async Task GetEmojis()
{
State = State.Loading;
Emojis = Source == "remote" ? await Api.Emoji.GetRemoteEmojiAsync() : await Api.Emoji.GetAllEmojiAsync();
if (Emojis.Count == 0)
{
State = State.Empty;
}
else
{
FilterEmojis();
State = State.Loaded;
}
}
// 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()
{
_module = await Js.InvokeAsync<IJSObjectReference>("import",
"./Pages/Moderation/CustomEmojis.razor.js");
await GetEmojis();
}
}