diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs index ca3d3604b1..09ab4c3b68 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Web/App.razor.cs @@ -7,8 +7,9 @@ public partial class App { protected override void OnParametersSet() { - WebAppBuilder.Configure() - .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering + AppBuilder.Configure() + .UseBlazor() + // .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering .SetupWithSingleViewLifetime(); base.OnParametersSet(); diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs index ae294151a6..909e2dd441 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs @@ -24,6 +24,7 @@ public class AvaloniaView : ComponentBase { builder.OpenElement(0, "div"); builder.AddAttribute(1, "id", _containerId); + builder.AddAttribute(2, "style", "width:100vw;height:100vh"); builder.CloseElement(); } @@ -31,7 +32,7 @@ public class AvaloniaView : ComponentBase { if (OperatingSystem.IsBrowser()) { - _ = await JSHost.ImportAsync("avalonia", "/_content/Avalonia.Web.Blazor/avalonia.js"); + await Avalonia.Web.Interop.AvaloniaModule.ImportMain(); _browserView = new BrowserView(_containerId); if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs index 26b4b15863..f38779f834 100644 --- a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs @@ -1,8 +1,11 @@ -using Avalonia.Controls; +using System.Runtime.Versioning; + +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Web.Blazor; +[SupportedOSPlatform("browser")] public static class WebAppBuilder { public static T SetupWithSingleViewLifetime( @@ -12,13 +15,21 @@ public static class WebAppBuilder return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); } + public static T UseBlazor(this T builder) where T : AppBuilderBase, new() + { + return builder + .UseBrowser() + .With(new BrowserPlatformOptions + { + FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Web.Blazor/{filePath}") + }); + } + public static AppBuilder Configure() where TApp : Application, new() { - var builder = AppBuilder.Configure() - .UseBrowser(); - - return builder; + return AppBuilder.Configure() + .UseBlazor(); } internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj index a4756a5e2b..cdfa095865 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.csproj +++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj @@ -39,10 +39,6 @@ - - - - @@ -52,4 +48,8 @@ + + + + diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index a5da719912..3a31679424 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -37,7 +37,6 @@ namespace Avalonia.Web private bool _useGL; private ITextInputMethodClient? _client; - private static int _canvasCount; public AvaloniaView(string divId) : this(DomHelper.GetElementById(divId) ?? throw new Exception($"Element with id {divId} was not found in the html document.")) @@ -63,8 +62,6 @@ namespace Avalonia.Web _splash = DomHelper.GetElementById("avalonia-splash"); - _canvas.SetProperty("id", $"avaloniaCanvas{_canvasCount++}"); - _topLevelImpl = new BrowserTopLevelImpl(this); _topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () => diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs index d962956567..00ed961fbe 100644 --- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs @@ -1,51 +1,54 @@ -using System.Runtime.InteropServices.JavaScript; -using System; +using System; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Media; using Avalonia.Web.Skia; using System.Runtime.Versioning; -namespace Avalonia.Web +namespace Avalonia.Web; + +[SupportedOSPlatform("browser")] +public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime +{ + public AvaloniaView? View; + + public Control? MainView + { + get => View!.Content; + set => View!.Content = value; + } +} + +public class BrowserPlatformOptions +{ + public Func FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}"); +} + + +[SupportedOSPlatform("browser")] +public static class WebAppBuilder { - [SupportedOSPlatform("browser")] - public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime + public static T SetupBrowserApp( + this T builder, string mainDivId) + where T : AppBuilderBase, new() { - public AvaloniaView? View; + var lifetime = new BrowserSingleViewLifetime(); - public Control? MainView - { - get => View!.Content; - set => View!.Content = value; - } + return builder + .UseBrowser() + .AfterSetup(b => + { + lifetime.View = new AvaloniaView(mainDivId); + }) + .SetupWithLifetime(lifetime); } - [SupportedOSPlatform("browser")] - public static partial class WebAppBuilder + public static T UseBrowser( + this T builder) + where T : AppBuilderBase, new() { - public static T SetupBrowserApp( - this T builder, string mainDivId) - where T : AppBuilderBase, new() - { - var lifetime = new BrowserSingleViewLifetime(); - - return builder - .UseBrowser() - .AfterSetup(b => - { - lifetime.View = new AvaloniaView(mainDivId); - }) - .SetupWithLifetime(lifetime); - } - - public static T UseBrowser( - this T builder) - where T : AppBuilderBase, new() - { - return builder - .UseWindowingSubsystem(BrowserWindowingPlatform.Register) - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }); - } + return builder + .UseWindowingSubsystem(BrowserWindowingPlatform.Register) + .UseSkia() + .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }); } } diff --git a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs new file mode 100644 index 0000000000..176b8d60fc --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices.JavaScript; +using System.Threading.Tasks; + +namespace Avalonia.Web.Interop; + +internal static class AvaloniaModule +{ + public const string MainModuleName = "avalonia"; + public const string StorageModuleName = "storage"; + + public static Task ImportMain() + { + var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions(); + return JSHost.ImportAsync(MainModuleName, options.FrameworkAssetPathResolver("avalonia.js")); + } + + public static Task ImportStorage() + { + var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions(); + return JSHost.ImportAsync(StorageModuleName, options.FrameworkAssetPathResolver("storage.js")); + } +} diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs index ff07d1757b..efa94916fa 100644 --- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs +++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs @@ -29,13 +29,13 @@ internal static partial class CanvasHelper return glInfo; } - [JSImport("Canvas.requestAnimationFrame", "avalonia")] + [JSImport("Canvas.requestAnimationFrame", AvaloniaModule.MainModuleName)] public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); - [JSImport("Canvas.setCanvasSize", "avalonia")] + [JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)] public static partial void SetCanvasSize(JSObject canvas, int height, int width); - [JSImport("Canvas.initGL", "avalonia")] + [JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)] private static partial JSObject InitGL( JSObject canvas, string canvasId, diff --git a/src/Web/Avalonia.Web/Interop/DomHelper.cs b/src/Web/Avalonia.Web/Interop/DomHelper.cs index c2cf0a0c44..80f146a57a 100644 --- a/src/Web/Avalonia.Web/Interop/DomHelper.cs +++ b/src/Web/Avalonia.Web/Interop/DomHelper.cs @@ -8,20 +8,20 @@ internal static partial class DomHelper [JSImport("globalThis.document.getElementById")] internal static partial JSObject? GetElementById(string id); - [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia")] + [JSImport("AvaloniaDOM.createAvaloniaHost", AvaloniaModule.MainModuleName)] public static partial JSObject CreateAvaloniaHost(JSObject element); - [JSImport("AvaloniaDOM.addClass", "avalonia")] + [JSImport("AvaloniaDOM.addClass", AvaloniaModule.MainModuleName)] public static partial void AddCssClass(JSObject element, string className); - [JSImport("SizeWatcher.observe", "avalonia")] + [JSImport("SizeWatcher.observe", AvaloniaModule.MainModuleName)] public static partial JSObject ObserveSize( JSObject canvas, string? canvasId, [JSMarshalAs>] Action onSizeChanged); - [JSImport("DpiWatcher.start", "avalonia")] + [JSImport("DpiWatcher.start", AvaloniaModule.MainModuleName)] public static partial double ObserveDpi( [JSMarshalAs>] Action onDpiChanged); diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index bdd1957e03..cfec9f30dc 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -6,7 +6,7 @@ namespace Avalonia.Web.Interop; internal static partial class InputHelper { - [JSImport("InputHelper.subscribeKeyEvents", "avalonia")] + [JSImport("InputHelper.subscribeKeyEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeKeyEvents( JSObject htmlElement, [JSMarshalAs>] @@ -14,7 +14,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func keyUp); - [JSImport("InputHelper.subscribeTextEvents", "avalonia")] + [JSImport("InputHelper.subscribeTextEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeTextEvents( JSObject htmlElement, [JSMarshalAs>] @@ -26,7 +26,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func onCompositionEnd); - [JSImport("InputHelper.subscribePointerEvents", "avalonia")] + [JSImport("InputHelper.subscribePointerEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribePointerEvents( JSObject htmlElement, [JSMarshalAs>] @@ -39,35 +39,35 @@ internal static partial class InputHelper Func wheel); - [JSImport("InputHelper.subscribeInputEvents", "avalonia")] + [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeInputEvents( JSObject htmlElement, [JSMarshalAs>] Func input); - [JSImport("InputHelper.clearInput", "avalonia")] + [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)] public static partial void ClearInputElement(JSObject htmlElement); - [JSImport("InputHelper.isInputElement", "avalonia")] + [JSImport("InputHelper.isInputElement", AvaloniaModule.MainModuleName)] public static partial void IsInputElement(JSObject htmlElement); - [JSImport("InputHelper.focusElement", "avalonia")] + [JSImport("InputHelper.focusElement", AvaloniaModule.MainModuleName)] public static partial void FocusElement(JSObject htmlElement); - [JSImport("InputHelper.setCursor", "avalonia")] + [JSImport("InputHelper.setCursor", AvaloniaModule.MainModuleName)] public static partial void SetCursor(JSObject htmlElement, string kind); - [JSImport("InputHelper.hide", "avalonia")] + [JSImport("InputHelper.hide", AvaloniaModule.MainModuleName)] public static partial void HideElement(JSObject htmlElement); - [JSImport("InputHelper.show", "avalonia")] + [JSImport("InputHelper.show", AvaloniaModule.MainModuleName)] public static partial void ShowElement(JSObject htmlElement); - [JSImport("InputHelper.setSurroundingText", "avalonia")] + [JSImport("InputHelper.setSurroundingText", AvaloniaModule.MainModuleName)] public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end); - [JSImport("InputHelper.setBounds", "avalonia")] + [JSImport("InputHelper.setBounds", AvaloniaModule.MainModuleName)] public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret); [JSImport("globalThis.navigator.clipboard.readText")] diff --git a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs index 8144f64fc7..d3baaa2533 100644 --- a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs +++ b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs @@ -5,24 +5,24 @@ namespace Avalonia.Web.Interop; internal static partial class NativeControlHostHelper { - [JSImport("NativeControlHost.createDefaultChild", "avalonia")] + [JSImport("NativeControlHost.createDefaultChild", AvaloniaModule.MainModuleName)] internal static partial JSObject CreateDefaultChild(JSObject? parent); - [JSImport("NativeControlHost.createAttachment", "avalonia")] + [JSImport("NativeControlHost.createAttachment", AvaloniaModule.MainModuleName)] internal static partial JSObject CreateAttachment(); - [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia")] + [JSImport("NativeControlHost.initializeWithChildHandle", AvaloniaModule.MainModuleName)] internal static partial void InitializeWithChildHandle(JSObject element, JSObject child); - [JSImport("NativeControlHost.attachTo", "avalonia")] + [JSImport("NativeControlHost.attachTo", AvaloniaModule.MainModuleName)] internal static partial void AttachTo(JSObject element, JSObject? host); - [JSImport("NativeControlHost.showInBounds", "avalonia")] + [JSImport("NativeControlHost.showInBounds", AvaloniaModule.MainModuleName)] internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height); - [JSImport("NativeControlHost.hideWithSize", "avalonia")] + [JSImport("NativeControlHost.hideWithSize", AvaloniaModule.MainModuleName)] internal static partial void HideWithSize(JSObject element, double width, double height); - [JSImport("NativeControlHost.releaseChild", "avalonia")] + [JSImport("NativeControlHost.releaseChild", AvaloniaModule.MainModuleName)] internal static partial void ReleaseChild(JSObject element); } diff --git a/src/Web/Avalonia.Web/Interop/StorageHelper.cs b/src/Web/Avalonia.Web/Interop/StorageHelper.cs index d770e852d9..9a6cfb9fc2 100644 --- a/src/Web/Avalonia.Web/Interop/StorageHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StorageHelper.cs @@ -5,51 +5,51 @@ namespace Avalonia.Web.Interop; internal static partial class StorageHelper { - [JSImport("Caniuse.canShowOpenFilePicker", "avalonia")] + [JSImport("Caniuse.canShowOpenFilePicker", AvaloniaModule.MainModuleName)] public static partial bool CanShowOpenFilePicker(); - [JSImport("Caniuse.canShowSaveFilePicker", "avalonia")] + [JSImport("Caniuse.canShowSaveFilePicker", AvaloniaModule.MainModuleName)] public static partial bool CanShowSaveFilePicker(); - [JSImport("Caniuse.canShowDirectoryPicker", "avalonia")] + [JSImport("Caniuse.canShowDirectoryPicker", AvaloniaModule.MainModuleName)] public static partial bool CanShowDirectoryPicker(); - [JSImport("StorageProvider.selectFolderDialog", "storage")] + [JSImport("StorageProvider.selectFolderDialog", AvaloniaModule.StorageModuleName)] public static partial Task SelectFolderDialog(JSObject? startIn); - [JSImport("StorageProvider.openFileDialog", "storage")] + [JSImport("StorageProvider.openFileDialog", AvaloniaModule.StorageModuleName)] public static partial Task OpenFileDialog(JSObject? startIn, bool multiple, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.saveFileDialog", "storage")] + [JSImport("StorageProvider.saveFileDialog", AvaloniaModule.StorageModuleName)] public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.openBookmark", "storage")] + [JSImport("StorageProvider.openBookmark", AvaloniaModule.StorageModuleName)] public static partial Task OpenBookmark(string key); - [JSImport("StorageItem.saveBookmark", "storage")] + [JSImport("StorageItem.saveBookmark", AvaloniaModule.StorageModuleName)] public static partial Task SaveBookmark(JSObject item); - [JSImport("StorageItem.deleteBookmark", "storage")] + [JSImport("StorageItem.deleteBookmark", AvaloniaModule.StorageModuleName)] public static partial Task DeleteBookmark(JSObject item); - [JSImport("StorageItem.getProperties", "storage")] + [JSImport("StorageItem.getProperties", AvaloniaModule.StorageModuleName)] public static partial Task GetProperties(JSObject item); - [JSImport("StorageItem.openWrite", "storage")] + [JSImport("StorageItem.openWrite", AvaloniaModule.StorageModuleName)] public static partial Task OpenWrite(JSObject item); - [JSImport("StorageItem.openRead", "storage")] + [JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)] public static partial Task OpenRead(JSObject item); - [JSImport("StorageItem.getItems", "storage")] + [JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)] [return: JSMarshalAs>] public static partial Task GetItems(JSObject item); - [JSImport("StorageItems.itemsArray", "storage")] + [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)] public static partial JSObject[] ItemsArray(JSObject item); - [JSImport("StorageProvider.createAcceptType", "storage")] + [JSImport("StorageProvider.createAcceptType", AvaloniaModule.StorageModuleName)] public static partial JSObject CreateAcceptType(string description, string[] mimeTypes); } diff --git a/src/Web/Avalonia.Web/Interop/StreamHelper.cs b/src/Web/Avalonia.Web/Interop/StreamHelper.cs index 9cd5ca2591..d9de7bcbd8 100644 --- a/src/Web/Avalonia.Web/Interop/StreamHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs @@ -2,33 +2,33 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; -namespace Avalonia.Web.Storage; +namespace Avalonia.Web.Interop; /// /// Set of FileSystemWritableFileStream and Blob methods. /// internal static partial class StreamHelper { - [JSImport("StreamHelper.seek", "avalonia")] + [JSImport("StreamHelper.seek", AvaloniaModule.MainModuleName)] public static partial void Seek(JSObject stream, [JSMarshalAs] long position); - [JSImport("StreamHelper.truncate", "avalonia")] + [JSImport("StreamHelper.truncate", AvaloniaModule.MainModuleName)] public static partial void Truncate(JSObject stream, [JSMarshalAs] long size); - [JSImport("StreamHelper.write", "avalonia")] + [JSImport("StreamHelper.write", AvaloniaModule.MainModuleName)] public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data); - [JSImport("StreamHelper.close", "avalonia")] + [JSImport("StreamHelper.close", AvaloniaModule.MainModuleName)] public static partial Task CloseAsync(JSObject stream); - [JSImport("StreamHelper.byteLength", "avalonia")] + [JSImport("StreamHelper.byteLength", AvaloniaModule.MainModuleName)] [return: JSMarshalAs] public static partial long ByteLength(JSObject stream); - [JSImport("StreamHelper.sliceArrayBuffer", "avalonia")] + [JSImport("StreamHelper.sliceArrayBuffer", AvaloniaModule.MainModuleName)] private static partial Task SliceToArrayBuffer(JSObject stream, [JSMarshalAs] long offset, int count); - [JSImport("StreamHelper.toMemoryView", "avalonia")] + [JSImport("StreamHelper.toMemoryView", AvaloniaModule.MainModuleName)] [return: JSMarshalAs>] private static partial byte[] ArrayBufferToMemoryView(JSObject stream); diff --git a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs index 640c2fd745..77734ea62f 100644 --- a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs +++ b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs @@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading; using System.Threading.Tasks; +using Avalonia.Web.Interop; + namespace Avalonia.Web.Storage; [System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs index 81a621747e..3932b79ad0 100644 --- a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs +++ b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs @@ -20,7 +20,7 @@ internal class BrowserStorageProvider : IStorageProvider internal const string PickerCancelMessage = "The user aborted a request"; internal const string NoPermissionsMessage = "Permissions denied"; - private readonly Lazy> _lazyModule = new(() => JSHost.ImportAsync("storage", "./storage.js")); + private readonly Lazy _lazyModule = new(() => AvaloniaModule.ImportStorage()); public bool CanOpen => StorageHelper.CanShowOpenFilePicker(); public bool CanSave => StorageHelper.CanShowSaveFilePicker(); @@ -28,7 +28,7 @@ internal class BrowserStorageProvider : IStorageProvider public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter); @@ -62,7 +62,7 @@ internal class BrowserStorageProvider : IStorageProvider public async Task SaveFilePickerAsync(FilePickerSaveOptions options) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices); @@ -90,7 +90,7 @@ internal class BrowserStorageProvider : IStorageProvider public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; try @@ -106,14 +106,14 @@ internal class BrowserStorageProvider : IStorageProvider public async Task OpenFileBookmarkAsync(string bookmark) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var item = await StorageHelper.OpenBookmark(bookmark); return item is not null ? new JSStorageFile(item) : null; } public async Task OpenFolderBookmarkAsync(string bookmark) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var item = await StorageHelper.OpenBookmark(bookmark); return item is not null ? new JSStorageFolder(item) : null; } diff --git a/src/Web/Avalonia.Web/Storage/WriteableStream.cs b/src/Web/Avalonia.Web/Storage/WriteableStream.cs index f9699fef7a..09e438c34e 100644 --- a/src/Web/Avalonia.Web/Storage/WriteableStream.cs +++ b/src/Web/Avalonia.Web/Storage/WriteableStream.cs @@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading; using System.Threading.Tasks; +using Avalonia.Web.Interop; + namespace Avalonia.Web.Storage; [System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts index a78fd7ca87..0642bd475d 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts @@ -7,10 +7,18 @@ import { StreamHelper } from "./avalonia/stream"; import { NativeControlHost } from "./avalonia/nativeControlHost"; async function registerAvaloniaModule(api: RuntimeAPI): Promise { - api.setModuleImports("avalonia", avaloniaModule); + api.setModuleImports("avalonia", { + Caniuse, + Canvas, + InputHelper, + SizeWatcher, + DpiWatcher, + AvaloniaDOM, + StreamHelper, + NativeControlHost + }); } - -export const avaloniaModule = { +export { Caniuse, Canvas, InputHelper, diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 943b8330d5..494fde23e2 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -4,6 +4,8 @@ export class AvaloniaDOM { } static createAvaloniaHost(host: HTMLElement) { + const randomIdPart = Math.random().toString(36).replace(/[^a-z]+/g, "").substr(2, 10); + // Root element host.classList.add("avalonia-container"); host.tabIndex = 0; @@ -11,6 +13,7 @@ export class AvaloniaDOM { // Rendering target canvas const canvas = document.createElement("canvas"); + canvas.id = `canvas${randomIdPart}`; canvas.classList.add("avalonia-canvas"); canvas.style.backgroundColor = "#ccc"; canvas.style.width = "100%"; @@ -19,6 +22,7 @@ export class AvaloniaDOM { // Native controls host const nativeHost = document.createElement("div"); + canvas.id = `nativeHost${randomIdPart}`; nativeHost.classList.add("avalonia-native-host"); nativeHost.style.left = "0px"; nativeHost.style.top = "0px"; @@ -28,6 +32,7 @@ export class AvaloniaDOM { // IME const inputElement = document.createElement("input"); + canvas.id = `input${randomIdPart}`; inputElement.classList.add("avalonia-input-element"); inputElement.autocapitalize = "none"; inputElement.type = "text";