diff --git a/.gitignore b/.gitignore index 84faae1806..61a3b53de1 100644 --- a/.gitignore +++ b/.gitignore @@ -215,3 +215,4 @@ src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js node_modules src/Web/Avalonia.Web.Blazor/webapp/package-lock.json src/Web/Avalonia.Web.Blazor/wwwroot +src/Web/Avalonia.Web/wwwroot diff --git a/Avalonia.sln b/Avalonia.sln index 68335c672c..81a9b43890 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -212,7 +212,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ColorPick EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{EABE2161-989B-42BF-BD8D-1E34B20C21F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevGenerators", "src\tools\DevGenerators\DevGenerators.csproj", "{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\DevGenerators\DevGenerators.csproj", "{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}" +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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" EndProject @@ -407,9 +411,7 @@ Global {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|Any CPU.Build.0 = Release|Any CPU {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU {41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.Build.0 = Debug|Any CPU {41B02319-965D-4945-8005-C1A3D1224165}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -510,6 +512,14 @@ Global {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.Build.0 = Release|Any CPU + {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 @@ -583,6 +593,8 @@ Global {2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {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} {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} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ba351b91a3..33b2dc670a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,6 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' steps: - - task: PowerShell@2 displayName: Get PR Number inputs: @@ -35,6 +34,17 @@ jobs: inputs: version: 6.0.401 + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' + inputs: + version: 7.0.100-rc.1.22431.12 + + - task: CmdLine@2 + displayName: 'Install Workloads' + inputs: + script: | + dotnet workload install wasm-tools wasm-experimental + - task: CmdLine@2 displayName: 'Run Build' inputs: @@ -60,6 +70,17 @@ jobs: displayName: 'Use .NET Core SDK 6.0.401' inputs: version: 6.0.401 + + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' + inputs: + version: 7.0.100-rc.1.22431.12 + + - task: CmdLine@2 + displayName: 'Install Workloads' + inputs: + script: | + dotnet workload install wasm-tools wasm-experimental - task: CmdLine@2 displayName: 'Generate avalonia-native' @@ -121,11 +142,16 @@ jobs: inputs: version: 6.0.401 + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' + inputs: + version: 7.0.100-rc.1.22431.12 + - task: CmdLine@2 displayName: 'Install Workloads' inputs: script: | - dotnet workload install android ios + dotnet workload install android ios wasm-tools wasm-experimental - task: CmdLine@2 displayName: 'Install Nuke' diff --git a/global.json b/global.json index 7c2daee25f..44d4e10dbf 100644 --- a/global.json +++ b/global.json @@ -1,8 +1,4 @@ { - "sdk": { - "version": "6.0.401", - "rollForward": "latestFeature" - }, "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", "MSBuild.Sdk.Extras": "3.0.22", diff --git a/nukebuild/BuildTasksPatcher.cs b/nukebuild/BuildTasksPatcher.cs index e3766ae23f..5fd331035a 100644 --- a/nukebuild/BuildTasksPatcher.cs +++ b/nukebuild/BuildTasksPatcher.cs @@ -17,8 +17,12 @@ public class BuildTasksPatcher { if (entry.Name == "Avalonia.Build.Tasks.dll") { - var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll"); + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + var temp = Path.Combine(tempDir, Guid.NewGuid() + ".dll"); var output = temp + ".output"; + File.Copy(typeof(Microsoft.Build.Framework.ITask).Assembly.GetModules()[0].FullyQualifiedName, + Path.Combine(tempDir, "Microsoft.Build.Framework.dll")); var patched = new MemoryStream(); try { @@ -57,10 +61,8 @@ public class BuildTasksPatcher { try { - if (File.Exists(temp)) - File.Delete(temp); - if (File.Exists(output)) - File.Delete(output); + if(Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); } catch { diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 5db9579876..8c0d824298 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -4,9 +4,9 @@ false False - CS0649;CS0169 + CS0649;CS0169;SYSLIB0011 1 - net6.0 + net7.0 @@ -40,5 +40,9 @@ + + + + diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs index 46aa6efa72..4aa84e3ec4 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs @@ -18,6 +18,7 @@ public interface IStorageFile : IStorageItem /// /// Opens a stream for read access. /// + /// Task OpenReadAsync(); /// @@ -28,5 +29,6 @@ public interface IStorageFile : IStorageItem /// /// Opens stream for writing to the file. /// + /// Task OpenWriteAsync(); } diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index c8368e6d7a..d5e01087ef 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -30,4 +30,5 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Web.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Web, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index bd3b86f06d..b8692bb771 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; using System.Xml.Linq; using System.Linq; diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index c20b2f656e..75758d1315 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; using System.Text; using Avalonia.Markup.Xaml.PortableXaml; using Avalonia.Utilities; diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 5b5951e800..0e64e98f1e 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -287,7 +287,6 @@ namespace Avalonia.Web.Blazor // create the SkiaSharp context if (_context == null) { - Console.WriteLine("create glcontext"); _glInterface = GRGlInterface.Create(); _context = GRContext.CreateGl(_glInterface); diff --git a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj b/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj new file mode 100644 index 0000000000..13aad8c13e --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj @@ -0,0 +1,41 @@ + + + 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/EmbedSample.Browser.cs b/src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs new file mode 100644 index 0000000000..5baa4a6b35 --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.InteropServices.JavaScript; + +using Avalonia; +using Avalonia.Platform; +using Avalonia.Web; + +using ControlCatalog.Pages; + +namespace ControlCatalog.Web; + +public class EmbedSampleWeb : INativeDemoControl +{ + public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func createDefault) + { + if (isSecond) + { + var iframe = EmbedInterop.CreateElement("iframe"); + iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70"); + + return new JSObjectControlHandle(iframe); + } + else + { + var defaultHandle = (JSObjectControlHandle)createDefault(); + + _ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ => + { + EmbedInterop.AddAppButton(defaultHandle.Object); + }); + + return defaultHandle; + } + } +} + +internal static partial class EmbedInterop +{ + [JSImport("globalThis.document.createElement")] + public static partial JSObject CreateElement(string tagName); + + [JSImport("addAppButton", "embed.js")] + public static partial void AddAppButton(JSObject parentObject); +} diff --git a/src/Web/Avalonia.Web.Sample/Logo.svg b/src/Web/Avalonia.Web.Sample/Logo.svg new file mode 100644 index 0000000000..9685a23af1 --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/Logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Web/Avalonia.Web.Sample/Program.cs b/src/Web/Avalonia.Web.Sample/Program.cs new file mode 100644 index 0000000000..52acabb0fa --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/Program.cs @@ -0,0 +1,19 @@ +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.Sample/app.css b/src/Web/Avalonia.Web.Sample/app.css new file mode 100644 index 0000000000..04ea5bee19 --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/app.css @@ -0,0 +1,38 @@ +#out { + height: 100vh; + width: 100vw +} + +#avalonia-splash { + position: absolute; + height: 100%; + width: 100%; + color: whitesmoke; + background: #171C2C; + font-family: 'Nunito', sans-serif; +} + +#avalonia-splash a{ + color: whitesmoke; + text-decoration: none; +} + +.center { + display: flex; + justify-content: center; + height: 250px; +} + +.splash-close { + animation: fadeOut 1s forwards; +} + +@keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} diff --git a/src/Web/Avalonia.Web.Sample/embed.js b/src/Web/Avalonia.Web.Sample/embed.js new file mode 100644 index 0000000000..f393c80314 --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/embed.js @@ -0,0 +1,11 @@ +export function addAppButton(parent) { + var button = globalThis.document.createElement('button'); + button.innerText = 'Hello world'; + var clickCount = 0; + button.onclick = () => { + clickCount++; + button.innerText = 'Click count ' + clickCount; + }; + parent.appendChild(button); + return button; +} diff --git a/src/Web/Avalonia.Web.Sample/favicon.ico b/src/Web/Avalonia.Web.Sample/favicon.ico new file mode 100644 index 0000000000..da8d49ff9b Binary files /dev/null and b/src/Web/Avalonia.Web.Sample/favicon.ico differ diff --git a/src/Web/Avalonia.Web.Sample/index.html b/src/Web/Avalonia.Web.Sample/index.html new file mode 100644 index 0000000000..ee023790fb --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/index.html @@ -0,0 +1,31 @@ + + + + + + + Avalonia.Web.Sample + + + + + + + + + +
+
+
+

Powered by

+ + Avalonia Logo + Avalonia + +
+
+
+ + + + diff --git a/src/Web/Avalonia.Web.Sample/main.js b/src/Web/Avalonia.Web.Sample/main.js new file mode 100644 index 0000000000..3683aea181 --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/main.js @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet } from './dotnet.js' +import { createAvaloniaRuntime } from './avalonia.js'; + +const is_browser = typeof window != "undefined"; +if (!is_browser) throw new Error(`Expected to be running in a browser`); + +const dotnetRuntime = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + +await createAvaloniaRuntime(dotnetRuntime); + +const config = dotnetRuntime.getConfig(); + +await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); diff --git a/src/Web/Avalonia.Web.Sample/runtimeconfig.template.json b/src/Web/Avalonia.Web.Sample/runtimeconfig.template.json new file mode 100644 index 0000000000..8f0557352c --- /dev/null +++ b/src/Web/Avalonia.Web.Sample/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj new file mode 100644 index 0000000000..a4756a5e2b --- /dev/null +++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj @@ -0,0 +1,55 @@ + + + net7.0 + preview + enable + true + + + + + + + + + + + + + + + + + + + true + build\ + + + true + build\;buildTransitive\ + + + true + build/interop.js;buildTransitive/interop.js + + + true + build\wwwroot;buildTransitive\wwwroot + + + + + + + + + + + + + + + + + diff --git a/src/Web/Avalonia.Web/Avalonia.Web.props b/src/Web/Avalonia.Web/Avalonia.Web.props new file mode 100644 index 0000000000..6c975cd284 --- /dev/null +++ b/src/Web/Avalonia.Web/Avalonia.Web.props @@ -0,0 +1,5 @@ + + + $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js" + + diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Web/Avalonia.Web/Avalonia.Web.targets new file mode 100644 index 0000000000..d1bec2aa93 --- /dev/null +++ b/src/Web/Avalonia.Web/Avalonia.Web.targets @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs new file mode 100644 index 0000000000..e81620ffde --- /dev/null +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -0,0 +1,451 @@ +using System; +using System.Runtime.InteropServices.JavaScript; +using Avalonia.Controls; +using Avalonia.Controls.Embedding; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Input.TextInput; +using Avalonia.Rendering.Composition; +using Avalonia.Threading; +using Avalonia.Web.Interop; +using Avalonia.Web.Skia; + +using SkiaSharp; + +namespace Avalonia.Web +{ + [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings + public partial class AvaloniaView : ITextInputMethodImpl + { + private readonly BrowserTopLevelImpl _topLevelImpl; + private EmbeddableControlRoot _topLevel; + + private readonly JSObject _containerElement; + private readonly JSObject _canvas; + private readonly JSObject _nativeControlsContainer; + private readonly JSObject _inputElement; + private readonly JSObject? _splash; + + private GLInfo? _jsGlInfo = null; + private double _dpi = 1; + private Size _canvasSize = new(100.0, 100.0); + + private GRContext? _context; + private GRGlInterface? _glInterface; + private const SKColorType ColorType = SKColorType.Rgba8888; + + private bool _useGL; + private ITextInputMethodClient? _client; + private static int _canvasCount; + + public AvaloniaView(string divId) + { + var host = DomHelper.GetElementById(divId); + if (host == null) + { + throw new Exception($"Element with id {divId} was not found in the html document."); + } + + var hostContent = DomHelper.CreateAvaloniaHost(host); + if (hostContent == null) + { + throw new InvalidOperationException("Avalonia WASM host wasn't initialized."); + } + + _containerElement = hostContent.GetPropertyAsJSObject("host") + ?? throw new InvalidOperationException("Host cannot be null"); + _canvas = hostContent.GetPropertyAsJSObject("canvas") + ?? throw new InvalidOperationException("Canvas cannot be null"); + _nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost") + ?? throw new InvalidOperationException("NativeHost cannot be null"); + _inputElement = hostContent.GetPropertyAsJSObject("inputElement") + ?? throw new InvalidOperationException("InputElement cannot be null"); + + _splash = DomHelper.GetElementById("avalonia-splash"); + + _canvas.SetProperty("id", $"avaloniaCanvas{_canvasCount++}"); + + _topLevelImpl = new BrowserTopLevelImpl(this); + + _topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () => + { + Dispatcher.UIThread.Post(() => + { + if (_splash != null) + { + DomHelper.AddCssClass(_splash, "splash-close"); + } + }); + }); + + _topLevelImpl.SetCssCursor = (cursor) => + { + InputHelper.SetCursor(_containerElement, cursor); // macOS + InputHelper.SetCursor(_canvas, cursor); // windows + }; + + _topLevel.Prepare(); + + _topLevel.Renderer.Start(); + + InputHelper.SubscribeKeyEvents( + _containerElement, + OnKeyDown, + OnKeyUp); + + InputHelper.SubscribeTextEvents( + _inputElement, + OnTextInput, + OnCompositionStart, + OnCompositionUpdate, + OnCompositionEnd); + + InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); + + var skiaOptions = AvaloniaLocator.Current.GetService(); + + _dpi = DomHelper.ObserveDpi(OnDpiChanged); + + _useGL = skiaOptions?.CustomGpuFactory != null; + + if (_useGL) + { + _jsGlInfo = CanvasHelper.InitialiseGL(_canvas, OnRenderFrame); + // 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); + } + + _topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) }; + } + else + { + //var rasterInitialized = _interop.InitRaster(); + //Console.WriteLine("raster initialized: {0}", rasterInitialized); + + //_topLevelImpl.SetSurface(ColorType, + // new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData); + } + + CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); + + _topLevelImpl.SetClientSize(_canvasSize, _dpi); + + DomHelper.ObserveSize(host, divId, OnSizeChanged); + + CanvasHelper.RequestAnimationFrame(_canvas, true); + } + + private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) + { + var point = new RawPointerPoint + { + Position = new Point(args.GetPropertyAsDouble("offsetX"), args.GetPropertyAsDouble("offsetY")), + Pressure = (float)args.GetPropertyAsDouble("pressure"), + XTilt = (float)args.GetPropertyAsDouble("tiltX"), + YTilt = (float)args.GetPropertyAsDouble("tiltY"), + Twist = (float)args.GetPropertyAsDouble("twist") + }; + + return point; + } + + private bool OnPointerMove(JSObject args) + { + var type = args.GetPropertyAsString("pointertype"); + + var point = ExtractRawPointerFromJSArgs(args); + + return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + } + + private bool OnPointerDown(JSObject args) + { + var pointerType = args.GetPropertyAsString("pointerType"); + + var type = pointerType switch + { + "touch" => RawPointerEventType.TouchBegin, + _ => args.GetPropertyAsInt32("button") switch + { + 0 => RawPointerEventType.LeftButtonDown, + 1 => RawPointerEventType.MiddleButtonDown, + 2 => RawPointerEventType.RightButtonDown, + 3 => RawPointerEventType.XButton1Down, + 4 => RawPointerEventType.XButton2Down, + // 5 => Pen eraser button, + _ => RawPointerEventType.Move + } + }; + + var point = ExtractRawPointerFromJSArgs(args); + + return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + } + + private bool OnPointerUp(JSObject args) + { + var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; + + var type = pointerType switch + { + "touch" => RawPointerEventType.TouchEnd, + _ => args.GetPropertyAsInt32("button") switch + { + 0 => RawPointerEventType.LeftButtonUp, + 1 => RawPointerEventType.MiddleButtonUp, + 2 => RawPointerEventType.RightButtonUp, + 3 => RawPointerEventType.XButton1Up, + 4 => RawPointerEventType.XButton2Up, + // 5 => Pen eraser button, + _ => RawPointerEventType.Move + } + }; + + var point = ExtractRawPointerFromJSArgs(args); + + return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + } + + private bool OnWheel(JSObject args) + { + return _topLevelImpl.RawMouseWheelEvent(new Point(args.GetPropertyAsDouble("clientX"), args.GetPropertyAsDouble("clientY")), + new Vector(-(args.GetPropertyAsDouble("deltaX") / 50), -(args.GetPropertyAsDouble("deltaY") / 50)), GetModifiers(args)); + } + + private static RawInputModifiers GetModifiers(JSObject e) + { + var modifiers = RawInputModifiers.None; + + if (e.GetPropertyAsBoolean("ctrlKey")) + modifiers |= RawInputModifiers.Control; + if (e.GetPropertyAsBoolean("altKey")) + modifiers |= RawInputModifiers.Alt; + if (e.GetPropertyAsBoolean("shiftKey")) + modifiers |= RawInputModifiers.Shift; + if (e.GetPropertyAsBoolean("metaKey")) + modifiers |= RawInputModifiers.Meta; + + var buttons = e.GetPropertyAsInt32("buttons"); + if ((buttons & 1L) == 1) + modifiers |= RawInputModifiers.LeftMouseButton; + + if ((buttons & 2L) == 2) + modifiers |= e.GetPropertyAsString("type") == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton; + + if ((buttons & 4L) == 4) + modifiers |= RawInputModifiers.MiddleMouseButton; + + if ((buttons & 8L) == 8) + modifiers |= RawInputModifiers.XButton1MouseButton; + + if ((buttons & 16L) == 16) + modifiers |= RawInputModifiers.XButton2MouseButton; + + if ((buttons & 32L) == 32) + modifiers |= RawInputModifiers.PenEraser; + + return modifiers; + } + + private bool OnKeyDown (string code, string key, int modifier) + { + return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier); + } + + private bool OnKeyUp(string code, string key, int modifier) + { + return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, code, key, (RawInputModifiers)modifier); + } + + private bool OnTextInput (string type, string? data) + { + if(data == null || IsComposing) + { + return false; + } + + return _topLevelImpl.RawTextEvent(data); + } + + private bool OnCompositionStart (JSObject args) + { + if (_client == null) + return false; + + _client.SetPreeditText(null); + IsComposing = true; + + return false; + } + + private bool OnCompositionUpdate(JSObject args) + { + if (_client == null) + return false; + + _client.SetPreeditText(args.GetPropertyAsString("data")); + + return false; + } + + private bool OnCompositionEnd(JSObject args) + { + if (_client == null) + return false; + + IsComposing = false; + _client.SetPreeditText(null); + _topLevelImpl.RawTextEvent(args.GetPropertyAsString("data")!); + + return false; + } + + 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 Control? Content + { + get => (Control)_topLevel.Content!; + set => _topLevel.Content = value; + } + + public bool IsComposing { get; private set; } + + internal INativeControlHostImpl GetNativeControlHostImpl() + { + return new BrowserNativeControlHost(_nativeControlsContainer); + } + + 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 oldDpi, double newDpi) + { + if (Math.Abs(_dpi - newDpi) > 0.0001) + { + _dpi = newDpi; + + CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); + + _topLevelImpl.SetClientSize(_canvasSize, _dpi); + + ForceBlit(); + } + } + + private void OnSizeChanged(int height, int width) + { + var newSize = new Size(height, width); + + if (_canvasSize != newSize) + { + _canvasSize = newSize; + + CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); + + _topLevelImpl.SetClientSize(_canvasSize, _dpi); + + ForceBlit(); + } + } + + private void HideIme() + { + InputHelper.HideElement(_inputElement); + InputHelper.FocusElement(_containerElement); + } + + public void SetClient(ITextInputMethodClient? client) + { + Console.WriteLine("Set Client"); + if (_client != null) + { + _client.SurroundingTextChanged -= SurroundingTextChanged; + } + + if (client != null) + { + client.SurroundingTextChanged += SurroundingTextChanged; + } + + InputHelper.ClearInputElement(_inputElement); + + _client = client; + + if (_client != null) + { + InputHelper.ShowElement(_inputElement); + InputHelper.FocusElement(_inputElement); + + var surroundingText = _client.SurroundingText; + + InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); + + Console.WriteLine("Shown, focused and surrounded."); + } + else + { + HideIme(); + } + } + + private void SurroundingTextChanged(object? sender, EventArgs e) + { + if (_client != null) + { + var surroundingText = _client.SurroundingText; + + InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); + } + } + + public void 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) + { + } + + public void Reset() + { + InputHelper.ClearInputElement(_inputElement); + InputHelper.SetSurroundingText(_inputElement, "", 0, 0); + } + } +} diff --git a/src/Web/Avalonia.Web/BrowserNativeControlHost.cs b/src/Web/Avalonia.Web/BrowserNativeControlHost.cs new file mode 100644 index 0000000000..4cdcf627e6 --- /dev/null +++ b/src/Web/Avalonia.Web/BrowserNativeControlHost.cs @@ -0,0 +1,136 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices.JavaScript; + +using Avalonia.Controls.Platform; +using Avalonia.Platform; +using Avalonia.Web.Interop; + +namespace Avalonia.Web +{ + internal class BrowserNativeControlHost : INativeControlHostImpl + { + private readonly JSObject _hostElement; + + public BrowserNativeControlHost(JSObject element) + { + _hostElement = element; + } + + public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) + { + var element = NativeControlHostHelper.CreateDefaultChild(null); + return new JSObjectControlHandle(element); + } + + public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func create) + { + Attachment? a = null; + try + { + var child = create(new JSObjectControlHandle(_hostElement)); + var attachmenetReference = NativeControlHostHelper.CreateAttachment(); + // 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 = NativeControlHostHelper.CreateAttachment(); + var a = new Attachment(attachmenetReference, handle); + a.AttachedTo = this; + return a; + } + + public bool IsCompatibleWith(IPlatformHandle handle) => handle is JSObjectControlHandle; + + private 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 JSObject? _native; + private BrowserNativeControlHost? _attachedTo; + + public Attachment(JSObject native, IPlatformHandle handle) + { + _native = native; + NativeControlHostHelper.InitializeWithChildHandle(_native, ((JSObjectControlHandle)handle).Object); + } + + public void Dispose() + { + if (_native != null) + { + NativeControlHostHelper.ReleaseChild(_native); + _native.Dispose(); + _native = null; + } + } + + public INativeControlHostImpl? AttachedTo + { + get => _attachedTo!; + set + { + CheckDisposed(); + + var host = (BrowserNativeControlHost?)value; + if (host == null) + { + NativeControlHostHelper.AttachTo(_native, null); + } + else + { + NativeControlHostHelper.AttachTo(_native, host._hostElement); + } + _attachedTo = host; + } + } + + public bool IsCompatibleWith(INativeControlHostImpl host) => host is BrowserNativeControlHost; + + public void HideWithSize(Size size) + { + CheckDisposed(); + if (_attachedTo == null) + return; + + NativeControlHostHelper.HideWithSize(_native, Math.Max(1, size.Width), Math.Max(1, 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)); + + NativeControlHostHelper.ShowInBounds(_native, bounds.X, bounds.Y, bounds.Width, bounds.Height); + } + + [MemberNotNull(nameof(_native))] + private void CheckDisposed() + { + if (_native == null) + throw new ObjectDisposedException(nameof(Attachment)); + } + } + } +} diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs new file mode 100644 index 0000000000..091ab3f68c --- /dev/null +++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices.JavaScript; +using System; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Media; +using Avalonia.Web.Skia; + +namespace Avalonia.Web +{ + [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings + public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime + { + public AvaloniaView? View; + + public Control? MainView + { + get => View!.Content; + set => View!.Content = value; + } + } + + public static partial class WebAppBuilder + { + public static T SetupBrowserApp( + 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() }) + .AfterSetup(b => + { + lifetime.View = new AvaloniaView(mainDivId); + }) + .SetupWithLifetime(lifetime); + } + } +} diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs new file mode 100644 index 0000000000..b955da6df2 --- /dev/null +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +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.Skia; +using Avalonia.Web.Storage; + +namespace Avalonia.Web +{ + [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings + internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider + { + private Size _clientSize; + 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 BrowserTopLevelImpl(AvaloniaView avaloniaView) + { + Surfaces = Enumerable.Empty(); + _avaloniaView = avaloniaView; + TransparencyLevel = WindowTransparencyLevel.None; + AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1); + _touchDevice = new TouchDevice(); + _penDevice = new PenDevice(); + NativeControlHost = _avaloniaView.GetNativeControlHostImpl(); + } + + public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds; + + public void SetClientSize(Size newSize, double dpi) + { + if (Math.Abs(RenderScaling - dpi) > 0.0001) + { + if (Surfaces.FirstOrDefault() is BrowserSkiaSurface surface) + { + surface.Scaling = dpi; + } + + ScalingChanged?.Invoke(dpi); + } + + if (newSize != _clientSize) + { + _clientSize = newSize; + + if (Surfaces.FirstOrDefault() is BrowserSkiaSurface surface) + { + surface.Size = new PixelSize((int)newSize.Width, (int)newSize.Height); + } + + Resized?.Invoke(newSize, PlatformResizeReason.User); + } + } + + public bool 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); + + return args.Handled; + } + + return false; + } + + private IPointerDevice GetPointerDevice(string pointerType) + { + return pointerType switch + { + "touch" => _touchDevice, + "pen" => _penDevice, + _ => MouseDevice + }; + } + + public bool RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers) + { + if (_inputRoot is { }) + { + var args = new RawMouseWheelEventArgs(MouseDevice, Timestamp, _inputRoot, p, v, modifiers); + + Input?.Invoke(args); + + return args.Handled; + } + + return false; + } + + 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 bool RawTextEvent(string text) + { + if (_inputRoot is { }) + { + var args = new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text); + Input?.Invoke(args); + + return args.Handled; + } + + return false; + } + + 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 => (Surfaces.FirstOrDefault() as BrowserSkiaSurface)?.Scaling ?? 1; + + public IEnumerable Surfaces { get; set; } + + 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; } = BrowserWindowingPlatform.Keyboard; + public WindowTransparencyLevel TransparencyLevel { get; } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } + + public ITextInputMethodImpl TextInputMethod => _avaloniaView; + + public INativeControlHostImpl? NativeControlHost { get; } + public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider(); + } +} diff --git a/src/Web/Avalonia.Web/ClipboardImpl.cs b/src/Web/Avalonia.Web/ClipboardImpl.cs new file mode 100644 index 0000000000..793099f55a --- /dev/null +++ b/src/Web/Avalonia.Web/ClipboardImpl.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Web.Interop; + +namespace Avalonia.Web +{ + internal class ClipboardImpl : IClipboard + { + public Task GetTextAsync() + { + return InputHelper.ReadClipboardTextAsync(); + } + + public Task SetTextAsync(string text) + { + return InputHelper.WriteClipboardTextAsync(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/Cursor.cs b/src/Web/Avalonia.Web/Cursor.cs new file mode 100644 index 0000000000..5db0bdeda2 --- /dev/null +++ b/src/Web/Avalonia.Web/Cursor.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.Web +{ + 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/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs new file mode 100644 index 0000000000..66a1d421b7 --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.JavaScript; + +namespace Avalonia.Web.Interop; + +internal record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); + +[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings +internal static partial class CanvasHelper +{ + + [DllImport("libSkiaSharp", CallingConvention = CallingConvention.Cdecl)] + static extern JSObject InterceptGLObject(); + + public static GLInfo InitialiseGL(JSObject canvas, Action renderFrameCallback) + { + InterceptGLObject(); + + var info = InitGL(canvas, canvas.GetPropertyAsString("id")!, renderFrameCallback); + + var glInfo = new GLInfo( + info.GetPropertyAsInt32("context"), + (uint)info.GetPropertyAsInt32("fboId"), + info.GetPropertyAsInt32("stencil"), + info.GetPropertyAsInt32("sample"), + info.GetPropertyAsInt32("depth")); + + return glInfo; + } + + [JSImport("Canvas.requestAnimationFrame", "avalonia.ts")] + public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); + + [JSImport("Canvas.setCanvasSize", "avalonia.ts")] + public static partial void SetCanvasSize(JSObject canvas, int height, int width); + + [JSImport("Canvas.initGL", "avalonia.ts")] + private static partial JSObject InitGL( + JSObject canvas, + string canvasId, + [JSMarshalAs] Action renderFrameCallback); +} diff --git a/src/Web/Avalonia.Web/Interop/DomHelper.cs b/src/Web/Avalonia.Web/Interop/DomHelper.cs new file mode 100644 index 0000000000..c02bc4dae3 --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/DomHelper.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices.JavaScript; + +namespace Avalonia.Web.Interop; + +internal static partial class DomHelper +{ + [JSImport("globalThis.document.getElementById")] + internal static partial JSObject? GetElementById(string id); + + [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia.ts")] + public static partial JSObject CreateAvaloniaHost(JSObject element); + + [JSImport("AvaloniaDOM.addClass", "avalonia.ts")] + public static partial void AddCssClass(JSObject element, string className); + + [JSImport("SizeWatcher.observe", "avalonia.ts")] + public static partial JSObject ObserveSize( + JSObject canvas, + string canvasId, + [JSMarshalAs>] + Action onSizeChanged); + + [JSImport("DpiWatcher.start", "avalonia.ts")] + 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 new file mode 100644 index 0000000000..b32b6566f2 --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.InteropServices.JavaScript; +using System.Threading.Tasks; + +namespace Avalonia.Web.Interop; + +internal static partial class InputHelper +{ + [JSImport("InputHelper.subscribeKeyEvents", "avalonia.ts")] + public static partial void SubscribeKeyEvents( + JSObject htmlElement, + [JSMarshalAs>] + Func keyDown, + [JSMarshalAs>] + Func keyUp); + + [JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")] + public static partial void SubscribeTextEvents( + JSObject htmlElement, + [JSMarshalAs>] + Func onInput, + [JSMarshalAs>] + Func onCompositionStart, + [JSMarshalAs>] + Func onCompositionUpdate, + [JSMarshalAs>] + Func onCompositionEnd); + + [JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")] + public static partial void SubscribePointerEvents( + JSObject htmlElement, + [JSMarshalAs>] + Func pointerMove, + [JSMarshalAs>] + Func pointerDown, + [JSMarshalAs>] + Func pointerUp, + [JSMarshalAs>] + Func wheel); + + + [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")] + public static partial void SubscribeInputEvents( + JSObject htmlElement, + [JSMarshalAs>] + Func input); + + + [JSImport("InputHelper.clearInput", "avalonia.ts")] + public static partial void ClearInputElement(JSObject htmlElement); + + [JSImport("InputHelper.isInputElement", "avalonia.ts")] + public static partial void IsInputElement(JSObject htmlElement); + + [JSImport("InputHelper.focusElement", "avalonia.ts")] + public static partial void FocusElement(JSObject htmlElement); + + [JSImport("InputHelper.setCursor", "avalonia.ts")] + public static partial void SetCursor(JSObject htmlElement, string kind); + + [JSImport("InputHelper.hide", "avalonia.ts")] + public static partial void HideElement(JSObject htmlElement); + + [JSImport("InputHelper.show", "avalonia.ts")] + public static partial void ShowElement(JSObject htmlElement); + + [JSImport("InputHelper.setSurroundingText", "avalonia.ts")] + public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end); + + [JSImport("InputHelper.setBounds", "avalonia.ts")] + public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret); + + [JSImport("globalThis.navigator.clipboard.readText")] + public static partial Task ReadClipboardTextAsync(); + + [JSImport("globalThis.navigator.clipboard.writeText")] + public static partial Task WriteClipboardTextAsync(string text); +} diff --git a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs new file mode 100644 index 0000000000..5cc86bf622 --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices.JavaScript; + +namespace Avalonia.Web.Interop; + +internal static partial class NativeControlHostHelper +{ + [JSImport("NativeControlHost.createDefaultChild", "avalonia.ts")] + internal static partial JSObject CreateDefaultChild(JSObject? parent); + + [JSImport("NativeControlHost.createAttachment", "avalonia.ts")] + internal static partial JSObject CreateAttachment(); + + [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia.ts")] + internal static partial void InitializeWithChildHandle(JSObject element, JSObject child); + + [JSImport("NativeControlHost.attachTo", "avalonia.ts")] + internal static partial void AttachTo(JSObject element, JSObject? host); + + [JSImport("NativeControlHost.showInBounds", "avalonia.ts")] + internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height); + + [JSImport("NativeControlHost.hideWithSize", "avalonia.ts")] + internal static partial void HideWithSize(JSObject element, double width, double height); + + [JSImport("NativeControlHost.releaseChild", "avalonia.ts")] + 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 new file mode 100644 index 0000000000..4dc55fd959 --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/StorageHelper.cs @@ -0,0 +1,55 @@ +using System.Runtime.InteropServices.JavaScript; +using System.Threading.Tasks; + +namespace Avalonia.Web.Interop; + +internal static partial class StorageHelper +{ + [JSImport("Caniuse.canShowOpenFilePicker", "avalonia.ts")] + public static partial bool CanShowOpenFilePicker(); + + [JSImport("Caniuse.canShowSaveFilePicker", "avalonia.ts")] + public static partial bool CanShowSaveFilePicker(); + + [JSImport("Caniuse.canShowDirectoryPicker", "avalonia.ts")] + public static partial bool CanShowDirectoryPicker(); + + [JSImport("StorageProvider.selectFolderDialog", "storage.ts")] + public static partial Task SelectFolderDialog(JSObject? startIn); + + [JSImport("StorageProvider.openFileDialog", "storage.ts")] + public static partial Task OpenFileDialog(JSObject? startIn, bool multiple, + [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); + + [JSImport("StorageProvider.saveFileDialog", "storage.ts")] + public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName, + [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); + + [JSImport("StorageProvider.openBookmark", "storage.ts")] + public static partial Task OpenBookmark(string key); + + [JSImport("StorageItem.saveBookmark", "storage.ts")] + public static partial Task SaveBookmark(JSObject item); + + [JSImport("StorageItem.deleteBookmark", "storage.ts")] + public static partial Task DeleteBookmark(JSObject item); + + [JSImport("StorageItem.getProperties", "storage.ts")] + public static partial Task GetProperties(JSObject item); + + [JSImport("StorageItem.openWrite", "storage.ts")] + public static partial Task OpenWrite(JSObject item); + + [JSImport("StorageItem.openRead", "storage.ts")] + public static partial Task OpenRead(JSObject item); + + [JSImport("StorageItem.getItems", "storage.ts")] + [return: JSMarshalAs>] + public static partial Task GetItems(JSObject item); + + [JSImport("StorageItems.itemsArray", "storage.ts")] + public static partial JSObject[] ItemsArray(JSObject item); + + [JSImport("StorageProvider.createAcceptType", "storage.ts")] + 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 new file mode 100644 index 0000000000..0a83bbb871 --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices.JavaScript; +using System.Threading.Tasks; + +namespace Avalonia.Web.Storage; + +/// +/// Set of FileSystemWritableFileStream and Blob methods. +/// +internal static partial class StreamHelper +{ + [JSImport("StreamHelper.seek", "avalonia.ts")] + public static partial void Seek(JSObject stream, [JSMarshalAs] long position); + + [JSImport("StreamHelper.truncate", "avalonia.ts")] + public static partial void Truncate(JSObject stream, [JSMarshalAs] long size); + + [JSImport("StreamHelper.write", "avalonia.ts")] + public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data); + + [JSImport("StreamHelper.close", "avalonia.ts")] + public static partial Task CloseAsync(JSObject stream); + + [JSImport("StreamHelper.byteLength", "avalonia.ts")] + [return: JSMarshalAs] + public static partial long ByteLength(JSObject stream); + + [JSImport("StreamHelper.sliceArrayBuffer", "avalonia.ts")] + private static partial Task SliceToArrayBuffer(JSObject stream, [JSMarshalAs] long offset, int count); + + [JSImport("StreamHelper.toMemoryView", "avalonia.ts")] + [return: JSMarshalAs>] + private static partial byte[] ArrayBufferToMemoryView(JSObject stream); + + public static async Task SliceAsync(JSObject stream, long offset, int count) + { + using var buffer = await SliceToArrayBuffer(stream, offset, count); + return ArrayBufferToMemoryView(buffer); + } +} diff --git a/src/Web/Avalonia.Web/JSObjectControlHandle.cs b/src/Web/Avalonia.Web/JSObjectControlHandle.cs new file mode 100644 index 0000000000..e56ca123eb --- /dev/null +++ b/src/Web/Avalonia.Web/JSObjectControlHandle.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.InteropServices.JavaScript; + +using Avalonia.Controls.Platform; + +namespace Avalonia.Web; + +public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle +{ + internal const string ElementReferenceDescriptor = "JSObject"; + + public JSObjectControlHandle(JSObject reference) + { + Object = reference; + } + + public JSObject Object { get; } + + public IntPtr Handle => throw new NotSupportedException(); + + public string? HandleDescriptor => ElementReferenceDescriptor; + + public void Destroy() + { + if (Object is JSObject inProcess && !inProcess.IsDisposed) + { + inProcess.Dispose(); + } + } +} diff --git a/src/Web/Avalonia.Web/Keycodes.cs b/src/Web/Avalonia.Web/Keycodes.cs new file mode 100644 index 0000000000..d1185f6e45 --- /dev/null +++ b/src/Web/Avalonia.Web/Keycodes.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; + +using Avalonia.Input; + +namespace Avalonia.Web +{ + 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/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs new file mode 100644 index 0000000000..774848e19f --- /dev/null +++ b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs @@ -0,0 +1,18 @@ +using System; +using System.Diagnostics; +using Avalonia.Rendering; + +namespace Avalonia.Web +{ + 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/Skia/BrowserSkiaGpu.cs b/src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs new file mode 100644 index 0000000000..f80838232b --- /dev/null +++ b/src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Avalonia.Skia; + +namespace Avalonia.Web.Skia +{ + public class BrowserSkiaGpu : ISkiaGpu + { + public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable surfaces) + { + foreach (var surface in surfaces) + { + if (surface is BrowserSkiaSurface browserSkiaSurface) + { + return new BrowserSkiaGpuRenderTarget(browserSkiaSurface); + } + } + + return null; + } + + public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session) + { + return null; + } + } +} diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs b/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs new file mode 100644 index 0000000000..a7f7d9db3d --- /dev/null +++ b/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs @@ -0,0 +1,36 @@ +using Avalonia.Skia; +using SkiaSharp; + +namespace Avalonia.Web.Skia +{ + internal class BrowserSkiaGpuRenderSession : ISkiaGpuRenderSession + { + private readonly SKSurface _surface; + + public BrowserSkiaGpuRenderSession(BrowserSkiaSurface browserSkiaSurface, GRBackendRenderTarget renderTarget) + { + _surface = SKSurface.Create(browserSkiaSurface.Context, renderTarget, browserSkiaSurface.Origin, browserSkiaSurface.ColorType); + + GrContext = browserSkiaSurface.Context; + + ScaleFactor = browserSkiaSurface.Scaling; + + SurfaceOrigin = browserSkiaSurface.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/Skia/BrowserSkiaGpuRenderTarget.cs b/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs new file mode 100644 index 0000000000..dba9b34166 --- /dev/null +++ b/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs @@ -0,0 +1,39 @@ +using Avalonia.Skia; +using SkiaSharp; + +namespace Avalonia.Web.Skia +{ + internal class BrowserSkiaGpuRenderTarget : ISkiaGpuRenderTarget + { + private readonly GRBackendRenderTarget _renderTarget; + private readonly BrowserSkiaSurface _browserSkiaSurface; + private readonly PixelSize _size; + + public BrowserSkiaGpuRenderTarget(BrowserSkiaSurface browserSkiaSurface) + { + _size = browserSkiaSurface.Size; + + var glFbInfo = new GRGlFramebufferInfo(browserSkiaSurface.GlInfo.FboId, browserSkiaSurface.ColorType.ToGlSizedFormat()); + { + _browserSkiaSurface = browserSkiaSurface; + _renderTarget = new GRBackendRenderTarget( + (int)(browserSkiaSurface.Size.Width * browserSkiaSurface.Scaling), + (int)(browserSkiaSurface.Size.Height * browserSkiaSurface.Scaling), + browserSkiaSurface.GlInfo.Samples, + browserSkiaSurface.GlInfo.Stencils, glFbInfo); + } + } + + public void Dispose() + { + _renderTarget.Dispose(); + } + + public ISkiaGpuRenderSession BeginRenderingSession() + { + return new BrowserSkiaGpuRenderSession(_browserSkiaSurface, _renderTarget); + } + + public bool IsCorrupted => _browserSkiaSurface.Size != _size; + } +} diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs b/src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs new file mode 100644 index 0000000000..c7005583ac --- /dev/null +++ b/src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs @@ -0,0 +1,88 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using Avalonia.Skia; +using SkiaSharp; + +namespace Avalonia.Web.Skia +{ + internal class BrowserSkiaRasterSurface : IBrowserSkiaSurface, 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 BrowserSkiaRasterSurface( + 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/Skia/BrowserSkiaSurface.cs b/src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs new file mode 100644 index 0000000000..27a206c0ec --- /dev/null +++ b/src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs @@ -0,0 +1,30 @@ +using Avalonia.Web.Interop; +using SkiaSharp; + +namespace Avalonia.Web.Skia +{ + internal class BrowserSkiaSurface : IBrowserSkiaSurface + { + public BrowserSkiaSurface(GRContext context, 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 GLInfo GlInfo { get; set; } + } +} diff --git a/src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs b/src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs new file mode 100644 index 0000000000..7301ae45cd --- /dev/null +++ b/src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Web.Skia +{ + internal interface IBrowserSkiaSurface + { + public PixelSize Size { get; set; } + + public double Scaling { get; set; } + } +} diff --git a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs new file mode 100644 index 0000000000..640c2fd745 --- /dev/null +++ b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Runtime.InteropServices.JavaScript; +using System.Threading; +using System.Threading.Tasks; + +namespace Avalonia.Web.Storage; + +[System.Runtime.Versioning.SupportedOSPlatform("browser")] +internal class BlobReadableStream : Stream +{ + private JSObject? _jSReference; + private long _position; + private readonly long _length; + + public BlobReadableStream(JSObject jsStreamReference) + { + _jSReference = jsStreamReference; + _position = 0; + _length = StreamHelper.ByteLength(JSReference); + } + + private JSObject JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(WriteableStream)); + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => _length; + + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + public override void Flush() { } + + public override long Seek(long offset, SeekOrigin origin) + { + return _position = origin switch + { + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => offset + }; + } + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + throw new InvalidOperationException("Browser supports only ReadAsync"); + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken); + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + var numBytesToRead = (int)Math.Min(buffer.Length, Length - _position); + var bytesRead = await StreamHelper.SliceAsync(JSReference, _position, numBytesToRead); + if (bytesRead.Length != numBytesToRead) + { + throw new EndOfStreamException("Failed to read the requested number of bytes from the stream."); + } + + _position += bytesRead.Length; + bytesRead.CopyTo(buffer); + + return bytesRead.Length; + } + + protected override void Dispose(bool disposing) + { + if (_jSReference is { } jsReference) + { + _jSReference = null; + jsReference.Dispose(); + } + } +} diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs new file mode 100644 index 0000000000..55ae2bdef0 --- /dev/null +++ b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.JavaScript; +using System.Runtime.Versioning; +using System.Threading.Tasks; + +using Avalonia.Platform.Storage; +using Avalonia.Web.Interop; + +namespace Avalonia.Web.Storage; + +internal record FilePickerAcceptType(string Description, IReadOnlyDictionary> Accept); + +[SupportedOSPlatform("browser")] +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")); + + public bool CanOpen => StorageHelper.CanShowOpenFilePicker(); + public bool CanSave => StorageHelper.CanShowSaveFilePicker(); + public bool CanPickFolder => StorageHelper.CanShowDirectoryPicker(); + + public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) + { + _ = await _lazyModule.Value; + var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; + + var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter); + + try + { + using var items = await StorageHelper.OpenFileDialog(startIn, options.AllowMultiple, types, exludeAll); + if (items is null) + { + return Array.Empty(); + } + + var itemsArray = StorageHelper.ItemsArray(items); + return itemsArray.Select(item => new JSStorageFile(item)).ToArray(); + } + catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) + { + return Array.Empty(); + } + finally + { + if (types is not null) + { + foreach (var type in types) + { + type.Dispose(); + } + } + } + } + + public async Task SaveFilePickerAsync(FilePickerSaveOptions options) + { + _ = await _lazyModule.Value; + var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; + + var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices); + + try + { + var item = await StorageHelper.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; + } + finally + { + if (types is not null) + { + foreach (var type in types) + { + type.Dispose(); + } + } + } + } + + public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) + { + _ = await _lazyModule.Value; + var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; + + try + { + var item = await StorageHelper.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) + { + _ = 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; + var item = await StorageHelper.OpenBookmark(bookmark); + return item is not null ? new JSStorageFolder(item) : null; + } + + private static (JSObject[]? types, bool excludeAllOption) ConvertFileTypes(IEnumerable? input) + { + var types = input? + .Where(t => t.MimeTypes?.Any() == true && t != FilePickerFileTypes.All) + .Select(t => StorageHelper.CreateAcceptType(t.Name, t.MimeTypes!.ToArray())) + .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 JSObject? _fileHandle; + + protected JSStorageItem(JSObject fileHandle) + { + _fileHandle = fileHandle ?? throw new ArgumentNullException(nameof(fileHandle)); + } + + internal JSObject FileHandle => _fileHandle ?? throw new ObjectDisposedException(nameof(JSStorageItem)); + + public string Name => FileHandle.GetPropertyAsString("name") ?? string.Empty; + + public bool TryGetUri([NotNullWhen(true)] out Uri? uri) + { + uri = new Uri(Name, UriKind.Relative); + return false; + } + + public async Task GetBasicPropertiesAsync() + { + using var properties = await StorageHelper.GetProperties(FileHandle); + var size = (long?)properties?.GetPropertyAsDouble("Size"); + var lastModified = (long?)properties?.GetPropertyAsDouble("LastModified"); + + return new StorageItemProperties( + (ulong?)size, + dateCreated: null, + dateModified: lastModified > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(lastModified.Value) : null); + } + + public bool CanBookmark => true; + + public Task SaveBookmarkAsync() + { + return StorageHelper.SaveBookmark(FileHandle); + } + + public Task GetParentAsync() + { + return Task.FromResult(null); + } + + public Task ReleaseBookmarkAsync() + { + return StorageHelper.DeleteBookmark(FileHandle); + } + + public void Dispose() + { + _fileHandle?.Dispose(); + _fileHandle = null; + } +} + +internal class JSStorageFile : JSStorageItem, IStorageBookmarkFile +{ + public JSStorageFile(JSObject fileHandle) : base(fileHandle) + { + } + + public bool CanOpenRead => true; + public async Task OpenReadAsync() + { + try + { + var blob = await StorageHelper.OpenRead(FileHandle); + return new BlobReadableStream(blob); + } + catch (JSException ex) when (ex.Message == BrowserStorageProvider.NoPermissionsMessage) + { + throw new UnauthorizedAccessException("User denied permissions to open the file", ex); + } + } + + public bool CanOpenWrite => true; + public async Task OpenWriteAsync() + { + try + { + using var properties = await StorageHelper.GetProperties(FileHandle); + var streamWriter = await StorageHelper.OpenWrite(FileHandle); + var size = (long?)properties?.GetPropertyAsDouble("Size") ?? 0; + + return new WriteableStream(streamWriter, size); + } + catch (JSException ex) when (ex.Message == BrowserStorageProvider.NoPermissionsMessage) + { + throw new UnauthorizedAccessException("User denied permissions to open the file", ex); + } + } +} + +internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder +{ + public JSStorageFolder(JSObject fileHandle) : base(fileHandle) + { + } + + public async Task> GetItemsAsync() + { + using var items = await StorageHelper.GetItems(FileHandle); + if (items is null) + { + return Array.Empty(); + } + + var itemsArray = StorageHelper.ItemsArray(items); + + return itemsArray + .Select(reference => reference.GetPropertyAsString("kind") 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/Storage/WriteableStream.cs b/src/Web/Avalonia.Web/Storage/WriteableStream.cs new file mode 100644 index 0000000000..f9699fef7a --- /dev/null +++ b/src/Web/Avalonia.Web/Storage/WriteableStream.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using System.Runtime.InteropServices.JavaScript; +using System.Threading; +using System.Threading.Tasks; + +namespace Avalonia.Web.Storage; + +[System.Runtime.Versioning.SupportedOSPlatform("browser")] +// Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream +internal sealed class WriteableStream : Stream +{ + private JSObject? _jSReference; + + // Unfortunatelly we can't read current length/position, so we need to keep it C#-side only. + private long _length, _position; + + internal WriteableStream(JSObject jSReference, long initialLength) + { + _jSReference = jSReference; + _length = initialLength; + } + + private JSObject JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(WriteableStream)); + + 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 + }; + StreamHelper.Seek(JSReference, 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; + } + + StreamHelper.Truncate(JSReference, value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new InvalidOperationException("Browser supports only WriteAsync"); + } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(WriteAsyncInternal(buffer.ToArray(), cancellationToken)); + } + + private Task WriteAsyncInternal(byte[] buffer, CancellationToken _) + { + _position += buffer.Length; + + return StreamHelper.WriteAsync(JSReference, buffer); + } + + protected override void Dispose(bool disposing) + { + if (_jSReference is { } jsReference) + { + _jSReference = null; + try + { + _ = StreamHelper.CloseAsync(jsReference); + } + finally + { + jsReference.Dispose(); + } + } + } + + public override async ValueTask DisposeAsync() + { + if (_jSReference is { } jsReference) + { + _jSReference = null; + try + { + await StreamHelper.CloseAsync(jsReference); + } + finally + { + jsReference.Dispose(); + } + } + } +} diff --git a/src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs b/src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs new file mode 100644 index 0000000000..19f36403ad --- /dev/null +++ b/src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs @@ -0,0 +1,68 @@ +using System; +using Avalonia.Controls.Embedding; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; + +namespace Avalonia.Web +{ + internal class WebEmbeddableControlRoot : EmbeddableControlRoot + { + class SplashScreenCloseCustomDrawingOperation : ICustomDrawOperation + { + private bool _hasRendered; + private Action _onFirstRender; + + public SplashScreenCloseCustomDrawingOperation(Action onFirstRender) + { + _onFirstRender = onFirstRender; + } + + public Rect Bounds => Rect.Empty; + + public bool HasRendered => _hasRendered; + + public void Dispose() + { + + } + + public bool Equals(ICustomDrawOperation? other) + { + return false; + } + + public bool HitTest(Point p) + { + return false; + } + + public void Render(IDrawingContextImpl context) + { + _hasRendered = true; + _onFirstRender(); + } + } + + public WebEmbeddableControlRoot(ITopLevelImpl impl, Action onFirstRender) : base(impl) + { + _splashCloseOp = new SplashScreenCloseCustomDrawingOperation(() => + { + _splashCloseOp = null; + onFirstRender(); + }); + } + + private SplashScreenCloseCustomDrawingOperation? _splashCloseOp; + + public override void Render(DrawingContext context) + { + base.Render(context); + + if (_splashCloseOp != null) + { + context.Custom(_splashCloseOp); + } + } + } +} diff --git a/src/Web/Avalonia.Web/WinStubs.cs b/src/Web/Avalonia.Web/WinStubs.cs new file mode 100644 index 0000000000..b0961115fe --- /dev/null +++ b/src/Web/Avalonia.Web/WinStubs.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.IO; +using Avalonia.Platform; + +#nullable enable + +namespace Avalonia.Web +{ + 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/WindowingPlatform.cs b/src/Web/Avalonia.Web/WindowingPlatform.cs new file mode 100644 index 0000000000..3f14680241 --- /dev/null +++ b/src/Web/Avalonia.Web/WindowingPlatform.cs @@ -0,0 +1,105 @@ +using System; +using System.Threading; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Threading; + +namespace Avalonia.Web +{ + public class BrowserWindowingPlatform : 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("BrowserWindowingPlatform not registered."); + + public static void Register() + { + var instance = new BrowserWindowingPlatform(); + 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; // Browser is single threaded. + } + } + + public event Action? Signaled; + + private static IRuntimePlatform GetRuntimePlatform() + { + return AvaloniaLocator.Current.GetRequiredService(); + } + } +} diff --git a/src/Web/Avalonia.Web/interop.js b/src/Web/Avalonia.Web/interop.js new file mode 100644 index 0000000000..c7ae3a56c7 --- /dev/null +++ b/src/Web/Avalonia.Web/interop.js @@ -0,0 +1,13 @@ +var LibraryExample = { + // Internal functions + $EXAMPLE: { + internal_func: function () { + } + }, + InterceptGLObject: function () { + globalThis.AvaloniaGL = GL + } +} + +autoAddDeps(LibraryExample, '$EXAMPLE') +mergeInto(LibraryManager.library, LibraryExample) diff --git a/src/Web/Avalonia.Web/webapp/.eslintrc.json b/src/Web/Avalonia.Web/webapp/.eslintrc.json new file mode 100644 index 0000000000..4b7e24987f --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/.eslintrc.json @@ -0,0 +1,47 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": "standard-with-typescript", + "overrides": [], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "project": [ + "tsconfig.json" + ] + }, + "rules": { + "indent": [ + "warn", + 4 + ], + "@typescript-eslint/indent": [ + "warn", + 4 + ], + "quotes": ["warn", "double"], + "semi": ["error", "always"], + "@typescript-eslint/quotes": ["warn", "double"], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/semi": ["error", "always"], + "@typescript-eslint/member-delimiter-style": [ + "error", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ] + }, + "ignorePatterns": ["types/*"] +} diff --git a/src/Web/Avalonia.Web/webapp/build.js b/src/Web/Avalonia.Web/webapp/build.js new file mode 100644 index 0000000000..6b6df4c300 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/build.js @@ -0,0 +1,16 @@ +require("esbuild").build({ + entryPoints: [ + "./modules/avalonia.ts", + "./modules/storage.ts" + ], + outdir: "../wwwroot", + bundle: true, + minify: false, + format: "esm", + target: "es2016", + platform: "browser", + sourcemap: "linked", + loader: { ".ts": "ts" } +}) + .then(() => console.log("⚡ Done")) + .catch(() => process.exit(1)); diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts new file mode 100644 index 0000000000..98c912f940 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts @@ -0,0 +1,20 @@ +import { RuntimeAPI } from "../types/dotnet"; +import { SizeWatcher, DpiWatcher, Canvas } from "./avalonia/canvas"; +import { InputHelper } from "./avalonia/input"; +import { AvaloniaDOM } from "./avalonia/dom"; +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 + }); +} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts new file mode 100644 index 0000000000..6dedcb724f --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts @@ -0,0 +1,13 @@ +export class Caniuse { + public static canShowOpenFilePicker(): boolean { + return typeof window.showOpenFilePicker !== "undefined"; + } + + public static canShowSaveFilePicker(): boolean { + return typeof window.showSaveFilePicker !== "undefined"; + } + + public static canShowDirectoryPicker(): boolean { + return typeof window.showDirectoryPicker !== "undefined"; + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts new file mode 100644 index 0000000000..017ca58b8f --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -0,0 +1,303 @@ +interface SKGLViewInfo { + context: WebGLRenderingContext | WebGL2RenderingContext | undefined; + fboId: number; + stencil: number; + sample: number; + depth: number; +} + +type CanvasElement = { + Canvas: Canvas | undefined; +} & HTMLCanvasElement; + +export class Canvas { + static elements: Map; + + htmlCanvas: HTMLCanvasElement; + glInfo?: SKGLViewInfo; + renderFrameCallback: () => void; + renderLoopEnabled: boolean = false; + renderLoopRequest: number = 0; + newWidth?: number; + newHeight?: number; + + public static initGL(element: HTMLCanvasElement, elementId: string, renderFrameCallback: () => void): SKGLViewInfo | null { + const view = Canvas.init(true, element, elementId, renderFrameCallback); + if (!view || !view.glInfo) { + return null; + } + + return view.glInfo; + } + + static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, renderFrameCallback: () => void): Canvas | null { + const htmlCanvas = element as CanvasElement; + if (!htmlCanvas) { + console.error("No canvas element was provided."); + return null; + } + + if (!Canvas.elements) { + Canvas.elements = new Map(); + } + Canvas.elements.set(elementId, element); + + const view = new Canvas(useGL, element, renderFrameCallback); + + htmlCanvas.Canvas = view; + + return view; + } + + public constructor(useGL: boolean, element: HTMLCanvasElement, renderFrameCallback: () => void) { + this.htmlCanvas = element; + this.renderFrameCallback = renderFrameCallback; + + if (useGL) { + const ctx = Canvas.createWebGLContext(element); + if (!ctx) { + console.error("Failed to create WebGL context"); + return; + } + + const GL = (globalThis as any).AvaloniaGL; + + // make current + GL.makeContextCurrent(ctx); + + const GLctx = GL.currentContext.GLctx as WebGLRenderingContext; + + // 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 setEnableRenderLoop(enable: boolean): void { + 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 requestAnimationFrame(renderLoop?: boolean): void { + // 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) { + const GL = (globalThis as any).AvaloniaGL; + // 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(); + this.renderLoopRequest = 0; + + // we may want to draw the next frame + if (this.renderLoopEnabled) { + this.requestAnimationFrame(); + } + }); + } + + public setCanvasSize(width: number, height: number): void { + 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) { + const GL = (globalThis as any).AvaloniaGL; + // make current + GL.makeContextCurrent(this.glInfo.context); + } + } + + public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void { + const htmlCanvas = element as CanvasElement; + if (!htmlCanvas || !htmlCanvas.Canvas) { + return; + } + + htmlCanvas.Canvas.setCanvasSize(width, height); + } + + public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean): void { + const htmlCanvas = element as CanvasElement; + if (!htmlCanvas || !htmlCanvas.Canvas) { + return; + } + + htmlCanvas.Canvas.requestAnimationFrame(renderLoop); + } + + 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 + }; + + const GL = (globalThis as any).AvaloniaGL; + + 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; + } +} + +type SizeWatcherElement = { + SizeWatcher: SizeWatcherInstance; +} & HTMLElement; + +interface SizeWatcherInstance { + callback: (width: number, height: number) => void; +} + +export class SizeWatcher { + static observer: ResizeObserver; + static elements: Map; + + public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void { + if (!element || !callback) { + return; + } + + SizeWatcher.init(); + + const watcherElement = element as SizeWatcherElement; + watcherElement.SizeWatcher = { + callback + }; + + SizeWatcher.elements.set(elementId, element); + SizeWatcher.observer.observe(element); + + SizeWatcher.invoke(element); + } + + public static unobserve(elementId: string): void { + if (!elementId || !SizeWatcher.observer) { + return; + } + + const element = SizeWatcher.elements.get(elementId); + if (element) { + SizeWatcher.elements.delete(elementId); + SizeWatcher.observer.unobserve(element); + } + } + + static init(): void { + if (SizeWatcher.observer) { + return; + } + + SizeWatcher.elements = new Map(); + SizeWatcher.observer = new ResizeObserver((entries) => { + for (const entry of entries) { + SizeWatcher.invoke(entry.target); + } + }); + } + + static invoke(element: Element): void { + const watcherElement = element as SizeWatcherElement; + const instance = watcherElement.SizeWatcher; + + if (!instance || !instance.callback) { + return; + } + + return instance.callback(element.clientWidth, element.clientHeight); + } +} + +export class DpiWatcher { + static lastDpi: number; + static timerId: number; + static callback: (old: number, newdpi: number) => void; + + public static getDpi(): number { + return window.devicePixelRatio; + } + + public static start(callback: (old: number, newdpi: number) => void): number { + DpiWatcher.lastDpi = window.devicePixelRatio; + DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000); + DpiWatcher.callback = callback; + + return DpiWatcher.lastDpi; + } + + public static stop(): void { + window.clearInterval(DpiWatcher.timerId); + } + + static update(): void { + if (!DpiWatcher.callback) { + return; + } + + const currentDpi = window.devicePixelRatio; + const lastDpi = DpiWatcher.lastDpi; + DpiWatcher.lastDpi = currentDpi; + + if (Math.abs(lastDpi - currentDpi) > 0.001) { + DpiWatcher.callback(lastDpi, currentDpi); + } + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts new file mode 100644 index 0000000000..546b41669f --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts @@ -0,0 +1,149 @@ +// 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?.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; + } +} + +const 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/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts new file mode 100644 index 0000000000..943b8330d5 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -0,0 +1,60 @@ +export class AvaloniaDOM { + public static addClass(element: HTMLElement, className: string): void { + element.classList.add(className); + } + + static createAvaloniaHost(host: HTMLElement) { + // Root element + host.classList.add("avalonia-container"); + host.tabIndex = 0; + host.oncontextmenu = function () { return false; }; + + // Rendering target canvas + const canvas = document.createElement("canvas"); + canvas.classList.add("avalonia-canvas"); + canvas.style.backgroundColor = "#ccc"; + canvas.style.width = "100%"; + canvas.style.height = "100%"; + canvas.style.position = "absolute"; + + // Native controls host + const nativeHost = document.createElement("div"); + nativeHost.classList.add("avalonia-native-host"); + nativeHost.style.left = "0px"; + nativeHost.style.top = "0px"; + nativeHost.style.width = "100%"; + nativeHost.style.height = "100%"; + nativeHost.style.position = "absolute"; + + // IME + const inputElement = document.createElement("input"); + inputElement.classList.add("avalonia-input-element"); + inputElement.autocapitalize = "none"; + inputElement.type = "text"; + inputElement.spellcheck = false; + inputElement.style.padding = "0"; + inputElement.style.margin = "0"; + inputElement.style.position = "absolute"; + inputElement.style.overflow = "hidden"; + inputElement.style.borderStyle = "hidden"; + inputElement.style.outline = "none"; + inputElement.style.background = "transparent"; + inputElement.style.color = "transparent"; + inputElement.style.display = "none"; + inputElement.style.height = "20px"; + inputElement.onpaste = function () { return false; }; + inputElement.oncopy = function () { return false; }; + inputElement.oncut = function () { return false; }; + + host.prepend(inputElement); + host.prepend(nativeHost); + host.prepend(canvas); + + return { + host, + canvas, + nativeHost, + inputElement + }; + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts new file mode 100644 index 0000000000..768414ccab --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -0,0 +1,204 @@ +import { CaretHelper } from "./caretHelper"; + +enum RawInputModifiers { + None = 0, + Alt = 1, + Control = 2, + Shift = 4, + Meta = 8, + + LeftMouseButton = 16, + RightMouseButton = 32, + MiddleMouseButton = 64, + XButton1MouseButton = 128, + XButton2MouseButton = 256, + KeyboardMask = Alt | Control | Shift | Meta, + + PenInverted = 512, + PenEraser = 1024, + PenBarrelButton = 2048 +} + +export class InputHelper { + public static subscribeKeyEvents( + element: HTMLInputElement, + keyDownCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean, + keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean) { + const keyDownHandler = (args: KeyboardEvent) => { + if (keyDownCallback(args.code, args.key, this.getModifiers(args))) { + args.preventDefault(); + } + }; + element.addEventListener("keydown", keyDownHandler); + + const keyUpHandler = (args: KeyboardEvent) => { + if (keyUpCallback(args.code, args.key, this.getModifiers(args))) { + args.preventDefault(); + } + }; + + element.addEventListener("keyup", keyUpHandler); + + return () => { + element.removeEventListener("keydown", keyDownHandler); + element.removeEventListener("keyup", keyUpHandler); + }; + } + + public static subscribeTextEvents( + element: HTMLInputElement, + inputCallback: (type: string, data: string | null) => boolean, + compositionStartCallback: (args: CompositionEvent) => boolean, + compositionUpdateCallback: (args: CompositionEvent) => boolean, + compositionEndCallback: (args: CompositionEvent) => boolean) { + const inputHandler = (args: Event) => { + const inputEvent = args as InputEvent; + + // todo check cast + if (inputCallback(inputEvent.type, inputEvent.data)) { + args.preventDefault(); + } + }; + element.addEventListener("input", inputHandler); + + const compositionStartHandler = (args: CompositionEvent) => { + if (compositionStartCallback(args)) { + args.preventDefault(); + } + }; + element.addEventListener("compositionstart", compositionStartHandler); + + const compositionUpdateHandler = (args: CompositionEvent) => { + if (compositionUpdateCallback(args)) { + args.preventDefault(); + } + }; + element.addEventListener("compositionupdate", compositionUpdateHandler); + + const compositionEndHandler = (args: CompositionEvent) => { + if (compositionEndCallback(args)) { + args.preventDefault(); + } + }; + element.addEventListener("compositionend", compositionEndHandler); + + return () => { + element.removeEventListener("input", inputHandler); + element.removeEventListener("compositionstart", compositionStartHandler); + element.removeEventListener("compositionupdate", compositionUpdateHandler); + element.removeEventListener("compositionend", compositionEndHandler); + }; + } + + public static subscribePointerEvents( + element: HTMLInputElement, + pointerMoveCallback: (args: PointerEvent) => boolean, + pointerDownCallback: (args: PointerEvent) => boolean, + pointerUpCallback: (args: PointerEvent) => boolean, + wheelCallback: (args: WheelEvent) => boolean + ) { + const pointerMoveHandler = (args: PointerEvent) => { + if (pointerMoveCallback(args)) { + args.preventDefault(); + } + }; + + const pointerDownHandler = (args: PointerEvent) => { + if (pointerDownCallback(args)) { + args.preventDefault(); + } + }; + + const pointerUpHandler = (args: PointerEvent) => { + if (pointerUpCallback(args)) { + args.preventDefault(); + } + }; + + const wheelHandler = (args: WheelEvent) => { + if (wheelCallback(args)) { + args.preventDefault(); + } + }; + + element.addEventListener("pointermove", pointerMoveHandler); + element.addEventListener("pointerdown", pointerDownHandler); + element.addEventListener("pointerup", pointerUpHandler); + element.addEventListener("wheel", wheelHandler); + + return () => { + element.removeEventListener("pointerover", pointerMoveHandler); + element.removeEventListener("pointerdown", pointerDownHandler); + element.removeEventListener("pointerup", pointerUpHandler); + element.removeEventListener("wheel", wheelHandler); + }; + } + + public static subscribeInputEvents( + element: HTMLInputElement, + inputCallback: (value: string) => boolean + ) { + const inputHandler = (args: Event) => { + if (inputCallback((args as any).value)) { + args.preventDefault(); + } + }; + element.addEventListener("input", inputHandler); + + return () => { + element.removeEventListener("input", inputHandler); + }; + } + + public static clearInput(inputElement: HTMLInputElement) { + inputElement.value = ""; + } + + public static focusElement(inputElement: HTMLElement) { + 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"; + + const { 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 getModifiers(args: KeyboardEvent): RawInputModifiers { + let modifiers = RawInputModifiers.None; + + if (args.ctrlKey) { modifiers |= RawInputModifiers.Control; } + if (args.altKey) { modifiers |= RawInputModifiers.Alt; } + if (args.shiftKey) { modifiers |= RawInputModifiers.Shift; } + if (args.metaKey) { modifiers |= RawInputModifiers.Meta; } + + return modifiers; + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts new file mode 100644 index 0000000000..c65d5836df --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts @@ -0,0 +1,55 @@ +class NativeControlHostTopLevelAttachment { + _child?: HTMLElement; + _host?: HTMLElement; +} + +export class NativeControlHost { + public static createDefaultChild(parent?: HTMLElement): HTMLElement { + return document.createElement("div"); + } + + public static createAttachment(): NativeControlHostTopLevelAttachment { + return new NativeControlHostTopLevelAttachment(); + } + + public static initializeWithChildHandle(element: NativeControlHostTopLevelAttachment, child: HTMLElement): void { + element._child = child; + element._child.style.position = "absolute"; + } + + public static attachTo(element: NativeControlHostTopLevelAttachment, host?: HTMLElement): void { + if (element._host && element._child) { + element._host.removeChild(element._child); + } + + element._host = host; + + if (element._host && element._child) { + element._host.appendChild(element._child); + } + } + + public static showInBounds(element: NativeControlHostTopLevelAttachment, x: number, y: number, width: number, height: number): void { + if (element._child) { + element._child.style.top = `${y}px`; + element._child.style.left = `${x}px`; + element._child.style.width = `${width}px`; + element._child.style.height = `${height}px`; + element._child.style.display = "block"; + } + } + + public static hideWithSize(element: NativeControlHostTopLevelAttachment, width: number, height: number): void { + if (element._child) { + element._child.style.width = `${width}px`; + element._child.style.height = `${height}px`; + element._child.style.display = "none"; + } + } + + public static releaseChild(element: NativeControlHostTopLevelAttachment): void { + if (element._child) { + element._child = undefined; + } + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts new file mode 100644 index 0000000000..1f2c181edc --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts @@ -0,0 +1,40 @@ +import { IMemoryView } from "../../types/dotnet"; + +export class StreamHelper { + public static async seek(stream: FileSystemWritableFileStream, position: number) { + return await stream.seek(position); + } + + public static async truncate(stream: FileSystemWritableFileStream, size: number) { + return await stream.truncate(size); + } + + public static async close(stream: FileSystemWritableFileStream) { + return await stream.close(); + } + + public static async write(stream: FileSystemWritableFileStream, span: IMemoryView) { + const array = new Uint8Array(span.byteLength); + span.copyTo(array); + + const data: WriteParams = { + type: "write", + data: array + }; + + return await stream.write(data); + } + + public static byteLength(stream: Blob) { + return stream.size; + } + + public static async sliceArrayBuffer(stream: Blob, offset: number, count: number) { + const buffer = await stream.slice(offset, offset + count).arrayBuffer(); + return new Uint8Array(buffer); + } + + public static toMemoryView(buffer: Uint8Array): Uint8Array { + return buffer; + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/storage.ts b/src/Web/Avalonia.Web/webapp/modules/storage.ts new file mode 100644 index 0000000000..8b9987afa0 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/storage.ts @@ -0,0 +1,2 @@ +export { StorageItem, StorageItems } from "./storage/storageItem"; +export { StorageProvider } from "./storage/storageProvider"; diff --git a/src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts b/src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts new file mode 100644 index 0000000000..54b095d39e --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts @@ -0,0 +1,84 @@ +class InnerDbConnection { + constructor(private readonly database: IDBDatabase) { } + + private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore { + const tx = this.database.transaction(store, mode); + return tx.objectStore(store); + } + + public async put(store: string, obj: any, key?: IDBValidKey): Promise { + const os = this.openStore(store, "readwrite"); + + return await 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 async delete(store: string, key: IDBValidKey): Promise { + const os = this.openStore(store, "readwrite"); + + return await new Promise((resolve, reject) => { + const response = os.delete(key); + response.onsuccess = () => { + resolve(); + }; + response.onerror = () => { + reject(response.error); + }; + }); + } + + public close() { + this.database.close(); + } +} + +class IndexedDbWrapper { + constructor(private readonly databaseName: string, private readonly objectStores: [string]) { + } + + public async connect(): Promise { + const conn = window.indexedDB.open(this.databaseName, 1); + + conn.onupgradeneeded = event => { + const db = (event.target as IDBRequest).result; + this.objectStores.forEach(store => { + db.createObjectStore(store); + }); + }; + + return await new Promise((resolve, reject) => { + conn.onsuccess = event => { + resolve(new InnerDbConnection((event.target as IDBRequest).result)); + }; + conn.onerror = event => { + reject((event.target as IDBRequest).error); + }; + }); + } +} + +export const fileBookmarksStore: string = "fileBookmarks"; +export const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ + fileBookmarksStore +]); diff --git a/src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts b/src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts new file mode 100644 index 0000000000..b4cf3d278b --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts @@ -0,0 +1,111 @@ +import { avaloniaDb, fileBookmarksStore } from "./indexedDb"; + +export class StorageItem { + constructor(public handle: FileSystemHandle, private readonly bookmarkId?: string) { } + + public get name(): string { + return this.handle.name; + } + + public get kind(): string { + return this.handle.kind; + } + + public static async openRead(item: StorageItem): Promise { + if (!(item.handle instanceof FileSystemFileHandle)) { + throw new Error("StorageItem is not a file"); + } + + await item.verityPermissions("read"); + + const file = await item.handle.getFile(); + return file; + } + + public static async openWrite(item: StorageItem): Promise { + if (!(item.handle instanceof FileSystemFileHandle)) { + throw new Error("StorageItem is not a file"); + } + + await item.verityPermissions("readwrite"); + + return await item.handle.createWritable({ keepExistingData: true }); + } + + public static async getProperties(item: StorageItem): Promise<{ Size: number; LastModified: number; Type: string } | null> { + const file = item.handle instanceof FileSystemFileHandle && + await item.handle.getFile(); + + if (!file) { + return null; + } + + return { + Size: file.size, + LastModified: file.lastModified, + Type: file.type + }; + } + + public static async getItems(item: StorageItem): Promise { + if (item.handle.kind !== "directory") { + return new StorageItems([]); + } + + const items: StorageItem[] = []; + for await (const [, value] of (item.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("Permissions denied"); + } + } + + public static async saveBookmark(item: StorageItem): Promise { + // If file was previously bookmarked, just return old one. + if (item.bookmarkId) { + return item.bookmarkId; + } + + const connection = await avaloniaDb.connect(); + try { + const key = await connection.put(fileBookmarksStore, item.handle, item.generateBookmarkId()); + return key as string; + } finally { + connection.close(); + } + } + + public static async deleteBookmark(item: StorageItem): Promise { + if (!item.bookmarkId) { + return; + } + + const connection = await avaloniaDb.connect(); + try { + await connection.delete(fileBookmarksStore, item.bookmarkId); + } finally { + connection.close(); + } + } + + private generateBookmarkId(): string { + return Date.now().toString(36) + Math.random().toString(36).substring(2); + } +} + +export class StorageItems { + constructor(private readonly items: StorageItem[]) { } + + public static itemsArray(instance: StorageItems): StorageItem[] { + return instance.items; + } +} diff --git a/src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts b/src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts new file mode 100644 index 0000000000..0198f92528 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts @@ -0,0 +1,70 @@ +import { avaloniaDb, fileBookmarksStore } from "./indexedDb"; +import { StorageItem, StorageItems } from "./storageItem"; + +declare global { + type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + type StartInDirectory = WellKnownDirectory | FileSystemHandle; + interface OpenFilePickerOptions { + startIn?: StartInDirectory; + } + interface SaveFilePickerOptions { + startIn?: StartInDirectory; + } +} + +export class StorageProvider { + 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(); + } + } + + public static createAcceptType(description: string, mimeTypes: string[]): FilePickerAcceptType { + const accept: Record = {}; + mimeTypes.forEach(a => { accept[a] = []; }); + return { description, accept }; + } +} diff --git a/src/Web/Avalonia.Web/webapp/package-lock.json b/src/Web/Avalonia.Web/webapp/package-lock.json new file mode 100644 index 0000000000..947f7e12e7 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/package-lock.json @@ -0,0 +1,2234 @@ +{ + "name": "avalonia.web", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.9.tgz", + "integrity": "sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.9.tgz", + "integrity": "sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", + "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", + "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/emscripten": { + "version": "1.39.6", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", + "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/wicg-file-system-access": { + "version": "2020.9.5", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz", + "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz", + "integrity": "sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/type-utils": "5.38.1", + "@typescript-eslint/utils": "5.38.1", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", + "integrity": "sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/typescript-estree": "5.38.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz", + "integrity": "sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/visitor-keys": "5.38.1" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz", + "integrity": "sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.38.1", + "@typescript-eslint/utils": "5.38.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz", + "integrity": "sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz", + "integrity": "sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/visitor-keys": "5.38.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz", + "integrity": "sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/typescript-estree": "5.38.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz", + "integrity": "sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.38.1", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.3.tgz", + "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.6", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.9.tgz", + "integrity": "sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.9", + "@esbuild/linux-loong64": "0.15.9", + "esbuild-android-64": "0.15.9", + "esbuild-android-arm64": "0.15.9", + "esbuild-darwin-64": "0.15.9", + "esbuild-darwin-arm64": "0.15.9", + "esbuild-freebsd-64": "0.15.9", + "esbuild-freebsd-arm64": "0.15.9", + "esbuild-linux-32": "0.15.9", + "esbuild-linux-64": "0.15.9", + "esbuild-linux-arm": "0.15.9", + "esbuild-linux-arm64": "0.15.9", + "esbuild-linux-mips64le": "0.15.9", + "esbuild-linux-ppc64le": "0.15.9", + "esbuild-linux-riscv64": "0.15.9", + "esbuild-linux-s390x": "0.15.9", + "esbuild-netbsd-64": "0.15.9", + "esbuild-openbsd-64": "0.15.9", + "esbuild-sunos-64": "0.15.9", + "esbuild-windows-32": "0.15.9", + "esbuild-windows-64": "0.15.9", + "esbuild-windows-arm64": "0.15.9" + } + }, + "esbuild-android-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.9.tgz", + "integrity": "sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.9.tgz", + "integrity": "sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.9.tgz", + "integrity": "sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.9.tgz", + "integrity": "sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.9.tgz", + "integrity": "sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.9.tgz", + "integrity": "sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.9.tgz", + "integrity": "sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.9.tgz", + "integrity": "sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.9.tgz", + "integrity": "sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.9.tgz", + "integrity": "sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.9.tgz", + "integrity": "sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.9.tgz", + "integrity": "sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.9.tgz", + "integrity": "sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.9.tgz", + "integrity": "sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.9.tgz", + "integrity": "sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.9.tgz", + "integrity": "sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.9.tgz", + "integrity": "sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.9.tgz", + "integrity": "sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.9.tgz", + "integrity": "sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.9.tgz", + "integrity": "sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.24.0.tgz", + "integrity": "sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.2", + "@humanwhocodes/config-array": "^0.10.5", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true + }, + "eslint-config-standard-with-typescript": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz", + "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-n": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.3.0.tgz", + "integrity": "sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==", + "dev": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.10.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.7" + } + }, + "eslint-plugin-promise": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", + "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-sdsl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", + "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/src/Web/Avalonia.Web/webapp/package.json b/src/Web/Avalonia.Web/webapp/package.json new file mode 100644 index 0000000000..8845dec604 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/package.json @@ -0,0 +1,22 @@ +{ + "name": "avalonia.web", + "scripts": { + "typecheck": "npx tsc -noEmit", + "eslint": "npx eslint . --fix", + "prebuild": "npm-run-all typecheck eslint", + "build": "node build.js" + }, + "devDependencies": { + "@types/emscripten": "^1.39.6", + "@types/wicg-file-system-access": "^2020.9.5", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "esbuild": "^0.15.7", + "eslint": "^8.24.0", + "eslint-config-standard-with-typescript": "^23.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.3.0", + "eslint-plugin-promise": "^6.0.1", + "npm-run-all": "^4.1.5", + "typescript": "^4.8.3" + } +} diff --git a/src/Web/Avalonia.Web/webapp/tsconfig.json b/src/Web/Avalonia.Web/webapp/tsconfig.json new file mode 100644 index 0000000000..ad0e727150 --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "es2020", + "strict": true, + "sourceMap": true, + "noEmitOnError": true, + "isolatedModules": true, // we need it for esbuild + "lib": [ + "dom", + "es2016", + "esnext.asynciterable" + ] + }, + "exclude": [ + "node_modules" + ] + } + \ No newline at end of file diff --git a/src/Web/Avalonia.Web/webapp/types/dotnet.d.ts b/src/Web/Avalonia.Web/webapp/types/dotnet.d.ts new file mode 100644 index 0000000000..0067ee3e0e --- /dev/null +++ b/src/Web/Avalonia.Web/webapp/types/dotnet.d.ts @@ -0,0 +1,270 @@ +// See https://raw.githubusercontent.com/dotnet/runtime/main/src/mono/wasm/runtime/dotnet.d.ts + +//! Licensed to the .NET Foundation under one or more agreements. +//! The .NET Foundation licenses this file to you under the MIT license. +//! +//! This is generated file, see src/mono/wasm/runtime/rollup.config.js + +//! This is not considered public API with backward compatibility guarantees. + +interface DotnetHostBuilder { + withConfig(config: MonoConfig): DotnetHostBuilder; + withConfigSrc(configSrc: string): DotnetHostBuilder; + withApplicationArguments(...args: string[]): DotnetHostBuilder; + withEnvironmentVariable(name: string, value: string): DotnetHostBuilder; + withEnvironmentVariables(variables: { + [i: string]: string; + }): DotnetHostBuilder; + withVirtualWorkingDirectory(vfsPath: string): DotnetHostBuilder; + withDiagnosticTracing(enabled: boolean): DotnetHostBuilder; + withDebugging(level: number): DotnetHostBuilder; + withMainAssembly(mainAssemblyName: string): DotnetHostBuilder; + withApplicationArgumentsFromQuery(): DotnetHostBuilder; + create(): Promise; + run(): Promise; +} + +declare interface NativePointer { + __brandNativePointer: "NativePointer"; +} +declare interface VoidPtr extends NativePointer { + __brand: "VoidPtr"; +} +declare interface CharPtr extends NativePointer { + __brand: "CharPtr"; +} +declare interface Int32Ptr extends NativePointer { + __brand: "Int32Ptr"; +} +declare interface EmscriptenModule { + HEAP8: Int8Array; + HEAP16: Int16Array; + HEAP32: Int32Array; + HEAPU8: Uint8Array; + HEAPU16: Uint16Array; + HEAPU32: Uint32Array; + HEAPF32: Float32Array; + HEAPF64: Float64Array; + _malloc(size: number): VoidPtr; + _free(ptr: VoidPtr): void; + print(message: string): void; + printErr(message: string): void; + ccall(ident: string, returnType?: string | null, argTypes?: string[], args?: any[], opts?: any): T; + cwrap(ident: string, returnType: string, argTypes?: string[], opts?: any): T; + cwrap(ident: string, ...args: any[]): T; + setValue(ptr: VoidPtr, value: number, type: string, noSafe?: number | boolean): void; + setValue(ptr: Int32Ptr, value: number, type: string, noSafe?: number | boolean): void; + getValue(ptr: number, type: string, noSafe?: number | boolean): number; + UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; + UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; + FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; + FS_readFile(filename: string, opts: any): any; + removeRunDependency(id: string): void; + addRunDependency(id: string): void; + stackSave(): VoidPtr; + stackRestore(stack: VoidPtr): void; + stackAlloc(size: number): VoidPtr; + ready: Promise; + instantiateWasm?: InstantiateWasmCallBack; + preInit?: (() => any)[] | (() => any); + preRun?: (() => any)[] | (() => any); + onRuntimeInitialized?: () => any; + postRun?: (() => any)[] | (() => any); + onAbort?: { + (error: any): void; + }; +} +declare type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void; +declare type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any; +declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; + +declare type MonoConfig = { + /** + * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script. + */ + assemblyRootFolder?: string; + /** + * A list of assets to load along with the runtime. + */ + assets?: AssetEntry[]; + /** + * Additional search locations for assets. + */ + remoteSources?: string[]; + /** + * It will not fail the startup is .pdb files can't be downloaded + */ + ignorePdbLoadErrors?: boolean; + /** + * We are throttling parallel downloads in order to avoid net::ERR_INSUFFICIENT_RESOURCES on chrome. The default value is 16. + */ + maxParallelDownloads?: number; + /** + * Name of the assembly with main entrypoint + */ + mainAssemblyName?: string; + /** + * Configures the runtime's globalization mode + */ + globalizationMode?: GlobalizationMode; + /** + * debugLevel > 0 enables debugging and sets the debug log level to debugLevel + * debugLevel == 0 disables debugging and enables interpreter optimizations + * debugLevel < 0 enabled debugging and disables debug logging. + */ + debugLevel?: number; + /** + * Enables diagnostic log messages during startup + */ + diagnosticTracing?: boolean; + /** + * Dictionary-style Object containing environment variables + */ + environmentVariables?: { + [i: string]: string; + }; + /** + * initial number of workers to add to the emscripten pthread pool + */ + pthreadPoolSize?: number; +}; +interface ResourceRequest { + name: string; + behavior: AssetBehaviours; + resolvedUrl?: string; + hash?: string; +} +interface LoadingResource { + name: string; + url: string; + response: Promise; +} +interface AssetEntry extends ResourceRequest { + /** + * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded. + */ + virtualPath?: string; + /** + * Culture code + */ + culture?: string; + /** + * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. + */ + loadRemote?: boolean; + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ + isOptional?: boolean; + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ + buffer?: ArrayBuffer; + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource; +} +declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads"; +declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". +"invariant" | // operate in invariant globalization mode. +"auto"; +declare type DotnetModuleConfig = { + disableDotnet6Compatibility?: boolean; + config?: MonoConfig; + configSrc?: string; + onConfigLoaded?: (config: MonoConfig) => void | Promise; + onDotnetReady?: () => void | Promise; + imports?: any; + exports?: string[]; + downloadResource?: (request: ResourceRequest) => LoadingResource | undefined; +} & Partial; +declare type APIType = { + runMain: (mainAssemblyName: string, args: string[]) => Promise; + runMainAndExit: (mainAssemblyName: string, args: string[]) => Promise; + setEnvironmentVariable: (name: string, value: string) => void; + getAssemblyExports(assemblyName: string): Promise; + setModuleImports(moduleName: string, moduleImports: any): void; + getConfig: () => MonoConfig; + setHeapB32: (offset: NativePointer, value: number | boolean) => void; + setHeapU8: (offset: NativePointer, value: number) => void; + setHeapU16: (offset: NativePointer, value: number) => void; + setHeapU32: (offset: NativePointer, value: NativePointer | number) => void; + setHeapI8: (offset: NativePointer, value: number) => void; + setHeapI16: (offset: NativePointer, value: number) => void; + setHeapI32: (offset: NativePointer, value: number) => void; + setHeapI52: (offset: NativePointer, value: number) => void; + setHeapU52: (offset: NativePointer, value: number) => void; + setHeapI64Big: (offset: NativePointer, value: bigint) => void; + setHeapF32: (offset: NativePointer, value: number) => void; + setHeapF64: (offset: NativePointer, value: number) => void; + getHeapB32: (offset: NativePointer) => boolean; + getHeapU8: (offset: NativePointer) => number; + getHeapU16: (offset: NativePointer) => number; + getHeapU32: (offset: NativePointer) => number; + getHeapI8: (offset: NativePointer) => number; + getHeapI16: (offset: NativePointer) => number; + getHeapI32: (offset: NativePointer) => number; + getHeapI52: (offset: NativePointer) => number; + getHeapU52: (offset: NativePointer) => number; + getHeapI64Big: (offset: NativePointer) => bigint; + getHeapF32: (offset: NativePointer) => number; + getHeapF64: (offset: NativePointer) => number; +}; +declare type RuntimeAPI = { + /** + * @deprecated Please use API object instead. See also MONOType in dotnet-legacy.d.ts + */ + MONO: any; + /** + * @deprecated Please use API object instead. See also BINDINGType in dotnet-legacy.d.ts + */ + BINDING: any; + INTERNAL: any; + Module: EmscriptenModule; + runtimeId: number; + runtimeBuildInfo: { + productVersion: string; + gitHash: string; + buildConfiguration: string; + }; +} & APIType; +declare type ModuleAPI = { + dotnet: DotnetHostBuilder; + exit: (code: number, reason?: any) => void; +}; +declare function createDotnetRuntime(moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)): Promise; +declare type CreateDotnetRuntimeType = typeof createDotnetRuntime; + +declare global { + function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined; +} + +declare const dotnet: ModuleAPI["dotnet"]; +declare const exit: ModuleAPI["exit"]; + +export { CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, ModuleAPI, MonoConfig, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; + +export interface IMemoryView { + /** + * copies elements from provided source to the wasm memory. + * target has to have the elements of the same type as the underlying C# array. + * same as TypedArray.set() + */ + set(source: TypedArray, targetOffset?: number): void; + /** + * copies elements from wasm memory to provided target. + * target has to have the elements of the same type as the underlying C# array. + */ + copyTo(target: TypedArray, sourceOffset?: number): void; + /** + * same as TypedArray.slice() + */ + slice(start?: number, end?: number): TypedArray; + + get length(): number; + get byteLength(): number; +} diff --git a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj index e05566c454..99d4fd2a27 100644 --- a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj +++ b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj @@ -4,6 +4,12 @@ 13.0 true + + + + + + diff --git a/src/iOS/Avalonia.iOS/CombinedSpan3.cs b/src/iOS/Avalonia.iOS/CombinedSpan3.cs index 29d8eb3955..e9f44b7d58 100644 --- a/src/iOS/Avalonia.iOS/CombinedSpan3.cs +++ b/src/iOS/Avalonia.iOS/CombinedSpan3.cs @@ -16,7 +16,7 @@ internal ref struct CombinedSpan3 public int Length => Span1.Length + Span2.Length + Span3.Length; - void CopyFromSpan(ReadOnlySpan from, ref int offset, ref Span to) + void CopyFromSpan(ReadOnlySpan from, int offset, ref Span to) { if(to.Length == 0) return; @@ -33,8 +33,8 @@ internal ref struct CombinedSpan3 public void CopyTo(Span to, int offset) { - CopyFromSpan(Span1, ref offset, ref to); - CopyFromSpan(Span2, ref offset, ref to); - CopyFromSpan(Span3, ref offset, ref to); + CopyFromSpan(Span1, offset, ref to); + CopyFromSpan(Span2, offset, ref to); + CopyFromSpan(Span3, offset, ref to); } }