Browse Source

Use webpack modules

pull/8820/head
Max Katz 4 years ago
parent
commit
c8a2e65120
  1. 15
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  2. 18
      src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs
  3. 41
      src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs
  4. 31
      src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs
  5. 12
      src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs
  6. 32
      src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs
  7. 45
      src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs
  8. 41
      src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs
  9. 2
      src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs
  10. 5
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/Avalonia.ts
  11. 0
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts
  12. 0
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts
  13. 0
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts
  14. 0
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts
  15. 0
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts
  16. 1
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts
  17. 0
      src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts
  18. 12
      src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts
  19. 6
      src/Web/Avalonia.Web.Blazor/webapp/package.json
  20. 40
      src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js
  21. 6
      src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js
  22. 26
      src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js
  23. 19
      src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js
  24. 48
      src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js
  25. 172
      src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js
  26. 39
      src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js
  27. 72
      src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js
  28. 199
      src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js

15
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<IJSInProcessRuntime>().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();

18
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<AvaloniaModule> ImportAsync(IJSRuntime js)
{
var interop = new AvaloniaModule(js);
await interop.ImportAsync();
return interop;
}
}
}

41
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<double>? callbacksEvent;
private readonly FloatFloatActionHelper callbackHelper;
private readonly FloatFloatActionHelper _callbackHelper;
private readonly AvaloniaModule _module;
private DotNetObjectReference<FloatFloatActionHelper>? callbackReference;
public static async Task<DpiWatcherInterop> ImportAsync(IJSRuntime js, Action<double>? callback = null)
public DpiWatcherInterop(AvaloniaModule module, Action<double>? 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<double> 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<double>(StartSymbol, callbackReference);
return _module.Invoke<double>(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<double>(GetDpiSymbol);
public double GetDpi() => _module.Invoke<double>(GetDpiSymbol);
}
}

31
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<InputHelperInterop> 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);
}
}

12
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<TValue>(string identifier, params object?[]? args) =>
internal TValue Invoke<TValue>(string identifier, params object?[]? args) =>
Module.Invoke<TValue>(identifier, args);
protected ValueTask InvokeAsync(string identifier, params object?[]? args) =>
internal ValueTask InvokeAsync(string identifier, params object?[]? args) =>
Module.InvokeVoidAsync(identifier, args);
protected ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object?[]? args) =>
internal ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object?[]? args) =>
Module.InvokeAsync<TValue>(identifier, args);
protected virtual void OnDisposingModule() { }

32
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<NativeControlHostInterop> 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<IJSInProcessObjectReference>(CreateDefaultChildSymbol);
var element = _module.Invoke<IJSInProcessObjectReference>(CreateDefaultChildSymbol);
return new JSObjectControlHandle(element);
}
@ -43,9 +35,9 @@ namespace Avalonia.Web.Blazor.Interop
Attachment? a = null;
try
{
using var hostElementJsReference = Invoke<IJSInProcessObjectReference>(GetReferenceSymbol, hostElement);
using var hostElementJsReference = _module.Invoke<IJSInProcessObjectReference>(GetReferenceSymbol, _hostElement);
var child = create(new JSObjectControlHandle(hostElementJsReference));
var attachmenetReference = Invoke<IJSInProcessObjectReference>(CreateAttachmentSymbol);
var attachmenetReference = _module.Invoke<IJSInProcessObjectReference>(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<IJSInProcessObjectReference>(CreateAttachmentSymbol);
var attachmenetReference = _module.Invoke<IJSInProcessObjectReference>(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;
}

45
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<ActionHelper>? callbackReference;
public static async Task<SKHtmlCanvasInterop> 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<GLInfo>(InitGLSymbol, htmlCanvas, htmlElementId, callbackReference);
return _module.Invoke<GLInfo>(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<bool>(InitRasterSymbol, htmlCanvas, htmlElementId, callbackReference);
return _module.Invoke<bool>(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);
}

41
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<FloatFloatActionHelper>? callbackReference;
public static async Task<SizeWatcherInterop> ImportAsync(IJSRuntime js, ElementReference element, Action<SKSize> callback)
public SizeWatcherInterop(AvaloniaModule module, ElementReference element, Action<SKSize> 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<SKSize> 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;

2
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<StorageProviderInterop> ImportAsync(IJSRuntime js)

5
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"

0
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/DpiWatcher.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts

0
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/InputHelper.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts

0
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/NativeControlHost.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts

0
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SKHtmlCanvas.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts

0
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SizeWatcher.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts

1
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts

@ -1 +0,0 @@


0
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/IndexedDbWrapper.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts

12
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(

6
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"

40
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,
}
};

6
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

26
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

19
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

48
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

172
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

39
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

72
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

199
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
Loading…
Cancel
Save