···11-export function openDialog(element) {
22- element.show()
33-}
44-55-export function closeDialog(element) {
66- element.close()
77-}
88-99-export function getSelectionStart(element) {
1010- return element.selectionStart;
1111-}
1212-1313-export function openUpload(element) {
1414- element.click();
1515-}
1616-171/**
182 * Sets up paste handling in the main compose textarea to allow pasting quotes and media attachments.
193 * Is called by Blazor when the compose dialog is opened.
···11-export function getPosition(ref){
22- let rect = ref.getBoundingClientRect()
33- let x = rect.x + window.scrollX;
44- let y = rect.y + window.scrollY;
55- return [x, y]
66-}
77-88-export function openDialog(ref){
99- ref.setAttribute("open", "open");
1010-}
1111-1212-export function closeDialog(ref){
1313- ref.close();
1414-}
+55-2
Iceshrimp.Frontend/Core/Services/JsService.cs
···1010{
1111 public IJSObjectReference? Module { private get; set; }
12121313- public ValueTask ShowDialogAsync(ElementReference dialog) => Module!.InvokeVoidAsync("showDialog", dialog);
1313+ /// <summary>
1414+ /// Displays a dialog
1515+ /// </summary>
1616+ /// <param name="dialog">Dialog element</param>
1717+ /// <param name="modal">Show as a modal</param>
1818+ public ValueTask ShowDialogAsync(ElementReference dialog, bool modal = true) =>
1919+ Module!.InvokeVoidAsync("showDialog", dialog, modal);
2020+2121+ /// <summary>
2222+ /// Closes a dialog
2323+ /// </summary>
2424+ /// <param name="dialog">Dialog element</param>
2525+ /// <param name="returnValue">Return value for the dialog element</param>
2626+ public ValueTask CloseDialogAsync(ElementReference dialog, string? returnValue = null) =>
2727+ Module!.InvokeVoidAsync("closeDialog", dialog, returnValue);
2828+2929+ /// <summary>
3030+ /// Scrolls an element into view
3131+ /// </summary>
3232+ /// <param name="element">Element to scroll to</param>
3333+ /// <param name="behavior">Smoothing behavior</param>
3434+ public ValueTask ScrollToElementAsync(ElementReference element, ScrollBehavior behavior = ScrollBehavior.Auto) =>
3535+ Module!.InvokeVoidAsync("scrollToElement", element, behavior.ToString().ToLower());
3636+3737+ /// <summary>
3838+ /// Simulates a mouse click on an element
3939+ /// </summary>
4040+ /// <param name="element">Element to click</param>
4141+ public ValueTask ClickElementAsync(ElementReference element) => Module!.InvokeVoidAsync("clickElement", element);
14421515- public ValueTask CloseDialogAsync(ElementReference dialog, string? returnValue = null) => Module!.InvokeVoidAsync("closeDialog", dialog, returnValue);
4343+ /// <summary>
4444+ /// Gets the position of an element
4545+ /// </summary>
4646+ /// <param name="element">Element</param>
4747+ /// <param name="includeScroll">Include window scroll in position</param>
4848+ /// <returns>Element X and Y coordinates</returns>
4949+ public async ValueTask<(double, double)> GetPositionAsync(ElementReference element, bool includeScroll)
5050+ {
5151+ var pos = await Module!.InvokeAsync<double[]>("getPosition", element, includeScroll);
5252+ return (pos[0], pos[1]);
5353+ }
5454+5555+ /// <summary>
5656+ /// Get the selection start of an element
5757+ /// </summary>
5858+ /// <param name="element">Selected element</param>
5959+ /// <returns>Selection start index</returns>
6060+ public ValueTask<int> GetSelectionStartAsync(ElementReference element) =>
6161+ Module!.InvokeAsync<int>("getSelectionStart", element);
6262+6363+ public enum ScrollBehavior
6464+ {
6565+ Auto,
6666+ Instant,
6767+ Smooth,
6868+ }
1669}
+3-5
Iceshrimp.Frontend/Pages/DrivePage.razor
···1212@using Iceshrimp.Frontend.Components
1313@inject ApiService Api;
1414@inject GlobalComponentSvc Global;
1515-@inject IJSRuntime Js;
1515+@inject JsService Js;
1616@inject IStringLocalizer<Localization> Loc;
1717@inject ILogger<DrivePage> Logger;
1818@inject NavigationManager Nav;
···121121 private DriveStatusResponse? Status { get; set; }
122122 private State State { get; set; }
123123 private InputFile UploadInput { get; set; } = null!;
124124- private IJSObjectReference _module = null!;
125124126125 private async Task Load()
127126 {
···152151 protected override async Task OnInitializedAsync()
153152 {
154153 await Load();
155155- _module = await Js.InvokeAsync<IJSObjectReference>("import",
156156- "./Pages/DrivePage.razor.js");
157154 }
158155159156 protected override async Task OnParametersSetAsync()
···195192 // That way we get it's functionality, without the styling limitations of the InputFile component
196193 private async Task OpenUpload()
197194 {
198198- await _module.InvokeVoidAsync("openUpload", UploadInput.Element);
195195+ if (UploadInput.Element is not { } element) return;
196196+ await Js.ClickElementAsync(element);
199197 }
200198201199 private async Task Upload(InputFileChangeEventArgs e)
-3
Iceshrimp.Frontend/Pages/DrivePage.razor.js
···11-export function openUpload(element) {
22- element.click();
33-}
···11-export function getSelectionStart(element) {
22- return element.selectionStart;
33-}
+43-2
Iceshrimp.Frontend/wwwroot/js-interop.js
···11/**
22 * Displays a dialog
33 * @param {HTMLDialogElement} dialog Element reference
44+ * @param {boolean} modal Show as a modal
45 */
55-export function showDialog(dialog) {
66- dialog.showModal();
66+export function showDialog(dialog, modal) {
77+ if (modal) dialog.showModal();
88+ else dialog.show();
79}
810911/**
···1315 */
1416export function closeDialog(dialog, returnValue) {
1517 dialog.close(returnValue);
1818+}
1919+2020+/**
2121+ * Scroll to element
2222+ * @param {HTMLElement} element
2323+ * @param {"auto" | "instant" | "smooth"} behavior
2424+ */
2525+export function scrollToElement(element, behavior) {
2626+ element.scrollIntoView({behavior});
2727+}
2828+2929+/**
3030+ * Simulates a mouse click on an element
3131+ * @param {HTMLElement} element
3232+ */
3333+export function clickElement(element) {
3434+ element.click();
3535+}
3636+3737+/**
3838+ * Gets the position of an element
3939+ * @param {HTMLElement} element Element
4040+ * @param {boolean} includeScroll Include window scroll in position
4141+ * @return {number[]} Element X and Y coordinates
4242+ */
4343+export function getPosition(element, includeScroll) {
4444+ const rect = element.getBoundingClientRect();
4545+ let x = rect.x + (includeScroll ? window.scrollX : 0);
4646+ let y = rect.y + (includeScroll ? window.scrollY : 0);
4747+ return [Math.round(x), Math.round(y)];
4848+}
4949+5050+/**
5151+ * Get start of a selection
5252+ * @param element Element
5353+ * @return {number} Start index
5454+ */
5555+export function getSelectionStart(element) {
5656+ return element.selectionStart;
1657}