a fork of iceshrimp.net but a tweaked frontend to my personal liking. waow
fediverse social-media social iceshrimp fedi
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[frontend/pages] Add batch select toggle and actions to local emoji management

authored by

pancakes and committed by
Iceshrimp development
44f867b2 eb19998e

+178 -27
+21 -8
Iceshrimp.Frontend/Components/EmojiManagementEntry.razor
··· 8 8 @inject GlobalComponentSvc Global; 9 9 @inject IStringLocalizer<Localization> Loc; 10 10 11 - <div @ref="EmojiButton" class="emoji-entry" @onclick="SelectEmoji" @onclick:stopPropagation="true"> 11 + <div @ref="EmojiButton" class="emoji-entry @(Selected ? "batch-selected" : "")" @onclick="SelectEmoji" @onclick:stopPropagation="true"> 12 12 <InlineEmoji Name="@Emoji.Name" Url="@Emoji.PublicUrl" Hover="@true" Size="3rem"/> 13 13 <div class="emoji-details"> 14 14 <span class="emoji-name">@Emoji.Name</span> ··· 82 82 </div> 83 83 84 84 @code { 85 - [Parameter, EditorRequired] public required EmojiResponse Emoji { get; set; } 86 - [Parameter] public bool Remote { get; set; } 87 - [Parameter] public EventCallback<EmojiResponse> DeleteEmoji { get; set; } 88 - [Parameter] public EventCallback FilterEmojis { get; set; } 89 - private ElementReference EmojiButton { get; set; } 90 - private Menu EmojiMenu { get; set; } = null!; 85 + [Parameter, EditorRequired] public required EmojiResponse Emoji { get; set; } 86 + [Parameter] public bool Remote { get; set; } 87 + [Parameter] public EventCallback<EmojiResponse> DeleteEmoji { get; set; } 88 + [Parameter] public EventCallback FilterEmojis { get; set; } 89 + [Parameter] public EventCallback<EmojiResponse> ToggleEmoji { get; set; } 90 + [Parameter] public bool BatchSelection { get; set; } 91 + [Parameter] public bool Selected { get; set; } 92 + private ElementReference EmojiButton { get; set; } 93 + private Menu EmojiMenu { get; set; } = null!; 91 94 92 - private void SelectEmoji() => EmojiMenu.Toggle(EmojiButton); 95 + private async Task SelectEmoji() 96 + { 97 + if (BatchSelection) 98 + { 99 + await ToggleEmoji.InvokeAsync(Emoji); 100 + } 101 + else 102 + { 103 + EmojiMenu.Toggle(EmojiButton); 104 + } 105 + } 93 106 94 107 private async Task Clone() => 95 108 await Global.ConfirmDialog?.Confirm(new EventCallback<bool>(this, CloneCallback), @Loc["Clone {0} from {1}?", Emoji.Name, Emoji.Category ?? ""], Icons.Copy, @Loc["Clone"])!;
+1 -1
Iceshrimp.Frontend/Components/EmojiManagementEntry.razor.css
··· 9 9 cursor: pointer; 10 10 } 11 11 12 - .emoji-entry:hover { 12 + .emoji-entry:hover, .emoji-entry.batch-selected { 13 13 background-color: var(--hover-color); 14 14 } 15 15
+3
Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs
··· 41 41 42 42 public Task<EmojiResponse?> GetEmojiAsync(string id) => 43 43 api.CallNullableAsync<EmojiResponse>(HttpMethod.Get, $"/emoji/{id}"); 44 + 45 + public Task<List<EmojiResponse>> BatchUpdateEmojiAsync(BatchUpdateEmojiRequest request) => 46 + api.CallAsync<List<EmojiResponse>>(HttpMethod.Patch, $"/emoji/batch", data: request); 44 47 }
+153 -18
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor
··· 22 22 @Loc["Custom Emojis"] 23 23 @if (State is State.Empty or State.Loaded) 24 24 { 25 - <span class="action btn" @onclick="OpenUpload"> 26 - <Icon Name="Icons.Upload"/> 27 - <span>@Loc["Upload"]</span> 28 - </span> 29 - <span class="action btn" @onclick="OpenImport"> 30 - <Icon Name="Icons.FileArrowUp"/> 31 - <span>@Loc["Import pack"]</span> 32 - </span> 25 + @if (BatchSelection) 26 + { 27 + <span class="action btn" @onclick="BatchSetCategory" disabled="@(Selected.Count == 0)"> 28 + <Icon Name="Icons.Folder"/> 29 + <span>@Loc["Set category"]</span> 30 + </span> 31 + <span class="action btn" @onclick="BatchSetLicense" disabled="@(Selected.Count == 0)"> 32 + <Icon Name="Icons.Article"/> 33 + <span>@Loc["Set license"]</span> 34 + </span> 35 + <span class="action btn" @onclick="BatchDelete" disabled="@(Selected.Count == 0)"> 36 + <Icon Name="Icons.Trash"/> 37 + <span>@Loc["Delete"]</span> 38 + </span> 39 + <span class="action btn" @onclick="ToggleBatchSelection"> 40 + <Icon Name="Icons.Selection"/> 41 + <span>@Loc["Single select"]</span> 42 + </span> 43 + } 44 + else 45 + { 46 + <span class="action btn" @onclick="OpenUpload"> 47 + <Icon Name="Icons.Upload"/> 48 + <span>@Loc["Upload"]</span> 49 + </span> 50 + <span class="action btn" @onclick="OpenImport"> 51 + <Icon Name="Icons.FileArrowUp"/> 52 + <span>@Loc["Import pack"]</span> 53 + </span> 54 + <span class="action btn" @onclick="ToggleBatchSelection"> 55 + <Icon Name="Icons.SelectionPlus"/> 56 + <span>@Loc["Batch select"]</span> 57 + </span> 58 + } 33 59 } 34 - @if (State is not State.Loading) 60 + @if (State is not State.Loading && !BatchSelection) 35 61 { 36 62 <a class="action btn" href="/mod/emojis/remote"> 37 63 <Icon Name="Icons.ArrowSquareOut"/> ··· 55 81 <div class="emoji-list"> 56 82 @foreach (var emoji in category.Value) 57 83 { 58 - <EmojiManagementEntry Emoji="@emoji" DeleteEmoji="DeleteEmoji" FilterEmojis="FilterEmojis"/> 84 + <EmojiManagementEntry Emoji="@emoji" DeleteEmoji="DeleteEmoji" 85 + FilterEmojis="FilterEmojis" ToggleEmoji="SelectEmoji" BatchSelection="BatchSelection" 86 + Selected="@(Selected.Contains(emoji))"/> 59 87 } 60 88 </div> 61 89 } ··· 77 105 </div> 78 106 79 107 @code { 80 - private List<EmojiResponse> Emojis { get; set; } = []; 81 - private Dictionary<string, List<EmojiResponse>> Categories { get; set; } = new(); 82 - private string EmojiFilter { get; set; } = ""; 83 - private State State { get; set; } 84 - private InputFile UploadInput { get; set; } = null!; 85 - private IBrowserFile UploadFile { get; set; } = null!; 86 - private InputFile ImportInput { get; set; } = null!; 87 - private IBrowserFile ImportFile { get; set; } = null!; 108 + private List<EmojiResponse> Emojis { get; set; } = []; 109 + private Dictionary<string, List<EmojiResponse>> Categories { get; set; } = new(); 110 + private string EmojiFilter { get; set; } = ""; 111 + private State State { get; set; } 112 + private InputFile UploadInput { get; set; } = null!; 113 + private IBrowserFile UploadFile { get; set; } = null!; 114 + private InputFile ImportInput { get; set; } = null!; 115 + private IBrowserFile ImportFile { get; set; } = null!; 116 + private bool BatchSelection { get; set; } 117 + private List<EmojiResponse> Selected { get; set; } = []; 88 118 private IJSObjectReference _module = null!; 89 119 90 120 private void FilterEmojis() ··· 100 130 StateHasChanged(); 101 131 } 102 132 133 + private void SelectEmoji(EmojiResponse emoji) 134 + { 135 + if (!BatchSelection) return; 136 + 137 + if (Selected.Contains(emoji)) 138 + { 139 + Selected.Remove(emoji); 140 + } 141 + else 142 + { 143 + Selected.Add(emoji); 144 + } 145 + 146 + StateHasChanged(); 147 + } 148 + 103 149 private async Task GetEmojis() 104 150 { 105 151 State = State.Loading; ··· 119 165 120 166 private void DeleteEmoji(EmojiResponse emoji) 121 167 { 168 + Selected.Remove(emoji); 122 169 Emojis.Remove(emoji); 123 170 } 124 171 ··· 132 179 private async Task OpenImport() 133 180 { 134 181 await _module.InvokeVoidAsync("openUpload", ImportInput.Element); 182 + } 183 + 184 + private void ToggleBatchSelection() 185 + { 186 + BatchSelection = !BatchSelection; 187 + if (!BatchSelection) Selected.Clear(); 188 + StateHasChanged(); 189 + } 190 + 191 + private async Task BatchSetCategory() 192 + { 193 + if (!BatchSelection || Selected.Count == 0) return; 194 + await Global.PromptDialog?.Prompt(new EventCallback<string?>(this, BatchSetCategoryCallback), Loc["Set category for {0} emojis", Selected.Count], "", null, true)!; 195 + } 196 + 197 + private async Task BatchSetCategoryCallback(string? category) 198 + { 199 + if (category == null) return; 200 + 201 + try 202 + { 203 + var res = await Api.Emoji.BatchUpdateEmojiAsync(new BatchUpdateEmojiRequest { Ids = Selected.Select(p => p.Id).ToList(), Category = category }); 204 + 205 + foreach (var emoji in res) 206 + { 207 + var e = Emojis.First(p => p.Id == emoji.Id); 208 + e.Category = emoji.Category; 209 + } 210 + 211 + FilterEmojis(); 212 + } 213 + catch (ApiException e) 214 + { 215 + await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 216 + } 217 + } 218 + 219 + private async Task BatchSetLicense() 220 + { 221 + if (!BatchSelection || Selected.Count == 0) return; 222 + await Global.PromptDialog?.Prompt(new EventCallback<string?>(this, BatchSetLicenseCallback), Loc["Set license for {0} emojis", Selected.Count], "", null, true, true)!; 223 + } 224 + 225 + private async Task BatchSetLicenseCallback(string? license) 226 + { 227 + if (license == null) return; 228 + 229 + try 230 + { 231 + var res = await Api.Emoji.BatchUpdateEmojiAsync(new BatchUpdateEmojiRequest { Ids = Selected.Select(p => p.Id).ToList(), License = license }); 232 + 233 + foreach (var emoji in res) 234 + { 235 + var e = Emojis.First(p => p.Id == emoji.Id); 236 + e.License = emoji.License; 237 + } 238 + 239 + StateHasChanged(); 240 + } 241 + catch (ApiException e) 242 + { 243 + await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 244 + } 245 + } 246 + 247 + private async Task BatchDelete() 248 + { 249 + if (!BatchSelection || Selected.Count == 0) return; 250 + await Global.ConfirmDialog?.Confirm(new EventCallback<bool>(this, BatchDeleteCallback), Loc["Delete {0} emojis?", Selected.Count], Icons.Trash, Loc["Delete"])!; 251 + } 252 + 253 + private async Task BatchDeleteCallback(bool delete) 254 + { 255 + if (!delete) return; 256 + try 257 + { 258 + foreach (var emoji in Selected) 259 + { 260 + var res = await Api.Emoji.DeleteEmojiAsync(emoji.Id); 261 + if (res) DeleteEmoji(emoji); 262 + } 263 + 264 + FilterEmojis(); 265 + } 266 + catch (ApiException e) 267 + { 268 + await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 269 + } 135 270 } 136 271 137 272 private async Task Upload(InputFileChangeEventArgs e)