diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 07db6013c7..d4081ca043 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -28,6 +28,7 @@ namespace Avalonia.Web.Blazor private SizeWatcherInterop? _sizeWatcher = null; private DpiWatcherInterop? _dpiWatcher = null; private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null; + private AvaloniaModule? _avaloniaModule = null; private InputHelperInterop? _inputHelper = null; private InputHelperInterop? _canvasHelper = null; private NativeControlHostInterop? _nativeControlHost = null; @@ -241,8 +242,10 @@ namespace Avalonia.Web.Blazor { AvaloniaLocator.CurrentMutable.Bind().ToConstant((IJSInProcessRuntime)Js); - _inputHelper = await InputHelperInterop.ImportAsync(Js, _inputElement); - _canvasHelper = await InputHelperInterop.ImportAsync(Js, _htmlCanvas); + _avaloniaModule = await AvaloniaModule.ImportAsync(Js); + + _inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement); + _canvasHelper = new InputHelperInterop(_avaloniaModule, _htmlCanvas); _inputHelper.Hide(); _canvasHelper.SetCursor("default"); @@ -252,11 +255,11 @@ namespace Avalonia.Web.Blazor _canvasHelper.SetCursor(x); //windows }; - _nativeControlHost = await NativeControlHostInterop.ImportAsync(Js, _nativeControlsContainer); + _nativeControlHost = new NativeControlHostInterop(_avaloniaModule, _nativeControlsContainer); _storageProvider = await StorageProviderInterop.ImportAsync(Js); Console.WriteLine("starting html canvas setup"); - _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame); + _interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame); Console.WriteLine("Interop created"); @@ -306,8 +309,8 @@ namespace Avalonia.Web.Blazor { _interop.RequestAnimationFrame(true); - _sizeWatcher = await SizeWatcherInterop.ImportAsync(Js, _htmlCanvas, OnSizeChanged); - _dpiWatcher = await DpiWatcherInterop.ImportAsync(Js, OnDpiChanged); + _sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged); + _dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged); _topLevel.Prepare(); diff --git a/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs new file mode 100644 index 0000000000..7aac879fa6 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs @@ -0,0 +1,18 @@ +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 index 29a2686b3f..2c39cb8d00 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs @@ -1,43 +1,29 @@ -using System; -using System.Threading.Tasks; using Microsoft.JSInterop; namespace Avalonia.Web.Blazor.Interop { - internal class DpiWatcherInterop : JSModuleInterop + internal class DpiWatcherInterop : IDisposable { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/DpiWatcher.js"; private const string StartSymbol = "DpiWatcher.start"; private const string StopSymbol = "DpiWatcher.stop"; private const string GetDpiSymbol = "DpiWatcher.getDpi"; - private static DpiWatcherInterop? instance; - private event Action? callbacksEvent; - private readonly FloatFloatActionHelper callbackHelper; + private readonly FloatFloatActionHelper _callbackHelper; + private readonly AvaloniaModule _module; private DotNetObjectReference? callbackReference; - public static async Task ImportAsync(IJSRuntime js, Action? callback = null) + public DpiWatcherInterop(AvaloniaModule module, Action? callback = null) { - var interop = Get(js); - await interop.ImportAsync(); - if (callback != null) - interop.Subscribe(callback); - return interop; - } + _module = module; + _callbackHelper = new FloatFloatActionHelper((o, n) => callbacksEvent?.Invoke(n)); - public static DpiWatcherInterop Get(IJSRuntime js) => - instance ??= new DpiWatcherInterop(js); - - private DpiWatcherInterop(IJSRuntime js) - : base(js, JsFilename) - { - callbackHelper = new FloatFloatActionHelper((o, n) => callbacksEvent?.Invoke(n)); + if (callback != null) + Subscribe(callback); } - protected override void OnDisposingModule() => - Stop(); + public void Dispose() => Stop(); public void Subscribe(Action callback) { @@ -65,9 +51,9 @@ namespace Avalonia.Web.Blazor.Interop if (callbackReference != null) return GetDpi(); - callbackReference = DotNetObjectReference.Create(callbackHelper); + callbackReference = DotNetObjectReference.Create(_callbackHelper); - return Invoke(StartSymbol, callbackReference); + return _module.Invoke(StartSymbol, callbackReference); } private void Stop() @@ -75,13 +61,12 @@ namespace Avalonia.Web.Blazor.Interop if (callbackReference == null) return; - Invoke(StopSymbol); + _module.Invoke(StopSymbol); callbackReference?.Dispose(); callbackReference = null; } - public double GetDpi() => - Invoke(GetDpiSymbol); + public double GetDpi() => _module.Invoke(GetDpiSymbol); } } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs index 6d71e0c48f..294e71eb1f 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs @@ -1,41 +1,32 @@ using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; namespace Avalonia.Web.Blazor.Interop { - internal class InputHelperInterop : JSModuleInterop + internal class InputHelperInterop { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/InputHelper.js"; 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 readonly ElementReference inputElement; + private readonly AvaloniaModule _module; + private readonly ElementReference _inputElement; - public static async Task ImportAsync(IJSRuntime js, ElementReference element) + public InputHelperInterop(AvaloniaModule module, ElementReference element) { - var interop = new InputHelperInterop(js, element); - await interop.ImportAsync(); - return interop; + _module = module; + _inputElement = element; } - public InputHelperInterop(IJSRuntime js, ElementReference element) - : base(js, JsFilename) - { - inputElement = element; - } - - public void Clear() => Invoke(ClearSymbol, inputElement); + public void Clear() => _module.Invoke(ClearSymbol, _inputElement); - public void Focus() => Invoke(FocusSymbol, inputElement); + public void Focus() => _module.Invoke(FocusSymbol, _inputElement); - public void SetCursor(string kind) => Invoke(SetCursorSymbol, inputElement, kind); + public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind); - public void Hide() => Invoke(HideSymbol, inputElement); + public void Hide() => _module.Invoke(HideSymbol, _inputElement); - public void Show() => Invoke(ShowSymbol, inputElement); + public void Show() => _module.Invoke(ShowSymbol, _inputElement); } } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs index 589b6b56bb..dca1b53650 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using Microsoft.JSInterop; +using Microsoft.JSInterop; namespace Avalonia.Web.Blazor.Interop { @@ -31,16 +29,16 @@ namespace Avalonia.Web.Blazor.Interop protected IJSUnmarshalledObjectReference Module => module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first."); - protected void Invoke(string identifier, params object?[]? args) => + internal void Invoke(string identifier, params object?[]? args) => Module.InvokeVoid(identifier, args); - protected TValue Invoke(string identifier, params object?[]? args) => + internal TValue Invoke(string identifier, params object?[]? args) => Module.Invoke(identifier, args); - protected ValueTask InvokeAsync(string identifier, params object?[]? args) => + internal ValueTask InvokeAsync(string identifier, params object?[]? args) => Module.InvokeVoidAsync(identifier, args); - protected ValueTask InvokeAsync(string identifier, params object?[]? 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 index 48362b03c4..b824fcae46 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Platform; using Avalonia.Platform; @@ -10,31 +9,24 @@ using Microsoft.JSInterop; namespace Avalonia.Web.Blazor.Interop { - internal class NativeControlHostInterop : JSModuleInterop, INativeControlHostImpl + internal class NativeControlHostInterop : INativeControlHostImpl { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/NativeControlHost.js"; private const string CreateDefaultChildSymbol = "NativeControlHost.CreateDefaultChild"; private const string CreateAttachmentSymbol = "NativeControlHost.CreateAttachment"; private const string GetReferenceSymbol = "NativeControlHost.GetReference"; - private readonly ElementReference hostElement; + private readonly AvaloniaModule _module; + private readonly ElementReference _hostElement; - public static async Task ImportAsync(IJSRuntime js, ElementReference element) + public NativeControlHostInterop(AvaloniaModule module, ElementReference element) { - var interop = new NativeControlHostInterop(js, element); - await interop.ImportAsync(); - return interop; - } - - public NativeControlHostInterop(IJSRuntime js, ElementReference element) - : base(js, JsFilename) - { - hostElement = element; + _module = module; + _hostElement = element; } public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) { - var element = Invoke(CreateDefaultChildSymbol); + var element = _module.Invoke(CreateDefaultChildSymbol); return new JSObjectControlHandle(element); } @@ -43,9 +35,9 @@ namespace Avalonia.Web.Blazor.Interop Attachment? a = null; try { - using var hostElementJsReference = Invoke(GetReferenceSymbol, hostElement); + using var hostElementJsReference = _module.Invoke(GetReferenceSymbol, _hostElement); var child = create(new JSObjectControlHandle(hostElementJsReference)); - var attachmenetReference = Invoke(CreateAttachmentSymbol); + 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); @@ -62,7 +54,7 @@ namespace Avalonia.Web.Blazor.Interop public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) { - var attachmenetReference = Invoke(CreateAttachmentSymbol); + var attachmenetReference = _module.Invoke(CreateAttachmentSymbol); var a = new Attachment(attachmenetReference, handle); a.AttachedTo = this; return a; @@ -111,7 +103,7 @@ namespace Avalonia.Web.Blazor.Interop } else { - _native.InvokeVoid(AttachToSymbol, host.hostElement); + _native.InvokeVoid(AttachToSymbol, host._hostElement); } _attachedTo = host; } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs index 9cbbf24086..cf9350fb62 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs @@ -4,7 +4,7 @@ using SkiaSharp; namespace Avalonia.Web.Blazor.Interop { - internal class SKHtmlCanvasInterop : JSModuleInterop + internal class SKHtmlCanvasInterop : IDisposable { private const string JsFilename = "./_content/Avalonia.Web.Blazor/SKHtmlCanvas.js"; private const string InitGLSymbol = "SKHtmlCanvas.initGL"; @@ -14,39 +14,32 @@ namespace Avalonia.Web.Blazor.Interop private const string SetCanvasSizeSymbol = "SKHtmlCanvas.setCanvasSize"; private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData"; - private readonly ElementReference htmlCanvas; - private readonly string htmlElementId; - private readonly ActionHelper callbackHelper; + private readonly AvaloniaModule _module; + private readonly ElementReference _htmlCanvas; + private readonly string _htmlElementId; + private readonly ActionHelper _callbackHelper; private DotNetObjectReference? callbackReference; - public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) + public SKHtmlCanvasInterop(AvaloniaModule module, ElementReference element, Action renderFrameCallback) { - var interop = new SKHtmlCanvasInterop(js, element, callback); - await interop.ImportAsync(); - return interop; - } - - public SKHtmlCanvasInterop(IJSRuntime js, ElementReference element, Action renderFrameCallback) - : base(js, JsFilename) - { - htmlCanvas = element; - htmlElementId = element.Id; + _module = module; + _htmlCanvas = element; + _htmlElementId = element.Id; - callbackHelper = new ActionHelper(renderFrameCallback); + _callbackHelper = new ActionHelper(renderFrameCallback); } - protected override void OnDisposingModule() => - Deinit(); + 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); + callbackReference = DotNetObjectReference.Create(_callbackHelper); - return Invoke(InitGLSymbol, htmlCanvas, htmlElementId, callbackReference); + return _module.Invoke(InitGLSymbol, _htmlCanvas, _htmlElementId, callbackReference); } public bool InitRaster() @@ -54,9 +47,9 @@ namespace Avalonia.Web.Blazor.Interop if (callbackReference != null) throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - callbackReference = DotNetObjectReference.Create(callbackHelper); + callbackReference = DotNetObjectReference.Create(_callbackHelper); - return Invoke(InitRasterSymbol, htmlCanvas, htmlElementId, callbackReference); + return _module.Invoke(InitRasterSymbol, _htmlCanvas, _htmlElementId, callbackReference); } public void Deinit() @@ -64,19 +57,19 @@ namespace Avalonia.Web.Blazor.Interop if (callbackReference == null) return; - Invoke(DeinitSymbol, htmlElementId); + _module.Invoke(DeinitSymbol, _htmlElementId); callbackReference?.Dispose(); } public void RequestAnimationFrame(bool enableRenderLoop) => - Invoke(RequestAnimationFrameSymbol, htmlCanvas, enableRenderLoop); + _module.Invoke(RequestAnimationFrameSymbol, _htmlCanvas, enableRenderLoop); public void SetCanvasSize(int rawWidth, int rawHeight) => - Invoke(SetCanvasSizeSymbol, htmlCanvas, rawWidth, rawHeight); + _module.Invoke(SetCanvasSizeSymbol, _htmlCanvas, rawWidth, rawHeight); public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => - Invoke(PutImageDataSymbol, htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); + _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 index 8904137b8b..10198751a9 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs @@ -1,50 +1,39 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using SkiaSharp; namespace Avalonia.Web.Blazor.Interop { - internal class SizeWatcherInterop : JSModuleInterop + internal class SizeWatcherInterop : IDisposable { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/SizeWatcher.js"; private const string ObserveSymbol = "SizeWatcher.observe"; private const string UnobserveSymbol = "SizeWatcher.unobserve"; - private readonly ElementReference htmlElement; - private readonly string htmlElementId; - private readonly FloatFloatActionHelper callbackHelper; + private readonly AvaloniaModule _module; + private readonly ElementReference _htmlElement; + private readonly string _htmlElementId; + private readonly FloatFloatActionHelper _callbackHelper; private DotNetObjectReference? callbackReference; - public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) + public SizeWatcherInterop(AvaloniaModule module, ElementReference element, Action callback) { - var interop = new SizeWatcherInterop(js, element, callback); - await interop.ImportAsync(); - interop.Start(); - return interop; + _module = module; + _htmlElement = element; + _htmlElementId = element.Id; + _callbackHelper = new FloatFloatActionHelper((x, y) => callback(new SKSize(x, y))); } - public SizeWatcherInterop(IJSRuntime js, ElementReference element, Action callback) - : base(js, JsFilename) - { - htmlElement = element; - htmlElementId = element.Id; - callbackHelper = new FloatFloatActionHelper((x, y) => callback(new SKSize(x, y))); - } - - protected override void OnDisposingModule() => - Stop(); + public void Dispose() => Stop(); public void Start() { if (callbackReference != null) return; - callbackReference = DotNetObjectReference.Create(callbackHelper); + callbackReference = DotNetObjectReference.Create(_callbackHelper); - Invoke(ObserveSymbol, htmlElement, htmlElementId, callbackReference); + _module.Invoke(ObserveSymbol, _htmlElement, _htmlElementId, callbackReference); } public void Stop() @@ -52,7 +41,7 @@ namespace Avalonia.Web.Blazor.Interop if (callbackReference == null) return; - Invoke(UnobserveSymbol, htmlElementId); + _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 index 2bc46e97b5..f6428c8ef1 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs @@ -12,7 +12,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage internal class StorageProviderInterop : JSModuleInterop, IStorageProvider { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/StorageProvider.js"; + private const string JsFilename = "./_content/Avalonia.Web.Blazor/avaloniaStorage.js"; private const string PickerCancelMessage = "The user aborted a request"; public static async Task ImportAsync(IJSRuntime js) diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/Avalonia.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/Avalonia.ts new file mode 100644 index 0000000000..1ce2e850d9 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/Avalonia.ts @@ -0,0 +1,5 @@ +export { DpiWatcher } from "./DpiWatcher" +export { InputHelper } from "./InputHelper" +export { NativeControlHost } from "./NativeControlHost" +export { SizeWatcher } from "./SizeWatcher" +export { SKHtmlCanvas } from "./SKHtmlCanvas" diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/DpiWatcher.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts similarity index 100% rename from src/Web/Avalonia.Web.Blazor/webapp/modules/Common/DpiWatcher.ts rename to src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/InputHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts similarity index 100% rename from src/Web/Avalonia.Web.Blazor/webapp/modules/Common/InputHelper.ts rename to src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/NativeControlHost.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts similarity index 100% rename from src/Web/Avalonia.Web.Blazor/webapp/modules/Common/NativeControlHost.ts rename to src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SKHtmlCanvas.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts similarity index 100% rename from src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SKHtmlCanvas.ts rename to src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SizeWatcher.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts similarity index 100% rename from src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SizeWatcher.ts rename to src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Common/IndexedDbWrapper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts similarity index 100% rename from src/Web/Avalonia.Web.Blazor/webapp/modules/Common/IndexedDbWrapper.ts rename to src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts index 65b8fa908e..896e174e43 100644 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts +++ b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts @@ -2,7 +2,7 @@ declare global { type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; - type StartInDirectory = WellKnownDirectory | FileSystemFileHandle; + type StartInDirectory = WellKnownDirectory | FileSystemHandle; interface OpenFilePickerOptions { startIn?: StartInDirectory } @@ -14,10 +14,10 @@ declare global { const fileBookmarksStore: string = "fileBookmarks"; const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ fileBookmarksStore -]) +]); class StorageItem { - constructor(private handle: FileSystemHandle, private bookmarkId?: string) { } + constructor(public handle: FileSystemHandle, private bookmarkId?: string) { } public getName(): string { return this.handle.name @@ -69,7 +69,7 @@ class StorageItem { } const items: StorageItem[] = []; - for await (const [key, value] of this.handle.entries()) { + for await (const [key, value] of (this.handle as any).entries()) { items.push(new StorageItem(value)); } return new StorageItems(items); @@ -90,7 +90,7 @@ class StorageItem { if (this.bookmarkId) { return this.bookmarkId; } - + const connection = await avaloniaDb.connect(); try { const key = await connection.put(fileBookmarksStore, this.handle, this.generateBookmarkId()); @@ -172,7 +172,7 @@ export class StorageProvider { }; const handles = await window.showOpenFilePicker(options); - return new StorageItems(handles.map(handle => new StorageItem(handle))); + return new StorageItems(handles.map((handle: FileSystemHandle) => new StorageItem(handle))); } public static async saveFileDialog( diff --git a/src/Web/Avalonia.Web.Blazor/webapp/package.json b/src/Web/Avalonia.Web.Blazor/webapp/package.json index 130e3032b4..c312118a72 100644 --- a/src/Web/Avalonia.Web.Blazor/webapp/package.json +++ b/src/Web/Avalonia.Web.Blazor/webapp/package.json @@ -1,12 +1,14 @@ { "name": "avalonia.web", "scripts": { - "build": "tsc --build", - "clean": "tsc --build --clean" + "dist": "cross-env NODE_ENV=production webpack", + "build": "cross-env NODE_ENV=development webpack" }, "devDependencies": { "@types/emscripten": "^1.39.6", "@types/wicg-file-system-access": "^2020.9.5", + "cross-env": "^7.0.3", + "ts-loader": "^9.3.1", "typescript": "^4.7.4", "webpack": "^5.73.0", "webpack-cli": "^4.10.0" diff --git a/src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js b/src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js index e69de29bb2..52d507a2b2 100644 --- a/src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js +++ b/src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js @@ -0,0 +1,40 @@ +const path = require('path'); +const prod = process.env.NODE_ENV == 'production'; + +module.exports = { + mode: prod ? "production" : "development", + devtool: 'source-map', + target: ["web", "es2020"], + entry: { + avalonia: './modules/Avalonia/Avalonia.ts', + avaloniaStorage: { + import: './modules/Storage/StorageProvider.ts', + dependOn: 'avalonia', + } + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, '../wwwroot'), + library: { + type: 'module', + }, + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.ts', '.js'], + }, + optimization: { + minimize: false + }, + experiments: { + outputModule: true, + } +}; diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js new file mode 100644 index 0000000000..849ea089d5 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js @@ -0,0 +1,6 @@ +export { DpiWatcher } from "./DpiWatcher"; +export { InputHelper } from "./InputHelper"; +export { NativeControlHost } from "./NativeControlHost"; +export { SizeWatcher } from "./SizeWatcher"; +export { SKHtmlCanvas } from "./SKHtmlCanvas"; +//# sourceMappingURL=Avalonia.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js new file mode 100644 index 0000000000..17b6999108 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js @@ -0,0 +1,26 @@ +export class DpiWatcher { + static getDpi() { + return window.devicePixelRatio; + } + static start(callback) { + DpiWatcher.lastDpi = window.devicePixelRatio; + DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000); + DpiWatcher.callback = callback; + return DpiWatcher.lastDpi; + } + static stop() { + 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); + } + } +} +//# sourceMappingURL=DpiWatcher.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js new file mode 100644 index 0000000000..97a4500a00 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js @@ -0,0 +1,19 @@ +export class InputHelper { + static clear(inputElement) { + inputElement.value = ""; + } + static focus(inputElement) { + inputElement.focus(); + inputElement.setSelectionRange(0, 0); + } + static setCursor(inputElement, kind) { + inputElement.style.cursor = kind; + } + static hide(inputElement) { + inputElement.style.display = 'none'; + } + static show(inputElement) { + inputElement.style.display = 'block'; + } +} +//# sourceMappingURL=InputHelper.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js new file mode 100644 index 0000000000..05f804ed8c --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js @@ -0,0 +1,48 @@ +export class NativeControlHost { + static CreateDefaultChild(parent) { + return document.createElement("div"); + } + static GetReference(element) { + return element; + } + static CreateAttachment() { + return new NativeControlHostTopLevelAttachment(); + } +} +class NativeControlHostTopLevelAttachment { + InitializeWithChildHandle(child) { + this._child = child; + this._child.style.position = "absolute"; + } + AttachTo(host) { + 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, y, width, height) { + 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, height) { + if (this._child) { + this._child.style.width = width + "px"; + this._child.style.height = height + "px"; + this._child.style.display = "none"; + } + } + ReleaseChild() { + if (this._child) { + this._child = undefined; + } + } +} +//# sourceMappingURL=NativeControlHost.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js new file mode 100644 index 0000000000..5900026acd --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js @@ -0,0 +1,172 @@ +export class SKHtmlCanvas { + constructor(useGL, element, callback) { + this.renderLoopEnabled = false; + this.renderLoopRequest = 0; + 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; + } + GL.makeContextCurrent(ctx); + const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING); + this.glInfo = { + context: ctx, + fboId: fbo ? fbo.id : 0, + stencil: GLctx.getParameter(GLctx.STENCIL_BITS), + sample: 0, + depth: GLctx.getParameter(GLctx.DEPTH_BITS), + }; + } + } + static initGL(element, elementId, callback) { + var view = SKHtmlCanvas.init(true, element, elementId, callback); + if (!view || !view.glInfo) + return null; + return view.glInfo; + } + static initRaster(element, elementId, callback) { + var view = SKHtmlCanvas.init(false, element, elementId, callback); + if (!view) + return false; + return true; + } + static init(useGL, element, elementId, callback) { + var htmlCanvas = element; + 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; + } + static deinit(elementId) { + if (!elementId) + return; + const element = SKHtmlCanvas.elements.get(elementId); + SKHtmlCanvas.elements.delete(elementId); + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.deinit(); + htmlCanvas.SKHtmlCanvas = undefined; + } + static requestAnimationFrame(element, renderLoop) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); + } + static setCanvasSize(element, width, height) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); + } + static setEnableRenderLoop(element, enable) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); + } + static putImageData(element, pData, width, height) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); + } + deinit() { + this.setEnableRenderLoop(false); + } + setCanvasSize(width, height) { + 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) { + GL.makeContextCurrent(this.glInfo.context); + } + } + requestAnimationFrame(renderLoop) { + if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) + this.setEnableRenderLoop(renderLoop); + if (this.renderLoopRequest !== 0) + return; + this.renderLoopRequest = window.requestAnimationFrame(() => { + if (this.glInfo) { + 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; + if (this.renderLoopEnabled) + this.requestAnimationFrame(); + }); + } + setEnableRenderLoop(enable) { + this.renderLoopEnabled = enable; + if (enable) { + this.requestAnimationFrame(); + } + else if (this.renderLoopRequest !== 0) { + window.cancelAnimationFrame(this.renderLoopRequest); + this.renderLoopRequest = 0; + } + } + putImageData(pData, width, height) { + 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; + } + this.htmlCanvas.width = width; + this.htmlCanvas.height = height; + 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) { + 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 = 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; + } +} +//# sourceMappingURL=SKHtmlCanvas.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js new file mode 100644 index 0000000000..1c6ce26155 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js @@ -0,0 +1,39 @@ +export class SizeWatcher { + static observe(element, elementId, callback) { + if (!element || !callback) + return; + SizeWatcher.init(); + const watcherElement = element; + watcherElement.SizeWatcher = { + callback: callback + }; + SizeWatcher.elements.set(elementId, element); + SizeWatcher.observer.observe(element); + SizeWatcher.invoke(element); + } + static unobserve(elementId) { + if (!elementId || !SizeWatcher.observer) + return; + const element = SizeWatcher.elements.get(elementId); + SizeWatcher.elements.delete(elementId); + SizeWatcher.observer.unobserve(element); + } + static init() { + if (SizeWatcher.observer) + return; + SizeWatcher.elements = new Map(); + SizeWatcher.observer = new ResizeObserver((entries) => { + for (let entry of entries) { + SizeWatcher.invoke(entry.target); + } + }); + } + static invoke(element) { + const watcherElement = element; + const instance = watcherElement.SizeWatcher; + if (!instance || !instance.callback) + return; + return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); + } +} +//# sourceMappingURL=SizeWatcher.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js new file mode 100644 index 0000000000..bf9cab01bc --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js @@ -0,0 +1,72 @@ +class InnerDbConnection { + constructor(database) { + this.database = database; + } + openStore(store, mode) { + const tx = this.database.transaction(store, mode); + return tx.objectStore(store); + } + put(store, obj, key) { + 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); + }; + }); + } + get(store, key) { + 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); + }; + }); + } + delete(store, key) { + const os = this.openStore(store, "readwrite"); + return new Promise((resolve, reject) => { + const response = os.delete(key); + response.onsuccess = () => { + resolve(); + }; + response.onerror = () => { + reject(response.error); + }; + }); + } + close() { + this.database.close(); + } +} +export class IndexedDbWrapper { + constructor(databaseName, objectStores) { + this.databaseName = databaseName; + this.objectStores = objectStores; + } + connect() { + 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); + }; + }); + } +} +//# sourceMappingURL=IndexedDbWrapper.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js new file mode 100644 index 0000000000..042a83d177 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js @@ -0,0 +1,199 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +}; +import { IndexedDbWrapper } from "./IndexedDbWrapper"; +const fileBookmarksStore = "fileBookmarks"; +const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ + fileBookmarksStore +]); +class StorageItem { + constructor(handle, bookmarkId) { + this.handle = handle; + this.bookmarkId = bookmarkId; + } + getName() { + return this.handle.name; + } + getKind() { + return this.handle.kind; + } + openRead() { + return __awaiter(this, void 0, void 0, function* () { + if (!(this.handle instanceof FileSystemFileHandle)) { + throw new Error("StorageItem is not a file"); + } + yield this.verityPermissions('read'); + const file = yield this.handle.getFile(); + return file; + }); + } + openWrite() { + return __awaiter(this, void 0, void 0, function* () { + if (!(this.handle instanceof FileSystemFileHandle)) { + throw new Error("StorageItem is not a file"); + } + yield this.verityPermissions('readwrite'); + return yield this.handle.createWritable({ keepExistingData: true }); + }); + } + getProperties() { + return __awaiter(this, void 0, void 0, function* () { + const file = this.handle instanceof FileSystemFileHandle + && (yield this.handle.getFile()); + if (!file) { + return null; + } + return { + Size: file.size, + LastModified: file.lastModified, + Type: file.type + }; + }); + } + getItems() { + var e_1, _a; + return __awaiter(this, void 0, void 0, function* () { + if (this.handle.kind !== "directory") { + return new StorageItems([]); + } + const items = []; + try { + for (var _b = __asyncValues(this.handle.entries()), _c; _c = yield _b.next(), !_c.done;) { + const [key, value] = _c.value; + items.push(new StorageItem(value)); + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b); + } + finally { if (e_1) throw e_1.error; } + } + return new StorageItems(items); + }); + } + verityPermissions(mode) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield this.handle.queryPermission({ mode })) === 'granted') { + return; + } + if ((yield this.handle.requestPermission({ mode })) === "denied") { + throw new Error("Read permissions denied"); + } + }); + } + saveBookmark() { + return __awaiter(this, void 0, void 0, function* () { + if (this.bookmarkId) { + return this.bookmarkId; + } + const connection = yield avaloniaDb.connect(); + try { + const key = yield connection.put(fileBookmarksStore, this.handle, this.generateBookmarkId()); + return key; + } + finally { + connection.close(); + } + }); + } + deleteBookmark() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.bookmarkId) { + return; + } + const connection = yield avaloniaDb.connect(); + try { + const key = yield connection.delete(fileBookmarksStore, this.bookmarkId); + } + finally { + connection.close(); + } + }); + } + generateBookmarkId() { + return Date.now().toString(36) + Math.random().toString(36).substring(2); + } +} +class StorageItems { + constructor(items) { + this.items = items; + } + count() { + return this.items.length; + } + at(index) { + return this.items[index]; + } +} +export class StorageProvider { + static canOpen() { + return typeof window.showOpenFilePicker !== 'undefined'; + } + static canSave() { + return typeof window.showSaveFilePicker !== 'undefined'; + } + static canPickFolder() { + return typeof window.showDirectoryPicker !== 'undefined'; + } + static selectFolderDialog(startIn) { + return __awaiter(this, void 0, void 0, function* () { + const options = { + startIn: ((startIn === null || startIn === void 0 ? void 0 : startIn.handle) || undefined) + }; + const handle = yield window.showDirectoryPicker(options); + return new StorageItem(handle); + }); + } + static openFileDialog(startIn, multiple, types, excludeAcceptAllOption) { + return __awaiter(this, void 0, void 0, function* () { + const options = { + startIn: ((startIn === null || startIn === void 0 ? void 0 : startIn.handle) || undefined), + multiple, + excludeAcceptAllOption, + types: (types || undefined) + }; + const handles = yield window.showOpenFilePicker(options); + return new StorageItems(handles.map((handle) => new StorageItem(handle))); + }); + } + static saveFileDialog(startIn, suggestedName, types, excludeAcceptAllOption) { + return __awaiter(this, void 0, void 0, function* () { + const options = { + startIn: ((startIn === null || startIn === void 0 ? void 0 : startIn.handle) || undefined), + suggestedName: (suggestedName || undefined), + excludeAcceptAllOption, + types: (types || undefined) + }; + const handle = yield window.showSaveFilePicker(options); + return new StorageItem(handle); + }); + } + static openBookmark(key) { + return __awaiter(this, void 0, void 0, function* () { + const connection = yield avaloniaDb.connect(); + try { + const handle = yield connection.get(fileBookmarksStore, key); + return handle && new StorageItem(handle, key); + } + finally { + connection.close(); + } + }); + } +} +//# sourceMappingURL=StorageProvider.js.map \ No newline at end of file