[frontend/pages] Add page for managing local and remote custom emojis
This commit is contained in:
parent
91e0f09bc5
commit
fdca264241
4 changed files with 203 additions and 0 deletions
|
@ -27,6 +27,12 @@
|
|||
<span class="text">@Loc["Overview"]</span>
|
||||
</div>
|
||||
</NavLink>
|
||||
<NavLink href="/mod/emojis">
|
||||
<div class="sidebar-btn">
|
||||
<Icon Name="Icons.Smiley"/>
|
||||
<span class="text">@Loc["Custom Emojis"]</span>
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
|
|
@ -9,6 +9,9 @@ internal class EmojiControllerModel(ApiClient api)
|
|||
public Task<List<EmojiResponse>> GetAllEmojiAsync() =>
|
||||
api.CallAsync<List<EmojiResponse>>(HttpMethod.Get, "/emoji");
|
||||
|
||||
public Task<List<EmojiResponse>> GetRemoteEmojiAsync() =>
|
||||
api.CallAsync<List<EmojiResponse>>(HttpMethod.Get, "/emoji/remote");
|
||||
|
||||
public Task<EmojiResponse> UploadEmojiAsync(IBrowserFile file) =>
|
||||
api.CallAsync<EmojiResponse>(HttpMethod.Post, "/emoji", data: file);
|
||||
|
||||
|
|
120
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor
Normal file
120
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor
Normal file
|
@ -0,0 +1,120 @@
|
|||
@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 IStringLocalizer<Localization> Loc;
|
||||
|
||||
<SectionContent SectionName="top-bar">
|
||||
<Icon Name="Icons.Smiley"></Icon>
|
||||
@Loc["Custom Emojis"]
|
||||
</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)
|
||||
{
|
||||
<div class="emoji-entry">
|
||||
<InlineEmoji Name="@emoji.Name" Url="@emoji.PublicUrl" Size="3rem"/>
|
||||
<div class="emoji-details">
|
||||
<span class="emoji-name">@emoji.Name</span>
|
||||
<span>
|
||||
@foreach (var alias in emoji.Aliases)
|
||||
{
|
||||
<span class="emoji-alias">@alias</span>
|
||||
}
|
||||
</span>
|
||||
<span class="labels">
|
||||
@if (Source == "remote")
|
||||
{
|
||||
<Icon Name="Icons.ArrowSquareOut" title="@Loc["Remote"]"/>
|
||||
}
|
||||
@if (emoji.Sensitive)
|
||||
{
|
||||
<Icon Name="Icons.EyeSlash" title="@Loc["Sensitive"]"/>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(emoji.License))
|
||||
{
|
||||
<Icon Name="Icons.Article" title="@emoji.License"/>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (State is State.Empty)
|
||||
{
|
||||
<div class="body">
|
||||
<i>This instance has no emojis</i>
|
||||
</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 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;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await GetEmojis();
|
||||
}
|
||||
}
|
74
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css
Normal file
74
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css
Normal file
|
@ -0,0 +1,74 @@
|
|||
.emoji-search {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-from {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
display: inline-block;
|
||||
margin: 1rem 0 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.emoji-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.emoji-entry {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--foreground-color);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.emoji-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-wrap: wrap;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.emoji-name::before,
|
||||
.emoji-name::after {
|
||||
display: inline;
|
||||
content: ":";
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.emoji-alias {
|
||||
opacity: 0.7;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.emoji-alias::before,
|
||||
.emoji-alias::after {
|
||||
display: inline;
|
||||
content: ":";
|
||||
}
|
||||
|
||||
::deep {
|
||||
.labels .ph {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
color: var(--notice-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.emoji-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue