From 0d05c40ca25381cec24a1c6456891113794d7b6a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 10 Oct 2022 04:05:14 -0400 Subject: [PATCH 01/10] Remove old blazor backend. Keep blazor components with new WASM implementation. --- samples/ControlCatalog.Web/App.razor.cs | 4 - .../ControlCatalog.Web.csproj | 40 +- .../ControlCatalog.Web/EmbedSample.Browser.cs | 34 -- ...valonia.Web.Blazor.CompilationTuning.props | 7 - .../Avalonia.Web.Blazor.csproj | 53 +- .../Avalonia.Web.Blazor.props | 4 - .../Avalonia.Web.Blazor.targets | 6 - .../AvaloniaBlazorAppBuilder.cs | 20 - src/Web/Avalonia.Web.Blazor/AvaloniaView.cs | 43 ++ .../Avalonia.Web.Blazor/AvaloniaView.razor | 67 --- .../Avalonia.Web.Blazor/AvaloniaView.razor.cs | 500 ------------------ .../BlazorSingleViewLifetime.cs | 35 +- src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs | 25 - .../BlazorSkiaGpuRenderSession.cs | 37 -- .../BlazorSkiaGpuRenderTarget.cs | 39 -- .../BlazorSkiaRasterSurface.cs | 87 --- .../Avalonia.Web.Blazor/BlazorSkiaSurface.cs | 30 -- src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs | 34 -- src/Web/Avalonia.Web.Blazor/Cursor.cs | 93 ---- .../Avalonia.Web.Blazor/IBlazorSkiaSurface.cs | 9 - .../Interop/ActionHelper.cs | 81 --- .../Interop/AvaloniaModule.cs | 18 - .../Interop/DpiWatcherInterop.cs | 72 --- .../Interop/FocusHelperInterop.cs | 22 - .../Interop/InputHelperInterop.cs | 130 ----- .../Interop/JSModuleInterop.cs | 46 -- .../Interop/NativeControlHostImpl.cs | 144 ----- .../Interop/SKHtmlCanvasInterop.cs | 76 --- .../Interop/SizeWatcherInterop.cs | 50 -- .../Interop/Storage/StorageProviderInterop.cs | 225 -------- .../Interop/Storage/WriteableStream.cs | 124 ----- .../JSObjectControlHandle.cs | 35 -- src/Web/Avalonia.Web.Blazor/Keycodes.cs | 127 ----- .../ManualTriggerRenderTimer.cs | 17 - .../RazorViewTopLevelImpl.cs | 222 -------- src/Web/Avalonia.Web.Blazor/WinStubs.cs | 50 -- .../Avalonia.Web.Blazor/WindowingPlatform.cs | 106 ---- src/Web/Avalonia.Web.Blazor/_Imports.razor | 1 - src/Web/Avalonia.Web.Blazor/webapp/build.js | 16 - .../webapp/modules/Avalonia.ts | 7 - .../webapp/modules/Avalonia/CaretHelper.ts | 149 ------ .../webapp/modules/Avalonia/DpiWatcher.ts | 40 -- .../webapp/modules/Avalonia/FocusHelper.ts | 9 - .../webapp/modules/Avalonia/InputHelper.ts | 86 --- .../modules/Avalonia/NativeControlHost.ts | 61 --- .../webapp/modules/Avalonia/SKHtmlCanvas.ts | 255 --------- .../webapp/modules/Avalonia/SizeWatcher.ts | 67 --- .../webapp/modules/Storage.ts | 1 - .../modules/Storage/IndexedDbWrapper.ts | 79 --- .../webapp/modules/Storage/StorageProvider.ts | 204 ------- .../Avalonia.Web.Blazor/webapp/package.json | 13 - .../Avalonia.Web.Blazor/webapp/tsconfig.json | 18 - .../webapp/types/dotnet/index.d.ts | 56 -- src/Web/Avalonia.Web.Sample/main.js | 4 +- src/Web/Avalonia.Web/AvaloniaView.cs | 19 +- .../Avalonia.Web/BrowserSingleViewLifetime.cs | 22 +- src/Web/Avalonia.Web/Cursor.cs | 2 +- src/Web/Avalonia.Web/Interop/CanvasHelper.cs | 6 +- src/Web/Avalonia.Web/Interop/DomHelper.cs | 10 +- src/Web/Avalonia.Web/Interop/InputHelper.cs | 24 +- .../Interop/NativeControlHostHelper.cs | 14 +- src/Web/Avalonia.Web/Interop/StorageHelper.cs | 30 +- src/Web/Avalonia.Web/Interop/StreamHelper.cs | 14 +- .../Avalonia.Web/ManualTriggerRenderTimer.cs | 2 +- .../Storage/BrowserStorageProvider.cs | 2 +- src/Web/Avalonia.Web/WindowingPlatform.cs | 2 +- .../Avalonia.Web/webapp/modules/avalonia.ts | 26 +- .../webapp/modules/avalonia/canvas.ts | 4 +- 68 files changed, 182 insertions(+), 3773 deletions(-) delete mode 100644 samples/ControlCatalog.Web/EmbedSample.Browser.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props delete mode 100644 src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props delete mode 100644 src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets delete mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs create mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaView.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaView.razor delete mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Cursor.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Keycodes.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/WinStubs.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/_Imports.razor delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/build.js delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/package.json delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs index bcd2a6fefc..ca3d3604b1 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Web/App.razor.cs @@ -8,10 +8,6 @@ public partial class App protected override void OnParametersSet() { WebAppBuilder.Configure() - .AfterSetup(_ => - { - ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); - }) .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering .SetupWithSingleViewLifetime(); diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index b2c9ec72eb..03fb31f0d3 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -1,44 +1,16 @@  - net6.0 + net7.0 + browser-wasm enable - - true 16777216 false false - - - false - -O1 - false - - - - true - true - -O3 - -O3 - false - false - false - false - false - false - true - false - true - true - true - link - true - - - - + + @@ -50,8 +22,8 @@ - - + + diff --git a/samples/ControlCatalog.Web/EmbedSample.Browser.cs b/samples/ControlCatalog.Web/EmbedSample.Browser.cs deleted file mode 100644 index 5fe14409de..0000000000 --- a/samples/ControlCatalog.Web/EmbedSample.Browser.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -using Avalonia; -using Avalonia.Platform; -using Avalonia.Web.Blazor; - -using ControlCatalog.Pages; - -using Microsoft.JSInterop; - -namespace ControlCatalog.Web; - -public class EmbedSampleWeb : INativeDemoControl -{ - public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func createDefault) - { - var runtime = AvaloniaLocator.Current.GetRequiredService(); - - if (isSecond) - { - var iframe = runtime.Invoke("document.createElement", "iframe"); - iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70"); - - return new JSObjectControlHandle(iframe); - } - else - { - // window.createAppButton source is defined in "app.js" file. - var button = runtime.Invoke("window.createAppButton"); - - return new JSObjectControlHandle(button); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props deleted file mode 100644 index eb5e5dd733..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props +++ /dev/null @@ -1,7 +0,0 @@ - - - 16777216 - false - false - - diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj index 693a6a1462..1c31e0eb5d 100644 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj +++ b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj @@ -1,53 +1,40 @@  - net6.0 - enable + net7.0 Avalonia.Web.Blazor - preview - false - true + _IncludeGeneratedAvaloniaStaticFiles;$(ResolveStaticWebAssetsInputsDependsOn) - - - - - true - build\ - - - true - build\;buildTransitive\ - + - - - - - - - - - - + - - - - - - - + + + <_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/**/*.*" /> + + + + diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props deleted file mode 100644 index dd96a60c6a..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets deleted file mode 100644 index e9052fda88..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs deleted file mode 100644 index 11d9bcc98f..0000000000 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Platform; - -namespace Avalonia.Web.Blazor -{ - public class AvaloniaBlazorAppBuilder : AppBuilderBase - { - public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action platformServices) - : base(platform, platformServices) - { - } - - public AvaloniaBlazorAppBuilder() - : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.ApplicationType!.Assembly)) - { - UseWindowingSubsystem(BlazorWindowingPlatform.Register); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs new file mode 100644 index 0000000000..ae294151a6 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices.JavaScript; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using System; +using Avalonia.Controls.ApplicationLifetimes; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using BrowserView = Avalonia.Web.AvaloniaView; + +namespace Avalonia.Web.Blazor; + +[SupportedOSPlatform("browser")] +public class AvaloniaView : ComponentBase +{ + private BrowserView? _browserView; + private readonly string _containerId; + + public AvaloniaView() + { + _containerId = "av_container_" + Guid.NewGuid(); + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "id", _containerId); + builder.CloseElement(); + } + + protected override async Task OnInitializedAsync() + { + if (OperatingSystem.IsBrowser()) + { + _ = await JSHost.ImportAsync("avalonia", "/_content/Avalonia.Web.Blazor/avalonia.js"); + + _browserView = new BrowserView(_containerId); + if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) + { + _browserView.Content = lifetime.MainView; + } + } + } +} diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor deleted file mode 100644 index 5a3b9d5f71..0000000000 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor +++ /dev/null @@ -1,67 +0,0 @@ -
- - - -
- - -
- - diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs deleted file mode 100644 index 0e64e98f1e..0000000000 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ /dev/null @@ -1,500 +0,0 @@ -using System.Diagnostics; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls.Embedding; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Input.TextInput; -using Avalonia.Platform.Storage; -using Avalonia.Rendering; -using Avalonia.Rendering.Composition; -using Avalonia.Web.Blazor.Interop; -using Avalonia.Web.Blazor.Interop.Storage; - -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.JSInterop; - -using SkiaSharp; -using HTMLPointerEventArgs = Microsoft.AspNetCore.Components.Web.PointerEventArgs; - -namespace Avalonia.Web.Blazor -{ - public partial class AvaloniaView : ITextInputMethodImpl - { - private readonly RazorViewTopLevelImpl _topLevelImpl; - private EmbeddableControlRoot _topLevel; - - // Interop - private SKHtmlCanvasInterop? _interop = null; - private SizeWatcherInterop? _sizeWatcher = null; - private DpiWatcherInterop? _dpiWatcher = null; - private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null; - private AvaloniaModule? _avaloniaModule = null; - private InputHelperInterop? _inputHelper = null; - private FocusHelperInterop? _canvasHelper = null; - private FocusHelperInterop? _containerHelper = null; - private NativeControlHostInterop? _nativeControlHost = null; - private StorageProviderInterop? _storageProvider = null; - private ElementReference _htmlCanvas; - private ElementReference _inputElement; - private ElementReference _containerElement; - private ElementReference _nativeControlsContainer; - private double _dpi = 1; - private SKSize _canvasSize = new (100, 100); - - private GRContext? _context; - private GRGlInterface? _glInterface; - private const SKColorType ColorType = SKColorType.Rgba8888; - - private bool _useGL; - private bool _inputElementFocused; - - private ITextInputMethodClient? _client; - - - [Inject] private IJSRuntime Js { get; set; } = null!; - - public AvaloniaView() - { - _topLevelImpl = new RazorViewTopLevelImpl(this); - - _topLevel = new EmbeddableControlRoot(_topLevelImpl); - - if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) - { - _topLevel.Content = lifetime.MainView; - } - } - - public bool KeyPreventDefault { get; set; } - - public ITextInputMethodClient? Client => _client; - - public bool IsActive => _client != null; - - public bool IsComposing { get; private set; } - - internal INativeControlHostImpl GetNativeControlHostImpl() - { - return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); - } - - internal IStorageProvider GetStorageProvider() - { - return _storageProvider ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); - } - - private void OnPointerCancel(HTMLPointerEventArgs e) - { - if (e.PointerType == "touch") - { - _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, e.PointerType, GetPointFromEventArgs(e), - GetModifiers(e), e.PointerId); - } - } - - private void OnPointerMove(HTMLPointerEventArgs e) - { - var type = e.PointerType switch - { - "touch" => RawPointerEventType.TouchUpdate, - _ => RawPointerEventType.Move - }; - - _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); - } - - private void OnPointerUp(HTMLPointerEventArgs e) - { - var type = e.PointerType switch - { - "touch" => RawPointerEventType.TouchEnd, - _ => e.Button switch - { - 0 => RawPointerEventType.LeftButtonUp, - 1 => RawPointerEventType.MiddleButtonUp, - 2 => RawPointerEventType.RightButtonUp, - 3 => RawPointerEventType.XButton1Up, - 4 => RawPointerEventType.XButton2Up, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } - }; - - _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); - } - - private void OnPointerDown(HTMLPointerEventArgs e) - { - var type = e.PointerType switch - { - "touch" => RawPointerEventType.TouchBegin, - _ => e.Button switch - { - 0 => RawPointerEventType.LeftButtonDown, - 1 => RawPointerEventType.MiddleButtonDown, - 2 => RawPointerEventType.RightButtonDown, - 3 => RawPointerEventType.XButton1Down, - 4 => RawPointerEventType.XButton2Down, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } - }; - - _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); - } - - private static RawPointerPoint GetPointFromEventArgs(HTMLPointerEventArgs args) - { - return new RawPointerPoint - { - Position = new Point(args.ClientX, args.ClientY), - Pressure = args.Pressure, - XTilt = args.TiltX, - YTilt = args.TiltY - // Twist = args.Twist - read from JS code directly when - }; - } - - private void OnWheel(WheelEventArgs e) - { - _topLevelImpl.RawMouseWheelEvent( new Point(e.ClientX, e.ClientY), - new Vector(-(e.DeltaX / 50), -(e.DeltaY / 50)), GetModifiers(e)); - } - - private static RawInputModifiers GetModifiers(MouseEventArgs e) - { - var modifiers = RawInputModifiers.None; - - if (e.CtrlKey) - modifiers |= RawInputModifiers.Control; - if (e.AltKey) - modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) - modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) - modifiers |= RawInputModifiers.Meta; - - if ((e.Buttons & 1L) == 1) - modifiers |= RawInputModifiers.LeftMouseButton; - - if ((e.Buttons & 2L) == 2) - modifiers |= e.Type == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton; - - if ((e.Buttons & 4L) == 4) - modifiers |= RawInputModifiers.MiddleMouseButton; - - if ((e.Buttons & 8L) == 8) - modifiers |= RawInputModifiers.XButton1MouseButton; - - if ((e.Buttons & 16L) == 16) - modifiers |= RawInputModifiers.XButton2MouseButton; - - if ((e.Buttons & 32L) == 32) - modifiers |= RawInputModifiers.PenEraser; - - return modifiers; - } - - private static RawInputModifiers GetModifiers(KeyboardEventArgs e) - { - var modifiers = RawInputModifiers.None; - - if (e.CtrlKey) - modifiers |= RawInputModifiers.Control; - if (e.AltKey) - modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) - modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) - modifiers |= RawInputModifiers.Meta; - - return modifiers; - } - - private void OnKeyDown(KeyboardEventArgs e) - { - KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, e.Key, GetModifiers(e)); - } - - private void OnKeyUp(KeyboardEventArgs e) - { - KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, e.Key, GetModifiers(e)); - } - - private void OnFocus(FocusEventArgs e) - { - // if focus has unexpectedly moved from the input element to the container element, - // shift it back to the input element - if (_inputElementFocused && _inputHelper is not null) - { - _inputHelper.Focus(); - } - } - - [Parameter(CaptureUnmatchedValues = true)] - public IReadOnlyDictionary? AdditionalAttributes { get; set; } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant((IJSInProcessRuntime)Js); - - _avaloniaModule = await AvaloniaModule.ImportAsync(Js); - - _canvasHelper = new FocusHelperInterop(_avaloniaModule, _htmlCanvas); - _containerHelper = new FocusHelperInterop(_avaloniaModule, _containerElement); - _inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement); - - _inputHelper.CompositionEvent += InputHelperOnCompositionEvent; - _inputHelper.InputEvent += InputHelperOnInputEvent; - - HideIme(); - _canvasHelper.SetCursor("default"); - _topLevelImpl.SetCssCursor = x => - { - _inputHelper.SetCursor(x); //macOS - _canvasHelper.SetCursor(x); //windows - }; - - _nativeControlHost = new NativeControlHostInterop(_avaloniaModule, _nativeControlsContainer); - _storageProvider = await StorageProviderInterop.ImportAsync(Js); - - Console.WriteLine("starting html canvas setup"); - _interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame); - - Console.WriteLine("Interop created"); - - var skiaOptions = AvaloniaLocator.Current.GetService(); - _useGL = skiaOptions?.CustomGpuFactory != null; - - if (_useGL) - { - _jsGlInfo = _interop.InitGL(); - Console.WriteLine("jsglinfo created - init gl"); - } - else - { - var rasterInitialized = _interop.InitRaster(); - Console.WriteLine("raster initialized: {0}", rasterInitialized); - } - - if (_useGL) - { - // create the SkiaSharp context - if (_context == null) - { - _glInterface = GRGlInterface.Create(); - _context = GRContext.CreateGl(_glInterface); - - - // bump the default resource cache limit - _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); - Console.WriteLine("glcontext created and resource limit set"); - } - - _topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType, - new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi); - } - else - { - _topLevelImpl.SetSurface(ColorType, - new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData); - } - - _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - - Threading.Dispatcher.UIThread.Post(async () => - { - _interop.RequestAnimationFrame(true); - - _sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged); - _dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged); - - _sizeWatcher.Start(); - _topLevel.Prepare(); - - _topLevel.Renderer.Start(); - }); - } - } - - private void InputHelperOnInputEvent(object? sender, WebInputEventArgs e) - { - if (IsComposing) - { - return; - } - - _topLevelImpl.RawTextEvent(e.Data); - - e.Handled = true; - } - - private void InputHelperOnCompositionEvent(object? sender, WebCompositionEventArgs e) - { - if(_client == null) - { - return; - } - - switch (e.Type) - { - case WebCompositionEventArgs.WebCompositionEventType.Start: - _client.SetPreeditText(null); - IsComposing = true; - break; - case WebCompositionEventArgs.WebCompositionEventType.Update: - _client.SetPreeditText(e.Data); - break; - case WebCompositionEventArgs.WebCompositionEventType.End: - IsComposing = false; - _client.SetPreeditText(null); - _topLevelImpl.RawTextEvent(e.Data); - break; - } - } - - private void OnRenderFrame() - { - if (_useGL && (_jsGlInfo == null)) - { - Console.WriteLine("nothing to render"); - return; - } - if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0) - { - Console.WriteLine("nothing to render"); - return; - } - - ManualTriggerRenderTimer.Instance.RaiseTick(); - } - - public void Dispose() - { - _dpiWatcher?.Unsubscribe(OnDpiChanged); - _sizeWatcher?.Dispose(); - _interop?.Dispose(); - } - - private void ForceBlit() - { - // Note: this is technically a hack, but it's a kinda unique use case when - // we want to blit the previous frame - // renderer doesn't have much control over the render target - // we render on the UI thread - // We also don't want to have it as a meaningful public API. - // Therefore we have InternalsVisibleTo hack here. - - if (_topLevel.Renderer is CompositingRenderer dr) - { - dr.CompositionTarget.ImmediateUIThreadRender(); - } - } - - private void OnDpiChanged(double newDpi) - { - if (Math.Abs(_dpi - newDpi) > 0.0001) - { - _dpi = newDpi; - - _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - - _topLevelImpl.SetClientSize(_canvasSize, _dpi); - - ForceBlit(); - } - } - - private void OnSizeChanged(SKSize newSize) - { - if (_canvasSize != newSize) - { - _canvasSize = newSize; - - _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - - _topLevelImpl.SetClientSize(_canvasSize, _dpi); - - ForceBlit(); - } - } - - private void HideIme() - { - _inputHelper?.Hide(); - _containerHelper?.Focus(); - } - - public void SetClient(ITextInputMethodClient? client) - { - if (_inputHelper is null) - { - return; - } - - if(_client != null) - { - _client.SurroundingTextChanged -= SurroundingTextChanged; - } - - if(client != null) - { - client.SurroundingTextChanged += SurroundingTextChanged; - } - - _inputHelper.Clear(); - - _client = client; - - if (IsActive && _client != null) - { - _inputHelper.Show(); - _inputElementFocused = true; - _inputHelper.Focus(); - - var surroundingText = _client.SurroundingText; - - _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); - } - else - { - _inputElementFocused = false; - HideIme(); - } - } - - private void SurroundingTextChanged(object? sender, EventArgs e) - { - if(_client != null) - { - var surroundingText = _client.SurroundingText; - - _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); - } - } - - public void SetCursorRect(Rect rect) - { - _inputHelper?.Focus(); - var bounds = new PixelRect((int)rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height); - - _inputHelper?.SetBounds(bounds, _client?.SurroundingText.CursorOffset ?? 0); - _inputHelper?.Focus(); - } - - public void SetOptions(TextInputOptions options) - { - } - - public void Reset() - { - _inputHelper?.Clear(); - _inputHelper?.SetSurroundingText("", 0, 0); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs index 7970f09a58..26b4b15863 100644 --- a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs @@ -1,31 +1,28 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Media; -namespace Avalonia.Web.Blazor +namespace Avalonia.Web.Blazor; + +public static class WebAppBuilder { - public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime + public static T SetupWithSingleViewLifetime( + this T builder) + where T : AppBuilderBase, new() { - public Control? MainView { get; set; } + return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); } - public static class WebAppBuilder + public static AppBuilder Configure() + where TApp : Application, new() { - public static T SetupWithSingleViewLifetime( - this T builder) - where T : AppBuilderBase, new() - { - return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); - } + var builder = AppBuilder.Configure() + .UseBrowser(); - public static AvaloniaBlazorAppBuilder Configure() - where TApp : Application, new() - { - var builder = AvaloniaBlazorAppBuilder.Configure() - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() }); + return builder; + } - return builder; - } + internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime + { + public Control? MainView { get; set; } } } diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs deleted file mode 100644 index 6fa7bf0bde..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Avalonia.Skia; - -namespace Avalonia.Web.Blazor -{ - public class BlazorSkiaGpu : ISkiaGpu - { - public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable surfaces) - { - foreach (var surface in surfaces) - { - if (surface is BlazorSkiaSurface blazorSkiaSurface) - { - return new BlazorSkiaGpuRenderTarget(blazorSkiaSurface); - } - } - - return null; - } - - public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session) - { - return null; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs deleted file mode 100644 index 0c53825131..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Avalonia.Skia; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaGpuRenderSession : ISkiaGpuRenderSession - { - private readonly SKSurface _surface; - - - public BlazorSkiaGpuRenderSession(BlazorSkiaSurface blazorSkiaSurface, GRBackendRenderTarget renderTarget) - { - _surface = SKSurface.Create(blazorSkiaSurface.Context, renderTarget, blazorSkiaSurface.Origin, blazorSkiaSurface.ColorType); - - GrContext = blazorSkiaSurface.Context; - - ScaleFactor = blazorSkiaSurface.Scaling; - - SurfaceOrigin = blazorSkiaSurface.Origin; - } - - public void Dispose() - { - _surface.Flush(); - - _surface.Dispose(); - } - - public GRContext GrContext { get; } - - public SKSurface SkSurface => _surface; - - public double ScaleFactor { get; } - - public GRSurfaceOrigin SurfaceOrigin { get; } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs deleted file mode 100644 index fa6a39f210..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Avalonia.Skia; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaGpuRenderTarget : ISkiaGpuRenderTarget - { - private readonly GRBackendRenderTarget _renderTarget; - private readonly BlazorSkiaSurface _blazorSkiaSurface; - private readonly PixelSize _size; - - public BlazorSkiaGpuRenderTarget(BlazorSkiaSurface blazorSkiaSurface) - { - _size = blazorSkiaSurface.Size; - - var glFbInfo = new GRGlFramebufferInfo(blazorSkiaSurface.GlInfo.FboId, blazorSkiaSurface.ColorType.ToGlSizedFormat()); - { - _blazorSkiaSurface = blazorSkiaSurface; - _renderTarget = new GRBackendRenderTarget( - (int)(blazorSkiaSurface.Size.Width * blazorSkiaSurface.Scaling), - (int)(blazorSkiaSurface.Size.Height * blazorSkiaSurface.Scaling), - blazorSkiaSurface.GlInfo.Samples, - blazorSkiaSurface.GlInfo.Stencils, glFbInfo); - } - } - - public void Dispose() - { - _renderTarget.Dispose(); - } - - public ISkiaGpuRenderSession BeginRenderingSession() - { - return new BlazorSkiaGpuRenderSession(_blazorSkiaSurface, _renderTarget); - } - - public bool IsCorrupted => _blazorSkiaSurface.Size != _size; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs deleted file mode 100644 index 603a792de3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Runtime.InteropServices; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; -using Avalonia.Skia; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable - { - public SKColorType ColorType { get; set; } - - public PixelSize Size { get; set; } - - public double Scaling { get; set; } - - private FramebufferData? _fbData; - private readonly Action _blitCallback; - private readonly Action _onDisposeAction; - - public BlazorSkiaRasterSurface( - SKColorType colorType, PixelSize size, double scaling, Action blitCallback) - { - ColorType = colorType; - Size = size; - Scaling = scaling; - _blitCallback = blitCallback; - _onDisposeAction = Blit; - } - - public void Dispose() - { - _fbData?.Dispose(); - _fbData = null; - } - - public ILockedFramebuffer Lock() - { - var bytesPerPixel = 4; // TODO: derive from ColorType - var dpi = Scaling * 96.0; - var width = (int)(Size.Width * Scaling); - var height = (int)(Size.Height * Scaling); - - if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height) - { - _fbData?.Dispose(); - _fbData = new FramebufferData(width, height, bytesPerPixel); - } - - var pixelFormat = ColorType.ToPixelFormat(); - var data = _fbData.Value; - return new LockedFramebuffer( - data.Address, data.Size, data.RowBytes, - new Vector(dpi, dpi), pixelFormat, _onDisposeAction); - } - - private void Blit() - { - if (_fbData != null) - { - var data = _fbData.Value; - _blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height)); - } - } - - private readonly struct FramebufferData - { - public PixelSize Size { get; } - - public int RowBytes { get; } - - public IntPtr Address { get; } - - public FramebufferData(int width, int height, int bytesPerPixel) - { - Size = new PixelSize(width, height); - RowBytes = width * bytesPerPixel; - Address = Marshal.AllocHGlobal(width * height * bytesPerPixel); - } - - public void Dispose() - { - Marshal.FreeHGlobal(Address); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs deleted file mode 100644 index fb49df338b..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Avalonia.Web.Blazor.Interop; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaSurface : IBlazorSkiaSurface - { - public BlazorSkiaSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling, GRSurfaceOrigin origin) - { - Context = context; - GlInfo = glInfo; - ColorType = colorType; - Size = size; - Scaling = scaling; - Origin = origin; - } - - public SKColorType ColorType { get; set; } - - public PixelSize Size { get; set; } - - public GRContext Context { get; set; } - - public GRSurfaceOrigin Origin { get; set; } - - public double Scaling { get; set; } - - public SKHtmlCanvasInterop.GLInfo GlInfo { get; set; } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs b/src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs deleted file mode 100644 index bafc07ca15..0000000000 --- a/src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor -{ - internal class ClipboardImpl : IClipboard - { - public async Task GetTextAsync() - { - return await AvaloniaLocator.Current.GetRequiredService(). - InvokeAsync("navigator.clipboard.readText"); - } - - public async Task SetTextAsync(string text) - { - await AvaloniaLocator.Current.GetRequiredService(). - InvokeAsync("navigator.clipboard.writeText",text); - } - - public async Task ClearAsync() => await SetTextAsync(""); - - public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - - public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); - - public Task GetDataAsync(string format) => Task.FromResult(new()); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs deleted file mode 100644 index d921b2fa6c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Cursor.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Avalonia.Input; -using Avalonia.Platform; - -namespace Avalonia.Web.Blazor -{ - public class CssCursor : ICursorImpl - { - public const string Default = "default"; - public string? Value { get; set; } - - public CssCursor(StandardCursorType type) - { - Value = ToKeyword(type); - } - - /// - /// Create a cursor from base64 image - /// - public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) - { - Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; - } - - /// - /// Create a cursor from url to *.cur file. - /// - public CssCursor(string url, StandardCursorType fallback) - { - Value = $"url('{url}'), {ToKeyword(fallback)}"; - } - - /// - /// Create a cursor from png/svg and hotspot position - /// - public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback) - { - Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}"; - } - - private static string ToKeyword(StandardCursorType type) => type switch - { - StandardCursorType.Hand => "pointer", - StandardCursorType.Cross => "crosshair", - StandardCursorType.Help => "help", - StandardCursorType.Ibeam => "text", - StandardCursorType.No => "not-allowed", - StandardCursorType.None => "none", - StandardCursorType.Wait => "progress", - StandardCursorType.AppStarting => "wait", - - StandardCursorType.DragMove => "move", - StandardCursorType.DragCopy => "copy", - StandardCursorType.DragLink => "alias", - - StandardCursorType.UpArrow => "default",/*not found matching one*/ - StandardCursorType.SizeWestEast => "ew-resize", - StandardCursorType.SizeNorthSouth => "ns-resize", - StandardCursorType.SizeAll => "move", - - StandardCursorType.TopSide => "n-resize", - StandardCursorType.BottomSide => "s-resize", - StandardCursorType.LeftSide => "w-resize", - StandardCursorType.RightSide => "e-resize", - StandardCursorType.TopLeftCorner => "nw-resize", - StandardCursorType.TopRightCorner => "ne-resize", - StandardCursorType.BottomLeftCorner => "sw-resize", - StandardCursorType.BottomRightCorner => "se-resize", - - _ => Default, - }; - - public void Dispose() {} - } - - internal class CssCursorFactory : ICursorFactory - { - public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) - { - using var imageStream = new MemoryStream(); - cursor.Save(imageStream); - - //not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser. - var base64String = Convert.ToBase64String(imageStream.ToArray()); - return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow); - } - - ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) - { - return new CssCursor(cursorType); - } - } -} - diff --git a/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs deleted file mode 100644 index 5463893e27..0000000000 --- a/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.Web.Blazor -{ - internal interface IBlazorSkiaSurface - { - public PixelSize Size { get; set; } - - public double Scaling { get; set; } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs b/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs deleted file mode 100644 index 8bb266a942..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.ComponentModel; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke() => action?.Invoke(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T param1) => action?.Invoke(param1); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T1 p1, T2 p2) => action?.Invoke(p1, p2); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T1 p1, T2 p2, T3 p3) => action?.Invoke(p1, p2, p3); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T1 p1, T2 p2, T3 p3, T4 p4) => action?.Invoke(p1, p2, p3, p4); - } - - - - - -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs deleted file mode 100644 index ff13e95aa7..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class AvaloniaModule : JSModuleInterop - { - private AvaloniaModule(IJSRuntime js) : base(js, "./_content/Avalonia.Web.Blazor/Avalonia.js") - { - } - - public static async Task ImportAsync(IJSRuntime js) - { - var interop = new AvaloniaModule(js); - await interop.ImportAsync(); - return interop; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs deleted file mode 100644 index c86c72f29c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class DpiWatcherInterop : IDisposable - { - private const string StartSymbol = "DpiWatcher.start"; - private const string StopSymbol = "DpiWatcher.stop"; - private const string GetDpiSymbol = "DpiWatcher.getDpi"; - - private event Action? callbacksEvent; - private readonly ActionHelper _callbackHelper; - private readonly AvaloniaModule _module; - - private DotNetObjectReference>? callbackReference; - - public DpiWatcherInterop(AvaloniaModule module, Action? callback = null) - { - _module = module; - _callbackHelper = new ActionHelper((o, n) => callbacksEvent?.Invoke(n)); - - if (callback != null) - Subscribe(callback); - } - - public void Dispose() => Stop(); - - public void Subscribe(Action callback) - { - var shouldStart = callbacksEvent == null; - - callbacksEvent += callback; - - var dpi = shouldStart - ? Start() - : GetDpi(); - - callback(dpi); - } - - public void Unsubscribe(Action callback) - { - callbacksEvent -= callback; - - if (callbacksEvent == null) - Stop(); - } - - private double Start() - { - if (callbackReference != null) - return GetDpi(); - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - return _module.Invoke(StartSymbol, callbackReference); - } - - private void Stop() - { - if (callbackReference == null) - return; - - _module.Invoke(StopSymbol); - - callbackReference?.Dispose(); - callbackReference = null; - } - - public double GetDpi() => _module.Invoke(GetDpiSymbol); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs deleted file mode 100644 index 090909f98f..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Avalonia.Web.Blazor.Interop; - -internal class FocusHelperInterop -{ - private const string FocusSymbol = "FocusHelper.focus"; - private const string SetCursorSymbol = "FocusHelper.setCursor"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _inputElement; - - public FocusHelperInterop(AvaloniaModule module, ElementReference inputElement) - { - _module = module; - _inputElement = inputElement; - } - - public void Focus() => _module.Invoke(FocusSymbol, _inputElement); - - public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind); -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs deleted file mode 100644 index 8872339f91..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class WebCompositionEventArgs : EventArgs - { - public enum WebCompositionEventType - { - Start, - Update, - End - } - - public WebCompositionEventArgs(string type, string data) - { - Type = type switch - { - "compositionstart" => WebCompositionEventType.Start, - "compositionupdate" => WebCompositionEventType.Update, - "compositionend" => WebCompositionEventType.End, - _ => Type - }; - - Data = data; - } - - public WebCompositionEventType Type { get; } - - public string Data { get; } - } - - internal class WebInputEventArgs - { - public WebInputEventArgs(string type, string data) - { - Type = type; - Data = data; - } - - public string Type { get; } - - public string Data { get; } - - public bool Handled { get; set; } - } - - internal class InputHelperInterop - { - private const string ClearSymbol = "InputHelper.clear"; - private const string FocusSymbol = "InputHelper.focus"; - private const string SetCursorSymbol = "InputHelper.setCursor"; - private const string HideSymbol = "InputHelper.hide"; - private const string ShowSymbol = "InputHelper.show"; - private const string StartSymbol = "InputHelper.start"; - private const string SetSurroundingTextSymbol = "InputHelper.setSurroundingText"; - private const string SetBoundsSymbol = "InputHelper.setBounds"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _inputElement; - private readonly ActionHelper _compositionAction; - private readonly ActionHelper _inputAction; - - private DotNetObjectReference>? compositionActionReference; - private DotNetObjectReference>? inputActionReference; - - public InputHelperInterop(AvaloniaModule module, ElementReference inputElement) - { - _module = module; - _inputElement = inputElement; - - _compositionAction = new ActionHelper(OnCompositionEvent); - _inputAction = new ActionHelper(OnInputEvent); - - Start(); - } - - public event EventHandler? CompositionEvent; - public event EventHandler? InputEvent; - - private void OnCompositionEvent(string type, string data) - { - Console.WriteLine($"CompositionEvent Handler Helper {CompositionEvent == null} "); - CompositionEvent?.Invoke(this, new WebCompositionEventArgs(type, data)); - } - - private void OnInputEvent(string type, string data) - { - Console.WriteLine($"InputEvent Handler Helper {InputEvent == null} "); - - var args = new WebInputEventArgs(type, data); - - InputEvent?.Invoke(this, args); - } - - public void Clear() => _module.Invoke(ClearSymbol, _inputElement); - - public void Focus() => _module.Invoke(FocusSymbol, _inputElement); - - public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind); - - public void Hide() => _module.Invoke(HideSymbol, _inputElement); - - public void Show() => _module.Invoke(ShowSymbol, _inputElement); - - private void Start() - { - if(compositionActionReference != null) - { - return; - } - - compositionActionReference = DotNetObjectReference.Create(_compositionAction); - - inputActionReference = DotNetObjectReference.Create(_inputAction); - - _module.Invoke(StartSymbol, _inputElement, compositionActionReference, inputActionReference); - } - - public void SetSurroundingText(string text, int start, int end) - { - _module.Invoke(SetSurroundingTextSymbol, _inputElement, text, start, end); - } - - public void SetBounds(PixelRect bounds, int caret) - { - _module.Invoke(SetBoundsSymbol, _inputElement, bounds.X, bounds.Y, bounds.Width, bounds.Height, caret); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs deleted file mode 100644 index dca1b53650..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class JSModuleInterop : IDisposable - { - private readonly Task moduleTask; - private IJSUnmarshalledObjectReference? module; - - public JSModuleInterop(IJSRuntime js, string filename) - { - if (js is not IJSInProcessRuntime) - throw new NotSupportedException("SkiaSharp currently only works on Web Assembly."); - - moduleTask = js.InvokeAsync("import", filename).AsTask(); - } - - public async Task ImportAsync() - { - module = await moduleTask; - } - - public void Dispose() - { - OnDisposingModule(); - Module.Dispose(); - } - - protected IJSUnmarshalledObjectReference Module => - module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first."); - - internal void Invoke(string identifier, params object?[]? args) => - Module.InvokeVoid(identifier, args); - - internal TValue Invoke(string identifier, params object?[]? args) => - Module.Invoke(identifier, args); - - internal ValueTask InvokeAsync(string identifier, params object?[]? args) => - Module.InvokeVoidAsync(identifier, args); - - internal ValueTask InvokeAsync(string identifier, params object?[]? args) => - Module.InvokeAsync(identifier, args); - - protected virtual void OnDisposingModule() { } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs b/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs deleted file mode 100644 index b824fcae46..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using Avalonia.Controls.Platform; -using Avalonia.Platform; - -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - - internal class NativeControlHostInterop : INativeControlHostImpl - { - private const string CreateDefaultChildSymbol = "NativeControlHost.CreateDefaultChild"; - private const string CreateAttachmentSymbol = "NativeControlHost.CreateAttachment"; - private const string GetReferenceSymbol = "NativeControlHost.GetReference"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _hostElement; - - public NativeControlHostInterop(AvaloniaModule module, ElementReference element) - { - _module = module; - _hostElement = element; - } - - public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) - { - var element = _module.Invoke(CreateDefaultChildSymbol); - return new JSObjectControlHandle(element); - } - - public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func create) - { - Attachment? a = null; - try - { - using var hostElementJsReference = _module.Invoke(GetReferenceSymbol, _hostElement); - var child = create(new JSObjectControlHandle(hostElementJsReference)); - var attachmenetReference = _module.Invoke(CreateAttachmentSymbol); - // It has to be assigned to the variable before property setter is called so we dispose it on exception -#pragma warning disable IDE0017 // Simplify object initialization - a = new Attachment(attachmenetReference, child); -#pragma warning restore IDE0017 // Simplify object initialization - a.AttachedTo = this; - return a; - } - catch - { - a?.Dispose(); - throw; - } - } - - public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) - { - var attachmenetReference = _module.Invoke(CreateAttachmentSymbol); - var a = new Attachment(attachmenetReference, handle); - a.AttachedTo = this; - return a; - } - - public bool IsCompatibleWith(IPlatformHandle handle) => handle is JSObjectControlHandle; - - class Attachment : INativeControlHostControlTopLevelAttachment - { - private const string InitializeWithChildHandleSymbol = "InitializeWithChildHandle"; - private const string AttachToSymbol = "AttachTo"; - private const string ShowInBoundsSymbol = "ShowInBounds"; - private const string HideWithSizeSymbol = "HideWithSize"; - private const string ReleaseChildSymbol = "ReleaseChild"; - - private IJSInProcessObjectReference? _native; - private NativeControlHostInterop? _attachedTo; - - public Attachment(IJSInProcessObjectReference native, IPlatformHandle handle) - { - _native = native; - _native.InvokeVoid(InitializeWithChildHandleSymbol, ((JSObjectControlHandle)handle).Object); - } - - public void Dispose() - { - if (_native != null) - { - _native.InvokeVoid(ReleaseChildSymbol); - _native.Dispose(); - _native = null; - } - } - - public INativeControlHostImpl? AttachedTo - { - get => _attachedTo!; - set - { - CheckDisposed(); - - var host = (NativeControlHostInterop?)value; - if (host == null) - { - _native.InvokeVoid(AttachToSymbol); - } - else - { - _native.InvokeVoid(AttachToSymbol, host._hostElement); - } - _attachedTo = host; - } - } - - public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostInterop; - - public void HideWithSize(Size size) - { - CheckDisposed(); - if (_attachedTo == null) - return; - - _native.InvokeVoid(HideWithSizeSymbol, Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height)); - } - - public void ShowInBounds(Rect bounds) - { - CheckDisposed(); - - if (_attachedTo == null) - throw new InvalidOperationException("Native control isn't attached to a toplevel"); - - bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), - Math.Max(1, bounds.Height)); - - _native.InvokeVoid(ShowInBoundsSymbol, (float)bounds.X, (float)bounds.Y, (float)bounds.Width, (float)bounds.Height); - } - - [MemberNotNull(nameof(_native))] - private void CheckDisposed() - { - if (_native == null) - throw new ObjectDisposedException(nameof(Attachment)); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs deleted file mode 100644 index cf9350fb62..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class SKHtmlCanvasInterop : IDisposable - { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/SKHtmlCanvas.js"; - private const string InitGLSymbol = "SKHtmlCanvas.initGL"; - private const string InitRasterSymbol = "SKHtmlCanvas.initRaster"; - private const string DeinitSymbol = "SKHtmlCanvas.deinit"; - private const string RequestAnimationFrameSymbol = "SKHtmlCanvas.requestAnimationFrame"; - private const string SetCanvasSizeSymbol = "SKHtmlCanvas.setCanvasSize"; - private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _htmlCanvas; - private readonly string _htmlElementId; - private readonly ActionHelper _callbackHelper; - - private DotNetObjectReference? callbackReference; - - public SKHtmlCanvasInterop(AvaloniaModule module, ElementReference element, Action renderFrameCallback) - { - _module = module; - _htmlCanvas = element; - _htmlElementId = element.Id; - - _callbackHelper = new ActionHelper(renderFrameCallback); - } - - public void Dispose() => Deinit(); - - public GLInfo InitGL() - { - if (callbackReference != null) - throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - return _module.Invoke(InitGLSymbol, _htmlCanvas, _htmlElementId, callbackReference); - } - - public bool InitRaster() - { - if (callbackReference != null) - throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - return _module.Invoke(InitRasterSymbol, _htmlCanvas, _htmlElementId, callbackReference); - } - - public void Deinit() - { - if (callbackReference == null) - return; - - _module.Invoke(DeinitSymbol, _htmlElementId); - - callbackReference?.Dispose(); - } - - public void RequestAnimationFrame(bool enableRenderLoop) => - _module.Invoke(RequestAnimationFrameSymbol, _htmlCanvas, enableRenderLoop); - - public void SetCanvasSize(int rawWidth, int rawHeight) => - _module.Invoke(SetCanvasSizeSymbol, _htmlCanvas, rawWidth, rawHeight); - - public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => - _module.Invoke(PutImageDataSymbol, _htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); - - public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs deleted file mode 100644 index e21ae837d0..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class SizeWatcherInterop : IDisposable - { - private const string ObserveSymbol = "SizeWatcher.observe"; - private const string UnobserveSymbol = "SizeWatcher.unobserve"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _htmlElement; - private readonly string _htmlElementId; - private readonly ActionHelper _callbackHelper; - - private DotNetObjectReference>? callbackReference; - - public SizeWatcherInterop(AvaloniaModule module, ElementReference element, Action callback) - { - _module = module; - _htmlElement = element; - _htmlElementId = element.Id; - _callbackHelper = new ActionHelper((x, y) => callback(new SKSize(x, y))); - } - - public void Dispose() => Stop(); - - public void Start() - { - if (callbackReference != null) - return; - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - _module.Invoke(ObserveSymbol, _htmlElement, _htmlElementId, callbackReference); - } - - public void Stop() - { - if (callbackReference == null) - return; - - _module.Invoke(UnobserveSymbol, _htmlElementId); - - callbackReference?.Dispose(); - callbackReference = null; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs deleted file mode 100644 index 690d9683df..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using Avalonia.Platform.Storage; - -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop.Storage -{ - internal record FilePickerAcceptType(string Description, IReadOnlyDictionary> Accept); - - internal record FileProperties(ulong Size, long LastModified, string? Type); - - internal class StorageProviderInterop : JSModuleInterop, IStorageProvider - { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/Storage.js"; - private const string PickerCancelMessage = "The user aborted a request"; - - public static async Task ImportAsync(IJSRuntime js) - { - var interop = new StorageProviderInterop(js); - await interop.ImportAsync(); - return interop; - } - - public StorageProviderInterop(IJSRuntime js) - : base(js, JsFilename) - { - } - - public bool CanOpen => Invoke("StorageProvider.canOpen"); - public bool CanSave => Invoke("StorageProvider.canSave"); - public bool CanPickFolder => Invoke("StorageProvider.canPickFolder"); - - public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) - { - try - { - var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - - var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter); - var items = await InvokeAsync("StorageProvider.openFileDialog", startIn, options.AllowMultiple, types, exludeAll); - var count = items.Invoke("count"); - - return Enumerable.Range(0, count) - .Select(index => new JSStorageFile(items.Invoke("at", index))) - .ToArray(); - } - catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) - { - return Array.Empty(); - } - } - - public async Task SaveFilePickerAsync(FilePickerSaveOptions options) - { - try - { - var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - - var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices); - var item = await InvokeAsync("StorageProvider.saveFileDialog", startIn, options.SuggestedFileName, types, exludeAll); - - return item is not null ? new JSStorageFile(item) : null; - } - catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) - { - return null; - } - } - - public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) - { - try - { - var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - - var item = await InvokeAsync("StorageProvider.selectFolderDialog", startIn); - - return item is not null ? new[] { new JSStorageFolder(item) } : Array.Empty(); - } - catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) - { - return Array.Empty(); - } - } - - public async Task OpenFileBookmarkAsync(string bookmark) - { - var item = await InvokeAsync("StorageProvider.openBookmark", bookmark); - return item is not null ? new JSStorageFile(item) : null; - } - - public async Task OpenFolderBookmarkAsync(string bookmark) - { - var item = await InvokeAsync("StorageProvider.openBookmark", bookmark); - return item is not null ? new JSStorageFolder(item) : null; - } - - private static (FilePickerAcceptType[]? types, bool excludeAllOption) ConvertFileTypes(IEnumerable? input) - { - var types = input? - .Where(t => t.MimeTypes?.Any() == true && t != FilePickerFileTypes.All) - .Select(t => new FilePickerAcceptType(t.Name, t.MimeTypes! - .ToDictionary(m => m, _ => (IReadOnlyList)Array.Empty()))) - .ToArray(); - if (types?.Length == 0) - { - types = null; - } - - var inlcudeAll = input?.Contains(FilePickerFileTypes.All) == true || types is null; - - return (types, !inlcudeAll); - } - } - - internal abstract class JSStorageItem : IStorageBookmarkItem - { - internal IJSInProcessObjectReference? _fileHandle; - - protected JSStorageItem(IJSInProcessObjectReference fileHandle) - { - _fileHandle = fileHandle ?? throw new ArgumentNullException(nameof(fileHandle)); - } - - internal IJSInProcessObjectReference FileHandle => _fileHandle ?? throw new ObjectDisposedException(nameof(JSStorageItem)); - - public string Name => FileHandle.Invoke("getName"); - - public bool TryGetUri([NotNullWhen(true)] out Uri? uri) - { - uri = new Uri(Name, UriKind.Relative); - return false; - } - - public async Task GetBasicPropertiesAsync() - { - var properties = await FileHandle.InvokeAsync("getProperties"); - - return new StorageItemProperties( - properties?.Size, - dateCreated: null, - dateModified: properties?.LastModified > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(properties.LastModified) : null); - } - - public bool CanBookmark => true; - - public Task SaveBookmarkAsync() - { - return FileHandle.InvokeAsync("saveBookmark").AsTask(); - } - - public Task GetParentAsync() - { - return Task.FromResult(null); - } - - public Task ReleaseBookmarkAsync() - { - return FileHandle.InvokeAsync("deleteBookmark").AsTask(); - } - - public void Dispose() - { - _fileHandle?.Dispose(); - _fileHandle = null; - } - } - - internal class JSStorageFile : JSStorageItem, IStorageBookmarkFile - { - public JSStorageFile(IJSInProcessObjectReference fileHandle) : base(fileHandle) - { - } - - public bool CanOpenRead => true; - public async Task OpenReadAsync() - { - var stream = await FileHandle.InvokeAsync("openRead"); - // Remove maxAllowedSize limit, as developer can decide if they read only small part or everything. - return await stream.OpenReadStreamAsync(long.MaxValue, CancellationToken.None); - } - - public bool CanOpenWrite => true; - public async Task OpenWriteAsync() - { - var properties = await FileHandle.InvokeAsync("getProperties"); - var streamWriter = await FileHandle.InvokeAsync("openWrite"); - - return new JSWriteableStream(streamWriter, (long)(properties?.Size ?? 0)); - } - } - - internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder - { - public JSStorageFolder(IJSInProcessObjectReference fileHandle) : base(fileHandle) - { - } - - public async Task> GetItemsAsync() - { - var items = await FileHandle.InvokeAsync("getItems"); - if (items is null) - { - return Array.Empty(); - } - - var count = items.Invoke("count"); - - return Enumerable.Range(0, count) - .Select(index => - { - var reference = items.Invoke("at", index); - return reference.Invoke("getKind") switch - { - "directory" => (IStorageItem)new JSStorageFolder(reference), - "file" => new JSStorageFile(reference), - _ => null - }; - }) - .Where(i => i is not null) - .ToArray()!; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs deleted file mode 100644 index 55a7831b1a..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Buffers; -using System.Text.Json.Serialization; - -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop.Storage -{ - // Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream - internal sealed class JSWriteableStream : Stream - { - private IJSInProcessObjectReference? _jSReference; - - // Unfortunatelly we can't read current length/position, so we need to keep it C#-side only. - private long _length, _position; - - internal JSWriteableStream(IJSInProcessObjectReference jSReference, long initialLength) - { - _jSReference = jSReference; - _length = initialLength; - } - - private IJSInProcessObjectReference JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(JSWriteableStream)); - - public override bool CanRead => false; - - public override bool CanSeek => true; - - public override bool CanWrite => true; - - public override long Length => _length; - - public override long Position - { - get => _position; - set => Seek(_position, SeekOrigin.Begin); - } - - public override void Flush() - { - // no-op - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - var position = origin switch - { - SeekOrigin.Current => _position + offset, - SeekOrigin.End => _length + offset, - _ => offset - }; - JSReference.InvokeVoid("seek", position); - return position; - } - - public override void SetLength(long value) - { - _length = value; - - // See https://docs.w3cub.com/dom/filesystemwritablefilestream/truncate - // If the offset is smaller than the size, it remains unchanged. If the offset is larger than size, the offset is set to that size - if (_position > _length) - { - _position = _length; - } - - JSReference.InvokeVoid("truncate", value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("Synchronous writes are not supported."); - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (offset != 0 || count != buffer.Length) - { - // TODO, we need to pass prepared buffer to the JS - // Can't use ArrayPool as it can return bigger array than requested - // Can't use Span/Memory, as it's not supported by JS interop yet. - // Alternatively we can pass original buffer and offset+count, so it can be trimmed on the JS side (but is it more efficient tho?) - buffer = buffer.AsMemory(offset, count).ToArray(); - } - return WriteAsyncInternal(buffer, cancellationToken).AsTask(); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - return WriteAsyncInternal(buffer.ToArray(), cancellationToken); - } - - private ValueTask WriteAsyncInternal(byte[] buffer, CancellationToken _) - { - _position += buffer.Length; - - return JSReference.InvokeVoidAsync("write", buffer); - } - - protected override void Dispose(bool disposing) - { - if (_jSReference is { } jsReference) - { - _jSReference = null; - jsReference.InvokeVoid("close"); - jsReference.Dispose(); - } - } - - public override async ValueTask DisposeAsync() - { - if (_jSReference is { } jsReference) - { - _jSReference = null; - await jsReference.InvokeVoidAsync("close"); - await jsReference.DisposeAsync(); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs b/src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs deleted file mode 100644 index 4426c3fbd7..0000000000 --- a/src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable enable -using Avalonia.Controls.Platform; - -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor -{ - public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle - { - internal const string ElementReferenceDescriptor = "JSObjectReference"; - - public JSObjectControlHandle(IJSObjectReference reference) - { - Object = reference; - } - - public IJSObjectReference Object { get; } - - public IntPtr Handle => throw new NotSupportedException(); - - public string? HandleDescriptor => ElementReferenceDescriptor; - - public void Destroy() - { - if (Object is IJSInProcessObjectReference inProcess) - { - inProcess.Dispose(); - } - else - { - _ = Object.DisposeAsync(); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Keycodes.cs b/src/Web/Avalonia.Web.Blazor/Keycodes.cs deleted file mode 100644 index ea30f0a9f0..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Keycodes.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Avalonia.Input; - -namespace Avalonia.Web.Blazor -{ - internal static class Keycodes - { - public static Dictionary KeyCodes = new() - { - { "Escape", Key.Escape }, - { "Digit1", Key.D1 }, - { "Digit2", Key.D2 }, - { "Digit3", Key.D3 }, - { "Digit4", Key.D4 }, - { "Digit5", Key.D5 }, - { "Digit6", Key.D6 }, - { "Digit7", Key.D7 }, - { "Digit8", Key.D8 }, - { "Digit9", Key.D9 }, - { "Digit0", Key.D0 }, - { "Minus", Key.OemMinus }, - //{ "Equal" , Key. }, - { "Backspace", Key.Back }, - { "Tab", Key.Tab }, - { "KeyQ", Key.Q }, - { "KeyW", Key.W }, - { "KeyE", Key.E }, - { "KeyR", Key.R }, - { "KeyT", Key.T }, - { "KeyY", Key.Y }, - { "KeyU", Key.U }, - { "KeyI", Key.I }, - { "KeyO", Key.O }, - { "KeyP", Key.P }, - { "BracketLeft", Key.OemOpenBrackets }, - { "BracketRight", Key.OemCloseBrackets }, - { "Enter", Key.Enter }, - { "ControlLeft", Key.LeftCtrl }, - { "KeyA", Key.A }, - { "KeyS", Key.S }, - { "KeyD", Key.D }, - { "KeyF", Key.F }, - { "KeyG", Key.G }, - { "KeyH", Key.H }, - { "KeyJ", Key.J }, - { "KeyK", Key.K }, - { "KeyL", Key.L }, - { "Semicolon", Key.OemSemicolon }, - { "Quote", Key.OemQuotes }, - //{ "Backquote" , Key. }, - { "ShiftLeft", Key.LeftShift }, - { "Backslash", Key.OemBackslash }, - { "KeyZ", Key.Z }, - { "KeyX", Key.X }, - { "KeyC", Key.C }, - { "KeyV", Key.V }, - { "KeyB", Key.B }, - { "KeyN", Key.N }, - { "KeyM", Key.M }, - { "Comma", Key.OemComma }, - { "Period", Key.OemPeriod }, - //{ "Slash" , Key. }, - { "ShiftRight", Key.RightShift }, - { "NumpadMultiply", Key.Multiply }, - { "AltLeft", Key.LeftAlt }, - { "Space", Key.Space }, - { "CapsLock", Key.CapsLock }, - { "F1", Key.F1 }, - { "F2", Key.F2 }, - { "F3", Key.F3 }, - { "F4", Key.F4 }, - { "F5", Key.F5 }, - { "F6", Key.F6 }, - { "F7", Key.F7 }, - { "F8", Key.F8 }, - { "F9", Key.F9 }, - { "F10", Key.F10 }, - { "NumLock", Key.NumLock }, - { "ScrollLock", Key.Scroll }, - { "Numpad7", Key.NumPad7 }, - { "Numpad8", Key.NumPad8 }, - { "Numpad9", Key.NumPad9 }, - { "NumpadSubtract", Key.Subtract }, - { "Numpad4", Key.NumPad4 }, - { "Numpad5", Key.NumPad5 }, - { "Numpad6", Key.NumPad6 }, - { "NumpadAdd", Key.Add }, - { "Numpad1", Key.NumPad1 }, - { "Numpad2", Key.NumPad2 }, - { "Numpad3", Key.NumPad3 }, - { "Numpad0", Key.NumPad0 }, - { "NumpadDecimal", Key.Decimal }, - { "Unidentified", Key.NoName }, - //{ "IntlBackslash" , Key.bac }, - { "F11", Key.F11 }, - { "F12", Key.F12 }, - //{ "IntlRo" , Key.Ro }, - //{ "Unidentified" , Key. }, - { "Convert", Key.ImeConvert }, - { "KanaMode", Key.KanaMode }, - { "NonConvert", Key.ImeNonConvert }, - //{ "Unidentified" , Key. }, - { "NumpadEnter", Key.Enter }, - { "ControlRight", Key.RightCtrl }, - { "NumpadDivide", Key.Divide }, - { "PrintScreen", Key.PrintScreen }, - { "AltRight", Key.RightAlt }, - //{ "Unidentified" , Key. }, - { "Home", Key.Home }, - { "ArrowUp", Key.Up }, - { "PageUp", Key.PageUp }, - { "ArrowLeft", Key.Left }, - { "ArrowRight", Key.Right }, - { "End", Key.End }, - { "ArrowDown", Key.Down }, - { "PageDown", Key.PageDown }, - { "Insert", Key.Insert }, - { "Delete", Key.Delete }, - //{ "Unidentified" , Key. }, - { "AudioVolumeMute", Key.VolumeMute }, - { "AudioVolumeDown", Key.VolumeDown }, - { "AudioVolumeUp", Key.VolumeUp }, - //{ "NumpadEqual" , Key. }, - { "Pause", Key.Pause }, - { "NumpadComma", Key.OemComma } - }; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs deleted file mode 100644 index 7b9feab2e3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Diagnostics; -using Avalonia.Rendering; - -namespace Avalonia.Web.Blazor -{ - public class ManualTriggerRenderTimer : IRenderTimer - { - private static readonly Stopwatch s_sw = Stopwatch.StartNew(); - - public static ManualTriggerRenderTimer Instance { get; } = new(); - - public void RaiseTick() => Tick?.Invoke(s_sw.Elapsed); - - public event Action? Tick; - public bool RunsInBackground => false; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs deleted file mode 100644 index 3a09c16932..0000000000 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System.Diagnostics; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Input.TextInput; -using Avalonia.Platform; -using Avalonia.Platform.Storage; -using Avalonia.Rendering; -using Avalonia.Rendering.Composition; -using Avalonia.Web.Blazor.Interop; -using SkiaSharp; - -#nullable enable - -namespace Avalonia.Web.Blazor -{ - internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider - { - private Size _clientSize; - private IBlazorSkiaSurface? _currentSurface; - private IInputRoot? _inputRoot; - private readonly Stopwatch _sw = Stopwatch.StartNew(); - private readonly AvaloniaView _avaloniaView; - private readonly TouchDevice _touchDevice; - private readonly PenDevice _penDevice; - private string _currentCursor = CssCursor.Default; - - public RazorViewTopLevelImpl(AvaloniaView avaloniaView) - { - _avaloniaView = avaloniaView; - TransparencyLevel = WindowTransparencyLevel.None; - AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1); - _touchDevice = new TouchDevice(); - _penDevice = new PenDevice(); - } - - public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds; - - - internal void SetSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling) - { - _currentSurface = - new BlazorSkiaSurface(context, glInfo, colorType, size, scaling, GRSurfaceOrigin.BottomLeft); - } - - internal void SetSurface(SKColorType colorType, PixelSize size, double scaling, Action blitCallback) - { - _currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback); - } - - public void SetClientSize(SKSize size, double dpi) - { - var newSize = new Size(size.Width, size.Height); - - if (Math.Abs(RenderScaling - dpi) > 0.0001) - { - if (_currentSurface is { }) - { - _currentSurface.Scaling = dpi; - } - - ScalingChanged?.Invoke(dpi); - } - - if (newSize != _clientSize) - { - _clientSize = newSize; - - if (_currentSurface is { }) - { - _currentSurface.Size = new PixelSize((int)size.Width, (int)size.Height); - } - - Resized?.Invoke(newSize, PlatformResizeReason.User); - } - } - - public void RawPointerEvent( - RawPointerEventType eventType, string pointerType, - RawPointerPoint p, RawInputModifiers modifiers, long touchPointId) - { - if (_inputRoot is { } - && Input is { } input) - { - var device = GetPointerDevice(pointerType); - var args = device is TouchDevice ? - new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) : - new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers) - { - RawPointerId = touchPointId - }; - - input.Invoke(args); - } - } - - private IPointerDevice GetPointerDevice(string pointerType) - { - return pointerType switch - { - "touch" => _touchDevice, - "pen" => _penDevice, - _ => MouseDevice - }; - } - - public void RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers) - { - if (_inputRoot is { }) - { - Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, _inputRoot, p, v, modifiers)); - } - } - - public bool RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers) - { - if (Keycodes.KeyCodes.TryGetValue(code, out var avkey)) - { - if (_inputRoot is { }) - { - var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers); - - Input?.Invoke(args); - - return args.Handled; - } - } - else if (Keycodes.KeyCodes.TryGetValue(key, out avkey)) - { - if (_inputRoot is { }) - { - var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers); - - Input?.Invoke(args); - - return args.Handled; - } - } - - return false; - } - - public void RawTextEvent(string text) - { - if (_inputRoot is { }) - { - Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text)); - } - } - - public void Dispose() - { - - } - - public IRenderer CreateRenderer(IRenderRoot root) - { - var loop = AvaloniaLocator.Current.GetRequiredService(); - return new CompositingRenderer(root, new Compositor(loop, null)); - } - - public void Invalidate(Rect rect) - { - //Console.WriteLine("invalidate rect called"); - } - - public void SetInputRoot(IInputRoot inputRoot) - { - _inputRoot = inputRoot; - } - - public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y); - - public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y); - - public void SetCursor(ICursorImpl? cursor) - { - var val = (cursor as CssCursor)?.Value ?? CssCursor.Default; - if (_currentCursor != val) - { - SetCssCursor?.Invoke(val); - _currentCursor = val; - } - } - - public IPopupImpl? CreatePopup() - { - return null; - } - - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) - { - - } - - public Size ClientSize => _clientSize; - public Size? FrameSize => null; - public double RenderScaling => _currentSurface?.Scaling ?? 1; - - public IEnumerable Surfaces => new object[] { _currentSurface! }; - - public Action? SetCssCursor { get; set; } - public Action? Input { get; set; } - public Action? Paint { get; set; } - public Action? Resized { get; set; } - public Action? ScalingChanged { get; set; } - public Action? TransparencyLevelChanged { get; set; } - public Action? Closed { get; set; } - public Action? LostFocus { get; set; } - public IMouseDevice MouseDevice { get; } = new MouseDevice(); - - public IKeyboardDevice KeyboardDevice { get; } = BlazorWindowingPlatform.Keyboard; - public WindowTransparencyLevel TransparencyLevel { get; } - public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } - - public ITextInputMethodImpl TextInputMethod => _avaloniaView; - - public INativeControlHostImpl? NativeControlHost => _avaloniaView.GetNativeControlHostImpl(); - public IStorageProvider StorageProvider => _avaloniaView.GetStorageProvider(); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs deleted file mode 100644 index 808d1526d3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/WinStubs.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Avalonia.Platform; - -#nullable enable - -namespace Avalonia.Web.Blazor -{ - internal class IconLoaderStub : IPlatformIconLoader - { - private class IconStub : IWindowIconImpl - { - public void Save(Stream outputStream) - { - - } - } - - public IWindowIconImpl LoadIcon(string fileName) => new IconStub(); - - public IWindowIconImpl LoadIcon(Stream stream) => new IconStub(); - - public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub(); - } - - internal class ScreenStub : IScreenImpl - { - public int ScreenCount => 1; - - public IReadOnlyList AllScreens { get; } = - new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; - - public Screen? ScreenFromPoint(PixelPoint point) - { - return ScreenHelper.ScreenFromPoint(point, AllScreens); - } - - public Screen? ScreenFromRect(PixelRect rect) - { - return ScreenHelper.ScreenFromRect(rect, AllScreens); - } - - public Screen? ScreenFromWindow(IWindowBaseImpl window) - { - return ScreenHelper.ScreenFromWindow(window, AllScreens); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs deleted file mode 100644 index 46c05d8e8c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Threading; - -#nullable enable - -namespace Avalonia.Web.Blazor -{ - public class BlazorWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface - { - private bool _signaled; - private static KeyboardDevice? s_keyboard; - - public IWindowImpl CreateWindow() => throw new NotSupportedException(); - - IWindowImpl IWindowingPlatform.CreateEmbeddableWindow() - { - throw new NotImplementedException(); - } - - public ITrayIconImpl? CreateTrayIcon() - { - return null; - } - - public static KeyboardDevice Keyboard => s_keyboard ?? - throw new InvalidOperationException("BlazorWindowingPlatform not registered."); - - public static void Register() - { - var instance = new BlazorWindowingPlatform(); - s_keyboard = new KeyboardDevice(); - AvaloniaLocator.CurrentMutable - .Bind().ToSingleton() - .Bind().ToSingleton() - .Bind().ToConstant(s_keyboard) - .Bind().ToConstant(instance) - .Bind().ToConstant(instance) - .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(ManualTriggerRenderTimer.Instance) - .Bind().ToConstant(instance) - .Bind().ToSingleton() - .Bind().ToSingleton(); - } - - public Size DoubleClickSize { get; } = new Size(2, 2); - - public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); - - public Size TouchDoubleClickSize => new Size(16, 16); - - public TimeSpan TouchDoubleClickTime => DoubleClickTime; - public void RunLoop(CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } - - public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) - { - return GetRuntimePlatform() - .StartSystemTimer(interval, () => - { - Dispatcher.UIThread.RunJobs(priority); - tick(); - }); - } - - public void Signal(DispatcherPriority priority) - { - if (_signaled) - return; - - _signaled = true; - - IDisposable? disp = null; - - disp = GetRuntimePlatform() - .StartSystemTimer(TimeSpan.FromMilliseconds(1), - () => - { - _signaled = false; - disp?.Dispose(); - - Signaled?.Invoke(null); - }); - } - - public bool CurrentThreadIsLoopThread - { - get - { - return true; // Blazor is single threaded. - } - } - - public event Action? Signaled; - - private static IRuntimePlatform GetRuntimePlatform() - { - return AvaloniaLocator.Current.GetRequiredService(); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/_Imports.razor b/src/Web/Avalonia.Web.Blazor/_Imports.razor deleted file mode 100644 index 77285129da..0000000000 --- a/src/Web/Avalonia.Web.Blazor/_Imports.razor +++ /dev/null @@ -1 +0,0 @@ -@using Microsoft.AspNetCore.Components.Web diff --git a/src/Web/Avalonia.Web.Blazor/webapp/build.js b/src/Web/Avalonia.Web.Blazor/webapp/build.js deleted file mode 100644 index 9d585dcade..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/build.js +++ /dev/null @@ -1,16 +0,0 @@ -require("esbuild").build({ - entryPoints: [ - "./modules/Avalonia.ts", - "./modules/Storage.ts" - ], - outdir: "../wwwroot", - bundle: true, - minify: true, - format: "esm", - target: "es2016", - platform: "browser", - sourcemap: "linked", - loader: {".ts": "ts"} - }) - .then(() => console.log("⚡ Done")) - .catch(() => process.exit(1)); \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts deleted file mode 100644 index 369f628a44..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { DpiWatcher } from "./Avalonia/DpiWatcher" -export { InputHelper } from "./Avalonia/InputHelper" -export { FocusHelper } from "./Avalonia/FocusHelper" -export { NativeControlHost } from "./Avalonia/NativeControlHost" -export { SizeWatcher } from "./Avalonia/SizeWatcher" -export { SKHtmlCanvas } from "./Avalonia/SKHtmlCanvas" -export { CaretHelper } from "./Avalonia/CaretHelper" diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts deleted file mode 100644 index 5709854087..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Based on https://github.com/component/textarea-caret-position/blob/master/index.js -export class CaretHelper { - public static getCaretCoordinates( - element: HTMLInputElement | HTMLTextAreaElement, - position: number, - options?: { debug: boolean } - ) { - if (!isBrowser) { - throw new Error( - "textarea-caret-position#getCaretCoordinates should only be called in a browser" - ); - } - - const debug = (options && options.debug) || false; - if (debug) { - const el = document.querySelector( - "#input-textarea-caret-position-mirror-div" - ); - if (el) el.parentNode?.removeChild(el); - } - - // The mirror div will replicate the textarea's style - const div = document.createElement("div"); - div.id = "input-textarea-caret-position-mirror-div"; - document.body.appendChild(div); - - const style = div.style; - const computed = window.getComputedStyle - ? window.getComputedStyle(element) - : ((element as any)["currentStyle"] as CSSStyleDeclaration); // currentStyle for IE < 9 - const isInput = element.nodeName === "INPUT"; - - // Default textarea styles - style.whiteSpace = "pre-wrap"; - if (!isInput) style.wordWrap = "break-word"; // only for textarea-s - - // Position off-screen - style.position = "absolute"; // required to return coordinates properly - if (!debug) style.visibility = "hidden"; // not 'display: none' because we want rendering - - // Transfer the element's properties to the div - properties.forEach((prop: string) => { - if (isInput && prop === "lineHeight") { - // Special case for s because text is rendered centered and line height may be != height - if (computed.boxSizing === "border-box") { - const height = parseInt(computed.height); - const outerHeight = - parseInt(computed.paddingTop) + - parseInt(computed.paddingBottom) + - parseInt(computed.borderTopWidth) + - parseInt(computed.borderBottomWidth); - const targetHeight = outerHeight + parseInt(computed.lineHeight); - if (height > targetHeight) { - style.lineHeight = height - outerHeight + "px"; - } else if (height === targetHeight) { - style.lineHeight = computed.lineHeight; - } else { - style.lineHeight = "0"; - } - } else { - style.lineHeight = computed.height; - } - } else { - (style as any)[prop] = (computed as any)[prop]; - } - }); - - if (isFirefox) { - // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275 - if (element.scrollHeight > parseInt(computed.height)) - style.overflowY = "scroll"; - } else { - style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll' - } - - div.textContent = element.value.substring(0, position); - // The second special handling for input type="text" vs textarea: - // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037 - if (isInput) div.textContent = div.textContent.replace(/\s/g, "\u00a0"); - - const span = document.createElement("span"); - // Wrapping must be replicated *exactly*, including when a long word gets - // onto the next line, with whitespace at the end of the line before (#7). - // The *only* reliable way to do that is to copy the *entire* rest of the - // textarea's content into the created at the caret position. - // For inputs, just '.' would be enough, but no need to bother. - span.textContent = element.value.substring(position) || "."; // || because a completely empty faux span doesn't render at all - div.appendChild(span); - - const coordinates = { - top: span.offsetTop + parseInt(computed["borderTopWidth"]), - left: span.offsetLeft + parseInt(computed["borderLeftWidth"]), - height: parseInt(computed["lineHeight"]), - }; - - if (debug) { - span.style.backgroundColor = "#aaa"; - } else { - document.body.removeChild(div); - } - - return coordinates; - } -} - - -var properties = [ - "direction", // RTL support - "boxSizing", - "width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does - "height", - "overflowX", - "overflowY", // copy the scrollbar for IE - - "borderTopWidth", - "borderRightWidth", - "borderBottomWidth", - "borderLeftWidth", - "borderStyle", - - "paddingTop", - "paddingRight", - "paddingBottom", - "paddingLeft", - - // https://developer.mozilla.org/en-US/docs/Web/CSS/font - "fontStyle", - "fontVariant", - "fontWeight", - "fontStretch", - "fontSize", - "fontSizeAdjust", - "lineHeight", - "fontFamily", - - "textAlign", - "textTransform", - "textIndent", - "textDecoration", // might not make a difference, but better be safe - - "letterSpacing", - "wordSpacing", - - "tabSize", - "MozTabSize", -]; - -const isBrowser = typeof window !== "undefined"; -const isFirefox = isBrowser && (window as any).mozInnerScreenX != null; diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts deleted file mode 100644 index 06235782f8..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts +++ /dev/null @@ -1,40 +0,0 @@ -export class DpiWatcher { - static lastDpi: number; - static timerId: number; - static callback?: DotNet.DotNetObject; - - public static getDpi() { - return window.devicePixelRatio; - } - - public static start(callback: DotNet.DotNetObject): number { - //console.info(`Starting DPI watcher with callback ${callback._id}...`); - - DpiWatcher.lastDpi = window.devicePixelRatio; - DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000); - DpiWatcher.callback = callback; - - return DpiWatcher.lastDpi; - } - - public static stop() { - //console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`); - - window.clearInterval(DpiWatcher.timerId); - - DpiWatcher.callback = undefined; - } - - static update() { - if (!DpiWatcher.callback) - return; - - const currentDpi = window.devicePixelRatio; - const lastDpi = DpiWatcher.lastDpi; - DpiWatcher.lastDpi = currentDpi; - - if (Math.abs(lastDpi - currentDpi) > 0.001) { - DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts deleted file mode 100644 index 96ffee3d53..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class FocusHelper { - public static focus(inputElement: HTMLElement) { - inputElement.focus(); - } - - public static setCursor(inputElement: HTMLInputElement, kind: string) { - inputElement.style.cursor = kind; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts deleted file mode 100644 index 1ea85d84e3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {CaretHelper} from "./CaretHelper"; - -export class InputHelper { - static inputCallback?: DotNet.DotNetObject; - static compositionCallback?: DotNet.DotNetObject - - public static start(inputElement: HTMLInputElement, compositionCallback: DotNet.DotNetObject, inputCallback: DotNet.DotNetObject) - { - InputHelper.compositionCallback = compositionCallback; - - inputElement.addEventListener('compositionstart', InputHelper.onCompositionEvent); - inputElement.addEventListener('compositionupdate', InputHelper.onCompositionEvent); - inputElement.addEventListener('compositionend', InputHelper.onCompositionEvent); - - InputHelper.inputCallback = inputCallback; - - inputElement.addEventListener('input', InputHelper.onInputEvent); - } - - public static clear(inputElement: HTMLInputElement) { - inputElement.value = ""; - } - public static focus(inputElement: HTMLInputElement) { - inputElement.focus(); - } - - public static setCursor(inputElement: HTMLInputElement, kind: string) { - inputElement.style.cursor = kind; - } - - public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) { - inputElement.style.left = (x).toFixed(0) + "px"; - inputElement.style.top = (y).toFixed(0) + "px"; - - let {height, left, top} = CaretHelper.getCaretCoordinates(inputElement, caret); - - inputElement.style.left = (x - left).toFixed(0) + "px"; - inputElement.style.top = (y - top).toFixed(0) + "px"; - } - - public static hide(inputElement: HTMLInputElement) { - inputElement.style.display = 'none'; - } - - public static show(inputElement: HTMLInputElement) { - inputElement.style.display = 'block'; - } - - public static setSurroundingText(inputElement: HTMLInputElement, text: string, start: number, end: number) { - if (!inputElement) { - return; - } - - inputElement.value = text; - inputElement.setSelectionRange(start, end); - inputElement.style.width = "20px"; - inputElement.style.width = inputElement.scrollWidth + "px"; - } - - private static onCompositionEvent(ev: CompositionEvent) - { - if(!InputHelper.compositionCallback) - return; - - switch (ev.type) - { - case "compositionstart": - case "compositionupdate": - case "compositionend": - InputHelper.compositionCallback.invokeMethod('Invoke', ev.type, ev.data); - break; - } - } - - private static onInputEvent(ev: Event) { - if (!InputHelper.inputCallback) - return; - - var inputEvent = ev as InputEvent; - - InputHelper.inputCallback.invokeMethod('Invoke', ev.type, inputEvent.data); - } -} - - - diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts deleted file mode 100644 index 9e5c3843c8..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts +++ /dev/null @@ -1,61 +0,0 @@ -export class NativeControlHost { - public static CreateDefaultChild(parent: HTMLElement): HTMLElement { - return document.createElement("div"); - } - - // Used to convert ElementReference to JSObjectReference. - // Is there a better way? - public static GetReference(element: Element): Element { - return element; - } - - public static CreateAttachment(): NativeControlHostTopLevelAttachment { - return new NativeControlHostTopLevelAttachment(); - } -} - -class NativeControlHostTopLevelAttachment { - _child?: HTMLElement; - _host?: HTMLElement; - - InitializeWithChildHandle(child: HTMLElement) { - this._child = child; - this._child.style.position = "absolute"; - } - - AttachTo(host: HTMLElement): void { - if (this._host && this._child) { - this._host.removeChild(this._child); - } - - this._host = host; - - if (this._host && this._child) { - this._host.appendChild(this._child); - } - } - - ShowInBounds(x: number, y: number, width: number, height: number): void { - if (this._child) { - this._child.style.top = y + "px"; - this._child.style.left = x + "px"; - this._child.style.width = width + "px"; - this._child.style.height = height + "px"; - this._child.style.display = "block"; - } - } - - HideWithSize(width: number, height: number): void { - if (this._child) { - this._child.style.width = width + "px"; - this._child.style.height = height + "px"; - this._child.style.display = "none"; - } - } - - ReleaseChild(): void { - if (this._child) { - this._child = undefined; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts deleted file mode 100644 index e934f74807..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts +++ /dev/null @@ -1,255 +0,0 @@ -// aliases for emscripten -declare let GL: any; -declare let GLctx: WebGLRenderingContext; -declare let Module: EmscriptenModule; - -// container for gl info -type SKGLViewInfo = { - context: WebGLRenderingContext | WebGL2RenderingContext | undefined; - fboId: number; - stencil: number; - sample: number; - depth: number; -} - -// alias for a potential skia html canvas -type SKHtmlCanvasElement = { - SKHtmlCanvas: SKHtmlCanvas | undefined -} & HTMLCanvasElement - -export class SKHtmlCanvas { - static elements: Map; - - htmlCanvas: HTMLCanvasElement; - glInfo?: SKGLViewInfo; - renderFrameCallback: DotNet.DotNetObject; - renderLoopEnabled: boolean = false; - renderLoopRequest: number = 0; - newWidth?: number; - newHeight?: number; - - public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKGLViewInfo | null { - var view = SKHtmlCanvas.init(true, element, elementId, callback); - if (!view || !view.glInfo) - return null; - - return view.glInfo; - } - - public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): boolean { - var view = SKHtmlCanvas.init(false, element, elementId, callback); - if (!view) - return false; - - return true; - } - - static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKHtmlCanvas | null { - var htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas) { - console.error(`No canvas element was provided.`); - return null; - } - - if (!SKHtmlCanvas.elements) - SKHtmlCanvas.elements = new Map(); - SKHtmlCanvas.elements.set(elementId, element); - - const view = new SKHtmlCanvas(useGL, element, callback); - - htmlCanvas.SKHtmlCanvas = view; - - return view; - } - - public static deinit(elementId: string) { - if (!elementId) - return; - - const element = SKHtmlCanvas.elements.get(elementId); - SKHtmlCanvas.elements.delete(elementId); - - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.deinit(); - htmlCanvas.SKHtmlCanvas = undefined; - } - - public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); - } - - public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); - } - - public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); - } - - public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); - } - - public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObject) { - this.htmlCanvas = element; - this.renderFrameCallback = callback; - - if (useGL) { - const ctx = SKHtmlCanvas.createWebGLContext(this.htmlCanvas); - if (!ctx) { - console.error(`Failed to create WebGL context: err ${ctx}`); - return; - } - - // make current - GL.makeContextCurrent(ctx); - - // read values - const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING); - this.glInfo = { - context: ctx, - fboId: fbo ? fbo.id : 0, - stencil: GLctx.getParameter(GLctx.STENCIL_BITS), - sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES) - depth: GLctx.getParameter(GLctx.DEPTH_BITS), - }; - } - } - - public deinit() { - this.setEnableRenderLoop(false); - } - - public setCanvasSize(width: number, height: number) { - this.newWidth = width; - this.newHeight = height; - - if (this.htmlCanvas.width != this.newWidth) { - this.htmlCanvas.width = this.newWidth; - } - - if (this.htmlCanvas.height != this.newHeight) { - this.htmlCanvas.height = this.newHeight; - } - - if (this.glInfo) { - // make current - GL.makeContextCurrent(this.glInfo.context); - } - } - - public requestAnimationFrame(renderLoop?: boolean) { - // optionally update the render loop - if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) - this.setEnableRenderLoop(renderLoop); - - // skip because we have a render loop - if (this.renderLoopRequest !== 0) - return; - - // add the draw to the next frame - this.renderLoopRequest = window.requestAnimationFrame(() => { - if (this.glInfo) { - // make current - GL.makeContextCurrent(this.glInfo.context); - } - - if (this.htmlCanvas.width != this.newWidth) { - this.htmlCanvas.width = this.newWidth || 0; - } - - if (this.htmlCanvas.height != this.newHeight) { - this.htmlCanvas.height = this.newHeight || 0; - } - - this.renderFrameCallback.invokeMethod('Invoke'); - this.renderLoopRequest = 0; - - // we may want to draw the next frame - if (this.renderLoopEnabled) - this.requestAnimationFrame(); - }); - } - - public setEnableRenderLoop(enable: boolean) { - this.renderLoopEnabled = enable; - - // either start the new frame or cancel the existing one - if (enable) { - //console.info(`Enabling render loop with callback ${this.renderFrameCallback._id}...`); - this.requestAnimationFrame(); - } else if (this.renderLoopRequest !== 0) { - window.cancelAnimationFrame(this.renderLoopRequest); - this.renderLoopRequest = 0; - } - } - - public putImageData(pData: number, width: number, height: number): boolean { - if (this.glInfo || !pData || width <= 0 || width <= 0) - return false; - - var ctx = this.htmlCanvas.getContext('2d'); - if (!ctx) { - console.error(`Failed to obtain 2D canvas context.`); - return false; - } - - // make sure the canvas is scaled correctly for the drawing - this.htmlCanvas.width = width; - this.htmlCanvas.height = height; - - // set the canvas to be the bytes - var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); - var imageData = new ImageData(buffer, width, height); - ctx.putImageData(imageData, 0, 0); - - return true; - } - - static createWebGLContext(htmlCanvas: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext { - const contextAttributes = { - alpha: 1, - depth: 1, - stencil: 8, - antialias: 0, - premultipliedAlpha: 1, - preserveDrawingBuffer: 0, - preferLowPowerToHighPerformance: 0, - failIfMajorPerformanceCaveat: 0, - majorVersion: 2, - minorVersion: 0, - enableExtensionsByDefault: 1, - explicitSwapControl: 0, - renderViaOffscreenBackBuffer: 1, - }; - - let ctx: WebGLRenderingContext = GL.createContext(htmlCanvas, contextAttributes); - if (!ctx && contextAttributes.majorVersion > 1) { - console.warn('Falling back to WebGL 1.0'); - contextAttributes.majorVersion = 1; - contextAttributes.minorVersion = 0; - ctx = GL.createContext(htmlCanvas, contextAttributes); - } - - return ctx; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts deleted file mode 100644 index 715b252988..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts +++ /dev/null @@ -1,67 +0,0 @@ -type SizeWatcherElement = { - SizeWatcher: SizeWatcherInstance; -} & HTMLElement - -type SizeWatcherInstance = { - callback: DotNet.DotNetObject; -} - -export class SizeWatcher { - static observer: ResizeObserver; - static elements: Map; - - public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObject) { - if (!element || !callback) - return; - - //console.info(`Adding size watcher observation with callback ${callback._id}...`); - - SizeWatcher.init(); - - const watcherElement = element as SizeWatcherElement; - watcherElement.SizeWatcher = { - callback: callback - }; - - SizeWatcher.elements.set(elementId, element); - SizeWatcher.observer.observe(element); - - SizeWatcher.invoke(element); - } - - public static unobserve(elementId: string) { - if (!elementId || !SizeWatcher.observer) - return; - - //console.info('Removing size watcher observation...'); - - const element = SizeWatcher.elements.get(elementId)!; - - SizeWatcher.elements.delete(elementId); - SizeWatcher.observer.unobserve(element); - } - - static init() { - if (SizeWatcher.observer) - return; - - //console.info('Starting size watcher...'); - - SizeWatcher.elements = new Map(); - SizeWatcher.observer = new ResizeObserver((entries) => { - for (let entry of entries) { - SizeWatcher.invoke(entry.target); - } - }); - } - - static invoke(element: Element) { - const watcherElement = element as SizeWatcherElement; - const instance = watcherElement.SizeWatcher; - - if (!instance || !instance.callback) - return; - - return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts deleted file mode 100644 index 2c4654e81b..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts +++ /dev/null @@ -1 +0,0 @@ -export { StorageProvider } from "./Storage/StorageProvider" diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts deleted file mode 100644 index 2eaa8de2fe..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts +++ /dev/null @@ -1,79 +0,0 @@ -class InnerDbConnection { - constructor(private database: IDBDatabase) { } - - private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore { - const tx = this.database.transaction(store, mode); - return tx.objectStore(store); - } - - public put(store: string, obj: any, key?: IDBValidKey): Promise { - const os = this.openStore(store, "readwrite"); - - return new Promise((resolve, reject) => { - const response = os.put(obj, key); - response.onsuccess = () => { - resolve(response.result); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - - public get(store: string, key: IDBValidKey): any { - const os = this.openStore(store, "readonly"); - - return new Promise((resolve, reject) => { - const response = os.get(key); - response.onsuccess = () => { - resolve(response.result); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - - public delete(store: string, key: IDBValidKey): Promise { - const os = this.openStore(store, "readwrite"); - - return new Promise((resolve, reject) => { - const response = os.delete(key); - response.onsuccess = () => { - resolve(); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - - public close() { - this.database.close(); - } -} - -export class IndexedDbWrapper { - constructor(private databaseName: string, private objectStores: [string]) { - } - - public connect(): Promise { - const conn = window.indexedDB.open(this.databaseName, 1); - - conn.onupgradeneeded = event => { - const db = (>event.target).result; - this.objectStores.forEach(store => { - db.createObjectStore(store); - }); - }; - - return new Promise((resolve, reject) => { - conn.onsuccess = event => { - resolve(new InnerDbConnection((>event.target).result)); - }; - conn.onerror = event => { - reject((>event.target).error); - }; - }); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts deleted file mode 100644 index 896e174e43..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { IndexedDbWrapper } from "./IndexedDbWrapper"; - -declare global { - type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; - type StartInDirectory = WellKnownDirectory | FileSystemHandle; - interface OpenFilePickerOptions { - startIn?: StartInDirectory - } - interface SaveFilePickerOptions { - startIn?: StartInDirectory - } -} - -const fileBookmarksStore: string = "fileBookmarks"; -const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ - fileBookmarksStore -]); - -class StorageItem { - constructor(public handle: FileSystemHandle, private bookmarkId?: string) { } - - public getName(): string { - return this.handle.name - } - - public getKind(): string { - return this.handle.kind; - } - - public async openRead(): Promise { - if (!(this.handle instanceof FileSystemFileHandle)) { - throw new Error("StorageItem is not a file"); - } - - await this.verityPermissions('read'); - - const file = await this.handle.getFile(); - return file; - } - - public async openWrite(): Promise { - if (!(this.handle instanceof FileSystemFileHandle)) { - throw new Error("StorageItem is not a file"); - } - - await this.verityPermissions('readwrite'); - - return await this.handle.createWritable({ keepExistingData: true }); - } - - public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string } | null> { - const file = this.handle instanceof FileSystemFileHandle - && await this.handle.getFile(); - - if (!file) { - return null; - } - - return { - Size: file.size, - LastModified: file.lastModified, - Type: file.type - } - } - - public async getItems(): Promise { - if (this.handle.kind !== "directory"){ - return new StorageItems([]); - } - - const items: StorageItem[] = []; - for await (const [key, value] of (this.handle as any).entries()) { - items.push(new StorageItem(value)); - } - return new StorageItems(items); - } - - private async verityPermissions(mode: FileSystemPermissionMode): Promise { - if (await this.handle.queryPermission({ mode }) === 'granted') { - return; - } - - if (await this.handle.requestPermission({ mode }) === "denied") { - throw new Error("Read permissions denied"); - } - } - - public async saveBookmark(): Promise { - // If file was previously bookmarked, just return old one. - if (this.bookmarkId) { - return this.bookmarkId; - } - - const connection = await avaloniaDb.connect(); - try { - const key = await connection.put(fileBookmarksStore, this.handle, this.generateBookmarkId()); - return key; - } - finally { - connection.close(); - } - } - - public async deleteBookmark(): Promise { - if (!this.bookmarkId) { - return; - } - - const connection = await avaloniaDb.connect(); - try { - const key = await connection.delete(fileBookmarksStore, this.bookmarkId); - } - finally { - connection.close(); - } - } - - private generateBookmarkId(): string { - return Date.now().toString(36) + Math.random().toString(36).substring(2); - } -} - -class StorageItems { - constructor(private items: StorageItem[]) { } - - public count(): number { - return this.items.length; - } - - public at(index: number): StorageItem { - return this.items[index]; - } -} - -export class StorageProvider { - - public static canOpen(): boolean { - return typeof window.showOpenFilePicker !== 'undefined'; - } - - public static canSave(): boolean { - return typeof window.showSaveFilePicker !== 'undefined'; - } - - public static canPickFolder(): boolean { - return typeof window.showDirectoryPicker !== 'undefined'; - } - - public static async selectFolderDialog( - startIn: StorageItem | null) - : Promise { - - // 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined. - const options: DirectoryPickerOptions = { - startIn: (startIn?.handle || undefined) - }; - - const handle = await window.showDirectoryPicker(options); - return new StorageItem(handle); - } - - public static async openFileDialog( - startIn: StorageItem | null, multiple: boolean, - types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) - : Promise { - - const options: OpenFilePickerOptions = { - startIn: (startIn?.handle || undefined), - multiple, - excludeAcceptAllOption, - types: (types || undefined) - }; - - const handles = await window.showOpenFilePicker(options); - return new StorageItems(handles.map((handle: FileSystemHandle) => new StorageItem(handle))); - } - - public static async saveFileDialog( - startIn: StorageItem | null, suggestedName: string | null, - types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) - : Promise { - - const options: SaveFilePickerOptions = { - startIn: (startIn?.handle || undefined), - suggestedName: (suggestedName || undefined), - excludeAcceptAllOption, - types: (types || undefined) - }; - - const handle = await window.showSaveFilePicker(options); - return new StorageItem(handle); - } - - public static async openBookmark(key: string): Promise { - const connection = await avaloniaDb.connect(); - try { - const handle = await connection.get(fileBookmarksStore, key); - return handle && new StorageItem(handle, key); - } - finally { - connection.close(); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/package.json b/src/Web/Avalonia.Web.Blazor/webapp/package.json deleted file mode 100644 index 27e15b0abd..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "avalonia.web", - "scripts": { - "prebuild": "tsc -noEmit", - "build": "node build.js" - }, - "devDependencies": { - "@types/emscripten": "^1.39.6", - "@types/wicg-file-system-access": "^2020.9.5", - "typescript": "^4.7.4", - "esbuild": "^0.15.7" - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json b/src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json deleted file mode 100644 index 4e90bc230d..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "es2016", - "strict": true, - "sourceMap": true, - "outDir": "../wwwroot", - "noEmitOnError": true, - "isolatedModules": true, // we need it for esbuild - "lib": [ - "dom", - "es2016", - "esnext.asynciterable" - ] - }, - "exclude": [ - "node_modules" - ] -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts b/src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts deleted file mode 100644 index 932dfa1e1f..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Type definitions for non-npm package @blazor/javascript-interop 3.1 -// Project: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.1 -// Definitions by: Piotr Błażejewicz (Peter Blazejewicz) -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// Minimum TypeScript Version: 3.0 - -// Here be dragons! -// This is community-maintained definition file intended to ease the process of developing -// high quality JavaScript interop code to be used in Blazor application from your C# .NET code. -// Could be removed without a notice in case official definition types ships with Blazor itself. - -// tslint:disable:no-unnecessary-generics - -declare namespace DotNet { - /** - * Invokes the specified .NET public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T; - /** - * Invokes the specified .NET public method asynchronously. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; - /** - * Represents the .NET instance passed by reference to JavaScript. - */ - interface DotNetObject { - /** - * Invokes the specified .NET instance public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - invokeMethod(methodIdentifier: string, ...args: any[]): T; - /** - * Invokes the specified .NET instance public method asynchronously. - * - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise; - } -} diff --git a/src/Web/Avalonia.Web.Sample/main.js b/src/Web/Avalonia.Web.Sample/main.js index 3683aea181..87f8a4f943 100644 --- a/src/Web/Avalonia.Web.Sample/main.js +++ b/src/Web/Avalonia.Web.Sample/main.js @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { dotnet } from './dotnet.js' -import { createAvaloniaRuntime } from './avalonia.js'; +import { registerAvaloniaModule } from './avalonia.js'; const is_browser = typeof window != "undefined"; if (!is_browser) throw new Error(`Expected to be running in a browser`); @@ -12,7 +12,7 @@ const dotnetRuntime = await dotnet .withApplicationArgumentsFromQuery() .create(); -await createAvaloniaRuntime(dotnetRuntime); +await registerAvaloniaModule(dotnetRuntime); const config = dotnetRuntime.getConfig(); diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index e81620ffde..a5da719912 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -40,13 +40,12 @@ namespace Avalonia.Web 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.")) { - var host = DomHelper.GetElementById(divId); - if (host == null) - { - throw new Exception($"Element with id {divId} was not found in the html document."); - } + } + public AvaloniaView(JSObject host) + { var hostContent = DomHelper.CreateAvaloniaHost(host); if (hostContent == null) { @@ -137,7 +136,7 @@ namespace Avalonia.Web _topLevelImpl.SetClientSize(_canvasSize, _dpi); - DomHelper.ObserveSize(host, divId, OnSizeChanged); + DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); } @@ -387,7 +386,7 @@ namespace Avalonia.Web InputHelper.FocusElement(_containerElement); } - public void SetClient(ITextInputMethodClient? client) + void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client) { Console.WriteLine("Set Client"); if (_client != null) @@ -431,18 +430,18 @@ namespace Avalonia.Web } } - public void SetCursorRect(Rect rect) + void ITextInputMethodImpl.SetCursorRect(Rect rect) { InputHelper.FocusElement(_inputElement); InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0); InputHelper.FocusElement(_inputElement); } - public void SetOptions(TextInputOptions options) + void ITextInputMethodImpl.SetOptions(TextInputOptions options) { } - public void Reset() + void ITextInputMethodImpl.Reset() { InputHelper.ClearInputElement(_inputElement); InputHelper.SetSurroundingText(_inputElement, "", 0, 0); diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs index 091ab3f68c..d962956567 100644 --- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs @@ -4,10 +4,11 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Media; using Avalonia.Web.Skia; +using System.Runtime.Versioning; namespace Avalonia.Web { - [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings + [SupportedOSPlatform("browser")] public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime { public AvaloniaView? View; @@ -19,23 +20,32 @@ namespace Avalonia.Web } } + [SupportedOSPlatform("browser")] public static partial class WebAppBuilder { public static T SetupBrowserApp( - this T builder, string mainDivId) - where T : AppBuilderBase, new() + this T builder, string mainDivId) + where T : AppBuilderBase, new() { var lifetime = new BrowserSingleViewLifetime(); return builder - .UseWindowingSubsystem(BrowserWindowingPlatform.Register) - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }) + .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() }); + } } } diff --git a/src/Web/Avalonia.Web/Cursor.cs b/src/Web/Avalonia.Web/Cursor.cs index 5db0bdeda2..af7098f800 100644 --- a/src/Web/Avalonia.Web/Cursor.cs +++ b/src/Web/Avalonia.Web/Cursor.cs @@ -5,7 +5,7 @@ using Avalonia.Platform; namespace Avalonia.Web { - public class CssCursor : ICursorImpl + internal class CssCursor : ICursorImpl { public const string Default = "default"; public string? Value { get; set; } diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs index 66a1d421b7..ff07d1757b 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.ts")] + [JSImport("Canvas.requestAnimationFrame", "avalonia")] public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); - [JSImport("Canvas.setCanvasSize", "avalonia.ts")] + [JSImport("Canvas.setCanvasSize", "avalonia")] public static partial void SetCanvasSize(JSObject canvas, int height, int width); - [JSImport("Canvas.initGL", "avalonia.ts")] + [JSImport("Canvas.initGL", "avalonia")] 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 c02bc4dae3..c2cf0a0c44 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.ts")] + [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia")] public static partial JSObject CreateAvaloniaHost(JSObject element); - [JSImport("AvaloniaDOM.addClass", "avalonia.ts")] + [JSImport("AvaloniaDOM.addClass", "avalonia")] public static partial void AddCssClass(JSObject element, string className); - [JSImport("SizeWatcher.observe", "avalonia.ts")] + [JSImport("SizeWatcher.observe", "avalonia")] public static partial JSObject ObserveSize( JSObject canvas, - string canvasId, + string? canvasId, [JSMarshalAs>] Action onSizeChanged); - [JSImport("DpiWatcher.start", "avalonia.ts")] + [JSImport("DpiWatcher.start", "avalonia")] 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 b32b6566f2..bdd1957e03 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.ts")] + [JSImport("InputHelper.subscribeKeyEvents", "avalonia")] public static partial void SubscribeKeyEvents( JSObject htmlElement, [JSMarshalAs>] @@ -14,7 +14,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func keyUp); - [JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribeTextEvents", "avalonia")] public static partial void SubscribeTextEvents( JSObject htmlElement, [JSMarshalAs>] @@ -26,7 +26,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func onCompositionEnd); - [JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribePointerEvents", "avalonia")] public static partial void SubscribePointerEvents( JSObject htmlElement, [JSMarshalAs>] @@ -39,35 +39,35 @@ internal static partial class InputHelper Func wheel); - [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribeInputEvents", "avalonia")] public static partial void SubscribeInputEvents( JSObject htmlElement, [JSMarshalAs>] Func input); - [JSImport("InputHelper.clearInput", "avalonia.ts")] + [JSImport("InputHelper.clearInput", "avalonia")] public static partial void ClearInputElement(JSObject htmlElement); - [JSImport("InputHelper.isInputElement", "avalonia.ts")] + [JSImport("InputHelper.isInputElement", "avalonia")] public static partial void IsInputElement(JSObject htmlElement); - [JSImport("InputHelper.focusElement", "avalonia.ts")] + [JSImport("InputHelper.focusElement", "avalonia")] public static partial void FocusElement(JSObject htmlElement); - [JSImport("InputHelper.setCursor", "avalonia.ts")] + [JSImport("InputHelper.setCursor", "avalonia")] public static partial void SetCursor(JSObject htmlElement, string kind); - [JSImport("InputHelper.hide", "avalonia.ts")] + [JSImport("InputHelper.hide", "avalonia")] public static partial void HideElement(JSObject htmlElement); - [JSImport("InputHelper.show", "avalonia.ts")] + [JSImport("InputHelper.show", "avalonia")] public static partial void ShowElement(JSObject htmlElement); - [JSImport("InputHelper.setSurroundingText", "avalonia.ts")] + [JSImport("InputHelper.setSurroundingText", "avalonia")] public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end); - [JSImport("InputHelper.setBounds", "avalonia.ts")] + [JSImport("InputHelper.setBounds", "avalonia")] 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 5cc86bf622..8144f64fc7 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.ts")] + [JSImport("NativeControlHost.createDefaultChild", "avalonia")] internal static partial JSObject CreateDefaultChild(JSObject? parent); - [JSImport("NativeControlHost.createAttachment", "avalonia.ts")] + [JSImport("NativeControlHost.createAttachment", "avalonia")] internal static partial JSObject CreateAttachment(); - [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia.ts")] + [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia")] internal static partial void InitializeWithChildHandle(JSObject element, JSObject child); - [JSImport("NativeControlHost.attachTo", "avalonia.ts")] + [JSImport("NativeControlHost.attachTo", "avalonia")] internal static partial void AttachTo(JSObject element, JSObject? host); - [JSImport("NativeControlHost.showInBounds", "avalonia.ts")] + [JSImport("NativeControlHost.showInBounds", "avalonia")] internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height); - [JSImport("NativeControlHost.hideWithSize", "avalonia.ts")] + [JSImport("NativeControlHost.hideWithSize", "avalonia")] internal static partial void HideWithSize(JSObject element, double width, double height); - [JSImport("NativeControlHost.releaseChild", "avalonia.ts")] + [JSImport("NativeControlHost.releaseChild", "avalonia")] 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 4dc55fd959..d770e852d9 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.ts")] + [JSImport("Caniuse.canShowOpenFilePicker", "avalonia")] public static partial bool CanShowOpenFilePicker(); - [JSImport("Caniuse.canShowSaveFilePicker", "avalonia.ts")] + [JSImport("Caniuse.canShowSaveFilePicker", "avalonia")] public static partial bool CanShowSaveFilePicker(); - [JSImport("Caniuse.canShowDirectoryPicker", "avalonia.ts")] + [JSImport("Caniuse.canShowDirectoryPicker", "avalonia")] public static partial bool CanShowDirectoryPicker(); - [JSImport("StorageProvider.selectFolderDialog", "storage.ts")] + [JSImport("StorageProvider.selectFolderDialog", "storage")] public static partial Task SelectFolderDialog(JSObject? startIn); - [JSImport("StorageProvider.openFileDialog", "storage.ts")] + [JSImport("StorageProvider.openFileDialog", "storage")] public static partial Task OpenFileDialog(JSObject? startIn, bool multiple, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.saveFileDialog", "storage.ts")] + [JSImport("StorageProvider.saveFileDialog", "storage")] public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.openBookmark", "storage.ts")] + [JSImport("StorageProvider.openBookmark", "storage")] public static partial Task OpenBookmark(string key); - [JSImport("StorageItem.saveBookmark", "storage.ts")] + [JSImport("StorageItem.saveBookmark", "storage")] public static partial Task SaveBookmark(JSObject item); - [JSImport("StorageItem.deleteBookmark", "storage.ts")] + [JSImport("StorageItem.deleteBookmark", "storage")] public static partial Task DeleteBookmark(JSObject item); - [JSImport("StorageItem.getProperties", "storage.ts")] + [JSImport("StorageItem.getProperties", "storage")] public static partial Task GetProperties(JSObject item); - [JSImport("StorageItem.openWrite", "storage.ts")] + [JSImport("StorageItem.openWrite", "storage")] public static partial Task OpenWrite(JSObject item); - [JSImport("StorageItem.openRead", "storage.ts")] + [JSImport("StorageItem.openRead", "storage")] public static partial Task OpenRead(JSObject item); - [JSImport("StorageItem.getItems", "storage.ts")] + [JSImport("StorageItem.getItems", "storage")] [return: JSMarshalAs>] public static partial Task GetItems(JSObject item); - [JSImport("StorageItems.itemsArray", "storage.ts")] + [JSImport("StorageItems.itemsArray", "storage")] public static partial JSObject[] ItemsArray(JSObject item); - [JSImport("StorageProvider.createAcceptType", "storage.ts")] + [JSImport("StorageProvider.createAcceptType", "storage")] 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 0a83bbb871..9cd5ca2591 100644 --- a/src/Web/Avalonia.Web/Interop/StreamHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs @@ -9,26 +9,26 @@ namespace Avalonia.Web.Storage; /// internal static partial class StreamHelper { - [JSImport("StreamHelper.seek", "avalonia.ts")] + [JSImport("StreamHelper.seek", "avalonia")] public static partial void Seek(JSObject stream, [JSMarshalAs] long position); - [JSImport("StreamHelper.truncate", "avalonia.ts")] + [JSImport("StreamHelper.truncate", "avalonia")] public static partial void Truncate(JSObject stream, [JSMarshalAs] long size); - [JSImport("StreamHelper.write", "avalonia.ts")] + [JSImport("StreamHelper.write", "avalonia")] public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data); - [JSImport("StreamHelper.close", "avalonia.ts")] + [JSImport("StreamHelper.close", "avalonia")] public static partial Task CloseAsync(JSObject stream); - [JSImport("StreamHelper.byteLength", "avalonia.ts")] + [JSImport("StreamHelper.byteLength", "avalonia")] [return: JSMarshalAs] public static partial long ByteLength(JSObject stream); - [JSImport("StreamHelper.sliceArrayBuffer", "avalonia.ts")] + [JSImport("StreamHelper.sliceArrayBuffer", "avalonia")] private static partial Task SliceToArrayBuffer(JSObject stream, [JSMarshalAs] long offset, int count); - [JSImport("StreamHelper.toMemoryView", "avalonia.ts")] + [JSImport("StreamHelper.toMemoryView", "avalonia")] [return: JSMarshalAs>] private static partial byte[] ArrayBufferToMemoryView(JSObject stream); diff --git a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs index 774848e19f..3309a6dd9f 100644 --- a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs +++ b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs @@ -4,7 +4,7 @@ using Avalonia.Rendering; namespace Avalonia.Web { - public class ManualTriggerRenderTimer : IRenderTimer + internal class ManualTriggerRenderTimer : IRenderTimer { private static readonly Stopwatch s_sw = Stopwatch.StartNew(); diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs index 55ae2bdef0..81a621747e 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.ts", "./storage.js")); + private readonly Lazy> _lazyModule = new(() => JSHost.ImportAsync("storage", "./storage.js")); public bool CanOpen => StorageHelper.CanShowOpenFilePicker(); public bool CanSave => StorageHelper.CanShowSaveFilePicker(); diff --git a/src/Web/Avalonia.Web/WindowingPlatform.cs b/src/Web/Avalonia.Web/WindowingPlatform.cs index 3f14680241..7c2a84516b 100644 --- a/src/Web/Avalonia.Web/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web/WindowingPlatform.cs @@ -8,7 +8,7 @@ using Avalonia.Threading; namespace Avalonia.Web { - public class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface + internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface { private bool _signaled; private static KeyboardDevice? s_keyboard; diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts index 98c912f940..a78fd7ca87 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts @@ -6,15 +6,19 @@ import { Caniuse } from "./avalonia/caniuse"; import { StreamHelper } from "./avalonia/stream"; import { NativeControlHost } from "./avalonia/nativeControlHost"; -export async function createAvaloniaRuntime(api: RuntimeAPI): Promise { - api.setModuleImports("avalonia.ts", { - Caniuse, - Canvas, - InputHelper, - SizeWatcher, - DpiWatcher, - AvaloniaDOM, - StreamHelper, - NativeControlHost - }); +async function registerAvaloniaModule(api: RuntimeAPI): Promise { + api.setModuleImports("avalonia", avaloniaModule); } + +export const avaloniaModule = { + Caniuse, + Canvas, + InputHelper, + SizeWatcher, + DpiWatcher, + AvaloniaDOM, + StreamHelper, + NativeControlHost, + + registerAvaloniaModule +}; diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts index 017ca58b8f..9ae9b3d2a8 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -211,7 +211,7 @@ export class SizeWatcher { static observer: ResizeObserver; static elements: Map; - public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void { + public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void { if (!element || !callback) { return; } @@ -223,7 +223,7 @@ export class SizeWatcher { callback }; - SizeWatcher.elements.set(elementId, element); + SizeWatcher.elements.set(elementId ?? element.id, element); SizeWatcher.observer.observe(element); SizeWatcher.invoke(element); From 897f7c97064d9a4ca7894df35ee245bf77bd0bb3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 10 Oct 2022 05:10:49 -0400 Subject: [PATCH 02/10] Reuse module initialization and make it configurable + some fixes --- samples/ControlCatalog.Web/App.razor.cs | 5 +- src/Web/Avalonia.Web.Blazor/AvaloniaView.cs | 3 +- .../BlazorSingleViewLifetime.cs | 21 +++-- src/Web/Avalonia.Web/Avalonia.Web.csproj | 8 +- src/Web/Avalonia.Web/AvaloniaView.cs | 3 - .../Avalonia.Web/BrowserSingleViewLifetime.cs | 79 ++++++++++--------- .../Avalonia.Web/Interop/AvaloniaModule.cs | 22 ++++++ src/Web/Avalonia.Web/Interop/CanvasHelper.cs | 6 +- src/Web/Avalonia.Web/Interop/DomHelper.cs | 8 +- src/Web/Avalonia.Web/Interop/InputHelper.cs | 24 +++--- .../Interop/NativeControlHostHelper.cs | 14 ++-- src/Web/Avalonia.Web/Interop/StorageHelper.cs | 30 +++---- src/Web/Avalonia.Web/Interop/StreamHelper.cs | 16 ++-- .../Storage/BlobReadableStream.cs | 2 + .../Storage/BrowserStorageProvider.cs | 12 +-- .../Avalonia.Web/Storage/WriteableStream.cs | 2 + .../Avalonia.Web/webapp/modules/avalonia.ts | 14 +++- .../webapp/modules/avalonia/dom.ts | 5 ++ 18 files changed, 163 insertions(+), 111 deletions(-) create mode 100644 src/Web/Avalonia.Web/Interop/AvaloniaModule.cs 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"; From c682c469487ff0a46c525a228e991f463634e93c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 6 Oct 2022 18:25:17 +0100 Subject: [PATCH 03/10] better example splash screen. --- src/Web/Avalonia.Web.Sample/app.css | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Web/Avalonia.Web.Sample/app.css b/src/Web/Avalonia.Web.Sample/app.css index 04ea5bee19..27680f6e1a 100644 --- a/src/Web/Avalonia.Web.Sample/app.css +++ b/src/Web/Avalonia.Web.Sample/app.css @@ -4,12 +4,15 @@ } #avalonia-splash { - position: absolute; + position: relative; height: 100%; width: 100%; color: whitesmoke; background: #171C2C; font-family: 'Nunito', sans-serif; + background-position: center; + background-size: cover; + background-repeat: no-repeat; } #avalonia-splash a{ @@ -24,15 +27,23 @@ } .splash-close { - animation: fadeOut 1s forwards; + animation: slide 0.5s linear 1s forwards; } -@keyframes fadeOut { - from { - opacity: 1; +@keyframes slide { + 0% { + top: 0%; } - to { + 50% { + opacity: 80%; + } + + 100% { + top: 100%; + overflow: hidden; opacity: 0; + display: none; + visibility: collapse; } } From 4a94292d339f49e23a198a70accd846f20a7c6d2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 6 Oct 2022 21:42:56 +0100 Subject: [PATCH 04/10] keep input box at zIndex-1 to prevent it interfering with the user... still works luckily --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 494fde23e2..da499cd1ec 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -47,6 +47,7 @@ export class AvaloniaDOM { inputElement.style.color = "transparent"; inputElement.style.display = "none"; inputElement.style.height = "20px"; + inputElement.style.zIndex = "-1"; inputElement.onpaste = function () { return false; }; inputElement.oncopy = function () { return false; }; inputElement.oncut = function () { return false; }; From 5503e8f77593f2d1119fc1803eaa65f032e65a0b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 10 Oct 2022 18:28:14 +0100 Subject: [PATCH 05/10] move sample, and fix splash screen positioning and rename blazor sample. --- Avalonia.sln | 12 ++--- .../App.razor | 0 .../App.razor.cs | 2 +- .../ControlCatalog.Blazor.Web.csproj | 29 +++++++++++++ .../Pages/Index.razor | 0 samples/ControlCatalog.Blazor.Web/Program.cs | 29 +++++++++++++ .../Properties/launchSettings.json | 0 .../Shared/MainLayout.razor | 0 .../_Imports.razor | 0 .../wwwroot/css/app.css | 0 .../wwwroot/favicon.ico | Bin .../wwwroot/index.html | 0 .../ControlCatalog.Web.csproj | 40 +++++++++++------ .../EmbedSample.Browser.cs | 2 - .../ControlCatalog.Web}/Logo.svg | 0 samples/ControlCatalog.Web/Program.cs | 34 +++++---------- .../ControlCatalog.Web}/app.css | 0 .../ControlCatalog.Web}/embed.js | 0 .../ControlCatalog.Web}/favicon.ico | Bin .../ControlCatalog.Web}/index.html | 2 +- .../ControlCatalog.Web}/main.js | 0 .../runtimeconfig.template.json | 0 .../Avalonia.Web.Sample.csproj | 41 ------------------ src/Web/Avalonia.Web.Sample/Program.cs | 19 -------- .../webapp/modules/avalonia/dom.ts | 1 + 25 files changed, 105 insertions(+), 106 deletions(-) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/App.razor (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/App.razor.cs (91%) create mode 100644 samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/Pages/Index.razor (100%) create mode 100644 samples/ControlCatalog.Blazor.Web/Program.cs rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/Properties/launchSettings.json (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/Shared/MainLayout.razor (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/_Imports.razor (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/wwwroot/css/app.css (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/wwwroot/favicon.ico (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/wwwroot/index.html (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/EmbedSample.Browser.cs (98%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/Logo.svg (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/app.css (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/embed.js (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/favicon.ico (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/index.html (95%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/main.js (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/runtimeconfig.template.json (100%) delete mode 100644 src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj delete mode 100644 src/Web/Avalonia.Web.Sample/Program.cs diff --git a/Avalonia.sln b/Avalonia.sln index 81a9b43890..d53ccd63fb 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -198,7 +198,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Web\ControlCatalog.Blazor.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}" EndProject @@ -218,13 +218,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Ava EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -579,6 +579,7 @@ Global {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} @@ -595,7 +596,6 @@ Global {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} - {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098} diff --git a/samples/ControlCatalog.Web/App.razor b/samples/ControlCatalog.Blazor.Web/App.razor similarity index 100% rename from samples/ControlCatalog.Web/App.razor rename to samples/ControlCatalog.Blazor.Web/App.razor diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Blazor.Web/App.razor.cs similarity index 91% rename from samples/ControlCatalog.Web/App.razor.cs rename to samples/ControlCatalog.Blazor.Web/App.razor.cs index 09ab4c3b68..8cc0095f20 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Blazor.Web/App.razor.cs @@ -1,7 +1,7 @@ using Avalonia; using Avalonia.Web.Blazor; -namespace ControlCatalog.Web; +namespace ControlCatalog.Blazor.Web; public partial class App { diff --git a/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj b/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj new file mode 100644 index 0000000000..03fb31f0d3 --- /dev/null +++ b/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj @@ -0,0 +1,29 @@ + + + net7.0 + browser-wasm + enable + 16777216 + false + false + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog.Web/Pages/Index.razor b/samples/ControlCatalog.Blazor.Web/Pages/Index.razor similarity index 100% rename from samples/ControlCatalog.Web/Pages/Index.razor rename to samples/ControlCatalog.Blazor.Web/Pages/Index.razor diff --git a/samples/ControlCatalog.Blazor.Web/Program.cs b/samples/ControlCatalog.Blazor.Web/Program.cs new file mode 100644 index 0000000000..d71b125fa1 --- /dev/null +++ b/samples/ControlCatalog.Blazor.Web/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; +using ControlCatalog.Blazor.Web; + +public class Program +{ + public static async Task Main(string[] args) + { + await CreateHostBuilder(args).Build().RunAsync(); + } + + public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + builder.RootComponents.Add("#app"); + + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + return builder; + } +} + + + + diff --git a/samples/ControlCatalog.Web/Properties/launchSettings.json b/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json similarity index 100% rename from samples/ControlCatalog.Web/Properties/launchSettings.json rename to samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json diff --git a/samples/ControlCatalog.Web/Shared/MainLayout.razor b/samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor similarity index 100% rename from samples/ControlCatalog.Web/Shared/MainLayout.razor rename to samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor diff --git a/samples/ControlCatalog.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor similarity index 100% rename from samples/ControlCatalog.Web/_Imports.razor rename to samples/ControlCatalog.Blazor.Web/_Imports.razor diff --git a/samples/ControlCatalog.Web/wwwroot/css/app.css b/samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css similarity index 100% rename from samples/ControlCatalog.Web/wwwroot/css/app.css rename to samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css diff --git a/samples/ControlCatalog.Web/wwwroot/favicon.ico b/samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico similarity index 100% rename from samples/ControlCatalog.Web/wwwroot/favicon.ico rename to samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico diff --git a/samples/ControlCatalog.Web/wwwroot/index.html b/samples/ControlCatalog.Blazor.Web/wwwroot/index.html similarity index 100% rename from samples/ControlCatalog.Web/wwwroot/index.html rename to samples/ControlCatalog.Blazor.Web/wwwroot/index.html diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 03fb31f0d3..4dd4ca25d0 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -1,29 +1,41 @@ - + net7.0 browser-wasm - enable - 16777216 - false - false + main.js + Exe + true + true + true + -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0 - - - - + + true + true + full + true + true + true + -O3 + -O3 + - + - - + + + + + + + + - - diff --git a/src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs b/samples/ControlCatalog.Web/EmbedSample.Browser.cs similarity index 98% rename from src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs rename to samples/ControlCatalog.Web/EmbedSample.Browser.cs index 5baa4a6b35..5cfbb608cc 100644 --- a/src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs +++ b/samples/ControlCatalog.Web/EmbedSample.Browser.cs @@ -1,7 +1,5 @@ using System; using System.Runtime.InteropServices.JavaScript; - -using Avalonia; using Avalonia.Platform; using Avalonia.Web; diff --git a/src/Web/Avalonia.Web.Sample/Logo.svg b/samples/ControlCatalog.Web/Logo.svg similarity index 100% rename from src/Web/Avalonia.Web.Sample/Logo.svg rename to samples/ControlCatalog.Web/Logo.svg diff --git a/samples/ControlCatalog.Web/Program.cs b/samples/ControlCatalog.Web/Program.cs index d1a7925813..52acabb0fa 100644 --- a/samples/ControlCatalog.Web/Program.cs +++ b/samples/ControlCatalog.Web/Program.cs @@ -1,29 +1,19 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; +using Avalonia; +using Avalonia.Web; +using ControlCatalog; using ControlCatalog.Web; -public class Program +internal partial class Program { - public static async Task Main(string[] args) + private static void Main(string[] args) { - await CreateHostBuilder(args).Build().RunAsync(); + BuildAvaloniaApp() + .AfterSetup(_ => + { + ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); + }).SetupBrowserApp("out"); } - public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - - builder.RootComponents.Add("#app"); - - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - - return builder; - } + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure(); } - - - - diff --git a/src/Web/Avalonia.Web.Sample/app.css b/samples/ControlCatalog.Web/app.css similarity index 100% rename from src/Web/Avalonia.Web.Sample/app.css rename to samples/ControlCatalog.Web/app.css diff --git a/src/Web/Avalonia.Web.Sample/embed.js b/samples/ControlCatalog.Web/embed.js similarity index 100% rename from src/Web/Avalonia.Web.Sample/embed.js rename to samples/ControlCatalog.Web/embed.js diff --git a/src/Web/Avalonia.Web.Sample/favicon.ico b/samples/ControlCatalog.Web/favicon.ico similarity index 100% rename from src/Web/Avalonia.Web.Sample/favicon.ico rename to samples/ControlCatalog.Web/favicon.ico diff --git a/src/Web/Avalonia.Web.Sample/index.html b/samples/ControlCatalog.Web/index.html similarity index 95% rename from src/Web/Avalonia.Web.Sample/index.html rename to samples/ControlCatalog.Web/index.html index ee023790fb..226ae70695 100644 --- a/src/Web/Avalonia.Web.Sample/index.html +++ b/samples/ControlCatalog.Web/index.html @@ -4,7 +4,7 @@ - Avalonia.Web.Sample + AvaloniaUI - ControlCatalog diff --git a/src/Web/Avalonia.Web.Sample/main.js b/samples/ControlCatalog.Web/main.js similarity index 100% rename from src/Web/Avalonia.Web.Sample/main.js rename to samples/ControlCatalog.Web/main.js diff --git a/src/Web/Avalonia.Web.Sample/runtimeconfig.template.json b/samples/ControlCatalog.Web/runtimeconfig.template.json similarity index 100% rename from src/Web/Avalonia.Web.Sample/runtimeconfig.template.json rename to samples/ControlCatalog.Web/runtimeconfig.template.json diff --git a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj b/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj deleted file mode 100644 index 13aad8c13e..0000000000 --- a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - net7.0 - browser-wasm - main.js - Exe - true - true - true - -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0 - - - - true - true - full - true - true - true - -O3 - -O3 - - - - - - - - - - - - - - - - - - - - diff --git a/src/Web/Avalonia.Web.Sample/Program.cs b/src/Web/Avalonia.Web.Sample/Program.cs deleted file mode 100644 index 52acabb0fa..0000000000 --- a/src/Web/Avalonia.Web.Sample/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia; -using Avalonia.Web; -using ControlCatalog; -using ControlCatalog.Web; - -internal partial class Program -{ - private static void Main(string[] args) - { - BuildAvaloniaApp() - .AfterSetup(_ => - { - ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); - }).SetupBrowserApp("out"); - } - - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure(); -} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index da499cd1ec..2257d56a92 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -10,6 +10,7 @@ export class AvaloniaDOM { host.classList.add("avalonia-container"); host.tabIndex = 0; host.oncontextmenu = function () { return false; }; + host.style.overflow = "hidden"; // Rendering target canvas const canvas = document.createElement("canvas"); From f2e5193819b4dd661cfa3ae1d662fcfa2684c5a8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 10 Oct 2022 18:40:42 +0100 Subject: [PATCH 06/10] add roots.xml --- samples/ControlCatalog.Web/ControlCatalog.Web.csproj | 4 ++++ samples/ControlCatalog.Web/Roots.xml | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 samples/ControlCatalog.Web/Roots.xml diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 4dd4ca25d0..6dc6a2ec3e 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -21,6 +21,10 @@ -O3 + + + + diff --git a/samples/ControlCatalog.Web/Roots.xml b/samples/ControlCatalog.Web/Roots.xml new file mode 100644 index 0000000000..3c13098159 --- /dev/null +++ b/samples/ControlCatalog.Web/Roots.xml @@ -0,0 +1,6 @@ + + + + + + From 6cf47980f337b4e8f9c47c463fc60937de26345f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 08:59:16 +0100 Subject: [PATCH 07/10] fix trimmer roots include. --- samples/ControlCatalog.Web/ControlCatalog.Web.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 6dc6a2ec3e..0ddec3444b 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -21,9 +21,9 @@ -O3 - + - + From 33f9f52ed4eaf4df6fef0c9bc2036f2c1dfb9ffd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 12:48:04 +0100 Subject: [PATCH 08/10] fix build error. --- samples/ControlCatalog.Blazor.Web/_Imports.razor | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/ControlCatalog.Blazor.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor index 04c7a8690e..9088ea0731 100644 --- a/samples/ControlCatalog.Blazor.Web/_Imports.razor +++ b/samples/ControlCatalog.Blazor.Web/_Imports.razor @@ -6,6 +6,5 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using ControlCatalog.Web @using ControlCatalog.Web.Shared @using SkiaSharp From 70e5844df9c6009c8fa1bba0d81c3bdab9504afd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 12:48:35 +0100 Subject: [PATCH 09/10] update sln. --- Avalonia.sln | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Avalonia.sln b/Avalonia.sln index d53ccd63fb..61592e1d44 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -198,8 +198,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Web\ControlCatalog.Blazor.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}" @@ -216,8 +214,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" @@ -226,6 +222,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -480,10 +480,6 @@ Global {25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.Build.0 = Release|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -516,10 +512,6 @@ Global {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.Build.0 = Release|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -536,6 +528,14 @@ Global {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -587,7 +587,6 @@ Global {676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098} {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} - {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098} @@ -595,11 +594,12 @@ Global {EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098} {62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} From ff25d17c0b70ea8c3fad120f5aa11036ecbd80c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 12:52:40 +0100 Subject: [PATCH 10/10] fix build --- samples/ControlCatalog.Blazor.Web/_Imports.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Blazor.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor index 9088ea0731..0e6d11b419 100644 --- a/samples/ControlCatalog.Blazor.Web/_Imports.razor +++ b/samples/ControlCatalog.Blazor.Web/_Imports.razor @@ -6,5 +6,5 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using ControlCatalog.Web.Shared +@using ControlCatalog.Blazor.Web.Shared @using SkiaSharp