Merge tag 'v2.3.19' into testing
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 9 Nov 2023 11:20:40 +0000 (12:20 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 9 Nov 2023 11:20:40 +0000 (12:20 +0100)
132 files changed:
.gitignore
Makefile
NOTICE
js/.externalToolBuilders/npm run build.launch [new file with mode: 0644]
js/.gitignore [new file with mode: 0644]
js/.project [new file with mode: 0644]
js/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
js/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
js/Makefile [new file with mode: 0644]
js/org.argeo.app.js/.gitignore [new file with mode: 0644]
js/org.argeo.app.js/.project [new file with mode: 0644]
js/org.argeo.app.js/bnd.bnd [new file with mode: 0644]
js/org.argeo.app.js/build.properties [new file with mode: 0644]
js/package-lock.json [new file with mode: 0644]
js/package.json [new file with mode: 0644]
js/src/chart/BarChart.js [new file with mode: 0644]
js/src/chart/ChartJsPart.js [new file with mode: 0644]
js/src/chart/ChartPart.js [new file with mode: 0644]
js/src/chart/TestGraph.js [new file with mode: 0644]
js/src/chart/export-package.js [new file with mode: 0644]
js/src/chart/index.html [new file with mode: 0644]
js/src/chart/index.js [new file with mode: 0644]
js/src/geo/BboxVectorSource.js [new file with mode: 0644]
js/src/geo/MapPart.js [new file with mode: 0644]
js/src/geo/OpenLayersMapPart.js [new file with mode: 0644]
js/src/geo/OpenLayersUtils.js [new file with mode: 0644]
js/src/geo/SentinelCloudless.js [new file with mode: 0644]
js/src/geo/export-package.js [new file with mode: 0644]
js/src/geo/index.html [new file with mode: 0644]
js/src/geo/index.js [new file with mode: 0644]
js/webpack.common.js [new file with mode: 0644]
js/webpack.dev.js [new file with mode: 0644]
js/webpack.prod.js [new file with mode: 0644]
org.argeo.app.api/src/org/argeo/app/api/EntityConstants.java
org.argeo.app.api/src/org/argeo/app/api/EntityDefinition.java
org.argeo.app.api/src/org/argeo/app/api/EntityName.java
org.argeo.app.api/src/org/argeo/app/api/EntityNames.java
org.argeo.app.api/src/org/argeo/app/api/EntityType.java
org.argeo.app.api/src/org/argeo/app/api/WGS84PosName.java
org.argeo.app.api/src/org/argeo/app/api/entity.cnd
org.argeo.app.api/src/org/argeo/app/api/entity.xsd [new file with mode: 0644]
org.argeo.app.api/src/org/argeo/app/api/entityFeature.xsd [new file with mode: 0644]
org.argeo.app.core/bnd.bnd
org.argeo.app.core/src/org/argeo/app/core/AbstractEntityDefinition.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/core/SuiteContentNamespace.java
org.argeo.app.core/src/org/argeo/app/core/schemas/entity.xsd [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GeoJsonUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GeoToSvg.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GeoToolsTest.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GeoUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GmlAttr.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GmlType.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/GpxUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/geonames/GeonamesAdm.java [deleted file]
org.argeo.app.core/src/org/argeo/app/geo/geonames/ImportGeonamesAdmin.java [deleted file]
org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/js/JsReference.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java
org.argeo.app.core/src/org/argeo/app/xforms/xforms.cnd
org.argeo.app.geo/.classpath [new file with mode: 0644]
org.argeo.app.geo/.project [new file with mode: 0644]
org.argeo.app.geo/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
org.argeo.app.geo/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.argeo.app.geo/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
org.argeo.app.geo/OSGI-INF/wfsHttpHandler.xml [new file with mode: 0644]
org.argeo.app.geo/bnd.bnd [new file with mode: 0644]
org.argeo.app.geo/build.properties [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/api/geo/FeatureAdapter.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/api/geo/WfsKvp.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/CqlUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoJson.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoJsonUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoShapeUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoToSvg.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoTools.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoToolsTest.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GeoUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GmlAttr.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GmlType.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GpxUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/JTS.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/geonames/GeonamesAdm.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/geonames/ImportGeonamesAdmin.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/http/WfsUtils.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/BboxVectorSource.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/MapPart.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/FeatureFormat.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/GeoJSON.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/Layer.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/OSM.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/Source.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/VectorLayer.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/VectorSource.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/View.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java
org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkManifestServlet.java
org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java
org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/FopServlet.java
org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/GeoToSvgServlet.java [deleted file]
sdk/argeo-build
sdk/argeo-suite-desktop.properties
sdk/argeo-suite-server.properties
sdk/branches/unstable.bnd
swt/org.argeo.app.geo.swt/.classpath [new file with mode: 0644]
swt/org.argeo.app.geo.swt/.project [new file with mode: 0644]
swt/org.argeo.app.geo.swt/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
swt/org.argeo.app.geo.swt/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
swt/org.argeo.app.geo.swt/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
swt/org.argeo.app.geo.swt/bnd.bnd [new file with mode: 0644]
swt/org.argeo.app.geo.swt/build.properties [new file with mode: 0644]
swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/AbstractJsChart.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SuiteSwtUtils.java
swt/org.argeo.app.ui/OSGI-INF/defaultMap.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml
swt/org.argeo.app.ui/bnd.bnd
swt/org.argeo.app.ui/config/defaultMap.properties [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java

index c769bb12b88739488d7b23ac1126f8533c1c800a..305a343d92faf5d7873d09eb5c80f7226f072372 100644 (file)
@@ -5,3 +5,4 @@
 **/.DS_Store
 /sdk.mk
 /output/
+**/node_modules
index d83a2247970def5dbce28f6e8d7a4ee3832a985c..ad5bd8b73da21e60c8a93677da404c8e7de359b9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 include sdk.mk
-.PHONY: clean all osgi
+.PHONY: clean all osgi web
 
-all: osgi
+all: web osgi
        
 install: osgi-install
 
@@ -13,14 +13,16 @@ BUNDLES = \
 org.argeo.app.api \
 org.argeo.app.core \
 org.argeo.app.jcr \
+org.argeo.app.geo \
 org.argeo.app.servlet.odk \
 org.argeo.app.servlet.publish \
 org.argeo.app.theme.default \
 org.argeo.app.profile.acr.fs \
 org.argeo.app.profile.acr.jcr \
+org.argeo.suite.knowledge \
 swt/org.argeo.app.swt \
+swt/org.argeo.app.geo.swt \
 swt/org.argeo.app.ui \
-org.argeo.suite.knowledge \
 
 DEP_CATEGORIES = \
 org.argeo.tp \
@@ -42,5 +44,13 @@ swt/rap/org.argeo.cms \
 
 clean:
        rm -rf $(BUILD_BASE)
+       make -C js clean
+
+native-deps-debian:
+       sudo apt install npm
+
+## WEB
+web:
+       make -C js all
 
-include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
diff --git a/NOTICE b/NOTICE
index 583ff425ec954ec71adffb0dd3e1371f4817dc2b..de09206854adc95af3d42e7a4b524e87458fcb71 100644 (file)
--- a/NOTICE
+++ b/NOTICE
@@ -39,6 +39,27 @@ whether to do so. The GNU General Public License gives permission to release a
 modified version without this exception; this exception also makes it possible 
 to release a modified version which carries forward this exception.
 
+# Apache License Permission
+
+Linking Argeo Suite statically or dynamically with other modules is making a 
+combined work based on Argeo Suite. Thus, the terms and conditions of the GNU 
+General Public License cover the whole combination when this license becomes 
+applicable.
+
+In addition, as a special exception, the copyright holders of Argeo Suite give 
+you permission to combine Argeo Suite with any program released under the 
+terms and conditions of the Apache License v2.0 or any later version of this 
+license. You may copy and distribute such a system following the terms of 
+the GNU GPL for Argeo Suite and the licenses of the other code concerned, 
+provided that you include the source code of that other code when and as 
+the GNU GPL requires distribution of source code.
+
+Note that people who make modified versions of Argeo Suite are not obligated 
+to grant this special exception for their modified versions; it is their choice 
+whether to do so. The GNU General Public License gives permission to release a 
+modified version without this exception; this exception also makes it possible 
+to release a modified version which carries forward this exception.
+
 # Java Content Repository API version 2.0 Permission
 
 Linking Argeo Suite statically or dynamically with other modules is making a 
diff --git a/js/.externalToolBuilders/npm run build.launch b/js/.externalToolBuilders/npm run build.launch
new file mode 100644 (file)
index 0000000..c706fc1
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
+    <stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${project}"/>
+    <booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="true"/>
+    <stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/argeo-suite-js/package-lock.json&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/argeo-suite-js/package.json&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/argeo-suite-js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/argeo-suite-js/webpack.common.js&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/argeo-suite-js/webpack.dev.js&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/argeo-suite-js/webpack.prod.js&quot; type=&quot;1&quot;/&gt;&#10;&lt;/resources&gt;}"/>
+    <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="/usr/bin/npm"/>
+    <stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
+    <stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="run build"/>
+    <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
+    <stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${project_loc:argeo-suite-js}"/>
+</launchConfiguration>
diff --git a/js/.gitignore b/js/.gitignore
new file mode 100644 (file)
index 0000000..cf1db2e
--- /dev/null
@@ -0,0 +1 @@
+/org/
diff --git a/js/.project b/js/.project
new file mode 100644 (file)
index 0000000..07e0c1b
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>argeo-suite-js</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
+                       <triggers>auto,full,incremental,</triggers>
+                       <arguments>
+                               <dictionary>
+                                       <key>LaunchConfigHandle</key>
+                                       <value>&lt;project&gt;/.externalToolBuilders/npm run build.launch</value>
+                               </dictionary>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+</projectDescription>
diff --git a/js/.settings/org.eclipse.core.resources.prefs b/js/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..99f26c0
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/js/.settings/org.eclipse.pde.core.prefs b/js/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..f29e940
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/js/Makefile b/js/Makefile
new file mode 100644 (file)
index 0000000..a43be73
--- /dev/null
@@ -0,0 +1,29 @@
+include ../sdk.mk
+
+A2_CATEGORY = org.argeo.suite
+
+BUNDLES = \
+org.argeo.app.js \
+
+all: npm-ci webpack osgi
+
+webpack:
+       npm run build-prod
+
+webpack-dev:
+       npm run build
+
+clean:
+       $(foreach bundle, $(BUNDLES), rm -rf $(bundle)/org) 
+
+npm-ci:
+       npm ci
+       
+npm-install:
+       npm install
+       
+jsdoc:
+       node_modules/.bin/jsdoc -r src \
+               -d $(SDK_BUILD_BASE)/jsdoc/argeo-suite-js
+
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
diff --git a/js/org.argeo.app.js/.gitignore b/js/org.argeo.app.js/.gitignore
new file mode 100644 (file)
index 0000000..cf1db2e
--- /dev/null
@@ -0,0 +1 @@
+/org/
diff --git a/js/org.argeo.app.js/.project b/js/org.argeo.app.js/.project
new file mode 100644 (file)
index 0000000..a2edfff
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.js</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+       </natures>
+</projectDescription>
diff --git a/js/org.argeo.app.js/bnd.bnd b/js/org.argeo.app.js/bnd.bnd
new file mode 100644 (file)
index 0000000..cac0263
--- /dev/null
@@ -0,0 +1,6 @@
+Export-Package: \
+org.argeo.app.js,\
+
+
+Provide-Capability:\
+cms.publish;pkg=org.argeo.app.js;file="*.png,*.js,*.map,*.css,*.html",\
diff --git a/js/org.argeo.app.js/build.properties b/js/org.argeo.app.js/build.properties
new file mode 100644 (file)
index 0000000..9b31242
--- /dev/null
@@ -0,0 +1,2 @@
+bin.includes = META-INF/,\
+               org/
diff --git a/js/package-lock.json b/js/package-lock.json
new file mode 100644 (file)
index 0000000..5d82b6a
--- /dev/null
@@ -0,0 +1,6847 @@
+{
+       "name": "org.argeo.app.js",
+       "version": "2.3.0.next",
+       "lockfileVersion": 3,
+       "requires": true,
+       "packages": {
+               "": {
+                       "name": "org.argeo.app.js",
+                       "version": "2.3.0.next",
+                       "license": "GPL",
+                       "dependencies": {
+                               "@nieuwlandgeo/sldreader": "0.3.x",
+                               "chart.js": "4.x.x",
+                               "chartjs-plugin-annotation": "^3.0.1",
+                               "ol": "8.x.x"
+                       },
+                       "devDependencies": {
+                               "css-loader": "^6.8.1",
+                               "css-minimizer-webpack-plugin": "^5.0.1",
+                               "html-webpack-plugin": "^5.5.3",
+                               "jsdoc": "^4.0.2",
+                               "mini-css-extract-plugin": "^2.7.6",
+                               "npm-check-updates": "^16.13.2",
+                               "style-loader": "^3.3.3",
+                               "webpack": "^5.83.1",
+                               "webpack-cli": "^5.1.1",
+                               "webpack-merge": "^5.9.0"
+                       }
+               },
+               "node_modules/@babel/parser": {
+                       "version": "7.23.0",
+                       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+                       "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
+                       "dev": true,
+                       "bin": {
+                               "parser": "bin/babel-parser.js"
+                       },
+                       "engines": {
+                               "node": ">=6.0.0"
+                       }
+               },
+               "node_modules/@colors/colors": {
+                       "version": "1.5.0",
+                       "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
+                       "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
+                       "dev": true,
+                       "optional": true,
+                       "engines": {
+                               "node": ">=0.1.90"
+                       }
+               },
+               "node_modules/@discoveryjs/json-ext": {
+                       "version": "0.5.7",
+                       "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+                       "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10.0.0"
+                       }
+               },
+               "node_modules/@isaacs/cliui": {
+                       "version": "8.0.2",
+                       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+                       "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+                       "dev": true,
+                       "dependencies": {
+                               "string-width": "^5.1.2",
+                               "string-width-cjs": "npm:string-width@^4.2.0",
+                               "strip-ansi": "^7.0.1",
+                               "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+                               "wrap-ansi": "^8.1.0",
+                               "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       }
+               },
+               "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+                       "version": "9.2.2",
+                       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+                       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+                       "dev": true
+               },
+               "node_modules/@isaacs/cliui/node_modules/string-width": {
+                       "version": "5.1.2",
+                       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+                       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+                       "dev": true,
+                       "dependencies": {
+                               "eastasianwidth": "^0.2.0",
+                               "emoji-regex": "^9.2.2",
+                               "strip-ansi": "^7.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/@jest/schemas": {
+                       "version": "29.6.3",
+                       "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+                       "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@sinclair/typebox": "^0.27.8"
+                       },
+                       "engines": {
+                               "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@jest/types": {
+                       "version": "29.6.3",
+                       "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+                       "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jest/schemas": "^29.6.3",
+                               "@types/istanbul-lib-coverage": "^2.0.0",
+                               "@types/istanbul-reports": "^3.0.0",
+                               "@types/node": "*",
+                               "@types/yargs": "^17.0.8",
+                               "chalk": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@jridgewell/gen-mapping": {
+                       "version": "0.3.3",
+                       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+                       "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jridgewell/set-array": "^1.0.1",
+                               "@jridgewell/sourcemap-codec": "^1.4.10",
+                               "@jridgewell/trace-mapping": "^0.3.9"
+                       },
+                       "engines": {
+                               "node": ">=6.0.0"
+                       }
+               },
+               "node_modules/@jridgewell/resolve-uri": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
+                       "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6.0.0"
+                       }
+               },
+               "node_modules/@jridgewell/set-array": {
+                       "version": "1.1.2",
+                       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+                       "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6.0.0"
+                       }
+               },
+               "node_modules/@jridgewell/source-map": {
+                       "version": "0.3.5",
+                       "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
+                       "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jridgewell/gen-mapping": "^0.3.0",
+                               "@jridgewell/trace-mapping": "^0.3.9"
+                       }
+               },
+               "node_modules/@jridgewell/sourcemap-codec": {
+                       "version": "1.4.15",
+                       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+                       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+                       "dev": true
+               },
+               "node_modules/@jridgewell/trace-mapping": {
+                       "version": "0.3.19",
+                       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
+                       "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jridgewell/resolve-uri": "^3.1.0",
+                               "@jridgewell/sourcemap-codec": "^1.4.14"
+                       }
+               },
+               "node_modules/@jsdoc/salty": {
+                       "version": "0.2.5",
+                       "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz",
+                       "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==",
+                       "dev": true,
+                       "dependencies": {
+                               "lodash": "^4.17.21"
+                       },
+                       "engines": {
+                               "node": ">=v12.0.0"
+                       }
+               },
+               "node_modules/@kurkle/color": {
+                       "version": "0.3.2",
+                       "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+                       "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+               },
+               "node_modules/@nieuwlandgeo/sldreader": {
+                       "version": "0.3.1",
+                       "resolved": "https://registry.npmjs.org/@nieuwlandgeo/sldreader/-/sldreader-0.3.1.tgz",
+                       "integrity": "sha512-gP1dw7ftVT34L6nv8dDtERNIJYENwe2I37Vwdm3NQH+KKHDk7vwrTANxvgKgbNybMXHF29jvI97Z/bkZYBqdxQ==",
+                       "peerDependencies": {
+                               "ol": ">= 5.3.0"
+                       }
+               },
+               "node_modules/@nodelib/fs.scandir": {
+                       "version": "2.1.5",
+                       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+                       "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+                       "dev": true,
+                       "dependencies": {
+                               "@nodelib/fs.stat": "2.0.5",
+                               "run-parallel": "^1.1.9"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/@nodelib/fs.stat": {
+                       "version": "2.0.5",
+                       "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+                       "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/@nodelib/fs.walk": {
+                       "version": "1.2.8",
+                       "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+                       "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@nodelib/fs.scandir": "2.1.5",
+                               "fastq": "^1.6.0"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/@npmcli/fs": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz",
+                       "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==",
+                       "dev": true,
+                       "dependencies": {
+                               "semver": "^7.3.5"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/git": {
+                       "version": "4.1.0",
+                       "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz",
+                       "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@npmcli/promise-spawn": "^6.0.0",
+                               "lru-cache": "^7.4.4",
+                               "npm-pick-manifest": "^8.0.0",
+                               "proc-log": "^3.0.0",
+                               "promise-inflight": "^1.0.1",
+                               "promise-retry": "^2.0.1",
+                               "semver": "^7.3.5",
+                               "which": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/git/node_modules/which": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                       "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                       "dev": true,
+                       "dependencies": {
+                               "isexe": "^2.0.0"
+                       },
+                       "bin": {
+                               "node-which": "bin/which.js"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/installed-package-contents": {
+                       "version": "2.0.2",
+                       "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz",
+                       "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "npm-bundled": "^3.0.0",
+                               "npm-normalize-package-bin": "^3.0.0"
+                       },
+                       "bin": {
+                               "installed-package-contents": "lib/index.js"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/node-gyp": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz",
+                       "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/promise-spawn": {
+                       "version": "6.0.2",
+                       "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz",
+                       "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==",
+                       "dev": true,
+                       "dependencies": {
+                               "which": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/promise-spawn/node_modules/which": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                       "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                       "dev": true,
+                       "dependencies": {
+                               "isexe": "^2.0.0"
+                       },
+                       "bin": {
+                               "node-which": "bin/which.js"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/run-script": {
+                       "version": "6.0.2",
+                       "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz",
+                       "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@npmcli/node-gyp": "^3.0.0",
+                               "@npmcli/promise-spawn": "^6.0.0",
+                               "node-gyp": "^9.0.0",
+                               "read-package-json-fast": "^3.0.0",
+                               "which": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@npmcli/run-script/node_modules/which": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                       "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                       "dev": true,
+                       "dependencies": {
+                               "isexe": "^2.0.0"
+                       },
+                       "bin": {
+                               "node-which": "bin/which.js"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@petamoriken/float16": {
+                       "version": "3.8.4",
+                       "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.4.tgz",
+                       "integrity": "sha512-kB+NJ5Br56ZhElKsf0pM7/PQfrDdDVMRz8f0JM6eVOGE+L89z9hwcst9QvWBBnazzuqGTGtPsJNZoQ1JdNiGSQ=="
+               },
+               "node_modules/@pkgjs/parseargs": {
+                       "version": "0.11.0",
+                       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+                       "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+                       "dev": true,
+                       "optional": true,
+                       "engines": {
+                               "node": ">=14"
+                       }
+               },
+               "node_modules/@pnpm/config.env-replace": {
+                       "version": "1.1.0",
+                       "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
+                       "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12.22.0"
+                       }
+               },
+               "node_modules/@pnpm/network.ca-file": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz",
+                       "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==",
+                       "dev": true,
+                       "dependencies": {
+                               "graceful-fs": "4.2.10"
+                       },
+                       "engines": {
+                               "node": ">=12.22.0"
+                       }
+               },
+               "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": {
+                       "version": "4.2.10",
+                       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+                       "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+                       "dev": true
+               },
+               "node_modules/@pnpm/npm-conf": {
+                       "version": "2.2.2",
+                       "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz",
+                       "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@pnpm/config.env-replace": "^1.1.0",
+                               "@pnpm/network.ca-file": "^1.0.1",
+                               "config-chain": "^1.1.11"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       }
+               },
+               "node_modules/@sigstore/bundle": {
+                       "version": "1.1.0",
+                       "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz",
+                       "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==",
+                       "dev": true,
+                       "dependencies": {
+                               "@sigstore/protobuf-specs": "^0.2.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@sigstore/protobuf-specs": {
+                       "version": "0.2.1",
+                       "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz",
+                       "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@sigstore/sign": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz",
+                       "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@sigstore/bundle": "^1.1.0",
+                               "@sigstore/protobuf-specs": "^0.2.0",
+                               "make-fetch-happen": "^11.0.1"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@sigstore/tuf": {
+                       "version": "1.0.3",
+                       "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz",
+                       "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@sigstore/protobuf-specs": "^0.2.0",
+                               "tuf-js": "^1.1.7"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@sinclair/typebox": {
+                       "version": "0.27.8",
+                       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+                       "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+                       "dev": true
+               },
+               "node_modules/@sindresorhus/is": {
+                       "version": "5.6.0",
+                       "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
+                       "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sindresorhus/is?sponsor=1"
+                       }
+               },
+               "node_modules/@szmarczak/http-timer": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
+                       "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
+                       "dev": true,
+                       "dependencies": {
+                               "defer-to-connect": "^2.0.1"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       }
+               },
+               "node_modules/@tootallnate/once": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+                       "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 10"
+                       }
+               },
+               "node_modules/@trysound/sax": {
+                       "version": "0.2.0",
+                       "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
+                       "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10.13.0"
+                       }
+               },
+               "node_modules/@tufjs/canonical-json": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz",
+                       "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@tufjs/models": {
+                       "version": "1.0.4",
+                       "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz",
+                       "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==",
+                       "dev": true,
+                       "dependencies": {
+                               "@tufjs/canonical-json": "1.0.0",
+                               "minimatch": "^9.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/@types/eslint": {
+                       "version": "8.44.3",
+                       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz",
+                       "integrity": "sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/estree": "*",
+                               "@types/json-schema": "*"
+                       }
+               },
+               "node_modules/@types/eslint-scope": {
+                       "version": "3.7.5",
+                       "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz",
+                       "integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/eslint": "*",
+                               "@types/estree": "*"
+                       }
+               },
+               "node_modules/@types/estree": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
+                       "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
+                       "dev": true
+               },
+               "node_modules/@types/html-minifier-terser": {
+                       "version": "6.1.0",
+                       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
+                       "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==",
+                       "dev": true
+               },
+               "node_modules/@types/http-cache-semantics": {
+                       "version": "4.0.2",
+                       "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz",
+                       "integrity": "sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==",
+                       "dev": true
+               },
+               "node_modules/@types/istanbul-lib-coverage": {
+                       "version": "2.0.4",
+                       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+                       "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+                       "dev": true
+               },
+               "node_modules/@types/istanbul-lib-report": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+                       "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/istanbul-lib-coverage": "*"
+                       }
+               },
+               "node_modules/@types/istanbul-reports": {
+                       "version": "3.0.2",
+                       "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz",
+                       "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/istanbul-lib-report": "*"
+                       }
+               },
+               "node_modules/@types/json-schema": {
+                       "version": "7.0.13",
+                       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
+                       "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==",
+                       "dev": true
+               },
+               "node_modules/@types/linkify-it": {
+                       "version": "3.0.3",
+                       "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz",
+                       "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==",
+                       "dev": true
+               },
+               "node_modules/@types/markdown-it": {
+                       "version": "12.2.3",
+                       "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
+                       "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/linkify-it": "*",
+                               "@types/mdurl": "*"
+                       }
+               },
+               "node_modules/@types/mdurl": {
+                       "version": "1.0.3",
+                       "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.3.tgz",
+                       "integrity": "sha512-T5k6kTXak79gwmIOaDF2UUQXFbnBE0zBUzF20pz7wDYu0RQMzWg+Ml/Pz50214NsFHBITkoi5VtdjFZnJ2ijjA==",
+                       "dev": true
+               },
+               "node_modules/@types/node": {
+                       "version": "20.8.3",
+                       "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.3.tgz",
+                       "integrity": "sha512-jxiZQFpb+NlH5kjW49vXxvxTjeeqlbsnTAdBTKpzEdPs9itay7MscYXz3Fo9VYFEsfQ6LJFitHad3faerLAjCw==",
+                       "dev": true
+               },
+               "node_modules/@types/yargs": {
+                       "version": "17.0.28",
+                       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz",
+                       "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/yargs-parser": "*"
+                       }
+               },
+               "node_modules/@types/yargs-parser": {
+                       "version": "21.0.1",
+                       "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz",
+                       "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==",
+                       "dev": true
+               },
+               "node_modules/@webassemblyjs/ast": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
+                       "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/helper-numbers": "1.11.6",
+                               "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
+                       }
+               },
+               "node_modules/@webassemblyjs/floating-point-hex-parser": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
+                       "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
+                       "dev": true
+               },
+               "node_modules/@webassemblyjs/helper-api-error": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
+                       "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
+                       "dev": true
+               },
+               "node_modules/@webassemblyjs/helper-buffer": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
+                       "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
+                       "dev": true
+               },
+               "node_modules/@webassemblyjs/helper-numbers": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
+                       "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/floating-point-hex-parser": "1.11.6",
+                               "@webassemblyjs/helper-api-error": "1.11.6",
+                               "@xtuc/long": "4.2.2"
+                       }
+               },
+               "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
+                       "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
+                       "dev": true
+               },
+               "node_modules/@webassemblyjs/helper-wasm-section": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
+                       "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/ast": "1.11.6",
+                               "@webassemblyjs/helper-buffer": "1.11.6",
+                               "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+                               "@webassemblyjs/wasm-gen": "1.11.6"
+                       }
+               },
+               "node_modules/@webassemblyjs/ieee754": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
+                       "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@xtuc/ieee754": "^1.2.0"
+                       }
+               },
+               "node_modules/@webassemblyjs/leb128": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
+                       "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@xtuc/long": "4.2.2"
+                       }
+               },
+               "node_modules/@webassemblyjs/utf8": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
+                       "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
+                       "dev": true
+               },
+               "node_modules/@webassemblyjs/wasm-edit": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
+                       "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/ast": "1.11.6",
+                               "@webassemblyjs/helper-buffer": "1.11.6",
+                               "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+                               "@webassemblyjs/helper-wasm-section": "1.11.6",
+                               "@webassemblyjs/wasm-gen": "1.11.6",
+                               "@webassemblyjs/wasm-opt": "1.11.6",
+                               "@webassemblyjs/wasm-parser": "1.11.6",
+                               "@webassemblyjs/wast-printer": "1.11.6"
+                       }
+               },
+               "node_modules/@webassemblyjs/wasm-gen": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
+                       "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/ast": "1.11.6",
+                               "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+                               "@webassemblyjs/ieee754": "1.11.6",
+                               "@webassemblyjs/leb128": "1.11.6",
+                               "@webassemblyjs/utf8": "1.11.6"
+                       }
+               },
+               "node_modules/@webassemblyjs/wasm-opt": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
+                       "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/ast": "1.11.6",
+                               "@webassemblyjs/helper-buffer": "1.11.6",
+                               "@webassemblyjs/wasm-gen": "1.11.6",
+                               "@webassemblyjs/wasm-parser": "1.11.6"
+                       }
+               },
+               "node_modules/@webassemblyjs/wasm-parser": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
+                       "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/ast": "1.11.6",
+                               "@webassemblyjs/helper-api-error": "1.11.6",
+                               "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+                               "@webassemblyjs/ieee754": "1.11.6",
+                               "@webassemblyjs/leb128": "1.11.6",
+                               "@webassemblyjs/utf8": "1.11.6"
+                       }
+               },
+               "node_modules/@webassemblyjs/wast-printer": {
+                       "version": "1.11.6",
+                       "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
+                       "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
+                       "dev": true,
+                       "dependencies": {
+                               "@webassemblyjs/ast": "1.11.6",
+                               "@xtuc/long": "4.2.2"
+                       }
+               },
+               "node_modules/@webpack-cli/configtest": {
+                       "version": "2.1.1",
+                       "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
+                       "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.15.0"
+                       },
+                       "peerDependencies": {
+                               "webpack": "5.x.x",
+                               "webpack-cli": "5.x.x"
+                       }
+               },
+               "node_modules/@webpack-cli/info": {
+                       "version": "2.0.2",
+                       "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
+                       "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.15.0"
+                       },
+                       "peerDependencies": {
+                               "webpack": "5.x.x",
+                               "webpack-cli": "5.x.x"
+                       }
+               },
+               "node_modules/@webpack-cli/serve": {
+                       "version": "2.0.5",
+                       "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
+                       "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.15.0"
+                       },
+                       "peerDependencies": {
+                               "webpack": "5.x.x",
+                               "webpack-cli": "5.x.x"
+                       },
+                       "peerDependenciesMeta": {
+                               "webpack-dev-server": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/@xtuc/ieee754": {
+                       "version": "1.2.0",
+                       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+                       "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+                       "dev": true
+               },
+               "node_modules/@xtuc/long": {
+                       "version": "4.2.2",
+                       "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+                       "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+                       "dev": true
+               },
+               "node_modules/abbrev": {
+                       "version": "1.1.1",
+                       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+                       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+                       "dev": true
+               },
+               "node_modules/acorn": {
+                       "version": "8.10.0",
+                       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+                       "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
+                       "dev": true,
+                       "bin": {
+                               "acorn": "bin/acorn"
+                       },
+                       "engines": {
+                               "node": ">=0.4.0"
+                       }
+               },
+               "node_modules/acorn-import-assertions": {
+                       "version": "1.9.0",
+                       "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+                       "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
+                       "dev": true,
+                       "peerDependencies": {
+                               "acorn": "^8"
+                       }
+               },
+               "node_modules/agent-base": {
+                       "version": "6.0.2",
+                       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+                       "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "debug": "4"
+                       },
+                       "engines": {
+                               "node": ">= 6.0.0"
+                       }
+               },
+               "node_modules/agentkeepalive": {
+                       "version": "4.5.0",
+                       "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+                       "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+                       "dev": true,
+                       "dependencies": {
+                               "humanize-ms": "^1.2.1"
+                       },
+                       "engines": {
+                               "node": ">= 8.0.0"
+                       }
+               },
+               "node_modules/aggregate-error": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+                       "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+                       "dev": true,
+                       "dependencies": {
+                               "clean-stack": "^2.0.0",
+                               "indent-string": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/ajv": {
+                       "version": "8.12.0",
+                       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+                       "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+                       "dev": true,
+                       "dependencies": {
+                               "fast-deep-equal": "^3.1.1",
+                               "json-schema-traverse": "^1.0.0",
+                               "require-from-string": "^2.0.2",
+                               "uri-js": "^4.2.2"
+                       },
+                       "funding": {
+                               "type": "github",
+                               "url": "https://github.com/sponsors/epoberezkin"
+                       }
+               },
+               "node_modules/ajv-formats": {
+                       "version": "2.1.1",
+                       "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+                       "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+                       "dev": true,
+                       "dependencies": {
+                               "ajv": "^8.0.0"
+                       },
+                       "peerDependencies": {
+                               "ajv": "^8.0.0"
+                       },
+                       "peerDependenciesMeta": {
+                               "ajv": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/ajv-keywords": {
+                       "version": "5.1.0",
+                       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+                       "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+                       "dev": true,
+                       "dependencies": {
+                               "fast-deep-equal": "^3.1.3"
+                       },
+                       "peerDependencies": {
+                               "ajv": "^8.8.2"
+                       }
+               },
+               "node_modules/ansi-align": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+                       "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+                       "dev": true,
+                       "dependencies": {
+                               "string-width": "^4.1.0"
+                       }
+               },
+               "node_modules/ansi-regex": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+                       "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+                       }
+               },
+               "node_modules/ansi-styles": {
+                       "version": "4.3.0",
+                       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                       "dev": true,
+                       "dependencies": {
+                               "color-convert": "^2.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+                       }
+               },
+               "node_modules/aproba": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+                       "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+                       "dev": true
+               },
+               "node_modules/are-we-there-yet": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+                       "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+                       "dev": true,
+                       "dependencies": {
+                               "delegates": "^1.0.0",
+                               "readable-stream": "^3.6.0"
+                       },
+                       "engines": {
+                               "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+                       }
+               },
+               "node_modules/argparse": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+                       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+                       "dev": true
+               },
+               "node_modules/array-union": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+                       "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/balanced-match": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+                       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+                       "dev": true
+               },
+               "node_modules/bluebird": {
+                       "version": "3.7.2",
+                       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+                       "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+                       "dev": true
+               },
+               "node_modules/boolbase": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+                       "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+                       "dev": true
+               },
+               "node_modules/boxen": {
+                       "version": "7.1.1",
+                       "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz",
+                       "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-align": "^3.0.1",
+                               "camelcase": "^7.0.1",
+                               "chalk": "^5.2.0",
+                               "cli-boxes": "^3.0.0",
+                               "string-width": "^5.1.2",
+                               "type-fest": "^2.13.0",
+                               "widest-line": "^4.0.1",
+                               "wrap-ansi": "^8.1.0"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/boxen/node_modules/chalk": {
+                       "version": "5.3.0",
+                       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+                       "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.17.0 || ^14.13 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/chalk?sponsor=1"
+                       }
+               },
+               "node_modules/boxen/node_modules/emoji-regex": {
+                       "version": "9.2.2",
+                       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+                       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+                       "dev": true
+               },
+               "node_modules/boxen/node_modules/string-width": {
+                       "version": "5.1.2",
+                       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+                       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+                       "dev": true,
+                       "dependencies": {
+                               "eastasianwidth": "^0.2.0",
+                               "emoji-regex": "^9.2.2",
+                               "strip-ansi": "^7.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/brace-expansion": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                       "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                       "dev": true,
+                       "dependencies": {
+                               "balanced-match": "^1.0.0"
+                       }
+               },
+               "node_modules/braces": {
+                       "version": "3.0.2",
+                       "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+                       "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+                       "dev": true,
+                       "dependencies": {
+                               "fill-range": "^7.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/browserslist": {
+                       "version": "4.22.1",
+                       "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
+                       "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "opencollective",
+                                       "url": "https://opencollective.com/browserslist"
+                               },
+                               {
+                                       "type": "tidelift",
+                                       "url": "https://tidelift.com/funding/github/npm/browserslist"
+                               },
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/ai"
+                               }
+                       ],
+                       "dependencies": {
+                               "caniuse-lite": "^1.0.30001541",
+                               "electron-to-chromium": "^1.4.535",
+                               "node-releases": "^2.0.13",
+                               "update-browserslist-db": "^1.0.13"
+                       },
+                       "bin": {
+                               "browserslist": "cli.js"
+                       },
+                       "engines": {
+                               "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+                       }
+               },
+               "node_modules/buffer-from": {
+                       "version": "1.1.2",
+                       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+                       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+                       "dev": true
+               },
+               "node_modules/builtins": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
+                       "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "semver": "^7.0.0"
+                       }
+               },
+               "node_modules/cacache": {
+                       "version": "17.1.4",
+                       "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
+                       "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==",
+                       "dev": true,
+                       "dependencies": {
+                               "@npmcli/fs": "^3.1.0",
+                               "fs-minipass": "^3.0.0",
+                               "glob": "^10.2.2",
+                               "lru-cache": "^7.7.1",
+                               "minipass": "^7.0.3",
+                               "minipass-collect": "^1.0.2",
+                               "minipass-flush": "^1.0.5",
+                               "minipass-pipeline": "^1.2.4",
+                               "p-map": "^4.0.0",
+                               "ssri": "^10.0.0",
+                               "tar": "^6.1.11",
+                               "unique-filename": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/cacache/node_modules/minipass": {
+                       "version": "7.0.4",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                       "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       }
+               },
+               "node_modules/cacheable-lookup": {
+                       "version": "7.0.0",
+                       "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
+                       "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.16"
+                       }
+               },
+               "node_modules/cacheable-request": {
+                       "version": "10.2.14",
+                       "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
+                       "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/http-cache-semantics": "^4.0.2",
+                               "get-stream": "^6.0.1",
+                               "http-cache-semantics": "^4.1.1",
+                               "keyv": "^4.5.3",
+                               "mimic-response": "^4.0.0",
+                               "normalize-url": "^8.0.0",
+                               "responselike": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       }
+               },
+               "node_modules/camel-case": {
+                       "version": "4.1.2",
+                       "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
+                       "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
+                       "dev": true,
+                       "dependencies": {
+                               "pascal-case": "^3.1.2",
+                               "tslib": "^2.0.3"
+                       }
+               },
+               "node_modules/camelcase": {
+                       "version": "7.0.1",
+                       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
+                       "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/caniuse-api": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
+                       "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.0.0",
+                               "caniuse-lite": "^1.0.0",
+                               "lodash.memoize": "^4.1.2",
+                               "lodash.uniq": "^4.5.0"
+                       }
+               },
+               "node_modules/caniuse-lite": {
+                       "version": "1.0.30001546",
+                       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz",
+                       "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "opencollective",
+                                       "url": "https://opencollective.com/browserslist"
+                               },
+                               {
+                                       "type": "tidelift",
+                                       "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+                               },
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/ai"
+                               }
+                       ]
+               },
+               "node_modules/catharsis": {
+                       "version": "0.9.0",
+                       "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
+                       "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
+                       "dev": true,
+                       "dependencies": {
+                               "lodash": "^4.17.15"
+                       },
+                       "engines": {
+                               "node": ">= 10"
+                       }
+               },
+               "node_modules/chalk": {
+                       "version": "4.1.2",
+                       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+                       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-styles": "^4.1.0",
+                               "supports-color": "^7.1.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/chalk?sponsor=1"
+                       }
+               },
+               "node_modules/chalk/node_modules/supports-color": {
+                       "version": "7.2.0",
+                       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+                       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+                       "dev": true,
+                       "dependencies": {
+                               "has-flag": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/chart.js": {
+                       "version": "4.4.0",
+                       "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz",
+                       "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==",
+                       "dependencies": {
+                               "@kurkle/color": "^0.3.0"
+                       },
+                       "engines": {
+                               "pnpm": ">=7"
+                       }
+               },
+               "node_modules/chartjs-plugin-annotation": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.0.1.tgz",
+                       "integrity": "sha512-hlIrXXKqSDgb+ZjVYHefmlZUXK8KbkCPiynSVrTb/HjTMkT62cOInaT1NTQCKtxKKOm9oHp958DY3RTAFKtkHg==",
+                       "peerDependencies": {
+                               "chart.js": ">=4.0.0"
+                       }
+               },
+               "node_modules/chownr": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+                       "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/chrome-trace-event": {
+                       "version": "1.0.3",
+                       "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
+                       "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6.0"
+                       }
+               },
+               "node_modules/ci-info": {
+                       "version": "3.9.0",
+                       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+                       "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/sibiraj-s"
+                               }
+                       ],
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/clean-css": {
+                       "version": "5.3.2",
+                       "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
+                       "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==",
+                       "dev": true,
+                       "dependencies": {
+                               "source-map": "~0.6.0"
+                       },
+                       "engines": {
+                               "node": ">= 10.0"
+                       }
+               },
+               "node_modules/clean-stack": {
+                       "version": "2.2.0",
+                       "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+                       "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/cli-boxes": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+                       "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/cli-table3": {
+                       "version": "0.6.3",
+                       "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
+                       "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==",
+                       "dev": true,
+                       "dependencies": {
+                               "string-width": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "10.* || >= 12.*"
+                       },
+                       "optionalDependencies": {
+                               "@colors/colors": "1.5.0"
+                       }
+               },
+               "node_modules/clone-deep": {
+                       "version": "4.0.1",
+                       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+                       "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-plain-object": "^2.0.4",
+                               "kind-of": "^6.0.2",
+                               "shallow-clone": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/color-convert": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "color-name": "~1.1.4"
+                       },
+                       "engines": {
+                               "node": ">=7.0.0"
+                       }
+               },
+               "node_modules/color-name": {
+                       "version": "1.1.4",
+                       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+                       "dev": true
+               },
+               "node_modules/color-support": {
+                       "version": "1.1.3",
+                       "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+                       "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+                       "dev": true,
+                       "bin": {
+                               "color-support": "bin.js"
+                       }
+               },
+               "node_modules/colord": {
+                       "version": "2.9.3",
+                       "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
+                       "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
+                       "dev": true
+               },
+               "node_modules/colorette": {
+                       "version": "2.0.20",
+                       "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+                       "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+                       "dev": true
+               },
+               "node_modules/commander": {
+                       "version": "8.3.0",
+                       "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+                       "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 12"
+                       }
+               },
+               "node_modules/concat-map": {
+                       "version": "0.0.1",
+                       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+                       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+                       "dev": true
+               },
+               "node_modules/config-chain": {
+                       "version": "1.1.13",
+                       "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+                       "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ini": "^1.3.4",
+                               "proto-list": "~1.2.1"
+                       }
+               },
+               "node_modules/config-chain/node_modules/ini": {
+                       "version": "1.3.8",
+                       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+                       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+                       "dev": true
+               },
+               "node_modules/configstore": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz",
+                       "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==",
+                       "dev": true,
+                       "dependencies": {
+                               "dot-prop": "^6.0.1",
+                               "graceful-fs": "^4.2.6",
+                               "unique-string": "^3.0.0",
+                               "write-file-atomic": "^3.0.3",
+                               "xdg-basedir": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/yeoman/configstore?sponsor=1"
+                       }
+               },
+               "node_modules/console-control-strings": {
+                       "version": "1.1.0",
+                       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+                       "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+                       "dev": true
+               },
+               "node_modules/cross-spawn": {
+                       "version": "7.0.3",
+                       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+                       "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+                       "dev": true,
+                       "dependencies": {
+                               "path-key": "^3.1.0",
+                               "shebang-command": "^2.0.0",
+                               "which": "^2.0.1"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/crypto-random-string": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz",
+                       "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==",
+                       "dev": true,
+                       "dependencies": {
+                               "type-fest": "^1.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/crypto-random-string/node_modules/type-fest": {
+                       "version": "1.4.0",
+                       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+                       "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/css-declaration-sorter": {
+                       "version": "6.4.1",
+                       "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
+                       "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^10 || ^12 || >=14"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.0.9"
+                       }
+               },
+               "node_modules/css-loader": {
+                       "version": "6.8.1",
+                       "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz",
+                       "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==",
+                       "dev": true,
+                       "dependencies": {
+                               "icss-utils": "^5.1.0",
+                               "postcss": "^8.4.21",
+                               "postcss-modules-extract-imports": "^3.0.0",
+                               "postcss-modules-local-by-default": "^4.0.3",
+                               "postcss-modules-scope": "^3.0.0",
+                               "postcss-modules-values": "^4.0.0",
+                               "postcss-value-parser": "^4.2.0",
+                               "semver": "^7.3.8"
+                       },
+                       "engines": {
+                               "node": ">= 12.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependencies": {
+                               "webpack": "^5.0.0"
+                       }
+               },
+               "node_modules/css-minimizer-webpack-plugin": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz",
+                       "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jridgewell/trace-mapping": "^0.3.18",
+                               "cssnano": "^6.0.1",
+                               "jest-worker": "^29.4.3",
+                               "postcss": "^8.4.24",
+                               "schema-utils": "^4.0.1",
+                               "serialize-javascript": "^6.0.1"
+                       },
+                       "engines": {
+                               "node": ">= 14.15.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependencies": {
+                               "webpack": "^5.0.0"
+                       },
+                       "peerDependenciesMeta": {
+                               "@parcel/css": {
+                                       "optional": true
+                               },
+                               "@swc/css": {
+                                       "optional": true
+                               },
+                               "clean-css": {
+                                       "optional": true
+                               },
+                               "csso": {
+                                       "optional": true
+                               },
+                               "esbuild": {
+                                       "optional": true
+                               },
+                               "lightningcss": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/css-select": {
+                       "version": "4.3.0",
+                       "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
+                       "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "boolbase": "^1.0.0",
+                               "css-what": "^6.0.1",
+                               "domhandler": "^4.3.1",
+                               "domutils": "^2.8.0",
+                               "nth-check": "^2.0.1"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/fb55"
+                       }
+               },
+               "node_modules/css-tree": {
+                       "version": "2.3.1",
+                       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+                       "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+                       "dev": true,
+                       "dependencies": {
+                               "mdn-data": "2.0.30",
+                               "source-map-js": "^1.0.1"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+                       }
+               },
+               "node_modules/css-what": {
+                       "version": "6.1.0",
+                       "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+                       "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 6"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/fb55"
+                       }
+               },
+               "node_modules/cssesc": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+                       "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+                       "dev": true,
+                       "bin": {
+                               "cssesc": "bin/cssesc"
+                       },
+                       "engines": {
+                               "node": ">=4"
+                       }
+               },
+               "node_modules/cssnano": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.1.tgz",
+                       "integrity": "sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==",
+                       "dev": true,
+                       "dependencies": {
+                               "cssnano-preset-default": "^6.0.1",
+                               "lilconfig": "^2.1.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/cssnano"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/cssnano-preset-default": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz",
+                       "integrity": "sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "css-declaration-sorter": "^6.3.1",
+                               "cssnano-utils": "^4.0.0",
+                               "postcss-calc": "^9.0.0",
+                               "postcss-colormin": "^6.0.0",
+                               "postcss-convert-values": "^6.0.0",
+                               "postcss-discard-comments": "^6.0.0",
+                               "postcss-discard-duplicates": "^6.0.0",
+                               "postcss-discard-empty": "^6.0.0",
+                               "postcss-discard-overridden": "^6.0.0",
+                               "postcss-merge-longhand": "^6.0.0",
+                               "postcss-merge-rules": "^6.0.1",
+                               "postcss-minify-font-values": "^6.0.0",
+                               "postcss-minify-gradients": "^6.0.0",
+                               "postcss-minify-params": "^6.0.0",
+                               "postcss-minify-selectors": "^6.0.0",
+                               "postcss-normalize-charset": "^6.0.0",
+                               "postcss-normalize-display-values": "^6.0.0",
+                               "postcss-normalize-positions": "^6.0.0",
+                               "postcss-normalize-repeat-style": "^6.0.0",
+                               "postcss-normalize-string": "^6.0.0",
+                               "postcss-normalize-timing-functions": "^6.0.0",
+                               "postcss-normalize-unicode": "^6.0.0",
+                               "postcss-normalize-url": "^6.0.0",
+                               "postcss-normalize-whitespace": "^6.0.0",
+                               "postcss-ordered-values": "^6.0.0",
+                               "postcss-reduce-initial": "^6.0.0",
+                               "postcss-reduce-transforms": "^6.0.0",
+                               "postcss-svgo": "^6.0.0",
+                               "postcss-unique-selectors": "^6.0.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/cssnano-utils": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.0.tgz",
+                       "integrity": "sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/csso": {
+                       "version": "5.0.5",
+                       "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
+                       "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "css-tree": "~2.2.0"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
+                               "npm": ">=7.0.0"
+                       }
+               },
+               "node_modules/csso/node_modules/css-tree": {
+                       "version": "2.2.1",
+                       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
+                       "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
+                       "dev": true,
+                       "dependencies": {
+                               "mdn-data": "2.0.28",
+                               "source-map-js": "^1.0.1"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
+                               "npm": ">=7.0.0"
+                       }
+               },
+               "node_modules/csso/node_modules/mdn-data": {
+                       "version": "2.0.28",
+                       "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
+                       "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
+                       "dev": true
+               },
+               "node_modules/debug": {
+                       "version": "4.3.4",
+                       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+                       "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ms": "2.1.2"
+                       },
+                       "engines": {
+                               "node": ">=6.0"
+                       },
+                       "peerDependenciesMeta": {
+                               "supports-color": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/decompress-response": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+                       "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "mimic-response": "^3.1.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/decompress-response/node_modules/mimic-response": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+                       "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/deep-extend": {
+                       "version": "0.6.0",
+                       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+                       "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=4.0.0"
+                       }
+               },
+               "node_modules/defer-to-connect": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+                       "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/delegates": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+                       "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+                       "dev": true
+               },
+               "node_modules/dir-glob": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+                       "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+                       "dev": true,
+                       "dependencies": {
+                               "path-type": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/dom-converter": {
+                       "version": "0.2.0",
+                       "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
+                       "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==",
+                       "dev": true,
+                       "dependencies": {
+                               "utila": "~0.4"
+                       }
+               },
+               "node_modules/dom-serializer": {
+                       "version": "1.4.1",
+                       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+                       "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
+                       "dev": true,
+                       "dependencies": {
+                               "domelementtype": "^2.0.1",
+                               "domhandler": "^4.2.0",
+                               "entities": "^2.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+                       }
+               },
+               "node_modules/domelementtype": {
+                       "version": "2.3.0",
+                       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+                       "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/fb55"
+                               }
+                       ]
+               },
+               "node_modules/domhandler": {
+                       "version": "4.3.1",
+                       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+                       "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "domelementtype": "^2.2.0"
+                       },
+                       "engines": {
+                               "node": ">= 4"
+                       },
+                       "funding": {
+                               "url": "https://github.com/fb55/domhandler?sponsor=1"
+                       }
+               },
+               "node_modules/domutils": {
+                       "version": "2.8.0",
+                       "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
+                       "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+                       "dev": true,
+                       "dependencies": {
+                               "dom-serializer": "^1.0.1",
+                               "domelementtype": "^2.2.0",
+                               "domhandler": "^4.2.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/fb55/domutils?sponsor=1"
+                       }
+               },
+               "node_modules/dot-case": {
+                       "version": "3.0.4",
+                       "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+                       "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+                       "dev": true,
+                       "dependencies": {
+                               "no-case": "^3.0.4",
+                               "tslib": "^2.0.3"
+                       }
+               },
+               "node_modules/dot-prop": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+                       "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-obj": "^2.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/earcut": {
+                       "version": "2.2.4",
+                       "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
+                       "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
+               },
+               "node_modules/eastasianwidth": {
+                       "version": "0.2.0",
+                       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+                       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+                       "dev": true
+               },
+               "node_modules/electron-to-chromium": {
+                       "version": "1.4.544",
+                       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.544.tgz",
+                       "integrity": "sha512-54z7squS1FyFRSUqq/knOFSptjjogLZXbKcYk3B0qkE1KZzvqASwRZnY2KzZQJqIYLVD38XZeoiMRflYSwyO4w==",
+                       "dev": true
+               },
+               "node_modules/emoji-regex": {
+                       "version": "8.0.0",
+                       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+                       "dev": true
+               },
+               "node_modules/encoding": {
+                       "version": "0.1.13",
+                       "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+                       "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
+                       "dev": true,
+                       "optional": true,
+                       "dependencies": {
+                               "iconv-lite": "^0.6.2"
+                       }
+               },
+               "node_modules/enhanced-resolve": {
+                       "version": "5.15.0",
+                       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+                       "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+                       "dev": true,
+                       "dependencies": {
+                               "graceful-fs": "^4.2.4",
+                               "tapable": "^2.2.0"
+                       },
+                       "engines": {
+                               "node": ">=10.13.0"
+                       }
+               },
+               "node_modules/entities": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
+                       "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
+                       "dev": true,
+                       "funding": {
+                               "url": "https://github.com/fb55/entities?sponsor=1"
+                       }
+               },
+               "node_modules/env-paths": {
+                       "version": "2.2.1",
+                       "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+                       "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/envinfo": {
+                       "version": "7.10.0",
+                       "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz",
+                       "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==",
+                       "dev": true,
+                       "bin": {
+                               "envinfo": "dist/cli.js"
+                       },
+                       "engines": {
+                               "node": ">=4"
+                       }
+               },
+               "node_modules/err-code": {
+                       "version": "2.0.3",
+                       "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+                       "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
+                       "dev": true
+               },
+               "node_modules/es-module-lexer": {
+                       "version": "1.3.1",
+                       "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz",
+                       "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==",
+                       "dev": true
+               },
+               "node_modules/escalade": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+                       "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/escape-goat": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz",
+                       "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/escape-string-regexp": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+                       "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/eslint-scope": {
+                       "version": "5.1.1",
+                       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+                       "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+                       "dev": true,
+                       "dependencies": {
+                               "esrecurse": "^4.3.0",
+                               "estraverse": "^4.1.1"
+                       },
+                       "engines": {
+                               "node": ">=8.0.0"
+                       }
+               },
+               "node_modules/esrecurse": {
+                       "version": "4.3.0",
+                       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+                       "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+                       "dev": true,
+                       "dependencies": {
+                               "estraverse": "^5.2.0"
+                       },
+                       "engines": {
+                               "node": ">=4.0"
+                       }
+               },
+               "node_modules/esrecurse/node_modules/estraverse": {
+                       "version": "5.3.0",
+                       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+                       "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=4.0"
+                       }
+               },
+               "node_modules/estraverse": {
+                       "version": "4.3.0",
+                       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+                       "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=4.0"
+                       }
+               },
+               "node_modules/events": {
+                       "version": "3.3.0",
+                       "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+                       "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.8.x"
+                       }
+               },
+               "node_modules/exponential-backoff": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz",
+                       "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==",
+                       "dev": true
+               },
+               "node_modules/fast-deep-equal": {
+                       "version": "3.1.3",
+                       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+                       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+                       "dev": true
+               },
+               "node_modules/fast-glob": {
+                       "version": "3.3.1",
+                       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+                       "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@nodelib/fs.stat": "^2.0.2",
+                               "@nodelib/fs.walk": "^1.2.3",
+                               "glob-parent": "^5.1.2",
+                               "merge2": "^1.3.0",
+                               "micromatch": "^4.0.4"
+                       },
+                       "engines": {
+                               "node": ">=8.6.0"
+                       }
+               },
+               "node_modules/fast-json-stable-stringify": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+                       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+                       "dev": true
+               },
+               "node_modules/fast-memoize": {
+                       "version": "2.5.2",
+                       "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz",
+                       "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==",
+                       "dev": true
+               },
+               "node_modules/fastest-levenshtein": {
+                       "version": "1.0.16",
+                       "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+                       "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 4.9.1"
+                       }
+               },
+               "node_modules/fastq": {
+                       "version": "1.15.0",
+                       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+                       "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+                       "dev": true,
+                       "dependencies": {
+                               "reusify": "^1.0.4"
+                       }
+               },
+               "node_modules/fill-range": {
+                       "version": "7.0.1",
+                       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+                       "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "to-regex-range": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/find-up": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+                       "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+                       "dev": true,
+                       "dependencies": {
+                               "locate-path": "^6.0.0",
+                               "path-exists": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/foreground-child": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+                       "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+                       "dev": true,
+                       "dependencies": {
+                               "cross-spawn": "^7.0.0",
+                               "signal-exit": "^4.0.1"
+                       },
+                       "engines": {
+                               "node": ">=14"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/form-data-encoder": {
+                       "version": "2.1.4",
+                       "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
+                       "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 14.17"
+                       }
+               },
+               "node_modules/fp-and-or": {
+                       "version": "0.1.4",
+                       "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.4.tgz",
+                       "integrity": "sha512-+yRYRhpnFPWXSly/6V4Lw9IfOV26uu30kynGJ03PW+MnjOEQe45RZ141QcS0aJehYBYA50GfCDnsRbFJdhssRw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/fs-minipass": {
+                       "version": "3.0.3",
+                       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+                       "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^7.0.3"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/fs-minipass/node_modules/minipass": {
+                       "version": "7.0.4",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                       "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       }
+               },
+               "node_modules/fs.realpath": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+                       "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+                       "dev": true
+               },
+               "node_modules/gauge": {
+                       "version": "4.0.4",
+                       "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+                       "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+                       "dev": true,
+                       "dependencies": {
+                               "aproba": "^1.0.3 || ^2.0.0",
+                               "color-support": "^1.1.3",
+                               "console-control-strings": "^1.1.0",
+                               "has-unicode": "^2.0.1",
+                               "signal-exit": "^3.0.7",
+                               "string-width": "^4.2.3",
+                               "strip-ansi": "^6.0.1",
+                               "wide-align": "^1.1.5"
+                       },
+                       "engines": {
+                               "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+                       }
+               },
+               "node_modules/gauge/node_modules/ansi-regex": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/gauge/node_modules/signal-exit": {
+                       "version": "3.0.7",
+                       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+                       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+                       "dev": true
+               },
+               "node_modules/gauge/node_modules/strip-ansi": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/geotiff": {
+                       "version": "2.0.7",
+                       "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz",
+                       "integrity": "sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==",
+                       "dependencies": {
+                               "@petamoriken/float16": "^3.4.7",
+                               "lerc": "^3.0.0",
+                               "pako": "^2.0.4",
+                               "parse-headers": "^2.0.2",
+                               "quick-lru": "^6.1.1",
+                               "web-worker": "^1.2.0",
+                               "xml-utils": "^1.0.2"
+                       },
+                       "engines": {
+                               "node": ">=10.19"
+                       }
+               },
+               "node_modules/get-stdin": {
+                       "version": "8.0.0",
+                       "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
+                       "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/get-stream": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+                       "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/glob": {
+                       "version": "10.3.10",
+                       "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                       "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                       "dev": true,
+                       "dependencies": {
+                               "foreground-child": "^3.1.0",
+                               "jackspeak": "^2.3.5",
+                               "minimatch": "^9.0.1",
+                               "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                               "path-scurry": "^1.10.1"
+                       },
+                       "bin": {
+                               "glob": "dist/esm/bin.mjs"
+                       },
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/glob-parent": {
+                       "version": "5.1.2",
+                       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+                       "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-glob": "^4.0.1"
+                       },
+                       "engines": {
+                               "node": ">= 6"
+                       }
+               },
+               "node_modules/glob-to-regexp": {
+                       "version": "0.4.1",
+                       "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+                       "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+                       "dev": true
+               },
+               "node_modules/global-dirs": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz",
+                       "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==",
+                       "dev": true,
+                       "dependencies": {
+                               "ini": "2.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/global-dirs/node_modules/ini": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
+                       "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/globby": {
+                       "version": "11.1.0",
+                       "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+                       "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+                       "dev": true,
+                       "dependencies": {
+                               "array-union": "^2.1.0",
+                               "dir-glob": "^3.0.1",
+                               "fast-glob": "^3.2.9",
+                               "ignore": "^5.2.0",
+                               "merge2": "^1.4.1",
+                               "slash": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/got": {
+                       "version": "12.6.1",
+                       "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
+                       "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@sindresorhus/is": "^5.2.0",
+                               "@szmarczak/http-timer": "^5.0.1",
+                               "cacheable-lookup": "^7.0.0",
+                               "cacheable-request": "^10.2.8",
+                               "decompress-response": "^6.0.0",
+                               "form-data-encoder": "^2.1.2",
+                               "get-stream": "^6.0.1",
+                               "http2-wrapper": "^2.1.10",
+                               "lowercase-keys": "^3.0.0",
+                               "p-cancelable": "^3.0.0",
+                               "responselike": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sindresorhus/got?sponsor=1"
+                       }
+               },
+               "node_modules/graceful-fs": {
+                       "version": "4.2.11",
+                       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+                       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+                       "dev": true
+               },
+               "node_modules/has": {
+                       "version": "1.0.4",
+                       "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
+                       "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 0.4.0"
+                       }
+               },
+               "node_modules/has-flag": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/has-unicode": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+                       "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+                       "dev": true
+               },
+               "node_modules/has-yarn": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz",
+                       "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/he": {
+                       "version": "1.2.0",
+                       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+                       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+                       "dev": true,
+                       "bin": {
+                               "he": "bin/he"
+                       }
+               },
+               "node_modules/hosted-git-info": {
+                       "version": "5.2.1",
+                       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz",
+                       "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==",
+                       "dev": true,
+                       "dependencies": {
+                               "lru-cache": "^7.5.1"
+                       },
+                       "engines": {
+                               "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+                       }
+               },
+               "node_modules/html-minifier-terser": {
+                       "version": "6.1.0",
+                       "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
+                       "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==",
+                       "dev": true,
+                       "dependencies": {
+                               "camel-case": "^4.1.2",
+                               "clean-css": "^5.2.2",
+                               "commander": "^8.3.0",
+                               "he": "^1.2.0",
+                               "param-case": "^3.0.4",
+                               "relateurl": "^0.2.7",
+                               "terser": "^5.10.0"
+                       },
+                       "bin": {
+                               "html-minifier-terser": "cli.js"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       }
+               },
+               "node_modules/html-webpack-plugin": {
+                       "version": "5.5.3",
+                       "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz",
+                       "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/html-minifier-terser": "^6.0.0",
+                               "html-minifier-terser": "^6.0.2",
+                               "lodash": "^4.17.21",
+                               "pretty-error": "^4.0.0",
+                               "tapable": "^2.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/html-webpack-plugin"
+                       },
+                       "peerDependencies": {
+                               "webpack": "^5.20.0"
+                       }
+               },
+               "node_modules/htmlparser2": {
+                       "version": "6.1.0",
+                       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
+                       "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
+                       "dev": true,
+                       "funding": [
+                               "https://github.com/fb55/htmlparser2?sponsor=1",
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/fb55"
+                               }
+                       ],
+                       "dependencies": {
+                               "domelementtype": "^2.0.1",
+                               "domhandler": "^4.0.0",
+                               "domutils": "^2.5.2",
+                               "entities": "^2.0.0"
+                       }
+               },
+               "node_modules/http-cache-semantics": {
+                       "version": "4.1.1",
+                       "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+                       "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+                       "dev": true
+               },
+               "node_modules/http-proxy-agent": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+                       "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+                       "dev": true,
+                       "dependencies": {
+                               "@tootallnate/once": "2",
+                               "agent-base": "6",
+                               "debug": "4"
+                       },
+                       "engines": {
+                               "node": ">= 6"
+                       }
+               },
+               "node_modules/http2-wrapper": {
+                       "version": "2.2.0",
+                       "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz",
+                       "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "quick-lru": "^5.1.1",
+                               "resolve-alpn": "^1.2.0"
+                       },
+                       "engines": {
+                               "node": ">=10.19.0"
+                       }
+               },
+               "node_modules/http2-wrapper/node_modules/quick-lru": {
+                       "version": "5.1.1",
+                       "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+                       "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/https-proxy-agent": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+                       "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+                       "dev": true,
+                       "dependencies": {
+                               "agent-base": "6",
+                               "debug": "4"
+                       },
+                       "engines": {
+                               "node": ">= 6"
+                       }
+               },
+               "node_modules/humanize-ms": {
+                       "version": "1.2.1",
+                       "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+                       "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ms": "^2.0.0"
+                       }
+               },
+               "node_modules/iconv-lite": {
+                       "version": "0.6.3",
+                       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+                       "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+                       "dev": true,
+                       "optional": true,
+                       "dependencies": {
+                               "safer-buffer": ">= 2.1.2 < 3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/icss-utils": {
+                       "version": "5.1.0",
+                       "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+                       "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^10 || ^12 || >= 14"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.1.0"
+                       }
+               },
+               "node_modules/ieee754": {
+                       "version": "1.2.1",
+                       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+                       "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/feross"
+                               },
+                               {
+                                       "type": "patreon",
+                                       "url": "https://www.patreon.com/feross"
+                               },
+                               {
+                                       "type": "consulting",
+                                       "url": "https://feross.org/support"
+                               }
+                       ]
+               },
+               "node_modules/ignore": {
+                       "version": "5.2.4",
+                       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+                       "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 4"
+                       }
+               },
+               "node_modules/ignore-walk": {
+                       "version": "6.0.3",
+                       "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz",
+                       "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==",
+                       "dev": true,
+                       "dependencies": {
+                               "minimatch": "^9.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/import-lazy": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
+                       "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/import-local": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
+                       "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
+                       "dev": true,
+                       "dependencies": {
+                               "pkg-dir": "^4.2.0",
+                               "resolve-cwd": "^3.0.0"
+                       },
+                       "bin": {
+                               "import-local-fixture": "fixtures/cli.js"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/imurmurhash": {
+                       "version": "0.1.4",
+                       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+                       "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.8.19"
+                       }
+               },
+               "node_modules/indent-string": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+                       "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/inflight": {
+                       "version": "1.0.6",
+                       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+                       "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+                       "dev": true,
+                       "dependencies": {
+                               "once": "^1.3.0",
+                               "wrappy": "1"
+                       }
+               },
+               "node_modules/inherits": {
+                       "version": "2.0.4",
+                       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+                       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+                       "dev": true
+               },
+               "node_modules/ini": {
+                       "version": "4.1.1",
+                       "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
+                       "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/interpret": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
+                       "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10.13.0"
+                       }
+               },
+               "node_modules/ip": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
+                       "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
+                       "dev": true
+               },
+               "node_modules/is-ci": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
+                       "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ci-info": "^3.2.0"
+                       },
+                       "bin": {
+                               "is-ci": "bin.js"
+                       }
+               },
+               "node_modules/is-core-module": {
+                       "version": "2.13.0",
+                       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
+                       "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "has": "^1.0.3"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/ljharb"
+                       }
+               },
+               "node_modules/is-extglob": {
+                       "version": "2.1.1",
+                       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+                       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/is-fullwidth-code-point": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/is-glob": {
+                       "version": "4.0.3",
+                       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+                       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-extglob": "^2.1.1"
+                       },
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/is-installed-globally": {
+                       "version": "0.4.0",
+                       "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
+                       "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "global-dirs": "^3.0.0",
+                               "is-path-inside": "^3.0.2"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/is-lambda": {
+                       "version": "1.0.1",
+                       "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
+                       "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
+                       "dev": true
+               },
+               "node_modules/is-npm": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz",
+                       "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/is-number": {
+                       "version": "7.0.0",
+                       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+                       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.12.0"
+                       }
+               },
+               "node_modules/is-obj": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+                       "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/is-path-inside": {
+                       "version": "3.0.3",
+                       "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+                       "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/is-plain-object": {
+                       "version": "2.0.4",
+                       "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+                       "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+                       "dev": true,
+                       "dependencies": {
+                               "isobject": "^3.0.1"
+                       },
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/is-typedarray": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+                       "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
+                       "dev": true
+               },
+               "node_modules/is-yarn-global": {
+                       "version": "0.4.1",
+                       "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz",
+                       "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12"
+                       }
+               },
+               "node_modules/isexe": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+                       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+                       "dev": true
+               },
+               "node_modules/isobject": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+                       "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/jackspeak": {
+                       "version": "2.3.6",
+                       "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+                       "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@isaacs/cliui": "^8.0.2"
+                       },
+                       "engines": {
+                               "node": ">=14"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       },
+                       "optionalDependencies": {
+                               "@pkgjs/parseargs": "^0.11.0"
+                       }
+               },
+               "node_modules/jest-util": {
+                       "version": "29.7.0",
+                       "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+                       "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jest/types": "^29.6.3",
+                               "@types/node": "*",
+                               "chalk": "^4.0.0",
+                               "ci-info": "^3.2.0",
+                               "graceful-fs": "^4.2.9",
+                               "picomatch": "^2.2.3"
+                       },
+                       "engines": {
+                               "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/jest-worker": {
+                       "version": "29.7.0",
+                       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+                       "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/node": "*",
+                               "jest-util": "^29.7.0",
+                               "merge-stream": "^2.0.0",
+                               "supports-color": "^8.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/jju": {
+                       "version": "1.4.0",
+                       "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
+                       "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
+                       "dev": true
+               },
+               "node_modules/js-yaml": {
+                       "version": "4.1.0",
+                       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+                       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+                       "dev": true,
+                       "dependencies": {
+                               "argparse": "^2.0.1"
+                       },
+                       "bin": {
+                               "js-yaml": "bin/js-yaml.js"
+                       }
+               },
+               "node_modules/js2xmlparser": {
+                       "version": "4.0.2",
+                       "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz",
+                       "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==",
+                       "dev": true,
+                       "dependencies": {
+                               "xmlcreate": "^2.0.4"
+                       }
+               },
+               "node_modules/jsdoc": {
+                       "version": "4.0.2",
+                       "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz",
+                       "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@babel/parser": "^7.20.15",
+                               "@jsdoc/salty": "^0.2.1",
+                               "@types/markdown-it": "^12.2.3",
+                               "bluebird": "^3.7.2",
+                               "catharsis": "^0.9.0",
+                               "escape-string-regexp": "^2.0.0",
+                               "js2xmlparser": "^4.0.2",
+                               "klaw": "^3.0.0",
+                               "markdown-it": "^12.3.2",
+                               "markdown-it-anchor": "^8.4.1",
+                               "marked": "^4.0.10",
+                               "mkdirp": "^1.0.4",
+                               "requizzle": "^0.2.3",
+                               "strip-json-comments": "^3.1.0",
+                               "underscore": "~1.13.2"
+                       },
+                       "bin": {
+                               "jsdoc": "jsdoc.js"
+                       },
+                       "engines": {
+                               "node": ">=12.0.0"
+                       }
+               },
+               "node_modules/json-buffer": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+                       "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+                       "dev": true
+               },
+               "node_modules/json-parse-even-better-errors": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz",
+                       "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/json-parse-helpfulerror": {
+                       "version": "1.0.3",
+                       "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz",
+                       "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==",
+                       "dev": true,
+                       "dependencies": {
+                               "jju": "^1.1.0"
+                       }
+               },
+               "node_modules/json-schema-traverse": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+                       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+                       "dev": true
+               },
+               "node_modules/json5": {
+                       "version": "2.2.3",
+                       "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+                       "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+                       "dev": true,
+                       "bin": {
+                               "json5": "lib/cli.js"
+                       },
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/jsonlines": {
+                       "version": "0.1.1",
+                       "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz",
+                       "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==",
+                       "dev": true
+               },
+               "node_modules/jsonparse": {
+                       "version": "1.3.1",
+                       "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+                       "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
+                       "dev": true,
+                       "engines": [
+                               "node >= 0.2.0"
+                       ]
+               },
+               "node_modules/keyv": {
+                       "version": "4.5.4",
+                       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+                       "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+                       "dev": true,
+                       "dependencies": {
+                               "json-buffer": "3.0.1"
+                       }
+               },
+               "node_modules/kind-of": {
+                       "version": "6.0.3",
+                       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+                       "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/klaw": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
+                       "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
+                       "dev": true,
+                       "dependencies": {
+                               "graceful-fs": "^4.1.9"
+                       }
+               },
+               "node_modules/kleur": {
+                       "version": "4.1.5",
+                       "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+                       "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/latest-version": {
+                       "version": "7.0.0",
+                       "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz",
+                       "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==",
+                       "dev": true,
+                       "dependencies": {
+                               "package-json": "^8.1.0"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/lerc": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
+                       "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww=="
+               },
+               "node_modules/lilconfig": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+                       "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/linkify-it": {
+                       "version": "3.0.3",
+                       "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
+                       "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "uc.micro": "^1.0.1"
+                       }
+               },
+               "node_modules/loader-runner": {
+                       "version": "4.3.0",
+                       "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+                       "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6.11.5"
+                       }
+               },
+               "node_modules/locate-path": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+                       "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+                       "dev": true,
+                       "dependencies": {
+                               "p-locate": "^5.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/lodash": {
+                       "version": "4.17.21",
+                       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+                       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+                       "dev": true
+               },
+               "node_modules/lodash.memoize": {
+                       "version": "4.1.2",
+                       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+                       "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+                       "dev": true
+               },
+               "node_modules/lodash.uniq": {
+                       "version": "4.5.0",
+                       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+                       "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
+                       "dev": true
+               },
+               "node_modules/lower-case": {
+                       "version": "2.0.2",
+                       "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+                       "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+                       "dev": true,
+                       "dependencies": {
+                               "tslib": "^2.0.3"
+                       }
+               },
+               "node_modules/lowercase-keys": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
+                       "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/lru-cache": {
+                       "version": "7.18.3",
+                       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                       "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12"
+                       }
+               },
+               "node_modules/make-fetch-happen": {
+                       "version": "11.1.1",
+                       "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz",
+                       "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==",
+                       "dev": true,
+                       "dependencies": {
+                               "agentkeepalive": "^4.2.1",
+                               "cacache": "^17.0.0",
+                               "http-cache-semantics": "^4.1.1",
+                               "http-proxy-agent": "^5.0.0",
+                               "https-proxy-agent": "^5.0.0",
+                               "is-lambda": "^1.0.1",
+                               "lru-cache": "^7.7.1",
+                               "minipass": "^5.0.0",
+                               "minipass-fetch": "^3.0.0",
+                               "minipass-flush": "^1.0.5",
+                               "minipass-pipeline": "^1.2.4",
+                               "negotiator": "^0.6.3",
+                               "promise-retry": "^2.0.1",
+                               "socks-proxy-agent": "^7.0.0",
+                               "ssri": "^10.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/markdown-it": {
+                       "version": "12.3.2",
+                       "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
+                       "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
+                       "dev": true,
+                       "dependencies": {
+                               "argparse": "^2.0.1",
+                               "entities": "~2.1.0",
+                               "linkify-it": "^3.0.1",
+                               "mdurl": "^1.0.1",
+                               "uc.micro": "^1.0.5"
+                       },
+                       "bin": {
+                               "markdown-it": "bin/markdown-it.js"
+                       }
+               },
+               "node_modules/markdown-it-anchor": {
+                       "version": "8.6.7",
+                       "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz",
+                       "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
+                       "dev": true,
+                       "peerDependencies": {
+                               "@types/markdown-it": "*",
+                               "markdown-it": "*"
+                       }
+               },
+               "node_modules/marked": {
+                       "version": "4.3.0",
+                       "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+                       "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+                       "dev": true,
+                       "bin": {
+                               "marked": "bin/marked.js"
+                       },
+                       "engines": {
+                               "node": ">= 12"
+                       }
+               },
+               "node_modules/mdn-data": {
+                       "version": "2.0.30",
+                       "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+                       "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+                       "dev": true
+               },
+               "node_modules/mdurl": {
+                       "version": "1.0.1",
+                       "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+                       "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
+                       "dev": true
+               },
+               "node_modules/merge-stream": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+                       "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+                       "dev": true
+               },
+               "node_modules/merge2": {
+                       "version": "1.4.1",
+                       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+                       "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/micromatch": {
+                       "version": "4.0.5",
+                       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+                       "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+                       "dev": true,
+                       "dependencies": {
+                               "braces": "^3.0.2",
+                               "picomatch": "^2.3.1"
+                       },
+                       "engines": {
+                               "node": ">=8.6"
+                       }
+               },
+               "node_modules/mime-db": {
+                       "version": "1.52.0",
+                       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+                       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 0.6"
+                       }
+               },
+               "node_modules/mime-types": {
+                       "version": "2.1.35",
+                       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+                       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+                       "dev": true,
+                       "dependencies": {
+                               "mime-db": "1.52.0"
+                       },
+                       "engines": {
+                               "node": ">= 0.6"
+                       }
+               },
+               "node_modules/mimic-response": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
+                       "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/mini-css-extract-plugin": {
+                       "version": "2.7.6",
+                       "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz",
+                       "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==",
+                       "dev": true,
+                       "dependencies": {
+                               "schema-utils": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">= 12.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependencies": {
+                               "webpack": "^5.0.0"
+                       }
+               },
+               "node_modules/minimatch": {
+                       "version": "9.0.3",
+                       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                       "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                       "dev": true,
+                       "dependencies": {
+                               "brace-expansion": "^2.0.1"
+                       },
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/minimist": {
+                       "version": "1.2.8",
+                       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+                       "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+                       "dev": true,
+                       "funding": {
+                               "url": "https://github.com/sponsors/ljharb"
+                       }
+               },
+               "node_modules/minipass": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                       "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-collect": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+                       "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/minipass-collect/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-fetch": {
+                       "version": "3.0.4",
+                       "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz",
+                       "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^7.0.3",
+                               "minipass-sized": "^1.0.3",
+                               "minizlib": "^2.1.2"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       },
+                       "optionalDependencies": {
+                               "encoding": "^0.1.13"
+                       }
+               },
+               "node_modules/minipass-fetch/node_modules/minipass": {
+                       "version": "7.0.4",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                       "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       }
+               },
+               "node_modules/minipass-flush": {
+                       "version": "1.0.5",
+                       "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+                       "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/minipass-flush/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-json-stream": {
+                       "version": "1.0.1",
+                       "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz",
+                       "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==",
+                       "dev": true,
+                       "dependencies": {
+                               "jsonparse": "^1.3.1",
+                               "minipass": "^3.0.0"
+                       }
+               },
+               "node_modules/minipass-json-stream/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-pipeline": {
+                       "version": "1.2.4",
+                       "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+                       "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-pipeline/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-sized": {
+                       "version": "1.0.3",
+                       "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
+                       "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minipass-sized/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/minizlib": {
+                       "version": "2.1.2",
+                       "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+                       "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^3.0.0",
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/minizlib/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/mkdirp": {
+                       "version": "1.0.4",
+                       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+                       "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+                       "dev": true,
+                       "bin": {
+                               "mkdirp": "bin/cmd.js"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/ms": {
+                       "version": "2.1.2",
+                       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                       "dev": true
+               },
+               "node_modules/nanoid": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+                       "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/ai"
+                               }
+                       ],
+                       "bin": {
+                               "nanoid": "bin/nanoid.cjs"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+                       }
+               },
+               "node_modules/negotiator": {
+                       "version": "0.6.3",
+                       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+                       "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 0.6"
+                       }
+               },
+               "node_modules/neo-async": {
+                       "version": "2.6.2",
+                       "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+                       "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+                       "dev": true
+               },
+               "node_modules/no-case": {
+                       "version": "3.0.4",
+                       "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+                       "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+                       "dev": true,
+                       "dependencies": {
+                               "lower-case": "^2.0.2",
+                               "tslib": "^2.0.3"
+                       }
+               },
+               "node_modules/node-gyp": {
+                       "version": "9.4.0",
+                       "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz",
+                       "integrity": "sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==",
+                       "dev": true,
+                       "dependencies": {
+                               "env-paths": "^2.2.0",
+                               "exponential-backoff": "^3.1.1",
+                               "glob": "^7.1.4",
+                               "graceful-fs": "^4.2.6",
+                               "make-fetch-happen": "^11.0.3",
+                               "nopt": "^6.0.0",
+                               "npmlog": "^6.0.0",
+                               "rimraf": "^3.0.2",
+                               "semver": "^7.3.5",
+                               "tar": "^6.1.2",
+                               "which": "^2.0.2"
+                       },
+                       "bin": {
+                               "node-gyp": "bin/node-gyp.js"
+                       },
+                       "engines": {
+                               "node": "^12.13 || ^14.13 || >=16"
+                       }
+               },
+               "node_modules/node-gyp/node_modules/brace-expansion": {
+                       "version": "1.1.11",
+                       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                       "dev": true,
+                       "dependencies": {
+                               "balanced-match": "^1.0.0",
+                               "concat-map": "0.0.1"
+                       }
+               },
+               "node_modules/node-gyp/node_modules/glob": {
+                       "version": "7.2.3",
+                       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+                       "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "fs.realpath": "^1.0.0",
+                               "inflight": "^1.0.4",
+                               "inherits": "2",
+                               "minimatch": "^3.1.1",
+                               "once": "^1.3.0",
+                               "path-is-absolute": "^1.0.0"
+                       },
+                       "engines": {
+                               "node": "*"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/node-gyp/node_modules/minimatch": {
+                       "version": "3.1.2",
+                       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+                       "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+                       "dev": true,
+                       "dependencies": {
+                               "brace-expansion": "^1.1.7"
+                       },
+                       "engines": {
+                               "node": "*"
+                       }
+               },
+               "node_modules/node-gyp/node_modules/rimraf": {
+                       "version": "3.0.2",
+                       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+                       "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+                       "dev": true,
+                       "dependencies": {
+                               "glob": "^7.1.3"
+                       },
+                       "bin": {
+                               "rimraf": "bin.js"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/node-releases": {
+                       "version": "2.0.13",
+                       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
+                       "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
+                       "dev": true
+               },
+               "node_modules/nopt": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
+                       "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==",
+                       "dev": true,
+                       "dependencies": {
+                               "abbrev": "^1.0.0"
+                       },
+                       "bin": {
+                               "nopt": "bin/nopt.js"
+                       },
+                       "engines": {
+                               "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+                       }
+               },
+               "node_modules/normalize-package-data": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz",
+                       "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "hosted-git-info": "^6.0.0",
+                               "is-core-module": "^2.8.1",
+                               "semver": "^7.3.5",
+                               "validate-npm-package-license": "^3.0.4"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/normalize-package-data/node_modules/hosted-git-info": {
+                       "version": "6.1.1",
+                       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                       "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                       "dev": true,
+                       "dependencies": {
+                               "lru-cache": "^7.5.1"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/normalize-url": {
+                       "version": "8.0.0",
+                       "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
+                       "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/npm-bundled": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz",
+                       "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "npm-normalize-package-bin": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-check-updates": {
+                       "version": "16.14.5",
+                       "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.5.tgz",
+                       "integrity": "sha512-f7v3YzPUgadtkB2LAVhiWMjrSejJ0N8OM9JjjVfxBz2neHqmPSWQUAUA+U/p3xeXHl9bghRD6knRqBhm9dkRGg==",
+                       "dev": true,
+                       "dependencies": {
+                               "chalk": "^5.3.0",
+                               "cli-table3": "^0.6.3",
+                               "commander": "^10.0.1",
+                               "fast-memoize": "^2.5.2",
+                               "find-up": "5.0.0",
+                               "fp-and-or": "^0.1.3",
+                               "get-stdin": "^8.0.0",
+                               "globby": "^11.0.4",
+                               "hosted-git-info": "^5.1.0",
+                               "ini": "^4.1.1",
+                               "js-yaml": "^4.1.0",
+                               "json-parse-helpfulerror": "^1.0.3",
+                               "jsonlines": "^0.1.1",
+                               "lodash": "^4.17.21",
+                               "make-fetch-happen": "^11.1.1",
+                               "minimatch": "^9.0.3",
+                               "p-map": "^4.0.0",
+                               "pacote": "15.2.0",
+                               "parse-github-url": "^1.0.2",
+                               "progress": "^2.0.3",
+                               "prompts-ncu": "^3.0.0",
+                               "rc-config-loader": "^4.1.3",
+                               "remote-git-tags": "^3.0.0",
+                               "rimraf": "^5.0.1",
+                               "semver": "^7.5.4",
+                               "semver-utils": "^1.1.4",
+                               "source-map-support": "^0.5.21",
+                               "spawn-please": "^2.0.1",
+                               "strip-ansi": "^7.1.0",
+                               "strip-json-comments": "^5.0.1",
+                               "untildify": "^4.0.0",
+                               "update-notifier": "^6.0.2"
+                       },
+                       "bin": {
+                               "ncu": "build/src/bin/cli.js",
+                               "npm-check-updates": "build/src/bin/cli.js"
+                       },
+                       "engines": {
+                               "node": ">=14.14"
+                       }
+               },
+               "node_modules/npm-check-updates/node_modules/chalk": {
+                       "version": "5.3.0",
+                       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+                       "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.17.0 || ^14.13 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/chalk?sponsor=1"
+                       }
+               },
+               "node_modules/npm-check-updates/node_modules/commander": {
+                       "version": "10.0.1",
+                       "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+                       "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14"
+                       }
+               },
+               "node_modules/npm-check-updates/node_modules/strip-json-comments": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
+                       "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/npm-install-checks": {
+                       "version": "6.3.0",
+                       "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz",
+                       "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==",
+                       "dev": true,
+                       "dependencies": {
+                               "semver": "^7.1.1"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-normalize-package-bin": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",
+                       "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-package-arg": {
+                       "version": "10.1.0",
+                       "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz",
+                       "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==",
+                       "dev": true,
+                       "dependencies": {
+                               "hosted-git-info": "^6.0.0",
+                               "proc-log": "^3.0.0",
+                               "semver": "^7.3.5",
+                               "validate-npm-package-name": "^5.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-package-arg/node_modules/hosted-git-info": {
+                       "version": "6.1.1",
+                       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                       "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                       "dev": true,
+                       "dependencies": {
+                               "lru-cache": "^7.5.1"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-packlist": {
+                       "version": "7.0.4",
+                       "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz",
+                       "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "ignore-walk": "^6.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-pick-manifest": {
+                       "version": "8.0.2",
+                       "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz",
+                       "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==",
+                       "dev": true,
+                       "dependencies": {
+                               "npm-install-checks": "^6.0.0",
+                               "npm-normalize-package-bin": "^3.0.0",
+                               "npm-package-arg": "^10.0.0",
+                               "semver": "^7.3.5"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npm-registry-fetch": {
+                       "version": "14.0.5",
+                       "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz",
+                       "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==",
+                       "dev": true,
+                       "dependencies": {
+                               "make-fetch-happen": "^11.0.0",
+                               "minipass": "^5.0.0",
+                               "minipass-fetch": "^3.0.0",
+                               "minipass-json-stream": "^1.0.1",
+                               "minizlib": "^2.1.2",
+                               "npm-package-arg": "^10.0.0",
+                               "proc-log": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/npmlog": {
+                       "version": "6.0.2",
+                       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+                       "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+                       "dev": true,
+                       "dependencies": {
+                               "are-we-there-yet": "^3.0.0",
+                               "console-control-strings": "^1.1.0",
+                               "gauge": "^4.0.3",
+                               "set-blocking": "^2.0.0"
+                       },
+                       "engines": {
+                               "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+                       }
+               },
+               "node_modules/nth-check": {
+                       "version": "2.1.1",
+                       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+                       "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+                       "dev": true,
+                       "dependencies": {
+                               "boolbase": "^1.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/fb55/nth-check?sponsor=1"
+                       }
+               },
+               "node_modules/ol": {
+                       "version": "8.1.0",
+                       "resolved": "https://registry.npmjs.org/ol/-/ol-8.1.0.tgz",
+                       "integrity": "sha512-cx3SH2plpFS9fM8pp1nCypgQXGJD7Mcb1E3mEySmy5XEw1DUEo+kkNzgtAZz5qupekqi7aU9iBJEjCoMfqvO2Q==",
+                       "dependencies": {
+                               "earcut": "^2.2.3",
+                               "geotiff": "^2.0.7",
+                               "pbf": "3.2.1",
+                               "rbush": "^3.0.1"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/openlayers"
+                       }
+               },
+               "node_modules/once": {
+                       "version": "1.4.0",
+                       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+                       "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+                       "dev": true,
+                       "dependencies": {
+                               "wrappy": "1"
+                       }
+               },
+               "node_modules/p-cancelable": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+                       "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12.20"
+                       }
+               },
+               "node_modules/p-limit": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+                       "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "yocto-queue": "^0.1.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/p-locate": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+                       "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+                       "dev": true,
+                       "dependencies": {
+                               "p-limit": "^3.0.2"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/p-map": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                       "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "aggregate-error": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/p-try": {
+                       "version": "2.2.0",
+                       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+                       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/package-json": {
+                       "version": "8.1.1",
+                       "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz",
+                       "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==",
+                       "dev": true,
+                       "dependencies": {
+                               "got": "^12.1.0",
+                               "registry-auth-token": "^5.0.1",
+                               "registry-url": "^6.0.0",
+                               "semver": "^7.3.7"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/pacote": {
+                       "version": "15.2.0",
+                       "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz",
+                       "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@npmcli/git": "^4.0.0",
+                               "@npmcli/installed-package-contents": "^2.0.1",
+                               "@npmcli/promise-spawn": "^6.0.1",
+                               "@npmcli/run-script": "^6.0.0",
+                               "cacache": "^17.0.0",
+                               "fs-minipass": "^3.0.0",
+                               "minipass": "^5.0.0",
+                               "npm-package-arg": "^10.0.0",
+                               "npm-packlist": "^7.0.0",
+                               "npm-pick-manifest": "^8.0.0",
+                               "npm-registry-fetch": "^14.0.0",
+                               "proc-log": "^3.0.0",
+                               "promise-retry": "^2.0.1",
+                               "read-package-json": "^6.0.0",
+                               "read-package-json-fast": "^3.0.0",
+                               "sigstore": "^1.3.0",
+                               "ssri": "^10.0.0",
+                               "tar": "^6.1.11"
+                       },
+                       "bin": {
+                               "pacote": "lib/bin.js"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/pako": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+                       "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
+               },
+               "node_modules/param-case": {
+                       "version": "3.0.4",
+                       "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
+                       "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
+                       "dev": true,
+                       "dependencies": {
+                               "dot-case": "^3.0.4",
+                               "tslib": "^2.0.3"
+                       }
+               },
+               "node_modules/parse-github-url": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz",
+                       "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==",
+                       "dev": true,
+                       "bin": {
+                               "parse-github-url": "cli.js"
+                       },
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/parse-headers": {
+                       "version": "2.0.5",
+                       "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",
+                       "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA=="
+               },
+               "node_modules/pascal-case": {
+                       "version": "3.1.2",
+                       "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
+                       "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
+                       "dev": true,
+                       "dependencies": {
+                               "no-case": "^3.0.4",
+                               "tslib": "^2.0.3"
+                       }
+               },
+               "node_modules/path-exists": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+                       "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/path-is-absolute": {
+                       "version": "1.0.1",
+                       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+                       "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/path-key": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+                       "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/path-parse": {
+                       "version": "1.0.7",
+                       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+                       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+                       "dev": true
+               },
+               "node_modules/path-scurry": {
+                       "version": "1.10.1",
+                       "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
+                       "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "lru-cache": "^9.1.1 || ^10.0.0",
+                               "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+                       },
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/path-scurry/node_modules/lru-cache": {
+                       "version": "10.0.1",
+                       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
+                       "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
+                       "dev": true,
+                       "engines": {
+                               "node": "14 || >=16.14"
+                       }
+               },
+               "node_modules/path-type": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+                       "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/pbf": {
+                       "version": "3.2.1",
+                       "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
+                       "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
+                       "dependencies": {
+                               "ieee754": "^1.1.12",
+                               "resolve-protobuf-schema": "^2.1.0"
+                       },
+                       "bin": {
+                               "pbf": "bin/pbf"
+                       }
+               },
+               "node_modules/picocolors": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+                       "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+                       "dev": true
+               },
+               "node_modules/picomatch": {
+                       "version": "2.3.1",
+                       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+                       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8.6"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/jonschlinkert"
+                       }
+               },
+               "node_modules/pkg-dir": {
+                       "version": "4.2.0",
+                       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+                       "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "find-up": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/pkg-dir/node_modules/find-up": {
+                       "version": "4.1.0",
+                       "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+                       "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+                       "dev": true,
+                       "dependencies": {
+                               "locate-path": "^5.0.0",
+                               "path-exists": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/pkg-dir/node_modules/locate-path": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+                       "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+                       "dev": true,
+                       "dependencies": {
+                               "p-locate": "^4.1.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/pkg-dir/node_modules/p-limit": {
+                       "version": "2.3.0",
+                       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+                       "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+                       "dev": true,
+                       "dependencies": {
+                               "p-try": "^2.0.0"
+                       },
+                       "engines": {
+                               "node": ">=6"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/pkg-dir/node_modules/p-locate": {
+                       "version": "4.1.0",
+                       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+                       "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+                       "dev": true,
+                       "dependencies": {
+                               "p-limit": "^2.2.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/postcss": {
+                       "version": "8.4.31",
+                       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+                       "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "opencollective",
+                                       "url": "https://opencollective.com/postcss/"
+                               },
+                               {
+                                       "type": "tidelift",
+                                       "url": "https://tidelift.com/funding/github/npm/postcss"
+                               },
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/ai"
+                               }
+                       ],
+                       "dependencies": {
+                               "nanoid": "^3.3.6",
+                               "picocolors": "^1.0.0",
+                               "source-map-js": "^1.0.2"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12 || >=14"
+                       }
+               },
+               "node_modules/postcss-calc": {
+                       "version": "9.0.1",
+                       "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz",
+                       "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-selector-parser": "^6.0.11",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.2"
+                       }
+               },
+               "node_modules/postcss-colormin": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.0.tgz",
+                       "integrity": "sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "caniuse-api": "^3.0.0",
+                               "colord": "^2.9.1",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-convert-values": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz",
+                       "integrity": "sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-discard-comments": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz",
+                       "integrity": "sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-discard-duplicates": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz",
+                       "integrity": "sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-discard-empty": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz",
+                       "integrity": "sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-discard-overridden": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz",
+                       "integrity": "sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-merge-longhand": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz",
+                       "integrity": "sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0",
+                               "stylehacks": "^6.0.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-merge-rules": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz",
+                       "integrity": "sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "caniuse-api": "^3.0.0",
+                               "cssnano-utils": "^4.0.0",
+                               "postcss-selector-parser": "^6.0.5"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-minify-font-values": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz",
+                       "integrity": "sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-minify-gradients": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz",
+                       "integrity": "sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==",
+                       "dev": true,
+                       "dependencies": {
+                               "colord": "^2.9.1",
+                               "cssnano-utils": "^4.0.0",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-minify-params": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz",
+                       "integrity": "sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "cssnano-utils": "^4.0.0",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-minify-selectors": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz",
+                       "integrity": "sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-selector-parser": "^6.0.5"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-modules-extract-imports": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
+                       "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^10 || ^12 || >= 14"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.1.0"
+                       }
+               },
+               "node_modules/postcss-modules-local-by-default": {
+                       "version": "4.0.3",
+                       "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz",
+                       "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==",
+                       "dev": true,
+                       "dependencies": {
+                               "icss-utils": "^5.0.0",
+                               "postcss-selector-parser": "^6.0.2",
+                               "postcss-value-parser": "^4.1.0"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12 || >= 14"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.1.0"
+                       }
+               },
+               "node_modules/postcss-modules-scope": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
+                       "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-selector-parser": "^6.0.4"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12 || >= 14"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.1.0"
+                       }
+               },
+               "node_modules/postcss-modules-values": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+                       "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "icss-utils": "^5.0.0"
+                       },
+                       "engines": {
+                               "node": "^10 || ^12 || >= 14"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.1.0"
+                       }
+               },
+               "node_modules/postcss-normalize-charset": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz",
+                       "integrity": "sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-display-values": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz",
+                       "integrity": "sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-positions": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz",
+                       "integrity": "sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-repeat-style": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz",
+                       "integrity": "sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-string": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz",
+                       "integrity": "sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-timing-functions": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz",
+                       "integrity": "sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-unicode": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz",
+                       "integrity": "sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-url": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz",
+                       "integrity": "sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-normalize-whitespace": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz",
+                       "integrity": "sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-ordered-values": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz",
+                       "integrity": "sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==",
+                       "dev": true,
+                       "dependencies": {
+                               "cssnano-utils": "^4.0.0",
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-reduce-initial": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz",
+                       "integrity": "sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "caniuse-api": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-reduce-transforms": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz",
+                       "integrity": "sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-selector-parser": {
+                       "version": "6.0.13",
+                       "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+                       "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "cssesc": "^3.0.0",
+                               "util-deprecate": "^1.0.2"
+                       },
+                       "engines": {
+                               "node": ">=4"
+                       }
+               },
+               "node_modules/postcss-svgo": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.0.tgz",
+                       "integrity": "sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-value-parser": "^4.2.0",
+                               "svgo": "^3.0.2"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >= 18"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-unique-selectors": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz",
+                       "integrity": "sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==",
+                       "dev": true,
+                       "dependencies": {
+                               "postcss-selector-parser": "^6.0.5"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/postcss-value-parser": {
+                       "version": "4.2.0",
+                       "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+                       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+                       "dev": true
+               },
+               "node_modules/pretty-error": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
+                       "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==",
+                       "dev": true,
+                       "dependencies": {
+                               "lodash": "^4.17.20",
+                               "renderkid": "^3.0.0"
+                       }
+               },
+               "node_modules/proc-log": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz",
+                       "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/progress": {
+                       "version": "2.0.3",
+                       "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+                       "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.4.0"
+                       }
+               },
+               "node_modules/promise-inflight": {
+                       "version": "1.0.1",
+                       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+                       "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
+                       "dev": true
+               },
+               "node_modules/promise-retry": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+                       "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+                       "dev": true,
+                       "dependencies": {
+                               "err-code": "^2.0.2",
+                               "retry": "^0.12.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/prompts-ncu": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-3.0.0.tgz",
+                       "integrity": "sha512-qyz9UxZ5MlPKWVhWrCmSZ1ahm2GVYdjLb8og2sg0IPth1KRuhcggHGuijz0e41dkx35p1t1q3GRISGH7QGALFA==",
+                       "dev": true,
+                       "dependencies": {
+                               "kleur": "^4.0.1",
+                               "sisteransi": "^1.0.5"
+                       },
+                       "engines": {
+                               "node": ">= 14"
+                       }
+               },
+               "node_modules/proto-list": {
+                       "version": "1.2.4",
+                       "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+                       "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+                       "dev": true
+               },
+               "node_modules/protocol-buffers-schema": {
+                       "version": "3.6.0",
+                       "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
+                       "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
+               },
+               "node_modules/punycode": {
+                       "version": "2.3.0",
+                       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+                       "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/pupa": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz",
+                       "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==",
+                       "dev": true,
+                       "dependencies": {
+                               "escape-goat": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=12.20"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/queue-microtask": {
+                       "version": "1.2.3",
+                       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+                       "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/feross"
+                               },
+                               {
+                                       "type": "patreon",
+                                       "url": "https://www.patreon.com/feross"
+                               },
+                               {
+                                       "type": "consulting",
+                                       "url": "https://feross.org/support"
+                               }
+                       ]
+               },
+               "node_modules/quick-lru": {
+                       "version": "6.1.2",
+                       "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz",
+                       "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==",
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/quickselect": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+                       "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+               },
+               "node_modules/randombytes": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+                       "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "safe-buffer": "^5.1.0"
+                       }
+               },
+               "node_modules/rbush": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
+                       "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
+                       "dependencies": {
+                               "quickselect": "^2.0.0"
+                       }
+               },
+               "node_modules/rc": {
+                       "version": "1.2.8",
+                       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+                       "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+                       "dev": true,
+                       "dependencies": {
+                               "deep-extend": "^0.6.0",
+                               "ini": "~1.3.0",
+                               "minimist": "^1.2.0",
+                               "strip-json-comments": "~2.0.1"
+                       },
+                       "bin": {
+                               "rc": "cli.js"
+                       }
+               },
+               "node_modules/rc-config-loader": {
+                       "version": "4.1.3",
+                       "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.3.tgz",
+                       "integrity": "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==",
+                       "dev": true,
+                       "dependencies": {
+                               "debug": "^4.3.4",
+                               "js-yaml": "^4.1.0",
+                               "json5": "^2.2.2",
+                               "require-from-string": "^2.0.2"
+                       }
+               },
+               "node_modules/rc/node_modules/ini": {
+                       "version": "1.3.8",
+                       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+                       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+                       "dev": true
+               },
+               "node_modules/rc/node_modules/strip-json-comments": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+                       "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/read-package-json": {
+                       "version": "6.0.4",
+                       "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz",
+                       "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==",
+                       "dev": true,
+                       "dependencies": {
+                               "glob": "^10.2.2",
+                               "json-parse-even-better-errors": "^3.0.0",
+                               "normalize-package-data": "^5.0.0",
+                               "npm-normalize-package-bin": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/read-package-json-fast": {
+                       "version": "3.0.2",
+                       "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz",
+                       "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==",
+                       "dev": true,
+                       "dependencies": {
+                               "json-parse-even-better-errors": "^3.0.0",
+                               "npm-normalize-package-bin": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/readable-stream": {
+                       "version": "3.6.2",
+                       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+                       "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+                       "dev": true,
+                       "dependencies": {
+                               "inherits": "^2.0.3",
+                               "string_decoder": "^1.1.1",
+                               "util-deprecate": "^1.0.1"
+                       },
+                       "engines": {
+                               "node": ">= 6"
+                       }
+               },
+               "node_modules/rechoir": {
+                       "version": "0.8.0",
+                       "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
+                       "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "resolve": "^1.20.0"
+                       },
+                       "engines": {
+                               "node": ">= 10.13.0"
+                       }
+               },
+               "node_modules/registry-auth-token": {
+                       "version": "5.0.2",
+                       "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz",
+                       "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@pnpm/npm-conf": "^2.1.0"
+                       },
+                       "engines": {
+                               "node": ">=14"
+                       }
+               },
+               "node_modules/registry-url": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz",
+                       "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "rc": "1.2.8"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/relateurl": {
+                       "version": "0.2.7",
+                       "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+                       "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 0.10"
+                       }
+               },
+               "node_modules/remote-git-tags": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz",
+                       "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/renderkid": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
+                       "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==",
+                       "dev": true,
+                       "dependencies": {
+                               "css-select": "^4.1.3",
+                               "dom-converter": "^0.2.0",
+                               "htmlparser2": "^6.1.0",
+                               "lodash": "^4.17.21",
+                               "strip-ansi": "^6.0.1"
+                       }
+               },
+               "node_modules/renderkid/node_modules/ansi-regex": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/renderkid/node_modules/strip-ansi": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/require-from-string": {
+                       "version": "2.0.2",
+                       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+                       "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/requizzle": {
+                       "version": "0.2.4",
+                       "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz",
+                       "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==",
+                       "dev": true,
+                       "dependencies": {
+                               "lodash": "^4.17.21"
+                       }
+               },
+               "node_modules/resolve": {
+                       "version": "1.22.6",
+                       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
+                       "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-core-module": "^2.13.0",
+                               "path-parse": "^1.0.7",
+                               "supports-preserve-symlinks-flag": "^1.0.0"
+                       },
+                       "bin": {
+                               "resolve": "bin/resolve"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/ljharb"
+                       }
+               },
+               "node_modules/resolve-alpn": {
+                       "version": "1.2.1",
+                       "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+                       "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+                       "dev": true
+               },
+               "node_modules/resolve-cwd": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+                       "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+                       "dev": true,
+                       "dependencies": {
+                               "resolve-from": "^5.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/resolve-from": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+                       "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/resolve-protobuf-schema": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+                       "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+                       "dependencies": {
+                               "protocol-buffers-schema": "^3.3.1"
+                       }
+               },
+               "node_modules/responselike": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
+                       "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==",
+                       "dev": true,
+                       "dependencies": {
+                               "lowercase-keys": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/retry": {
+                       "version": "0.12.0",
+                       "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+                       "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 4"
+                       }
+               },
+               "node_modules/reusify": {
+                       "version": "1.0.4",
+                       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+                       "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+                       "dev": true,
+                       "engines": {
+                               "iojs": ">=1.0.0",
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/rimraf": {
+                       "version": "5.0.5",
+                       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
+                       "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
+                       "dev": true,
+                       "dependencies": {
+                               "glob": "^10.3.7"
+                       },
+                       "bin": {
+                               "rimraf": "dist/esm/bin.mjs"
+                       },
+                       "engines": {
+                               "node": ">=14"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/run-parallel": {
+                       "version": "1.2.0",
+                       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+                       "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/feross"
+                               },
+                               {
+                                       "type": "patreon",
+                                       "url": "https://www.patreon.com/feross"
+                               },
+                               {
+                                       "type": "consulting",
+                                       "url": "https://feross.org/support"
+                               }
+                       ],
+                       "dependencies": {
+                               "queue-microtask": "^1.2.2"
+                       }
+               },
+               "node_modules/safe-buffer": {
+                       "version": "5.2.1",
+                       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                       "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/feross"
+                               },
+                               {
+                                       "type": "patreon",
+                                       "url": "https://www.patreon.com/feross"
+                               },
+                               {
+                                       "type": "consulting",
+                                       "url": "https://feross.org/support"
+                               }
+                       ]
+               },
+               "node_modules/safer-buffer": {
+                       "version": "2.1.2",
+                       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+                       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+                       "dev": true,
+                       "optional": true
+               },
+               "node_modules/schema-utils": {
+                       "version": "4.2.0",
+                       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+                       "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/json-schema": "^7.0.9",
+                               "ajv": "^8.9.0",
+                               "ajv-formats": "^2.1.1",
+                               "ajv-keywords": "^5.1.0"
+                       },
+                       "engines": {
+                               "node": ">= 12.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       }
+               },
+               "node_modules/semver": {
+                       "version": "7.5.4",
+                       "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                       "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                       "dev": true,
+                       "dependencies": {
+                               "lru-cache": "^6.0.0"
+                       },
+                       "bin": {
+                               "semver": "bin/semver.js"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/semver-diff": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz",
+                       "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==",
+                       "dev": true,
+                       "dependencies": {
+                               "semver": "^7.3.5"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/semver-utils": {
+                       "version": "1.1.4",
+                       "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz",
+                       "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==",
+                       "dev": true
+               },
+               "node_modules/semver/node_modules/lru-cache": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                       "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/serialize-javascript": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
+                       "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
+                       "dev": true,
+                       "dependencies": {
+                               "randombytes": "^2.1.0"
+                       }
+               },
+               "node_modules/set-blocking": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+                       "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+                       "dev": true
+               },
+               "node_modules/shallow-clone": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+                       "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+                       "dev": true,
+                       "dependencies": {
+                               "kind-of": "^6.0.2"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/shebang-command": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+                       "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+                       "dev": true,
+                       "dependencies": {
+                               "shebang-regex": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/shebang-regex": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+                       "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/signal-exit": {
+                       "version": "4.1.0",
+                       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                       "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/isaacs"
+                       }
+               },
+               "node_modules/sigstore": {
+                       "version": "1.9.0",
+                       "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz",
+                       "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==",
+                       "dev": true,
+                       "dependencies": {
+                               "@sigstore/bundle": "^1.1.0",
+                               "@sigstore/protobuf-specs": "^0.2.0",
+                               "@sigstore/sign": "^1.0.0",
+                               "@sigstore/tuf": "^1.0.3",
+                               "make-fetch-happen": "^11.0.1"
+                       },
+                       "bin": {
+                               "sigstore": "bin/sigstore.js"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/sisteransi": {
+                       "version": "1.0.5",
+                       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+                       "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+                       "dev": true
+               },
+               "node_modules/slash": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+                       "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/smart-buffer": {
+                       "version": "4.2.0",
+                       "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+                       "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 6.0.0",
+                               "npm": ">= 3.0.0"
+                       }
+               },
+               "node_modules/socks": {
+                       "version": "2.7.1",
+                       "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
+                       "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ip": "^2.0.0",
+                               "smart-buffer": "^4.2.0"
+                       },
+                       "engines": {
+                               "node": ">= 10.13.0",
+                               "npm": ">= 3.0.0"
+                       }
+               },
+               "node_modules/socks-proxy-agent": {
+                       "version": "7.0.0",
+                       "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
+                       "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
+                       "dev": true,
+                       "dependencies": {
+                               "agent-base": "^6.0.2",
+                               "debug": "^4.3.3",
+                               "socks": "^2.6.2"
+                       },
+                       "engines": {
+                               "node": ">= 10"
+                       }
+               },
+               "node_modules/source-map": {
+                       "version": "0.6.1",
+                       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/source-map-js": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+                       "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.10.0"
+                       }
+               },
+               "node_modules/source-map-support": {
+                       "version": "0.5.21",
+                       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+                       "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+                       "dev": true,
+                       "dependencies": {
+                               "buffer-from": "^1.0.0",
+                               "source-map": "^0.6.0"
+                       }
+               },
+               "node_modules/spawn-please": {
+                       "version": "2.0.2",
+                       "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.2.tgz",
+                       "integrity": "sha512-KM8coezO6ISQ89c1BzyWNtcn2V2kAVtwIXd3cN/V5a0xPYc1F/vydrRc01wsKFEQ/p+V1a4sw4z2yMITIXrgGw==",
+                       "dev": true,
+                       "dependencies": {
+                               "cross-spawn": "^7.0.3"
+                       },
+                       "engines": {
+                               "node": ">=14"
+                       }
+               },
+               "node_modules/spdx-correct": {
+                       "version": "3.2.0",
+                       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+                       "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+                       "dev": true,
+                       "dependencies": {
+                               "spdx-expression-parse": "^3.0.0",
+                               "spdx-license-ids": "^3.0.0"
+                       }
+               },
+               "node_modules/spdx-exceptions": {
+                       "version": "2.3.0",
+                       "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+                       "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+                       "dev": true
+               },
+               "node_modules/spdx-expression-parse": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+                       "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "spdx-exceptions": "^2.1.0",
+                               "spdx-license-ids": "^3.0.0"
+                       }
+               },
+               "node_modules/spdx-license-ids": {
+                       "version": "3.0.16",
+                       "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
+                       "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
+                       "dev": true
+               },
+               "node_modules/ssri": {
+                       "version": "10.0.5",
+                       "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                       "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^7.0.3"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/ssri/node_modules/minipass": {
+                       "version": "7.0.4",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                       "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=16 || 14 >=14.17"
+                       }
+               },
+               "node_modules/string_decoder": {
+                       "version": "1.3.0",
+                       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+                       "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+                       "dev": true,
+                       "dependencies": {
+                               "safe-buffer": "~5.2.0"
+                       }
+               },
+               "node_modules/string-width": {
+                       "version": "4.2.3",
+                       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                       "dev": true,
+                       "dependencies": {
+                               "emoji-regex": "^8.0.0",
+                               "is-fullwidth-code-point": "^3.0.0",
+                               "strip-ansi": "^6.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/string-width-cjs": {
+                       "name": "string-width",
+                       "version": "4.2.3",
+                       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                       "dev": true,
+                       "dependencies": {
+                               "emoji-regex": "^8.0.0",
+                               "is-fullwidth-code-point": "^3.0.0",
+                               "strip-ansi": "^6.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/string-width-cjs/node_modules/ansi-regex": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/string-width-cjs/node_modules/strip-ansi": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/string-width/node_modules/ansi-regex": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/string-width/node_modules/strip-ansi": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/strip-ansi": {
+                       "version": "7.1.0",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+                       "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^6.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+                       }
+               },
+               "node_modules/strip-ansi-cjs": {
+                       "name": "strip-ansi",
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/strip-json-comments": {
+                       "version": "3.1.1",
+                       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+                       "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/style-loader": {
+                       "version": "3.3.3",
+                       "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz",
+                       "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 12.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependencies": {
+                               "webpack": "^5.0.0"
+                       }
+               },
+               "node_modules/stylehacks": {
+                       "version": "6.0.0",
+                       "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.0.tgz",
+                       "integrity": "sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==",
+                       "dev": true,
+                       "dependencies": {
+                               "browserslist": "^4.21.4",
+                               "postcss-selector-parser": "^6.0.4"
+                       },
+                       "engines": {
+                               "node": "^14 || ^16 || >=18.0"
+                       },
+                       "peerDependencies": {
+                               "postcss": "^8.2.15"
+                       }
+               },
+               "node_modules/supports-color": {
+                       "version": "8.1.1",
+                       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+                       "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "has-flag": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/supports-color?sponsor=1"
+                       }
+               },
+               "node_modules/supports-preserve-symlinks-flag": {
+                       "version": "1.0.0",
+                       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+                       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 0.4"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/ljharb"
+                       }
+               },
+               "node_modules/svgo": {
+                       "version": "3.0.2",
+                       "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz",
+                       "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@trysound/sax": "0.2.0",
+                               "commander": "^7.2.0",
+                               "css-select": "^5.1.0",
+                               "css-tree": "^2.2.1",
+                               "csso": "^5.0.5",
+                               "picocolors": "^1.0.0"
+                       },
+                       "bin": {
+                               "svgo": "bin/svgo"
+                       },
+                       "engines": {
+                               "node": ">=14.0.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/svgo"
+                       }
+               },
+               "node_modules/svgo/node_modules/commander": {
+                       "version": "7.2.0",
+                       "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+                       "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">= 10"
+                       }
+               },
+               "node_modules/svgo/node_modules/css-select": {
+                       "version": "5.1.0",
+                       "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+                       "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+                       "dev": true,
+                       "dependencies": {
+                               "boolbase": "^1.0.0",
+                               "css-what": "^6.1.0",
+                               "domhandler": "^5.0.2",
+                               "domutils": "^3.0.1",
+                               "nth-check": "^2.0.1"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/fb55"
+                       }
+               },
+               "node_modules/svgo/node_modules/dom-serializer": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+                       "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+                       "dev": true,
+                       "dependencies": {
+                               "domelementtype": "^2.3.0",
+                               "domhandler": "^5.0.2",
+                               "entities": "^4.2.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+                       }
+               },
+               "node_modules/svgo/node_modules/domhandler": {
+                       "version": "5.0.3",
+                       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+                       "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+                       "dev": true,
+                       "dependencies": {
+                               "domelementtype": "^2.3.0"
+                       },
+                       "engines": {
+                               "node": ">= 4"
+                       },
+                       "funding": {
+                               "url": "https://github.com/fb55/domhandler?sponsor=1"
+                       }
+               },
+               "node_modules/svgo/node_modules/domutils": {
+                       "version": "3.1.0",
+                       "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+                       "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+                       "dev": true,
+                       "dependencies": {
+                               "dom-serializer": "^2.0.0",
+                               "domelementtype": "^2.3.0",
+                               "domhandler": "^5.0.3"
+                       },
+                       "funding": {
+                               "url": "https://github.com/fb55/domutils?sponsor=1"
+                       }
+               },
+               "node_modules/svgo/node_modules/entities": {
+                       "version": "4.5.0",
+                       "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+                       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=0.12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/fb55/entities?sponsor=1"
+                       }
+               },
+               "node_modules/tapable": {
+                       "version": "2.2.1",
+                       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+                       "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=6"
+                       }
+               },
+               "node_modules/tar": {
+                       "version": "6.2.0",
+                       "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
+                       "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "chownr": "^2.0.0",
+                               "fs-minipass": "^2.0.0",
+                               "minipass": "^5.0.0",
+                               "minizlib": "^2.1.1",
+                               "mkdirp": "^1.0.3",
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/tar/node_modules/fs-minipass": {
+                       "version": "2.1.0",
+                       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+                       "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+                       "dev": true,
+                       "dependencies": {
+                               "minipass": "^3.0.0"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": {
+                       "version": "3.3.6",
+                       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                       "dev": true,
+                       "dependencies": {
+                               "yallist": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/terser": {
+                       "version": "5.21.0",
+                       "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz",
+                       "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jridgewell/source-map": "^0.3.3",
+                               "acorn": "^8.8.2",
+                               "commander": "^2.20.0",
+                               "source-map-support": "~0.5.20"
+                       },
+                       "bin": {
+                               "terser": "bin/terser"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       }
+               },
+               "node_modules/terser-webpack-plugin": {
+                       "version": "5.3.9",
+                       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
+                       "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
+                       "dev": true,
+                       "dependencies": {
+                               "@jridgewell/trace-mapping": "^0.3.17",
+                               "jest-worker": "^27.4.5",
+                               "schema-utils": "^3.1.1",
+                               "serialize-javascript": "^6.0.1",
+                               "terser": "^5.16.8"
+                       },
+                       "engines": {
+                               "node": ">= 10.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependencies": {
+                               "webpack": "^5.1.0"
+                       },
+                       "peerDependenciesMeta": {
+                               "@swc/core": {
+                                       "optional": true
+                               },
+                               "esbuild": {
+                                       "optional": true
+                               },
+                               "uglify-js": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/terser-webpack-plugin/node_modules/ajv": {
+                       "version": "6.12.6",
+                       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+                       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+                       "dev": true,
+                       "dependencies": {
+                               "fast-deep-equal": "^3.1.1",
+                               "fast-json-stable-stringify": "^2.0.0",
+                               "json-schema-traverse": "^0.4.1",
+                               "uri-js": "^4.2.2"
+                       },
+                       "funding": {
+                               "type": "github",
+                               "url": "https://github.com/sponsors/epoberezkin"
+                       }
+               },
+               "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
+                       "version": "3.5.2",
+                       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+                       "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+                       "dev": true,
+                       "peerDependencies": {
+                               "ajv": "^6.9.1"
+                       }
+               },
+               "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
+                       "version": "27.5.1",
+                       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+                       "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/node": "*",
+                               "merge-stream": "^2.0.0",
+                               "supports-color": "^8.0.0"
+                       },
+                       "engines": {
+                               "node": ">= 10.13.0"
+                       }
+               },
+               "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
+                       "version": "0.4.1",
+                       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+                       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+                       "dev": true
+               },
+               "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
+                       "version": "3.3.0",
+                       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+                       "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/json-schema": "^7.0.8",
+                               "ajv": "^6.12.5",
+                               "ajv-keywords": "^3.5.2"
+                       },
+                       "engines": {
+                               "node": ">= 10.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       }
+               },
+               "node_modules/terser/node_modules/commander": {
+                       "version": "2.20.3",
+                       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+                       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+                       "dev": true
+               },
+               "node_modules/to-regex-range": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+                       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-number": "^7.0.0"
+                       },
+                       "engines": {
+                               "node": ">=8.0"
+                       }
+               },
+               "node_modules/tslib": {
+                       "version": "2.6.2",
+                       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+                       "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+                       "dev": true
+               },
+               "node_modules/tuf-js": {
+                       "version": "1.1.7",
+                       "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz",
+                       "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@tufjs/models": "1.0.4",
+                               "debug": "^4.3.4",
+                               "make-fetch-happen": "^11.1.1"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/type-fest": {
+                       "version": "2.19.0",
+                       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+                       "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12.20"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/typedarray-to-buffer": {
+                       "version": "3.1.5",
+                       "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+                       "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "is-typedarray": "^1.0.0"
+                       }
+               },
+               "node_modules/uc.micro": {
+                       "version": "1.0.6",
+                       "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+                       "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+                       "dev": true
+               },
+               "node_modules/underscore": {
+                       "version": "1.13.6",
+                       "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
+                       "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
+                       "dev": true
+               },
+               "node_modules/unique-filename": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                       "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
+                       "dev": true,
+                       "dependencies": {
+                               "unique-slug": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/unique-slug": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                       "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "imurmurhash": "^0.1.4"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/unique-string": {
+                       "version": "3.0.0",
+                       "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz",
+                       "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "crypto-random-string": "^4.0.0"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/untildify": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+                       "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/update-browserslist-db": {
+                       "version": "1.0.13",
+                       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+                       "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+                       "dev": true,
+                       "funding": [
+                               {
+                                       "type": "opencollective",
+                                       "url": "https://opencollective.com/browserslist"
+                               },
+                               {
+                                       "type": "tidelift",
+                                       "url": "https://tidelift.com/funding/github/npm/browserslist"
+                               },
+                               {
+                                       "type": "github",
+                                       "url": "https://github.com/sponsors/ai"
+                               }
+                       ],
+                       "dependencies": {
+                               "escalade": "^3.1.1",
+                               "picocolors": "^1.0.0"
+                       },
+                       "bin": {
+                               "update-browserslist-db": "cli.js"
+                       },
+                       "peerDependencies": {
+                               "browserslist": ">= 4.21.0"
+                       }
+               },
+               "node_modules/update-notifier": {
+                       "version": "6.0.2",
+                       "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz",
+                       "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==",
+                       "dev": true,
+                       "dependencies": {
+                               "boxen": "^7.0.0",
+                               "chalk": "^5.0.1",
+                               "configstore": "^6.0.0",
+                               "has-yarn": "^3.0.0",
+                               "import-lazy": "^4.0.0",
+                               "is-ci": "^3.0.1",
+                               "is-installed-globally": "^0.4.0",
+                               "is-npm": "^6.0.0",
+                               "is-yarn-global": "^0.4.0",
+                               "latest-version": "^7.0.0",
+                               "pupa": "^3.1.0",
+                               "semver": "^7.3.7",
+                               "semver-diff": "^4.0.0",
+                               "xdg-basedir": "^5.1.0"
+                       },
+                       "engines": {
+                               "node": ">=14.16"
+                       },
+                       "funding": {
+                               "url": "https://github.com/yeoman/update-notifier?sponsor=1"
+                       }
+               },
+               "node_modules/update-notifier/node_modules/chalk": {
+                       "version": "5.3.0",
+                       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+                       "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+                       "dev": true,
+                       "engines": {
+                               "node": "^12.17.0 || ^14.13 || >=16.0.0"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/chalk?sponsor=1"
+                       }
+               },
+               "node_modules/uri-js": {
+                       "version": "4.4.1",
+                       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+                       "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+                       "dev": true,
+                       "dependencies": {
+                               "punycode": "^2.1.0"
+                       }
+               },
+               "node_modules/util-deprecate": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+                       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+                       "dev": true
+               },
+               "node_modules/utila": {
+                       "version": "0.4.0",
+                       "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+                       "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
+                       "dev": true
+               },
+               "node_modules/validate-npm-package-license": {
+                       "version": "3.0.4",
+                       "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+                       "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+                       "dev": true,
+                       "dependencies": {
+                               "spdx-correct": "^3.0.0",
+                               "spdx-expression-parse": "^3.0.0"
+                       }
+               },
+               "node_modules/validate-npm-package-name": {
+                       "version": "5.0.0",
+                       "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz",
+                       "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "builtins": "^5.0.0"
+                       },
+                       "engines": {
+                               "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+                       }
+               },
+               "node_modules/watchpack": {
+                       "version": "2.4.0",
+                       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
+                       "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+                       "dev": true,
+                       "dependencies": {
+                               "glob-to-regexp": "^0.4.1",
+                               "graceful-fs": "^4.1.2"
+                       },
+                       "engines": {
+                               "node": ">=10.13.0"
+                       }
+               },
+               "node_modules/web-worker": {
+                       "version": "1.2.0",
+                       "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
+                       "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
+               },
+               "node_modules/webpack": {
+                       "version": "5.88.2",
+                       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
+                       "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/eslint-scope": "^3.7.3",
+                               "@types/estree": "^1.0.0",
+                               "@webassemblyjs/ast": "^1.11.5",
+                               "@webassemblyjs/wasm-edit": "^1.11.5",
+                               "@webassemblyjs/wasm-parser": "^1.11.5",
+                               "acorn": "^8.7.1",
+                               "acorn-import-assertions": "^1.9.0",
+                               "browserslist": "^4.14.5",
+                               "chrome-trace-event": "^1.0.2",
+                               "enhanced-resolve": "^5.15.0",
+                               "es-module-lexer": "^1.2.1",
+                               "eslint-scope": "5.1.1",
+                               "events": "^3.2.0",
+                               "glob-to-regexp": "^0.4.1",
+                               "graceful-fs": "^4.2.9",
+                               "json-parse-even-better-errors": "^2.3.1",
+                               "loader-runner": "^4.2.0",
+                               "mime-types": "^2.1.27",
+                               "neo-async": "^2.6.2",
+                               "schema-utils": "^3.2.0",
+                               "tapable": "^2.1.1",
+                               "terser-webpack-plugin": "^5.3.7",
+                               "watchpack": "^2.4.0",
+                               "webpack-sources": "^3.2.3"
+                       },
+                       "bin": {
+                               "webpack": "bin/webpack.js"
+                       },
+                       "engines": {
+                               "node": ">=10.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependenciesMeta": {
+                               "webpack-cli": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/webpack-cli": {
+                       "version": "5.1.4",
+                       "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
+                       "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@discoveryjs/json-ext": "^0.5.0",
+                               "@webpack-cli/configtest": "^2.1.1",
+                               "@webpack-cli/info": "^2.0.2",
+                               "@webpack-cli/serve": "^2.0.5",
+                               "colorette": "^2.0.14",
+                               "commander": "^10.0.1",
+                               "cross-spawn": "^7.0.3",
+                               "envinfo": "^7.7.3",
+                               "fastest-levenshtein": "^1.0.12",
+                               "import-local": "^3.0.2",
+                               "interpret": "^3.1.1",
+                               "rechoir": "^0.8.0",
+                               "webpack-merge": "^5.7.3"
+                       },
+                       "bin": {
+                               "webpack-cli": "bin/cli.js"
+                       },
+                       "engines": {
+                               "node": ">=14.15.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       },
+                       "peerDependencies": {
+                               "webpack": "5.x.x"
+                       },
+                       "peerDependenciesMeta": {
+                               "@webpack-cli/generators": {
+                                       "optional": true
+                               },
+                               "webpack-bundle-analyzer": {
+                                       "optional": true
+                               },
+                               "webpack-dev-server": {
+                                       "optional": true
+                               }
+                       }
+               },
+               "node_modules/webpack-cli/node_modules/commander": {
+                       "version": "10.0.1",
+                       "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+                       "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=14"
+                       }
+               },
+               "node_modules/webpack-merge": {
+                       "version": "5.9.0",
+                       "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz",
+                       "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==",
+                       "dev": true,
+                       "dependencies": {
+                               "clone-deep": "^4.0.1",
+                               "wildcard": "^2.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10.0.0"
+                       }
+               },
+               "node_modules/webpack-sources": {
+                       "version": "3.2.3",
+                       "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
+                       "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10.13.0"
+                       }
+               },
+               "node_modules/webpack/node_modules/ajv": {
+                       "version": "6.12.6",
+                       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+                       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+                       "dev": true,
+                       "dependencies": {
+                               "fast-deep-equal": "^3.1.1",
+                               "fast-json-stable-stringify": "^2.0.0",
+                               "json-schema-traverse": "^0.4.1",
+                               "uri-js": "^4.2.2"
+                       },
+                       "funding": {
+                               "type": "github",
+                               "url": "https://github.com/sponsors/epoberezkin"
+                       }
+               },
+               "node_modules/webpack/node_modules/ajv-keywords": {
+                       "version": "3.5.2",
+                       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+                       "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+                       "dev": true,
+                       "peerDependencies": {
+                               "ajv": "^6.9.1"
+                       }
+               },
+               "node_modules/webpack/node_modules/json-parse-even-better-errors": {
+                       "version": "2.3.1",
+                       "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+                       "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+                       "dev": true
+               },
+               "node_modules/webpack/node_modules/json-schema-traverse": {
+                       "version": "0.4.1",
+                       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+                       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+                       "dev": true
+               },
+               "node_modules/webpack/node_modules/schema-utils": {
+                       "version": "3.3.0",
+                       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+                       "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+                       "dev": true,
+                       "dependencies": {
+                               "@types/json-schema": "^7.0.8",
+                               "ajv": "^6.12.5",
+                               "ajv-keywords": "^3.5.2"
+                       },
+                       "engines": {
+                               "node": ">= 10.13.0"
+                       },
+                       "funding": {
+                               "type": "opencollective",
+                               "url": "https://opencollective.com/webpack"
+                       }
+               },
+               "node_modules/which": {
+                       "version": "2.0.2",
+                       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+                       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+                       "dev": true,
+                       "dependencies": {
+                               "isexe": "^2.0.0"
+                       },
+                       "bin": {
+                               "node-which": "bin/node-which"
+                       },
+                       "engines": {
+                               "node": ">= 8"
+                       }
+               },
+               "node_modules/wide-align": {
+                       "version": "1.1.5",
+                       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+                       "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+                       "dev": true,
+                       "dependencies": {
+                               "string-width": "^1.0.2 || 2 || 3 || 4"
+                       }
+               },
+               "node_modules/widest-line": {
+                       "version": "4.0.1",
+                       "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
+                       "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
+                       "dev": true,
+                       "dependencies": {
+                               "string-width": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/widest-line/node_modules/emoji-regex": {
+                       "version": "9.2.2",
+                       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+                       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+                       "dev": true
+               },
+               "node_modules/widest-line/node_modules/string-width": {
+                       "version": "5.1.2",
+                       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+                       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+                       "dev": true,
+                       "dependencies": {
+                               "eastasianwidth": "^0.2.0",
+                               "emoji-regex": "^9.2.2",
+                               "strip-ansi": "^7.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/wildcard": {
+                       "version": "2.0.1",
+                       "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
+                       "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
+                       "dev": true
+               },
+               "node_modules/wrap-ansi": {
+                       "version": "8.1.0",
+                       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+                       "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-styles": "^6.1.0",
+                               "string-width": "^5.0.1",
+                               "strip-ansi": "^7.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+                       }
+               },
+               "node_modules/wrap-ansi-cjs": {
+                       "name": "wrap-ansi",
+                       "version": "7.0.0",
+                       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-styles": "^4.0.0",
+                               "string-width": "^4.1.0",
+                               "strip-ansi": "^6.0.0"
+                       },
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+                       }
+               },
+               "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+                       "version": "5.0.1",
+                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+                       "version": "6.0.1",
+                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                       "dev": true,
+                       "dependencies": {
+                               "ansi-regex": "^5.0.1"
+                       },
+                       "engines": {
+                               "node": ">=8"
+                       }
+               },
+               "node_modules/wrap-ansi/node_modules/ansi-styles": {
+                       "version": "6.2.1",
+                       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+                       "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+                       }
+               },
+               "node_modules/wrap-ansi/node_modules/emoji-regex": {
+                       "version": "9.2.2",
+                       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+                       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+                       "dev": true
+               },
+               "node_modules/wrap-ansi/node_modules/string-width": {
+                       "version": "5.1.2",
+                       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+                       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+                       "dev": true,
+                       "dependencies": {
+                               "eastasianwidth": "^0.2.0",
+                               "emoji-regex": "^9.2.2",
+                               "strip-ansi": "^7.0.1"
+                       },
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/wrappy": {
+                       "version": "1.0.2",
+                       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+                       "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+                       "dev": true
+               },
+               "node_modules/write-file-atomic": {
+                       "version": "3.0.3",
+                       "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+                       "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+                       "dev": true,
+                       "dependencies": {
+                               "imurmurhash": "^0.1.4",
+                               "is-typedarray": "^1.0.0",
+                               "signal-exit": "^3.0.2",
+                               "typedarray-to-buffer": "^3.1.5"
+                       }
+               },
+               "node_modules/write-file-atomic/node_modules/signal-exit": {
+                       "version": "3.0.7",
+                       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+                       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+                       "dev": true
+               },
+               "node_modules/xdg-basedir": {
+                       "version": "5.1.0",
+                       "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz",
+                       "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=12"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               },
+               "node_modules/xml-utils": {
+                       "version": "1.7.0",
+                       "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.7.0.tgz",
+                       "integrity": "sha512-bWB489+RQQclC7A9OW8e5BzbT8Tu//jtAOvkYwewFr+Q9T9KDGvfzC1lp0pYPEQPEoPQLDkmxkepSC/2gIAZGw=="
+               },
+               "node_modules/xmlcreate": {
+                       "version": "2.0.4",
+                       "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz",
+                       "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==",
+                       "dev": true
+               },
+               "node_modules/yallist": {
+                       "version": "4.0.0",
+                       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+                       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+                       "dev": true
+               },
+               "node_modules/yocto-queue": {
+                       "version": "0.1.0",
+                       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+                       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+                       "dev": true,
+                       "engines": {
+                               "node": ">=10"
+                       },
+                       "funding": {
+                               "url": "https://github.com/sponsors/sindresorhus"
+                       }
+               }
+       }
+}
diff --git a/js/package.json b/js/package.json
new file mode 100644 (file)
index 0000000..4cef5e9
--- /dev/null
@@ -0,0 +1,31 @@
+{
+       "name": "org.argeo.app.js",
+       "version": "2.3.0.next",
+       "description": "",
+       "private": "true",
+       "scripts": {
+               "build": "webpack --config webpack.dev.js",
+               "build-prod": "webpack --config webpack.prod.js"
+       },
+       "keywords": [],
+       "author": "",
+       "license": "GPL",
+       "devDependencies": {
+               "css-loader": "^6.8.1",
+               "css-minimizer-webpack-plugin": "^5.0.1",
+               "html-webpack-plugin": "^5.5.3",
+               "jsdoc": "^4.0.2",
+               "mini-css-extract-plugin": "^2.7.6",
+               "npm-check-updates": "^16.13.2",
+               "style-loader": "^3.3.3",
+               "webpack": "^5.83.1",
+               "webpack-cli": "^5.1.1",
+               "webpack-merge": "^5.9.0"
+       },
+       "dependencies": {
+               "@nieuwlandgeo/sldreader": "0.3.x",
+               "chart.js": "4.x.x",
+               "chartjs-plugin-annotation": "^3.0.1",
+               "ol": "8.x.x"
+       }
+}
diff --git a/js/src/chart/BarChart.js b/js/src/chart/BarChart.js
new file mode 100644 (file)
index 0000000..d65b9cc
--- /dev/null
@@ -0,0 +1,27 @@
+import Chart from 'chart.js/auto';
+
+import ChartJsPart from './ChartJsPart.js';
+
+export default class BarChart extends ChartJsPart {
+       /** Constructor taking the mapName as an argument. */
+       constructor(chartName) {
+               super(chartName);
+               this.setChart(new Chart(this.getChartCanvas(), {
+                       type: 'bar',
+                       data: {
+                               datasets: []
+                       },
+                       options: {
+                               scales: {
+                                       y: {
+                                               beginAtZero: true
+                                       },
+                               },
+                               animation: false,
+                       }
+               }));
+
+       }
+
+
+}
diff --git a/js/src/chart/ChartJsPart.js b/js/src/chart/ChartJsPart.js
new file mode 100644 (file)
index 0000000..ac60ce3
--- /dev/null
@@ -0,0 +1,65 @@
+import ChartPart from './ChartPart.js';
+
+import { Chart } from 'chart.js';
+import annotationPlugin from 'chartjs-plugin-annotation';
+
+Chart.register(annotationPlugin);
+
+export default class ChartJsPart extends ChartPart {
+       #chart;
+
+       /** Constructor taking the mapName as an argument. */
+       constructor(chartName) {
+               super(chartName);
+       }
+
+       setChart(chart) {
+               this.#chart = chart;
+       }
+
+       getChart() {
+               return this.#chart;
+       }
+
+       //
+       // DATA
+       //
+       setLabels(labels) {
+               const chart = this.getChart();
+               chart.data.labels = labels;
+               this.update();
+       }
+
+       addDataset(label, data) {
+               const chart = this.getChart();
+               chart.data.datasets.push({
+                       label: label,
+                       data: data,
+                       borderWidth: 1
+               });
+               this.update();
+       }
+
+       setData(labels, label, data) {
+               this.clearDatasets();
+               this.setLabels(labels);
+               this.addDataset(label, data);
+       }
+
+       setDatasets(labels, datasets) {
+               const chart = this.getChart();
+               chart.data.datasets = datasets;
+               chart.data.labels = labels;
+               this.update();
+       }
+
+       clearDatasets() {
+               const chart = this.getChart();
+               chart.data.datasets = [];
+               this.update();
+       }
+
+       update() {
+               this.#chart.update();
+       }
+}
diff --git a/js/src/chart/ChartPart.js b/js/src/chart/ChartPart.js
new file mode 100644 (file)
index 0000000..1fe9221
--- /dev/null
@@ -0,0 +1,34 @@
+/** API to be used by Java.
+ *  @module MapPart
+ */
+
+/** Abstract base class for displaying a map. */
+export default class ChartPart {
+
+       /** The name of the chart, will also be the name of the variable */
+       #chartName;
+
+       constructor(chartName) {
+               this.#chartName = chartName;
+               this.createChartCanvas(this.#chartName);
+       }
+
+
+       //
+       // HTML
+       //
+       /** Create the div element where the chart will be displayed. */
+       createChartCanvas(id) {
+               const chartDiv = document.createElement('canvas');
+               chartDiv.id = id;
+               //chartDiv.style.cssText = 'width: 100%;';
+               chartDiv.style.cssText = 'width: 100%; height: 100vh;';
+               document.body.appendChild(chartDiv);
+       }
+
+       /** Get the div element where the chart is displayed. */
+       getChartCanvas() {
+               return document.getElementById(this.#chartName);
+       }
+
+}
\ No newline at end of file
diff --git a/js/src/chart/TestGraph.js b/js/src/chart/TestGraph.js
new file mode 100644 (file)
index 0000000..9cc67db
--- /dev/null
@@ -0,0 +1,27 @@
+import Chart from 'chart.js/auto';
+
+export default class TestGraph {
+
+       init() {
+               const ctx = document.getElementById('myChart');
+
+               new Chart(ctx, {
+                       type: 'bar',
+                       data: {
+                               labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
+                               datasets: [{
+                                       label: '# of Votes',
+                                       data: [12, 19, 3, 5, 2, 3],
+                                       borderWidth: 1
+                               }]
+                       },
+                       options: {
+                               scales: {
+                                       y: {
+                                               beginAtZero: true
+                                       }
+                               }
+                       }
+               });
+       }
+}
diff --git a/js/src/chart/export-package.js b/js/src/chart/export-package.js
new file mode 100644 (file)
index 0000000..5fbcc0a
--- /dev/null
@@ -0,0 +1,21 @@
+import BarChart from './BarChart.js';
+import TestGraph from './TestGraph.js';
+//import { rectY, binX } from "@observablehq/plot";
+
+// PSEUDO PACKAGE
+if (typeof globalThis.argeo === 'undefined')
+       globalThis.argeo = {};
+if (typeof globalThis.argeo.app === 'undefined')
+       globalThis.argeo.app = {};
+if (typeof globalThis.argeo.app.chart === 'undefined')
+       globalThis.argeo.app.chart = {};
+
+// PUBLIC CLASSES
+globalThis.argeo.app.chart.BarChart = BarChart;
+globalThis.argeo.app.chart.TestGraph = TestGraph;
+
+//const plot = rectY({ length: 10000 }, binX({ y: "count" }, { x: Math.random })).plot();
+//const div = document.querySelector("#myplot");
+//div.append(plot);
+
+"use strict";
diff --git a/js/src/chart/index.html b/js/src/chart/index.html
new file mode 100644 (file)
index 0000000..72f6094
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+       <meta charset="UTF-8">
+</head>
+
+<body>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/js/src/chart/index.js b/js/src/chart/index.js
new file mode 100644 (file)
index 0000000..6ff32b0
--- /dev/null
@@ -0,0 +1 @@
+import './export-package.js';
diff --git a/js/src/geo/BboxVectorSource.js b/js/src/geo/BboxVectorSource.js
new file mode 100644 (file)
index 0000000..e1051b0
--- /dev/null
@@ -0,0 +1,25 @@
+
+import VectorSource from 'ol/source/Vector.js';
+import { bbox } from 'ol/loadingstrategy';
+import { transformToEpsg4326LatLonExtent } from './OpenLayersUtils.js';
+
+export default class BboxVectorSource extends VectorSource {
+       constructor(options) {
+               super(BboxVectorSource.processOptions(options));
+       }
+
+       static processOptions(options) {
+               options.strategy = bbox;
+               options.url = function(extent, resolution, projection) {
+                       var bbox = transformToEpsg4326LatLonExtent(extent, projection);
+
+                       const baseUrl = options.baseUrl;
+                       // invert bbox order in order to have minLat,minLon,maxLat,maxLon as required by WFS 2.0.0
+                       const url = baseUrl + '&bbox=' + bbox.join(',') + ',EPSG:4326';
+                       return url;
+               }
+               return options;
+       }
+
+
+}
\ No newline at end of file
diff --git a/js/src/geo/MapPart.js b/js/src/geo/MapPart.js
new file mode 100644 (file)
index 0000000..b7fd086
--- /dev/null
@@ -0,0 +1,62 @@
+/** API to be used by Java.
+ *  @module MapPart
+ */
+
+/** Abstract base class for displaying a map. */
+export default class MapPart {
+
+       /** The name of the map, will also be the name of the variable */
+       #mapName;
+
+       constructor(mapName) {
+               this.#mapName = mapName;
+               this.createMapDiv(this.#mapName);
+       }
+
+       //
+       // ABSTRACT METHODS
+       //
+       /** Set the center of the map to the given coordinates. */
+       setCenter(lng, lat) {
+               throw new Error("Abstract method");
+       }
+
+       //
+       // EXTENSIONS
+       //
+       loadMapModule(url) {
+               var script = document.createElement("script");
+               script.src = url;
+               document.head.appendChild(script);
+               //              import(url)
+               //                      .then(module => { })
+               //                      .catch((error) => 'An error occurred while loading the component');
+       }
+
+       //
+       // ACCESSORS
+       //
+       getMapName() {
+               return this.#mapName;
+       }
+
+       //
+       // HTML
+       //
+       createMapDiv(id) {
+               var mapDiv = document.createElement('div');
+               mapDiv.id = id;
+               mapDiv.className = this.getMapDivCssClass();
+               mapDiv.style.cssText = 'width: 100%; height: 100vh;';
+               document.body.appendChild(mapDiv);
+       }
+
+       getMapDivCssClass() {
+               throw new Error("Abstract method");
+       }
+
+       newObject(js) {
+               const func = new Function(js);
+               return (func());
+       }
+}
diff --git a/js/src/geo/OpenLayersMapPart.js b/js/src/geo/OpenLayersMapPart.js
new file mode 100644 (file)
index 0000000..d033e30
--- /dev/null
@@ -0,0 +1,303 @@
+/** OpenLayers-based implementation. 
+ * @module OpenLayersMapPart
+ */
+
+import { fromLonLat, getPointResolution } from 'ol/proj.js';
+
+import TileLayer from 'ol/layer/Tile.js';
+
+import OSM from 'ol/source/OSM.js';
+import { isEmpty } from 'ol/extent';
+
+import Select from 'ol/interaction/Select.js';
+import Overlay from 'ol/Overlay.js';
+
+import Map from 'ol/Map.js';
+
+import { OverviewMap, ScaleLine, defaults as defaultControls } from 'ol/control.js';
+import { easeOut } from 'ol/easing';
+
+import * as SLDReader from '@nieuwlandgeo/sldreader';
+
+import MapPart from './MapPart.js';
+import { transformToOlLonLatExtent } from './OpenLayersUtils.js';
+
+/** OpenLayers implementation of MapPart. */
+export default class OpenLayersMapPart extends MapPart {
+       /** The OpenLayers Map. */
+       #map;
+
+       /** The overview map */
+       #overviewMap;
+
+       /** Styled layer descriptor */
+       #sld;
+
+       /** The select interaction */
+       select;
+
+       /** Externally added callback functions. */
+       callbacks = {};
+
+       /** Constructor taking the mapName as an argument. */
+       constructor(mapName) {
+               super(mapName);
+               this.#overviewMap = new OverviewMap({
+                       layers: [
+                               new TileLayer({
+                                       source: new OSM(),
+                               }),
+                       ],
+               });
+               this.select = new Select();
+               this.#map = new Map({
+                       controls: defaultControls({
+                               attribution: false,
+                               rotate: false,
+                       }).extend([this.#overviewMap, new ScaleLine({
+                               bar: false,
+                               steps: 2,
+                               text: false,
+                               minWidth: 150,
+                               maxWidth: 200,
+                       })]),
+                       layers: [
+                       ],
+                       //                                              view: new View({
+                       //                                                      projection: 'EPSG:4326',
+                       //                                                      center: [0, 0],
+                       //                                                      zoom: 2,
+                       //                                              }),
+                       target: this.getMapName(),
+               });
+               this.#map.addInteraction(this.select);
+               //this.#map.getView().set('projection', 'EPSG:4326', true);
+       }
+
+       /* GEOGRAPHICAL METHODS */
+
+       setCenter(lat, lon) {
+               this.#map.getView().setCenter(fromLonLat([lon, lat]));
+       }
+
+       fit(extent, options) {
+               var transformed = transformToOlLonLatExtent(extent, this.#map.getView().getProjection());
+               this.#map.getView().fit(transformed, options);
+       }
+
+       /** Accessors */
+       getMap() {
+               return this.#map;
+       }
+
+       getLayerByName(name) {
+               let layers = this.#map.getLayers();
+               for (let i = 0; i < layers.getLength(); i++) {
+                       let layer = layers.item(i);
+                       let n = layer.get('name');
+                       if (n !== undefined) {
+                               if (name === n)
+                                       return layer;
+                       }
+               }
+               return undefined;
+       }
+
+       /* CALLBACKS */
+       enableFeatureSingleClick() {
+               // we cannot use 'this' in the function provided to OpenLayers
+               let mapPart = this;
+               this.#map.on('singleclick', function(e) {
+                       let feature = null;
+                       // we chose the first one
+                       e.map.forEachFeatureAtPixel(e.pixel, function(f) {
+                               feature = f;
+                               return true;
+                       });
+                       if (feature !== null)
+                               mapPart.callbacks['onFeatureSingleClick'](feature.get('cr:path'));
+               });
+       }
+
+       enableFeatureSelected() {
+               // we cannot use 'this' in the function provided to OpenLayers
+               let mapPart = this;
+               var select = new Select();
+               this.#map.addInteraction(select);
+               select.on('select', function(e) {
+                       if (e.selected.length > 0) {
+                               let feature = e.selected[0];
+                               mapPart.callbacks['onFeatureSelected'](feature.get('cr:path'));
+                       }
+               });
+       }
+
+       enableFeaturePopup() {
+               // we cannot use 'this' in the function provided to OpenLayers
+               let mapPart = this;
+               /**
+                * Elements that make up the popup.
+                */
+               const container = document.getElementById('popup');
+               const content = document.getElementById('popup-content');
+               const closer = document.getElementById('popup-closer');
+
+               /**
+                * Create an overlay to anchor the popup to the map.
+                */
+               const overlay = new Overlay({
+                       element: container,
+                       autoPan: false,
+                       autoPanAnimation: {
+                               duration: 250,
+                       },
+               });
+               this.#map.addOverlay(overlay);
+
+               let selected = null;
+               this.#map.on('pointermove', function(e) {
+                       if (selected !== null) {
+                               selected.setStyle(undefined);
+                               selected = null;
+                       }
+
+                       e.map.forEachFeatureAtPixel(e.pixel, function(f) {
+                               selected = f;
+                               return true;
+                       });
+
+                       if (selected == null) {
+                               overlay.setPosition(undefined);
+                               return;
+                       }
+                       const coordinate = e.coordinate;
+                       const path = selected.get('cr:path');
+                       if (path === null)
+                               return true;
+                       const res = mapPart.callbacks['onFeaturePopup'](path);
+                       if (res != null) {
+                               content.innerHTML = res;
+                               overlay.setPosition(coordinate);
+                       } else {
+                               overlay.setPosition(undefined);
+                       }
+               });
+       }
+
+       selectFeatures(layerName, featureIds) {
+               // we cannot use 'this' in the function provided to OpenLayers
+               let mapPart = this;
+               this.select.getFeatures().clear();
+               const layer = this.getLayerByName(layerName);
+               const source = layer.getSource();
+               for (const featureId of featureIds) {
+                       let feature = source.getFeatureById(featureId);
+                       if (feature === null) {
+                               source.on('featuresloadend', function(e) {
+                                       feature = source.getFeatureById(featureId);
+                                       if (feature !== null)
+                                               mapPart.select.getFeatures().push(feature);
+                               });
+                       } else {
+                               this.select.getFeatures().push(feature);
+                       }
+               }
+       }
+
+       fitToLayer(layerName) {
+               // we cannot use 'this' in the function provided to OpenLayers
+               let mapPart = this;
+               const layer = this.getLayerByName(layerName);
+               const source = layer.getSource();
+               const extent = source.getExtent();
+               const options = {
+                       duration: 1000,
+                       padding: [20, 20, 20, 20],
+                       easing: easeOut,
+               };
+               if (!isEmpty(extent))
+                       this.#map.getView().fit(source.getExtent(), options);
+               source.on('featuresloadend', function(e) {
+                       mapPart.getMap().getView().fit(source.getExtent(), options);
+               });
+       }
+
+       //
+       // HTML
+       //
+       getMapDivCssClass() {
+               return 'map';
+       }
+
+
+       //
+       // STATIC FOR EXTENSION
+       //
+       static newStyle(args) {
+               return new Style(args);
+       }
+
+       static newIcon(args) {
+               return new Icon(args);
+       }
+
+       //
+       // SLD STYLING
+       //
+
+       setSld(xml) {
+               this.#sld = SLDReader.Reader(xml);
+       }
+
+       /** Get a FeatureTypeStyle (https://nieuwlandgeo.github.io/SLDReader/api.html#FeatureTypeStyle).  */
+       getFeatureTypeStyle(styledLayerName, styleName) {
+               const sldLayer = SLDReader.getLayer(this.#sld, styledLayerName);
+               const style = styleName === undefined ? SLDReader.getStyle(sldLayer) : SLDReader.getStyle(sldLayer, styleName);
+               // OpenLayers can only use one definition
+               const featureTypeStyle = style.featuretypestyles[0];
+               return featureTypeStyle;
+       }
+
+       applyStyle(layerName, styledLayerName, styleName) {
+               const layer = this.getLayerByName(layerName);
+               const featureTypeStyle = this.getFeatureTypeStyle(styledLayerName, styleName);
+               const viewProjection = this.#map.getView().getProjection();
+               const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
+                       // Use the convertResolution option to calculate a more accurate resolution.
+                       convertResolution: viewResolution => {
+                               const viewCenter = this.#map.getView().getCenter();
+                               return getPointResolution(viewProjection, viewResolution, viewCenter);
+                       },
+                       // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
+                       // to update the vector layer when an image finishes loading.
+                       // If you do not do this, the image will only be visible after next layer pan/zoom.
+                       imageLoadedCallback: () => {
+                               layer.changed();
+                       },
+               });
+               layer.setStyle(olStyleFunction);
+       }
+
+       #applySLD(vectorLayer, text) {
+               const sldObject = SLDReader.Reader(text);
+               const sldLayer = SLDReader.getLayer(sldObject);
+               const style = SLDReader.getStyle(sldLayer);
+               const featureTypeStyle = style.featuretypestyles[0];
+
+               const viewProjection = this.#map.getView().getProjection();
+               const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
+                       // Use the convertResolution option to calculate a more accurate resolution.
+                       convertResolution: viewResolution => {
+                               const viewCenter = this.#map.getView().getCenter();
+                               return getPointResolution(viewProjection, viewResolution, viewCenter);
+                       },
+                       // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
+                       // to update the vector layer when an image finishes loading.
+                       // If you do not do this, the image will only be visible after next layer pan/zoom.
+                       imageLoadedCallback: () => {
+                               vectorLayer.changed();
+                       },
+               });
+               vectorLayer.setStyle(olStyleFunction);
+       }
+}
diff --git a/js/src/geo/OpenLayersUtils.js b/js/src/geo/OpenLayersUtils.js
new file mode 100644 (file)
index 0000000..b85ad7d
--- /dev/null
@@ -0,0 +1,31 @@
+import { transformExtent } from 'ol/proj.js';
+
+
+export function transformToEpsg4326LatLonExtent(extent, projection) {
+       const proj = projection.getCode();
+       if (proj === 'EPSG:4326')
+               return toLatLonExtent(extent);
+       var transformed = transformExtent(extent, proj, 'EPSG:4326');
+       return toLatLonExtent(transformed);
+}
+
+/** From EPSG:4326 lat/lon to a proj lon/lat */
+export function transformToOlLonLatExtent(extent, projection) {
+       const proj = projection.getCode();
+       if (proj === 'EPSG:4326')
+               return toLonLatExtent(extent);
+       const reordered = toLonLatExtent(extent);
+       var transformed = transformExtent(reordered, 'EPSG:4326', proj);
+       return transformed;
+}
+
+/** Converts from an extent in OpenLayers order (lon/lat) to WFS 2.0 order (lat/lon). */
+export function toLatLonExtent(extent) {
+       return [extent[1], extent[0], extent[3], extent[2]];
+}
+
+/** Converts from an extent in WFS 2.0 order (lat/lon) to OpenLayers order (lon/lat) . */
+export function toLonLatExtent(extent) {
+       return [extent[1], extent[0], extent[3], extent[2]];
+}
+
diff --git a/js/src/geo/SentinelCloudless.js b/js/src/geo/SentinelCloudless.js
new file mode 100644 (file)
index 0000000..820a440
--- /dev/null
@@ -0,0 +1,54 @@
+
+import WMTS from 'ol/source/WMTS.js';
+import WMTSTileGrid from 'ol/tilegrid/WMTS';
+import { getTopLeft } from 'ol/extent';
+import { getWidth } from 'ol/extent';
+import { get as getProjection } from 'ol/proj';
+
+export default class SentinelCloudless extends WMTS {
+       static source_s2CL2019;
+       static EPSG4326 = getProjection('EPSG:4326');
+
+       static resolutions;
+       static matrixIds;
+
+       static {
+               let min_zoom = 6;
+               let max_zoom = 17;
+               let zoomOffset = 1;
+
+               // from https://s2maps.eu/
+               let size = getWidth(this.EPSG4326.getExtent()) / 512;
+               this.resolutions = new Array(max_zoom + zoomOffset);
+               this.matrixIds = new Array(max_zoom + zoomOffset);
+               for (let z = min_zoom; z <= max_zoom; ++z) {
+                       // generate resolutions and matrixIds arrays for this WMTS
+                       this.resolutions[z] = size / Math.pow(2, z);
+                       this.matrixIds[z] = z;
+               }
+       }
+       
+       constructor() {
+               super({
+                       urls: [
+                               "//a.s2maps-tiles.eu/wmts/",
+                               "//b.s2maps-tiles.eu/wmts/",
+                               "//c.s2maps-tiles.eu/wmts/",
+                               "//d.s2maps-tiles.eu/wmts/",
+                               "//e.s2maps-tiles.eu/wmts/"
+                       ],
+                       layer: 's2cloudless-2021',
+                       matrixSet: 'WGS84',
+                       format: 'image/jpeg',
+                       projection: SentinelCloudless.EPSG4326,
+                       tileGrid: new WMTSTileGrid({
+                               origin: getTopLeft(SentinelCloudless.EPSG4326.getExtent()),
+                               resolutions: SentinelCloudless.resolutions,
+                               matrixIds: SentinelCloudless.matrixIds,
+                       }),
+                       style: 'default',
+                       transition: 0,
+                       wrapX: true
+               });
+       }
+}
\ No newline at end of file
diff --git a/js/src/geo/export-package.js b/js/src/geo/export-package.js
new file mode 100644 (file)
index 0000000..50b9d04
--- /dev/null
@@ -0,0 +1,44 @@
+import OpenLayersMapPart from './OpenLayersMapPart.js';
+import BboxVectorSource from './BboxVectorSource.js';
+import SentinelCloudless from './SentinelCloudless.js';
+
+import Map from 'ol/Map.js';
+import View from 'ol/View.js';
+import OSM from 'ol/source/OSM.js';
+import TileLayer from 'ol/layer/Tile.js';
+import VectorSource from 'ol/source/Vector.js';
+import VectorLayer from 'ol/layer/Vector.js';
+import GeoJSON from 'ol/format/GeoJSON.js';
+import { Style, Icon } from 'ol/style.js';
+
+// PSEUDO PACKAGE
+if (typeof globalThis.argeo === 'undefined')
+       globalThis.argeo = {};
+if (typeof globalThis.argeo.app === 'undefined')
+       globalThis.argeo.app = {};
+if (typeof globalThis.argeo.app.geo === 'undefined')
+       globalThis.argeo.app.geo = {};
+
+// THIRD PARTY
+if (typeof globalThis.argeo.tp === 'undefined')
+       globalThis.argeo.tp = {};
+if (typeof globalThis.argeo.tp.ol === 'undefined')
+       globalThis.argeo.tp.ol = {};
+
+// PUBLIC CLASSES
+globalThis.argeo.app.geo.OpenLayersMapPart = OpenLayersMapPart;
+globalThis.argeo.app.geo.BboxVectorSource = BboxVectorSource;
+globalThis.argeo.app.geo.SentinelCloudless = SentinelCloudless;
+
+globalThis.argeo.tp.ol.Map = Map;
+globalThis.argeo.tp.ol.View = View;
+globalThis.argeo.tp.ol.TileLayer = TileLayer;
+globalThis.argeo.tp.ol.OSM = OSM;
+globalThis.argeo.tp.ol.VectorSource = VectorSource;
+globalThis.argeo.tp.ol.VectorLayer = VectorLayer;
+globalThis.argeo.tp.ol.GeoJSON = GeoJSON;
+globalThis.argeo.tp.ol.Style = Style;
+globalThis.argeo.tp.ol.Icon = Icon;
+
+"use strict";
+
diff --git a/js/src/geo/index.html b/js/src/geo/index.html
new file mode 100644 (file)
index 0000000..bfc2233
--- /dev/null
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+       <meta charset="UTF-8">
+       <style>
+               .ol-popup {
+                       position: absolute;
+                       background-color: white;
+                       box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+                       padding: 5px;
+                       border-radius: 10px;
+                       border: 1px solid #cccccc;
+                       bottom: 12px;
+                       left: -50px;
+                       min-width: 130px;
+               }
+
+               .ol-popup:after,
+               .ol-popup:before {
+                       top: 100%;
+                       border: solid transparent;
+                       content: " ";
+                       height: 0;
+                       width: 0;
+                       position: absolute;
+                       pointer-events: none;
+               }
+
+               .ol-popup:after {
+                       border-top-color: white;
+                       border-width: 10px;
+                       left: 48px;
+                       margin-left: -10px;
+               }
+
+               .ol-popup:before {
+                       border-top-color: #cccccc;
+                       border-width: 11px;
+                       left: 48px;
+                       margin-left: -11px;
+               }
+
+               .ol-popup-closer {
+                       text-decoration: none;
+                       position: absolute;
+                       top: 2px;
+                       right: 8px;
+               }
+
+               .ol-popup-closer:after {
+                       content: "✖";
+               }
+
+               .map .ol-scale-line {
+                       bottom: 0px;
+                       left: 50%;
+                       margin-right: -50%;
+                       transform: translate(-50%, -50%);
+               }
+
+               .map .ol-scale-bar {
+                       bottom: 0px;
+                       left: 50%;
+                       margin-right: -50%;
+                       transform: translate(-50%, -50%);
+               }
+
+               #popup-content {
+                       font: 16px sans-serif;
+               }
+       </style>
+</head>
+
+<body>
+       <!-- Popup -->
+       <div id="popup" class="ol-popup">
+               <div id="popup-content"></div>
+       </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/js/src/geo/index.js b/js/src/geo/index.js
new file mode 100644 (file)
index 0000000..f389db0
--- /dev/null
@@ -0,0 +1,4 @@
+import './export-package.js';
+
+// webpack specific
+import 'ol/ol.css';
diff --git a/js/webpack.common.js b/js/webpack.common.js
new file mode 100644 (file)
index 0000000..ba57965
--- /dev/null
@@ -0,0 +1,61 @@
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+//const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const path = require('path');
+
+module.exports = {
+       entry: {
+               "geo": './src/geo/index.js',
+               "chart": './src/chart/index.js',
+       },
+       output: {
+               filename: '[name].[contenthash].js',
+               path: path.resolve(__dirname, 'org.argeo.app.js/org/argeo/app/js'),
+               publicPath: '/pkg/org.argeo.app.js',
+               clean: true,
+       },
+       optimization: {
+               moduleIds: 'deterministic',
+               runtimeChunk: 'single',
+               // split code
+               splitChunks: {
+                       chunks: 'all',
+               },
+               //              minimizer: [
+               //                      // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
+               //                      `...`,
+               //                      new CssMinimizerPlugin(),
+               //              ],
+       },
+       module: {
+               rules: [
+                       {
+                               test: /\.(css)$/,
+                               use: [
+                                       MiniCssExtractPlugin.loader,
+                                       'css-loader',
+                               ],
+                       },
+               ],
+       },
+       plugins: [
+               // deal with CSS
+               new MiniCssExtractPlugin(),
+               // deal with HTML generation
+               new HtmlWebpackPlugin({
+                       title: 'Argeo Suite Geo JS',
+                       template: 'src/geo/index.html',
+                       scriptLoading: 'module',
+                       filename: 'geo.html',
+                       chunks: ['geo'],
+               }),
+               new HtmlWebpackPlugin({
+                       title: 'Argeo Suite Chart JS',
+                       template: 'src/chart/index.html',
+                       scriptLoading: 'module',
+                       filename: 'chart.html',
+                       chunks: ['chart'],
+               }),
+
+       ],
+};
\ No newline at end of file
diff --git a/js/webpack.dev.js b/js/webpack.dev.js
new file mode 100644 (file)
index 0000000..3d79473
--- /dev/null
@@ -0,0 +1,11 @@
+const { merge } = require('webpack-merge');
+const common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+       mode: 'development',
+       //devtool: 'source-map', // original code
+       devtool: 'eval-source-map',
+       devServer: {
+               static: './dist',
+       },
+});
\ No newline at end of file
diff --git a/js/webpack.prod.js b/js/webpack.prod.js
new file mode 100644 (file)
index 0000000..adb905a
--- /dev/null
@@ -0,0 +1,7 @@
+const { merge } = require('webpack-merge');
+const common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+       mode: 'production',
+       devtool: 'source-map', // original code
+});
\ No newline at end of file
index c7885be9a22649c1268d1be5d7a6dcc4a18157aa..f04a6d0b457b841bfe9f4f90fef0f8d96e6318c2 100644 (file)
@@ -3,6 +3,6 @@ package org.argeo.app.api;
 /** Constant related to entities, typically used in an OSGi context. */
 public interface EntityConstants {
        final static String TYPE = "entity.type";
-       final static String DEFAULT_EDITOR_ID = "entity.defaultEditorId";
+//     final static String DEFAULT_EDITOR_ID = "entity.defaultEditorId";
 
 }
index e1280cf03c77da17e6e7469301e96a2175c478f8..97f7d7c68a30e337d0f5d0e7f946958b57ae542a 100644 (file)
@@ -1,10 +1,8 @@
 package org.argeo.app.api;
 
-import javax.jcr.Node;
-
 /** The definition of an entity, a composite configurable data structure. */
 public interface EntityDefinition {
-       String getEditorId(Node entity);
+//     String getEditorId(Node entity);
 
        String getType();
 }
index 308f459076b16f900f32cad710d01638f37bb97f..43302945b8a64a65f4b5185b99995a5a79d3fd64 100644 (file)
@@ -2,8 +2,16 @@ package org.argeo.app.api;
 
 import org.argeo.api.acr.QNamed;
 
+/** Names used in the entity namespace http://www.argeo.org/ns/entity. */
 public enum EntityName implements QNamed {
-       type, //
+       type, relatedTo, //
+       // time,
+       date,
+       // geography
+       minLat, minLon, maxLat, maxLon,
+       // geo entities
+       place,
+       //
        ;
 
        @Override
index 5b0707e10a89293e7d4795251f0616b5d3bddbac..b6240b3126f136bb3f71e9550f4a0ba6cfa8cd3f 100644 (file)
@@ -18,16 +18,16 @@ public interface EntityNames {
        final String ADM = "adm";
 
        @Deprecated
-       final String ENTITY_TYPE = "entity:type";
+       final String ENTITY_TYPE = EntityName.type.get();
 
        // GENERIC CONCEPTS
 //     /** The language which is relevant. */
 //     final String XML_LANG = "xml:lang";
        /** The date which is relevant. */
        @Deprecated
-       final String ENTITY_DATE = "entity:date";
+       final String ENTITY_DATE = EntityName.date.get();
        @Deprecated
-       final String ENTITY_RELATED_TO = "entity:relatedTo";
+       final String ENTITY_RELATED_TO = EntityName.relatedTo.get();
 
        // DEFAULT FOLDER NAMES
        final String MEDIA = "media";
@@ -35,22 +35,22 @@ public interface EntityNames {
 
        // LDAP-LIKE ENTITIES
        @Deprecated
-       final String DISPLAY_NAME = LdapAttr.displayName.property();
+       final String DISPLAY_NAME = LdapAttr.displayName.get();
        // Persons
        @Deprecated
-       final String GIVEN_NAME = LdapAttr.givenName.property();
+       final String GIVEN_NAME = LdapAttr.givenName.get();
        @Deprecated
-       final String SURNAME = LdapAttr.sn.property();
+       final String SURNAME = LdapAttr.sn.get();
        @Deprecated
-       final String EMAIL = LdapAttr.mail.property();
+       final String EMAIL = LdapAttr.mail.get();
        @Deprecated
-       final String OU = LdapAttr.ou.property();
+       final String OU = LdapAttr.ou.get();
 
        // WGS84
        @Deprecated
        final String GEO_LAT = WGS84PosName.lat.get();
        @Deprecated
-       final String GEO_LONG = WGS84PosName.lng.get();
+       final String GEO_LONG = WGS84PosName.lon.get();
        @Deprecated
        final String GEO_ALT = WGS84PosName.alt.get();
 
index 1e308214aecc99327e141c05d9648e082b5a7974..8a258ebc38d4c60511bc10df3680b8cfd5102a53 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.app.api;
 
 import org.argeo.api.acr.QNamed;
 
-/** Types related to entities. */
+/** Types used in the entity namespace http://www.argeo.org/ns/entity. */
 public enum EntityType implements QNamed {
        // entity
        entity, local, relatedTo,
@@ -15,7 +15,7 @@ public enum EntityType implements QNamed {
        // graphics
        box,
        // geography
-       geopoint, bearing,
+       geopoint, bearing, geobounded,
        // ldap
        person, user;
 
index 49de2d8fe25453571e33856416c16e67d024c961..4ba1a1307f9d10fb3fb54eb12ed8ea760dc4c4f1 100644 (file)
@@ -8,7 +8,7 @@ import org.argeo.api.acr.QNamed;
  * @see https://www.w3.org/2003/01/geo/
  */
 public enum WGS84PosName implements QNamed {
-       lat, lng("long"), alt;
+       lat, lon("long"), alt;
 
        private final String localName;
 
index 396b6f203187a4b2f0220b915323b6d5de47707f..9da2af7a4a15d5333242f35f77e64b76d8c53a25 100644 (file)
@@ -115,3 +115,12 @@ mixin
 [entity:bearing]
 mixin
 - svg:direction (DOUBLE)
+
+[entity:geobounded]
+mixin
+- entity:minLat (DOUBLE)
+- entity:minLon (DOUBLE)
+- entity:maxLat (DOUBLE)
+- entity:maxLon (DOUBLE)
+- entity:minAlt (DOUBLE)
+- entity:maxAlt (DOUBLE)
diff --git a/org.argeo.app.api/src/org/argeo/app/api/entity.xsd b/org.argeo.app.api/src/org/argeo/app/api/entity.xsd
new file mode 100644 (file)
index 0000000..a2fbfcd
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+       elementFormDefault="qualified" attributeFormDefault="unqualified"
+       targetNamespace="http://www.argeo.org/ns/entity"
+       xmlns:entity="http://www.argeo.org/ns/entity">
+
+       <xs:attribute name="date" type="xs:date" />
+
+       <xs:element name="local">
+               <xs:complexType>
+                       <xs:sequence>
+                               <xs:any minOccurs="0" maxOccurs="unbounded"
+                                       namespace="##local" processContents="lax" />
+                       </xs:sequence>
+                       <xs:anyAttribute namespace="##any"
+                               processContents="lax" />
+               </xs:complexType>
+       </xs:element>
+
+       <xs:element name="terms">
+               <xs:complexType>
+                       <xs:sequence minOccurs="0" maxOccurs="unbounded">
+                               <xs:element ref="entity:term"></xs:element>
+                       </xs:sequence>
+               </xs:complexType>
+       </xs:element>
+
+       <xs:element name="term">
+               <xs:complexType>
+                       <xs:sequence minOccurs="0" maxOccurs="unbounded">
+                               <xs:element ref="entity:term"></xs:element>
+                       </xs:sequence>
+                       <xs:anyAttribute namespace="##any"
+                               processContents="lax" />
+               </xs:complexType>
+       </xs:element>
+</xs:schema>
\ No newline at end of file
diff --git a/org.argeo.app.api/src/org/argeo/app/api/entityFeature.xsd b/org.argeo.app.api/src/org/argeo/app/api/entityFeature.xsd
new file mode 100644 (file)
index 0000000..805bd1a
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+       xmlns="http://apaf.on.netiket.eu/ns/apaf"
+       targetNamespace="http://apaf.on.netiket.eu/ns/apaf"
+       xmlns:entity="http://www.argeo.org/ns/entity" xmlns:dav="DAV:"
+       xmlns:gml="http://www.opengis.net/gml">
+<!--   <xs:import -->
+<!--           schemaLocation="entity.xsd" -->
+<!--           namespace="http://www.argeo.org/ns/entity"></xs:import> -->
+       <!-- <xs:import -->
+       <!-- schemaLocation="https://schemas.opengis.net/gml/3.2.1/gml.xsd" -->
+       <!-- namespace="http://www.opengis.net/gml/3.2"></xs:import> -->
+       <xs:import namespace="http://www.opengis.net/gml"
+               schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd" />
+
+       <xs:complexType name="entityFeatureType">
+               <xs:complexContent>
+                       <xs:extension base="gml:AbstractFeatureType">
+                               <xs:sequence>
+                                       <xs:element name="area" type="gml:PolygonPropertyType" />
+                                       <xs:element name="geopoint" type="gml:PointPropertyType" />
+                                       <xs:element name="path" type="xs:string" />
+                               </xs:sequence>
+                       </xs:extension>
+               </xs:complexContent>
+       </xs:complexType>
+
+       <xs:element name="entityFeature" type="entityFeatureType"
+               substitutionGroup="gml:_Feature" />
+
+       <!-- <xs:complexType name="TestFeatureCollectionType"> <xs:complexContent> 
+               <xs:extension base="gml:AbstractFeatureCollectionType" /> </xs:complexContent> 
+               </xs:complexType> <xs:element name="TestFeatureCollection" type="TestFeatureCollectionType" 
+               /> -->
+
+</xs:schema>
\ No newline at end of file
index c07e947376dfd6800c04940bfba22e25ea20da20..7671c880a7422d31e18cbf1ec691be0e741a339f 100644 (file)
@@ -5,9 +5,9 @@ OSGI-INF/suiteMaintenance.xml,\
 OSGI-INF/termsContentProvider.xml,\
 
 Import-Package:\
-tech.units.indriya.unit,\
+javax.measure.quantity,\
 org.osgi.service.useradmin,\
-com.fasterxml.jackson.core,\
+tech.units.indriya.unit,\
 org.argeo.cms.acr,\
 *
 
diff --git a/org.argeo.app.core/src/org/argeo/app/core/AbstractEntityDefinition.java b/org.argeo.app.core/src/org/argeo/app/core/AbstractEntityDefinition.java
new file mode 100644 (file)
index 0000000..7e10bf0
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.app.core;
+
+import org.argeo.app.api.EntityDefinition;
+
+public abstract class AbstractEntityDefinition implements EntityDefinition {
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+
+       }
+}
index 48c508b45c54c6697ad84569d7335578a5e30fec..6b5ab3c45bed02dd651c5acf76ca0871e86dd99e 100644 (file)
@@ -10,7 +10,7 @@ public enum SuiteContentNamespace implements ContentNamespace {
        //
        // ARGEO
        //
-       ENTITY("entity", "http://www.argeo.org/ns/entity", "entity.xsd", null),
+       ENTITY("entity", "http://www.argeo.org/ns/entity", "/org/argeo/app/api/entity.xsd", null),
        //
        ARGEO_DBK("argeodbk", "http://www.argeo.org/ns/argeodbk", null, null),
        //
@@ -62,7 +62,10 @@ public enum SuiteContentNamespace implements ContentNamespace {
                Objects.requireNonNull(namespace);
                this.namespace = namespace;
                if (resourceFileName != null) {
-                       resource = getClass().getResource(RESOURCE_BASE + resourceFileName);
+                       if (!resourceFileName.startsWith("/"))
+                               resource = getClass().getResource(RESOURCE_BASE + resourceFileName);
+                       else
+                               resource = getClass().getResource(resourceFileName);
                        Objects.requireNonNull(resource);
                }
                if (publicUrl != null)
diff --git a/org.argeo.app.core/src/org/argeo/app/core/schemas/entity.xsd b/org.argeo.app.core/src/org/argeo/app/core/schemas/entity.xsd
deleted file mode 100644 (file)
index a2fbfcd..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
-       elementFormDefault="qualified" attributeFormDefault="unqualified"
-       targetNamespace="http://www.argeo.org/ns/entity"
-       xmlns:entity="http://www.argeo.org/ns/entity">
-
-       <xs:attribute name="date" type="xs:date" />
-
-       <xs:element name="local">
-               <xs:complexType>
-                       <xs:sequence>
-                               <xs:any minOccurs="0" maxOccurs="unbounded"
-                                       namespace="##local" processContents="lax" />
-                       </xs:sequence>
-                       <xs:anyAttribute namespace="##any"
-                               processContents="lax" />
-               </xs:complexType>
-       </xs:element>
-
-       <xs:element name="terms">
-               <xs:complexType>
-                       <xs:sequence minOccurs="0" maxOccurs="unbounded">
-                               <xs:element ref="entity:term"></xs:element>
-                       </xs:sequence>
-               </xs:complexType>
-       </xs:element>
-
-       <xs:element name="term">
-               <xs:complexType>
-                       <xs:sequence minOccurs="0" maxOccurs="unbounded">
-                               <xs:element ref="entity:term"></xs:element>
-                       </xs:sequence>
-                       <xs:anyAttribute namespace="##any"
-                               processContents="lax" />
-               </xs:complexType>
-       </xs:element>
-</xs:schema>
\ No newline at end of file
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GeoJsonUtils.java b/org.argeo.app.core/src/org/argeo/app/geo/GeoJsonUtils.java
deleted file mode 100644 (file)
index 84610c8..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.app.geo;
-
-import java.util.Map;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-/** Geo data utilities. */
-public class GeoJsonUtils {
-
-       /** Add these properties to all features. */
-       public static void addProperties(JsonNode tree, Map<String, String> map) {
-               for (JsonNode feature : tree.get("features")) {
-                       ObjectNode properties = (ObjectNode) feature.get("properties");
-                       for (String key : map.keySet()) {
-                               properties.put(key, map.get(key));
-                       }
-               }
-       }
-
-       /** Singleton. */
-       private GeoJsonUtils() {
-       }
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GeoToSvg.java b/org.argeo.app.core/src/org/argeo/app/geo/GeoToSvg.java
deleted file mode 100644 (file)
index abb5b39..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.argeo.app.geo;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Converts a geographical feature to an SVG. */
-public class GeoToSvg {
-       public void convertGeoJsonToSvg(Path source, Path target) {
-               ObjectMapper objectMapper = new ObjectMapper();
-               try (InputStream in = Files.newInputStream(source);
-                               Writer out = Files.newBufferedWriter(target, StandardCharsets.UTF_8)) {
-                       JsonNode tree = objectMapper.readTree(in);
-                       JsonNode coord = tree.get("features").get(0).get("geometry").get("coordinates");
-                       double ratio = 100;
-                       double minX = Double.POSITIVE_INFINITY;
-                       double maxX = Double.NEGATIVE_INFINITY;
-                       double minY = Double.POSITIVE_INFINITY;
-                       double maxY = Double.NEGATIVE_INFINITY;
-                       List<String> shapes = new ArrayList<>();
-                       for (JsonNode shape : coord) {
-                               StringBuffer sb = new StringBuffer();
-                               sb.append("<polyline style=\"stroke-width:0.00000003;stroke:#000000;\" points=\"");
-                               for (JsonNode latlng : shape) {
-                                       double lat = latlng.get(0).asDouble();
-                                       double y = lat * ratio;
-                                       if (y < minY)
-                                               minY = y;
-                                       if (y > maxY)
-                                               maxY = y;
-                                       double lng = latlng.get(1).asDouble();
-                                       double x = lng * ratio;
-                                       if (x < minX)
-                                               minX = x;
-                                       if (x > maxX)
-                                               maxX = x;
-                                       sb.append(y + "," + x + " ");
-                               }
-                               sb.append("\">");
-                               sb.append("</polyline>\n");
-                               shapes.add(sb.toString());
-                       }
-
-                       double width = maxX - minX;
-                       double height = maxY - minY;
-                       out.write("<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
-                       out.write(" width=\"" + (int) (width * 1000) + "\"\n");
-                       out.write(" height=\"" + (int) (height * 1000) + "\"\n");
-                       out.write(" viewBox=\"" + minX + "," + minY + "," + width + "," + height + "\"\n");
-                       out.write(">\n");
-                       for (String shape : shapes) {
-                               out.write(shape);
-                               out.write("\n");
-                       }
-                       out.write("</svg>");
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot convert " + source + " to " + target, e);
-               }
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GeoToolsTest.java b/org.argeo.app.core/src/org/argeo/app/geo/GeoToolsTest.java
deleted file mode 100644 (file)
index a771196..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-package org.argeo.app.geo;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStreamReader;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.geotools.data.DataUtilities;
-import org.geotools.data.DefaultTransaction;
-import org.geotools.data.Transaction;
-import org.geotools.data.collection.ListFeatureCollection;
-import org.geotools.data.shapefile.ShapefileDataStore;
-import org.geotools.data.shapefile.ShapefileDataStoreFactory;
-import org.geotools.data.simple.SimpleFeatureCollection;
-import org.geotools.data.simple.SimpleFeatureSource;
-import org.geotools.data.simple.SimpleFeatureStore;
-import org.geotools.feature.simple.SimpleFeatureBuilder;
-import org.geotools.geometry.jts.JTSFactoryFinder;
-import org.geotools.swing.data.JFileDataStoreChooser;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.Point;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-
-public class GeoToolsTest {
-       public GeoToolsTest() {
-
-       }
-
-       public void init() {
-               try {
-                       main(null);
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public void destroy() {
-
-       }
-
-       public static void main(String[] args) throws Exception {
-               final SimpleFeatureType TYPE = DataUtilities.createType("Location", "the_geom:Point:srid=4326," + // <- the
-               // geometry
-               // attribute:
-               // Point
-               // type
-                               "name:String," + // <- a String attribute
-                               "number:Integer" // a number attribute
-               );
-               final SimpleFeatureType TYPE_HULL = DataUtilities.createType("Hull", "the_geom:MultiPolygon:srid=4326");
-               System.out.println("TYPE:" + TYPE);
-
-               /*
-                * A list to collect features as we create them.
-                */
-               List<SimpleFeature> features = new ArrayList<>();
-               List<Coordinate> coordinates = new ArrayList<>();
-
-               /*
-                * GeometryFactory will be used to create the geometry attribute of each
-                * feature, using a Point object for the location.
-                */
-               GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
-
-               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
-
-               try (BufferedReader reader = new BufferedReader(
-                               new InputStreamReader(GeoToolsTest.class.getResourceAsStream("/org/djapps/on/apaf/locations.csv")))) {
-                       /* First line of the data file is the header */
-                       String line = reader.readLine();
-                       System.out.println("Header: " + line);
-
-                       for (line = reader.readLine(); line != null; line = reader.readLine()) {
-                               if (line.trim().length() > 0) { // skip blank lines
-                                       String[] tokens = line.split("\\,");
-
-                                       double latitude = Double.parseDouble(tokens[0]);
-                                       double longitude = Double.parseDouble(tokens[1]);
-                                       String name = tokens[2].trim();
-                                       int number = Integer.parseInt(tokens[3].trim());
-
-                                       /* Longitude (= x coord) first ! */
-                                       Coordinate coordinate = new Coordinate(longitude, latitude);
-                                       coordinates.add(coordinate);
-                                       Point point = geometryFactory.createPoint(coordinate);
-
-                                       featureBuilder.add(point);
-                                       featureBuilder.add(name);
-                                       featureBuilder.add(number);
-                                       SimpleFeature feature = featureBuilder.buildFeature(null);
-                                       features.add(feature);
-                               }
-                       }
-               }
-
-               LineString lineString = geometryFactory
-                               .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
-               Geometry convexHull = lineString.convexHull();
-               System.out.println(convexHull.toText());
-               SimpleFeatureBuilder hullFeatureBuilder = new SimpleFeatureBuilder(TYPE_HULL);
-               hullFeatureBuilder.add(convexHull);
-               SimpleFeature hull = hullFeatureBuilder.buildFeature(null);
-
-               /*
-                * Get an output file name and create the new shapefile
-                */
-               File newFile = getNewShapeFile();
-
-               ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
-
-               Map<String, Serializable> params = new HashMap<>();
-               params.put("url", newFile.toURI().toURL());
-               params.put("create spatial index", Boolean.TRUE);
-
-               ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
-
-               /*
-                * TYPE is used as a template to describe the file contents
-                */
-               newDataStore.createSchema(TYPE_HULL);
-
-               /*
-                * Write the features to the shapefile
-                */
-               Transaction transaction = new DefaultTransaction("create");
-
-               String typeName = newDataStore.getTypeNames()[0];
-               SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
-               SimpleFeatureType SHAPE_TYPE = featureSource.getSchema();
-               /*
-                * The Shapefile format has a couple limitations: - "the_geom" is always first,
-                * and used for the geometry attribute name - "the_geom" must be of type Point,
-                * MultiPoint, MuiltiLineString, MultiPolygon - Attribute names are limited in
-                * length - Not all data types are supported (example Timestamp represented as
-                * Date)
-                *
-                * Each data store has different limitations so check the resulting
-                * SimpleFeatureType.
-                */
-               System.out.println("SHAPE:" + SHAPE_TYPE);
-
-               if (featureSource instanceof SimpleFeatureStore) {
-                       SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
-                       /*
-                        * SimpleFeatureStore has a method to add features from a
-                        * SimpleFeatureCollection object, so we use the ListFeatureCollection class to
-                        * wrap our list of features.
-                        */
-                       SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, Collections.singletonList(hull));
-                       featureStore.setTransaction(transaction);
-                       try {
-                               featureStore.addFeatures(collection);
-                               transaction.commit();
-                       } catch (Exception problem) {
-                               problem.printStackTrace();
-                               transaction.rollback();
-                       } finally {
-                               transaction.close();
-                       }
-               } else {
-                       System.out.println(typeName + " does not support read/write access");
-               }
-//             if (featureSource instanceof SimpleFeatureStore) {
-//                     SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
-//                     /*
-//                      * SimpleFeatureStore has a method to add features from a
-//                      * SimpleFeatureCollection object, so we use the ListFeatureCollection class to
-//                      * wrap our list of features.
-//                      */
-//                     SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
-//                     featureStore.setTransaction(transaction);
-//                     try {
-//                             featureStore.addFeatures(collection);
-//                             transaction.commit();
-//                     } catch (Exception problem) {
-//                             problem.printStackTrace();
-//                             transaction.rollback();
-//                     } finally {
-//                             transaction.close();
-//                     }
-//             } else {
-//                     System.out.println(typeName + " does not support read/write access");
-//             }
-       }
-
-       /**
-        * Prompt the user for the name and path to use for the output shapefile
-        *
-        * @param csvFile the input csv file used to create a default shapefile name
-        * @return name and path for the shapefile as a new File object
-        */
-       private static File getNewShapeFile() {
-//        String path = csvFile.getAbsolutePath();
-//        String newPath = path.substring(0, path.length() - 4) + ".shp";
-
-               JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
-               chooser.setDialogTitle("Save shapefile");
-//        chooser.setSelectedFile(new File(newPath));
-
-               int returnVal = chooser.showSaveDialog(null);
-
-               if (returnVal != JFileDataStoreChooser.APPROVE_OPTION) {
-                       // the user cancelled the dialog
-                       System.exit(0);
-               }
-
-               File newFile = chooser.getSelectedFile();
-
-               return newFile;
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GeoUtils.java b/org.argeo.app.core/src/org/argeo/app/geo/GeoUtils.java
deleted file mode 100644 (file)
index 1f8846d..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-package org.argeo.app.geo;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.io.Writer;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.measure.Quantity;
-import javax.measure.quantity.Area;
-
-import org.geotools.data.DefaultTransaction;
-import org.geotools.data.Transaction;
-import org.geotools.data.collection.ListFeatureCollection;
-import org.geotools.data.shapefile.ShapefileDataStore;
-import org.geotools.data.shapefile.ShapefileDataStoreFactory;
-import org.geotools.data.simple.SimpleFeatureCollection;
-import org.geotools.data.simple.SimpleFeatureIterator;
-import org.geotools.data.simple.SimpleFeatureSource;
-import org.geotools.data.simple.SimpleFeatureStore;
-import org.geotools.geometry.jts.JTS;
-import org.geotools.referencing.CRS;
-import org.geotools.referencing.crs.DefaultGeographicCRS;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-
-import si.uom.SI;
-import tech.units.indriya.quantity.Quantities;
-
-/** Utilities around geographical format, mostly wrapping GeoTools patterns. */
-public class GeoUtils {
-
-       /** In square meters. */
-       public static Quantity<Area> calcArea(SimpleFeature feature) {
-               try {
-                       Polygon p = (Polygon) feature.getDefaultGeometry();
-                       Point centroid = p.getCentroid();
-                       String code = "AUTO:42001," + centroid.getX() + "," + centroid.getY();
-                       CoordinateReferenceSystem auto = CRS.decode(code);
-
-                       MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
-
-                       Polygon projed = (Polygon) JTS.transform(p, transform);
-                       return Quantities.getQuantity(projed.getArea(), SI.SQUARE_METRE);
-               } catch (MismatchedDimensionException | FactoryException | TransformException e) {
-                       throw new IllegalStateException("Cannot claculate area of feature");
-               }
-       }
-
-       public static void exportToSvg(SimpleFeatureCollection features, Writer out, int width, int height) {
-               try {
-                       double minY = Double.POSITIVE_INFINITY;
-                       double maxY = Double.NEGATIVE_INFINITY;
-                       double minX = Double.POSITIVE_INFINITY;
-                       double maxX = Double.NEGATIVE_INFINITY;
-                       List<String> shapes = new ArrayList<>();
-                       for (SimpleFeatureIterator it = features.features(); it.hasNext();) {
-                               SimpleFeature feature = it.next();
-                               StringBuffer sb = new StringBuffer();
-                               sb.append("<polyline style=\"stroke-width:1;stroke:#000000;fill:none;\" points=\"");
-
-                               Polygon p = (Polygon) feature.getDefaultGeometry();
-                               Point centroid = p.getCentroid();
-                               String code = "AUTO:42001," + centroid.getX() + "," + centroid.getY();
-                               CoordinateReferenceSystem auto = CRS.decode(code);
-
-                               MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
-
-                               Polygon projed = (Polygon) JTS.transform(p, transform);
-
-                               for (Coordinate coord : projed.getCoordinates()) {
-                                       double x = coord.x;
-                                       if (x < minX)
-                                               minX = x;
-                                       if (x > maxX)
-                                               maxX = x;
-                                       double y = -coord.y;
-                                       if (y < minY)
-                                               minY = y;
-                                       if (y > maxY)
-                                               maxY = y;
-                                       sb.append(x + "," + y + " ");
-                               }
-                               sb.append("\">");
-                               sb.append("</polyline>\n");
-                               shapes.add(sb.toString());
-
-                       }
-                       double viewportHeight = maxY - minY;
-                       double viewportWidth = maxX - minX;
-                       out.write("<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
-                       out.write(" width=\"" + width + "\"\n");
-                       out.write(" height=\"" + height + "\"\n");
-                       out.write(" viewBox=\"" + minX + " " + minY + " " + viewportWidth + " " + viewportHeight + "\"\n");
-                       out.write(" preserveAspectRatio=\"xMidYMid meet\"\n");
-                       out.write(">\n");
-                       for (String shape : shapes) {
-                               out.write(shape);
-                               out.write("\n");
-                       }
-                       out.write("</svg>");
-               } catch (IOException | FactoryException | MismatchedDimensionException | TransformException e) {
-                       throw new RuntimeException("Cannot export to SVG", e);
-               }
-       }
-
-       /** Write a list of simple features to a shapefile. */
-       public static void saveFeaturesAsShapefile(SimpleFeatureType featureType, List<SimpleFeature> features,
-                       Path shpFile) {
-               try {
-                       ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
-
-                       Map<String, Serializable> params = new HashMap<>();
-                       params.put("url", shpFile.toUri().toURL());
-
-                       params.put("create spatial index", Boolean.TRUE);
-
-                       ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
-                       newDataStore.createSchema(featureType);
-
-                       String typeName = newDataStore.getTypeNames()[0];
-                       SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
-                       if (featureSource instanceof SimpleFeatureStore) {
-                               SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
-                               SimpleFeatureCollection collection = new ListFeatureCollection(featureType, features);
-
-                               try (Transaction transaction = new DefaultTransaction("create")) {
-                                       try {
-                                               featureStore.setTransaction(transaction);
-                                               featureStore.addFeatures(collection);
-                                               transaction.commit();
-                                       } catch (Exception problem) {
-                                               transaction.rollback();
-                                               throw new RuntimeException("Cannot write shapefile " + shpFile, problem);
-                                       }
-                               }
-                       } else {
-                               throw new IllegalArgumentException(typeName + " does not support read/write access");
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot write shapefile " + shpFile, e);
-               }
-       }
-
-       /** Singleton. */
-       private GeoUtils() {
-       }
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GmlAttr.java b/org.argeo.app.core/src/org/argeo/app/geo/GmlAttr.java
deleted file mode 100644 (file)
index 77b0885..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.app.geo;
-
-import org.argeo.api.acr.QNamed;
-
-public enum GmlAttr implements QNamed {
-       uom
-       //
-       ;
-
-       public final static String UOM_SQUARE_METERS = "m2";
-
-       @Override
-       public String getNamespace() {
-               return "http://www.opengis.net/gml/3.2";
-       }
-
-       @Override
-       public String getDefaultPrefix() {
-               return "gml";
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GmlType.java b/org.argeo.app.core/src/org/argeo/app/geo/GmlType.java
deleted file mode 100644 (file)
index 980a5e4..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.app.geo;
-
-import org.argeo.api.acr.QNamed;
-
-public enum GmlType implements QNamed {
-       measure
-       //
-       ;
-
-       @Override
-       public String getNamespace() {
-               return "http://www.opengis.net/gml/3.2";
-       }
-
-       @Override
-       public String getDefaultPrefix() {
-               return "gml";
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GpxUtils.java b/org.argeo.app.core/src/org/argeo/app/geo/GpxUtils.java
deleted file mode 100644 (file)
index 645b08b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-package org.argeo.app.geo;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringTokenizer;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.geotools.data.DataUtilities;
-import org.geotools.feature.SchemaException;
-import org.geotools.feature.simple.SimpleFeatureBuilder;
-import org.geotools.geometry.jts.JTSFactoryFinder;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/** Utilities around the GPX format. */
-public class GpxUtils {
-
-       public static SimpleFeature parseGpxToPolygon(InputStream in) throws IOException {
-               try {
-                       final SimpleFeatureType TYPE = DataUtilities.createType("Area", "the_geom:Polygon:srid=4326");
-                       SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
-
-                       GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
-                       List<Coordinate> coordinates = new ArrayList<>();
-                       SAXParserFactory factory = SAXParserFactory.newInstance();
-                       SAXParser saxParser = factory.newSAXParser();
-
-                       saxParser.parse(in, new DefaultHandler() {
-
-                               @Override
-                               public void startElement(String uri, String localName, String qName, Attributes attributes)
-                                               throws SAXException {
-                                       if ("trkpt".equals(qName)) {
-                                               Double latitude = Double.parseDouble(attributes.getValue("lat"));
-                                               Double longitude = Double.parseDouble(attributes.getValue("lon"));
-                                               Coordinate coordinate = new Coordinate(longitude, latitude);
-                                               coordinates.add(coordinate);
-                                       }
-                               }
-
-                       });
-                       // close the line string
-                       coordinates.add(coordinates.get(0));
-
-                       Polygon polygon = geometryFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
-                       featureBuilder.add(polygon);
-                       SimpleFeature area = featureBuilder.buildFeature(null);
-                       return area;
-               } catch (ParserConfigurationException | SAXException | SchemaException e) {
-                       throw new RuntimeException("Cannot convert GPX", e);
-               }
-       }
-
-       public static void writeGeoShapeAsGpx(String geoShape, OutputStream out) throws IOException {
-               Objects.requireNonNull(geoShape);
-               Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
-               writer.append("<gpx><trk><trkseg>");
-               StringTokenizer stSeg = new StringTokenizer(geoShape.trim(), ";");
-               while (stSeg.hasMoreTokens()) {
-                       StringTokenizer stPt = new StringTokenizer(stSeg.nextToken().trim(), " ");
-                       String lat = stPt.nextToken();
-                       String lng = stPt.nextToken();
-                       String alt = stPt.nextToken();
-                       // String precision = stPt.nextToken();
-                       writer.append("<trkpt");
-                       writer.append(" lat=\"").append(lat).append('\"');
-                       writer.append(" lon=\"").append(lng).append('\"');
-                       if (!alt.equals("0.0")) {
-                               writer.append('>');
-                               writer.append("<ele>").append(alt).append("</ele>");
-                               writer.append("</trkpt>");
-                       } else {
-                               writer.append("/>");
-                       }
-               }
-               writer.append("</trkseg></trk></gpx>");
-               writer.flush();
-       }
-
-       /** Singleton. */
-       private GpxUtils() {
-       }
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/geonames/GeonamesAdm.java b/org.argeo.app.core/src/org/argeo/app/geo/geonames/GeonamesAdm.java
deleted file mode 100644 (file)
index 45febd2..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-package org.argeo.app.geo.geonames;
-
-import java.time.LocalDate;
-import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-/** A Geonames administrative subdivision. */
-public class GeonamesAdm {
-       private final Long geonameId;
-       private final String countryCode;
-       private final String adminCode1;
-       private final String admLevel;
-       private final Integer level;
-       private final String name;
-       private final String asciiName;
-       private final List<String> alternateNames;
-       private final Double lat;
-       private final Double lng;
-       private final LocalDate lastUpdated;
-       private final ZoneId timeZone;
-
-       private final Long[] upperLevelIds = new Long[5];
-       private final List<GeonamesAdm> upperLevels = new ArrayList<>();
-
-       private List<String> row;
-
-       /** Initialise from a row in the main Geonames table. */
-       public GeonamesAdm(List<String> row) {
-               geonameId = Long.parseLong(row.get(0));
-               admLevel = row.get(7);
-               countryCode = row.get(8);
-               adminCode1 = row.get(10);
-               if (admLevel.startsWith("ADM")) {
-                       if (admLevel.endsWith("H"))
-                               level = Integer.parseInt(admLevel.substring(3, admLevel.length() - 1));
-                       else
-                               level = Integer.parseInt(admLevel.substring(3));
-               } else if (admLevel.equals("PCLI")) {
-                       level = 0;
-               } else {
-                       throw new IllegalArgumentException("Unsupported admin level " + admLevel);
-               }
-               name = row.get(1);
-               asciiName = row.get(2);
-               alternateNames = Arrays.asList(row.get(3).split(","));
-               lat = Double.parseDouble(row.get(4));
-               lng = Double.parseDouble(row.get(5));
-               lastUpdated = LocalDate.parse(row.get(18));
-               timeZone = ZoneId.of(row.get(17));
-               // upper levels
-               if (row.get(11) != null && !row.get(11).trim().equals(""))
-                       upperLevelIds[2] = Long.parseLong(row.get(11));
-               if (row.get(12) != null && !row.get(12).trim().equals(""))
-                       upperLevelIds[3] = Long.parseLong(row.get(12));
-               if (row.get(13) != null && !row.get(13).trim().equals(""))
-                       upperLevelIds[4] = Long.parseLong(row.get(13));
-               this.row = row;
-       }
-
-       public void mapUpperLevels(Map<Long, GeonamesAdm> index) {
-               for (int i = 0; i < level; i++) {
-                       Long geonameId = upperLevelIds[i];
-                       upperLevels.add(i, index.get(geonameId));
-               }
-       }
-
-       public Long getGeonameId() {
-               return geonameId;
-       }
-
-       public Integer getLevel() {
-               return level;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public String getName(Function<String, String> transform) {
-               if (transform != null)
-                       return transform.apply(name);
-               else
-                       return name;
-
-       }
-
-       public String getAsciiName() {
-               return asciiName;
-       }
-
-       public List<String> getAlternateNames() {
-               return alternateNames;
-       }
-
-       public Double getLat() {
-               return lat;
-       }
-
-       public Double getLng() {
-               return lng;
-       }
-
-       public String getCountryCode() {
-               return countryCode;
-       }
-
-       public String getAdmLevel() {
-               return admLevel;
-       }
-
-       public List<String> getRow() {
-               return row;
-       }
-
-       public LocalDate getLastUpdated() {
-               return lastUpdated;
-       }
-
-       public ZoneId getTimeZone() {
-               return timeZone;
-       }
-
-       public String getAdminCode1() {
-               return adminCode1;
-       }
-
-       public Long[] getUpperLevelIds() {
-               return upperLevelIds;
-       }
-
-       public List<GeonamesAdm> getUpperLevels() {
-               return upperLevels;
-       }
-
-       @Override
-       public int hashCode() {
-               return geonameId.intValue();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof GeonamesAdm))
-                       return false;
-               GeonamesAdm other = (GeonamesAdm) obj;
-               return geonameId.equals(other.geonameId);
-       }
-
-       @Override
-       public String toString() {
-               return name + " (ADM" + level + " " + geonameId + ")";
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/geonames/ImportGeonamesAdmin.java b/org.argeo.app.core/src/org/argeo/app/geo/geonames/ImportGeonamesAdmin.java
deleted file mode 100644 (file)
index be2b507..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.argeo.app.geo.geonames;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.cms.util.CsvParser;
-import org.argeo.cms.util.CsvWriter;
-
-/** Import GeoNames administrative division from the main table. */
-public class ImportGeonamesAdmin {
-       // private Log log = LogFactory.getLog(ImportGeonamesAdmin.class);
-       private Map<Long, GeonamesAdm> geonamesAdms = new HashMap<>();
-
-       /** Loads the data. */
-       public void parse(InputStream in) {
-               Map<String, Long> countryGeonameIds = new HashMap<>();
-               Map<String, Long> admin1GeonameIds = new HashMap<>();
-               CsvParser csvParser = new CsvParser() {
-
-                       @Override
-                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-                               if (!"A".equals(tokens.get(6)))
-                                       return;
-                               GeonamesAdm geonamesAdm = new GeonamesAdm(tokens);
-                               geonamesAdms.put(geonamesAdm.getGeonameId(), geonamesAdm);
-                               if (geonamesAdm.getAdmLevel().equals("PCLI"))
-                                       countryGeonameIds.put(geonamesAdm.getCountryCode(), geonamesAdm.getGeonameId());
-                               if (geonamesAdm.getAdmLevel().equals("ADM1"))
-                                       admin1GeonameIds.put(geonamesAdm.getAdminCode1(), geonamesAdm.getGeonameId());
-                       }
-               };
-               csvParser.setSeparator('\t');
-               csvParser.setNoHeader(true);
-               csvParser.parse(in, StandardCharsets.UTF_8);
-
-               // fill upper levels
-               for (GeonamesAdm adm : geonamesAdms.values()) {
-                       adm.getUpperLevelIds()[0] = countryGeonameIds.get(adm.getCountryCode());
-                       if (adm.getLevel() > 0)
-                               adm.getUpperLevelIds()[1] = admin1GeonameIds.get(adm.getAdminCode1());
-                       adm.mapUpperLevels(geonamesAdms);
-               }
-
-       }
-
-       public Map<Long, GeonamesAdm> getGeonamesAdms() {
-               return geonamesAdms;
-       }
-
-       /**
-        * Copies only the Geonames of feature class 'A' (administrative subdivisions).
-        */
-       public static void filterGeonamesAdm(InputStream in, OutputStream out) {
-               CsvWriter csvWriter = new CsvWriter(out, StandardCharsets.UTF_8);
-               csvWriter.setSeparator('\t');
-               CsvParser csvParser = new CsvParser() {
-
-                       @Override
-                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-                               if (tokens.size() < 7 || !"A".equals(tokens.get(6)))
-                                       return;
-                               csvWriter.writeLine(tokens);
-                       }
-               };
-               csvParser.setSeparator('\t');
-               csvParser.setNoHeader(true);
-               csvParser.parse(in, StandardCharsets.UTF_8);
-       }
-
-       public static void main(String[] args) throws IOException {
-//             String country = "allCountries";
-               String country = "CI";
-//             try (InputStream in = Files
-//                             .newInputStream(Paths.get(System.getProperty("user.home") + "/gis/data/geonames/" + country + ".txt"));
-//                             OutputStream out = Files.newOutputStream(
-//                                             Paths.get(System.getProperty("user.home") + "/gis/data/geonames/" + country + "-adm.txt"))) {
-//                     ImportGeonamesAdmin.filterGeonamesAdm(in, out);
-//             }
-               try (InputStream in = Files.newInputStream(
-                               Paths.get(System.getProperty("user.home") + "/gis/data/geonames/" + country + "-adm.txt"))) {
-                       ImportGeonamesAdmin importGeonamesAdmin = new ImportGeonamesAdmin();
-                       importGeonamesAdmin.parse(in);
-               }
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java b/org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java
new file mode 100644 (file)
index 0000000..3e13b68
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.app.ux.js;
+
+import java.util.Optional;
+
+public abstract class AbstractJsObject {
+       /**
+        * JavaScript expression returning a reference to the object. It can be either a
+        * variable or a function call. If it is not set the object is assumed to be a
+        * new.
+        */
+       private String reference;
+
+       private JsClient jsClient;
+
+       private Object[] jsConstructorArgs;
+
+//     public AbstractJsObject(JsClient jsClient, String reference) {
+//             Objects.requireNonNull(jsClient);
+//             Objects.requireNonNull(reference, "JS reference cannot be null");
+//             this.jsClient = jsClient;
+//             this.reference = reference;
+//     }
+
+       public AbstractJsObject(Object... args) {
+               if (args.length == 2 && args[0] instanceof JsClient jsClient) {
+                       this.jsClient = jsClient;
+                       this.reference = args[1].toString();
+               } else {
+                       this.jsConstructorArgs = args;
+               }
+       }
+
+       public abstract String getJsPackage();
+
+       public void create(JsClient jsClient, String varName) {
+               if (!isNew())
+                       throw new IllegalStateException("JS object " + getJsClassName() + " is not new");
+               if (isFunctionReference())
+                       throw new IllegalStateException(
+                                       "JS object " + getJsClassName() + " cannot be created since it is a function reference");
+               jsClient.execute(jsClient.getJsVarName(varName) + " = " + newJs() + ";");
+               reference = varName;
+               this.jsClient = jsClient;
+       }
+
+       public void delete() {
+               if (isNew())
+                       throw new IllegalStateException(
+                                       "JS object " + getJsClassName() + " cannot be deleted since it is anonymous");
+               if (isFunctionReference())
+                       throw new IllegalStateException(
+                                       "JS object " + getJsClassName() + " cannot be deleted since it is a function reference");
+               jsClient.execute(reference + " = undefined; delete " + reference + ";");
+       }
+
+       public boolean isNew() {
+               return reference == null;
+       }
+
+       public boolean isFunctionReference() {
+               return !isNew() && !getReference().endsWith(")");
+       }
+
+       public String getReference() {
+               return reference;
+       }
+
+       protected String getJsReference() {
+               return jsClient.getJsVarName(reference);
+       }
+
+       protected String newJs() {
+               StringBuilder sb = new StringBuilder();
+               sb.append("new ");
+               sb.append(getJsClassName());
+               sb.append("(");
+               sb.append(JsClient.toJsArgs(jsConstructorArgs));
+               sb.append(")");
+               return sb.toString();
+       }
+
+       public String getJsClassName() {
+               return getJsPackage() + "." + getClass().getSimpleName();
+       }
+
+       public Object callMethod(String methodName, Object... args) {
+               return jsClient.callMethod(getJsReference(), methodName + "(" + JsClient.toJsArgs(args) + ")");
+       }
+
+       public void executeMethod(String methodName, Object... args) {
+               jsClient.executeMethod(getJsReference(), methodName + "(" + JsClient.toJsArgs(args) + ")");
+       }
+
+       protected String getMethodName() {
+               StackWalker walker = StackWalker.getInstance();
+               Optional<String> methodName = walker.walk(frames -> {
+                       return frames.skip(1).findFirst().map(StackWalker.StackFrame::getMethodName);
+               });
+               return methodName.orElseThrow();
+       }
+
+       protected JsClient getJsClient() {
+               return jsClient;
+       }
+
+       protected Object[] getJsConstructorArgs() {
+               return jsConstructorArgs;
+       }
+
+       protected void setJsConstructorArgs(Object[] jsConstructorArgs) {
+               this.jsConstructorArgs = jsConstructorArgs;
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java b/org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java
new file mode 100644 (file)
index 0000000..2503cf0
--- /dev/null
@@ -0,0 +1,148 @@
+package org.argeo.app.ux.js;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Function;
+
+/**
+ * A remote JavaScript view (typically in a web browser) which is tightly
+ * integrated with a local UX part.
+ */
+public interface JsClient {
+
+       /*
+        * TO IMPLEMENT
+        */
+
+       /**
+        * Execute this JavaScript on the client side after making sure that the page
+        * has been loaded and the map object has been created.
+        * 
+        * @param js   the JavaScript code, possibly formatted according to
+        *             {@link String#format}, with {@link Locale#ROOT} as locale (for
+        *             stability of decimal separator, as expected by JavaScript.
+        * @param args the optional arguments of
+        *             {@link String#format(String, Object...)}
+        */
+       Object evaluate(String js, Object... args);
+
+       /**
+        * Executes this JavaScript without expecting a return value.
+        * 
+        * @param js   the JavaScript code, possibly formatted according to
+        *             {@link String#format}, with {@link Locale#ROOT} as locale (for
+        *             stability of decimal separator, as expected by JavaScript.
+        * @param args the optional arguments of
+        *             {@link String#format(String, Object...)}
+        */
+       void execute(String js, Object... args);
+
+       /** @return the globally usable function name. */
+       String createJsFunction(String name, Function<Object[], Object> toDo);
+
+       /** Get a global variable name. */
+       String getJsVarName(String name);
+
+       /**
+        * Completion stage when the client is ready (typically the page has loaded in
+        * the browser).
+        */
+       CompletionStage<Boolean> getReadyStage();
+
+       /*
+        * DEFAULTS
+        */
+
+       default Object callMethod(String jsObject, String methodCall, Object... args) {
+               return evaluate(jsObject + '.' + methodCall, args);
+       }
+
+       default void executeMethod(String jsObject, String methodCall, Object... args) {
+               execute(jsObject + '.' + methodCall, args);
+       }
+
+       default boolean isInstanceOf(String reference, String jsClass) {
+               return (Boolean) evaluate("return "+getJsVarName(reference) + " instanceof " + jsClass);
+       }
+
+       /*
+        * UTILITIES
+        */
+
+       static String toJsValue(Object o) {
+               if (o instanceof CharSequence)
+                       return '\'' + o.toString() + '\'';
+               else if (o instanceof Number)
+                       return o.toString();
+               else if (o instanceof Boolean)
+                       return o.toString();
+               else if (o instanceof Map map)
+                       return toJsMap(map);
+               else if (o instanceof Object[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof int[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof long[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof double[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof AbstractJsObject jsObject) {
+                       if (jsObject.isNew())
+                               return jsObject.newJs();
+                       else
+                               return jsObject.getJsReference();
+               } else if (o instanceof JsReference jsReference) {
+                       return jsReference.get();
+               } else
+                       return '\'' + o.toString() + '\'';
+       }
+
+       static String toJsArgs(Object... arr) {
+               StringJoiner sj = new StringJoiner(",");
+               for (Object o : arr) {
+                       sj.add(toJsValue(o));
+               }
+               return sj.toString();
+       }
+
+       static String toJsArray(Object... arr) {
+               StringJoiner sj = new StringJoiner(",", "[", "]");
+               for (Object o : arr) {
+                       sj.add(toJsValue(o));
+               }
+               return sj.toString();
+       }
+
+       static String toJsArray(String... arr) {
+               return toJsArray((Object[]) arr);
+       }
+
+       static String toJsArray(double... arr) {
+               return Arrays.toString(arr);
+       }
+
+       static String toJsArray(long... arr) {
+               return Arrays.toString(arr);
+       }
+
+       static String toJsArray(int... arr) {
+               return Arrays.toString(arr);
+       }
+
+       static String toJsMap(Map<?, ?> map) {
+               StringJoiner sj = new StringJoiner(",", "{", "}");
+               // TODO escape forbidden characters
+               for (Object key : map.keySet()) {
+                       sj.add("'" + key + "':" + toJsValue(map.get(key)));
+               }
+               return sj.toString();
+       }
+
+       static String escapeQuotes(String str) {
+               return str.replace("'", "\\'").replace("\"", "\\\"");
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/js/JsReference.java b/org.argeo.app.core/src/org/argeo/app/ux/js/JsReference.java
new file mode 100644 (file)
index 0000000..0cd8d81
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.app.ux.js;
+
+import java.util.function.Supplier;
+
+/** Plain JS reference, to be directly serialised. */
+public class JsReference implements Supplier<String> {
+       private final String reference;
+
+       public JsReference(String reference) {
+               this.reference = reference;
+       }
+
+       public String get() {
+               return reference;
+       }
+
+}
index 6fee2f96fefd53f7a36ca34264dc25a047de33cb..feea106b01b2bc4e29c1f600489868550df1fbe1 100644 (file)
@@ -4,9 +4,11 @@ import org.argeo.api.acr.Content;
 
 /** Called when a user has received a new form submission. */
 public interface FormSubmissionListener {
+       final static String XML_SUBMISSION_FILE = "xml_submission_file";
+       
        /**
         * Called after a form submission has been stored in the user area. The
         * submission will be deleted if any exception is thrown.
         */
-       void formSubmissionReceived(Content content);
+       void formSubmissionReceived(Content content, boolean isIncomplete);
 }
index c24dbaeed73e945167657c1989d0455608e56159..9d4dc51bc7183295ce7c703d38bb41f24ab9f275 100644 (file)
@@ -37,3 +37,4 @@
 - * (STRING)
 + xforms:label (jcrx:xmlvalue) = jcrx:xmlvalue
 + xforms:hint (jcrx:xmlvalue) = jcrx:xmlvalue *
++ xforms:setvalue (xforms:setvalue) = xforms:setvalue *
diff --git a/org.argeo.app.geo/.classpath b/org.argeo.app.geo/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.app.geo/.project b/org.argeo.app.geo/.project
new file mode 100644 (file)
index 0000000..a41594e
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.geo</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.app.geo/.settings/org.eclipse.core.resources.prefs b/org.argeo.app.geo/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..99f26c0
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.argeo.app.geo/.settings/org.eclipse.jdt.core.prefs b/org.argeo.app.geo/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..62ef348
--- /dev/null
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/org.argeo.app.geo/.settings/org.eclipse.pde.core.prefs b/org.argeo.app.geo/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..f29e940
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/org.argeo.app.geo/OSGI-INF/wfsHttpHandler.xml b/org.argeo.app.geo/OSGI-INF/wfsHttpHandler.xml
new file mode 100644 (file)
index 0000000..0347631
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true">
+   <implementation class="org.argeo.app.geo.http.WfsHttpHandler"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.HttpHandler"/>
+   </service>
+   <property name="context.path" type="String" value="/api/wfs/" />
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
+   <reference bind="addFeatureAdapter" cardinality="0..n" interface="org.argeo.app.api.geo.FeatureAdapter" name="FeatureAdapter" policy="dynamic" unbind="removeFeatureAdapter"/>
+</scr:component>
diff --git a/org.argeo.app.geo/bnd.bnd b/org.argeo.app.geo/bnd.bnd
new file mode 100644 (file)
index 0000000..37b6d31
--- /dev/null
@@ -0,0 +1,8 @@
+Import-Package:\
+tech.units.indriya.unit,\
+com.fasterxml.jackson.core,\
+org.geotools.xml.transform,\
+*
+
+Service-Component:\
+OSGI-INF/wfsHttpHandler.xml,\
diff --git a/org.argeo.app.geo/build.properties b/org.argeo.app.geo/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.app.geo/src/org/argeo/app/api/geo/FeatureAdapter.java b/org.argeo.app.geo/src/org/argeo/app/api/geo/FeatureAdapter.java
new file mode 100644 (file)
index 0000000..69689a9
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.app.api.geo;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.search.AndFilter;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.geo.acr.GeoEntityUtils;
+import org.locationtech.jts.geom.Geometry;
+
+import jakarta.json.stream.JsonGenerator;
+
+public interface FeatureAdapter {
+       default Geometry getDefaultGeometry(Content c, QName targetFeature) {
+               // TODO deal with more defaults
+               // TODO deal with target feature
+               if (c.hasContentClass(EntityType.geopoint)) {
+                       return getGeoPointGeometry(c);
+               }
+               return null;
+       }
+
+       void writeProperties(JsonGenerator g, Content content, QName targetFeature);
+
+       void addConstraintsForFeature(AndFilter filter, QName targetFeature);
+
+       static Geometry getGeoPointGeometry(Content c) {
+               if (c.hasContentClass(EntityType.geopoint)) {
+                       return GeoEntityUtils.toPoint(c);
+//                     double latitude = c.get(WGS84PosName.lat, Double.class).get();
+//                     double longitude = c.get(WGS84PosName.lon, Double.class).get();
+//
+//                     Coordinate coordinate = new Coordinate(longitude, latitude);
+//                     Point the_geom = JTS.GEOMETRY_FACTORY.createPoint(coordinate);
+//                     return the_geom;
+               }
+               return null;
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/api/geo/WfsKvp.java b/org.argeo.app.geo/src/org/argeo/app/api/geo/WfsKvp.java
new file mode 100644 (file)
index 0000000..166cdf0
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.app.api.geo;
+
+/** Keys used for WFS KVP (key-value pair) encoding. */
+public enum WfsKvp {
+       CQL_FILTER("cql_filter"), //
+       OUTPUT_FORMAT("outputFormat"), //
+       TYPE_NAMES("typeNames"), //
+       BBOX("bbox"), //
+       FORMAT_OPTIONS("format_options"), //
+       ;
+
+       public final static String FILENAME_ = "filename:";
+
+       private final String key;
+
+       private WfsKvp(String key) {
+               this.key = key;
+       }
+
+       public String getKey() {
+               return key;
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/CqlUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/CqlUtils.java
new file mode 100644 (file)
index 0000000..2186c54
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.app.geo;
+
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.search.AndFilter;
+import org.argeo.api.acr.search.BasicSearch;
+import org.argeo.api.acr.search.ContentFilter;
+import org.geotools.api.filter.And;
+import org.geotools.api.filter.Filter;
+import org.geotools.api.filter.PropertyIsEqualTo;
+import org.geotools.api.filter.expression.Literal;
+import org.geotools.api.filter.expression.PropertyName;
+import org.geotools.filter.text.cql2.CQL;
+import org.geotools.filter.text.cql2.CQLException;
+
+/** Utilities around the CQL query format. */
+public class CqlUtils {
+
+       public static void filter(BasicSearch search, String cql) {
+               try {
+                       filter(search, CQL.toFilter(cql));
+               } catch (CQLException e) {
+                       throw new IllegalArgumentException("Cannot parse CQL: " + cql, e);
+               }
+       }
+
+       public static void filter(BasicSearch search, Filter filter) {
+               search.where((where) -> {
+                       if (filter instanceof And and) {
+                               processAnd(where, and);
+                       } else if (filter instanceof PropertyIsEqualTo propertyIsEqualTo) {
+                               processIsEqualTo(where, propertyIsEqualTo);
+                       } else {
+                               throw new IllegalArgumentException("Unsupported filter " + filter.getClass());
+                       }
+               });
+       }
+
+       private static void processAnd(AndFilter contentFilter, And filter) {
+               for (Filter child : filter.getChildren()) {
+                       if (child instanceof PropertyIsEqualTo propertyIsEqualTo) {
+                               processIsEqualTo(contentFilter, propertyIsEqualTo);
+                       }
+               }
+
+       }
+
+       private static void processIsEqualTo(ContentFilter<?> contentFilter, PropertyIsEqualTo propertyIsEqualTo) {
+               // TODO properly deal with types etc.
+               // see GeoTools org.geotools.filter.text.commons.ExpressionToText
+               PropertyName propertyName = (PropertyName) propertyIsEqualTo.getExpression1();
+               Literal value = (Literal) propertyIsEqualTo.getExpression2();
+               // String escaped = literal.toString().replaceAll("'", "''");
+               contentFilter.eq(NamespaceUtils.parsePrefixedName(propertyName.toString()), value.toString());
+       }
+
+       /** singleton */
+       private CqlUtils() {
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoJson.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoJson.java
new file mode 100644 (file)
index 0000000..8c4c7b2
--- /dev/null
@@ -0,0 +1,172 @@
+package org.argeo.app.geo;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.stream.JsonGenerator;
+
+/**
+ * GeoJSon format.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc7946
+ */
+public class GeoJson {
+       public final static String POINT_TYPE = "Point";
+       public final static String MULTI_POINT_TYPE = "MultiPoint";
+       public final static String LINE_STRING_TYPE = "LineString";
+       public final static String POLYGON_TYPE = "Polygon";
+       public final static String GEOMETRY_COLLECTION_TYPE = "GeometryCollection";
+
+       public final static String TYPE = "type";
+       public final static String GEOMETRY = "geometry";
+       public final static String GEOMETRIES = "geometries";
+       public final static String COORDINATES = "coordinates";
+       public final static String BBOX = "bbox";
+       public final static String PROPERTIES = "properties";
+
+       /*
+        * WRITE
+        */
+       /** Writes a {@link Geometry} as GeoJSON. */
+       public static void writeGeometry(JsonGenerator g, Geometry geometry) {
+               if (geometry instanceof Point point) {
+                       g.write(TYPE, POINT_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       writeCoordinate(g, point.getCoordinate());
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof MultiPoint multiPoint) {
+                       g.write(TYPE, MULTI_POINT_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       writeCoordinates(g, multiPoint.getCoordinates());
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof LineString lineString) {
+                       g.write(TYPE, LINE_STRING_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       writeCoordinates(g, lineString.getCoordinates());
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof Polygon polygon) {
+                       g.write(TYPE, POLYGON_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       LinearRing exteriorRing = polygon.getExteriorRing();
+                       g.writeStartArray();
+                       writeCoordinates(g, exteriorRing.getCoordinates());
+                       g.writeEnd();
+                       for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
+                               LinearRing interiorRing = polygon.getInteriorRingN(i);
+                               // TODO verify that holes are clockwise
+                               g.writeStartArray();
+                               writeCoordinates(g, interiorRing.getCoordinates());
+                               g.writeEnd();
+                       }
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof GeometryCollection geometryCollection) {// must be last
+                       g.write(TYPE, GEOMETRY_COLLECTION_TYPE);
+                       g.writeStartArray(GEOMETRIES);
+                       for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
+                               g.writeStartObject();
+                               writeGeometry(g, geometryCollection.getGeometryN(i));
+                               g.writeEnd();// geometry object
+                       }
+                       g.writeEnd();// geometries array
+               } else {
+                       throw new IllegalArgumentException(geometry.getClass() + " is not supported.");
+               }
+       }
+
+       /** Writes a sequence of coordinates [[lat,lon],[lat,lon]] */
+       public static void writeCoordinates(JsonGenerator g, Coordinate[] coordinates) {
+               for (Coordinate coordinate : coordinates) {
+                       g.writeStartArray();
+                       writeCoordinate(g, coordinate);
+                       g.writeEnd();
+               }
+       }
+
+       /** Writes a pair of coordinates [lat,lon]. */
+       public static void writeCoordinate(JsonGenerator g, Coordinate coordinate) {
+               // !! longitude is first in GeoJSON
+               g.write(coordinate.getY());
+               g.write(coordinate.getX());
+               double z = coordinate.getZ();
+               if (!Double.isNaN(z)) {
+                       g.write(z);
+               }
+       }
+
+       /**
+        * Writes the {@link Envelope} of a {@link Geometry} as a bbox GeoJSON object.
+        */
+       public static void writeBBox(JsonGenerator g, Geometry geometry) {
+               g.writeStartArray(BBOX);
+               Envelope envelope = geometry.getEnvelopeInternal();
+               g.write(envelope.getMinX());
+               g.write(envelope.getMinY());
+               g.write(envelope.getMaxX());
+               g.write(envelope.getMaxY());
+               g.writeEnd();
+       }
+
+       /*
+        * READ
+        */
+       /** Reads a geometry from the geometry object of a GEoJSON feature. */
+       @SuppressWarnings("unchecked")
+       public static <T extends Geometry> T readGeometry(JsonObject geom, Class<T> clss) {
+               String type = geom.getString(TYPE);
+               JsonArray coordinates = geom.getJsonArray(COORDINATES);
+               Geometry res = switch (type) {
+               case POINT_TYPE: {
+                       Coordinate coord = readCoordinate(coordinates);
+                       yield JTS.GEOMETRY_FACTORY_WGS84.createPoint(coord);
+               }
+               case LINE_STRING_TYPE: {
+                       Coordinate[] coords = readCoordinates(coordinates);
+                       yield JTS.GEOMETRY_FACTORY_WGS84.createLineString(coords);
+               }
+               case POLYGON_TYPE: {
+                       assert coordinates.size() > 0;
+                       LinearRing exterior = JTS.GEOMETRY_FACTORY_WGS84
+                                       .createLinearRing(readCoordinates(coordinates.getJsonArray(0)));
+                       LinearRing[] holes = new LinearRing[coordinates.size() - 1];
+                       for (int i = 0; i < coordinates.size() - 1; i++) {
+                               holes[i] = JTS.GEOMETRY_FACTORY_WGS84
+                                               .createLinearRing(readCoordinates(coordinates.getJsonArray(i + 1)));
+                       }
+                       yield JTS.GEOMETRY_FACTORY_WGS84.createPolygon(exterior, holes);
+               }
+               default:
+                       throw new IllegalArgumentException("Unexpected value: " + type);
+               };
+//             res.normalize();
+               return (T) res;
+       }
+
+       /** Reads a coordinate pair [lon,lat]. */
+       public static Coordinate readCoordinate(JsonArray arr) {
+               assert arr.size() >= 2;
+               // !! longitude is first in GeoJSon
+               return new Coordinate(arr.getJsonNumber(1).doubleValue(), arr.getJsonNumber(0).doubleValue());
+       }
+
+       /** Reads a coordinate sequence [[lon,lat],[lon,lat]]. */
+       public static Coordinate[] readCoordinates(JsonArray arr) {
+               Coordinate[] coords = new Coordinate[arr.size()];
+               for (int i = 0; i < arr.size(); i++)
+                       coords[i] = readCoordinate(arr.getJsonArray(i));
+               return coords;
+       }
+
+       /** singleton */
+       private GeoJson() {
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoJsonUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoJsonUtils.java
new file mode 100644 (file)
index 0000000..84610c8
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.app.geo;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/** Geo data utilities. */
+public class GeoJsonUtils {
+
+       /** Add these properties to all features. */
+       public static void addProperties(JsonNode tree, Map<String, String> map) {
+               for (JsonNode feature : tree.get("features")) {
+                       ObjectNode properties = (ObjectNode) feature.get("properties");
+                       for (String key : map.keySet()) {
+                               properties.put(key, map.get(key));
+                       }
+               }
+       }
+
+       /** Singleton. */
+       private GeoJsonUtils() {
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoShapeUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoShapeUtils.java
new file mode 100644 (file)
index 0000000..d35492a
--- /dev/null
@@ -0,0 +1,100 @@
+package org.argeo.app.geo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+/** Utilities around ODK's GeoShape format */
+public class GeoShapeUtils {
+
+       @SuppressWarnings("unchecked")
+       public static <T> T geoShapeToGeometry(String geoShape, Class<T> clss) {
+               Objects.requireNonNull(geoShape);
+               GeometryFactory geometryFactory = JTS.GEOMETRY_FACTORY_WGS84;
+               List<Coordinate> coordinates = new ArrayList<>();
+               StringTokenizer stSeg = new StringTokenizer(geoShape.trim(), ";");
+               while (stSeg.hasMoreTokens()) {
+                       StringTokenizer stPt = new StringTokenizer(stSeg.nextToken().trim(), " ");
+                       String lat = stPt.nextToken();
+                       String lng = stPt.nextToken();
+                       String alt = stPt.nextToken();
+                       // String precision = stPt.nextToken();
+                       Coordinate coord;
+                       if (!alt.equals("0.0")) {
+                               coord = new Coordinate(Double.parseDouble(lat), Double.parseDouble(lng), Double.parseDouble(alt));
+                       } else {
+                               coord = new Coordinate(Double.parseDouble(lat), Double.parseDouble(lng));
+                       }
+                       coordinates.add(coord);
+               }
+               if (LineString.class.isAssignableFrom(clss)) {
+                       LineString lineString = geometryFactory
+                                       .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       return (T) lineString;
+               } else if (MultiPoint.class.isAssignableFrom(clss)) {
+                       MultiPoint multiPoint = geometryFactory
+                                       .createMultiPointFromCoords(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       return (T) multiPoint;
+               } else if (Polygon.class.isAssignableFrom(clss)) {
+                       Coordinate first = coordinates.get(0);
+                       Coordinate last = coordinates.get(coordinates.size() - 1);
+                       if (!(first.getX() == last.getX() && first.getY() == last.getY())) {
+                               // close the line string
+                               coordinates.add(first);
+                       }
+                       Polygon polygon = geometryFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       return (T) polygon;
+               } else {
+                       throw new IllegalArgumentException("Unsupported format " + clss);
+               }
+       }
+
+       /** Converts a {@link Geometry} with WGS84 coordinates to a GeoShape. */
+       public static String geometryToGeoShape(Geometry geometry) {
+               if (geometry instanceof Point point) {
+                       Coordinate coordinate = point.getCoordinate();
+                       StringBuilder sb = new StringBuilder();
+                       appendToGeoShape(sb, coordinate.getX(), coordinate.getY(), coordinate.getZ());
+                       return sb.toString();
+               } else if (geometry instanceof Polygon || geometry instanceof LineString) {
+                       StringBuilder sb = new StringBuilder();
+                       for (Coordinate coordinate : geometry.getCoordinates()) {
+                               appendToGeoShape(sb, coordinate.getX(), coordinate.getY(), coordinate.getZ());
+                               sb.append(';');
+                       }
+                       return sb.toString();
+               } else {
+                       throw new IllegalArgumentException("Unsupported geometry " + geometry.getClass());
+               }
+       }
+
+       public static String geoPointToGeoShape(double lon, double lat, double alt) {
+               StringBuilder sb = new StringBuilder();
+               appendToGeoShape(sb, lon, lat, alt);
+               return sb.toString();
+       }
+
+       private static void appendToGeoShape(StringBuilder sb, double lon, double lat, double alt) {
+               sb.append(lat).append(' ');
+               sb.append(lon).append(' ');
+               if (alt != Double.NaN)
+                       sb.append(alt).append(' ');
+               else
+                       sb.append("0 ");
+               sb.append('0');
+       }
+
+       /** singleton */
+       private GeoShapeUtils() {
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoToSvg.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoToSvg.java
new file mode 100644 (file)
index 0000000..abb5b39
--- /dev/null
@@ -0,0 +1,69 @@
+package org.argeo.app.geo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Converts a geographical feature to an SVG. */
+public class GeoToSvg {
+       public void convertGeoJsonToSvg(Path source, Path target) {
+               ObjectMapper objectMapper = new ObjectMapper();
+               try (InputStream in = Files.newInputStream(source);
+                               Writer out = Files.newBufferedWriter(target, StandardCharsets.UTF_8)) {
+                       JsonNode tree = objectMapper.readTree(in);
+                       JsonNode coord = tree.get("features").get(0).get("geometry").get("coordinates");
+                       double ratio = 100;
+                       double minX = Double.POSITIVE_INFINITY;
+                       double maxX = Double.NEGATIVE_INFINITY;
+                       double minY = Double.POSITIVE_INFINITY;
+                       double maxY = Double.NEGATIVE_INFINITY;
+                       List<String> shapes = new ArrayList<>();
+                       for (JsonNode shape : coord) {
+                               StringBuffer sb = new StringBuffer();
+                               sb.append("<polyline style=\"stroke-width:0.00000003;stroke:#000000;\" points=\"");
+                               for (JsonNode latlng : shape) {
+                                       double lat = latlng.get(0).asDouble();
+                                       double y = lat * ratio;
+                                       if (y < minY)
+                                               minY = y;
+                                       if (y > maxY)
+                                               maxY = y;
+                                       double lng = latlng.get(1).asDouble();
+                                       double x = lng * ratio;
+                                       if (x < minX)
+                                               minX = x;
+                                       if (x > maxX)
+                                               maxX = x;
+                                       sb.append(y + "," + x + " ");
+                               }
+                               sb.append("\">");
+                               sb.append("</polyline>\n");
+                               shapes.add(sb.toString());
+                       }
+
+                       double width = maxX - minX;
+                       double height = maxY - minY;
+                       out.write("<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
+                       out.write(" width=\"" + (int) (width * 1000) + "\"\n");
+                       out.write(" height=\"" + (int) (height * 1000) + "\"\n");
+                       out.write(" viewBox=\"" + minX + "," + minY + "," + width + "," + height + "\"\n");
+                       out.write(">\n");
+                       for (String shape : shapes) {
+                               out.write(shape);
+                               out.write("\n");
+                       }
+                       out.write("</svg>");
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot convert " + source + " to " + target, e);
+               }
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoTools.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoTools.java
new file mode 100644 (file)
index 0000000..7571da7
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.app.geo;
+
+import org.argeo.api.cms.CmsLog;
+import org.geotools.api.filter.FilterFactory;
+import org.geotools.api.style.StyleFactory;
+import org.geotools.factory.CommonFactoryFinder;
+
+/**
+ * Factories initialisation and workarounds for the GeoTools library. The idea
+ * is to code defensively around factory initialisation, API changes, and issues
+ * related to running in an OSGi environment. Rather see {@link GeoUtils} for
+ * functional static utilities.
+ */
+public class GeoTools {
+       private final static CmsLog log = CmsLog.getLog(GeoTools.class);
+
+       public final static StyleFactory STYLE_FACTORY;
+       public final static FilterFactory FILTER_FACTORY;
+
+       static {
+               try {
+                       STYLE_FACTORY = CommonFactoryFinder.getStyleFactory();
+                       FILTER_FACTORY = CommonFactoryFinder.getFilterFactory();
+               } catch (RuntimeException e) {
+                       log.error("Basic GeoTools initialisation failed, geographical utilities are probably not available", e);
+                       throw e;
+               }
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoToolsTest.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoToolsTest.java
new file mode 100644 (file)
index 0000000..e6f9c19
--- /dev/null
@@ -0,0 +1,221 @@
+package org.argeo.app.geo;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.geotools.api.data.SimpleFeatureSource;
+import org.geotools.api.data.SimpleFeatureStore;
+import org.geotools.api.data.Transaction;
+import org.geotools.api.feature.simple.SimpleFeature;
+import org.geotools.api.feature.simple.SimpleFeatureType;
+import org.geotools.data.DataUtilities;
+import org.geotools.data.DefaultTransaction;
+import org.geotools.data.collection.ListFeatureCollection;
+import org.geotools.data.shapefile.ShapefileDataStore;
+import org.geotools.data.shapefile.ShapefileDataStoreFactory;
+import org.geotools.data.simple.SimpleFeatureCollection;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.geotools.swing.data.JFileDataStoreChooser;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+
+public class GeoToolsTest {
+       public GeoToolsTest() {
+
+       }
+
+       public void init() {
+               try {
+                       main(null);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void destroy() {
+
+       }
+
+       public static void main(String[] args) throws Exception {
+               final SimpleFeatureType TYPE = DataUtilities.createType("Location", "the_geom:Point:srid=4326," + // <- the
+               // geometry
+               // attribute:
+               // Point
+               // type
+                               "name:String," + // <- a String attribute
+                               "number:Integer" // a number attribute
+               );
+               final SimpleFeatureType TYPE_HULL = DataUtilities.createType("Hull", "the_geom:MultiPolygon:srid=4326");
+               System.out.println("TYPE:" + TYPE);
+
+               /*
+                * A list to collect features as we create them.
+                */
+               List<SimpleFeature> features = new ArrayList<>();
+               List<Coordinate> coordinates = new ArrayList<>();
+
+               /*
+                * GeometryFactory will be used to create the geometry attribute of each
+                * feature, using a Point object for the location.
+                */
+               GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
+
+               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
+
+               try (BufferedReader reader = new BufferedReader(
+                               new InputStreamReader(GeoToolsTest.class.getResourceAsStream("/org/djapps/on/apaf/locations.csv")))) {
+                       /* First line of the data file is the header */
+                       String line = reader.readLine();
+                       System.out.println("Header: " + line);
+
+                       for (line = reader.readLine(); line != null; line = reader.readLine()) {
+                               if (line.trim().length() > 0) { // skip blank lines
+                                       String[] tokens = line.split("\\,");
+
+                                       double latitude = Double.parseDouble(tokens[0]);
+                                       double longitude = Double.parseDouble(tokens[1]);
+                                       String name = tokens[2].trim();
+                                       int number = Integer.parseInt(tokens[3].trim());
+
+                                       /* Longitude (= x coord) first ! */
+                                       Coordinate coordinate = new Coordinate(longitude, latitude);
+                                       coordinates.add(coordinate);
+                                       Point point = geometryFactory.createPoint(coordinate);
+
+                                       featureBuilder.add(point);
+                                       featureBuilder.add(name);
+                                       featureBuilder.add(number);
+                                       SimpleFeature feature = featureBuilder.buildFeature(null);
+                                       features.add(feature);
+                               }
+                       }
+               }
+
+               LineString lineString = geometryFactory
+                               .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+               Geometry convexHull = lineString.convexHull();
+               System.out.println(convexHull.toText());
+               SimpleFeatureBuilder hullFeatureBuilder = new SimpleFeatureBuilder(TYPE_HULL);
+               hullFeatureBuilder.add(convexHull);
+               SimpleFeature hull = hullFeatureBuilder.buildFeature(null);
+
+               /*
+                * Get an output file name and create the new shapefile
+                */
+               File newFile = getNewShapeFile();
+
+               ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
+
+               Map<String, Serializable> params = new HashMap<>();
+               params.put("url", newFile.toURI().toURL());
+               params.put("create spatial index", Boolean.TRUE);
+
+               ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
+
+               /*
+                * TYPE is used as a template to describe the file contents
+                */
+               newDataStore.createSchema(TYPE_HULL);
+
+               /*
+                * Write the features to the shapefile
+                */
+               Transaction transaction = new DefaultTransaction("create");
+
+               String typeName = newDataStore.getTypeNames()[0];
+               SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
+               SimpleFeatureType SHAPE_TYPE = featureSource.getSchema();
+               /*
+                * The Shapefile format has a couple limitations: - "the_geom" is always first,
+                * and used for the geometry attribute name - "the_geom" must be of type Point,
+                * MultiPoint, MuiltiLineString, MultiPolygon - Attribute names are limited in
+                * length - Not all data types are supported (example Timestamp represented as
+                * Date)
+                *
+                * Each data store has different limitations so check the resulting
+                * SimpleFeatureType.
+                */
+               System.out.println("SHAPE:" + SHAPE_TYPE);
+
+               if (featureSource instanceof SimpleFeatureStore) {
+                       SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
+                       /*
+                        * SimpleFeatureStore has a method to add features from a
+                        * SimpleFeatureCollection object, so we use the ListFeatureCollection class to
+                        * wrap our list of features.
+                        */
+                       SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, Collections.singletonList(hull));
+                       featureStore.setTransaction(transaction);
+                       try {
+                               featureStore.addFeatures(collection);
+                               transaction.commit();
+                       } catch (Exception problem) {
+                               problem.printStackTrace();
+                               transaction.rollback();
+                       } finally {
+                               transaction.close();
+                       }
+               } else {
+                       System.out.println(typeName + " does not support read/write access");
+               }
+//             if (featureSource instanceof SimpleFeatureStore) {
+//                     SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
+//                     /*
+//                      * SimpleFeatureStore has a method to add features from a
+//                      * SimpleFeatureCollection object, so we use the ListFeatureCollection class to
+//                      * wrap our list of features.
+//                      */
+//                     SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
+//                     featureStore.setTransaction(transaction);
+//                     try {
+//                             featureStore.addFeatures(collection);
+//                             transaction.commit();
+//                     } catch (Exception problem) {
+//                             problem.printStackTrace();
+//                             transaction.rollback();
+//                     } finally {
+//                             transaction.close();
+//                     }
+//             } else {
+//                     System.out.println(typeName + " does not support read/write access");
+//             }
+       }
+
+       /**
+        * Prompt the user for the name and path to use for the output shapefile
+        *
+        * @param csvFile the input csv file used to create a default shapefile name
+        * @return name and path for the shapefile as a new File object
+        */
+       private static File getNewShapeFile() {
+//        String path = csvFile.getAbsolutePath();
+//        String newPath = path.substring(0, path.length() - 4) + ".shp";
+
+               JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
+               chooser.setDialogTitle("Save shapefile");
+//        chooser.setSelectedFile(new File(newPath));
+
+               int returnVal = chooser.showSaveDialog(null);
+
+               if (returnVal != JFileDataStoreChooser.APPROVE_OPTION) {
+                       // the user cancelled the dialog
+                       System.exit(0);
+               }
+
+               File newFile = chooser.getSelectedFile();
+
+               return newFile;
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoUtils.java
new file mode 100644 (file)
index 0000000..f134678
--- /dev/null
@@ -0,0 +1,458 @@
+package org.argeo.app.geo;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.measure.Quantity;
+import javax.measure.quantity.Area;
+import javax.xml.transform.TransformerException;
+
+import org.geotools.api.data.SimpleFeatureSource;
+import org.geotools.api.data.SimpleFeatureStore;
+import org.geotools.api.data.Transaction;
+import org.geotools.api.feature.simple.SimpleFeature;
+import org.geotools.api.feature.simple.SimpleFeatureType;
+import org.geotools.api.filter.Filter;
+import org.geotools.api.filter.FilterFactory;
+import org.geotools.api.filter.expression.Expression;
+import org.geotools.api.geometry.MismatchedDimensionException;
+import org.geotools.api.referencing.FactoryException;
+import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
+import org.geotools.api.referencing.operation.MathTransform;
+import org.geotools.api.referencing.operation.TransformException;
+import org.geotools.api.style.AnchorPoint;
+import org.geotools.api.style.Displacement;
+import org.geotools.api.style.FeatureTypeConstraint;
+import org.geotools.api.style.FeatureTypeStyle;
+import org.geotools.api.style.Fill;
+import org.geotools.api.style.Graphic;
+import org.geotools.api.style.GraphicalSymbol;
+import org.geotools.api.style.NamedLayer;
+import org.geotools.api.style.PointSymbolizer;
+import org.geotools.api.style.Rule;
+import org.geotools.api.style.Stroke;
+import org.geotools.api.style.Style;
+import org.geotools.api.style.StyleFactory;
+import org.geotools.api.style.StyledLayerDescriptor;
+import org.geotools.api.style.UserLayer;
+import org.geotools.data.DefaultTransaction;
+import org.geotools.data.collection.ListFeatureCollection;
+import org.geotools.data.shapefile.ShapefileDataStore;
+import org.geotools.data.shapefile.ShapefileDataStoreFactory;
+import org.geotools.data.simple.SimpleFeatureCollection;
+import org.geotools.factory.CommonFactoryFinder;
+import org.geotools.geometry.jts.JTS;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.styling.css.CssParser;
+import org.geotools.styling.css.CssTranslator;
+import org.geotools.styling.css.Stylesheet;
+import org.geotools.xml.styling.SLDTransformer;
+import org.locationtech.jts.algorithm.hull.ConcaveHull;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.util.GeometryFixer;
+import org.locationtech.jts.operation.polygonize.Polygonizer;
+
+import si.uom.SI;
+import tech.units.indriya.quantity.Quantities;
+
+/** Utilities around geographical format, mostly wrapping GeoTools patterns. */
+public class GeoUtils {
+       public final static String EPSG_4326 = "EPSG:4326";
+
+       /** In square meters. */
+       public static Quantity<Area> calcArea(SimpleFeature feature) {
+               try {
+                       Polygon polygon = (Polygon) feature.getDefaultGeometry();
+                       Point centroid = polygon.getCentroid();
+//                     CoordinateReferenceSystem crs = feature.getDefaultGeometryProperty().getType()
+//                                     .getCoordinateReferenceSystem();
+                       // TODO convert the centroid
+//                     if (!crs.getName().equals(DefaultGeographicCRS.WGS84.getName()))
+//                             throw new IllegalArgumentException("Only WGS84 CRS is currently supported");
+
+                       // create automatic CRS for optimal computation
+                       String code = "AUTO:42001," + centroid.getX() + "," + centroid.getY();
+                       CoordinateReferenceSystem auto = CRS.decode(code);
+
+                       MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
+
+                       Polygon projed = (Polygon) JTS.transform(polygon, transform);
+                       return Quantities.getQuantity(projed.getArea(), SI.SQUARE_METRE);
+               } catch (MismatchedDimensionException | FactoryException | TransformException e) {
+                       throw new IllegalStateException("Cannot calculate area of feature", e);
+               }
+       }
+
+       /** In square meters. The {@link Polygon} must use WGS84 coordinates. */
+       public static Quantity<Area> calcArea(Polygon polygon) {
+               assert polygon.getSRID() == 0 || polygon.getSRID() == 4326;
+               return calcArea(polygon, DefaultGeographicCRS.WGS84, polygon.getCentroid());
+       }
+
+       public static Quantity<Area> calcArea(Polygon polygon, CoordinateReferenceSystem crs, Point wgs84centroid) {
+               try {
+                       // create automatic CRS for optimal computation
+                       String code = "AUTO:42001," + wgs84centroid.getX() + "," + wgs84centroid.getY();
+                       CoordinateReferenceSystem auto = CRS.decode(code);
+
+                       MathTransform transform = CRS.findMathTransform(crs, auto);
+
+                       Polygon projed = (Polygon) JTS.transform(polygon, transform);
+                       return Quantities.getQuantity(projed.getArea(), SI.SQUARE_METRE);
+               } catch (MismatchedDimensionException | FactoryException | TransformException e) {
+                       throw new IllegalStateException("Cannot calculate area of feature", e);
+               }
+       }
+
+       public static void exportToSvg(Geometry[] geometries, Writer out, int width, int height) {
+               try {
+                       double minY = Double.POSITIVE_INFINITY;
+                       double maxY = Double.NEGATIVE_INFINITY;
+                       double minX = Double.POSITIVE_INFINITY;
+                       double maxX = Double.NEGATIVE_INFINITY;
+                       List<String> shapes = new ArrayList<>();
+                       for (Geometry geometry : geometries) {
+                               StringBuffer sb = new StringBuffer();
+                               sb.append("<polyline style=\"stroke-width:1;stroke:#000000;fill:none;\" points=\"");
+
+                               if (geometry instanceof Polygon p) {
+                                       Point centroid = p.getCentroid();
+                                       String code = "AUTO:42001," + centroid.getX() + "," + centroid.getY();
+                                       CoordinateReferenceSystem auto = CRS.decode(code);
+
+                                       MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
+
+                                       Polygon projed = (Polygon) JTS.transform(p, transform);
+
+                                       // EPSG4326 are in lat/lon order, so we invert coordinates
+                                       for (Coordinate coord : projed.getCoordinates()) {
+                                               double x = coord.y;
+                                               if (x < minX)
+                                                       minX = x;
+                                               if (x > maxX)
+                                                       maxX = x;
+                                               double y = -coord.x;
+                                               if (y < minY)
+                                                       minY = y;
+                                               if (y > maxY)
+                                                       maxY = y;
+                                               sb.append(x + "," + y + " ");
+                                       }
+                                       sb.append("\">");
+                                       sb.append("</polyline>\n");
+                                       shapes.add(sb.toString());
+                               } else {
+                                       throw new IllegalArgumentException("Unsuppported geometry type " + geometry.getClass().getName());
+                               }
+                       }
+                       double viewportHeight = maxY - minY;
+                       double viewportWidth = maxX - minX;
+                       out.write("<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
+                       out.write(" width=\"" + width + "\"\n");
+                       out.write(" height=\"" + height + "\"\n");
+                       out.write(" viewBox=\"" + minX + " " + minY + " " + viewportWidth + " " + viewportHeight + "\"\n");
+                       out.write(" preserveAspectRatio=\"xMidYMid meet\"\n");
+                       out.write(">\n");
+                       for (String shape : shapes) {
+                               out.write(shape);
+                               out.write("\n");
+                       }
+                       out.write("</svg>");
+               } catch (IOException | FactoryException | MismatchedDimensionException | TransformException e) {
+                       throw new RuntimeException("Cannot export to SVG", e);
+               }
+       }
+
+       /** Write a list of simple features to a shapefile. */
+       public static void saveFeaturesAsShapefile(SimpleFeatureType featureType, List<SimpleFeature> features,
+                       Path shpFile) {
+               try {
+                       ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
+
+                       Map<String, Serializable> params = new HashMap<>();
+                       params.put("url", shpFile.toUri().toURL());
+
+                       params.put("create spatial index", Boolean.TRUE);
+
+                       ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
+                       newDataStore.createSchema(featureType);
+
+                       String typeName = newDataStore.getTypeNames()[0];
+                       SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
+                       if (featureSource instanceof SimpleFeatureStore) {
+                               SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
+                               SimpleFeatureCollection collection = new ListFeatureCollection(featureType, features);
+
+                               try (Transaction transaction = new DefaultTransaction("create")) {
+                                       try {
+                                               featureStore.setTransaction(transaction);
+                                               featureStore.addFeatures(collection);
+                                               transaction.commit();
+                                       } catch (Exception problem) {
+                                               transaction.rollback();
+                                               throw new RuntimeException("Cannot write shapefile " + shpFile, problem);
+                                       }
+                               }
+                       } else {
+                               throw new IllegalArgumentException(typeName + " does not support read/write access");
+                       }
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot write shapefile " + shpFile, e);
+               }
+       }
+
+       /*
+        * STYLING
+        */
+
+       public static Style createStyleFromCss(String css) {
+               Stylesheet ss = CssParser.parse(css);
+               CssTranslator translator = new CssTranslator();
+               Style style = translator.translate(ss);
+
+//             try {
+//                     SLDTransformer styleTransform = new SLDTransformer();
+//                     String xml = styleTransform.transform(style);
+//                     System.out.println(xml);
+//             } catch (TransformerException e) {
+//                     // TODO Auto-generated catch block
+//                     e.printStackTrace();
+//             }
+
+               return (Style) style;
+       }
+
+       public static String createSldFromCss(String name, String title, String css) {
+               StyledLayerDescriptor sld = GeoTools.STYLE_FACTORY.createStyledLayerDescriptor();
+               sld.setName(name);
+               sld.setTitle(title);
+
+               NamedLayer layer = GeoTools.STYLE_FACTORY.createNamedLayer();
+               layer.setName(name);
+
+               Style style = createStyleFromCss(css);
+               layer.styles().add(style);
+
+               sld.layers().add(layer);
+               return sldToXml(sld);
+       }
+
+       public static String sldToXml(StyledLayerDescriptor sld) {
+               try {
+                       SLDTransformer styleTransform = new SLDTransformer();
+                       String xml = styleTransform.transform(sld);
+//                     System.out.println(xml);
+                       return xml;
+               } catch (TransformerException e) {
+                       throw new IllegalStateException("Cannot write SLD as XML", e);
+               }
+       }
+
+       public static void main(String... args) {
+               String css = """
+                               * {
+                                  mark: symbol(circle);
+                                  mark-size: 6px;
+                                }
+
+                                :mark {
+                                  fill: red;
+                                }
+
+                                                               """;
+               createSldFromCss("test", "Test", css);
+       }
+
+       public static String createTestSLD() {
+
+               StyleFactory sf = CommonFactoryFinder.getStyleFactory();
+               FilterFactory ff = CommonFactoryFinder.getFilterFactory();
+
+               StyledLayerDescriptor sld = sf.createStyledLayerDescriptor();
+               sld.setName("sld");
+               sld.setTitle("Example");
+               sld.setAbstract("Example Style Layer Descriptor");
+
+               UserLayer layer = sf.createUserLayer();
+               layer.setName("layer");
+
+               //
+               // define constraint limited what features the sld applies to
+               FeatureTypeConstraint constraint = sf.createFeatureTypeConstraint("Feature", Filter.INCLUDE);
+
+               layer.layerFeatureConstraints().add(constraint);
+
+               //
+               // create a "user defined" style
+               Style style = sf.createStyle();
+               style.setName("style");
+               style.getDescription().setTitle("User Style");
+               style.getDescription().setAbstract("Definition of Style");
+
+               //
+               // define feature type styles used to actually define how features are rendered
+               FeatureTypeStyle featureTypeStyle = sf.createFeatureTypeStyle();
+
+               // RULE 1
+               // first rule to draw cities
+               Rule rule1 = sf.createRule();
+               rule1.setName("rule1");
+               rule1.getDescription().setTitle("City");
+               rule1.getDescription().setAbstract("Rule for drawing cities");
+//             rule1.setFilter(ff.less(ff.property("POPULATION"), ff.literal(50000)));
+
+               //
+               // create the graphical mark used to represent a city
+               Stroke stroke = sf.stroke(ff.literal("#000000"), null, null, null, null, null, null);
+               Fill fill = sf.fill(null, ff.literal(java.awt.Color.BLUE), ff.literal(1.0));
+
+//        // OnLineResource implemented by gt-metadata - so no factory!
+//        OnLineResourceImpl svg = new OnLineResourceImpl(new URI("file:city.svg"));
+//        svg.freeze(); // freeze to prevent modification at runtime
+//
+//        OnLineResourceImpl png = new OnLineResourceImpl(new URI("file:city.png"));
+//        png.freeze(); // freeze to prevent modification at runtime
+
+               //
+               // List of symbols is considered in order with the rendering engine choosing
+               // the first one it can handle. Allowing for svg, png, mark order
+               List<GraphicalSymbol> symbols = new ArrayList<>();
+//        symbols.add(sf.externalGraphic(svg, "svg", null)); // svg preferred
+//        symbols.add(sf.externalGraphic(png, "png", null)); // png preferred
+               symbols.add(sf.mark(ff.literal("circle"), fill, stroke)); // simple circle backup plan
+
+               Expression opacity = null; // use default
+               Expression size = ff.literal(10);
+               Expression rotation = null; // use default
+               AnchorPoint anchor = null; // use default
+               Displacement displacement = null; // use default
+
+               // define a point symbolizer of a small circle
+               Graphic city = sf.graphic(symbols, opacity, size, rotation, anchor, displacement);
+               PointSymbolizer pointSymbolizer = sf.pointSymbolizer("point", ff.property("the_geom"), null, null, city);
+
+               rule1.symbolizers().add(pointSymbolizer);
+
+               featureTypeStyle.rules().add(rule1);
+
+               //
+               // RULE 2 Default
+
+//             List<GraphicalSymbol> dotSymbols = new ArrayList<>();
+//             dotSymbols.add(sf.mark(ff.literal("circle"), null, null));
+//             Graphic dotGraphic = sf.graphic(dotSymbols, null, ff.literal(3), null, null, null);
+//             PointSymbolizer dotSymbolizer = sf.pointSymbolizer("dot", ff.property("the_geom"), null, null, dotGraphic);
+//             List<org.opengis.style.Symbolizer> symbolizers = new ArrayList<>();
+//             symbolizers.add(dotSymbolizer);
+//             Filter other = null; // null will mark this rule as "other" accepting all remaining features
+//             Rule rule2 = sf.rule("default", null, null, Double.MIN_VALUE, Double.MAX_VALUE, symbolizers, other);
+//             featureTypeStyle.rules().add(rule2);
+
+               style.featureTypeStyles().add(featureTypeStyle);
+
+               layer.userStyles().add(style);
+
+               sld.layers().add(layer);
+
+               try {
+                       SLDTransformer styleTransform = new SLDTransformer();
+                       String xml = styleTransform.transform(sld);
+                       System.out.println(xml);
+                       return xml;
+               } catch (TransformerException e) {
+                       throw new IllegalStateException(e);
+               }
+
+       }
+
+       public static long getScaleFromResolution(long resolution) {
+               // see
+               // https://gis.stackexchange.com/questions/242424/how-to-get-map-units-to-find-current-scale-in-openlayers
+               final double INCHES_PER_UNIT = 39.37;// m
+               // final double INCHES_PER_UNIT = 4374754;// dd
+               final long DOTS_PER_INCH = 72;
+               return Math.round(INCHES_PER_UNIT * DOTS_PER_INCH * resolution);
+       }
+
+       /*
+        * GEOMETRY
+        */
+       /**
+        * Ensure that a {@link Polygon} is valid and simple by removing artefacts
+        * (typically coming from GPS).
+        * 
+        * @return a simple and valid polygon, or null if the ignoredArea ratio is above
+        *         the provided threshold.
+        */
+       public static Polygon cleanPolygon(Polygon polygon, double ignoredAreaRatio) {
+               Polygonizer polygonizer = new Polygonizer(true);
+               Geometry fixed = GeometryFixer.fix(polygon, false);
+               polygonizer.add(fixed);
+               @SuppressWarnings("unchecked")
+               List<Polygon> polygons = new ArrayList<>(polygonizer.getPolygons());
+
+               if (polygons.size() == 0) {
+                       throw new IllegalStateException("Polygonizer failed to extract any polygon");
+               }
+
+               Polygon best;
+               if (polygons.size() == 1) {
+                       best = polygons.get(0);
+               } else {
+                       double totalArea = fixed.getArea();
+                       best = polygons.get(0);
+                       double bestArea = best.getArea();
+                       for (int i = 1; i < polygons.size(); i++) {
+                               Polygon p = polygons.get(i);
+                               double a = p.getArea();
+                               if (a > bestArea) {
+                                       best = p;
+                                       bestArea = a;
+                               } else {
+                                       // double ratio = a / totalArea;
+                               }
+                       }
+                       double ignoredRatio = (totalArea - bestArea) / totalArea;
+                       if (ignoredRatio > ignoredAreaRatio)
+                               return null;
+
+                       if (!best.isValid() || !best.isSimple()) {
+                               throw new IllegalStateException("The polygon is not simple and/or valid after cleaning");
+                       }
+               }
+               // while we are here, we make sure that the geometry will be normalised
+               best.normalize();
+               return best;
+       }
+
+       /**
+        * The smallest polygon without holes containing all the points in this
+        * geometry.
+        */
+       public static Polygon concaveHull(Geometry geom, double lengthRatio) {
+               Objects.requireNonNull(geom);
+               if (geom.getNumPoints() < 3)
+                       throw new IllegalArgumentException("At least 3 points are reuired to compute the concave hull and geometry "
+                                       + geom.getClass() + " contains only " + geom.getNumPoints());
+               Geometry hull = ConcaveHull.concaveHullByLengthRatio(geom, lengthRatio, false);
+               if (hull instanceof Polygon polygon)
+                       return polygon;
+               else
+                       throw new IllegalStateException("Hull is not a polygon but a " + hull.getClass());
+       }
+
+       /** Singleton. */
+       private GeoUtils() {
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GmlAttr.java b/org.argeo.app.geo/src/org/argeo/app/geo/GmlAttr.java
new file mode 100644 (file)
index 0000000..77b0885
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.app.geo;
+
+import org.argeo.api.acr.QNamed;
+
+public enum GmlAttr implements QNamed {
+       uom
+       //
+       ;
+
+       public final static String UOM_SQUARE_METERS = "m2";
+
+       @Override
+       public String getNamespace() {
+               return "http://www.opengis.net/gml/3.2";
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return "gml";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GmlType.java b/org.argeo.app.geo/src/org/argeo/app/geo/GmlType.java
new file mode 100644 (file)
index 0000000..980a5e4
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.app.geo;
+
+import org.argeo.api.acr.QNamed;
+
+public enum GmlType implements QNamed {
+       measure
+       //
+       ;
+
+       @Override
+       public String getNamespace() {
+               return "http://www.opengis.net/gml/3.2";
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return "gml";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GpxUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/GpxUtils.java
new file mode 100644 (file)
index 0000000..44afa2c
--- /dev/null
@@ -0,0 +1,151 @@
+package org.argeo.app.geo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.geotools.api.feature.simple.SimpleFeature;
+import org.geotools.api.feature.simple.SimpleFeatureType;
+import org.geotools.data.DataUtilities;
+import org.geotools.feature.SchemaException;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.Polygon;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** Utilities around the GPX format. */
+public class GpxUtils {
+       /** GPX as a LineString in WGS84 (feature type with only the_geom). */
+       public static final SimpleFeatureType LINESTRING_FEATURE_TYPE;
+       /** GPX as a Polygon in WGS84 (feature type with only the_geom). */
+       public static final SimpleFeatureType POLYGON_FEATURE_TYPE;
+
+       static {
+               try {
+                       LINESTRING_FEATURE_TYPE = DataUtilities.createType("Area", "the_geom:LineString:srid=4326");
+                       POLYGON_FEATURE_TYPE = DataUtilities.createType("Area", "the_geom:Polygon:srid=4326");
+               } catch (SchemaException e) {
+                       throw new RuntimeException("Cannot create GPX Feature type", e);
+               }
+       }
+
+       /**
+        * Converts a GPX track to either a {@link Geometry} with WGS84 coordinates
+        * ({@link LineString} or {@link Polygon}) or a {@link SimpleFeature} (with
+        * {@link #LINESTRING_FEATURE_TYPE}).
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> T parseGpxTrackTo(InputStream in, Class<T> clss) throws IOException {
+               GeometryFactory geometryFactory = JTS.GEOMETRY_FACTORY_WGS84;
+               List<Coordinate> coordinates = new ArrayList<>();
+               try {
+                       SAXParserFactory factory = SAXParserFactory.newInstance();
+                       SAXParser saxParser = factory.newSAXParser();
+
+                       saxParser.parse(in, new DefaultHandler() {
+
+                               @Override
+                               public void startElement(String uri, String localName, String qName, Attributes attributes)
+                                               throws SAXException {
+                                       if ("trkpt".equals(qName)) {
+                                               Double latitude = Double.parseDouble(attributes.getValue("lat"));
+                                               Double longitude = Double.parseDouble(attributes.getValue("lon"));
+                                               // TODO elevation in 3D context
+                                               Coordinate coordinate = new Coordinate(latitude, longitude);
+                                               coordinates.add(coordinate);
+                                       }
+                               }
+
+                       });
+               } catch (ParserConfigurationException | SAXException e) {
+                       throw new RuntimeException("Cannot convert GPX", e);
+               }
+
+               if (LineString.class.isAssignableFrom(clss)) {
+                       LineString lineString = geometryFactory
+                                       .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       return (T) lineString;
+               } else if (MultiPoint.class.isAssignableFrom(clss)) {
+                       MultiPoint multiPoint = geometryFactory
+                                       .createMultiPointFromCoords(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       // multiPoint.normalize();
+                       return (T) multiPoint;
+               } else if (Polygon.class.isAssignableFrom(clss)) {
+                       Coordinate first = coordinates.get(0);
+                       Coordinate last = coordinates.get(coordinates.size() - 1);
+                       if (!(first.getX() == last.getX() && first.getY() == last.getY())) {
+                               // close the line string
+                               coordinates.add(first);
+                       }
+                       Polygon polygon = geometryFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       return (T) polygon;
+               } else if (SimpleFeature.class.isAssignableFrom(clss)) {
+                       SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(LINESTRING_FEATURE_TYPE);
+                       LineString lineString = geometryFactory
+                                       .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       featureBuilder.add(lineString);
+                       SimpleFeature area = featureBuilder.buildFeature(null);
+                       return (T) area;
+               } else {
+                       throw new IllegalArgumentException("Unsupported format " + clss);
+               }
+       }
+
+       /** @deprecated Use {@link #parseGpxTrackTo(InputStream, Class)} instead. */
+       @Deprecated
+       public static SimpleFeature parseGpxToPolygon(InputStream in) throws IOException {
+               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(POLYGON_FEATURE_TYPE);
+               Polygon polygon = parseGpxTrackTo(in, Polygon.class);
+               featureBuilder.add(polygon);
+               SimpleFeature area = featureBuilder.buildFeature(null);
+               return area;
+       }
+
+       /** Write ODK GeoShape as a GPX file. */
+       public static void writeGeoShapeAsGpx(String geoShape, OutputStream out) throws IOException {
+               Objects.requireNonNull(geoShape);
+               Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
+               writer.append("<gpx><trk><trkseg>");
+               StringTokenizer stSeg = new StringTokenizer(geoShape.trim(), ";");
+               while (stSeg.hasMoreTokens()) {
+                       StringTokenizer stPt = new StringTokenizer(stSeg.nextToken().trim(), " ");
+                       String lat = stPt.nextToken();
+                       String lng = stPt.nextToken();
+                       String alt = stPt.nextToken();
+                       // String precision = stPt.nextToken();
+                       writer.append("<trkpt");
+                       writer.append(" lat=\"").append(lat).append('\"');
+                       writer.append(" lon=\"").append(lng).append('\"');
+                       if (!alt.equals("0.0")) {
+                               writer.append('>');
+                               writer.append("<ele>").append(alt).append("</ele>");
+                               writer.append("</trkpt>");
+                       } else {
+                               writer.append("/>");
+                       }
+               }
+               writer.append("</trkseg></trk></gpx>");
+               writer.flush();
+       }
+
+       /** Singleton. */
+       private GpxUtils() {
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/JTS.java b/org.argeo.app.geo/src/org/argeo/app/geo/JTS.java
new file mode 100644 (file)
index 0000000..ba7ecf9
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.app.geo;
+
+import org.argeo.api.cms.CmsLog;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.PrecisionModel;
+
+/**
+ * Factories initialisation and workarounds for the JTS library. The idea is to
+ * code defensively around factory initialisation, API changes, and issues
+ * related to running in an OSGi environment. Rather see {@link GeoUtils} for
+ * functional static utilities.
+ */
+public class JTS {
+       private final static CmsLog log = CmsLog.getLog(JTS.class);
+
+       public final static int WGS84_SRID = 4326;
+
+       /** A geometry factory with no SRID specified */
+       public final static GeometryFactory GEOMETRY_FACTORY;
+       /** A geometry factory with SRID 4326 (WGS84 in the EPSG database) */
+       public final static GeometryFactory GEOMETRY_FACTORY_WGS84;
+
+       static {
+               try {
+                       GEOMETRY_FACTORY = new GeometryFactory();
+                       GEOMETRY_FACTORY_WGS84 = new GeometryFactory(new PrecisionModel(), WGS84_SRID);
+               } catch (RuntimeException e) {
+                       log.error("Basic JTS initialisation failed, geographical utilities are probably not available", e);
+                       throw e;
+               }
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java
new file mode 100644 (file)
index 0000000..48c3c1b
--- /dev/null
@@ -0,0 +1,159 @@
+package org.argeo.app.geo.acr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.QNamed;
+import org.argeo.app.api.EntityName;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.WGS84PosName;
+import org.argeo.app.geo.GeoJson;
+import org.argeo.app.geo.JTS;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.Point;
+
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.stream.JsonGenerator;
+
+/** Utilities around entity types related to geography. */
+public class GeoEntityUtils {
+       public static final String _GEOM_JSON = ".geom.json";
+
+       public static void putGeometry(Content c, QNamed name, Geometry geometry) {
+               putGeometry(c, name.qName(), geometry);
+       }
+
+       public static void putGeometry(Content c, QName name, Geometry geometry) {
+               QName jsonFileName = getJsonFileName(name);
+               Content geom = c.soleChild(jsonFileName).orElseGet(
+                               () -> c.add(jsonFileName, Collections.singletonMap(DName.getcontenttype.qName(), "application/json")));
+               try (OutputStream out = geom.open(OutputStream.class)) {
+                       JsonGenerator g = Json.createGenerator(out);
+                       g.writeStartObject();
+                       GeoJson.writeGeometry(g, geometry);
+                       g.writeEnd();
+                       g.close();
+               } catch (IOException e) {
+                       throw new UncheckedIOException("Cannot add geometry " + name + " to " + c, e);
+               }
+
+//             try (BufferedReader in = new BufferedReader(
+//                             new InputStreamReader(geom.open(InputStream.class), StandardCharsets.UTF_8))) {
+//                     System.out.println(in.readLine());
+//             } catch (IOException e) {
+//                     throw new UncheckedIOException("Cannot parse " + c, e);
+//             }
+               updateBoundingBox(c);
+       }
+
+       public static boolean hasGeometry(Content c, QNamed name) {
+               return hasGeometry(c, name.qName());
+       }
+
+       public static boolean hasGeometry(Content c, QName name) {
+               QName jsonFileName = getJsonFileName(name);
+               return c.hasChild(jsonFileName);
+       }
+
+       public static <T extends Geometry> T getGeometry(Content c, QNamed name, Class<T> clss) {
+               return getGeometry(c, name.qName(), clss);
+       }
+
+       public static <T extends Geometry> T getGeometry(Content c, QName name, Class<T> clss) {
+               QName jsonFileName = getJsonFileName(name);
+               Content geom = c.soleChild(jsonFileName).orElse(null);
+               if (geom == null)
+                       return null;
+               try (Reader in = new InputStreamReader(geom.open(InputStream.class), StandardCharsets.UTF_8)) {
+                       JsonReader jsonReader = Json.createReader(in);
+                       JsonObject jsonObject = jsonReader.readObject();
+                       T readGeom = GeoJson.readGeometry(jsonObject, clss);
+                       return readGeom;
+               } catch (IOException e) {
+                       throw new UncheckedIOException("Cannot parse " + c, e);
+               }
+       }
+
+       private static QName getJsonFileName(QName name) {
+               QName jsonFileName = new ContentName(name.getNamespaceURI(), name.getLocalPart() + _GEOM_JSON);
+               return jsonFileName;
+       }
+
+       public static Point toPoint(Content c) {
+               if (c.containsKey(WGS84PosName.lon) && c.containsKey(WGS84PosName.lat)) {
+                       Double lat = c.get(WGS84PosName.lat, Double.class).orElseThrow();
+                       Double lon = c.get(WGS84PosName.lon, Double.class).orElseThrow();
+                       return JTS.GEOMETRY_FACTORY_WGS84.createPoint(new Coordinate(lat, lon));
+//                     Double alt = c.get(WGS84PosName.alt, Double.class).orElse(null);
+//                     return JTS.GEOMETRY_FACTORY_WGS84
+//                                     .createPoint(alt != null ? new Coordinate(lat, lon, alt) : new Coordinate(lat, lon));
+               }
+               return null;
+       }
+
+       public static GeometryCollection getGeometries(Content entity) {
+               List<Geometry> geoms = new ArrayList<>();
+               Point geoPoint = toPoint(entity);
+               if (geoPoint != null)
+                       geoms.add(geoPoint);
+
+               Geometry place = getGeometry(entity, EntityName.place.qName(), Geometry.class);
+               if (place != null)
+                       geoms.add(place);
+
+               if (geoms.isEmpty())
+                       return null;
+               GeometryCollection geometryCollection = JTS.GEOMETRY_FACTORY_WGS84
+                               .createGeometryCollection(geoms.toArray(new Geometry[geoms.size()]));
+               return geometryCollection;
+       }
+
+       public static void updateBoundingBox(Content entity) {
+               GeometryCollection geometryCollection = getGeometries(entity);
+               if (geometryCollection == null)
+                       return;
+               entity.addContentClasses(EntityType.geobounded.qName());
+
+               Envelope bbox = geometryCollection.getEnvelopeInternal();
+               entity.put(EntityName.minLat, bbox.getMinX());
+               entity.put(EntityName.minLon, bbox.getMinY());
+               entity.put(EntityName.maxLat, bbox.getMaxX());
+               entity.put(EntityName.maxLon, bbox.getMaxY());
+       }
+
+       public static void updateBoundingBox(Content entity, QName prop) {
+               Geometry geom = getGeometry(entity, prop, Geometry.class);
+               if (geom == null)
+                       return;
+               entity.addContentClasses(EntityType.geobounded.qName());
+
+               Envelope bbox = geom.getEnvelopeInternal();
+               entity.put(EntityName.minLat, bbox.getMinX());
+               entity.put(EntityName.minLon, bbox.getMinY());
+               entity.put(EntityName.maxLat, bbox.getMaxX());
+               entity.put(EntityName.maxLon, bbox.getMaxY());
+       }
+
+       /** singleton */
+       private GeoEntityUtils() {
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/geonames/GeonamesAdm.java b/org.argeo.app.geo/src/org/argeo/app/geo/geonames/GeonamesAdm.java
new file mode 100644 (file)
index 0000000..45febd2
--- /dev/null
@@ -0,0 +1,157 @@
+package org.argeo.app.geo.geonames;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/** A Geonames administrative subdivision. */
+public class GeonamesAdm {
+       private final Long geonameId;
+       private final String countryCode;
+       private final String adminCode1;
+       private final String admLevel;
+       private final Integer level;
+       private final String name;
+       private final String asciiName;
+       private final List<String> alternateNames;
+       private final Double lat;
+       private final Double lng;
+       private final LocalDate lastUpdated;
+       private final ZoneId timeZone;
+
+       private final Long[] upperLevelIds = new Long[5];
+       private final List<GeonamesAdm> upperLevels = new ArrayList<>();
+
+       private List<String> row;
+
+       /** Initialise from a row in the main Geonames table. */
+       public GeonamesAdm(List<String> row) {
+               geonameId = Long.parseLong(row.get(0));
+               admLevel = row.get(7);
+               countryCode = row.get(8);
+               adminCode1 = row.get(10);
+               if (admLevel.startsWith("ADM")) {
+                       if (admLevel.endsWith("H"))
+                               level = Integer.parseInt(admLevel.substring(3, admLevel.length() - 1));
+                       else
+                               level = Integer.parseInt(admLevel.substring(3));
+               } else if (admLevel.equals("PCLI")) {
+                       level = 0;
+               } else {
+                       throw new IllegalArgumentException("Unsupported admin level " + admLevel);
+               }
+               name = row.get(1);
+               asciiName = row.get(2);
+               alternateNames = Arrays.asList(row.get(3).split(","));
+               lat = Double.parseDouble(row.get(4));
+               lng = Double.parseDouble(row.get(5));
+               lastUpdated = LocalDate.parse(row.get(18));
+               timeZone = ZoneId.of(row.get(17));
+               // upper levels
+               if (row.get(11) != null && !row.get(11).trim().equals(""))
+                       upperLevelIds[2] = Long.parseLong(row.get(11));
+               if (row.get(12) != null && !row.get(12).trim().equals(""))
+                       upperLevelIds[3] = Long.parseLong(row.get(12));
+               if (row.get(13) != null && !row.get(13).trim().equals(""))
+                       upperLevelIds[4] = Long.parseLong(row.get(13));
+               this.row = row;
+       }
+
+       public void mapUpperLevels(Map<Long, GeonamesAdm> index) {
+               for (int i = 0; i < level; i++) {
+                       Long geonameId = upperLevelIds[i];
+                       upperLevels.add(i, index.get(geonameId));
+               }
+       }
+
+       public Long getGeonameId() {
+               return geonameId;
+       }
+
+       public Integer getLevel() {
+               return level;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public String getName(Function<String, String> transform) {
+               if (transform != null)
+                       return transform.apply(name);
+               else
+                       return name;
+
+       }
+
+       public String getAsciiName() {
+               return asciiName;
+       }
+
+       public List<String> getAlternateNames() {
+               return alternateNames;
+       }
+
+       public Double getLat() {
+               return lat;
+       }
+
+       public Double getLng() {
+               return lng;
+       }
+
+       public String getCountryCode() {
+               return countryCode;
+       }
+
+       public String getAdmLevel() {
+               return admLevel;
+       }
+
+       public List<String> getRow() {
+               return row;
+       }
+
+       public LocalDate getLastUpdated() {
+               return lastUpdated;
+       }
+
+       public ZoneId getTimeZone() {
+               return timeZone;
+       }
+
+       public String getAdminCode1() {
+               return adminCode1;
+       }
+
+       public Long[] getUpperLevelIds() {
+               return upperLevelIds;
+       }
+
+       public List<GeonamesAdm> getUpperLevels() {
+               return upperLevels;
+       }
+
+       @Override
+       public int hashCode() {
+               return geonameId.intValue();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof GeonamesAdm))
+                       return false;
+               GeonamesAdm other = (GeonamesAdm) obj;
+               return geonameId.equals(other.geonameId);
+       }
+
+       @Override
+       public String toString() {
+               return name + " (ADM" + level + " " + geonameId + ")";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/geonames/ImportGeonamesAdmin.java b/org.argeo.app.geo/src/org/argeo/app/geo/geonames/ImportGeonamesAdmin.java
new file mode 100644 (file)
index 0000000..be2b507
--- /dev/null
@@ -0,0 +1,93 @@
+package org.argeo.app.geo.geonames;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.cms.util.CsvParser;
+import org.argeo.cms.util.CsvWriter;
+
+/** Import GeoNames administrative division from the main table. */
+public class ImportGeonamesAdmin {
+       // private Log log = LogFactory.getLog(ImportGeonamesAdmin.class);
+       private Map<Long, GeonamesAdm> geonamesAdms = new HashMap<>();
+
+       /** Loads the data. */
+       public void parse(InputStream in) {
+               Map<String, Long> countryGeonameIds = new HashMap<>();
+               Map<String, Long> admin1GeonameIds = new HashMap<>();
+               CsvParser csvParser = new CsvParser() {
+
+                       @Override
+                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
+                               if (!"A".equals(tokens.get(6)))
+                                       return;
+                               GeonamesAdm geonamesAdm = new GeonamesAdm(tokens);
+                               geonamesAdms.put(geonamesAdm.getGeonameId(), geonamesAdm);
+                               if (geonamesAdm.getAdmLevel().equals("PCLI"))
+                                       countryGeonameIds.put(geonamesAdm.getCountryCode(), geonamesAdm.getGeonameId());
+                               if (geonamesAdm.getAdmLevel().equals("ADM1"))
+                                       admin1GeonameIds.put(geonamesAdm.getAdminCode1(), geonamesAdm.getGeonameId());
+                       }
+               };
+               csvParser.setSeparator('\t');
+               csvParser.setNoHeader(true);
+               csvParser.parse(in, StandardCharsets.UTF_8);
+
+               // fill upper levels
+               for (GeonamesAdm adm : geonamesAdms.values()) {
+                       adm.getUpperLevelIds()[0] = countryGeonameIds.get(adm.getCountryCode());
+                       if (adm.getLevel() > 0)
+                               adm.getUpperLevelIds()[1] = admin1GeonameIds.get(adm.getAdminCode1());
+                       adm.mapUpperLevels(geonamesAdms);
+               }
+
+       }
+
+       public Map<Long, GeonamesAdm> getGeonamesAdms() {
+               return geonamesAdms;
+       }
+
+       /**
+        * Copies only the Geonames of feature class 'A' (administrative subdivisions).
+        */
+       public static void filterGeonamesAdm(InputStream in, OutputStream out) {
+               CsvWriter csvWriter = new CsvWriter(out, StandardCharsets.UTF_8);
+               csvWriter.setSeparator('\t');
+               CsvParser csvParser = new CsvParser() {
+
+                       @Override
+                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
+                               if (tokens.size() < 7 || !"A".equals(tokens.get(6)))
+                                       return;
+                               csvWriter.writeLine(tokens);
+                       }
+               };
+               csvParser.setSeparator('\t');
+               csvParser.setNoHeader(true);
+               csvParser.parse(in, StandardCharsets.UTF_8);
+       }
+
+       public static void main(String[] args) throws IOException {
+//             String country = "allCountries";
+               String country = "CI";
+//             try (InputStream in = Files
+//                             .newInputStream(Paths.get(System.getProperty("user.home") + "/gis/data/geonames/" + country + ".txt"));
+//                             OutputStream out = Files.newOutputStream(
+//                                             Paths.get(System.getProperty("user.home") + "/gis/data/geonames/" + country + "-adm.txt"))) {
+//                     ImportGeonamesAdmin.filterGeonamesAdm(in, out);
+//             }
+               try (InputStream in = Files.newInputStream(
+                               Paths.get(System.getProperty("user.home") + "/gis/data/geonames/" + country + "-adm.txt"))) {
+                       ImportGeonamesAdmin importGeonamesAdmin = new ImportGeonamesAdmin();
+                       importGeonamesAdmin.parse(in);
+               }
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java b/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java
new file mode 100644 (file)
index 0000000..c12a77b
--- /dev/null
@@ -0,0 +1,508 @@
+package org.argeo.app.geo.http;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.search.AndFilter;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityName;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.WGS84PosName;
+import org.argeo.app.api.geo.FeatureAdapter;
+import org.argeo.app.api.geo.WfsKvp;
+import org.argeo.app.geo.CqlUtils;
+import org.argeo.app.geo.GeoJson;
+import org.argeo.app.geo.GeoUtils;
+import org.argeo.app.geo.GpxUtils;
+import org.argeo.app.geo.JTS;
+import org.argeo.app.geo.acr.GeoEntityUtils;
+import org.argeo.cms.acr.json.AcrJsonUtils;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.RemoteAuthHttpExchange;
+import org.argeo.cms.http.server.HttpServerUtils;
+import org.argeo.cms.util.LangUtils;
+import org.geotools.api.feature.GeometryAttribute;
+import org.geotools.api.feature.simple.SimpleFeature;
+import org.geotools.api.feature.simple.SimpleFeatureType;
+import org.geotools.api.feature.type.AttributeDescriptor;
+import org.geotools.api.feature.type.Name;
+import org.geotools.api.referencing.FactoryException;
+import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
+import org.geotools.api.referencing.operation.MathTransform;
+import org.geotools.api.referencing.operation.TransformException;
+import org.geotools.feature.DefaultFeatureCollection;
+import org.geotools.feature.NameImpl;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.wfs.GML;
+import org.geotools.wfs.GML.Version;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.Polygon;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+import jakarta.json.Json;
+import jakarta.json.stream.JsonGenerator;
+
+/** A partially implemented WFS 2.0 server. */
+public class WfsHttpHandler implements HttpHandler {
+       private final static CmsLog log = CmsLog.getLog(WfsHttpHandler.class);
+       private ProvidedRepository contentRepository;
+
+       private final Map<QName, FeatureAdapter> featureAdapters = new HashMap<>();
+
+       @Override
+       public void handle(HttpExchange exchange) throws IOException {
+               ContentSession session = HttpServerUtils.getContentSession(contentRepository, exchange);
+
+               String path = HttpServerUtils.subPath(exchange);
+
+               // content path
+               final String pathToUse = path;
+               String fileName = null;
+               boolean zipped = false;
+//             int lastSlash = path.lastIndexOf('/');
+//             if (lastSlash > 0) {
+//                     fileName = path.substring(lastSlash + 1);
+//             }
+//             if (fileName != null) {
+//                     pathToUse = path.substring(0, lastSlash);
+//                     if (path.endsWith(".zip")) {
+//                             zipped = true;
+//                     }
+//             } else {
+//                     pathToUse = path;
+//             }
+
+               Map<String, List<String>> parameters = HttpServerUtils.parseParameters(exchange);
+
+               // PARAMETERS
+               String cql = getKvpParameter(parameters, WfsKvp.CQL_FILTER);
+               String typeNamesStr = getKvpParameter(parameters, WfsKvp.TYPE_NAMES);
+               String outputFormat = getKvpParameter(parameters, WfsKvp.OUTPUT_FORMAT);
+               if (outputFormat == null) {
+                       outputFormat = "application/json";
+               }
+
+               // TODO deal with multiple
+               String formatOption = getKvpParameter(parameters, WfsKvp.FORMAT_OPTIONS);
+               if (formatOption != null) {
+                       if (formatOption.startsWith(WfsKvp.FILENAME_))
+                               fileName = formatOption.substring(WfsKvp.FILENAME_.length());
+               }
+               if (fileName != null && fileName.endsWith(".zip"))
+                       zipped = true;
+
+               // bbox
+               String bboxStr = getKvpParameter(parameters, WfsKvp.BBOX);
+               if (log.isTraceEnabled())
+                       log.trace(bboxStr);
+               final Envelope bbox;
+               if (bboxStr != null) {
+                       String srs;
+                       String[] arr = bboxStr.split(",");
+                       // TODO check SRS and convert to WGS84
+                       double minLat = Double.parseDouble(arr[0]);
+                       double minLon = Double.parseDouble(arr[1]);
+                       double maxLat = Double.parseDouble(arr[2]);
+                       double maxLon = Double.parseDouble(arr[3]);
+                       if (arr.length == 5) {
+                               srs = arr[4];
+                       } else {
+                               srs = null;
+                       }
+
+                       if (srs != null && !srs.equals(GeoUtils.EPSG_4326)) {
+                               try {
+                                       // TODO optimise
+                                       CoordinateReferenceSystem sourceCRS = CRS.decode(srs);
+                                       CoordinateReferenceSystem targetCRS = CRS.decode(GeoUtils.EPSG_4326);
+                                       MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
+                                       bbox = org.geotools.geometry.jts.JTS.transform(
+                                                       new Envelope(new Coordinate(minLat, minLon), new Coordinate(maxLat, maxLon)), transform);
+                               } catch (FactoryException | TransformException e) {
+                                       throw new IllegalArgumentException("Cannot convert bounding box", e);
+                                       // bbox = null;
+                               }
+                       } else {
+                               bbox = new Envelope(new Coordinate(minLat, minLon), new Coordinate(maxLat, maxLon));
+                       }
+               } else {
+                       bbox = null;
+               }
+
+               // response headers
+               exchange.getResponseHeaders().set(HttpHeader.DATE.getHeaderName(), Long.toString(System.currentTimeMillis()));
+
+               if (fileName != null) {
+                       exchange.getResponseHeaders().set(HttpHeader.CONTENT_DISPOSITION.getHeaderName(),
+                                       HttpHeader.ATTACHMENT + ";" + HttpHeader.FILENAME + "=\"" + fileName + "\"");
+
+               }
+
+               // content type
+               if (zipped) {
+                       exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/zip");
+
+               } else {
+                       switch (outputFormat) {
+                       case "application/json" -> {
+                               exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/json");
+                       }
+                       case "GML3" -> {
+//                     exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/gml+xml");
+                               exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/xml");
+                       }
+
+                       default -> throw new IllegalArgumentException("Unexpected value: " + outputFormat);
+                       }
+               }
+
+               List<QName> typeNames = new ArrayList<>();
+               if (typeNamesStr != null) {
+                       String[] arr = typeNamesStr.split(",");
+                       for (int i = 0; i < arr.length; i++) {
+                               typeNames.add(NamespaceUtils.parsePrefixedName(arr[i]));
+                       }
+               } else {
+                       typeNames.add(EntityType.local.qName());
+               }
+
+               if (typeNames.size() > 1)
+                       throw new UnsupportedOperationException("Only one type name is currently supported");
+
+               // QUERY
+               Stream<Content> res = session.search((search) -> {
+                       if (cql != null) {
+                               CqlUtils.filter(search.from(pathToUse), cql);
+                       } else {
+                               search.from(pathToUse);
+                       }
+                       for (QName typeName : typeNames) {
+                               FeatureAdapter featureAdapter = featureAdapters.get(typeName);
+                               if (featureAdapter == null)
+                                       throw new IllegalStateException("No feature adapter found for " + typeName);
+                               // f.isContentClass(typeName);
+                               RemoteAuthUtils.doAs(() -> {
+                                       featureAdapter.addConstraintsForFeature((AndFilter) search.getWhere(), typeName);
+                                       return null;
+                               }, new RemoteAuthHttpExchange(exchange));
+                       }
+
+                       if (bbox != null) {
+                               search.getWhere().any((or) -> {
+                                       // box overlap, see https://stackoverflow.com/questions/20925818/algorithm-to-check-if-two-boxes-overlap
+                                       // isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
+                                       // x1 = entity, x2 = bbox
+                                       or.all((and) -> {
+                                               and.lte(EntityName.minLat, bbox.getMaxX());
+                                               and.gte(EntityName.maxLat, bbox.getMinX());
+                                               and.lte(EntityName.minLon, bbox.getMaxY());
+                                               and.gte(EntityName.maxLon, bbox.getMinY());
+                                       });
+                                       or.all((and) -> {
+                                               and.gte(WGS84PosName.lat, bbox.getMinX());
+                                               and.gte(WGS84PosName.lon, bbox.getMinY());
+                                               and.lte(WGS84PosName.lat, bbox.getMaxX());
+                                               and.lte(WGS84PosName.lon, bbox.getMaxY());
+                                       });
+                               });
+                       }
+               });
+
+               exchange.sendResponseHeaders(200, 0);
+
+               final int BUFFER_SIZE = 100 * 1024;
+               try (OutputStream out = zipped ? new ZipOutputStream(exchange.getResponseBody())
+                               : new BufferedOutputStream(exchange.getResponseBody(), BUFFER_SIZE)) {
+                       if (out instanceof ZipOutputStream zipOut) {
+                               String unzippedFileName = fileName.substring(0, fileName.length() - ".zip".length());
+                               zipOut.putNextEntry(new ZipEntry(unzippedFileName));
+                       }
+
+                       if ("GML3".equals(outputFormat)) {
+                               encodeCollectionAsGML(res, out);
+                       } else if ("application/json".equals(outputFormat)) {
+                               encodeCollectionAsGeoJSon(res, out, typeNames);
+                       }
+               }
+       }
+
+       /**
+        * Retrieve KVP (keyword-value pairs) parameters, which are lower case, as per
+        * specifications.
+        * 
+        * @see https://docs.ogc.org/is/09-025r2/09-025r2.html#19
+        */
+       protected String getKvpParameter(Map<String, List<String>> parameters, WfsKvp key) {
+               Objects.requireNonNull(key, "KVP key cannot be null");
+               // let's first try the default (CAML case) which should be more efficient
+               List<String> values = parameters.get(key.getKey());
+               if (values == null) {
+                       // then let's do an ignore case comparison of the key
+                       keys: for (String k : parameters.keySet()) {
+                               if (key.getKey().equalsIgnoreCase(k)) {
+                                       values = parameters.get(k);
+                                       break keys;
+                               }
+                       }
+               }
+               if (values == null) // nothing was found
+                       return null;
+               if (values.size() != 1) {
+                       // although not completely clear from the standard, we assume keys must be
+                       // unique
+                       // since lists are defined here
+                       // https://docs.ogc.org/is/09-026r2/09-026r2.html#10
+                       throw new IllegalArgumentException("Key " + key + " as multiple values");
+               }
+               String value = values.get(0);
+               assert value != null;
+               return value;
+       }
+
+       protected void encodeCollectionAsGeoJSon(Stream<Content> features, OutputStream out, List<QName> typeNames)
+                       throws IOException {
+               long begin = System.currentTimeMillis();
+               AtomicLong count = new AtomicLong(0);
+               JsonGenerator generator = Json.createGenerator(out);
+               generator.writeStartObject();
+               generator.write("type", "FeatureCollection");
+               generator.writeStartArray("features");
+               features.forEach((c) -> {
+                       // TODO deal with multiple type names
+                       FeatureAdapter featureAdapter = null;
+                       QName typeName = null;
+                       if (!typeNames.isEmpty()) {
+                               typeName = typeNames.get(0);
+                               featureAdapter = featureAdapters.get(typeName);
+                       }
+
+                       boolean geometryWritten = false;
+//                     if (typeName.getLocalPart().equals("fieldSimpleFeature")) {
+//                             Content area = c.getContent("place.geom.json").orElse(null);
+//                             if (area != null) {
+//                                     generator.writeStartObject();
+//                                     generator.write("type", "Feature");
+//                                     String featureId = getFeatureId(c);
+//                                     if (featureId != null)
+//                                             generator.write("id", featureId);
+//
+//                                     generator.flush();
+//                                     try (InputStream in = area.open(InputStream.class)) {
+//                                             out.write(",\"geometry\":".getBytes());
+//                                             StreamUtils.copy(in, out);                                              
+//                                             //out.flush();
+//                                     } catch (Exception e) {
+//                                             log.error(c.getPath() + " : " + e.getMessage());
+//                                     } finally {
+//                                     }
+//                                     geometryWritten = true;
+//                             }else {
+//                                     return;
+//                             }
+//                     }
+
+                       if (!geometryWritten) {
+
+                               Geometry defaultGeometry = featureAdapter != null ? featureAdapter.getDefaultGeometry(c, typeName)
+                                               : getDefaultGeometry(c);
+                               if (defaultGeometry == null)
+                                       return;
+                               generator.writeStartObject();
+                               generator.write("type", "Feature");
+                               String featureId = getFeatureId(c);
+                               if (featureId != null)
+                                       generator.write("id", featureId);
+
+                               GeoJson.writeBBox(generator, defaultGeometry);
+                               generator.writeStartObject(GeoJson.GEOMETRY);
+                               GeoJson.writeGeometry(generator, defaultGeometry);
+                               generator.writeEnd();// geometry object
+                       }
+                       generator.writeStartObject(GeoJson.PROPERTIES);
+                       AcrJsonUtils.writeTimeProperties(generator, c);
+                       if (featureAdapter != null)
+                               featureAdapter.writeProperties(generator, c, typeName);
+                       else
+                               writeProperties(generator, c);
+                       generator.writeEnd();// properties object
+
+                       generator.writeEnd();// feature object
+
+                       if (count.incrementAndGet() % 10 == 0)
+                               try {
+                                       out.flush();
+                               } catch (IOException e) {
+                                       throw new UncheckedIOException(e);
+                               }
+               });
+               generator.writeEnd();// features array
+               generator.writeEnd().close();
+
+               log.debug("GeoJSon encoding took " + (System.currentTimeMillis() - begin) + " ms.");
+       }
+
+       protected Geometry getDefaultGeometry(Content content) {
+               if (content.hasContentClass(EntityType.geopoint)) {
+                       return GeoEntityUtils.toPoint(content);
+               }
+               return null;
+       }
+
+       protected String getFeatureId(Content content) {
+               String uuid = content.attr(LdapAttr.entryUUID);
+               return uuid;
+       }
+
+       public void writeProperties(JsonGenerator generator, Content content) {
+               String path = content.getPath();
+               generator.write("path", path);
+               if (content.hasContentClass(EntityType.local)) {
+                       String type = content.attr(EntityName.type);
+                       generator.write("type", type);
+               } else {
+                       List<QName> contentClasses = content.getContentClasses();
+                       if (!contentClasses.isEmpty()) {
+                               generator.write("type", NamespaceUtils.toPrefixedName(contentClasses.get(0)));
+                       }
+               }
+
+       }
+
+       protected void encodeCollectionAsGML(Stream<Content> features, OutputStream out) throws IOException {
+               String entityType = "entity";
+               URL schemaLocation = getClass().getResource("/org/argeo/app/api/entity.xsd");
+               String namespace = "http://www.argeo.org/ns/entity";
+
+               GML gml = new GML(Version.WFS1_1);
+               gml.setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
+               gml.setNamespace("local", namespace);
+
+               SimpleFeatureType featureType = gml.decodeSimpleFeatureType(schemaLocation,
+                               new NameImpl(namespace, entityType + "Feature"));
+
+//             CoordinateReferenceSystem crs=DefaultGeographicCRS.WGS84;
+//             QName featureName = new QName(namespace,"apafFieldFeature");
+//             GMLConfiguration configuration = new GMLConfiguration();
+//             FeatureType parsed = GTXML.parseFeatureType(configuration, featureName, crs);
+//             SimpleFeatureType featureType = DataUtilities.simple(parsed);
+
+               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
+
+               DefaultFeatureCollection featureCollection = new DefaultFeatureCollection();
+
+               features.forEach((c) -> {
+//                     boolean gpx = false;
+                       Geometry the_geom = null;
+                       Polygon the_area = null;
+//                     if (gpx) {
+                       Content area = c.getContent("gpx/area.gpx").orElse(null);
+                       if (area != null) {
+
+                               try (InputStream in = area.open(InputStream.class)) {
+                                       the_area = GpxUtils.parseGpxTrackTo(in, Polygon.class);
+                               } catch (IOException e) {
+                                       throw new UncheckedIOException("Cannot parse " + c, e);
+                               }
+                       }
+//                     } else {
+                       if (c.hasContentClass(EntityType.geopoint)) {
+                               double latitude = c.get(WGS84PosName.lat, Double.class).get();
+                               double longitude = c.get(WGS84PosName.lon, Double.class).get();
+
+                               Coordinate coordinate = new Coordinate(longitude, latitude);
+                               the_geom = JTS.GEOMETRY_FACTORY.createPoint(coordinate);
+                       }
+
+//                     }
+                       if (the_geom != null)
+                               featureBuilder.set(new NameImpl(namespace, "geopoint"), the_geom);
+                       if (the_area != null)
+                               featureBuilder.set(new NameImpl(namespace, "area"), the_area);
+
+                       List<AttributeDescriptor> attrDescs = featureType.getAttributeDescriptors();
+                       for (AttributeDescriptor attrDesc : attrDescs) {
+                               if (attrDesc instanceof GeometryAttribute)
+                                       continue;
+                               Name name = attrDesc.getName();
+                               QName qName = new QName(name.getNamespaceURI(), name.getLocalPart());
+                               String value = c.attr(qName);
+                               if (value == null) {
+                                       value = c.attr(name.getLocalPart());
+                               }
+                               if (value != null) {
+                                       featureBuilder.set(name, value);
+                               }
+                       }
+
+                       String uuid = c.attr(LdapAttr.entryUUID);
+
+                       SimpleFeature feature = featureBuilder.buildFeature(uuid);
+                       featureCollection.add(feature);
+
+               });
+               gml.encode(out, featureCollection);
+               out.close();
+
+       }
+
+       /*
+        * DEPENDENCY INJECTION
+        */
+
+       public void addFeatureAdapter(FeatureAdapter featureAdapter, Map<String, Object> properties) {
+               List<String> typeNames = LangUtils.toStringList(properties.get(WfsKvp.TYPE_NAMES.getKey()));
+               if (typeNames.isEmpty()) {
+                       log.warn("FeatureAdapter " + featureAdapter.getClass() + " does not declare type names. Ignoring it...");
+                       return;
+               }
+
+               for (String tn : typeNames) {
+                       QName typeName = NamespaceUtils.parsePrefixedName(tn);
+                       featureAdapters.put(typeName, featureAdapter);
+               }
+       }
+
+       public void removeFeatureAdapter(FeatureAdapter featureAdapter, Map<String, Object> properties) {
+               List<String> typeNames = LangUtils.toStringList(properties.get(WfsKvp.TYPE_NAMES.getKey()));
+               if (!typeNames.isEmpty()) {
+                       // ignore if noe type name declared
+                       return;
+               }
+
+               for (String tn : typeNames) {
+                       QName typeName = NamespaceUtils.parsePrefixedName(tn);
+                       featureAdapters.remove(typeName);
+               }
+       }
+
+       public void setContentRepository(ProvidedRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsUtils.java
new file mode 100644 (file)
index 0000000..f9876d9
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.app.geo.http;
+
+import javax.xml.namespace.NamespaceContext;
+
+/** Utilities around the WFS specifications. */
+public class WfsUtils {
+
+       public static NamespaceContext parseNamespacesKvpParameter() {
+               // TODO deal with multiple namespaces
+               return null;
+       }
+
+       /** singleton */
+       private WfsUtils() {
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java
new file mode 100644 (file)
index 0000000..63b9351
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.app.geo.ux;
+
+import org.argeo.app.ux.js.AbstractJsObject;
+
+public class AbstractGeoJsObject extends AbstractJsObject {
+       public final static String ARGEO_APP_GEO_JS_URL = "/pkg/org.argeo.app.js/geo.html";
+       public final static String JS_PACKAGE = "argeo.app.geo";
+
+       public AbstractGeoJsObject(Object... args) {
+               super(args);
+       }
+
+       @Override
+       public String getJsPackage() {
+               return JS_PACKAGE;
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/BboxVectorSource.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/BboxVectorSource.java
new file mode 100644 (file)
index 0000000..fa0656c
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.app.geo.ux;
+
+import org.argeo.app.ol.FeatureFormat;
+import org.argeo.app.ol.VectorSource;
+
+public class BboxVectorSource extends VectorSource {
+
+       public BboxVectorSource(Object... args) {
+               super(args);
+       }
+
+       public BboxVectorSource(String baseUrl, FeatureFormat format) {
+               setFormat(format);
+               setBaseUrl(baseUrl);
+       }
+
+       @Override
+       public String getJsPackage() {
+               return AbstractGeoJsObject.JS_PACKAGE;
+       }
+
+       public void setBaseUrl(String baseUrl) {
+               doSetValue(null, "baseUrl", baseUrl);
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/MapPart.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/MapPart.java
new file mode 100644 (file)
index 0000000..8d5da7e
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.app.geo.ux;
+
+/** An UX part displaying a map. */
+public interface MapPart {
+       void setCenter(double lng, double lat);
+
+       /** Event when a feature has been single-clicked. */
+       record FeatureSingleClickEvent(String path) {
+       };
+
+       /** Event when a feature has been selected. */
+       record FeatureSelectedEvent(String path) {
+       };
+
+       /** Event when a feature popup is requested. */
+       record FeaturePopupEvent(String path) {
+       };
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java
new file mode 100644 (file)
index 0000000..0d99f40
--- /dev/null
@@ -0,0 +1,107 @@
+package org.argeo.app.geo.ux;
+
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.argeo.app.ol.AbstractOlObject;
+import org.argeo.app.ol.Layer;
+import org.argeo.app.ol.OlMap;
+import org.argeo.app.ol.TileLayer;
+import org.argeo.app.ol.VectorLayer;
+import org.argeo.app.ux.js.JsClient;
+import org.locationtech.jts.geom.Envelope;
+
+/**
+ * A wrapper around an OpenLayers map, adding specific features, such as SLD
+ * styling.
+ */
+public class OpenLayersMapPart extends AbstractGeoJsObject implements MapPart {
+       private final String mapPartName;
+
+       public OpenLayersMapPart(JsClient jsClient, String mapPartName) {
+               super(mapPartName);
+               this.mapPartName = mapPartName;
+               create(jsClient, mapPartName);
+       }
+
+       public OlMap getMap() {
+               return new OlMap(getJsClient(), getReference() + ".getMap()");
+       }
+
+       public void setSld(String xml) {
+               executeMethod(getMethodName(), JsClient.escapeQuotes(xml));
+       }
+
+       public void setCenter(double lat, double lon) {
+               executeMethod(getMethodName(), lat, lon);
+       }
+
+       public void fit(double[] extent, Map<String, Object> options) {
+               executeMethod(getMethodName(), extent, options);
+       }
+
+       public void fit(Envelope extent, Map<String, Object> options) {
+               fit(new double[] { extent.getMinX(), extent.getMinY(), extent.getMaxX(), extent.getMaxY() }, options);
+       }
+
+       public void applyStyle(String layerName, String styledLayerName) {
+               executeMethod(getMethodName(), layerName, styledLayerName);
+       }
+
+       public Layer getLayer(String name) {
+               // TODO deal with not found
+               String reference = getReference() + ".getLayerByName('" + name + "')";
+               if (getJsClient().isInstanceOf(reference, AbstractOlObject.getJsClassName(VectorLayer.class))) {
+                       return new VectorLayer(getJsClient(), reference);
+               } else if (getJsClient().isInstanceOf(reference, AbstractOlObject.getJsClassName(TileLayer.class))) {
+                       return new TileLayer(getJsClient(), reference);
+               } else {
+                       return new Layer(getJsClient(), reference);
+               }
+       }
+
+       public String getMapPartName() {
+               return mapPartName;
+       }
+
+       public void selectFeatures(String layerName, Object... ids) {
+               executeMethod(getMethodName(), layerName, (Object[]) ids);
+       }
+
+       public void fitToLayer(String layerName) {
+               executeMethod(getMethodName(), layerName);
+       }
+
+       /*
+        * CALLBACKS
+        */
+       public void onFeatureSelected(Consumer<FeatureSelectedEvent> toDo) {
+               addCallback("FeatureSelected", (arr) -> {
+                       toDo.accept(new FeatureSelectedEvent((String) arr[0]));
+                       return null;
+               });
+       }
+
+       public void onFeatureSingleClick(Consumer<FeatureSingleClickEvent> toDo) {
+               addCallback("FeatureSingleClick", (arr) -> {
+                       toDo.accept(new FeatureSingleClickEvent((String) arr[0]));
+                       return null;
+               });
+       }
+
+       public void onFeaturePopup(Function<FeaturePopupEvent, String> toDo) {
+               addCallback("FeaturePopup", (arr) -> {
+                       return toDo.apply(new FeaturePopupEvent((String) arr[0]));
+               });
+       }
+
+       protected void addCallback(String suffix, Function<Object[], Object> toDo) {
+               getJsClient().getReadyStage().thenAccept((ready) -> {
+                       String functionName = getJsClient().createJsFunction(getMapPartName() + "__on" + suffix, toDo);
+                       getJsClient().execute(getJsReference() + ".callbacks['on" + suffix + "']=" + functionName + ";");
+                       executeMethod("enable" + suffix);
+               });
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java
new file mode 100644 (file)
index 0000000..36dcfe1
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.app.geo.ux;
+
+import org.argeo.app.ol.Source;
+
+public class SentinelCloudless extends Source {
+
+       public SentinelCloudless(Object... args) {
+               super(args);
+       }
+
+       @Override
+       public String getJsPackage() {
+               return "argeo.app.geo";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java b/org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java
new file mode 100644 (file)
index 0000000..db64d96
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.app.ol;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.argeo.app.ux.js.AbstractJsObject;
+
+public abstract class AbstractOlObject extends AbstractJsObject {
+       public final static String JS_PACKAGE = "argeo.tp.ol";
+
+       public AbstractOlObject(Object... args) {
+               super(args.length > 0 ? args : new Object[] { new HashMap<String, Object>() });
+       }
+
+//     public AbstractOlObject(Map<String, Object> options) {
+//             super(new Object[] { options });
+//     }
+
+       public String getJsPackage() {
+               return JS_PACKAGE;
+       }
+
+       @SuppressWarnings("unchecked")
+       protected Map<String, Object> getNewOptions() {
+               if (!isNew())
+                       throw new IllegalStateException("Object " + getJsClassName() + " is not new");
+               Object[] args = getJsConstructorArgs();
+               if (args.length != 1 || !(args[0] instanceof Map))
+                       throw new IllegalStateException("Object " + getJsClassName() + " has no available options");
+               return (Map<String, Object>) args[0];
+       }
+
+       protected void doSetValue(String methodName, String newOption, Object value) {
+               if (isNew()) {
+                       Objects.requireNonNull(newOption, "Value cannot be set as an option for " + getJsClassName() + ", use "
+                                       + methodName + "() after the object has been created");
+                       getNewOptions().put(newOption, value);
+               } else {
+                       Objects.requireNonNull(methodName, "Value cannot be set via a method for " + getJsClassName() + ", use "
+                                       + newOption + " before the object is created");
+                       executeMethod(methodName, value);
+               }
+       }
+
+       public void set(String key, Object value) {
+               set(key, value, false);
+       }
+
+       public void set(String key, Object value, boolean silent) {
+               if (isNew()) {
+                       getNewOptions().put(key, value);
+               } else {
+                       executeMethod(getMethodName(), new Object[] { key, value, silent });
+               }
+       }
+
+       public Object get(String key) {
+               if (isNew()) {
+                       return getNewOptions().get(key);
+               } else {
+                       // TDO deal with reference if we are trying to get an object
+                       return callMethod(getMethodName(), key);
+               }
+
+       }
+
+       public static String getJsClassName(Class<?> clss) {
+               if (AbstractOlObject.class.isAssignableFrom(clss)) {
+                       // NB: would failed for renamed classes
+                       return JS_PACKAGE + "." + clss.getSimpleName();
+               }
+               throw new IllegalArgumentException(clss + " is not an OpenLayers object");
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/FeatureFormat.java b/org.argeo.app.geo/src/org/argeo/app/ol/FeatureFormat.java
new file mode 100644 (file)
index 0000000..2d0f8bc
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.app.ol;
+
+public abstract class FeatureFormat extends AbstractOlObject {
+
+       public FeatureFormat(Object... args) {
+               super(args);
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/GeoJSON.java b/org.argeo.app.geo/src/org/argeo/app/ol/GeoJSON.java
new file mode 100644 (file)
index 0000000..3cab7bc
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.app.ol;
+
+public class GeoJSON extends FeatureFormat {
+
+       public GeoJSON(Object... args) {
+               super(args);
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/Layer.java b/org.argeo.app.geo/src/org/argeo/app/ol/Layer.java
new file mode 100644 (file)
index 0000000..feb0700
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.app.ol;
+
+import java.util.Objects;
+
+public class Layer extends AbstractOlObject {
+       public final static String NAME_KEY = "name";
+
+       // cached
+       private String name;
+
+       public Layer(Object... args) {
+               super(args);
+       }
+
+       public void setOpacity(double opacity) {
+               if (opacity < 0 || opacity > 1)
+                       throw new IllegalArgumentException("Opacity must be between 0 and 1");
+//             if (isNew())
+//                     getNewOptions().put("opacity", opacity);
+//             else
+//                     executeMethod(getMethodName(), opacity);
+               doSetValue(getMethodName(), "opacity", opacity);
+       }
+
+       public void setSource(Source source) {
+               Objects.requireNonNull(source);
+               if (isNew())
+                       getNewOptions().put("source", source);
+               else
+                       executeMethod(getMethodName(), source);
+       }
+
+       public Source getSource() {
+               String reference = getReference() + ".getSource()";
+               return new Source(getJsClient(), reference);
+       }
+
+       public void setMinResolution(double minResolution) {
+               if (isNew())
+                       getNewOptions().put("minResolution", minResolution);
+               else
+                       executeMethod(getMethodName(), minResolution);
+       }
+
+       public void setMaxResolution(double maxResolution) {
+               if (isNew())
+                       getNewOptions().put("maxResolution", maxResolution);
+               else
+                       executeMethod(getMethodName(), maxResolution);
+       }
+
+       public void setName(String name) {
+               set(NAME_KEY, name);
+               this.name = name;
+       }
+
+       public String getName() {
+               return name;
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/OSM.java b/org.argeo.app.geo/src/org/argeo/app/ol/OSM.java
new file mode 100644 (file)
index 0000000..6cc769f
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.app.ol;
+
+public class OSM extends Source {
+
+       public OSM(Object... args) {
+               super(args);
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java b/org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java
new file mode 100644 (file)
index 0000000..6ca37e3
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.app.ol;
+
+public class OlMap extends AbstractOlObject {
+
+       public OlMap(Object... args) {
+               super(args);
+       }
+
+       public void addLayer(Layer layer) {
+               executeMethod(getMethodName(), layer);
+       }
+
+       public View getView() {
+               return new View(getJsClient(), getReference() + ".getView()");
+       }
+
+       @Override
+       public String getJsClassName() {
+               return getJsPackage() + ".Map";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/Source.java b/org.argeo.app.geo/src/org/argeo/app/ol/Source.java
new file mode 100644 (file)
index 0000000..541353e
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.app.ol;
+
+public class Source extends AbstractOlObject {
+
+       public Source(Object... args) {
+               super(args);
+       }
+
+       public void refresh() {
+               executeMethod(getMethodName());
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java b/org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java
new file mode 100644 (file)
index 0000000..32f58ac
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.app.ol;
+
+public class TileLayer extends Layer {
+       public TileLayer(Object... args) {
+               super(args);
+       }
+
+       public TileLayer(Source source) {
+               setSource(source);
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/VectorLayer.java b/org.argeo.app.geo/src/org/argeo/app/ol/VectorLayer.java
new file mode 100644 (file)
index 0000000..5a4b6b4
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.app.ol;
+
+public class VectorLayer extends Layer {
+       public VectorLayer(Object... args) {
+               super(args);
+       }
+
+       public VectorLayer(String name, Source source) {
+               this(source);
+               setName(name);
+       }
+
+       public VectorLayer(Source source) {
+               setSource(source);
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/VectorSource.java b/org.argeo.app.geo/src/org/argeo/app/ol/VectorSource.java
new file mode 100644 (file)
index 0000000..3b60d0b
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.app.ol;
+
+public class VectorSource extends Source {
+
+       public VectorSource(Object... args) {
+               super(args);
+       }
+
+       public VectorSource(String url, FeatureFormat format) {
+               setUrl(url);
+               setFormat(format);
+       }
+
+       public void setUrl(String url) {
+               doSetValue(getMethodName(), "url", url);
+       }
+
+       public void setFormat(FeatureFormat format) {
+               doSetValue(null, "format", format);
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/View.java b/org.argeo.app.geo/src/org/argeo/app/ol/View.java
new file mode 100644 (file)
index 0000000..eab7a38
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.app.ol;
+
+public class View extends AbstractOlObject {
+       public View(Object... args) {
+               super(args);
+       }
+
+       public void setCenter(int[] coord) {
+               if (isNew())
+                       getNewOptions().put("center", coord);
+               else
+                       executeMethod(getMethodName(), new Object[] { coord });
+       }
+
+       public void setZoom(int zoom) {
+               if (isNew())
+                       getNewOptions().put("zoom", zoom);
+               else
+                       executeMethod(getMethodName(), zoom);
+       }
+
+//     public void fit(double[] extent) {
+//             executeMethod(getMethodName(), extent);
+//     }
+//     public void setProjection(String projection) {
+//             doSetValue(getMethodName(), "projection", projection);
+//     }
+}
index e76315c8d3106f65afc2dec9389831ff7808da6f..8795d02d7f63c341192455697feaa31e1c42d97a 100644 (file)
@@ -2,7 +2,6 @@ package org.argeo.app.jcr;
 
 import java.util.Map;
 
-import javax.jcr.Node;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -14,11 +13,12 @@ import org.argeo.jcr.Jcr;
 import org.osgi.framework.BundleContext;
 
 /** An entity definition based on a JCR data structure. */
+@Deprecated
 public class JcrEntityDefinition implements EntityDefinition {
        private Repository repository;
 
        private String type;
-       private String defaultEditorId;
+//     private String defaultEditorId;
 
        public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
                Session adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
@@ -26,7 +26,7 @@ public class JcrEntityDefinition implements EntityDefinition {
                        type = properties.get(EntityConstants.TYPE);
                        if (type == null)
                                throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
-                       defaultEditorId = properties.get(EntityConstants.DEFAULT_EDITOR_ID);
+//                     defaultEditorId = properties.get(EntityConstants.DEFAULT_EDITOR_ID);
 //                     String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
 //                     if (!adminSession.itemExists(definitionPath)) {
 //                             Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
@@ -48,10 +48,10 @@ public class JcrEntityDefinition implements EntityDefinition {
 
        }
 
-       @Override
-       public String getEditorId(Node entity) {
-               return defaultEditorId;
-       }
+//     @Override
+//     public String getEditorId(Node entity) {
+//             return defaultEditorId;
+//     }
 
        @Override
        public String getType() {
index 6e145c2b951fb0f87013e3887b29258a181973e9..de88a232fc57db372c6f9e62f3ba918415e07ae1 100644 (file)
@@ -30,6 +30,9 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.io.output.NullOutputStream;
 import org.argeo.app.api.EntityMimeType;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.WGS84PosName;
+import org.argeo.app.geo.GeoShapeUtils;
 import org.argeo.app.odk.OrxManifestName;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.ServletHttpRequest;
@@ -142,6 +145,7 @@ public class OdkManifestServlet extends HttpServlet {
                        }
                        // TODO make it more configurable
                        columnNames.add("display");
+                       columnNames.add("geometry");
 
                        if (EntityMimeType.XML.equals(mimeType)) {
                        } else if (EntityMimeType.CSV.equals(mimeType)) {
@@ -158,6 +162,16 @@ public class OdkManifestServlet extends HttpServlet {
                                                }
                                                // display
                                                lst.add(row.getValue("name").getString() + " (" + row.getValue("label").getString() + ")");
+                                               Node field = row.getNode("geopoint");
+                                               if (field != null && field.isNodeType(EntityType.geopoint.get())) {
+                                                       double lat = field.getProperty(WGS84PosName.lat.get()).getDouble();
+                                                       double lon = field.getProperty(WGS84PosName.lon.get()).getDouble();
+                                                       double alt = field.hasProperty(WGS84PosName.alt.get())
+                                                                       ? field.getProperty(WGS84PosName.alt.get()).getDouble()
+                                                                       : Double.NaN;
+                                                       String geoshape = GeoShapeUtils.geoPointToGeoShape(lon, lat, alt);
+                                                       lst.add(geoshape);
+                                               }
                                                csvWriter.writeLine(lst);
                                        }
                                } else {
index 3ef414bdd658e3aee2a7775570a361293c6872ae..da79b5ef924f786b52bf866ccb8a453e84d83d75 100644 (file)
@@ -39,13 +39,11 @@ public class OdkSubmissionServlet extends HttpServlet {
        private final static CmsLog log = CmsLog.getLog(OdkSubmissionServlet.class);
 
        private final static String XML_SUBMISSION_FILE = "xml_submission_file";
+       private final static String IS_INCOMPLETE = "*isIncomplete*";
 
        private DateTimeFormatter submissionNameFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmmssSSS")
                        .withZone(ZoneId.from(ZoneOffset.UTC));
 
-//     private Repository repository;
-//     private ContentRepository contentRepository;
-
        private Set<FormSubmissionListener> submissionListeners = new HashSet<>();
 
        private AppUserState appUserState;
@@ -56,30 +54,18 @@ public class OdkSubmissionServlet extends HttpServlet {
                resp.setHeader("X-OpenRosa-Version", "1.0");
                resp.setDateHeader("Date", System.currentTimeMillis());
 
-               // should be set in HEAD? Let's rather use defaults.
-               // resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024);
-
                RemoteAuthRequest request = new ServletHttpRequest(req);
-//             Session session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), request);
-//
                CmsSession cmsSession = RemoteAuthUtils.getCmsSession(request);
 
-//             Session adminSession = null;
-//             try {
-//                     // TODO centralise at a deeper level
-//                     adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-//                     SuiteJcrUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
-//             } finally {
-//                     Jcr.logout(adminSession);
-//             }
-
+               boolean isIncomplete = false;
                try {
                        Content sessionDir = appUserState.getOrCreateSessionDir(cmsSession);
                        Node cmsSessionNode = sessionDir.adapt(Node.class);
-                       // Node cmsSessionNode = SuiteJcrUtils.getCmsSessionNode(session, cmsSession);
-                       Node submission = cmsSessionNode.addNode(submissionNameFormatter.format(Instant.now()),
-                                       OrxType.submission.get());
+                       String submissionName = submissionNameFormatter.format(Instant.now());
+                       Node submission = cmsSessionNode.addNode(submissionName, OrxType.submission.get());
+                       String submissionPath = submission.getPath();
                        for (Part part : req.getParts()) {
+                               String partNameSane = JcrUtils.replaceInvalidChars(part.getName());
                                if (log.isTraceEnabled())
                                        log.trace("Part: " + part.getName() + ", " + part.getContentType());
 
@@ -88,6 +74,9 @@ public class OdkSubmissionServlet extends HttpServlet {
                                        cmsSessionNode.getSession().importXML(xml.getPath(), part.getInputStream(),
                                                        ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
 
+                               } else if (part.getName().equals(IS_INCOMPLETE)) {
+                                       isIncomplete = true;
+                                       log.debug("Form submission " + submissionPath + " is incomplete, expecting more to be uploaded...");
                                } else {
                                        Node fileNode;
                                        if (part.getName().endsWith(".jpg")) {
@@ -97,19 +86,17 @@ public class OdkSubmissionServlet extends HttpServlet {
                                                        ImageProcessor imageProcessor = new ImageProcessor(() -> part.getInputStream(),
                                                                        () -> Files.newOutputStream(temp));
                                                        imageProcessor.process();
-                                                       fileNode = JcrUtils.copyStreamAsFile(submission, part.getName(),
-                                                                       Files.newInputStream(temp));
+                                                       fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, Files.newInputStream(temp));
                                                } finally {
                                                        Files.deleteIfExists(temp);
                                                }
                                        } else {
-                                               fileNode = JcrUtils.copyStreamAsFile(submission, part.getName(), part.getInputStream());
+                                               fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, part.getInputStream());
                                        }
                                        String contentType = part.getContentType();
                                        if (contentType != null) {
                                                fileNode.addMixin(NodeType.MIX_MIMETYPE);
                                                fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
-
                                        }
                                        if (part.getName().endsWith(".jpg") || part.getName().endsWith(".png")) {
                                                // TODO meta data and thumbnails
@@ -120,22 +107,23 @@ public class OdkSubmissionServlet extends HttpServlet {
                        cmsSessionNode.getSession().save();
                        try {
                                for (FormSubmissionListener submissionListener : submissionListeners) {
-                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission));
+                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission), isIncomplete);
                                }
                        } catch (Exception e) {
-                               log.error("Cannot save submision, cancelling...", e);
-                               submission.remove();
+                               log.error("Cannot save submission, cancelling...", e);
+                               if (cmsSessionNode.getSession().hasPendingChanges())
+                                       cmsSessionNode.getSession().refresh(false);// discard
+                               if (cmsSessionNode.getSession().itemExists(submissionPath))
+                                       submission.remove();
                                cmsSessionNode.getSession().save();
                                resp.setStatus(503);
                                return;
                        }
 
                } catch (Exception e) {
-                       log.error("Cannot save submision", e);
+                       log.error("Cannot save submission", e);
                        resp.setStatus(503);
                        return;
-//             } finally {
-//                     Jcr.logout(session);
                }
 
                resp.setStatus(201);
@@ -144,10 +132,6 @@ public class OdkSubmissionServlet extends HttpServlet {
 
        }
 
-//     public void setRepository(Repository repository) {
-//             this.repository = repository;
-//     }
-
        public synchronized void addSubmissionListener(FormSubmissionListener listener) {
                submissionListeners.add(listener);
        }
@@ -156,10 +140,6 @@ public class OdkSubmissionServlet extends HttpServlet {
                submissionListeners.remove(listener);
        }
 
-//     public void setContentRepository(ContentRepository contentRepository) {
-//             this.contentRepository = contentRepository;
-//     }
-
        public void setAppUserState(AppUserState appUserState) {
                this.appUserState = appUserState;
        }
index b389883a23e6de2223cd17c9cc4cd88992502aa3..a4e07ba90a7f27a52c0391f3ffa8241588a129d7 100644 (file)
@@ -40,15 +40,16 @@ import org.apache.xmlgraphics.io.ResourceResolver;
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentRepository;
 import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.app.geo.GeoUtils;
 import org.argeo.app.geo.GpxUtils;
+import org.argeo.app.geo.acr.GeoEntityUtils;
 import org.argeo.cms.acr.xml.XmlNormalizer;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.ServletHttpRequest;
 import org.argeo.cms.util.LangUtils;
-import org.geotools.data.collection.ListFeatureCollection;
-import org.geotools.data.simple.SimpleFeatureCollection;
-import org.opengis.feature.simple.SimpleFeature;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.Polygon;
 
 import net.sf.saxon.BasicTransformerFactory;
 
@@ -109,21 +110,35 @@ public class FopServlet extends HttpServlet {
                                                return new StreamSource(in);
                                        }
                                        if (url.getScheme().equals("geo2svg")) {
-                                               String includePath = path + url.getPath();
-                                               String geoExt = includePath.substring(includePath.lastIndexOf('.'));
-                                               Content geoContent = session.get(includePath);
-                                               if (".gpx".equals(geoExt)) {
-                                                       try (InputStream in = geoContent.open(InputStream.class)) {
-                                                               SimpleFeature field = GpxUtils.parseGpxToPolygon(in);
-                                                               SimpleFeatureCollection features = new ListFeatureCollection(field.getType(), field);
-                                                               try (StringWriter writer = new StringWriter()) {
-                                                                       GeoUtils.exportToSvg(features, writer, 100, 100);
-                                                                       StreamSource res = new StreamSource(new StringReader(writer.toString()));
-                                                                       return res;
+                                               int lastDot = url.getPath().lastIndexOf('.');
+                                               Polygon polygon;
+                                               if (lastDot > 0) {
+                                                       String includePath = path + url.getPath();
+                                                       Content geoContent = session.get(includePath);
+                                                       String geoExt = includePath.substring(lastDot);
+                                                       if (".gpx".equals(geoExt)) {
+                                                               try (InputStream in = geoContent.open(InputStream.class)) {
+                                                                       polygon = GpxUtils.parseGpxTrackTo(in, Polygon.class);
                                                                }
+                                                       } else {
+                                                               throw new UnsupportedOperationException(geoExt + " is not supported");
                                                        }
                                                } else {
-                                                       throw new UnsupportedOperationException(geoExt + " is not supported");
+                                                       Content geoContent;
+                                                       String attrName;
+                                                       if (url.getPath().startsWith("/@")) {
+                                                               geoContent = content;
+                                                               attrName = url.getPath().substring(2);// remove /@
+                                                       } else {
+                                                               throw new IllegalArgumentException("Only direct attributes are currently supported");
+                                                       }
+                                                       polygon = GeoEntityUtils.getGeometry(geoContent, NamespaceUtils.parsePrefixedName(attrName),
+                                                                       Polygon.class);
+                                               }
+                                               try (StringWriter writer = new StringWriter()) {
+                                                       GeoUtils.exportToSvg(new Geometry[] { polygon }, writer, 100, 100);
+                                                       StreamSource res = new StreamSource(new StringReader(writer.toString()));
+                                                       return res;
                                                }
                                        }
                                }
diff --git a/org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/GeoToSvgServlet.java b/org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/GeoToSvgServlet.java
deleted file mode 100644 (file)
index c22b834..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.app.servlet.publish;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.ContentRepository;
-import org.argeo.api.acr.ContentSession;
-import org.argeo.app.geo.GeoUtils;
-import org.argeo.app.geo.GpxUtils;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.geotools.data.collection.ListFeatureCollection;
-import org.geotools.data.simple.SimpleFeatureCollection;
-import org.opengis.feature.simple.SimpleFeature;
-
-/**
- * A servlet transforming an geographical data to SVG.
- */
-public class GeoToSvgServlet extends HttpServlet {
-       private static final long serialVersionUID = -6346379324580671894L;
-       private ContentRepository contentRepository;
-
-       @Override
-       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               String servletPath = req.getServletPath();
-
-               servletPath = servletPath.substring(1, servletPath.lastIndexOf('.'));
-               servletPath = servletPath.substring(servletPath.indexOf('/'), servletPath.length());
-               String path = URLDecoder.decode(servletPath, StandardCharsets.UTF_8);
-               String ext = servletPath.substring(path.lastIndexOf('.'));
-
-               resp.setContentType("image/svg+xml");
-
-               ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(), new ServletHttpRequest(req));
-               Content content = session.get(path);
-               if (".gpx".equals(ext)) {
-                       try (InputStream in = content.open(InputStream.class)) {
-                               SimpleFeature field = GpxUtils.parseGpxToPolygon(in);
-
-                               SimpleFeatureCollection features = new ListFeatureCollection(field.getType(), field);
-                               GeoUtils.exportToSvg(features, resp.getWriter(), 100, 100);
-//                     log.debug("SVG:\n" + writer.toString() + "\n");
-                       }
-               }
-       }
-
-       public void start(Map<String, Object> properties) {
-       }
-
-       public void stop(Map<String, Object> properties) {
-
-       }
-
-       public void setContentRepository(ContentRepository contentRepository) {
-               this.contentRepository = contentRepository;
-       }
-
-}
index d9cae87d811258d5a13e43eea8492f3792377ce4..d5943f556d6fba9db0dd63d4c4cfceef89e4888e 160000 (submodule)
@@ -1 +1 @@
-Subproject commit d9cae87d811258d5a13e43eea8492f3792377ce4
+Subproject commit d5943f556d6fba9db0dd63d4c4cfceef89e4888e
index e541ba443a1150d1dc6d1480a5a73bbc8df88b46..8ada85496c393f7425c36412df57171b8f51dc37 100644 (file)
@@ -7,6 +7,8 @@ org.argeo.cms,\
 org.argeo.cms.swt.rcp,\
 org.argeo.cms.ee,\
 org.argeo.cms.lib.dbus,\
+org.argeo.cms.lib.equinox,\
+org.argeo.cms.lib.jetty,\
 
 argeo.osgi.start.4=\
 org.argeo.cms.jcr
@@ -14,8 +16,10 @@ org.argeo.cms.jcr
 argeo.osgi.start.5=\
 org.argeo.app.profile.acr.fs,\
 org.argeo.app.core,\
+org.argeo.app.jcr,\
 org.argeo.app.ui,\
 org.argeo.app.theme.default,\
+org.argeo.app.geo,\
 
 
 # Local
index a6003969c41a97f341d3ec5eb20d9fcbfda59273..a9b32712fc38c65569907df44d0d01ded1e21cc4 100644 (file)
@@ -39,6 +39,8 @@ log.org.argeo=DEBUG
 # DON'T CHANGE BELOW
 org.eclipse.equinox.http.jetty.autostart=false
 org.osgi.framework.system.packages.extra=\
+sun.security.internal.spec,\
+sun.security.provider,\
 com.sun.net.httpserver,\
 com.sun.jndi.ldap,\
 com.sun.jndi.ldap.sasl,\
index f9e03723973b759a460aae9d550d4767bf26351b..172581f99184fa93fcd58ee3659f8554bc299cfe 100644 (file)
@@ -1,6 +1,6 @@
 major=2
 minor=3
-micro=15
+micro=19
 qualifier=
 
 Bundle-Copyright= \
@@ -9,4 +9,4 @@ Copyright 2017-2023 Mathieu Baudier
 
 SPDX-License-Identifier= \
 GPL-2.0-or-later \
-OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-JCR-permissions
+OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-Apache-and-JCR-permissions
diff --git a/swt/org.argeo.app.geo.swt/.classpath b/swt/org.argeo.app.geo.swt/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/swt/org.argeo.app.geo.swt/.project b/swt/org.argeo.app.geo.swt/.project
new file mode 100644 (file)
index 0000000..cf7ca92
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.geo.swt</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/org.argeo.app.geo.swt/.settings/org.eclipse.core.resources.prefs b/swt/org.argeo.app.geo.swt/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..99f26c0
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/swt/org.argeo.app.geo.swt/.settings/org.eclipse.jdt.core.prefs b/swt/org.argeo.app.geo.swt/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..62ef348
--- /dev/null
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/swt/org.argeo.app.geo.swt/.settings/org.eclipse.pde.core.prefs b/swt/org.argeo.app.geo.swt/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..f29e940
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/swt/org.argeo.app.geo.swt/bnd.bnd b/swt/org.argeo.app.geo.swt/bnd.bnd
new file mode 100644 (file)
index 0000000..f8e867c
--- /dev/null
@@ -0,0 +1,7 @@
+Import-Package:\
+org.eclipse.swt,\
+org.argeo.cms.acr,\
+*
+
+Provide-Capability:\
+cms.publish;pkg=org.argeo.app.geo.swt.openlayers;file="*.png,*.js,*.css"
diff --git a/swt/org.argeo.app.geo.swt/build.properties b/swt/org.argeo.app.geo.swt/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java
new file mode 100644 (file)
index 0000000..283cbce
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.app.geo.swt;
+
+import org.argeo.api.acr.Content;
+import org.argeo.app.geo.ux.AbstractGeoJsObject;
+import org.argeo.app.geo.ux.OpenLayersMapPart;
+import org.argeo.app.geo.ux.SentinelCloudless;
+import org.argeo.app.ol.GeoJSON;
+import org.argeo.app.ol.Layer;
+import org.argeo.app.ol.OSM;
+import org.argeo.app.ol.TileLayer;
+import org.argeo.app.ol.VectorLayer;
+import org.argeo.app.ol.VectorSource;
+import org.argeo.app.swt.js.SwtBrowserJsPart;
+import org.argeo.app.ux.js.JsClient;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Create map parts. */
+public class MapUiProvider implements SwtUiProvider {
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               JsClient jsClient = new SwtBrowserJsPart(parent, 0, AbstractGeoJsObject.ARGEO_APP_GEO_JS_URL);
+               OpenLayersMapPart mapPart = new OpenLayersMapPart(jsClient, "defaultOverviewMap");
+               mapPart.getMap().getView().setCenter(new int[] { 0, 0 });
+               mapPart.getMap().getView().setZoom(6);
+
+               Layer satelliteLayer = new TileLayer(new SentinelCloudless());
+               satelliteLayer.setMaxResolution(200);
+               mapPart.getMap().addLayer(satelliteLayer);
+
+               TileLayer baseLayer = new TileLayer();
+               baseLayer.setSource(new OSM());
+               baseLayer.setOpacity(0.5);
+               mapPart.getMap().addLayer(baseLayer);
+
+               Layer dataLayer = new VectorLayer(new VectorSource(
+                               "https://openlayers.org/en/v4.6.5/examples/data/geojson/countries.geojson", new GeoJSON()));
+               mapPart.getMap().addLayer(dataLayer);
+
+//             SwtJsMapPart map = new SwtJsMapPart("defaultOverviewMap", parent, 0);
+//             map.setCenter(13.404954, 52.520008); // Berlin
+////           map.setCenter(-74.00597, 40.71427); // NYC
+////           map.addPoint(-74.00597, 40.71427, null);
+//             map.setZoom(6);
+//             // map.addUrlLayer("https://openlayers.org/en/v4.6.5/examples/data/geojson/countries.geojson",
+//             // Format.GEOJSON);
+               return parent;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/AbstractJsChart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/AbstractJsChart.java
new file mode 100644 (file)
index 0000000..8cae325
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.app.swt.chart;
+
+import org.argeo.app.swt.js.SwtBrowserJsPart;
+import org.eclipse.swt.widgets.Composite;
+
+/** Base class for charts. */
+public abstract class AbstractJsChart extends SwtBrowserJsPart {
+       private String chartName;
+
+       protected abstract String getJsImplementation();
+
+       public AbstractJsChart(String chartName, Composite parent, int style) {
+               super(parent, style, "/pkg/org.argeo.app.js/chart.html");
+               this.chartName = chartName;
+       }
+
+       @Override
+       protected void init() {
+               // create chart
+               doExecute(getJsChartVar() + " = new " + getJsImplementation() + "('" + chartName + "');");
+       }
+
+       protected String getJsChartVar() {
+               return getJsVarName(chartName);
+       }
+
+       protected void executeChartMethod(String methodCall, Object... args) {
+               executeMethod(getJsChartVar(), methodCall, args);
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java
new file mode 100644 (file)
index 0000000..bcfb15f
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.app.swt.chart;
+
+import java.io.StringWriter;
+
+import org.argeo.app.ux.js.JsClient;
+import org.eclipse.swt.widgets.Composite;
+
+import jakarta.json.Json;
+import jakarta.json.stream.JsonGenerator;
+
+public class SwtJsBarChart extends AbstractJsChart {
+
+       public SwtJsBarChart(String chartName, Composite parent, int style) {
+               super(chartName, parent, style);
+       }
+
+       @Override
+       protected String getJsImplementation() {
+               return "globalThis.argeo.app.chart.BarChart";
+       }
+
+       public void setLabels(String[] labels) {
+               executeChartMethod("setLabels(%s)", JsClient.toJsArray(labels));
+       }
+
+       public void addDataset(String label, int[] values) {
+               executeChartMethod("addDataset('%s', %s)", label, JsClient.toJsArray(values));
+       }
+
+       public void setData(String[] labels, String label, int[] values) {
+               executeChartMethod("setData(%s, '%s', %s)", JsClient.toJsArray(labels), label, JsClient.toJsArray(values));
+       }
+
+       public void setDatasets(String[] labels, String[] label, int[][] values) {
+               executeChartMethod("setDatasets(%s, %s)", JsClient.toJsArray(labels), toDatasets(label, values));
+       }
+
+       protected String toDatasets(String[] label, int[][] values) {
+               if (label.length != values.length)
+                       throw new IllegalArgumentException("Arrays must have the same length");
+               StringWriter writer = new StringWriter();
+               JsonGenerator g = Json.createGenerator(writer);
+               g.writeStartArray();
+               for (int i = 0; i < label.length; i++) {
+                       g.writeStartObject();
+                       g.write("label", label[i]);
+                       g.writeStartArray("data");
+                       for (int j = 0; j < values[i].length; j++) {
+                               g.write(values[i][j]);
+                       }
+                       g.writeEnd();// data array
+                       g.writeEnd();// dataset
+               }
+               g.writeEnd();
+               g.close();
+               return writer.toString();
+       }
+
+       public void clearDatasets() {
+               executeChartMethod("clearDatasets()");
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java
new file mode 100644 (file)
index 0000000..6782f5d
--- /dev/null
@@ -0,0 +1,194 @@
+package org.argeo.app.swt.js;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Function;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.ux.js.JsClient;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.swt.browser.ProgressEvent;
+import org.eclipse.swt.browser.ProgressListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * A part using a {@link Browser} and remote JavaScript components on the client
+ * side.
+ */
+public class SwtBrowserJsPart implements JsClient {
+       private final static CmsLog log = CmsLog.getLog(SwtBrowserJsPart.class);
+
+       private final static String GLOBAL_THIS_ = "globalThis.";
+
+       private final Browser browser;
+       private final CompletableFuture<Boolean> readyStage = new CompletableFuture<>();
+
+       /**
+        * Tasks that were requested before the context was ready. Typically
+        * configuration methods on the part while the user interfaces is being build.
+        */
+       private List<PreReadyToDo> preReadyToDos = new ArrayList<>();
+
+       public SwtBrowserJsPart(Composite parent, int style, String url) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               this.browser = new Browser(parent, 0);
+               if (parent.getLayout() instanceof GridLayout)
+                       browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               // TODO other layouts
+
+               URI u = cmsView.toBackendUri(url);
+               browser.setUrl(u.toString());
+               browser.addProgressListener(new ProgressListener() {
+                       static final long serialVersionUID = 1L;
+
+                       @Override
+                       public void completed(ProgressEvent event) {
+                               try {
+                                       init();
+                                       loadExtensions();
+                                       // execute todos in order
+                                       for (PreReadyToDo toDo : preReadyToDos) {
+                                               toDo.run();
+                                       }
+                                       preReadyToDos.clear();
+                                       readyStage.complete(true);
+                               } catch (Exception e) {
+                                       log.error("Cannot initialise " + url + " in browser", e);
+                                       readyStage.complete(false);
+                               }
+                       }
+
+                       @Override
+                       public void changed(ProgressEvent event) {
+                       }
+               });
+       }
+
+       /*
+        * LIFECYCLE
+        */
+
+       /**
+        * Called when the page has been loaded, typically in order to initialise
+        * JavaScript objects. One MUST use {@link #doExecute(String, Object...)} in
+        * order to do so, since the context is not yet considered ready and calls to
+        * {@link #evaluate(String, Object...)} will block.
+        */
+       protected void init() {
+       }
+
+       /**
+        * To be overridden with calls to {@link #loadExtension(String)}.
+        */
+       protected void loadExtensions() {
+
+       }
+
+       protected void loadExtension(String url) {
+               URI u = CmsSwtUtils.getCmsView(getControl()).toBackendUri(url);
+               browser.evaluate(String.format(Locale.ROOT, "import('%s')", u.toString()));
+       }
+
+       public CompletionStage<Boolean> getReadyStage() {
+               return readyStage.minimalCompletionStage();
+       }
+
+       /*
+        * JAVASCRIPT ACCESS
+        */
+
+       @Override
+       public Object evaluate(String js, Object... args) {
+               assert browser.getDisplay().equals(Display.findDisplay(Thread.currentThread())) : "Not the proper UI thread.";
+               if (!readyStage.isDone())
+                       throw new IllegalStateException("Methods returning a result can only be called after UI initialisation.");
+               if (browser.isDisposed())
+                       return null;
+               Object result = browser.evaluate(String.format(Locale.ROOT, js, args));
+               return result;
+       }
+
+       @Override
+       public void execute(String js, Object... args) {
+               String jsToExecute = String.format(Locale.ROOT, js, args);
+               if (readyStage.isDone()) {
+                       if (browser.isDisposed())
+                               return;
+                       boolean success = browser.execute(jsToExecute);
+                       if (!success)
+                               throw new RuntimeException("JavaScript execution failed.");
+               } else {
+                       PreReadyToDo toDo = new PreReadyToDo(jsToExecute);
+                       preReadyToDos.add(toDo);
+               }
+       }
+
+       @Override
+       public String createJsFunction(String name, Function<Object[], Object> toDo) {
+               // browser functions must be directly on window (RAP specific)
+               new BrowserFunction(browser, name) {
+
+                       @Override
+                       public Object function(Object[] arguments) {
+                               Object result = toDo.apply(arguments);
+                               return result;
+                       }
+
+               };
+               return "window." + name;
+       }
+
+       /**
+        * Directly executes, even if {@link #getReadyStage()} is not completed. Except
+        * in initialisation, {@link #evaluate(String, Object...)} should be used
+        * instead.
+        */
+       protected void doExecute(String js, Object... args) {
+               if (browser.isDisposed())
+                       return;
+               browser.execute(String.format(Locale.ROOT, js, args));
+       }
+
+       @Override
+       public String getJsVarName(String name) {
+               return GLOBAL_THIS_ + name;
+       }
+
+       class PreReadyToDo implements Runnable {
+               private String js;
+
+               public PreReadyToDo(String js) {
+                       this.js = js;
+               }
+
+               @Override
+               public void run() {
+                       if (browser.isDisposed())
+                               return;
+                       boolean success = browser.execute(js);
+                       if (!success && log.isTraceEnabled())
+                               log.error("Pre-ready JavaScript failed: " + js);
+               }
+       }
+
+       /*
+        * ACCESSORS
+        */
+
+       public Control getControl() {
+               return browser;
+       }
+
+}
index 109dd22d4ffe88188887ff25369b3eea7688ff52..549062f715739ffd43672878dbb6ae16e8b1b8d8 100644 (file)
@@ -16,6 +16,7 @@ import org.argeo.cms.swt.acr.SwtUiProvider;
 import org.argeo.cms.util.LangUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
@@ -59,6 +60,8 @@ public class DefaultEditionLayer implements SwtAppLayer {
                } else {
                        if (this.workArea != null) {
                                Composite area = new Composite(parent, SWT.NONE);
+                               // we set fill layout by default but it can be overridden
+                               area.setLayout(new FillLayout());
                                this.workArea.createUiPart(area, context);
                                return area;
                        }
index 39cde1b8cfc01c59ce9d842b0a31070d7bded5a7..66e84633a97037b7443aae84ad93808663806e51 100644 (file)
@@ -11,6 +11,7 @@ import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.api.cms.ux.CmsStyle;
 import org.argeo.app.ux.SuiteStyle;
 import org.argeo.cms.Localized;
+import org.argeo.cms.acr.ContentUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.acr.Img;
 import org.argeo.cms.swt.dialogs.CmsFeedback;
@@ -182,7 +183,7 @@ public class SuiteSwtUtils {
         * CONTENT
         */
        public static String toLink(Content content) {
-               return content != null ? "#" + CmsSwtUtils.cleanPathForUrl(content.getPath()) : null;
+               return content != null ? "#" + ContentUtils.cleanPathForUrl(content.getPath()) : null;
        }
 
        public static Text addFormLine(Composite parent, Localized label, Content content, QNamed property,
@@ -297,7 +298,7 @@ public class SuiteSwtUtils {
                Text txt = SuiteSwtUtils.addFormTextField(parent, text, null, 0);
                if (cmsEditable != null && cmsEditable.isEditing()) {
                        txt.addModifyListener((e) -> {
-                               content.put(property, txt.getText());
+                               content.put(property, txt.getText().trim());
                        });
                } else {
                        txt.setEditable(false);
diff --git a/swt/org.argeo.app.ui/OSGI-INF/defaultMap.xml b/swt/org.argeo.app.ui/OSGI-INF/defaultMap.xml
new file mode 100644 (file)
index 0000000..8d53c27
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+   <implementation class="org.argeo.app.geo.swt.MapUiProvider"/>
+   <properties entry="config/defaultMap.properties"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+</scr:component>
index 44f7a0e45ad00d097c683fdad90ca94c9e606857..372ca825a946995785e01aab7f32f0948057feca 100644 (file)
@@ -6,5 +6,5 @@
       <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
    </service>
    <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.geo.ui.overviewMap)"/>
+   <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.geo.ui.defaultMap)"/>
 </scr:component>
index 5bc2eead87a72c08a75f11d5867f23613e0c6d03..dcf439d72e2027892543fcd588ec4bbd298dfebc 100644 (file)
@@ -20,6 +20,7 @@ OSGI-INF/documentsFolder.xml,\
 OSGI-INF/fsEntryArea.xml,\
 OSGI-INF/mapLayer.xml,\
 OSGI-INF/overviewMap.xml,\
+OSGI-INF/defaultMap.xml,\
 OSGI-INF/wwwLayer.xml,\
 OSGI-INF/documentUiProvider.xml,\
 OSGI-INF/publishEntryArea.xml,\
@@ -36,6 +37,7 @@ org.eclipse.core.commands.common,\
 org.eclipse.jface.window,\
 org.eclipse.jface.dialogs,\
 org.eclipse.rap.rwt,\
+org.argeo.app.geo.swt,\
 javax.servlet.*;version="[3,5)",\
 javax.jcr.nodetype,\
 *
diff --git a/swt/org.argeo.app.ui/config/defaultMap.properties b/swt/org.argeo.app.ui/config/defaultMap.properties
new file mode 100644 (file)
index 0000000..fb14459
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.geo.ui.defaultMap
index e649bfc9b1f4118cb5c20f7ed5655609c724b047..62463c792eab8a2a666867e51c536d37fa26e563 100644 (file)
@@ -19,6 +19,7 @@ import org.argeo.app.api.EntityNames;
 import org.argeo.app.api.EntityType;
 import org.argeo.app.swt.ux.SuiteSwtUtils;
 import org.argeo.app.ux.SuiteUxEvent;
+import org.argeo.cms.acr.ContentUtils;
 import org.argeo.cms.jcr.acr.JcrContent;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.dialogs.LightweightDialog;
@@ -220,7 +221,7 @@ public class SuiteUiUtils {
        }
 
        public static String toLink(Node node) {
-               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(JcrContent.nodeToContent(node).getPath()) : null;
+               return node != null ? "#" + ContentUtils.cleanPathForUrl(JcrContent.nodeToContent(node).getPath()) : null;
        }
 
        public static Control addLink(Composite parent, String label, Node node, CmsStyle style)
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java
deleted file mode 100644 (file)
index 1301325..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.app.ui.openlayers;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-
-public class OLMap extends Composite {
-       private Label div;
-
-       public OLMap(Composite parent, int style) {
-               super(parent, style);
-               setLayout(CmsSwtUtils.noSpaceGridLayout());
-               div = new Label(this, SWT.NONE);
-               CmsSwtUtils.markup(div);
-               CmsSwtUtils.disableMarkupValidation(div);
-               div.setText("<div id='map'></div>");
-               div.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-}
index 38d995f1a7e077eddc4958f842827ebb8938991f..57f77f3f2a2580e68d5b11e0677af898c6522b3b 100644 (file)
@@ -3,8 +3,8 @@ package org.argeo.app.ui.publish;
 import java.awt.image.BufferedImage;
 import java.nio.file.Paths;
 
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.rendering.PDFRenderer;
+//import org.apache.pdfbox.pdmodel.PDDocument;
+//import org.apache.pdfbox.rendering.PDFRenderer;
 import org.argeo.eclipse.ui.specific.BufferedImageDisplay;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.FillLayout;
@@ -13,25 +13,25 @@ import org.eclipse.swt.widgets.Shell;
 
 public class PdfViewer {
        public static void main(String[] args) throws Exception {
-               PDDocument doc = PDDocument.load(Paths.get(args[0]).toFile());
-               PDFRenderer renderer = new PDFRenderer(doc);
-
-               BufferedImage image = renderer.renderImageWithDPI(0, 300);
-
-               Display display = new Display();
-               Shell shell = new Shell(display);
-               shell.setLayout(new FillLayout());
-
-               shell.setSize(200, 200);
-
-               BufferedImageDisplay imageDisplay = new BufferedImageDisplay(shell, SWT.NONE);
-               imageDisplay.setImage(image);
-
-               shell.open();
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-               display.dispose();
+//             PDDocument doc = PDDocument.load(Paths.get(args[0]).toFile());
+//             PDFRenderer renderer = new PDFRenderer(doc);
+//
+//             BufferedImage image = renderer.renderImageWithDPI(0, 300);
+//
+//             Display display = new Display();
+//             Shell shell = new Shell(display);
+//             shell.setLayout(new FillLayout());
+//
+//             shell.setSize(200, 200);
+//
+//             BufferedImageDisplay imageDisplay = new BufferedImageDisplay(shell, SWT.NONE);
+//             imageDisplay.setImage(image);
+//
+//             shell.open();
+//             while (!shell.isDisposed()) {
+//                     if (!display.readAndDispatch())
+//                             display.sleep();
+//             }
+//             display.dispose();
        }
 }