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/services] Dedupe Js interop scrollTo, getSelectionStart, openUpload

pancakes 7bccc421 9ac412a4

+137 -127
+7 -5
Iceshrimp.Frontend/Components/AttachmentView.razor
··· 1 1 @using Iceshrimp.Assets.PhosphorIcons 2 + @using Iceshrimp.Frontend.Core.Services 2 3 @using Iceshrimp.Shared.Schemas.Web 3 4 @inject IJSRuntime Js 5 + @inject JsService JsSvc 4 6 5 7 <dialog class="attachment-view" @onkeydown="OnKeyDown" @ref="Dialog"> 6 8 <button @onclick="CloseDialog" @onclick:stopPropagation="true" class="close"> ··· 82 84 } 83 85 84 86 Focused += 1; 85 - await Module.InvokeVoidAsync("scrollTo", _refs[Focused]); 87 + await JsSvc.ScrollToElementAsync(_refs[Focused]); 86 88 } 87 89 88 90 private async Task Prev() ··· 93 95 } 94 96 95 97 Focused -= 1; 96 - await Module.InvokeVoidAsync("scrollTo", _refs[Focused]); 98 + await JsSvc.ScrollToElementAsync(_refs[Focused]); 97 99 } 98 100 99 101 public async Task OpenDialog(NoteAttachment attachment) 100 102 { 101 - await Module.InvokeVoidAsync("openDialog", Dialog); 103 + await JsSvc.ShowDialogAsync(Dialog); 102 104 ScrollWidth = await Module.InvokeAsync<int>("getScrollWidth", Scroller); 103 105 var index = Attachments.IndexOf(attachment); 104 - await Module.InvokeVoidAsync("scrollTo", _refs[index]); 106 + await JsSvc.ScrollToElementAsync(_refs[index]); 105 107 } 106 108 107 109 private async Task CloseDialog() 108 110 { 109 - await Module.InvokeVoidAsync("closeDialog", Dialog); 111 + await JsSvc.CloseDialogAsync(Dialog); 110 112 } 111 113 112 114 protected override async Task OnAfterRenderAsync(bool firstRender)
-12
Iceshrimp.Frontend/Components/AttachmentView.razor.js
··· 1 - export function openDialog(element) { 2 - element.showModal() 3 - } 4 - 5 - export function closeDialog(element) { 6 - element.close() 7 - } 8 - 9 - export function scrollTo(element) { 10 - element.scrollIntoView() 11 - } 12 - 13 1 export function getScrollLeft(element) { 14 2 return element.scrollLeft 15 3 }
+6 -4
Iceshrimp.Frontend/Components/Compose.razor
··· 8 8 @using Iceshrimp.Shared.Schemas.Web 9 9 @using Microsoft.Extensions.Localization 10 10 @inject IJSRuntime Js 11 + @inject JsService JsSvc 11 12 @inject ApiService ApiService 12 13 @inject ComposeService ComposeService 13 14 @inject EmojiService EmojiService ··· 303 304 // That way we get it's functionality, without the styling limitations of the InputFile component 304 305 private async Task OpenUpload() 305 306 { 306 - await _module.InvokeVoidAsync("openUpload", UploadInput.Element); 307 + if (UploadInput.Element is not { } element) return; 308 + await JsSvc.ClickElementAsync(element); 307 309 } 308 310 309 311 public async Task OpenDialogRedraft(NoteResponse note) ··· 317 319 NoteDraft.ReplyId = note.ReplyId; 318 320 UploadingFiles = 0; 319 321 StateHasChanged(); 320 - await _module.InvokeVoidAsync("openDialog", Dialog); 322 + await JsSvc.ShowDialogAsync(Dialog, false); 321 323 } 322 324 323 325 public async Task OpenDialog(NoteBase? replyTo = null, NoteBase? quote = null) ··· 352 354 353 355 StateHasChanged(); 354 356 355 - await _module.InvokeVoidAsync("openDialog", Dialog); 357 + await JsSvc.ShowDialogAsync(Dialog, false); 356 358 } 357 359 358 360 private async Task<List<string>> EnumerateMentions(NoteBase noteBase) ··· 403 405 404 406 private async Task CloseDialog() 405 407 { 406 - await _module.InvokeVoidAsync("closeDialog", Dialog); 408 + await JsSvc.CloseDialogAsync(Dialog); 407 409 } 408 410 409 411 private async Task SendNote()
-16
Iceshrimp.Frontend/Components/Compose.razor.js
··· 1 - export function openDialog(element) { 2 - element.show() 3 - } 4 - 5 - export function closeDialog(element) { 6 - element.close() 7 - } 8 - 9 - export function getSelectionStart(element) { 10 - return element.selectionStart; 11 - } 12 - 13 - export function openUpload(element) { 14 - element.click(); 15 - } 16 - 17 1 /** 18 2 * Sets up paste handling in the main compose textarea to allow pasting quotes and media attachments. 19 3 * Is called by Blazor when the compose dialog is opened.
+9 -14
Iceshrimp.Frontend/Components/EmojiPicker.razor
··· 5 5 @using Microsoft.Extensions.Localization 6 6 @inject EmojiService EmojiService 7 7 @inject GlobalComponentSvc GlobalComponentSvc 8 - @inject IJSRuntime Js 8 + @inject JsService Js 9 9 @inject IStringLocalizer<Localization> Loc; 10 10 11 11 <dialog class="dialog" @ref="EmojiPickerRef"> ··· 34 34 private EventCallback<EmojiResponse> OnEmojiSelect { get; set; } 35 35 private List<EmojiResponse> EmojiList { get; set; } = []; 36 36 private ElementReference EmojiPickerRef { get; set; } 37 - private float _top; 38 - private float _left; 39 - private IJSInProcessObjectReference _module = null!; 37 + private double _top; 38 + private double _left; 40 39 41 40 private string EmojiFilter { get; set; } = ""; 42 41 private Dictionary<string, List<EmojiResponse>> Categories { get; set; } = []; 43 42 44 - protected override async Task OnInitializedAsync() 43 + protected override void OnInitialized() 45 44 { 46 45 GlobalComponentSvc.EmojiPicker = this; 47 - _module = (IJSInProcessObjectReference)await Js.InvokeAsync<IJSObjectReference>("import", 48 - "./Components/EmojiPicker.razor.js"); 49 46 } 50 47 51 48 private async Task Select(EmojiResponse emoji) 52 49 { 53 50 await OnEmojiSelect.InvokeAsync(emoji); 54 - Close(); 51 + await Close(); 55 52 } 56 53 57 - private void Close() 54 + private async Task Close() 58 55 { 59 - _module.InvokeVoid("closeDialog", EmojiPickerRef); 56 + await Js.CloseDialogAsync(EmojiPickerRef); 60 57 } 61 58 62 59 public async Task Open(ElementReference root, EventCallback<EmojiResponse> func) ··· 68 65 } 69 66 70 67 OnEmojiSelect = func; 71 - var pos = _module.Invoke<List<float>>("getPosition", root); 72 - _left = pos[0]; 73 - _top = pos[1]; 68 + (_left, _top) = await Js.GetPositionAsync(root, true); 69 + await Js.ShowDialogAsync(EmojiPickerRef, false); 74 70 StateHasChanged(); 75 - _module.InvokeVoid("openDialog", EmojiPickerRef); 76 71 } 77 72 78 73 private void FilterEmojis()
-14
Iceshrimp.Frontend/Components/EmojiPicker.razor.js
··· 1 - export function getPosition(ref){ 2 - let rect = ref.getBoundingClientRect() 3 - let x = rect.x + window.scrollX; 4 - let y = rect.y + window.scrollY; 5 - return [x, y] 6 - } 7 - 8 - export function openDialog(ref){ 9 - ref.setAttribute("open", "open"); 10 - } 11 - 12 - export function closeDialog(ref){ 13 - ref.close(); 14 - }
+55 -2
Iceshrimp.Frontend/Core/Services/JsService.cs
··· 10 10 { 11 11 public IJSObjectReference? Module { private get; set; } 12 12 13 - public ValueTask ShowDialogAsync(ElementReference dialog) => Module!.InvokeVoidAsync("showDialog", dialog); 13 + /// <summary> 14 + /// Displays a dialog 15 + /// </summary> 16 + /// <param name="dialog">Dialog element</param> 17 + /// <param name="modal">Show as a modal</param> 18 + public ValueTask ShowDialogAsync(ElementReference dialog, bool modal = true) => 19 + Module!.InvokeVoidAsync("showDialog", dialog, modal); 20 + 21 + /// <summary> 22 + /// Closes a dialog 23 + /// </summary> 24 + /// <param name="dialog">Dialog element</param> 25 + /// <param name="returnValue">Return value for the dialog element</param> 26 + public ValueTask CloseDialogAsync(ElementReference dialog, string? returnValue = null) => 27 + Module!.InvokeVoidAsync("closeDialog", dialog, returnValue); 28 + 29 + /// <summary> 30 + /// Scrolls an element into view 31 + /// </summary> 32 + /// <param name="element">Element to scroll to</param> 33 + /// <param name="behavior">Smoothing behavior</param> 34 + public ValueTask ScrollToElementAsync(ElementReference element, ScrollBehavior behavior = ScrollBehavior.Auto) => 35 + Module!.InvokeVoidAsync("scrollToElement", element, behavior.ToString().ToLower()); 36 + 37 + /// <summary> 38 + /// Simulates a mouse click on an element 39 + /// </summary> 40 + /// <param name="element">Element to click</param> 41 + public ValueTask ClickElementAsync(ElementReference element) => Module!.InvokeVoidAsync("clickElement", element); 14 42 15 - public ValueTask CloseDialogAsync(ElementReference dialog, string? returnValue = null) => Module!.InvokeVoidAsync("closeDialog", dialog, returnValue); 43 + /// <summary> 44 + /// Gets the position of an element 45 + /// </summary> 46 + /// <param name="element">Element</param> 47 + /// <param name="includeScroll">Include window scroll in position</param> 48 + /// <returns>Element X and Y coordinates</returns> 49 + public async ValueTask<(double, double)> GetPositionAsync(ElementReference element, bool includeScroll) 50 + { 51 + var pos = await Module!.InvokeAsync<double[]>("getPosition", element, includeScroll); 52 + return (pos[0], pos[1]); 53 + } 54 + 55 + /// <summary> 56 + /// Get the selection start of an element 57 + /// </summary> 58 + /// <param name="element">Selected element</param> 59 + /// <returns>Selection start index</returns> 60 + public ValueTask<int> GetSelectionStartAsync(ElementReference element) => 61 + Module!.InvokeAsync<int>("getSelectionStart", element); 62 + 63 + public enum ScrollBehavior 64 + { 65 + Auto, 66 + Instant, 67 + Smooth, 68 + } 16 69 }
+3 -5
Iceshrimp.Frontend/Pages/DrivePage.razor
··· 12 12 @using Iceshrimp.Frontend.Components 13 13 @inject ApiService Api; 14 14 @inject GlobalComponentSvc Global; 15 - @inject IJSRuntime Js; 15 + @inject JsService Js; 16 16 @inject IStringLocalizer<Localization> Loc; 17 17 @inject ILogger<DrivePage> Logger; 18 18 @inject NavigationManager Nav; ··· 121 121 private DriveStatusResponse? Status { get; set; } 122 122 private State State { get; set; } 123 123 private InputFile UploadInput { get; set; } = null!; 124 - private IJSObjectReference _module = null!; 125 124 126 125 private async Task Load() 127 126 { ··· 152 151 protected override async Task OnInitializedAsync() 153 152 { 154 153 await Load(); 155 - _module = await Js.InvokeAsync<IJSObjectReference>("import", 156 - "./Pages/DrivePage.razor.js"); 157 154 } 158 155 159 156 protected override async Task OnParametersSetAsync() ··· 195 192 // That way we get it's functionality, without the styling limitations of the InputFile component 196 193 private async Task OpenUpload() 197 194 { 198 - await _module.InvokeVoidAsync("openUpload", UploadInput.Element); 195 + if (UploadInput.Element is not { } element) return; 196 + await Js.ClickElementAsync(element); 199 197 } 200 198 201 199 private async Task Upload(InputFileChangeEventArgs e)
-3
Iceshrimp.Frontend/Pages/DrivePage.razor.js
··· 1 - export function openUpload(element) { 2 - element.click(); 3 - }
+7 -17
Iceshrimp.Frontend/Pages/Moderation/AnnouncementsManagement.razor
··· 13 13 @inject ApiService Api; 14 14 @inject EmojiService EmojiSvc; 15 15 @inject GlobalComponentSvc Global; 16 - @inject IJSRuntime Js; 16 + @inject JsService Js; 17 17 @inject IStringLocalizer<Localization> Loc; 18 18 19 19 <HeadTitle Text="@Loc["Manage announcements"]"/> ··· 146 146 private AnnouncementRequest Draft { get; set; } 147 147 private string? Editing { get; set; } 148 148 private bool Preview { get; set; } 149 - private readonly Lazy<Task<IJSObjectReference>> _moduleTask; 150 149 151 150 public AnnouncementsManagement() 152 151 { 153 152 Draft = new AnnouncementRequest { Title = "", Text = "", ImageUrl = null, ShowPopup = true }; 154 - _moduleTask = new Lazy<Task<IJSObjectReference>>(() => 155 - Js.InvokeAsync<IJSObjectReference>( 156 - "import", 157 - "./Pages/Moderation/AnnouncementsManagement.razor.js") 158 - .AsTask()); 159 153 } 160 154 161 155 private async Task NewAnnouncement() ··· 167 161 if (EmojiList.Count == 0) 168 162 EmojiList = await EmojiSvc.GetEmojiAsync(); 169 163 170 - var module = await _moduleTask.Value; 171 - await module.InvokeVoidAsync("openDialog", ComposeDialog); 164 + await Js.ShowDialogAsync(ComposeDialog, false); 172 165 } 173 166 174 167 private async Task EditAnnouncement(string id) ··· 183 176 if (EmojiList.Count == 0) 184 177 EmojiList = await EmojiSvc.GetEmojiAsync(); 185 178 186 - var module = await _moduleTask.Value; 187 - await module.InvokeVoidAsync("openDialog", ComposeDialog); 179 + await Js.ShowDialogAsync(ComposeDialog); 188 180 } 189 181 190 182 private async Task CloseDialog() 191 183 { 192 - var module = await _moduleTask.Value; 193 - await module.InvokeVoidAsync("closeDialog", ComposeDialog); 184 + await Js.CloseDialogAsync(ComposeDialog); 194 185 } 195 186 196 187 private async Task Send() ··· 227 218 228 219 private async Task OpenUploadImage() 229 220 { 230 - var module = await _moduleTask.Value; 231 - await module.InvokeVoidAsync("openUpload", ImageInput.Element); 221 + if (ImageInput.Element is not { } element) return; 222 + await Js.ClickElementAsync(element); 232 223 } 233 224 234 225 private async Task UploadImage(InputFileChangeEventArgs e) ··· 253 244 254 245 private async Task AddEmoji(EmojiResponse emoji) 255 246 { 256 - var module = await _moduleTask.Value; 257 - var pos = await module.InvokeAsync<int>("getSelectionStart", Textarea); 247 + var pos = await Js.GetSelectionStartAsync(Textarea); 258 248 var text = Draft.Text; 259 249 var emojiString = $":{emoji.Name}: "; 260 250 Draft.Text = text.Insert(pos, emojiString);
-15
Iceshrimp.Frontend/Pages/Moderation/AnnouncementsManagement.razor.js
··· 1 - export function openDialog(element) { 2 - element.show() 3 - } 4 - 5 - export function closeDialog(element) { 6 - element.close() 7 - } 8 - 9 - export function openUpload(element) { 10 - element.click(); 11 - } 12 - 13 - export function getSelectionStart(element) { 14 - return element.selectionStart; 15 - }
+5 -6
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor
··· 12 12 @layout ModerationLayout 13 13 @inject ApiService Api; 14 14 @inject GlobalComponentSvc Global; 15 - @inject IJSRuntime Js; 15 + @inject JsService Js; 16 16 @inject IStringLocalizer<Localization> Loc; 17 17 18 18 <HeadTitle Text="@Loc["Custom Emojis"]"/> ··· 115 115 private IBrowserFile ImportFile { get; set; } = null!; 116 116 private bool BatchSelection { get; set; } 117 117 private List<EmojiResponse> Selected { get; set; } = []; 118 - private IJSObjectReference _module = null!; 119 118 120 119 private void FilterEmojis() 121 120 { ··· 173 172 // That way we get it's functionality, without the styling limitations of the InputFile component 174 173 private async Task OpenUpload() 175 174 { 176 - await _module.InvokeVoidAsync("openUpload", UploadInput.Element); 175 + if (UploadInput.Element is not { } element) return; 176 + await Js.ClickElementAsync(element); 177 177 } 178 178 179 179 private async Task OpenImport() 180 180 { 181 - await _module.InvokeVoidAsync("openUpload", ImportInput.Element); 181 + if (ImportInput.Element is not { } element) return; 182 + await Js.ClickElementAsync(element); 182 183 } 183 184 184 185 private void ToggleBatchSelection() ··· 317 318 318 319 protected override async Task OnInitializedAsync() 319 320 { 320 - _module = await Js.InvokeAsync<IJSObjectReference>("import", 321 - "./Pages/Moderation/CustomEmojis.razor.js"); 322 321 await GetEmojis(); 323 322 } 324 323 }
-3
Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.js
··· 1 - export function openUpload(element) { 2 - element.click(); 3 - }
+2 -6
Iceshrimp.Frontend/Pages/Settings/Profile.razor
··· 14 14 @inject ApiService Api; 15 15 @inject ILogger<Profile> Logger; 16 16 @inject IStringLocalizer<Localization> Loc; 17 - @inject IJSRuntime Js 17 + @inject JsService Js 18 18 @inject GlobalComponentSvc GlobalComponentSvc 19 19 @inject SessionService Session; 20 20 ··· 197 197 private string PronounsLanguage { get; set; } = ""; 198 198 private string PronounsValue { get; set; } = ""; 199 199 private Dictionary<string, string> _languages = LanguageHelper.Bcp47Languages; 200 - 201 - private IJSObjectReference _module = null!; 202 200 203 201 private class UserPronouns 204 202 { ··· 208 206 209 207 protected override async Task OnInitializedAsync() 210 208 { 211 - _module = await Js.InvokeAsync<IJSObjectReference>("import", 212 - "./Pages/Settings/Profile.razor.js"); 213 209 try 214 210 { 215 211 UserProfile = await Api.Profile.GetProfileAsync(); ··· 323 319 324 320 private async Task AddEmoji(EmojiResponse emoji) 325 321 { 326 - var pos = await _module.InvokeAsync<int>("getSelectionStart", Description); 322 + var pos = await Js.GetSelectionStartAsync(Description); 327 323 var text = UserProfile.Description ?? ""; 328 324 var emojiString = $":{emoji.Name}: "; 329 325 UserProfile.Description = text.Insert(pos, emojiString);
-3
Iceshrimp.Frontend/Pages/Settings/Profile.razor.js
··· 1 - export function getSelectionStart(element) { 2 - return element.selectionStart; 3 - }
+43 -2
Iceshrimp.Frontend/wwwroot/js-interop.js
··· 1 1 /** 2 2 * Displays a dialog 3 3 * @param {HTMLDialogElement} dialog Element reference 4 + * @param {boolean} modal Show as a modal 4 5 */ 5 - export function showDialog(dialog) { 6 - dialog.showModal(); 6 + export function showDialog(dialog, modal) { 7 + if (modal) dialog.showModal(); 8 + else dialog.show(); 7 9 } 8 10 9 11 /** ··· 13 15 */ 14 16 export function closeDialog(dialog, returnValue) { 15 17 dialog.close(returnValue); 18 + } 19 + 20 + /** 21 + * Scroll to element 22 + * @param {HTMLElement} element 23 + * @param {"auto" | "instant" | "smooth"} behavior 24 + */ 25 + export function scrollToElement(element, behavior) { 26 + element.scrollIntoView({behavior}); 27 + } 28 + 29 + /** 30 + * Simulates a mouse click on an element 31 + * @param {HTMLElement} element 32 + */ 33 + export function clickElement(element) { 34 + element.click(); 35 + } 36 + 37 + /** 38 + * Gets the position of an element 39 + * @param {HTMLElement} element Element 40 + * @param {boolean} includeScroll Include window scroll in position 41 + * @return {number[]} Element X and Y coordinates 42 + */ 43 + export function getPosition(element, includeScroll) { 44 + const rect = element.getBoundingClientRect(); 45 + let x = rect.x + (includeScroll ? window.scrollX : 0); 46 + let y = rect.y + (includeScroll ? window.scrollY : 0); 47 + return [Math.round(x), Math.round(y)]; 48 + } 49 + 50 + /** 51 + * Get start of a selection 52 + * @param element Element 53 + * @return {number} Start index 54 + */ 55 + export function getSelectionStart(element) { 56 + return element.selectionStart; 16 57 }