From ef08d3fe304c98a4fbb34997908dc7a4f17d5d7c Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Tue, 21 Nov 2023 18:19:46 -0600 Subject: [PATCH 01/27] update build and testing for automation --- .../dymaptic.GeoBlazor.Core.csproj | 6 +++--- .../Pages/Index.razor | 17 +++++++++++++++-- ...tic.GeoBlazor.Core.Test.Blazor.Shared.csproj | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index b72b49fa..bd8b71b5 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -89,17 +89,17 @@ - + - + - + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Pages/Index.razor b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Pages/Index.razor index 16fee341..d00f09a2 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Pages/Index.razor +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Pages/Index.razor @@ -46,7 +46,12 @@ if (Configuration["runOnStart"] == "true") { - await RunTests(); + bool passed = await RunTests(); + + if (!passed) + { + Environment.ExitCode = 1; + } HostApplicationLifetime.StopApplication(); } } @@ -79,7 +84,7 @@ StateHasChanged(); } - private async Task RunTests() + private async Task RunTests() { _running = true; _results.Clear(); @@ -108,10 +113,18 @@ Failed: {result.Value.Failed.Count}"); foreach (KeyValuePair methodResult in result.Value.Passed) { resultBuilder.AppendLine($@"### {methodResult.Key} - Passed +{methodResult.Value}"); + } + + foreach (KeyValuePair methodResult in result.Value.Failed) + { + resultBuilder.AppendLine($@"### {methodResult.Key} - Failed {methodResult.Value}"); } } Console.WriteLine(resultBuilder.ToString()); + + return _results.Values.All(r => r.Failed.Count == 0); } private async Task OnTestResults(TestResult result) diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj index b3fe2802..bc93527e 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj @@ -25,10 +25,10 @@ - + - + From df084afc67ab58a4d26a260f1e3355a78688b7e5 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Tue, 21 Nov 2023 19:14:15 -0600 Subject: [PATCH 02/27] silence verbose copy messages --- src/dymaptic.GeoBlazor.Core/assetCopy.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 index 8bb5d18e..5370ef65 100644 --- a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 +++ b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 @@ -11,7 +11,7 @@ if ((Test-Path -Path "$OutputDir/ArcGISAssetsVersion.txt") -eq $true) If ((Get-Content "$OutputDir/ArcGISAssetsVersion.txt") -ne $ArcGISVersion) { Write-Output "Deleting old assets" - Remove-Item './wwwroot/assets/*' -Recurse -Verbose + Remove-Item './wwwroot/assets/*' -Recurse } } @@ -20,13 +20,13 @@ If ((Test-Path -Path './wwwroot/assets/*') -eq $false) Try { Write-Output "Copying Assets to wwwroot/assets" - Copy-Item -Path $SourceFiles -Destination $OutputDir -Recurse -Verbose + Copy-Item -Path $SourceFiles -Destination $OutputDir -Recurse } Catch { Write-Output $_ Write-Output "We ran into an issue while copying assets to wwwroot/assets. Deleting the copied files..." - Remove-Item './wwwroot/assets/*' -Recurse -Verbose + Remove-Item './wwwroot/assets/*' -Recurse pause } From 3ff21b00ae6a3d29763de706133ce73797c3684a Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 26 Nov 2023 10:05:21 -0600 Subject: [PATCH 03/27] fix assetCopy, set release mode --- src/dymaptic.GeoBlazor.Core/assetCopy.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 index 5370ef65..c33c51ae 100644 --- a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 +++ b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 @@ -1,4 +1,4 @@ -$SourceFiles = "./node_modules/@arcgis/core/assets/*" +$SourceFiles = "./node_modules/@arcgis/core/assets" $OutputDir = "./wwwroot/assets" $packageJson = (Get-Content "package.json" -Raw) | ConvertFrom-Json # read the version from package.json From 30b3d954f8618cf7e6c4941b70a816ae8d19f39d Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 26 Nov 2023 11:10:35 -0600 Subject: [PATCH 04/27] update package.json versions to help with build issue --- src/dymaptic.GeoBlazor.Core/assetCopy.ps1 | 2 +- src/dymaptic.GeoBlazor.Core/package-lock.json | 380 +++++++++--------- src/dymaptic.GeoBlazor.Core/package.json | 4 +- 3 files changed, 193 insertions(+), 193 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 index c33c51ae..5370ef65 100644 --- a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 +++ b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 @@ -1,4 +1,4 @@ -$SourceFiles = "./node_modules/@arcgis/core/assets" +$SourceFiles = "./node_modules/@arcgis/core/assets/*" $OutputDir = "./wwwroot/assets" $packageJson = (Get-Content "package.json" -Raw) | ConvertFrom-Json # read the version from package.json diff --git a/src/dymaptic.GeoBlazor.Core/package-lock.json b/src/dymaptic.GeoBlazor.Core/package-lock.json index d8ae3c27..eed7eb3f 100644 --- a/src/dymaptic.GeoBlazor.Core/package-lock.json +++ b/src/dymaptic.GeoBlazor.Core/package-lock.json @@ -8,8 +8,8 @@ "name": "dymaptic.GeoBlazor.Core", "license": "ISC", "dependencies": { - "@arcgis/core": "4.28", - "esbuild": "0.19.5", + "@arcgis/core": "4.28.10", + "esbuild": "0.19.7", "protobufjs": "7.2.5", "protobufjs-cli": "1.1.2" }, @@ -18,9 +18,9 @@ } }, "node_modules/@arcgis/core": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.28.0.tgz", - "integrity": "sha512-YTihhrezWGc1ci5bPS/bd5ZUHDnbaCpcwEGXmdk7h9xPmLtC3JHeIkVn0G+EJiyWkWVew9vsKL/864ToMLb/Sw==", + "version": "4.28.10", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.28.10.tgz", + "integrity": "sha512-FKvicMVDwFuKX8JKLqAfukzQU2F3AG7s3tDigTcIC4ApGRbj7Nc/F9dRfPZo+aY1Vl7Sa1FjYlo6tfV93LJ2Eg==", "dependencies": { "@esri/arcgis-html-sanitizer": "~3.0.1", "@esri/calcite-colors": "~6.1.0", @@ -44,9 +44,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", - "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.7.tgz", + "integrity": "sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==", "cpu": [ "arm" ], @@ -59,9 +59,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", - "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz", + "integrity": "sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==", "cpu": [ "arm64" ], @@ -74,9 +74,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", - "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.7.tgz", + "integrity": "sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==", "cpu": [ "x64" ], @@ -89,9 +89,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", - "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz", + "integrity": "sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==", "cpu": [ "arm64" ], @@ -104,9 +104,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", - "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz", + "integrity": "sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==", "cpu": [ "x64" ], @@ -119,9 +119,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", - "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz", + "integrity": "sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==", "cpu": [ "arm64" ], @@ -134,9 +134,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", - "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz", + "integrity": "sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==", "cpu": [ "x64" ], @@ -149,9 +149,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", - "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz", + "integrity": "sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==", "cpu": [ "arm" ], @@ -164,9 +164,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", - "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz", + "integrity": "sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==", "cpu": [ "arm64" ], @@ -179,9 +179,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", - "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz", + "integrity": "sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==", "cpu": [ "ia32" ], @@ -194,9 +194,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", - "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz", + "integrity": "sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==", "cpu": [ "loong64" ], @@ -209,9 +209,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", - "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz", + "integrity": "sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==", "cpu": [ "mips64el" ], @@ -224,9 +224,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", - "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz", + "integrity": "sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==", "cpu": [ "ppc64" ], @@ -239,9 +239,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", - "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz", + "integrity": "sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==", "cpu": [ "riscv64" ], @@ -254,9 +254,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", - "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz", + "integrity": "sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==", "cpu": [ "s390x" ], @@ -269,9 +269,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", - "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz", + "integrity": "sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==", "cpu": [ "x64" ], @@ -284,9 +284,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", - "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz", + "integrity": "sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==", "cpu": [ "x64" ], @@ -299,9 +299,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", - "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz", + "integrity": "sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==", "cpu": [ "x64" ], @@ -314,9 +314,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", - "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz", + "integrity": "sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==", "cpu": [ "x64" ], @@ -329,9 +329,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", - "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz", + "integrity": "sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==", "cpu": [ "arm64" ], @@ -344,9 +344,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", - "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz", + "integrity": "sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==", "cpu": [ "ia32" ], @@ -359,9 +359,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz", + "integrity": "sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==", "cpu": [ "x64" ], @@ -736,9 +736,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", - "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.7.tgz", + "integrity": "sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==", "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -747,28 +747,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.5", - "@esbuild/android-arm64": "0.19.5", - "@esbuild/android-x64": "0.19.5", - "@esbuild/darwin-arm64": "0.19.5", - "@esbuild/darwin-x64": "0.19.5", - "@esbuild/freebsd-arm64": "0.19.5", - "@esbuild/freebsd-x64": "0.19.5", - "@esbuild/linux-arm": "0.19.5", - "@esbuild/linux-arm64": "0.19.5", - "@esbuild/linux-ia32": "0.19.5", - "@esbuild/linux-loong64": "0.19.5", - "@esbuild/linux-mips64el": "0.19.5", - "@esbuild/linux-ppc64": "0.19.5", - "@esbuild/linux-riscv64": "0.19.5", - "@esbuild/linux-s390x": "0.19.5", - "@esbuild/linux-x64": "0.19.5", - "@esbuild/netbsd-x64": "0.19.5", - "@esbuild/openbsd-x64": "0.19.5", - "@esbuild/sunos-x64": "0.19.5", - "@esbuild/win32-arm64": "0.19.5", - "@esbuild/win32-ia32": "0.19.5", - "@esbuild/win32-x64": "0.19.5" + "@esbuild/android-arm": "0.19.7", + "@esbuild/android-arm64": "0.19.7", + "@esbuild/android-x64": "0.19.7", + "@esbuild/darwin-arm64": "0.19.7", + "@esbuild/darwin-x64": "0.19.7", + "@esbuild/freebsd-arm64": "0.19.7", + "@esbuild/freebsd-x64": "0.19.7", + "@esbuild/linux-arm": "0.19.7", + "@esbuild/linux-arm64": "0.19.7", + "@esbuild/linux-ia32": "0.19.7", + "@esbuild/linux-loong64": "0.19.7", + "@esbuild/linux-mips64el": "0.19.7", + "@esbuild/linux-ppc64": "0.19.7", + "@esbuild/linux-riscv64": "0.19.7", + "@esbuild/linux-s390x": "0.19.7", + "@esbuild/linux-x64": "0.19.7", + "@esbuild/netbsd-x64": "0.19.7", + "@esbuild/openbsd-x64": "0.19.7", + "@esbuild/sunos-x64": "0.19.7", + "@esbuild/win32-arm64": "0.19.7", + "@esbuild/win32-ia32": "0.19.7", + "@esbuild/win32-x64": "0.19.7" } }, "node_modules/escape-string-regexp": { @@ -1412,9 +1412,9 @@ }, "dependencies": { "@arcgis/core": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.28.0.tgz", - "integrity": "sha512-YTihhrezWGc1ci5bPS/bd5ZUHDnbaCpcwEGXmdk7h9xPmLtC3JHeIkVn0G+EJiyWkWVew9vsKL/864ToMLb/Sw==", + "version": "4.28.10", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.28.10.tgz", + "integrity": "sha512-FKvicMVDwFuKX8JKLqAfukzQU2F3AG7s3tDigTcIC4ApGRbj7Nc/F9dRfPZo+aY1Vl7Sa1FjYlo6tfV93LJ2Eg==", "requires": { "@esri/arcgis-html-sanitizer": "~3.0.1", "@esri/calcite-colors": "~6.1.0", @@ -1432,135 +1432,135 @@ "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==" }, "@esbuild/android-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", - "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.7.tgz", + "integrity": "sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==", "optional": true }, "@esbuild/android-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", - "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz", + "integrity": "sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==", "optional": true }, "@esbuild/android-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", - "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.7.tgz", + "integrity": "sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==", "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", - "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz", + "integrity": "sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==", "optional": true }, "@esbuild/darwin-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", - "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz", + "integrity": "sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==", "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", - "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz", + "integrity": "sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==", "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", - "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz", + "integrity": "sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==", "optional": true }, "@esbuild/linux-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", - "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz", + "integrity": "sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==", "optional": true }, "@esbuild/linux-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", - "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz", + "integrity": "sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==", "optional": true }, "@esbuild/linux-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", - "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz", + "integrity": "sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==", "optional": true }, "@esbuild/linux-loong64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", - "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz", + "integrity": "sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==", "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", - "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz", + "integrity": "sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==", "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", - "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz", + "integrity": "sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==", "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", - "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz", + "integrity": "sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==", "optional": true }, "@esbuild/linux-s390x": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", - "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz", + "integrity": "sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==", "optional": true }, "@esbuild/linux-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", - "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz", + "integrity": "sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==", "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", - "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz", + "integrity": "sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==", "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", - "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz", + "integrity": "sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==", "optional": true }, "@esbuild/sunos-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", - "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz", + "integrity": "sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==", "optional": true }, "@esbuild/win32-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", - "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz", + "integrity": "sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==", "optional": true }, "@esbuild/win32-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", - "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz", + "integrity": "sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==", "optional": true }, "@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz", + "integrity": "sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==", "optional": true }, "@esri/arcgis-html-sanitizer": { @@ -1877,32 +1877,32 @@ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" }, "esbuild": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", - "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.7.tgz", + "integrity": "sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==", "requires": { - "@esbuild/android-arm": "0.19.5", - "@esbuild/android-arm64": "0.19.5", - "@esbuild/android-x64": "0.19.5", - "@esbuild/darwin-arm64": "0.19.5", - "@esbuild/darwin-x64": "0.19.5", - "@esbuild/freebsd-arm64": "0.19.5", - "@esbuild/freebsd-x64": "0.19.5", - "@esbuild/linux-arm": "0.19.5", - "@esbuild/linux-arm64": "0.19.5", - "@esbuild/linux-ia32": "0.19.5", - "@esbuild/linux-loong64": "0.19.5", - "@esbuild/linux-mips64el": "0.19.5", - "@esbuild/linux-ppc64": "0.19.5", - "@esbuild/linux-riscv64": "0.19.5", - "@esbuild/linux-s390x": "0.19.5", - "@esbuild/linux-x64": "0.19.5", - "@esbuild/netbsd-x64": "0.19.5", - "@esbuild/openbsd-x64": "0.19.5", - "@esbuild/sunos-x64": "0.19.5", - "@esbuild/win32-arm64": "0.19.5", - "@esbuild/win32-ia32": "0.19.5", - "@esbuild/win32-x64": "0.19.5" + "@esbuild/android-arm": "0.19.7", + "@esbuild/android-arm64": "0.19.7", + "@esbuild/android-x64": "0.19.7", + "@esbuild/darwin-arm64": "0.19.7", + "@esbuild/darwin-x64": "0.19.7", + "@esbuild/freebsd-arm64": "0.19.7", + "@esbuild/freebsd-x64": "0.19.7", + "@esbuild/linux-arm": "0.19.7", + "@esbuild/linux-arm64": "0.19.7", + "@esbuild/linux-ia32": "0.19.7", + "@esbuild/linux-loong64": "0.19.7", + "@esbuild/linux-mips64el": "0.19.7", + "@esbuild/linux-ppc64": "0.19.7", + "@esbuild/linux-riscv64": "0.19.7", + "@esbuild/linux-s390x": "0.19.7", + "@esbuild/linux-x64": "0.19.7", + "@esbuild/netbsd-x64": "0.19.7", + "@esbuild/openbsd-x64": "0.19.7", + "@esbuild/sunos-x64": "0.19.7", + "@esbuild/win32-arm64": "0.19.7", + "@esbuild/win32-ia32": "0.19.7", + "@esbuild/win32-x64": "0.19.7" } }, "escape-string-regexp": { diff --git a/src/dymaptic.GeoBlazor.Core/package.json b/src/dymaptic.GeoBlazor.Core/package.json index 224b0aee..d44af661 100644 --- a/src/dymaptic.GeoBlazor.Core/package.json +++ b/src/dymaptic.GeoBlazor.Core/package.json @@ -11,8 +11,8 @@ "author": "Tim Purdum", "license": "ISC", "dependencies": { - "@arcgis/core": "4.28", - "esbuild": "0.19.5", + "@arcgis/core": "4.28.10", + "esbuild": "0.19.7", "protobufjs": "7.2.5", "protobufjs-cli": "1.1.2" }, From e07d21e97ebb477a40e3e0bf5c35e2f4d080684f Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 26 Nov 2023 14:33:50 -0600 Subject: [PATCH 05/27] Add console lines for test runner --- src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index bd8b71b5..477589cd 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -56,7 +56,7 @@ - + true Documentation Types From ab35468bdc380ba01ca3b37710323a5e0d4fcfbb Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Wed, 27 Dec 2023 11:22:57 -0600 Subject: [PATCH 06/27] Implemented and tested ApplyEdits changes via protobuf serialization --- Directory.Build.props | 2 +- .../Pages/ManyGraphics.razor | 2 +- ...maptic.GeoBlazor.Core.Sample.Shared.csproj | 18 +- .../Components/Layers/FeatureLayer.cs | 243 +++++++++++++++++- .../Scripts/dotNetBuilder.ts | 41 +++ .../Scripts/featureLayer.ts | 105 +++++++- .../Scripts/jsBuilder.ts | 2 +- .../Components/FeatureLayerTests.razor | 136 +++++++++- ...c.GeoBlazor.Core.Test.Blazor.Shared.csproj | 24 +- 9 files changed, 531 insertions(+), 42 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 158f8702..d94ca9e6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,6 +2,6 @@ enable enable - 2.5.2 + 2.5.3 \ No newline at end of file diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ManyGraphics.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ManyGraphics.razor index f9d2d6d3..2c5501d0 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ManyGraphics.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ManyGraphics.razor @@ -1,7 +1,7 @@ @page "/many-graphics" Many Graphics -

Many Points

+

Many Graphics

diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj index a1bd5aff..2e4a4375 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj @@ -18,12 +18,18 @@
- - - - - - + + + + + + + + + + + + diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs index e72d3bb1..eab27cdf 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs @@ -8,6 +8,7 @@ using dymaptic.GeoBlazor.Core.Serialization; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; +using ProtoBuf; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -243,7 +244,7 @@ public IReadOnlyCollection? Source get => _source; set { - if (value is not null) + if (value is not null && (_source is null || !_source!.Any())) { _source = new HashSet(value); } @@ -294,13 +295,18 @@ public IReadOnlyCollection? Fields /// /// If the layer is already loaded, you must use to add graphics. /// - public Task Add(Graphic graphic) + public async Task Add(Graphic graphic) { if (JsLayerReference is not null) { - throw new InvalidOperationException("Cannot add graphics to a feature layer that is already loaded. Use ApplyEdits instead."); + await ApplyEdits(new FeatureEdits + { + AddFeatures = new []{graphic} + }); + + return; } - return RegisterChildComponent(graphic); + await RegisterChildComponent(graphic); } /// @@ -316,7 +322,12 @@ public async Task Add(IEnumerable graphics) { if (JsLayerReference is not null) { - throw new InvalidOperationException("Cannot add graphics to a feature layer that is already loaded. Use ApplyEdits instead."); + await ApplyEdits(new FeatureEdits + { + AddFeatures = graphics + }); + + return; } foreach (Graphic graphic in graphics) { @@ -361,17 +372,182 @@ public async Task SetPopupTemplate(PopupTemplate template) /// Applies edits to features in a layer. New features can be created and existing features can be updated or deleted. Feature geometries and/or attributes may be modified. Only applicable to layers in a feature service and client-side features set through the FeatureLayer's source property. Attachments can also be added, updated or deleted. /// If client-side features are added, removed or updated at runtime using applyEdits() then use FeatureLayer's queryFeatures() method to return updated features. /// - public async Task ApplyEdits(FeatureEdits edits, FeatureEditOptions? options = null) + public async Task ApplyEdits(FeatureEdits edits, FeatureEditOptions? options = null, + CancellationToken cancellationToken = default) { // Verify that the layer is loaded. Layers with no graphics are not rendered and therefore not loaded // as far as GeoBlazor is concerned if (JsModule is not null && JsLayerReference is null) { - await Load(); + await Load(cancellationToken); + } + + FeatureEditsResult emptyResult = new FeatureEditsResult(Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + null); + + if (cancellationToken.IsCancellationRequested || + CancellationTokenSource.Token.IsCancellationRequested) + { + return emptyResult; + } + + Graphic[] addedFeatures = edits.AddFeatures?.ToArray() ?? Array.Empty(); + Graphic[] updatedFeatures = edits.UpdateFeatures?.ToArray() ?? Array.Empty(); + Graphic[] deletedFeatures = edits.DeleteFeatures?.ToArray() ?? Array.Empty(); + long? editMoment = null; + int chunkSize = View!.GraphicSerializationChunkSize ?? (View.IsMaui ? 100 : 200); + AbortManager ??= new AbortManager(JsRuntime); + + // return await JsLayerReference!.InvokeAsync("applyEdits", edits, options, + // View!.Id); + FeatureEditsResult? addFeatureResults = null; + FeatureEditsResult? updateFeatureResults = null; + FeatureEditsResult? deleteFeatureResults = null; + List editedFeatureResults = new(); + IJSObjectReference abortSignal = await AbortManager!.CreateAbortSignal(cancellationToken); + for (var index = 0; index < addedFeatures.Length; index += chunkSize) + { + int skip = index; + + addFeatureResults = + await SendEdits(addedFeatures.Skip(skip).Take(chunkSize) + .Select(g => g.ToSerializationRecord()).ToArray(), "add", + options, addFeatureResults, abortSignal, cancellationToken); + editMoment ??= addFeatureResults?.EditMoment; + } + for (var index = 0; index < updatedFeatures.Length; index += chunkSize) + { + int skip = index; + + updateFeatureResults = + await SendEdits(updatedFeatures.Skip(skip).Take(chunkSize) + .Select(g => g.ToSerializationRecord()).ToArray(), "update", + options, updateFeatureResults, abortSignal, cancellationToken); + editMoment ??= updateFeatureResults?.EditMoment; + } + for (var index = 0; index < deletedFeatures.Length; index += chunkSize) + { + int skip = index; + + deleteFeatureResults = + await SendEdits(deletedFeatures.Skip(skip).Take(chunkSize) + .Select(g => g.ToSerializationRecord()).ToArray(), "delete", + options, deleteFeatureResults, abortSignal, cancellationToken); + editMoment ??= deleteFeatureResults?.EditMoment; } - return await JsLayerReference!.InvokeAsync("applyEdits", edits, options, - View!.Id); + if (addFeatureResults?.EditedFeatureResults is not null) + { + editedFeatureResults.AddRange(addFeatureResults.EditedFeatureResults); + } + if (updateFeatureResults?.EditedFeatureResults is not null) + { + editedFeatureResults.AddRange(updateFeatureResults.EditedFeatureResults); + } + if (deleteFeatureResults?.EditedFeatureResults is not null) + { + editedFeatureResults.AddRange(deleteFeatureResults.EditedFeatureResults); + } + + FeatureEditsResult? attachmentResults = null; + if (edits.AddAttachments?.Any() == true || + edits.UpdateAttachments?.Any() == true || + edits.DeleteAttachments?.Any() == true) + { + FeatureEdits attachmentEdits = new() + { + AddAttachments = edits.AddAttachments, + UpdateAttachments = edits.UpdateAttachments, + DeleteAttachments = edits.DeleteAttachments + }; + attachmentResults = await JsLayerReference!.InvokeAsync( + "applyAttachmentEdits", cancellationToken, attachmentEdits, options, View!.Id, + abortSignal); + editMoment ??= attachmentResults.EditMoment; + if (attachmentResults.EditedFeatureResults is not null) + { + editedFeatureResults.AddRange(attachmentResults.EditedFeatureResults); + } + } + + if (_source is not null) + { + // update the in-memory collections: + foreach (Graphic addedGraphic in addedFeatures) + { + _source!.Add(addedGraphic); + } + foreach (Graphic updatedGraphic in updatedFeatures) + { + _source!.Remove(updatedGraphic); + _source!.Add(updatedGraphic); + } + foreach (Graphic deletedGraphic in deletedFeatures) + { + _source!.Remove(deletedGraphic); + } + } + + return new FeatureEditsResult + ( + addFeatureResults?.AddFeatureResults ?? Array.Empty(), + updateFeatureResults?.UpdateFeatureResults ?? Array.Empty(), + deleteFeatureResults?.DeleteFeatureResults ?? Array.Empty(), + attachmentResults?.AddAttachmentResults ?? Array.Empty(), + attachmentResults?.UpdateAttachmentResults ?? Array.Empty(), + attachmentResults?.DeleteAttachmentResults ?? Array.Empty(), + editedFeatureResults.ToArray(), + editMoment + ); + } + + private async Task SendEdits(GraphicSerializationRecord[] graphics, + string editType, FeatureEditOptions? options, FeatureEditsResult? currentResults, + IJSObjectReference abortSignal, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested || + CancellationTokenSource.Token.IsCancellationRequested) + { + return null; + } + + ProtoGraphicCollection collection = new(graphics); + MemoryStream ms = new(); + Serializer.Serialize(ms, collection); + + if (cancellationToken.IsCancellationRequested || + CancellationTokenSource.Token.IsCancellationRequested) + { + await ms.DisposeAsync(); + return null; + } + + ms.Seek(0, SeekOrigin.Begin); + + FeatureEditsResult result; + if (View!.IsWebAssembly) + { + result = await JsLayerReference!.InvokeAsync( + "applyGraphicEditsSynchronously", cancellationToken, ms.ToArray(), editType, options, + View!.Id, abortSignal); + await ms.DisposeAsync(); + await Task.Delay(1, cancellationToken); + } + else + { + using DotNetStreamReference streamRef = new(ms); + result = await JsLayerReference!.InvokeAsync( + "applyGraphicEditsFromStream", cancellationToken, streamRef, editType, options, + View!.Id, abortSignal); + } + + return result.Concat(currentResults); } /// @@ -1178,7 +1354,8 @@ public class AttachmentEdit /// /// The feature, objectId or globalId of feature associated with the attachment. /// - public Graphic Feature { get; set; } = default!; + [JsonConverter(typeof(GraphicToIdConverter))] + public Graphic? Feature { get; set; } /// /// The attachment to be added, updated or deleted. @@ -1335,10 +1512,32 @@ public class FeatureEditOptions /// /// The time edits were applied to the feature service. This parameter is returned only when the returnEditMoment parameter of the applyEdits() method is set to true. /// -public record FeatureEditsResult(FeatureEditResult[] AddFeatureResults, FeatureEditResult[] UpdateFeatureResults, - FeatureEditResult[] DeleteFeatureResults, FeatureEditResult[] AddAttachmentResults, - FeatureEditResult[] UpdateAttachmentResults, FeatureEditResult[] DeleteAttachmentResults, - EditedFeatureResult[]? EditedFeatureResults, long? EditMoment); +public record FeatureEditsResult( + FeatureEditResult[] AddFeatureResults, + FeatureEditResult[] UpdateFeatureResults, + FeatureEditResult[] DeleteFeatureResults, + FeatureEditResult[] AddAttachmentResults, + FeatureEditResult[] UpdateAttachmentResults, + FeatureEditResult[] DeleteAttachmentResults, + EditedFeatureResult[]? EditedFeatureResults, + long? EditMoment) +{ + public FeatureEditsResult Concat(FeatureEditsResult? other) + { + if (other is null) return this; + return this with + { + AddFeatureResults = AddFeatureResults.Concat(other.AddFeatureResults).ToArray(), + UpdateFeatureResults = UpdateFeatureResults.Concat(other.UpdateFeatureResults).ToArray(), + DeleteFeatureResults = DeleteFeatureResults.Concat(other.DeleteFeatureResults).ToArray(), + AddAttachmentResults = AddAttachmentResults.Concat(other.AddAttachmentResults).ToArray(), + UpdateAttachmentResults = UpdateAttachmentResults.Concat(other.UpdateAttachmentResults).ToArray(), + DeleteAttachmentResults = DeleteAttachmentResults.Concat(other.DeleteAttachmentResults).ToArray(), + EditedFeatureResults = (EditedFeatureResults ?? Array.Empty()) + .Concat(other.EditedFeatureResults ?? Array.Empty()).ToArray() + }; + } +} /// /// FeatureEditResult represents the result of adding, updating or deleting a feature or an attachment. @@ -1477,4 +1676,20 @@ public enum DrawingTool UpArrow, DownArrow #pragma warning restore CS1591 +} + +/// +/// One-directional converter to just send the component Id to JS +/// +internal class GraphicToIdConverter: JsonConverter +{ + public override Graphic? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, Graphic value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.Id.ToString()); + } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts index ba82b26b..a08c36ba 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts @@ -1144,4 +1144,45 @@ export function buildDotNetAuthoringInfo(jsAuthoringInfo: AuthoringInfo): any { } return dnAuthoringInfo; +} + +export function buildDotNetEditsResult(jsResult: __esri.EditsResult): any { + let dnResult: any = { + addFeatureResults: jsResult.addFeatureResults, + deleteFeatureResults: jsResult.deleteFeatureResults, + updateFeatureResults: jsResult.updateFeatureResults, + addAttachmentResults: jsResult.addAttachmentResults, + updateAttachmentResults: jsResult.updateAttachmentResults, + deleteAttachmentResults: jsResult.deleteAttachmentResults + }; + + if (hasValue(jsResult.editedFeatureResults)) { + dnResult.editedFeatureResults = jsResult.editedFeatureResults!.map(r =>{ + let dnEditedFeatureResult: any = { + layerId: r.layerId + }; + if (hasValue(r.editedFeatures.adds)) { + dnEditedFeatureResult.adds = r.editedFeatures.adds!.map(buildDotNetFeature); + } + if (hasValue(r.editedFeatures.deletes)) { + dnEditedFeatureResult.deletes = r.editedFeatures.deletes!.map(buildDotNetFeature); + } + if (hasValue(r.editedFeatures.updates)) { + dnEditedFeatureResult.updates = []; + for (let i = 0; i < r.editedFeatures.updates!.length; i++) { + let jsFeature = r.editedFeatures.updates![i]; + dnEditedFeatureResult.updates.push({ + original: jsFeature.original?.map(buildDotNetFeature), + current: jsFeature.current?.map(buildDotNetFeature) + }); + } + } + if (hasValue(r.editedFeatures.spatialReference)) { + dnEditedFeatureResult.spatialReference = buildDotNetSpatialReference(r.editedFeatures.spatialReference!); + } + + return dnEditedFeatureResult; + }); + } + return dnResult; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index aa753a5e..9a7166ed 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -20,7 +20,7 @@ import { buildJsRelationshipQuery, buildJsTopFeaturesQuery, buildJsPortalItem, - buildJsGraphic, buildJsEffect + buildJsGraphic, buildJsEffect, buildJsAttachmentEdit } from "./jsBuilder"; import { buildDotNetExtent, @@ -32,8 +32,14 @@ import { buildDotNetFeatureLayer, buildDotNetDomain, buildDotNetFeatureType, + buildDotNetEditsResult, } from "./dotNetBuilder"; -import { blazorServer, dotNetRefs, graphicsRefs } from "./arcGisJsInterop"; +import { + blazorServer, + dotNetRefs, + graphicsRefs, + getGraphicsFromProtobufStream, arcGisObjectRefs, hasValue, decodeProtobufGraphics +} from "./arcGisJsInterop"; import Graphic from "@arcgis/core/Graphic"; export default class FeatureLayerWrapper { @@ -246,17 +252,102 @@ export default class FeatureLayerWrapper { extent: buildDotNetExtent(result.extent) }; } - - async applyEdits(edits: DotNetApplyEdits, options: any, viewId: string): Promise { - let jsEdits = buildJsApplyEdits(edits, viewId); - let result; + + async applyGraphicEditsFromStream(streamRef: any, editType: string, options: any, + viewId: string, abortSignal: AbortSignal): Promise { + if (abortSignal.aborted) { + return; + } + let graphics = await getGraphicsFromProtobufStream(streamRef) as any[]; + return await this.applyGraphicEdits(graphics, editType, options, viewId, abortSignal); + } + + async applyGraphicEditsSynchronously(graphicsArray: Uint8Array, editType: string, options: any, + viewId: string, abortSignal: AbortSignal): Promise { + if (abortSignal.aborted) { + return; + } + let graphics = decodeProtobufGraphics(graphicsArray); + return await this.applyGraphicEdits(graphics, editType, options, viewId, abortSignal); + } + + async applyGraphicEdits(graphics: any[], editType: string, options: any, viewId: string, + abortSignal: AbortSignal): Promise { + let jsGraphics: Graphic[] = []; + for (const g of graphics) { + if (graphicsRefs.hasOwnProperty(g.id)) { + let graphic = graphicsRefs[g.id] as Graphic; + if (graphic !== undefined && graphic !== null) { + jsGraphics.push(graphic); + continue; + } + } + let jsGraphic = buildJsGraphic(g, viewId) as Graphic; + jsGraphics.push(jsGraphic); + } + if (abortSignal.aborted) { + return; + } + let featureEdits = {}; + switch (editType) { + case 'add': + featureEdits['addFeatures'] = jsGraphics; + break; + case 'update': + featureEdits['updateFeatures'] = jsGraphics; + break; + case 'delete': + featureEdits['deleteFeatures'] = jsGraphics; + break; + } + let result: __esri.EditsResult; + if (hasValue(options)) { + result = await this.layer.applyEdits(featureEdits, options); + } else { + result = await this.layer.applyEdits(featureEdits); + } + if (abortSignal.aborted) return; + (async () => { + for (let i = 0; i < jsGraphics.length; i++) { + let graphic = jsGraphics[i]; + let graphicObject = graphics[i]; + graphicsRefs[graphicObject.id] = graphic; + } + })(); + let dnResult = buildDotNetEditsResult(result); + return dnResult; + } + + async applyAttachmentEdits(edits: any, options: any, viewId: string, + abortSignal: AbortSignal): Promise { + if (abortSignal.aborted) return; + let addAttachments = edits.addAttachments?.map(e => { + return { + feature: graphicsRefs[e.feature], + attachment: e.attachment + } + }); + let updateAttachments = edits.updateAttachments?.map(e => { + return { + feature: graphicsRefs[e.feature], + attachment: e.attachment + } + }); + let jsEdits = { + addAttachments: addAttachments, + updateAttachments: updateAttachments, + deleteAttachments: edits.deleteAttachments + }; + let result: __esri.EditsResult; if (options !== null) { result = await this.layer.applyEdits(jsEdits, options); } else { result = await this.layer.applyEdits(jsEdits); } + if (abortSignal.aborted) return; - return result; + let dnResult = buildDotNetEditsResult(result); + return dnResult; } async getFeatureType(graphic: DotNetGraphic): Promise { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts index f6d95523..470d0608 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts @@ -1618,7 +1618,7 @@ function buildJsFormInput(dotNetFormInput: any): any { return undefined; } -function buildJsAttachmentEdit(dotNetAttachmentEdit: DotNetAttachmentsEdit, viewId: string): AttachmentEdit { +export function buildJsAttachmentEdit(dotNetAttachmentEdit: DotNetAttachmentsEdit, viewId: string): AttachmentEdit { return { feature: buildJsGraphic(dotNetAttachmentEdit.feature, viewId)!, attachment: dotNetAttachmentEdit.attachment diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/FeatureLayerTests.razor b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/FeatureLayerTests.razor index 67316caa..2bb4af05 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/FeatureLayerTests.razor +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/FeatureLayerTests.razor @@ -96,7 +96,7 @@ } [TestMethod] - public async Task TestCannotAddFeaturesWithAddMethodAfterRender(Action renderHandler) + public async Task TestCanAddFeaturesWithAddMethodAfterRender(Action renderHandler) { FeatureLayer? layer = null; AddMapRenderFragment( @@ -111,7 +111,137 @@ await WaitForMapToRender(); await AssertJavaScript("assertLayerExists", args: "feature"); - await Assert.ThrowsExceptionAsync(() => layer!.Add(new Graphic(new Point(0, 0), - new SimpleMarkerSymbol(color: new MapColor("black"))))); + await layer!.Add(new Graphic(new Point(0, 0), + new SimpleMarkerSymbol(color: new MapColor("black")))); + await WaitForMapToRender(); + await AssertJavaScript("assertGraphicExistsInLayer", args: new object[] { layer!.Id, "point", 1 }); + } + + [TestMethod] + public async Task TestCanAddManyFeaturesWithApplyEdits(Action renderHandler) + { + FeatureLayer? layer = null; + AddMapRenderFragment( + @ + + + + + ); + await WaitForMapToRender(); + await AssertJavaScript("assertLayerExists", args: "feature"); + + List graphics = []; + Random random = new(); + for (var i = 0; i < 2000; i++) + { + Point point = new Point(random.Next(-180, 180), random.Next(-80, 80)); + Graphic graphic = new(point); + graphics.Add(graphic); + } + FeatureEdits edits = new() + { + AddFeatures = graphics + }; + FeatureEditsResult result = await layer!.ApplyEdits(edits); + await WaitForMapToRender(); + await AssertJavaScript("assertGraphicExistsInLayer", args: new object[] { layer!.Id, "point", 2000 }); + Assert.AreEqual(2000, result.AddFeatureResults.Length); + Assert.AreEqual(2000, layer.Source!.Count); + } + + [TestMethod] + public async Task TestCanUpdateManyFeaturesWithApplyEdits(Action renderHandler) + { + FeatureLayer? layer = null; + AddMapRenderFragment( + @ + + + + + ); + await WaitForMapToRender(); + await AssertJavaScript("assertLayerExists", args: "feature"); + + List graphics = []; + Random random = new(); + for (var i = 0; i < 2000; i++) + { + Point point = new Point(random.Next(-180, 180), random.Next(-80, 80)); + Graphic graphic = new(point); + graphics.Add(graphic); + } + FeatureEdits edits = new() + { + AddFeatures = graphics + }; + FeatureEditsResult result = await layer!.ApplyEdits(edits); + await WaitForMapToRender(); + await AssertJavaScript("assertGraphicExistsInLayer", args: new object[] { layer!.Id, "point", 2000 }); + Assert.AreEqual(2000, result.AddFeatureResults.Length); + + foreach (Graphic graphic in graphics) + { + ((Point)graphic.Geometry!).X += 1; + ((Point)graphic.Geometry!).Y += 1; + } + + edits = new() + { + UpdateFeatures = graphics + }; + result = await layer!.ApplyEdits(edits); + await WaitForMapToRender(); + await AssertJavaScript("assertGraphicExistsInLayer", args: new object[] { layer!.Id, "point", 2000 }); + Assert.AreEqual(2000, result.UpdateFeatureResults.Length); + } + + [TestMethod] + public async Task TestCanDeleteManyFeaturesWithApplyEdits(Action renderHandler) + { + FeatureLayer? layer = null; + AddMapRenderFragment( + @ + + + + + ); + await WaitForMapToRender(); + await AssertJavaScript("assertLayerExists", args: "feature"); + + List graphics = []; + Random random = new(); + for (var i = 0; i < 2000; i++) + { + Point point = new Point(random.Next(-180, 180), random.Next(-80, 80)); + Graphic graphic = new(point); + graphics.Add(graphic); + } + FeatureEdits edits = new() + { + AddFeatures = graphics + }; + FeatureEditsResult result = await layer!.ApplyEdits(edits); + await WaitForMapToRender(); + await AssertJavaScript("assertGraphicExistsInLayer", args: new object[] { layer!.Id, "point", 2000 }); + Assert.AreEqual(2000, result.AddFeatureResults.Length); + + edits = new() + { + DeleteFeatures = graphics + }; + result = await layer!.ApplyEdits(edits); + await WaitForMapToRender(); + await Assert.ThrowsExceptionAsync(async() => + await AssertJavaScript("assertGraphicExistsInLayer", args: new object[] { layer!.Id, "point", 2000 })); + Assert.AreEqual(2000, result.DeleteFeatureResults.Length); } } \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj index b3fe2802..27d8a964 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj @@ -25,13 +25,19 @@ - - - - - - - - - + + + + + + + + + + + + + + + From 0948e24c1bd1ea0b692218b96eb8541b11fe6715 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Fri, 29 Dec 2023 15:34:09 -0600 Subject: [PATCH 07/27] Graphics Legend Widget --- .../Components/ActionBase.cs | 7 ------- .../Components/Layers/Graphic.cs | 13 +++++++++---- .../Components/Layers/Layer.cs | 8 -------- .../Components/MapComponent.razor.cs | 8 ++++++++ .../Components/Popups/FieldInfo.cs | 7 ------- .../Scripts/arcGisJsInterop.ts | 7 +++++++ 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs b/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs index 70bde145..0bd3cfbd 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs @@ -44,13 +44,6 @@ public abstract class ActionBase : MapComponent [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Disabled { get; set; } - /// - /// Indicates if the action is visible. - /// - [Parameter] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? Visible { get; set; } - /// /// The action function to perform on click. /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs index 6df67937..a743a310 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs @@ -43,8 +43,11 @@ public Graphic() /// /// Indicates the visibility of the graphic. /// + /// + /// Optional label override for this graphic in the GeoBlazor Pro GraphicsLegendWidget. + /// public Graphic(Geometry? geometry = null, Symbol? symbol = null, PopupTemplate? popupTemplate = null, - AttributesDictionary? attributes = null, bool? visible = null) + AttributesDictionary? attributes = null, bool? visible = null, string? legendLabel = null) { AllowRender = false; #pragma warning disable BL0005 @@ -52,6 +55,7 @@ public Graphic(Geometry? geometry = null, Symbol? symbol = null, PopupTemplate? Symbol = symbol; PopupTemplate = popupTemplate; Visible = visible; + LegendLabel = legendLabel; if (attributes is not null) { @@ -74,11 +78,12 @@ public Graphic(Geometry? geometry = null, Symbol? symbol = null, PopupTemplate? public AttributesDictionary Attributes { get; set; } = new(); /// - /// Indicates the visibility of the graphic. Default value: true. + /// Legend label override for this graphic in the GeoBlazor Pro Graphics Legend Widget. + /// Supports attribute substitution using the syntax {attributeName}. /// [Parameter] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? Visible { get; set; } + [JsonIgnore] + public string? LegendLabel { get; set; } /// /// The geometry that defines the graphic's location. diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs index adff8238..fb29d960 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs @@ -57,14 +57,6 @@ public abstract class Layer : MapComponent [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ListMode? ListMode { get; set; } - /// - /// Indicates if the layer is visible in the View. When false, the layer may still be added to a Map instance that is - /// referenced in a view, but its features will not be visible in the view. - /// - [Parameter] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? Visible { get; set; } - /// /// The full extent of the layer. By default, this is worldwide. This property may be used to set the extent of the /// view to match a layer's extent so that its features appear to fill the view. diff --git a/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs b/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs index 69c1f67a..fb2e5223 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs @@ -66,6 +66,13 @@ public abstract partial class MapComponent : ComponentBase, IAsyncDisposable [CascadingParameter(Name = "View")] [JsonIgnore] public MapView? View { get; set; } + + /// + /// Indicates the visibility of the component. Default value: true. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Visible { get; set; } /// /// A unique identifier, used to track components across .NET and JavaScript. @@ -221,6 +228,7 @@ public virtual void Refresh() public async Task SetVisibility(bool visible) { await JsModule!.InvokeVoidAsync("setVisibility", CancellationTokenSource.Token, Id, visible); + Visible = visible; } /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs index 4070f42d..1699662c 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs @@ -85,13 +85,6 @@ public FieldInfo(string? fieldName = null, string? label = null, string? tooltip [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Tooltip { get; set; } = string.Empty; - /// - /// Indicates whether the field is visible in the popup window. - /// - [Parameter] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? Visible { get; set; } - /// /// A string determining what type of input box editors see when editing the field. /// diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index 38a11358..80ea03f2 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -2776,6 +2776,13 @@ export function setVisibility(componentId: string, visible: boolean): void { let component: any | undefined = arcGisObjectRefs[componentId]; if (component !== undefined) { component.visible = visible; + return; + } + // check graphics too + let graphic = graphicsRefs[componentId]; + if (graphic !== undefined) { + graphic.visible = visible; + return; } } From 7ff21012af8fb1f31babc4efeadbab117b1ca973 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 30 Dec 2023 13:35:37 -0600 Subject: [PATCH 08/27] add missing xml --- .../Components/Layers/FeatureLayer.cs | 5 +++++ src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs index eab27cdf..e24244ae 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs @@ -1522,6 +1522,11 @@ public record FeatureEditsResult( EditedFeatureResult[]? EditedFeatureResults, long? EditMoment) { + /// + /// Concatenates two s. + /// + /// + /// public FeatureEditsResult Concat(FeatureEditsResult? other) { if (other is null) return this; diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 92d1dca9..9bc14393 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -80,7 +80,7 @@ - + From 80108ea4671e699ab3687dd580b77cb0e0d30c71 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 30 Dec 2023 13:46:02 -0600 Subject: [PATCH 09/27] try assetCopy without asterisk for GH action --- src/dymaptic.GeoBlazor.Core/assetCopy.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 index 5370ef65..bf1e255f 100644 --- a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 +++ b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 @@ -1,4 +1,4 @@ -$SourceFiles = "./node_modules/@arcgis/core/assets/*" +$SourceFiles = "./node_modules/@arcgis/core/assets/" $OutputDir = "./wwwroot/assets" $packageJson = (Get-Content "package.json" -Raw) | ConvertFrom-Json # read the version from package.json From 66992b66aefbb437c7be3f7e38f823ab88a80cf1 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 30 Dec 2023 13:58:49 -0600 Subject: [PATCH 10/27] add missing Documentation folder, fix pull/push in build --- src/dymaptic.GeoBlazor.Core/assetCopy.ps1 | 10 +++++++++- .../dymaptic.GeoBlazor.Core.csproj | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 index bf1e255f..ed21d635 100644 --- a/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 +++ b/src/dymaptic.GeoBlazor.Core/assetCopy.ps1 @@ -1,4 +1,12 @@ -$SourceFiles = "./node_modules/@arcgis/core/assets/" +if ($IsWindows) +{ + $SourceFiles = "./node_modules/@arcgis/core/assets/*" +} +else +{ + $SourceFiles = "./node_modules/@arcgis/core/assets/" +} + $OutputDir = "./wwwroot/assets" $packageJson = (Get-Content "package.json" -Raw) | ConvertFrom-Json # read the version from package.json diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 9bc14393..11c74777 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -81,6 +81,7 @@ + From abe50a11d435e0d22a567e78860963485559881a Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 30 Dec 2023 14:19:37 -0600 Subject: [PATCH 11/27] Update core targets --- src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 11c74777..73536813 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -97,12 +97,12 @@ - + - + @@ -113,7 +113,7 @@ - + From d92dc3690bf2a347ec6daa2952fd314d997db5e3 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 30 Dec 2023 16:46:08 -0600 Subject: [PATCH 12/27] Add Core workflows --- .github/workflows/dev-pr-build.yml | 28 +++++++++++++ .github/workflows/main-release-build.yml | 51 ++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 .github/workflows/dev-pr-build.yml create mode 100644 .github/workflows/main-release-build.yml diff --git a/.github/workflows/dev-pr-build.yml b/.github/workflows/dev-pr-build.yml new file mode 100644 index 00000000..85cfc2f0 --- /dev/null +++ b/.github/workflows/dev-pr-build.yml @@ -0,0 +1,28 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: Develop Branch PR Build + +on: + pull_request: + branches: [ "develop" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj + - name: Build Core + run: dotnet build ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj --no-restore /p:OptOutFromCoreEsBuild=false /p:GenerateDocs=false /p:UpdateTemplates=true -c Release diff --git a/.github/workflows/main-release-build.yml b/.github/workflows/main-release-build.yml new file mode 100644 index 00000000..6f4b18a1 --- /dev/null +++ b/.github/workflows/main-release-build.yml @@ -0,0 +1,51 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: Main Branch Release Build + +on: + push: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj + - name: Build Core + run: dotnet build ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj --no-restore /p:OptOutFromCoreEsBuild=false /p:GenerateDocs=false /p:UpdateTemplates=false -c Release + - name: Build Templates + run: dotnet build ./templates/dymaptic.GeoBlazor.Templates.csproj -c Release + - name: Publish Core to Nuget + run: dotnet nuget push ./src/dymaptic.GeoBlazor.Core/bin/Release/dymaptic.GeoBlazor.Core.*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate + - name: Publish Templates to Nuget + run: dotnet nuget push ./templates/bin/Release/dymaptic.GeoBlazor.Templates.*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate + - name: Copy Build Versions + run: | + CORE_VERSION=$(xmllint --xpath "//PropertyGroup/CoreVersion/text()" ./Directory.Build.props) + echo "CORE_VERSION=$CORE_VERSION" >> $GITHUB_ENV + - name: Copy PR Release Notes + run: | + PR_DESCRIPTION=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/pulls?state=closed&sort=updated&direction=desc" | \ + jq -r '.[0].body') + echo "PR_DESCRIPTION=$PR_DESCRIPTION" >> $GITHUB_ENV + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + body: ${{env.PR_DESCRIPTION}} + tag_name: "v${{env.PRO_VERSION}}" + generate_release_notes: true + prerelease: true + files: | + ./src/dymaptic.GeoBlazor.Core/bin/Release/dymaptic.GeoBlazor.Core.*.nupkg \ No newline at end of file From b02e43f72ed3aacc850c06742c5dc492b84fb1b5 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 30 Dec 2023 16:53:03 -0600 Subject: [PATCH 13/27] Save changes to template versions --- .github/workflows/dev-pr-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/dev-pr-build.yml b/.github/workflows/dev-pr-build.yml index 85cfc2f0..4ac8726e 100644 --- a/.github/workflows/dev-pr-build.yml +++ b/.github/workflows/dev-pr-build.yml @@ -26,3 +26,13 @@ jobs: run: dotnet restore ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj - name: Build Core run: dotnet build ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj --no-restore /p:OptOutFromCoreEsBuild=false /p:GenerateDocs=false /p:UpdateTemplates=true -c Release + + - name: Add & Commit + # Saves updated template versions + # You may pin to the exact commit or the version. + # uses: EndBug/add-and-commit@1bad3abcf0d6ec49a5857d124b0bfb52dc7bb081 + uses: EndBug/add-and-commit@v9.1.3 + with: + # The message for the commit + message: Docs and Templates Generated by GH Action + pull: '' \ No newline at end of file From 95ddae2f8986cf6075ae094b7a2625ce85891660 Mon Sep 17 00:00:00 2001 From: TimPurdum Date: Sat, 30 Dec 2023 22:54:24 +0000 Subject: [PATCH 14/27] Docs and Templates Generated by GH Action --- .../dymaptic.GeoBlazor.Template.Maui.csproj | 2 +- .../dymaptic.GeoBlazor.Template.Server.csproj | 2 +- .../dymaptic.GeoBlazor.Template.WebApp.Client.csproj | 2 +- .../dymaptic.GeoBlazor.Template.WebApp.csproj | 2 +- .../dymaptic.GeoBlazor.Template.WebAssembly.csproj | 2 +- templates/dymaptic.GeoBlazor.Templates.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/content/dymaptic.GeoBlazor.Template.Maui/dymaptic.GeoBlazor.Template.Maui.csproj b/templates/content/dymaptic.GeoBlazor.Template.Maui/dymaptic.GeoBlazor.Template.Maui.csproj index a83205ea..aaff2150 100644 --- a/templates/content/dymaptic.GeoBlazor.Template.Maui/dymaptic.GeoBlazor.Template.Maui.csproj +++ b/templates/content/dymaptic.GeoBlazor.Template.Maui/dymaptic.GeoBlazor.Template.Maui.csproj @@ -81,7 +81,7 @@ - + diff --git a/templates/content/dymaptic.GeoBlazor.Template.Server/dymaptic.GeoBlazor.Template.Server.csproj b/templates/content/dymaptic.GeoBlazor.Template.Server/dymaptic.GeoBlazor.Template.Server.csproj index 85217c3d..300f6b3f 100644 --- a/templates/content/dymaptic.GeoBlazor.Template.Server/dymaptic.GeoBlazor.Template.Server.csproj +++ b/templates/content/dymaptic.GeoBlazor.Template.Server/dymaptic.GeoBlazor.Template.Server.csproj @@ -7,7 +7,7 @@ - + diff --git a/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.Client/dymaptic.GeoBlazor.Template.WebApp.Client.csproj b/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.Client/dymaptic.GeoBlazor.Template.WebApp.Client.csproj index 2a754cb3..a6a34233 100644 --- a/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.Client/dymaptic.GeoBlazor.Template.WebApp.Client.csproj +++ b/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.Client/dymaptic.GeoBlazor.Template.WebApp.Client.csproj @@ -9,7 +9,7 @@ - + diff --git a/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.csproj b/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.csproj index 49bc225f..a9b60b17 100644 --- a/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.csproj +++ b/templates/content/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp/dymaptic.GeoBlazor.Template.WebApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/templates/content/dymaptic.GeoBlazor.Template.WebAssembly/dymaptic.GeoBlazor.Template.WebAssembly.csproj b/templates/content/dymaptic.GeoBlazor.Template.WebAssembly/dymaptic.GeoBlazor.Template.WebAssembly.csproj index 43574f16..c62f1ddf 100644 --- a/templates/content/dymaptic.GeoBlazor.Template.WebAssembly/dymaptic.GeoBlazor.Template.WebAssembly.csproj +++ b/templates/content/dymaptic.GeoBlazor.Template.WebAssembly/dymaptic.GeoBlazor.Template.WebAssembly.csproj @@ -7,7 +7,7 @@ - + diff --git a/templates/dymaptic.GeoBlazor.Templates.csproj b/templates/dymaptic.GeoBlazor.Templates.csproj index ba0dd006..e590c9b5 100644 --- a/templates/dymaptic.GeoBlazor.Templates.csproj +++ b/templates/dymaptic.GeoBlazor.Templates.csproj @@ -3,7 +3,7 @@ dymaptic.GeoBlazor.Templates GeoBlazor Project Templates - 2.5.2 + 2.5.3 2.5.2 MIT dymaptic From de975c3d4b03737120b61d002f79ee773bad0a48 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Mon, 1 Jan 2024 15:43:13 -0600 Subject: [PATCH 15/27] Routes, new two-way protobuf implementations. --- .../Shared/NavMenu.razor | 1 - .../Components/ActionBase.cs | 72 +++-- .../Components/Geometries/Extent.cs | 16 +- .../Components/Geometries/Geometry.cs | 79 ++++-- .../Components/Geometries/SpatialReference.cs | 31 +- .../Components/Layers/FeatureSet.cs | 78 +++--- .../Components/Layers/Graphic.cs | 47 +++- .../Components/Layers/GraphicsLayer.cs | 15 +- .../Components/Popups/ExpressionInfo.cs | 75 ++++- .../Popups/ExpressionPopupContent.cs | 20 ++ .../Components/Popups/FieldInfo.cs | 61 ++-- .../Components/Popups/FieldInfoFormat.cs | 37 ++- .../Components/Popups/FieldsPopupContent.cs | 3 +- .../Components/Popups/MediaInfo.cs | 265 ++++++++++++++++-- .../Components/Popups/PopupContent.cs | 37 ++- .../Components/Popups/PopupTemplate.cs | 91 ++++-- .../Popups/RelationshipPopupContent.cs | 80 +++++- .../Components/Symbols/MapFont.cs | 48 +++- .../Components/Symbols/Symbol.cs | 75 ++++- .../Components/Symbols/TextSymbol.cs | 15 +- .../Objects/AttributesDictionary.cs | 80 +++++- .../Objects/MapColor.cs | 4 + .../Objects/MapPath.cs | 40 ++- .../Scripts/arcGisJsInterop.ts | 59 +++- .../Scripts/dotNetBuilder.ts | 65 ++++- .../Scripts/featureLayer.ts | 65 +---- .../Scripts/featureLayerView.ts | 28 +- .../Scripts/jsBuilder.ts | 22 +- .../wwwroot/graphic.json | 52 +++- .../Components/TestRunnerBase.razor | 6 +- 30 files changed, 1247 insertions(+), 320 deletions(-) diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor index 61abd219..e6429068 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor @@ -109,7 +109,6 @@ new("query-related-features", "Query Related Features", "oi-people"), new("query-top-features", "Query Top Features", "oi-arrow-thick-top"), new("place-selector", "Place Selector", "oi-arrow-bottom"), - new("routing", "Routing", "oi-transfer"), new("service-areas", "Service Areas", "oi-comment-square"), new("calculate-geometries", "Calculate Geometries", "oi-clipboard"), new("projection", "Display Projection", "oi-sun"), diff --git a/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs b/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs index 70bde145..76640c9c 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/ActionBase.cs @@ -69,34 +69,72 @@ public abstract class ActionBase : MapComponent } [ProtoContract(Name = "Action")] -internal record ActionBaseSerializationRecord([property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - string Type, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record ActionBaseSerializationRecord : MapComponentSerializationRecord +{ + public ActionBaseSerializationRecord() + { + } + + public ActionBaseSerializationRecord(string Type, string? Title, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] string? ClassName, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(4)] bool? Active, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(5)] bool? Disabled, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(6)] bool? Visible, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(7)] string? Id) - : MapComponentSerializationRecord -{ + { + this.Type = Type; + this.Title = Title; + this.ClassName = ClassName; + this.Active = Active; + this.Disabled = Disabled; + this.Visible = Visible; + this.Id = Id; + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public string Type { get; init; } = string.Empty; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? Title { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public string? ClassName { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(4)] + public bool? Active { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(5)] + public bool? Disabled { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(6)] + public bool? Visible { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(7)] + public string? Id { get; init; } + [ProtoMember(8)] public string? Image { get; init; } [ProtoMember(9)] public bool? Value { get; init; } + + public ActionBase FromSerializationRecord() + { + return Type switch + { + "button" => new ActionButton(Title, Image, Id, null, ClassName, Active, Disabled, Visible), + "toggle" => new ActionToggle(Title, Id, null, Value, Active, Disabled, Visible), + _ => throw new NotSupportedException() + }; + } } /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Extent.cs b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Extent.cs index 2d9bf805..3c8018a9 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Extent.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Extent.cs @@ -127,14 +127,14 @@ internal override GeometrySerializationRecord ToSerializationRecord() { return new GeometrySerializationRecord(Type, null, SpatialReference?.ToSerializationRecord()) { - XMax = Xmax, - XMin = Xmin, - YMax = Ymax, - YMin = Ymin, - ZMax = Zmax, - ZMin = Zmin, - MMax = Mmax, - MMin = Mmin + Xmax = Xmax, + Xmin = Xmin, + Ymax = Ymax, + Ymin = Ymin, + Zmax = Zmax, + Zmin = Zmin, + Mmax = Mmax, + Mmin = Mmin }; } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Geometry.cs b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Geometry.cs index 674b7422..a3ed03b3 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Geometry.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Geometry.cs @@ -102,59 +102,94 @@ protected override async Task OnParametersSetAsync() } [ProtoContract(Name = "Geometry")] -internal record GeometrySerializationRecord([property: ProtoMember(1)] string Type, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record GeometrySerializationRecord : MapComponentSerializationRecord +{ + public GeometrySerializationRecord() + { + } + + public GeometrySerializationRecord(string Type, GeometrySerializationRecord? Extent, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] SpatialReferenceSerializationRecord? SpatialReference) - : MapComponentSerializationRecord -{ + { + this.Type = Type; + this.Extent = Extent; + this.SpatialReference = SpatialReference; + } + + [ProtoMember(1)] + public string Type { get; set; } = string.Empty; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public GeometrySerializationRecord? Extent { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public SpatialReferenceSerializationRecord? SpatialReference { get; set; } + [ProtoMember(4)] - public double? Longitude { get; init; } + public double? Longitude { get; set; } [ProtoMember(5)] - public double? Latitude { get; init; } + public double? Latitude { get; set; } [ProtoMember(6)] - public double? X { get; init; } + public double? X { get; set; } [ProtoMember(7)] - public double? Y { get; init; } + public double? Y { get; set; } [ProtoMember(8)] - public double? Z { get; init; } + public double? Z { get; set; } [ProtoMember(9)] - public MapPathSerializationRecord[]? Paths { get; init; } + public MapPathSerializationRecord[]? Paths { get; set; } [ProtoMember(10)] - public MapPathSerializationRecord[]? Rings { get; init; } + public MapPathSerializationRecord[]? Rings { get; set; } [ProtoMember(11)] - public double? XMax { get; init; } + public double? Xmax { get; set; } [ProtoMember(12)] - public double? XMin { get; init; } + public double? Xmin { get; set; } [ProtoMember(13)] - public double? YMax { get; init; } + public double? Ymax { get; set; } [ProtoMember(14)] - public double? YMin { get; init; } + public double? Ymin { get; set; } [ProtoMember(15)] - public double? ZMax { get; init; } + public double? Zmax { get; set; } [ProtoMember(16)] - public double? ZMin { get; init; } + public double? Zmin { get; set; } [ProtoMember(17)] - public double? MMax { get; init; } + public double? Mmax { get; set; } [ProtoMember(18)] - public double? MMin { get; init; } + public double? Mmin { get; set; } + + public Geometry FromSerializationRecord() + { + return Type switch + { + "point" => new Point(Longitude, Latitude, X, Y, Z, SpatialReference?.FromSerializationRecord(), + Extent?.FromSerializationRecord() as Extent), + "polyline" => new PolyLine(Paths!.Select(x => x.FromSerializationRecord()).ToArray(), + SpatialReference?.FromSerializationRecord(), + Extent?.FromSerializationRecord() as Extent), + "polygon" => new Polygon(Rings!.Select(x => x.FromSerializationRecord()).ToArray(), + SpatialReference?.FromSerializationRecord(), + Extent?.FromSerializationRecord() as Extent), + "extent" => new Extent(Xmax!.Value, Xmin!.Value, Ymax!.Value, Ymin!.Value, Zmax, Zmin, + Mmax, Mmin, SpatialReference?.FromSerializationRecord()), + _ => throw new ArgumentOutOfRangeException() + }; + } } internal class GeometryConverter : JsonConverter diff --git a/src/dymaptic.GeoBlazor.Core/Components/Geometries/SpatialReference.cs b/src/dymaptic.GeoBlazor.Core/Components/Geometries/SpatialReference.cs index 1e2061bd..5ed8839b 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Geometries/SpatialReference.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Geometries/SpatialReference.cs @@ -191,11 +191,28 @@ public override void Write(Utf8JsonWriter writer, SpatialReference value, JsonSe } [ProtoContract(Name = "SpatialReference")] -internal record SpatialReferenceSerializationRecord( - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - int? Wkid, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record SpatialReferenceSerializationRecord : MapComponentSerializationRecord +{ + public SpatialReferenceSerializationRecord() + { + } + + public SpatialReferenceSerializationRecord(int? Wkid, string? Wkt = null) - : MapComponentSerializationRecord; \ No newline at end of file + { + this.Wkid = Wkid; + this.Wkt = Wkt; + } + + public SpatialReference FromSerializationRecord() + { + return new SpatialReference(Wkid ?? 4326); + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public int? Wkid { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? Wkt { get; init; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureSet.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureSet.cs index d4b50b09..18ae8ada 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureSet.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureSet.cs @@ -10,36 +10,48 @@ namespace dymaptic.GeoBlazor.Core.Components.Layers; /// FeatureSet include query. /// ArcGIS Maps SDK for JavaScript /// -/// -/// The name of the layer's primary display field. The value of this property matches the name of one of the fields of -/// the feature. This is only applicable when the FeatureSet is returned from a task. It is ignored when the FeatureSet -/// is used as input to a geoprocessing task. -/// -/// -/// Typically, a layer has a limit on the number of features (i.e., records) returned by the query operation. If -/// maxRecordCount is configured for a layer, exceededTransferLimit will be true if a query matches more than the -/// maxRecordCount features. It will be false otherwise. Supported by ArcGIS Server version 10.1 and later. -/// -/// -/// The array of graphics returned from a task. -/// -/// -/// Information about each field. -/// -/// -/// The geometry type of features in the FeatureSet. All features's geometry must be of the same type. -/// -/// -/// The geometry used to query the features. It is useful for getting the buffer geometry generated when querying -/// features by distance or getting the query geometry projected in the outSpatialReference of the query. The query -/// geometry is returned only for client-side queries and hosted feature services. The query's returnQueryGeometry must -/// be set to true and the layer's capabilities.query.supportsQueryGeometry has to be true for the query to return -/// query geometry. -/// -/// -/// When a FeatureSet is used as input to Geoprocessor, the spatial reference is set to the map's spatial reference by -/// default. This value can be changed. When a FeatureSet is returned from a task, the value is the result as returned -/// from the server. -/// -public record FeatureSet(string? DisplayFieldName, bool? ExceededTransferLimit, Graphic[]? Features, Field[]? Fields, - GeometryType? GeometryType, Geometry? QueryGeometry, SpatialReference? SpatialReference); \ No newline at end of file +public class FeatureSet +{ + /// + /// The name of the layer's primary display field. The value of this property matches the name of one of the fields of + /// the feature. This is only applicable when the FeatureSet is returned from a task. It is ignored when the FeatureSet + /// is used as input to a geoprocessing task. + /// + public string? DisplayFieldName { get; set; } + /// + /// Typically, a layer has a limit on the number of features (i.e., records) returned by the query operation. If + /// maxRecordCount is configured for a layer, exceededTransferLimit will be true if a query matches more than the + /// maxRecordCount features. It will be false otherwise. Supported by ArcGIS Server version 10.1 and later. + /// + public bool? ExceededTransferLimit { get; set; } + /// + /// The array of graphics returned from a task. + /// + public Graphic[]? Features { get; set; } + + /// + /// Information about each field. + /// + public Field[]? Fields { get; set; } + + /// + /// The geometry type of features in the FeatureSet. All features's geometry must be of the same type. + /// + public GeometryType? GeometryType { get; set; } + + /// + /// The geometry used to query the features. It is useful for getting the buffer geometry generated when querying + /// features by distance or getting the query geometry projected in the outSpatialReference of the query. The query + /// geometry is returned only for client-side queries and hosted feature services. The query's returnQueryGeometry must + /// be set to true and the layer's capabilities.query.supportsQueryGeometry has to be true for the query to return + /// query geometry. + /// + public Geometry? QueryGeometry { get; set; } + + /// + /// When a FeatureSet is used as input to Geoprocessor, the spatial reference is set to the map's spatial reference by + /// default. This value can be changed. When a FeatureSet is returned from a task, the value is the result as returned + /// from the server. + /// + public SpatialReference? SpatialReference { get; set; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs index 6df67937..6bfb9d1f 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs @@ -365,15 +365,44 @@ await LayerJsModule.InvokeVoidAsync("setGraphicAttributes", } [ProtoContract(Name = "Graphic")] -internal record GraphicSerializationRecord([property: ProtoMember(1)] string Id, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record GraphicSerializationRecord : MapComponentSerializationRecord +{ + public GraphicSerializationRecord() + { + } + + public GraphicSerializationRecord(string Id, GeometrySerializationRecord? Geometry, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] SymbolSerializationRecord? Symbol, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(4)] PopupTemplateSerializationRecord? PopupTemplate, - [property: ProtoMember(5)] AttributeSerializationRecord[]? Attributes) - : MapComponentSerializationRecord; \ No newline at end of file + AttributeSerializationRecord[]? Attributes) + { + this.Id = Id; + this.Geometry = Geometry; + this.Symbol = Symbol; + this.PopupTemplate = PopupTemplate; + this.Attributes = Attributes; + } + + public Graphic FromSerializationRecord() + { + return new Graphic(Geometry?.FromSerializationRecord(), + Symbol?.FromSerializationRecord(), + PopupTemplate?.FromSerializationRecord(), + new AttributesDictionary(Attributes)); + } + + [ProtoMember(1)] + public string Id { get; set; } = string.Empty; + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public GeometrySerializationRecord? Geometry { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public SymbolSerializationRecord? Symbol { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(4)] + public PopupTemplateSerializationRecord? PopupTemplate { get; set; } + [ProtoMember(5)] + public AttributeSerializationRecord[]? Attributes { get; set; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/GraphicsLayer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/GraphicsLayer.cs index 4e3abd4b..f94e2852 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/GraphicsLayer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/GraphicsLayer.cs @@ -400,4 +400,17 @@ public override void Write(Utf8JsonWriter writer, IReadOnlyCollection v } [ProtoContract] -internal record ProtoGraphicCollection([property: ProtoMember(1)] GraphicSerializationRecord[] Graphics); \ No newline at end of file +internal record ProtoGraphicCollection +{ + public ProtoGraphicCollection() + { + } + + public ProtoGraphicCollection(GraphicSerializationRecord[] graphics) + { + Graphics = graphics; + } + + [property: ProtoMember(1)] + public GraphicSerializationRecord[] Graphics { get; set; } = Array.Empty(); +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionInfo.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionInfo.cs index 54f8d874..5db84897 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionInfo.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionInfo.cs @@ -17,6 +17,38 @@ namespace dymaptic.GeoBlazor.Core.Components.Popups; /// public class ExpressionInfo : MapComponent { + /// + /// Parameterless constructor for using as a razor component + /// + public ExpressionInfo() + { + } + + /// + /// Constructor for creating a new ExpressionInfo in code with parameters + /// + /// + /// An Arcade expression following the specification defined by the Arcade Popup Profile. Expressions must return a + /// + /// + /// The name of the expression. This is used to reference the value of the given expression in the popupTemplate's + /// + /// + /// The title used to describe the value returned by the expression in the popup. This will display if the value is + /// + /// + /// Indicates the return type of the Arcade expression. + /// + public ExpressionInfo(string? expression, string? name, string? title, ReturnType? returnType) + { +#pragma warning disable BL0005 + Expression = expression; + Name = name; + Title = title; + ReturnType = returnType; +#pragma warning restore BL0005 + } + /// /// An Arcade expression following the specification defined by the Arcade Popup Profile. Expressions must return a /// string or a number and may access data values from the feature, its layer, or other layers in the map or datastore @@ -56,20 +88,41 @@ internal ExpressionInfoSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "ExpressionInfo")] -internal record ExpressionInfoSerializationRecord( - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - string? Expression, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record ExpressionInfoSerializationRecord : MapComponentSerializationRecord +{ + public ExpressionInfoSerializationRecord() + { + } + + public ExpressionInfoSerializationRecord(string? Expression, string? Name, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] string? Title, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(4)] ReturnType? ReturnType) - : MapComponentSerializationRecord; + { + this.Expression = Expression; + this.Name = Name; + this.Title = Title; + this.ReturnType = ReturnType; + } + + public ExpressionInfo FromSerializationRecord() + { + return new ExpressionInfo(); + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public string? Expression { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? Name { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public string? Title { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(4)] + public ReturnType? ReturnType { get; init; } +} /// /// Indicates the return type of the Arcade expression. diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionPopupContent.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionPopupContent.cs index 4c5b5f36..9afefe63 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionPopupContent.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/ExpressionPopupContent.cs @@ -20,6 +20,26 @@ namespace dymaptic.GeoBlazor.Core.Components.Popups; /// public class ExpressionPopupContent : PopupContent { + /// + /// Parameterless constructor for use as a razor component. + /// + public ExpressionPopupContent() + { + } + + /// + /// Constructor for creating a ExpressionPopupContent in code. + /// + /// + /// Contains the Arcade expression used to create a popup content element. See the ElementExpressionInfo documentation + /// + public ExpressionPopupContent(ElementExpressionInfo? expressionInfo) + { +#pragma warning disable BL0005 + ExpressionInfo = expressionInfo; +#pragma warning restore BL0005 + } + /// public override string Type => "expression"; diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs index 4070f42d..00141b7d 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfo.cs @@ -156,25 +156,54 @@ internal FieldInfoSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "FieldInfo")] -internal record FieldInfoSerializationRecord([property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - string? FieldName = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record FieldInfoSerializationRecord : MapComponentSerializationRecord +{ + public FieldInfoSerializationRecord() + { + } + + public FieldInfoSerializationRecord(string? FieldName = null, string? Label = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] string? Tooltip = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(4)] string? StringFieldOption = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(5)] FieldInfoFormatSerializationRecord? Format = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(6)] bool? IsEditable = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(7)] bool? Visible = null) - : MapComponentSerializationRecord; \ No newline at end of file + { + this.FieldName = FieldName; + this.Label = Label; + this.Tooltip = Tooltip; + this.StringFieldOption = StringFieldOption; + this.Format = Format; + this.IsEditable = IsEditable; + this.Visible = Visible; + } + + public FieldInfo FromSerializationRecord() + { + return new FieldInfo(FieldName, Label, Tooltip, StringFieldOption, + Format?.FromSerializationRecord(), IsEditable, Visible); + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public string? FieldName { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? Label { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public string? Tooltip { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(4)] + public string? StringFieldOption { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(5)] + public FieldInfoFormatSerializationRecord? Format { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(6)] + public bool? IsEditable { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(7)] + public bool? Visible { get; init; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfoFormat.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfoFormat.cs index a387d2da..599ebbff 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfoFormat.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldInfoFormat.cs @@ -69,14 +69,33 @@ internal FieldInfoFormatSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "FieldInfoFormat")] -internal record FieldInfoFormatSerializationRecord( - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - int? Places, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record FieldInfoFormatSerializationRecord : MapComponentSerializationRecord +{ + public FieldInfoFormatSerializationRecord() + { + } + + public FieldInfoFormatSerializationRecord(int? Places, bool? DigitSeparator, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] string? DateFormat) - : MapComponentSerializationRecord; \ No newline at end of file + { + this.Places = Places; + this.DigitSeparator = DigitSeparator; + this.DateFormat = DateFormat; + } + + public FieldInfoFormat FromSerializationRecord() + { + return new FieldInfoFormat(Places, DigitSeparator, DateFormat); + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public int? Places { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public bool? DigitSeparator { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public string? DateFormat { get; init; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldsPopupContent.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldsPopupContent.cs index a871b9e7..046be3b8 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldsPopupContent.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/FieldsPopupContent.cs @@ -126,7 +126,8 @@ internal override PopupContentSerializationRecord ToSerializationRecord() { return new PopupContentSerializationRecord(Type) { - FieldInfos = FieldInfos?.ToArray(), Description = Description, Title = Title + FieldInfos = FieldInfos?.Select(i => i.ToSerializationRecord()).ToArray(), + Description = Description, Title = Title }; } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs index 841e6530..051dc2c9 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs @@ -21,9 +21,20 @@ public abstract class MediaInfo : MapComponent } [ProtoContract(Name = "MediaInfo")] -internal record MediaInfoSerializationRecord([property: ProtoMember(1)] string Type) - : MapComponentSerializationRecord +internal record MediaInfoSerializationRecord : MapComponentSerializationRecord { + public MediaInfoSerializationRecord() + { + } + + public MediaInfoSerializationRecord(string Type) + { + this.Type = Type; + } + + [ProtoMember(1)] + public string Type { get; init; } = string.Empty; + [ProtoMember(2)] public string? AltText { get; init; } @@ -38,6 +49,25 @@ internal record MediaInfoSerializationRecord([property: ProtoMember(1)] string T [ProtoMember(6)] public double? RefreshInterval { get; init; } + + public MediaInfo FromSerializationRecord() + { + return Type switch + { + "bar-chart" => new BarChartMediaInfo(Title, Caption, AltText, + Value?.FromSerializationRecord() as ChartMediaInfoValue), + "column-chart" => new ColumnChartMediaInfo(Title, Caption, AltText, + Value?.FromSerializationRecord() as ChartMediaInfoValue), + "pie-chart" => new PieChartMediaInfo(Title, Caption, AltText, + Value?.FromSerializationRecord() as ChartMediaInfoValue), + "line-chart" => new LineChartMediaInfo(Title, Caption, AltText, + Value?.FromSerializationRecord() as ChartMediaInfoValue), + "image-media" => new ImageMediaInfo(Title, Caption, AltText, + Value?.FromSerializationRecord() as ImageMediaInfoValue, + RefreshInterval), + _ => throw new NotSupportedException($"MediaInfo type {Type} is not supported.") + }; + } } /// @@ -46,6 +76,24 @@ internal record MediaInfoSerializationRecord([property: ProtoMember(1)] string T /// public class BarChartMediaInfo : MediaInfo { + /// + /// Parameterless constructor for use as a razor component. + /// + public BarChartMediaInfo() + { + } + + public BarChartMediaInfo(string? title = null, string? caption = null, string? altText = null, + ChartMediaInfoValue? value = null) + { +#pragma warning disable BL0005 + Title = title; + Caption = caption; + AltText = altText; + Value = value; +#pragma warning restore BL0005 + } + /// public override string Type => "bar-chart"; @@ -259,23 +307,56 @@ internal ChartMediaInfoValueSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "ChartMediaInfoValue")] -internal record ChartMediaInfoValueSerializationRecord([property: ProtoMember(1)] IEnumerable? Fields = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record ChartMediaInfoValueSerializationRecord : MapComponentSerializationRecord +{ + public ChartMediaInfoValueSerializationRecord() + { + } + + public ChartMediaInfoValueSerializationRecord(IEnumerable? Fields = null, string? NormalizeField = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] string? TooltipField = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(4)] IEnumerable? Series = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(5)] string? LinkURL = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(6)] string? SourceURL = null) - : MapComponentSerializationRecord; + { + this.Fields = Fields; + this.NormalizeField = NormalizeField; + this.TooltipField = TooltipField; + this.Series = Series; + this.LinkURL = LinkURL; + this.SourceURL = SourceURL; + } + + public object FromSerializationRecord() + { + if (LinkURL is not null || SourceURL is not null) + { + return new ImageMediaInfoValue(LinkURL, SourceURL); + } + + return new ChartMediaInfoValue(Fields?.ToArray(), NormalizeField, TooltipField, + Series?.Select(s => s.FromSerializationRecord()).ToArray()); + } + + [ProtoMember(1)] + public IEnumerable? Fields { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? NormalizeField { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public string? TooltipField { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(4)] + public IEnumerable? Series { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(5)] + public string? LinkURL { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(6)] + public string? SourceURL { get; init; } +} /// /// The ChartMediaInfoValueSeries class is a read-only support class that represents information specific to how data @@ -341,17 +422,36 @@ internal ChartMediaInfoValueSeriesSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "ChartMediaInfoValueSeries")] -internal record ChartMediaInfoValueSeriesSerializationRecord( - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - string? FieldName, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record ChartMediaInfoValueSeriesSerializationRecord : MapComponentSerializationRecord +{ + public ChartMediaInfoValueSeriesSerializationRecord() + { + } + + public ChartMediaInfoValueSeriesSerializationRecord(string? FieldName, string? Tooltip, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] double? Value) - : MapComponentSerializationRecord; + { + this.FieldName = FieldName; + this.Tooltip = Tooltip; + this.Value = Value; + } + + public ChartMediaInfoValueSeries FromSerializationRecord() + { + return new ChartMediaInfoValueSeries(FieldName, Tooltip, Value); + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public string? FieldName { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? Tooltip { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public double? Value { get; init; } +} /// /// A ColumnChartMediaInfo is a type of chart media element that represents a column chart displayed within a popup. @@ -359,6 +459,39 @@ internal record ChartMediaInfoValueSeriesSerializationRecord( /// public class ColumnChartMediaInfo : MediaInfo { + /// + /// Parameterless constructor for use as a razor component. + /// + public ColumnChartMediaInfo() + { + } + + /// + /// Constructor for building a in code. + /// + /// + /// The title of the media element. + /// + /// + /// Defines a caption for the media. + /// + /// + /// Provides an alternate text for an image if the image cannot be displayed. + /// + /// + /// Defines the chart value. + /// + public ColumnChartMediaInfo(string? title = null, string? caption = null, string? altText = null, + ChartMediaInfoValue? value = null) + { +#pragma warning disable BL0005 + Title = title; + Caption = caption; + AltText = altText; + Value = value; +#pragma warning restore BL0005 + } + /// public override string Type => "column-chart"; @@ -443,6 +576,43 @@ internal override MediaInfoSerializationRecord ToSerializationRecord() /// public class ImageMediaInfo : MediaInfo { + /// + /// Parameterless constructor for use as a razor component. + /// + public ImageMediaInfo() + { + } + + /// + /// Constructor for building a in code. + /// + /// + /// The title of the media element. + /// + /// + /// Defines a caption for the media. + /// + /// + /// Provides an alternate text for an image if the image cannot be displayed. + /// + /// + /// Defines the value format of the image media element and how the images should be retrieved. + /// + /// + /// Refresh interval of the layer in minutes. Non-zero value indicates automatic layer refresh at the specified + /// + public ImageMediaInfo(string? title = null, string? caption = null, string? altText = null, + ImageMediaInfoValue? value = null, double? refreshInterval = null) + { +#pragma warning disable BL0005 + Title = title; + Caption = caption; + AltText = altText; + Value = value; + RefreshInterval = refreshInterval; +#pragma warning restore BL0005 + } + /// public override string Type => "image-media"; @@ -540,6 +710,30 @@ internal override MediaInfoSerializationRecord ToSerializationRecord() /// public class ImageMediaInfoValue : MapComponent { + /// + /// Parameterless constructor for use as a razor component. + /// + public ImageMediaInfoValue() + { + } + + /// + /// Constructor for building a in code. + /// + /// + /// A string containing a URL to be launched in a browser when a user clicks the image. + /// + /// + /// A string containing the URL to the image. + /// + public ImageMediaInfoValue(string? linkURL = null, string? sourceURL = null) + { +#pragma warning disable BL0005 + LinkURL = linkURL; + SourceURL = sourceURL; +#pragma warning restore BL0005 + } + /// /// A string containing a URL to be launched in a browser when a user clicks the image. /// @@ -560,19 +754,30 @@ internal ChartMediaInfoValueSerializationRecord ToSerializationRecord() } } -internal record ImageMediaInfoValueSerializationRecord( - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - string? LinkURL, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - string? SourceURL) - : MapComponentSerializationRecord; - /// /// A LineChartMediaInfo is a type of chart media element that represents a line chart displayed within a popup. /// ArcGIS Maps SDK for JavaScript /// public class LineChartMediaInfo : MediaInfo { + /// + /// Parameterless constructor for use as a razor component. + /// + public LineChartMediaInfo() + { + } + + public LineChartMediaInfo(string? title = null, string? caption = null, string? altText = null, + ChartMediaInfoValue? value = null) + { +#pragma warning disable BL0005 + Title = title; + Caption = caption; + AltText = altText; + Value = value; +#pragma warning restore BL0005 + } + /// public override string Type => "line-chart"; diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupContent.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupContent.cs index 51ac7af2..dba78562 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupContent.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupContent.cs @@ -22,9 +22,20 @@ public abstract class PopupContent : MapComponent } [ProtoContract(Name = "PopupContent")] -internal record PopupContentSerializationRecord([property: ProtoMember(1)] string Type) - : MapComponentSerializationRecord +internal record PopupContentSerializationRecord : MapComponentSerializationRecord { + public PopupContentSerializationRecord() + { + } + + public PopupContentSerializationRecord(string Type) + { + this.Type = Type; + } + + [ProtoMember(1)] + public string Type { get; init; } = string.Empty; + [ProtoMember(2)] public string? Description { get; init; } @@ -38,7 +49,7 @@ internal record PopupContentSerializationRecord([property: ProtoMember(1)] strin public ElementExpressionInfo? ExpressionInfo { get; init; } [ProtoMember(6)] - public FieldInfo[]? FieldInfos { get; init; } + public FieldInfoSerializationRecord[]? FieldInfos { get; init; } [ProtoMember(7)] public string? ActiveMediaInfoIndex { get; init; } @@ -57,6 +68,26 @@ internal record PopupContentSerializationRecord([property: ProtoMember(1)] strin [ProtoMember(12)] public string? Text { get; init; } + + public PopupContent FromSerializationRecord() + { + return Type switch + { + "fields" => new FieldsPopupContent(FieldInfos?.Select(i => + i.FromSerializationRecord()).ToArray() ?? Array.Empty(), + Description, Title), + "text" => new TextPopupContent(Text), + "attachments" => new AttachmentsPopupContent(Title, Description, DisplayType), + "expression" => new ExpressionPopupContent(ExpressionInfo), + "media" => new MediaPopupContent(Title, Description, + MediaInfos?.Select(i => i.FromSerializationRecord()).ToArray(), + ActiveMediaInfoIndex), + "relationship" => new RelationshipPopupContent(Title, Description, DisplayCount, + DisplayType, OrderByFields?.Select(x => x.FromSerializationRecord()).ToHashSet(), + RelationshipId), + _ => throw new NotSupportedException($"PopupContent type {Type} is not supported") + }; + } } internal class PopupContentConverter : JsonConverter diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupTemplate.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupTemplate.cs index c4c79c2f..567ed55f 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupTemplate.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/PopupTemplate.cs @@ -333,34 +333,81 @@ internal PopupTemplateSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "PopupTemplate")] -internal record PopupTemplateSerializationRecord([property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(1)] - string? Title, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] +internal record PopupTemplateSerializationRecord : MapComponentSerializationRecord +{ + public PopupTemplateSerializationRecord() + { + } + + public PopupTemplateSerializationRecord(string? Title, string? StringContent = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(3)] IEnumerable? OutFields = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(4)] IEnumerable? FieldInfos = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(5)] IEnumerable? Content = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(6)] IEnumerable? ExpressionInfos = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(7)] bool? OverwriteActions = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(8)] bool? ReturnGeometry = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(9)] IEnumerable? Actions = null, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(10)] string? Id = null) - : MapComponentSerializationRecord; \ No newline at end of file + { + this.Title = Title; + this.StringContent = StringContent; + this.OutFields = OutFields; + this.FieldInfos = FieldInfos; + this.Content = Content; + this.ExpressionInfos = ExpressionInfos; + this.OverwriteActions = OverwriteActions; + this.ReturnGeometry = ReturnGeometry; + this.Actions = Actions; + this.Id = Id; + } + + public PopupTemplate FromSerializationRecord() + { + return new PopupTemplate(Title, StringContent, OutFields, + FieldInfos?.Select(f => f.FromSerializationRecord()), + Content?.Select(c => c.FromSerializationRecord()), + ExpressionInfos?.Select(e => e.FromSerializationRecord()), OverwriteActions, + ReturnGeometry, Actions?.Select(a => a.FromSerializationRecord())); + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(1)] + public string? Title { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public string? StringContent { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(3)] + public IEnumerable? OutFields { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(4)] + public IEnumerable? FieldInfos { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(5)] + public IEnumerable? Content { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(6)] + public IEnumerable? ExpressionInfos { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(7)] + public bool? OverwriteActions { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(8)] + public bool? ReturnGeometry { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(9)] + public IEnumerable? Actions { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(10)] + public string? Id { get; init; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/RelationshipPopupContent.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/RelationshipPopupContent.cs index db55af06..ca4ab6d2 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/RelationshipPopupContent.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/RelationshipPopupContent.cs @@ -1,5 +1,6 @@ using dymaptic.GeoBlazor.Core.Objects; using Microsoft.AspNetCore.Components; +using ProtoBuf; using System.Text.Json.Serialization; @@ -19,6 +20,51 @@ namespace dymaptic.GeoBlazor.Core.Components.Popups; /// public class RelationshipPopupContent : PopupContent { + /// + /// Parameterless constructor for use as a razor component + /// + public RelationshipPopupContent() + { + } + + /// + /// Constructor for creating content in C# code. + /// + /// + /// A heading indicating what the relationship's content represents. + /// + /// + /// Describes the relationship's content in detail. + /// + /// + /// A numeric value indicating the maximum number of related features to display in the list of related records. The + /// + /// + /// A string value indicating how to display related records within the relationship content. + /// + /// + /// An array of RelatedRecordsInfoFieldOrder indicating the display order for the related records, and whether they + /// + /// + /// The numeric id value for the defined relationship. This value can be found on the service itself or on the + /// + public RelationshipPopupContent(string? title = null, string? description = null, int? displayCount = null, + string? displayType = null, HashSet? orderByFields = null, + int? relationshipId = null) + { +#pragma warning disable BL0005 + Title = title; + Description = description; + DisplayCount = displayCount; + DisplayType = displayType; + if (orderByFields is not null) + { + OrderByFields = orderByFields; + } + RelationshipId = relationshipId; +#pragma warning restore BL0005 + } + /// public override string Type => "relationship"; @@ -89,6 +135,30 @@ internal override PopupContentSerializationRecord ToSerializationRecord() /// public class RelatedRecordsInfoFieldOrder : MapComponent { + /// + /// Parameterless constructor for use as a razor component + /// + public RelatedRecordsInfoFieldOrder() + { + } + + /// + /// Constructor for creating a new RelatedRecordsInfoFieldOrder in code with parameters + /// + /// + /// The attribute value of the field selected that will drive the sorting of related records. + /// + /// + /// Set the ascending or descending sort order of the returned related records. + /// + public RelatedRecordsInfoFieldOrder(string? field = null, OrderBy? order = null) + { +#pragma warning disable BL0005 + Field = field; + Order = order; +#pragma warning restore BL0005 + } + /// /// The attribute value of the field selected that will drive the sorting of related records. /// @@ -109,4 +179,12 @@ internal RelatedRecordsInfoFieldOrderSerializationRecord ToSerializationRecord() } } -internal record RelatedRecordsInfoFieldOrderSerializationRecord(string? Field, OrderBy? Order); \ No newline at end of file +internal record RelatedRecordsInfoFieldOrderSerializationRecord( + [property: ProtoMember(1)] string? Field, + [property: ProtoMember(2)] OrderBy? Order) +{ + public RelatedRecordsInfoFieldOrder FromSerializationRecord() + { + return new(Field, Order); + } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Symbols/MapFont.cs b/src/dymaptic.GeoBlazor.Core/Components/Symbols/MapFont.cs index 4cd1296e..688901d3 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Symbols/MapFont.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Symbols/MapFont.cs @@ -14,6 +14,38 @@ namespace dymaptic.GeoBlazor.Core.Components.Symbols; [ProtoContract] public class MapFont : MapComponent { + /// + /// Parameterless constructor for using as a razor component + /// + public MapFont() + { + } + + /// + /// Constructs a new MapFont in code with parameters + /// + /// + /// The font size in points. + /// + /// + /// The font family of the text. + /// + /// + /// The text style. + /// + /// + /// The text weight. + /// + public MapFont(int? size, string? family, string? style, string? weight) + { +#pragma warning disable BL0005 + Size = size; + Family = family; + FontStyle = style; + Weight = weight; +#pragma warning restore BL0005 + } + /// /// The font size in points. /// @@ -43,18 +75,4 @@ public class MapFont : MapComponent [ProtoMember(4)] public string? Weight { get; set; } - internal MapFontSerializationRecord ToSerializationRecord() - { - return new MapFontSerializationRecord(Size, Family, FontStyle, Weight); - } -} - -internal record MapFontSerializationRecord([property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - int? Size, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - string? Family, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - string? Style, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - string? Weight) - : MapComponentSerializationRecord; \ No newline at end of file +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Symbols/Symbol.cs b/src/dymaptic.GeoBlazor.Core/Components/Symbols/Symbol.cs index 3ae93b09..7f0db6cf 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Symbols/Symbol.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Symbols/Symbol.cs @@ -80,12 +80,26 @@ public override void Write(Utf8JsonWriter writer, Symbol value, JsonSerializerOp } [ProtoContract(Name = "Symbol")] -internal record SymbolSerializationRecord([property: ProtoMember(1)] string Type, - [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [property: ProtoMember(2)] - MapColor? Color) - : MapComponentSerializationRecord +internal record SymbolSerializationRecord : MapComponentSerializationRecord { + public SymbolSerializationRecord() + { + } + + public SymbolSerializationRecord(string Type, + MapColor? Color) + { + this.Type = Type; + this.Color = Color; + } + + [ProtoMember(1)] + public string Type { get; init; } = string.Empty; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [ProtoMember(2)] + public MapColor? Color { get; init; } + [ProtoMember(3)] public SymbolSerializationRecord? Outline { get; init; } @@ -117,7 +131,7 @@ internal record SymbolSerializationRecord([property: ProtoMember(1)] string Type public MapColor? HaloColor { get; init; } [ProtoMember(13)] - public double? HaloSize { get; init; } + public int? HaloSize { get; init; } [ProtoMember(14)] public MapFont? MapFont { get; init; } @@ -127,4 +141,53 @@ internal record SymbolSerializationRecord([property: ProtoMember(1)] string Type [ProtoMember(16)] public string? Url { get; init; } + + [ProtoMember(17)] + public MapColor? BackgroundColor { get; init; } + + [ProtoMember(18)] + public double? BorderLineSize { get; init; } + + [ProtoMember(19)] + public MapColor? BorderLineColor { get; init; } + + [ProtoMember(20)] + public string? HorizontalAlignment { get; init; } + + [ProtoMember(21)] + public bool? Kerning { get; init; } + + [ProtoMember(22)] + public double? LineHeight { get; init; } + + [ProtoMember(23)] + public string? LineWidth { get; init; } + + [ProtoMember(24)] + public bool? Rotated { get; init; } + + [ProtoMember(25)] + public string? VerticalAlignment { get; init; } + + + public Symbol FromSerializationRecord() + { + return Type switch + { + "outline" => new Outline(Color, Width, LineStyle is null ? null : Enum.Parse(LineStyle!)), + "simple-marker" => new SimpleMarkerSymbol(Outline?.FromSerializationRecord() as Outline, Color, Size, Style, + Angle, XOffset, YOffset, Style is null ? null : Enum.Parse(Style!)), + "simple-line" => new SimpleLineSymbol(Color, Width, LineStyle is null ? null : Enum.Parse(LineStyle!)), + "simple-fill" => new SimpleFillSymbol(Outline?.FromSerializationRecord() as Outline, Color, + Style is null ? null : Enum.Parse(Style!)), + "picture-marker" => new PictureMarkerSymbol(Url!, Height, Width), + "text" => new TextSymbol(Text ?? string.Empty, Color, HaloColor, HaloSize, + MapFont, Angle, BackgroundColor, BorderLineColor, + BorderLineSize, HorizontalAlignment is null ? null : Enum.Parse(HorizontalAlignment!), + Kerning, LineHeight, LineWidth, Rotated, + VerticalAlignment is null ? null : Enum.Parse(VerticalAlignment!), + XOffset?.ToString(), YOffset?.ToString()), + _ => throw new ArgumentException($"Unknown symbol type: {Type}") + }; + } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs b/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs index c1744657..d049ca29 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs @@ -304,7 +304,20 @@ internal override SymbolSerializationRecord ToSerializationRecord() { return new SymbolSerializationRecord(Type, Color) { - Text = Text, HaloColor = HaloColor, HaloSize = HaloSize, MapFont = Font + Text = Text, + HaloColor = HaloColor, + HaloSize = HaloSize is null ? null : (int)HaloSize.Value, + MapFont = Font, + Angle = Angle, + BackgroundColor = BackgroundColor, + BorderLineSize = BorderLineSize, + BorderLineColor = BorderLineColor, + HorizontalAlignment = HorizontalAlignment?.ToString(), + Kerning = Kerning, + LineHeight = LineHeight, + LineWidth = LineWidth, + Rotated = Rotated, + VerticalAlignment = VerticalAlignment?.ToString() }; } } diff --git a/src/dymaptic.GeoBlazor.Core/Objects/AttributesDictionary.cs b/src/dymaptic.GeoBlazor.Core/Objects/AttributesDictionary.cs index 7f53e8fe..fed7e467 100644 --- a/src/dymaptic.GeoBlazor.Core/Objects/AttributesDictionary.cs +++ b/src/dymaptic.GeoBlazor.Core/Objects/AttributesDictionary.cs @@ -1,5 +1,8 @@ -using ProtoBuf; +using dymaptic.GeoBlazor.Core.Components; +using ProtoBuf; using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata; using System.Text.Json; using System.Text.Json.Serialization; @@ -66,6 +69,50 @@ public AttributesDictionary(Dictionary dictionary) } } + internal AttributesDictionary(AttributeSerializationRecord[]? serializedAttributes) + { + _backingDictionary = new Dictionary(); + + if (serializedAttributes is not null) + { + foreach (AttributeSerializationRecord record in serializedAttributes) + { + if (record.Value is null) continue; + + switch (record.ValueType) + { + case "[object Number]": + _backingDictionary[record.Key] = double.Parse(record.Value); + + break; + case "[object Boolean]": + _backingDictionary[record.Key] = bool.Parse(record.Value); + + break; + case "[object String]": + if (Guid.TryParse(record.Value, out Guid guidValue)) + { + _backingDictionary[record.Key] = guidValue; + } + else + { + _backingDictionary[record.Key] = record.Value; + } + + break; + case "[object Date]": + _backingDictionary[record.Key] = DateTime.Parse(record.Value); + + break; + default: + _backingDictionary[record.Key] = record.Value; + + break; + } + } + } + } + /// /// Implicit conversion from to AttributesDictionary. /// This is only provided for backwards compatibility and may be removed in a future release. @@ -333,8 +380,35 @@ public object this[string key] } [ProtoContract(Name = "Attribute")] -internal record AttributeSerializationRecord([property: ProtoMember(1)] string Key, - [property: ProtoMember(2)] string? Value, [property: ProtoMember(3)] string ValueType); +internal record AttributeSerializationRecord : MapComponentSerializationRecord +{ + public AttributeSerializationRecord() + { + } + + public AttributeSerializationRecord(string Key, + string? Value, + string ValueType) + { + this.Key = Key; + this.Value = Value; + this.ValueType = ValueType; + } + + [ProtoMember(1)] + public string Key { get; init; } = string.Empty; + [ProtoMember(2)] + public string? Value { get; init; } + [ProtoMember(3)] + public string ValueType { get; init; } = string.Empty; + + public void Deconstruct(out string Key, out string? Value, out string ValueType) + { + Key = this.Key; + Value = this.Value; + ValueType = this.ValueType; + } +} internal class AttributesDictionaryConverter : JsonConverter { diff --git a/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs b/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs index c1b8c13b..980fff40 100644 --- a/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs +++ b/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs @@ -14,6 +14,10 @@ namespace dymaptic.GeoBlazor.Core.Objects; [ProtoContract] public class MapColor : IEquatable { + public MapColor() + { + } + /// /// Creates a new color with a collection of numeric values in rgb or rgba format. /// diff --git a/src/dymaptic.GeoBlazor.Core/Objects/MapPath.cs b/src/dymaptic.GeoBlazor.Core/Objects/MapPath.cs index 753cfe47..d67ed973 100644 --- a/src/dymaptic.GeoBlazor.Core/Objects/MapPath.cs +++ b/src/dymaptic.GeoBlazor.Core/Objects/MapPath.cs @@ -172,10 +172,46 @@ internal MapPointSerializationRecord ToSerializationRecord() } [ProtoContract(Name = "MapPath")] -internal record MapPathSerializationRecord([property: ProtoMember(1)] MapPointSerializationRecord[] Points); +internal record MapPathSerializationRecord +{ + public MapPathSerializationRecord() + { + } + + public MapPathSerializationRecord(MapPointSerializationRecord[] Points) + { + this.Points = Points; + } + + public MapPath FromSerializationRecord() + { + return new MapPath(Points.Select(p => p.FromSerializationRecord())); + } + + [ProtoMember(1)] + public MapPointSerializationRecord[] Points { get; init; } = Array.Empty(); +} [ProtoContract(Name = "MapPoint")] -internal record MapPointSerializationRecord([property: ProtoMember(1)] double[] Coordinates); +internal record MapPointSerializationRecord +{ + public MapPointSerializationRecord() + { + } + + public MapPointSerializationRecord(double[] Coordinates) + { + this.Coordinates = Coordinates; + } + + public MapPoint FromSerializationRecord() + { + return new MapPoint(Coordinates); + } + + [ProtoMember(1)] + public double[] Coordinates { get; init; } = Array.Empty(); +} internal class MapPointEqualityComparer : EqualityComparer { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index 38a11358..c53b5294 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -113,7 +113,7 @@ import { DotNetRasterColormapRenderer, DotNetAlgorithmicColorRamp, DotNetEffect, - DotNetMultidimensionalSubset, + DotNetMultidimensionalSubset, DotNetGraphic, DotNetPolyline, DotNetPolygon, } from "./definitions"; import WebTileLayer from "@arcgis/core/layers/WebTileLayer"; @@ -224,12 +224,18 @@ export function getSerializedDotNetObject(id: string): any { return objectRef; } -export function getProjectionWrapper(dotNetRef: any): ProjectionWrapper { +export async function getProjectionWrapper(dotNetRef: any): Promise { + if (ProtoGraphicCollection === undefined) { + await loadProtobuf(); + } let wrapper = new ProjectionWrapper(dotNetRef); return wrapper; } -export function getGeometryEngineWrapper(dotNetRef: any): GeometryEngineWrapper { +export async function getGeometryEngineWrapper(dotNetRef: any): Promise { + if (ProtoGraphicCollection === undefined) { + await loadProtobuf(); + } let wrapper = new GeometryEngineWrapper(dotNetRef); return wrapper; } @@ -2825,7 +2831,7 @@ function buildHitTestOptions(options: DotNetHitTestOptions, view: MapView): MapV return hitOptions; } -let ProtoGraphicCollection; +export let ProtoGraphicCollection; export async function loadProtobuf() { load("_content/dymaptic.GeoBlazor.Core/graphic.json", function (err, root) { @@ -2842,7 +2848,6 @@ export async function loadProtobuf() { export async function getGraphicsFromProtobufStream(streamRef): Promise { try { const buffer = await streamRef.arrayBuffer(); - console.debug(new Date() + " - " + buffer.byteLength + " bytes received from server."); return decodeProtobufGraphics(new Uint8Array(buffer)); } catch (error) { logError(error, null); @@ -2859,17 +2864,55 @@ export function decodeProtobufGraphics(uintArray: Uint8Array): any[] { arrays: false, objects: false }); - console.debug(new Date() + " - " + array.graphics.length + " graphics decoded from protobuf."); return array.graphics; } -export function encodeProtobufGraphics(graphics: any[]): Uint8Array { +export function getProtobufGraphicStream(graphics: DotNetGraphic[]): any { + for (let i = 0; i < graphics.length; i++) { + let graphic = graphics[i]; + if (hasValue(graphic.attributes)) { + graphic.attributes = Object.keys(graphic.attributes).map(attr => { + return { + key: attr, + value: graphic.attributes[attr]?.toString(), + valueType: Object.prototype.toString.call(graphic.attributes[attr]) + } + }); + } + if (hasValue(graphic.geometry.paths)) { + graphic.geometry.paths = (graphic.geometry as DotNetPolyline).paths.map(p => { + return { + points: p.map(pt => { + return { + coordinates: pt + } + }) + } + }); + } else { + graphic.geometry.paths = []; + } + if (hasValue(graphic.geometry.rings)) { + graphic.geometry.rings = (graphic.geometry as DotNetPolygon).rings.map(r => { + return { + points: r.map(pt => { + return { + coordinates: pt + } + }) + } + }); + } else { + graphic.geometry.rings = []; + } + } let obj = { graphics: graphics }; let collection = ProtoGraphicCollection.fromObject(obj); let encoded = ProtoGraphicCollection.encode(collection).finish(); - return encoded; + // @ts-ignore + return DotNet.createJSStreamReference(encoded); } let _authenticationManager: AuthenticationManager | null = null; diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts index a08c36ba..bb3a5717 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts @@ -50,7 +50,7 @@ import { DotNetInheritedDomain, DotNetRangeDomain, DotNetEffect, - DotNetFeatureTemplate + DotNetFeatureTemplate, DotNetFeatureSet } from "./definitions"; import Point from "@arcgis/core/geometry/Point"; import Polyline from "@arcgis/core/geometry/Polyline"; @@ -79,7 +79,7 @@ import ExpressionContent from "@arcgis/core/popup/content/ExpressionContent"; import ElementExpressionInfo from "@arcgis/core/popup/ElementExpressionInfo"; import FeatureLayer from "@arcgis/core/layers/FeatureLayer"; import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer"; -import {arcGisObjectRefs, copyValuesIfExists, hasValue} from "./arcGisJsInterop"; +import {arcGisObjectRefs, copyValuesIfExists, dotNetRefs, graphicsRefs, hasValue} from "./arcGisJsInterop"; import SimpleMarkerSymbol from "@arcgis/core/symbols/SimpleMarkerSymbol"; import Symbol from "@arcgis/core/symbols/Symbol"; import Graphic from "@arcgis/core/Graphic"; @@ -120,6 +120,8 @@ import SizeSchemeForPoint = __esri.SizeSchemeForPoint; import SizeSchemeForPolygon = __esri.SizeSchemeForPolygon; import UniqueValuesResult = __esri.UniqueValuesResult; import AuthoringInfo from "@arcgis/core/renderers/support/AuthoringInfo"; +import FeatureSet from "@arcgis/core/rest/support/FeatureSet"; +import DirectionsFeatureSet from "@arcgis/core/rest/support/DirectionsFeatureSet"; export function buildDotNetGraphic(graphic: Graphic): DotNetGraphic { @@ -1185,4 +1187,63 @@ export function buildDotNetEditsResult(jsResult: __esri.EditsResult): any { }); } return dnResult; +} + +export async function buildDotNetFeatureSet(jsFs: FeatureSet, viewId: string | null): Promise { + let dotNetFeatureSet: DotNetFeatureSet = { + features: [], + displayFieldName: jsFs.displayFieldName, + exceededTransferLimit: jsFs.exceededTransferLimit, + fields: jsFs.fields, + geometryType: jsFs.geometryType, + queryGeometry: buildDotNetGeometry(jsFs.queryGeometry), + spatialReference: buildDotNetSpatialReference(jsFs.spatialReference) + }; + let graphics: DotNetGraphic[] = []; + for (let i = 0; i < jsFs.features.length; i++) { + let feature = jsFs.features[i]; + let graphic: DotNetGraphic = buildDotNetGraphic(feature) as DotNetGraphic; + if (viewId !== undefined && viewId !== null) { + graphic.id = await dotNetRefs[viewId].invokeMethodAsync('GetId'); + graphicsRefs[graphic.id as string] = feature; + } + graphics.push(graphic); + } + dotNetFeatureSet.features = graphics; + + return dotNetFeatureSet; +} + +export async function buildDotNetDirectionsFeatureSet(jsFs: DirectionsFeatureSet, viewId: string | null): Promise { + let dotNetFeatureSet: any = { + features: [], + displayFieldName: jsFs.displayFieldName, + exceededTransferLimit: jsFs.exceededTransferLimit, + fields: jsFs.fields, + geometryType: jsFs.geometryType, + queryGeometry: buildDotNetGeometry(jsFs.queryGeometry), + spatialReference: buildDotNetSpatialReference(jsFs.spatialReference) + }; + let graphics: DotNetGraphic[] = []; + for (let i = 0; i < jsFs.features.length; i++) { + let feature = jsFs.features[i]; + let graphic: DotNetGraphic = buildDotNetGraphic(feature) as DotNetGraphic; + if (viewId !== undefined && viewId !== null) { + graphic.id = await dotNetRefs[viewId].invokeMethodAsync('GetId'); + graphicsRefs[graphic.id as string] = feature; + } + graphics.push(graphic); + } + dotNetFeatureSet.features = graphics; + if (hasValue(jsFs.extent)) { + dotNetFeatureSet.extent = buildDotNetExtent(jsFs.extent); + } + if (hasValue(jsFs.mergedGeometry)) { + dotNetFeatureSet.mergedGeometry = buildDotNetGeometry(jsFs.mergedGeometry); + } + + copyValuesIfExists(jsFs, dotNetFeatureSet, 'routeId', 'routeName', 'strings', + 'totalDriveTime', 'totalLength', 'totalTime'); + + return dotNetFeatureSet; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index 9a7166ed..77e6ef08 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -32,7 +32,7 @@ import { buildDotNetFeatureLayer, buildDotNetDomain, buildDotNetFeatureType, - buildDotNetEditsResult, + buildDotNetEditsResult, buildDotNetFeatureSet, } from "./dotNetBuilder"; import { blazorServer, @@ -90,26 +90,7 @@ export default class FeatureLayerWrapper { let featureSet = await this.layer.queryFeatures(jsQuery, options); - let dotNetFeatureSet: DotNetFeatureSet = { - features: [], - displayFieldName: featureSet.displayFieldName, - exceededTransferLimit: featureSet.exceededTransferLimit, - fields: featureSet.fields, - geometryType: featureSet.geometryType, - queryGeometry: buildDotNetGeometry(featureSet.queryGeometry), - spatialReference: buildDotNetSpatialReference(featureSet.spatialReference) - }; - let graphics: DotNetGraphic[] = []; - for (let i = 0; i < featureSet.features.length; i++) { - let feature = featureSet.features[i]; - let graphic: DotNetGraphic = buildDotNetGraphic(feature) as DotNetGraphic; - if (viewId !== undefined && viewId !== null) { - graphic.id = await dotNetRefs[viewId].invokeMethodAsync('GetId'); - graphicsRefs[graphic.id as string] = feature; - } - graphics.push(graphic); - } - dotNetFeatureSet.features = graphics; + let dotNetFeatureSet = await buildDotNetFeatureSet(featureSet, viewId); if (!blazorServer || dotNetRef === undefined || dotNetRef === null) { return dotNetFeatureSet; } @@ -146,26 +127,7 @@ export default class FeatureLayerWrapper { for (let prop in featureSetsDictionary) { if (featureSetsDictionary.hasOwnProperty(prop)) { let featureSet = featureSetsDictionary[prop] as FeatureSet; - let dotNetFeatureSet: DotNetFeatureSet = { - features: [], - displayFieldName: featureSet.displayFieldName, - exceededTransferLimit: featureSet.exceededTransferLimit, - fields: featureSet.fields, - geometryType: featureSet.geometryType, - queryGeometry: buildDotNetGeometry(featureSet.queryGeometry), - spatialReference: buildDotNetSpatialReference(featureSet.spatialReference) - }; - let graphics: DotNetGraphic[] = []; - for (let i = 0; i < featureSet.features.length; i++) { - let feature = featureSet.features[i]; - let graphic: DotNetGraphic = buildDotNetGraphic(feature) as DotNetGraphic; - if (viewId !== undefined && viewId !== null) { - graphic.id = await dotNetRefs[viewId].invokeMethodAsync('GetId'); - graphicsRefs[graphic.id as string] = feature; - } - graphics.push(graphic); - } - dotNetFeatureSet.features = graphics; + let dotNetFeatureSet = await buildDotNetFeatureSet(featureSet, viewId); graphicsDictionary[prop] = dotNetFeatureSet; } } @@ -197,26 +159,7 @@ export default class FeatureLayerWrapper { try { let jsQuery = buildJsTopFeaturesQuery(query); let featureSet = await this.layer.queryTopFeatures(jsQuery, options); - let dotNetFeatureSet: DotNetFeatureSet = { - features: [], - displayFieldName: featureSet.displayFieldName, - exceededTransferLimit: featureSet.exceededTransferLimit, - fields: featureSet.fields, - geometryType: featureSet.geometryType, - queryGeometry: buildDotNetGeometry(featureSet.queryGeometry), - spatialReference: buildDotNetSpatialReference(featureSet.spatialReference) - }; - let graphics: DotNetGraphic[] = []; - for (let i = 0; i < featureSet.features.length; i++) { - let feature = featureSet.features[i]; - let graphic: DotNetGraphic = buildDotNetGraphic(feature) as DotNetGraphic; - if (viewId !== undefined && viewId !== null) { - graphic.id = await dotNetRefs[viewId].invokeMethodAsync('GetId'); - graphicsRefs[graphic.id as string] = feature; - } - graphics.push(graphic); - } - dotNetFeatureSet.features = graphics; + let dotNetFeatureSet = await buildDotNetFeatureSet(featureSet, viewId); if (!blazorServer) { return dotNetFeatureSet; } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts index 01c4e4f5..771c4f87 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts @@ -3,7 +3,12 @@ import Query from "@arcgis/core/rest/support/Query"; import {DotNetFeatureEffect, DotNetFeatureFilter, DotNetFeatureSet, DotNetGraphic, DotNetQuery} from "./definitions"; import {buildJsFeatureEffect, buildJsFeatureFilter, buildJsQuery} from "./jsBuilder"; import {blazorServer, dotNetRefs, graphicsRefs} from "./arcGisJsInterop"; -import {buildDotNetGeometry, buildDotNetGraphic, buildDotNetSpatialReference} from "./dotNetBuilder"; +import { + buildDotNetFeatureSet, + buildDotNetGeometry, + buildDotNetGraphic, + buildDotNetSpatialReference +} from "./dotNetBuilder"; import FeatureEffect from "@arcgis/core/layers/support/FeatureEffect"; import FeatureFilter from "@arcgis/core/layers/support/FeatureFilter"; import Handle = __esri.Handle; @@ -64,26 +69,7 @@ export default class FeatureLayerViewWrapper { try { let jsQuery = buildJsQuery(query); let featureSet = await this.featureLayerView.queryFeatures(jsQuery, options); - let dotNetFeatureSet : DotNetFeatureSet = { - features: [], - displayFieldName: featureSet.displayFieldName, - exceededTransferLimit: featureSet.exceededTransferLimit, - fields: featureSet.fields, - geometryType: featureSet.geometryType, - queryGeometry: buildDotNetGeometry(featureSet.queryGeometry), - spatialReference: buildDotNetSpatialReference(featureSet.spatialReference) - }; - let graphics : DotNetGraphic[] = []; - for (let i = 0; i < featureSet.features.length; i++) { - let feature = featureSet.features[i]; - let graphic : DotNetGraphic = buildDotNetGraphic(feature) as DotNetGraphic; - if (viewId !== undefined && viewId !== null) { - graphic.id = await dotNetRefs[viewId].invokeMethodAsync('GetId'); - graphicsRefs[graphic.id as string] = feature; - } - graphics.push(graphic); - } - dotNetFeatureSet.features = graphics; + let dotNetFeatureSet = await buildDotNetFeatureSet(featureSet, viewId); if (!blazorServer) { return dotNetFeatureSet; } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts index 470d0608..7a27a977 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts @@ -100,7 +100,7 @@ import { DotNetUniqueValueRenderer, DotNetVectorFieldRenderer, DotNetViewpoint, - DotNetVisualVariable, + DotNetVisualVariable, DotNetFeatureSet, } from "./definitions"; import PictureMarkerSymbol from "@arcgis/core/symbols/PictureMarkerSymbol"; import Popup from "@arcgis/core/widgets/Popup"; @@ -172,6 +172,7 @@ import AuthoringInfo from "@arcgis/core/renderers/support/AuthoringInfo"; import AuthoringInfoVisualVariable from "@arcgis/core/renderers/support/AuthoringInfoVisualVariable"; import ActionButton from "@arcgis/core/support/actions/ActionButton"; import ActionToggle from "@arcgis/core/support/actions/ActionToggle"; +import FeatureSet from "@arcgis/core/rest/support/FeatureSet"; export function buildJsSpatialReference(dotNetSpatialReference: DotNetSpatialReference): SpatialReference { @@ -1967,3 +1968,22 @@ function buildJsSupportExpressionInfo(dnEI: any): supportExpressionInfo | null { title: dnEI.title ?? undefined } as supportExpressionInfo; } + +export function buildJsFeatureSet(dnFs: DotNetFeatureSet, viewId: string | null): FeatureSet { + let jsFeatureSet = new FeatureSet(); + copyValuesIfExists(dnFs, jsFeatureSet, 'displayFieldName', 'exceededTransferLimit', + 'geometryType'); + if (hasValue(dnFs.features)) { + jsFeatureSet.features = dnFs.features.map(f => buildJsGraphic(f, viewId) as Graphic); + } + if (hasValue(dnFs.fields)) { + jsFeatureSet.fields = dnFs.fields.map(f => buildJsField(f)); + } + if (hasValue(dnFs.queryGeometry)) { + jsFeatureSet.queryGeometry = buildJsGeometry(dnFs.queryGeometry as DotNetGeometry) as Geometry; + } + if (hasValue(dnFs.spatialReference)) { + jsFeatureSet.spatialReference = buildJsSpatialReference(dnFs.spatialReference as DotNetSpatialReference); + } + return jsFeatureSet; +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/wwwroot/graphic.json b/src/dymaptic.GeoBlazor.Core/wwwroot/graphic.json index 14584052..fe792e26 100644 --- a/src/dymaptic.GeoBlazor.Core/wwwroot/graphic.json +++ b/src/dymaptic.GeoBlazor.Core/wwwroot/graphic.json @@ -102,35 +102,35 @@ "type": "MapPath", "id": 10 }, - "xMax": { + "xmax": { "type": "double", "id": 11 }, - "xMin": { + "xmin": { "type": "double", "id": 12 }, - "yMax": { + "ymax": { "type": "double", "id": 13 }, - "yMin": { + "ymin": { "type": "double", "id": 14 }, - "zMax": { + "zmax": { "type": "double", "id": 15 }, - "zMin": { + "zmin": { "type": "double", "id": 16 }, - "mMax": { + "mmax": { "type": "double", "id": 17 }, - "mMin": { + "mmin": { "type": "double", "id": 18 } @@ -231,6 +231,42 @@ "url": { "type": "string", "id": 16 + }, + "backgroundColor": { + "type": "MapColor", + "id": 17 + }, + "borderLineSize": { + "type": "double", + "id": 18 + }, + "borderLineColor": { + "type": "MapColor", + "id": 19 + }, + "horizontalAlignment": { + "type": "string", + "id": 20 + }, + "kerning": { + "type": "bool", + "id": 21 + }, + "lineHeight": { + "type": "double", + "id": 22 + }, + "lineWidth": { + "type": "string", + "id": 23 + }, + "rotated": { + "type": "bool", + "id": 24 + }, + "verticalAlignment": { + "type": "string", + "id": 25 } } }, diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/TestRunnerBase.razor b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/TestRunnerBase.razor index 1bf1241d..0eee661e 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/TestRunnerBase.razor +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/TestRunnerBase.razor @@ -159,7 +159,11 @@ if (hasParameters) { void RenderHandler() => _methodsWithRenderedMaps.Add(methodInfo.Name); - await (Task)methodInfo.Invoke(this, new object[] { (Action)RenderHandler })!; + await (Task)methodInfo.Invoke(this, [(Action)RenderHandler])!; + } + else + { + await (Task)methodInfo.Invoke(this, null)!; } _passed[methodInfo.Name] = _resultBuilder.ToString(); From 5665b0af312fefecb1eff37008edfec51fdd0ffb Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Fri, 5 Jan 2024 15:04:45 -0600 Subject: [PATCH 16/27] invert authentication manager order of checks for AppId first. --- .../Scripts/authenticationManager.ts | 7 ++++--- src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/authenticationManager.ts b/src/dymaptic.GeoBlazor.Core/Scripts/authenticationManager.ts index d184cf9a..c0107572 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/authenticationManager.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/authenticationManager.ts @@ -8,9 +8,7 @@ export default class AuthenticationManager { private dotNetRef: any; constructor(dotNetReference, apiKey, appId, portalUrl) { - if (apiKey !== null) { - esriConfig.apiKey = apiKey; - } else { + if (appId !== null) { this.appId = appId; this.info = new OAuthInfo({ appId: appId, @@ -21,7 +19,10 @@ export default class AuthenticationManager { this.info.portalUrl = portalUrl; } IdentityManager.registerOAuthInfos([this.info]); + } else if (apiKey !== null) { + esriConfig.apiKey = apiKey; } + this.dotNetRef = dotNetReference; } diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 73536813..8819d25c 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -111,7 +111,7 @@ - + From 5989634fba728920ddf6a39936019d53f657c0ee Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 6 Jan 2024 08:42:50 -0600 Subject: [PATCH 17/27] Add missing xml comments, test gh action --- .../Components/Popups/MediaInfo.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs b/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs index 051dc2c9..2f3b1067 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Popups/MediaInfo.cs @@ -83,6 +83,21 @@ public BarChartMediaInfo() { } + /// + /// Constructor for building a in code. + /// + /// + /// The title of the media element. + /// + /// + /// Defines a caption for the media. + /// + /// + /// Provides an alternate text for an image if the image cannot be displayed. + /// + /// + /// Defines the chart value. + /// public BarChartMediaInfo(string? title = null, string? caption = null, string? altText = null, ChartMediaInfoValue? value = null) { @@ -767,6 +782,21 @@ public LineChartMediaInfo() { } + /// + /// Constructor for building a in code. + /// + /// + /// The title of the media element. + /// + /// + /// Defines a caption for the media. + /// + /// + /// Provides an alternate text for an image if the image cannot be displayed. + /// + /// + /// Defines the chart value. + /// public LineChartMediaInfo(string? title = null, string? caption = null, string? altText = null, ChartMediaInfoValue? value = null) { From 58c7c2e09d2ef8a29086829812621738c9d8b747 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 6 Jan 2024 08:55:32 -0600 Subject: [PATCH 18/27] add missing xml --- src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs b/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs index 980fff40..645ad8dd 100644 --- a/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs +++ b/src/dymaptic.GeoBlazor.Core/Objects/MapColor.cs @@ -14,6 +14,10 @@ namespace dymaptic.GeoBlazor.Core.Objects; [ProtoContract] public class MapColor : IEquatable { + /// + /// Parameterless constructor for Protobuf deserialization. + /// Not intended for public use. + /// public MapColor() { } From 38a10dbb01483ca4b8363bc1ffa93c0bd52afcd2 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 6 Jan 2024 09:35:25 -0600 Subject: [PATCH 19/27] update copyright year, add doc generation into build pipeline to enforce xml --- .github/workflows/dev-pr-build.yml | 2 +- src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-pr-build.yml b/.github/workflows/dev-pr-build.yml index 4ac8726e..7fa7efcb 100644 --- a/.github/workflows/dev-pr-build.yml +++ b/.github/workflows/dev-pr-build.yml @@ -25,7 +25,7 @@ jobs: - name: Restore dependencies run: dotnet restore ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj - name: Build Core - run: dotnet build ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj --no-restore /p:OptOutFromCoreEsBuild=false /p:GenerateDocs=false /p:UpdateTemplates=true -c Release + run: dotnet build ./src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj --no-restore /p:OptOutFromCoreEsBuild=false /p:GenerateDocs=true /p:UpdateTemplates=true -c Release - name: Add & Commit # Saves updated template versions diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 8819d25c..937e90ae 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -13,7 +13,7 @@ $(CoreVersion) dymaptic dymaptic - ©2023 by dymaptic + ©2024 by dymaptic ReadMe.md https://github.com/dymaptic/GeoBlazor https://geoblazor.com From 7659fdadb136af2556b7fda1c46f07716add1b58 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 14 Jan 2024 18:54:35 -0600 Subject: [PATCH 20/27] build fixes --- .../dymaptic.GeoBlazor.Core.Sample.Shared.csproj | 2 +- src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj index 2e4a4375..3e880a99 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 937e90ae..25684afd 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -32,6 +32,7 @@ + From ba61383fa552178854bc1263d00270f4832b156d Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 14 Jan 2024 21:50:51 -0600 Subject: [PATCH 21/27] Simplify registration, don't require callback to server --- .../RegistrationValidator.cs | 227 +++--------------- 1 file changed, 36 insertions(+), 191 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs index 1dce542e..feb60072 100644 --- a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs +++ b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Net.Http.Json; using System.Security; +using System.Text; using System.Text.Json; using System.Web; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member @@ -18,157 +19,59 @@ public interface IAppValidator internal class RegistrationValidator: IAppValidator { - public RegistrationValidator(GeoBlazorSettings settings, IJSRuntime jsRuntime, HttpClient httpClient, - NavigationManager navigationManager, IConfiguration configuration) + public RegistrationValidator(GeoBlazorSettings settings, IJSRuntime jsRuntime, NavigationManager navigationManager) { _settings = settings; _jsRuntime = jsRuntime; - _httpClient = httpClient; _navigationManager = navigationManager; - _machineName = Environment.MachineName; -#if DEBUG - _licenseServerUrl = configuration["LicenseServerUrl"] ?? "https://licensing-dev.dymaptic.com/"; -#endif } public async Task ValidateLicense() { - // if we've already shown the message, there's no need to check again - if (_messageShown) + // if we've already shown the message or validated, there's no need to check again + if (_messageShown || _isValidated) { return; } - // if we've already found a valid license while the software is running, there's no need to check - if ( _inMemoryValidationResult is not null && _inMemoryValidationResult.IsValid) - { - if (_inMemoryValidationResult.BaseUri != _navigationManager.BaseUri) - { - _inMemoryValidationResult = null; - } - else - { - return; - } - } + if (_validating) return; - string? storedValidation; - BlazorMode blazorMode; + _validating = true; - if (_jsRuntime.GetType().Name.Contains("Remote")) // Server - { - blazorMode = BlazorMode.Server; - storedValidation = await GetServerFileValidationResult(); - } - else - { - blazorMode = OperatingSystem.IsBrowser() ? BlazorMode.WebAssembly : BlazorMode.Maui; - storedValidation = await _jsRuntime.InvokeAsync("localStorage.getItem", "validationResult"); - } + BlazorMode blazorMode = _jsRuntime.GetType().Name.Contains("Remote") ? BlazorMode.Server : + OperatingSystem.IsBrowser() ? BlazorMode.WebAssembly : BlazorMode.Maui; - ValidationResult? validationResult = null; - ValidationResult? storedValidationResult = null; + string? registration = _settings.RegistrationKey; + bool valid = registration is not null; - if (storedValidation is not null) + if (valid) { try { - storedValidationResult = JsonSerializer.Deserialize(storedValidation); + string registrationText = Encoding.UTF8.GetString(Convert.FromBase64String(registration!)); - // don't use stored results with a different base uri or machine name - if (storedValidationResult?.BaseUri != _navigationManager.BaseUri || - !storedValidationResult.MachineName.Equals(_machineName, StringComparison.OrdinalIgnoreCase)) + RegistrationObject registrationObject = + JsonSerializer.Deserialize(registrationText)!; + if (valid && registrationObject!.LicenseVersion != 1) { - storedValidationResult = null; + valid = false; } - - if (storedValidationResult?.AttemptedConnect is not null && - storedValidationResult.AttemptedConnect.Value.AddMinutes(5) > DateTime.UtcNow) + if (valid && registrationObject.LicenseType != "Free") { - // too soon to check again, the connection was down - return; + valid = false; } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - - // if there is no valid stored license, call the server to get a new license - if ((storedValidationResult is null || !storedValidationResult.IsValid) && - _settings.RegistrationKey is not null) - { - try - { - var queryString = new Dictionary - { - { "licenseKey", HttpUtility.UrlEncode(_settings.RegistrationKey) }, - { "licenseTypeName", "Free" }, - { "softwareName", "GeoBlazorCore" } - }; - - if (!string.IsNullOrWhiteSpace(_settings.MauiAppName)) - { - queryString["mauiAppName"] = HttpUtility.UrlEncode(_settings.MauiAppName); - } - - // build query string without QueryHelpers, which is not available in WebAssembly - var queryStringString = string.Join("&", queryString.Select(kvp => $"{kvp.Key}={kvp.Value}")); -#if DEBUG - var url = $"{_licenseServerUrl}/api/validate?{queryStringString}"; -#else - var url = $"https://licensing.dymaptic.com/api/validate?{queryStringString}"; -#endif - _httpClient.DefaultRequestHeaders.Referrer = new Uri(_navigationManager.BaseUri); - HttpResponseMessage response = await _httpClient.GetAsync(url); - - validationResult = await response.Content.ReadFromJsonAsync(); - - if (validationResult is not null) - { - validationResult.MachineName = _machineName; - validationResult.BaseUri = _navigationManager.BaseUri; - string jsonResult = JsonSerializer.Serialize(validationResult); - - await SaveFile(blazorMode, jsonResult); - } - } - catch (HttpRequestException) - { - if (storedValidationResult?.AttemptedConnect is not null) + if (valid && registrationObject.Software != "GeoBlazorCore") { - validationResult = storedValidationResult; - validationResult.AttemptedConnect = DateTime.UtcNow; + valid = false; } - else - { - validationResult = - new ValidationResult(false, DateTime.MaxValue, "Unable to reach license server.") - { - AttemptedConnect = DateTime.UtcNow - }; - } - - string jsonResult = JsonSerializer.Serialize(validationResult); - await SaveFile(blazorMode, jsonResult); - - // the server appears to be down, try again in 5 minutes - return; } - catch (Exception) + catch { - // don't throw anything here, we will deal with failure after checking stored result + valid = false; } } - - // if we failed to reach the server, or the server returned an error, use the stored result - if (validationResult is null && storedValidationResult is not null) - { - validationResult = storedValidationResult; - } - - if (validationResult is null || !validationResult.IsValid) + + if (!valid) { if (!_messageShown) { @@ -180,86 +83,22 @@ public async Task ValidateLicense() } _messageShown = true; + return; } - return; - } - - _inMemoryValidationResult = validationResult; - } - - private async Task GetServerFileValidationResult() - { - string directoryPath = _settings.ValidationServerStoragePath ?? Path.GetTempPath(); - string filePath = Path.Combine(directoryPath, ServerFileName); - - if (!File.Exists(filePath)) - { - return null; - } - - return await File.ReadAllTextAsync(filePath); - } - - private async Task SaveFile(BlazorMode blazorMode, string encodedResult) - { - if (blazorMode == BlazorMode.Server) // Server - { - await SaveServerFileValidationResult(encodedResult); } - else - { - await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "validationResult", encodedResult); - } - } - - private async Task SaveServerFileValidationResult(string result) - { - string directoryPath = _settings.ValidationServerStoragePath ?? Path.GetTempPath(); - try - { - if (!Directory.Exists(directoryPath)) - { - Directory.CreateDirectory(directoryPath); - } - string filePath = Path.Combine(directoryPath, ServerFileName); - await File.WriteAllTextAsync(filePath, result); - } - catch (SecurityException) - { - string failMessage = - $"Unable to save registration validation file. Please verify that the application has write access to the directory {directoryPath}."; - await _jsRuntime.InvokeVoidAsync( - $"console.log('{failMessage}'"); - Console.WriteLine(failMessage); - } + _isValidated = true; + _validating = false; } - private static ValidationResult? _inMemoryValidationResult; + private static bool _isValidated; private readonly GeoBlazorSettings _settings; private readonly IJSRuntime _jsRuntime; - private readonly HttpClient _httpClient; private readonly NavigationManager _navigationManager; - private readonly string _machineName; - private const string ServerFileName = "geoblazor-registration-validation"; private readonly string _registrationMessage = "Thank you for using GeoBlazor! Please visit https://licensing.dymaptic.com/geoblazor-core to register."; private static bool _messageShown; -#if DEBUG - private readonly string _licenseServerUrl; -#endif -} - -/// -/// For internal use only -/// -public record ValidationResult(bool IsValid, DateTime ExpirationDate, string? Message = null) -{ - public string MachineName { get; set; } = string.Empty; - public Version? Version { get; set; } - - public DateTime? AttemptedConnect { get; set; } - public string? BaseUri { get; set; } + private bool _validating; } internal enum BlazorMode @@ -269,4 +108,10 @@ internal enum BlazorMode WebAssembly, Maui #pragma warning restore CS1591 -} \ No newline at end of file +} + +internal record RegistrationObject( + string Email, + string LicenseType, + string Software, + int LicenseVersion); \ No newline at end of file From 5bf70c8797e82e00f7a48d43b5366b2b00c5bfac Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 14 Jan 2024 22:03:13 -0600 Subject: [PATCH 22/27] clean up usings --- src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs index feb60072..01f0cc1b 100644 --- a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs +++ b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs @@ -1,12 +1,10 @@ using Microsoft.AspNetCore.Components; -using Microsoft.Extensions.Configuration; using Microsoft.JSInterop; using System.Diagnostics; -using System.Net.Http.Json; -using System.Security; using System.Text; using System.Text.Json; -using System.Web; + + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace dymaptic.GeoBlazor.Core; From edd7ec49df31452d67ef0100ae7f9c8386cfb341 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 21 Jan 2024 18:36:06 -0600 Subject: [PATCH 23/27] Clean up unneeded settings --- src/dymaptic.GeoBlazor.Core/GeoBlazorSettings.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/GeoBlazorSettings.cs b/src/dymaptic.GeoBlazor.Core/GeoBlazorSettings.cs index 5e85f6f6..74044ac0 100644 --- a/src/dymaptic.GeoBlazor.Core/GeoBlazorSettings.cs +++ b/src/dymaptic.GeoBlazor.Core/GeoBlazorSettings.cs @@ -14,15 +14,4 @@ public class GeoBlazorSettings /// The GeoBlazor Pro License Key, generated on the licensing server. /// public string? LicenseKey { get; set; } - - /// - /// The Application Name for a MAUI application. Not required for Blazor Server or Blazor WebAssembly. - /// - public string? MauiAppName { get; set; } - - /// - /// Optional setting to define where on your Blazor Server application the license validation file will be stored. - /// If not set, the default is `Path.GetTempPath()`. - /// - public string? ValidationServerStoragePath { get; set; } } \ No newline at end of file From 50de5e21d7dfb4900d41603fa073db6e34178692 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Wed, 31 Jan 2024 09:26:37 -0600 Subject: [PATCH 24/27] prevent returning all graphics for layer on hitTest. --- .../Scripts/dotNetBuilder.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts index bb3a5717..60ec9bda 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts @@ -376,12 +376,13 @@ export function buildDotNetLayerView(layerView: LayerView): DotNetLayerView { } } -export function buildDotNetLayer(layer: Layer): DotNetLayer { +export function buildDotNetLayer(layer: Layer, includeGraphics: boolean = true) + : DotNetLayer { switch (layer.type) { case 'feature': return buildDotNetFeatureLayer(layer as FeatureLayer); case 'graphics': - return buildDotNetGraphicsLayer(layer as GraphicsLayer); + return buildDotNetGraphicsLayer(layer as GraphicsLayer, includeGraphics); default: let dotNetLayer = { @@ -408,7 +409,8 @@ export function buildDotNetLayer(layer: Layer): DotNetLayer { } } -export function buildDotNetFeatureLayer(layer: FeatureLayer): DotNetFeatureLayer { +export function buildDotNetFeatureLayer(layer: FeatureLayer) + : DotNetFeatureLayer { let dotNetLayer = { title: layer.title, @@ -520,7 +522,8 @@ export function buildDotNetRangeDomain(domain: RangeDomain): DotNetRangeDomain { } as DotNetRangeDomain; } -export function buildDotNetGraphicsLayer(layer: GraphicsLayer): DotNetGraphicsLayer { +export function buildDotNetGraphicsLayer(layer: GraphicsLayer, includeGraphics: boolean = true) + : DotNetGraphicsLayer { let dotNetLayer = { title: layer.title, type: layer.type, @@ -533,7 +536,7 @@ export function buildDotNetGraphicsLayer(layer: GraphicsLayer): DotNetGraphicsLa dotNetLayer.fullExtent = buildDotNetExtent(layer.fullExtent) as DotNetExtent; } - if (layer.graphics !== undefined && layer.graphics !== null) { + if (includeGraphics && layer.graphics !== undefined && layer.graphics !== null) { dotNetLayer.graphics = (layer.graphics as MapCollection).items.map(g => buildDotNetGraphic(g)); } @@ -564,10 +567,9 @@ function buildDotNetViewHit(viewHit: ViewHit): DotNetViewHit | null { return { type: "graphic", graphic: buildDotNetGraphic(viewHit.graphic), - layer: buildDotNetLayer(viewHit.layer ?? viewHit.graphic.layer), + layer: buildDotNetLayer(viewHit.layer ?? viewHit.graphic.layer, false), mapPoint: buildDotNetPoint(viewHit.mapPoint) } as DotNetGraphicHit; - break; } return null; @@ -933,13 +935,13 @@ export function buildDotNetGoToOverrideParameters(parameters: any, viewId: strin let firstObject = parameters.target.target[0]; if (firstObject instanceof Graphic) { target.targetGraphics = []; - for (let g: Graphic in parameters.target.target as Graphic[]) { - target.targetGraphics.push(buildDotNetGraphic(g)); + for (let g in parameters.target.target as Graphic[]) { + target.targetGraphics.push(buildDotNetGraphic(g as any)); } } else if (firstObject instanceof Geometry) { target.targetGeometries = []; - for (let g: Geometry in parameters.target.target as Geometry[]) { - target.targetGeometries.push(buildDotNetGeometry(g)); + for (let g in parameters.target.target as Geometry[]) { + target.targetGeometries.push(buildDotNetGeometry(g as any)); } } else { target.targetCoordinates = parameters.target.target; From 622da3ad2f5b5bb68f87f2f2fac5ca613952bdca Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Fri, 2 Feb 2024 14:13:34 -0600 Subject: [PATCH 25/27] protobuf serialization fixes --- .../Components/Layers/Graphic.cs | 2 +- .../Components/Symbols/SimpleFillSymbol.cs | 3 ++- .../Components/Symbols/SimpleLineSymbol.cs | 3 ++- .../Components/Symbols/TextSymbol.cs | 10 ++++++++-- src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts | 1 + src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs index ac8c40a8..b64a2243 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs @@ -315,7 +315,7 @@ internal GraphicSerializationRecord ToSerializationRecord(bool refresh = false) Geometry?.ToSerializationRecord(), Symbol?.ToSerializationRecord(), PopupTemplate?.ToSerializationRecord(), - Attributes?.ToSerializationRecord()); + Attributes.ToSerializationRecord()); } return _serializationRecord; diff --git a/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleFillSymbol.cs b/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleFillSymbol.cs index f5192841..2ac8e555 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleFillSymbol.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleFillSymbol.cs @@ -107,7 +107,8 @@ internal override SymbolSerializationRecord ToSerializationRecord() { return new SymbolSerializationRecord(Type, Color) { - Outline = Outline?.ToSerializationRecord(), Style = FillStyle?.ToString().ToKebabCase() + Outline = Outline?.ToSerializationRecord(), + Style = FillStyle?.ToString().ToKebabCase() }; } } diff --git a/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleLineSymbol.cs b/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleLineSymbol.cs index 1c652aee..492a7fbb 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleLineSymbol.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Symbols/SimpleLineSymbol.cs @@ -59,7 +59,8 @@ internal override SymbolSerializationRecord ToSerializationRecord() { return new SymbolSerializationRecord(Type, Color) { - Width = Width, LineStyle = LineStyle?.ToString().ToKebabCase() + Width = Width, + Style = LineStyle?.ToString().ToKebabCase() }; } } diff --git a/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs b/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs index d049ca29..5d2487c7 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Symbols/TextSymbol.cs @@ -302,6 +302,10 @@ internal override void ValidateRequiredChildren() internal override SymbolSerializationRecord ToSerializationRecord() { + double.TryParse(XOffset?.Replace("px", "").Replace("pt", ""), + out double xOffsetNum); + double.TryParse(YOffset?.Replace("px", "").Replace("pt", "") + , out double yOffsetNum); return new SymbolSerializationRecord(Type, Color) { Text = Text, @@ -312,12 +316,14 @@ internal override SymbolSerializationRecord ToSerializationRecord() BackgroundColor = BackgroundColor, BorderLineSize = BorderLineSize, BorderLineColor = BorderLineColor, - HorizontalAlignment = HorizontalAlignment?.ToString(), + HorizontalAlignment = HorizontalAlignment?.ToString().ToKebabCase(), Kerning = Kerning, LineHeight = LineHeight, LineWidth = LineWidth, Rotated = Rotated, - VerticalAlignment = VerticalAlignment?.ToString() + VerticalAlignment = VerticalAlignment?.ToString().ToKebabCase(), + XOffset = xOffsetNum, + YOffset = yOffsetNum }; } } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts index 06ea21d9..507a231f 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts @@ -112,6 +112,7 @@ export interface DotNetSimpleLineSymbol extends DotNetSymbol { miterLimit: number; style: string; width: number; + lineStyle: string; } export interface DotNetPictureMarkerSymbol extends DotNetSymbol { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts index 7a27a977..ac76705f 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts @@ -420,7 +420,7 @@ export function buildJsSymbol(symbol: DotNetSymbol | null): Symbol | null { join: dnSimpleLineSymbol.join as any ?? "round", marker: dnSimpleLineSymbol.marker as any ?? null, miterLimit: dnSimpleLineSymbol.miterLimit ?? 2, - style: dnSimpleLineSymbol.style as any ?? "solid", + style: dnSimpleLineSymbol.lineStyle as any ?? dnSimpleLineSymbol.style as any ?? "solid", width: dnSimpleLineSymbol.width ?? 0.75 }); case "picture-marker": From e5fccaf5575290d13c2d7de277876015f991819b Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 3 Feb 2024 11:18:35 -0600 Subject: [PATCH 26/27] remove unneeded registration tests --- .../RegistrationTests.cs | 355 ------------------ 1 file changed, 355 deletions(-) delete mode 100644 test/dymaptic.GeoBlazor.Core.Test/RegistrationTests.cs diff --git a/test/dymaptic.GeoBlazor.Core.Test/RegistrationTests.cs b/test/dymaptic.GeoBlazor.Core.Test/RegistrationTests.cs deleted file mode 100644 index a73d4147..00000000 --- a/test/dymaptic.GeoBlazor.Core.Test/RegistrationTests.cs +++ /dev/null @@ -1,355 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.Extensions.Configuration; -using Microsoft.JSInterop; -using Microsoft.JSInterop.Infrastructure; -using System.Net; -using System.Reflection; -using System.Text; -using System.Text.Json; - - -namespace dymaptic.GeoBlazor.Core.Test; - -[TestClass] -public class RegistrationTests -{ - [TestInitialize] - public void SetupTest() - { - Type validatorType = typeof(RegistrationValidator); - FieldInfo? field = validatorType.GetField("_inMemoryValidationResult", - BindingFlags.Static | BindingFlags.NonPublic); - if (field != null) field.SetValue(null, null); - FieldInfo? messageField = validatorType.GetField("_messageShown", - BindingFlags.Static | BindingFlags.NonPublic); - if (messageField != null) messageField.SetValue(null, false); - } - - [TestCleanup] - public void CleanupTest() - { - ClearServerFile(); - } - - [TestMethod] - public async Task TestCanValidateRegistrationOnBlazorServer() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeRemoteJsRuntime(null), - messageHandler); - await validator.ValidateLicense(); - AssertMessageCalled(false); - Assert.AreEqual(1, messageHandler.CallCount); - } - - [TestMethod] - public async Task TestCanValidateStoredRegistrationOnBlazorServer() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - await SetServerFile(result); - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeRemoteJsRuntime(result)); - await validator.ValidateLicense(); - - Assert.AreEqual(0, messageHandler.CallCount); - AssertMessageCalled(false); - } - - [TestMethod] - public async Task TestCanValidateRegistrationOnBlazorWasm() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeInProcessJsRuntime(null), - messageHandler); - await validator.ValidateLicense(); - AssertMessageCalled(false); - Assert.AreEqual(1, messageHandler.CallCount); - } - - [TestMethod] - public async Task TestCanValidateStoredRegistrationOnBlazorWasm() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, - new FakeInProcessJsRuntime(result), - messageHandler); - await validator.ValidateLicense(); - - Assert.AreEqual(0, messageHandler.CallCount); - AssertMessageCalled(false); - } - - [TestMethod] - public async Task TestInvalidRegistrationOnBlazorServer() - { - ValidationResult result = new ValidationResult(false, DateTime.MinValue, - $"This registration is only for version Test of GeoBlazor") - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeRemoteJsRuntime(result)); - await validator.ValidateLicense(); - AssertMessageCalled(true); - } - - [TestMethod] - public async Task TestCanValidateInMemoryRegistration() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - - FieldInfo? field = typeof(RegistrationValidator).GetField("_inMemoryValidationResult", - BindingFlags.Static | BindingFlags.NonPublic); - if (field != null) field.SetValue(null, result); - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeInProcessJsRuntime(result), - messageHandler); - await validator.ValidateLicense(); - - Assert.AreEqual(0, messageHandler.CallCount); - AssertMessageCalled(false); - } - - [TestMethod] - public async Task TestInvalidRegistrationOnBlazorWasm() - { - ValidationResult result = new ValidationResult(false, DateTime.MinValue, - $"This registration is only for version Test of GeoBlazor") - { - BaseUri = "http://localhost/", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeInProcessJsRuntime(result)); - await validator.ValidateLicense(); - AssertMessageCalled(true); - } - - [TestMethod] - public async Task TestChangedUrlInvalidatesLicense() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "https://test1.com", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - await SetServerFile(result); - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeRemoteJsRuntime(result), - messageHandler); - await validator.ValidateLicense(); - Assert.AreEqual(1, messageHandler.CallCount); - AssertMessageCalled(false); - } - - [TestMethod] - public async Task TestChangedUrlInvalidatesInMemoryRegistration() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "https://test1.com", MachineName = Environment.MachineName, Version = new Version(0, 1, 0, 0) - }; - await SetServerFile(result); - - FieldInfo? field = typeof(RegistrationValidator).GetField("_inMemoryValidationResult", - BindingFlags.Static | BindingFlags.NonPublic); - if (field != null) field.SetValue(null, result); - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeRemoteJsRuntime(result), - messageHandler); - await validator.ValidateLicense(); - Assert.AreEqual(1, messageHandler.CallCount); - AssertMessageCalled(false); - } - - [TestMethod] - public async Task TestChangedMachineNameInvalidatesRegistration() - { - ValidationResult result = new(true, DateTime.MaxValue) - { - BaseUri = "http://localhost/", - MachineName = Environment.MachineName + "new", - Version = new Version(0, 1, 0, 0) - }; - await SetServerFile(result); - FakeHttpMessageHandler messageHandler = new(result, HttpStatusCode.OK); - - RegistrationValidator validator = GetValidator(result, HttpStatusCode.OK, new FakeRemoteJsRuntime(result), - messageHandler); - await validator.ValidateLicense(); - - Assert.AreEqual(1, messageHandler.CallCount); - AssertMessageCalled(false); - } - - private void AssertMessageCalled(bool wasCalled) - { - FieldInfo? field = typeof(RegistrationValidator).GetField("_messageShown", - BindingFlags.Static | BindingFlags.NonPublic); - - if (wasCalled) - { - Assert.IsTrue((bool?)field?.GetValue(null)); - } - else - { - Assert.IsFalse((bool?)field?.GetValue(null)); - } - } - - private RegistrationValidator GetValidator(object? responseContent, HttpStatusCode responseStatusCode, - IJSRuntime jsRuntime, FakeHttpMessageHandler? messageHandler = null) - { - messageHandler ??= new(responseContent, responseStatusCode); - HttpClient httpClient = new(messageHandler); - NavigationManager navigationManager = new FakeNavigationManager(); - IConfiguration config = new ConfigurationBuilder().Build(); - - return new RegistrationValidator(_settings, jsRuntime, httpClient, navigationManager, config); - } - - private void ClearServerFile() - { - string directoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - string filePath = Path.Combine(directoryPath, "geoblazor-registration-validation"); - - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - } - - private async Task SetServerFile(ValidationResult result) - { - string jsonResult = JsonSerializer.Serialize(result); - string encodedResult = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonResult)); - string directoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - string filePath = Path.Combine(directoryPath, "geoblazor-registration-validation"); - await File.WriteAllTextAsync(filePath, encodedResult); - } - - private readonly GeoBlazorSettings _settings = new() - { - RegistrationKey = "Test123", - ValidationServerStoragePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)! - }; -} - -public class FakeRemoteJsRuntime : FakeInProcessJsRuntime -{ - public FakeRemoteJsRuntime(ValidationResult? storedValidation) : base(storedValidation) - { - } -} - -public class FakeInProcessJsRuntime : IJSRuntime -{ - public FakeInProcessJsRuntime(ValidationResult? storedValidation) - { - _storedValidation = JsonSerializer.Serialize(storedValidation); - } - - public ValueTask InvokeAsync(string identifier, object?[]? args) - { - if (typeof(TValue) == typeof(IJSObjectReference)) - { - return ValueTask.FromResult((TValue)(object)new FakeJsObjectReference(_storedValidation)); - } - if (_storedValidation is null || typeof(TValue) == typeof(IJSVoidResult)) return default; - return ValueTask.FromResult((TValue)Convert.ChangeType(_storedValidation, typeof(TValue))); - } - - public ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, - object?[]? args) - { - if (typeof(TValue) == typeof(IJSObjectReference)) - { - return ValueTask.FromResult((TValue)(object)new FakeJsObjectReference(_storedValidation)); - } - if (_storedValidation is null || typeof(TValue) == typeof(IJSVoidResult)) return default; - return ValueTask.FromResult((TValue)Convert.ChangeType(_storedValidation, typeof(TValue))); - } - - private readonly string? _storedValidation; -} - -public class FakeJsObjectReference : IJSObjectReference -{ - private readonly string? _storedValidation; - - public FakeJsObjectReference(string? storedValidation) - { - _storedValidation = storedValidation; - } - - public ValueTask DisposeAsync() - { - throw new NotImplementedException(); - } - - public ValueTask InvokeAsync(string identifier, object?[]? args) - { - if (_storedValidation is null || typeof(TValue) == typeof(IJSVoidResult)) return default; - - return ValueTask.FromResult((TValue)Convert.ChangeType(_storedValidation, typeof(TValue))); - } - - public ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, - object?[]? args) - { - if (_storedValidation is null || typeof(TValue) == typeof(IJSVoidResult)) return default; - - return ValueTask.FromResult((TValue)Convert.ChangeType(_storedValidation, typeof(TValue))); - } -} - -public class FakeNavigationManager : NavigationManager -{ - public FakeNavigationManager() - { - Initialize("http://localhost/", "http://localhost/"); - } -} - -public class FakeHttpMessageHandler : HttpMessageHandler -{ - public FakeHttpMessageHandler(object? responseContent, HttpStatusCode responseStatusCode) - { - _responseContent = responseContent; - _responseStatusCode = responseStatusCode; - } - - public int CallCount { get; private set; } = 0; - - protected override Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) - { - string contentMessage = _responseContent is null ? string.Empty : JsonSerializer.Serialize(_responseContent); - CallCount++; - return Task.FromResult(new HttpResponseMessage - { - StatusCode = _responseStatusCode, Content = new StringContent(contentMessage) - }); - } - - private readonly object? _responseContent; - private readonly HttpStatusCode _responseStatusCode; -} \ No newline at end of file From 6b889eec803502c8e882b3e2b2aad83a78caa89a Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 4 Feb 2024 20:47:05 -0600 Subject: [PATCH 27/27] release testing bug fixes, documentation --- .../Pages/Drawing.razor | 165 +++++++++++------- .../Components/Layers/FeatureLayer.cs | 2 +- .../Components/Views/MapView.razor.cs | 2 +- .../Components/Widgets/Widget.razor.cs | 5 + .../Scripts/arcGisJsInterop.ts | 19 +- .../Scripts/dotNetBuilder.ts | 2 +- .../Scripts/featureLayer.ts | 7 - .../EnumToKebabCaseStringConverter.cs | 31 +++- 8 files changed, 146 insertions(+), 87 deletions(-) diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Drawing.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Drawing.razor index df35a7ba..67c717a9 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Drawing.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Drawing.razor @@ -17,8 +17,10 @@
- - + +
@@ -42,16 +44,19 @@ - @for (var i = 0; i < _mapPath.Count; i++) + @for (var i = 0; i < mapPath.Count; i++) { - MapPoint path = _mapPath[i]; + int index = i; + MapPoint path = mapPath[i]; @(i + 1) - + - + } @@ -77,16 +82,19 @@ - @for (var i = 0; i < _mapRings.Count; i++) + @for (var i = 0; i < mapRings.Count; i++) { - MapPoint path = _mapRings[i]; + int index = i; + MapPoint path = mapRings[i]; @(i + 1) - + - + } @@ -105,34 +113,7 @@ - - @if (_showPoint) - { - - - - - - - } - @if (_showLine) - { - - - - - } - @if (_showPolygon) - { - - - - - - - - } - + @@ -145,57 +126,66 @@ @code { - private readonly double _latitude = 34.027; - private readonly double _longitude = -118.805; - private double _pointLat = 34.027; - private double _pointLong = -118.805; - private bool _showPoint; - private bool _showLine; - private bool _showPolygon; - - private readonly MapPath _mapPath = new(new MapPoint(-118.821527826096, 34.0139576938577), - new MapPoint(-118.814893761649, 34.0080602407843), - new MapPoint(-118.808878330345, 34.0016642996246)); - - private readonly MapPath _mapRings = new(new MapPoint(-118.818984489994, 34.0137559967283), - new MapPoint(-118.806796597377, 34.0215816298725), - new MapPoint(-118.791432890735, 34.0163883241613), - new MapPoint(-118.79596686535, 34.008564864635), - new MapPoint(-118.808558110679, 34.0035027131376)); - - private void DrawPoint() + private async Task DrawPoint() { _showPoint = !_showPoint; + + if (_showPoint) + { + await _graphicsLayer!.Add(_pointGraphic); + } + else + { + await _graphicsLayer!.Remove(_pointGraphic); + } } private void DrawLine() { _showLine = !_showLine; + + if (_showLine) + { + _graphicsLayer!.Add(_polylineGraphic); + } + else + { + _graphicsLayer!.Remove(_polylineGraphic); + } } private void AddPoint() { - _mapPath.Add(new MapPoint(_mapPath[0][0] + 0.01, _mapPath[0][1] + 0.01)); + mapPath.Add(new MapPoint(mapPath[0][0] + 0.01, mapPath[0][1] + 0.01)); } private void RemovePoint() { - _mapPath.RemoveAt(_mapPath.Count - 1); + mapPath.RemoveAt(mapPath.Count - 1); } private void DrawPolygon() { _showPolygon = !_showPolygon; + + if (_showPolygon) + { + _graphicsLayer!.Add(_polygonGraphic); + } + else + { + _graphicsLayer!.Remove(_polygonGraphic); + } } private void AddPolygonPoint() { - _mapRings.Add(new MapPoint(_mapRings[0][0] + 0.01, _mapRings[0][1] + 0.01)); + mapRings.Add(new MapPoint(mapRings[0][0] + 0.01, mapRings[0][1] + 0.01)); } private void RemovePolygonPoint() { - _mapRings.RemoveAt(_mapRings.Count - 1); + mapRings.RemoveAt(mapRings.Count - 1); } private async Task GetPoint() @@ -222,13 +212,60 @@ _geometry = JsonSerializer.Serialize(geometry); } + private async Task SetPointLong(ChangeEventArgs args) + { + _pointLong = Convert.ToDouble(args.Value); + await _pointGraphic.SetGeometry(new Point(_pointLong, _pointLat)); + } + + private async Task SetPointLat(ChangeEventArgs args) + { + _pointLat = Convert.ToDouble(args.Value); + await _pointGraphic.SetGeometry(new Point(_pointLong, _pointLat)); + } + + private async Task SetLinePath(ChangeEventArgs args, int i, int j) + { + mapPath[i][j] = Convert.ToDouble(args.Value); + await _polylineGraphic.SetGeometry(new PolyLine([mapPath])); + } + + private async Task SetRingPath(ChangeEventArgs args, int i, int j) + { + mapRings[i][j] = Convert.ToDouble(args.Value); + await _polygonGraphic.SetGeometry(new Polygon([mapRings])); + } + private bool _showPointSection; private bool _showLineSection; private bool _showPolygonSection; - private Graphic? _polygonGraphic; - private Graphic? _pointGraphic; - private Graphic? _polylineGraphic; + private Graphic _polygonGraphic = new(new Polygon([ mapRings ]), + new SimpleFillSymbol(new Outline(new MapColor(255, 255, 255), 1), + new MapColor(81, 46, 132, 0.8))); + private Graphic _pointGraphic = new(new Point(_pointLong, _pointLat), + new SimpleMarkerSymbol(new Outline(new MapColor(255, 255, 255), 1), + new MapColor(81, 46, 132))); + private Graphic _polylineGraphic = new(new PolyLine([mapPath]), + new SimpleLineSymbol(new MapColor("white"), 2)); + private GraphicsLayer? _graphicsLayer; private string? _geometry; + private readonly double _latitude = 34.027; + private readonly double _longitude = -118.805; + private static double _pointLat = 34.027; + private static double _pointLong = -118.805; + private bool _showPoint; + private bool _showLine; + private bool _showPolygon; + + private static readonly MapPath mapPath = new(new MapPoint(-118.821527826096, 34.0139576938577), + new MapPoint(-118.814893761649, 34.0080602407843), + new MapPoint(-118.808878330345, 34.0016642996246)); + + private static readonly MapPath mapRings = new(new MapPoint(-118.818984489994, 34.0137559967283), + new MapPoint(-118.806796597377, 34.0215816298725), + new MapPoint(-118.791432890735, 34.0163883241613), + new MapPoint(-118.79596686535, 34.008564864635), + new MapPoint(-118.808558110679, 34.0035027131376)); private readonly AttributesDictionary _graphicAttributes = new(new Dictionary { { "Name", "Sample Polygon" }, diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs index e24244ae..3b894ddb 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs @@ -1661,7 +1661,7 @@ public record Thumbnail(string ContentType, string ImageData, double Height, dou /// /// Name of the default drawing tool defined for the template to create a feature. /// -[JsonConverter(typeof(EnumToKebabCaseStringConverter))] +[JsonConverter(typeof(DrawingToolStringConverter))] public enum DrawingTool { #pragma warning disable CS1591 diff --git a/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs b/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs index ee9384e8..4feef4ea 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs @@ -1891,7 +1891,7 @@ public async Task AddWidget(Widget widget) widget.JsModule ??= ViewJsModule; } - if (ViewJsModule is null) return; + if (ViewJsModule is null || !widget.ArcGisWidget) return; while (Rendering) { diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.razor.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.razor.cs index 32316d26..70ba8f07 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.razor.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.razor.cs @@ -73,6 +73,11 @@ public abstract partial class Widget : MapComponent ///
protected virtual bool Hidden => false; + /// + /// Indicates that the widget is sent to ArcGIS JS to render. + /// + protected internal virtual bool ArcGisWidget => true; + /// /// JS-invokable callback to register a JS Object Reference /// diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index 011ba009..ce8d5493 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -2120,15 +2120,7 @@ export async function addLayer(layerObject: any, viewId: string, isBasemapLayer? let view = arcGisObjectRefs[viewId] as View; if (!hasValue(view?.map)) return; - let newLayer: Layer | null; - if (arcGisObjectRefs.hasOwnProperty(layerObject.id)) { - newLayer = arcGisObjectRefs[layerObject.id] as Layer; - if (newLayer.destroyed) { - newLayer = await createLayer(layerObject, null, viewId); - } - } else { - newLayer = await createLayer(layerObject, null, viewId); - } + let newLayer = await createLayer(layerObject, null, viewId); if (newLayer === null) return; @@ -2149,10 +2141,13 @@ export async function addLayer(layerObject: any, viewId: string, isBasemapLayer? export async function createLayer(layerObject: any, wrap?: boolean | null, viewId?: string | null): Promise { if (arcGisObjectRefs.hasOwnProperty(layerObject.id)) { - if (wrap) { - return getObjectReference(arcGisObjectRefs[layerObject.id] as Layer); + let oldLayer = arcGisObjectRefs[layerObject.id] as Layer; + if (!oldLayer.destroyed) { + if (wrap) { + return getObjectReference(arcGisObjectRefs[layerObject.id] as Layer); + } + return arcGisObjectRefs[layerObject.id] as Layer; } - return arcGisObjectRefs[layerObject.id] as Layer; } let newLayer: Layer; switch (layerObject.type) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts index 60ec9bda..432af138 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts @@ -137,7 +137,7 @@ export function buildDotNetGraphic(graphic: Graphic): DotNetGraphic { } dotNetGraphic.uid = (graphic as any).uid; - dotNetGraphic.attributes = graphic.attributes; + dotNetGraphic.attributes = graphic.attributes ?? {}; if (graphic.symbol !== undefined && graphic.symbol !== null) { dotNetGraphic.symbol = buildDotNetSymbol(graphic.symbol); } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index 77e6ef08..6d45037e 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -218,13 +218,6 @@ export default class FeatureLayerWrapper { abortSignal: AbortSignal): Promise { let jsGraphics: Graphic[] = []; for (const g of graphics) { - if (graphicsRefs.hasOwnProperty(g.id)) { - let graphic = graphicsRefs[g.id] as Graphic; - if (graphic !== undefined && graphic !== null) { - jsGraphics.push(graphic); - continue; - } - } let jsGraphic = buildJsGraphic(g, viewId) as Graphic; jsGraphics.push(jsGraphic); } diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/EnumToKebabCaseStringConverter.cs b/src/dymaptic.GeoBlazor.Core/Serialization/EnumToKebabCaseStringConverter.cs index f85eb743..dd6e98f5 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/EnumToKebabCaseStringConverter.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/EnumToKebabCaseStringConverter.cs @@ -85,7 +85,9 @@ public override LabelPlacement Read(ref Utf8JsonReader reader, Type typeToConver try { - return value is not null ? (LabelPlacement)Enum.Parse(typeof(LabelPlacement), value, true) : default(LabelPlacement)!; + return value is not null + ? (LabelPlacement)Enum.Parse(typeof(LabelPlacement), value, true) + : default(LabelPlacement); } catch (Exception ex) { @@ -94,4 +96,31 @@ public override LabelPlacement Read(ref Utf8JsonReader reader, Type typeToConver return default(LabelPlacement); } } +} + +/// +/// Converts an enum to a kebab case string for serialization. Used with LabelPlacement which returns esriServerPointLabelPlacement from the ESRI JS. +/// +public class DrawingToolStringConverter : EnumToKebabCaseStringConverter +{ + /// + public override DrawingTool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString() + ?.Replace("-", string.Empty) + .Replace("esriFeatureEditTool", string.Empty); + + try + { + return value is not null + ? (DrawingTool)Enum.Parse(typeof(DrawingTool), value, true) + : default(DrawingTool); + } + catch (Exception ex) + { + Console.WriteLine(ex); + + return default(DrawingTool); + } + } } \ No newline at end of file