Browse Source

parse nodeset partially; build tree; display in vue

Martin Kunz 9 months ago
parent
commit
73eae2bab6

+ 1 - 1
README.md

@@ -1,6 +1,6 @@
 # modtool
 
-This template should help get you started developing with Vue 3 in Vite.
+Opc UA modeling tool.
 
 ## Recommended IDE Setup
 

+ 0 - 0
README.me


+ 0 - 5
jsconfig.json

@@ -1,5 +0,0 @@
-{
-    "compilerOptions": {
-      "allowJs": true
-    }
- }

+ 360 - 164
package-lock.json

@@ -9,24 +9,25 @@
       "version": "0.0.0",
       "dependencies": {
         "axios": "^1.4.0",
-        "fast-xml-parser": "^4.2.6",
-        "vue": "^3.3.4",
-        "vue-router": "^4.2.4"
+        "fast-xml-parser": "^4.2.7",
+        "jszip": "^3.10.1",
+        "pinia": "^2.1.6",
+        "vue": "^3.3.4"
       },
       "devDependencies": {
         "@rushstack/eslint-patch": "^1.3.2",
         "@tsconfig/node18": "^18.2.0",
-        "@types/node": "^18.17.1",
+        "@types/node": "^20.4.5",
         "@types/xml2js": "^0.4.11",
         "@vitejs/plugin-vue": "^4.2.3",
         "@vue/eslint-config-typescript": "^11.0.3",
         "@vue/tsconfig": "^0.4.0",
-        "eslint": "^8.45.0",
-        "eslint-plugin-vue": "^9.15.1",
+        "eslint": "^8.46.0",
+        "eslint-plugin-vue": "^9.16.0",
         "npm-run-all": "^4.1.5",
         "typescript": "~5.1.6",
-        "vite": "^4.4.6",
-        "vue-tsc": "^1.8.6"
+        "vite": "^4.4.7",
+        "vue-tsc": "^1.8.8"
       }
     },
     "node_modules/@aashutoshrathi/word-wrap": {
@@ -426,9 +427,9 @@
       }
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
-      "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz",
+      "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==",
       "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
@@ -449,9 +450,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.44.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
-      "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
+      "version": "8.46.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz",
+      "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -549,9 +550,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "18.17.1",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz",
-      "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==",
+      "version": "20.4.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
+      "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==",
       "dev": true
     },
     "node_modules/@types/semver": {
@@ -771,30 +772,30 @@
       }
     },
     "node_modules/@volar/language-core": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.9.2.tgz",
-      "integrity": "sha512-9GTes/IUPOl0YoV5RQWhCP5a4EDFFfJZGwZn1xA5ug1FO0G6GOVoJI6tQatujtcQmDOQlOM5/0NewnlumygPkQ==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.0.tgz",
+      "integrity": "sha512-ddyWwSYqcbEZNFHm+Z3NZd6M7Ihjcwl/9B5cZd8kECdimVXUFdFi60XHWD27nrWtUQIsUYIG7Ca1WBwV2u2LSQ==",
       "dev": true,
       "dependencies": {
-        "@volar/source-map": "1.9.2"
+        "@volar/source-map": "1.10.0"
       }
     },
     "node_modules/@volar/source-map": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.9.2.tgz",
-      "integrity": "sha512-rYTvV/HMf2CSRkd6oiVxcjX4rnSxEsVfJmw1KTmD4VTBXlz1+b16VIysQX4+1p/eZd2TyCeFblyylIxbZ+YOGg==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.0.tgz",
+      "integrity": "sha512-/ibWdcOzDGiq/GM1JU2eX8fH1bvAhl66hfe8yEgLEzg9txgr6qb5sQ/DEz5PcDL75tF5H5sCRRwn8Eu8ezi9mw==",
       "dev": true,
       "dependencies": {
         "muggle-string": "^0.3.1"
       }
     },
     "node_modules/@volar/typescript": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.9.2.tgz",
-      "integrity": "sha512-l4DA+S3ZVOWGACDdRNVSYZ41nuTWOH8OMS/yVeFV2fTmr/IuD37+3wzzGnjIPwCUa0w+fpg8vJPalzYetmlFTQ==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.0.tgz",
+      "integrity": "sha512-OtqGtFbUKYC0pLNIk3mHQp5xWnvL1CJIUc9VE39VdZ/oqpoBh5jKfb9uJ45Y4/oP/WYTrif/Uxl1k8VTPz66Gg==",
       "dev": true,
       "dependencies": {
-        "@volar/language-core": "1.9.2"
+        "@volar/language-core": "1.10.0"
       }
     },
     "node_modules/@vue/compiler-core": {
@@ -873,13 +874,13 @@
       }
     },
     "node_modules/@vue/language-core": {
-      "version": "1.8.6",
-      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.6.tgz",
-      "integrity": "sha512-PyYDMArbR7hnhqw9OEupr0s4ut0/ZfITp7WEjigF58cd2R0lRLNM1HPvzFMuULpy3ImBEOZI11KRIDirqOe+tQ==",
+      "version": "1.8.8",
+      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.8.tgz",
+      "integrity": "sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw==",
       "dev": true,
       "dependencies": {
-        "@volar/language-core": "~1.9.0",
-        "@volar/source-map": "~1.9.0",
+        "@volar/language-core": "~1.10.0",
+        "@volar/source-map": "~1.10.0",
         "@vue/compiler-dom": "^3.3.0",
         "@vue/reactivity": "^3.3.0",
         "@vue/shared": "^3.3.0",
@@ -983,13 +984,13 @@
       "dev": true
     },
     "node_modules/@vue/typescript": {
-      "version": "1.8.6",
-      "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.6.tgz",
-      "integrity": "sha512-sDQ5tltrSVS3lAkE3JtMRGJo91CLIxcWPy7yms/DT+ssxXpwxbVRD5Gok68HenEZBA4Klq7nW99sG/nTRnpPuQ==",
+      "version": "1.8.8",
+      "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.8.tgz",
+      "integrity": "sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==",
       "dev": true,
       "dependencies": {
-        "@volar/typescript": "~1.9.0",
-        "@vue/language-core": "1.8.6"
+        "@volar/typescript": "~1.10.0",
+        "@vue/language-core": "1.8.8"
       }
     },
     "node_modules/acorn": {
@@ -1235,6 +1236,11 @@
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
       "dev": true
     },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1486,27 +1492,27 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.45.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
-      "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
+      "version": "8.46.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz",
+      "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
-        "@eslint-community/regexpp": "^4.4.0",
-        "@eslint/eslintrc": "^2.1.0",
-        "@eslint/js": "8.44.0",
+        "@eslint-community/regexpp": "^4.6.1",
+        "@eslint/eslintrc": "^2.1.1",
+        "@eslint/js": "^8.46.0",
         "@humanwhocodes/config-array": "^0.11.10",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
-        "ajv": "^6.10.0",
+        "ajv": "^6.12.4",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
         "debug": "^4.3.2",
         "doctrine": "^3.0.0",
         "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.2.0",
-        "eslint-visitor-keys": "^3.4.1",
-        "espree": "^9.6.0",
+        "eslint-scope": "^7.2.2",
+        "eslint-visitor-keys": "^3.4.2",
+        "espree": "^9.6.1",
         "esquery": "^1.4.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
@@ -1540,17 +1546,17 @@
       }
     },
     "node_modules/eslint-plugin-vue": {
-      "version": "9.15.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz",
-      "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==",
+      "version": "9.16.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.16.0.tgz",
+      "integrity": "sha512-SonAuvQXFm9HBV9ww/YkYZVYPMR8ptxxuJwcKGnG9A65SyvNANP9MKlfnC49L6DIwt/YEQZwZWEMHOkL1d5s1A==",
       "dev": true,
       "dependencies": {
-        "@eslint-community/eslint-utils": "^4.3.0",
+        "@eslint-community/eslint-utils": "^4.4.0",
         "natural-compare": "^1.4.0",
-        "nth-check": "^2.0.1",
-        "postcss-selector-parser": "^6.0.9",
-        "semver": "^7.3.5",
-        "vue-eslint-parser": "^9.3.0",
+        "nth-check": "^2.1.1",
+        "postcss-selector-parser": "^6.0.13",
+        "semver": "^7.5.4",
+        "vue-eslint-parser": "^9.3.1",
         "xml-name-validator": "^4.0.0"
       },
       "engines": {
@@ -1574,9 +1580,9 @@
       }
     },
     "node_modules/eslint-visitor-keys": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
-      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz",
+      "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1586,9 +1592,9 @@
       }
     },
     "node_modules/eslint/node_modules/eslint-scope": {
-      "version": "7.2.1",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz",
-      "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==",
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
       "dev": true,
       "dependencies": {
         "esrecurse": "^4.3.0",
@@ -1739,9 +1745,9 @@
       "dev": true
     },
     "node_modules/fast-xml-parser": {
-      "version": "4.2.6",
-      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz",
-      "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==",
+      "version": "4.2.7",
+      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz",
+      "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==",
       "funding": [
         {
           "type": "paypal",
@@ -2163,6 +2169,11 @@
         "node": ">= 4"
       }
     },
+    "node_modules/immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+    },
     "node_modules/import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -2201,8 +2212,7 @@
     "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
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
     "node_modules/internal-slot": {
       "version": "1.0.5",
@@ -2498,6 +2508,17 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "node_modules/jszip": {
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "dependencies": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
     "node_modules/levn": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -2511,6 +2532,14 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "dependencies": {
+        "immediate": "~3.0.5"
+      }
+    },
     "node_modules/load-json-file": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@@ -2973,6 +3002,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3078,6 +3112,56 @@
         "node": ">=4"
       }
     },
+    "node_modules/pinia": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
+      "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.5.0",
+        "vue-demi": ">=0.14.5"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.4.0",
+        "typescript": ">=4.4.4",
+        "vue": "^2.6.14 || ^3.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pinia/node_modules/vue-demi": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
+      "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/postcss": {
       "version": "8.4.27",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
@@ -3127,6 +3211,11 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
     "node_modules/proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -3187,6 +3276,25 @@
         "node": ">=4"
       }
     },
+    "node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/readable-stream/node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+    },
     "node_modules/regexp.prototype.flags": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
@@ -3312,6 +3420,11 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
     "node_modules/safe-regex-test": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -3341,6 +3454,11 @@
         "node": ">=10"
       }
     },
+    "node_modules/setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3434,6 +3552,14 @@
       "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==",
       "dev": true
     },
+    "node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/string.prototype.padend": {
       "version": "3.1.4",
       "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz",
@@ -3690,7 +3816,7 @@
       "version": "5.1.6",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
       "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
-      "dev": true,
+      "devOptional": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -3726,8 +3852,7 @@
     "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
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
     "node_modules/validate-npm-package-license": {
       "version": "3.0.4",
@@ -3855,20 +3980,6 @@
         "node": ">=4.0"
       }
     },
-    "node_modules/vue-router": {
-      "version": "4.2.4",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz",
-      "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==",
-      "dependencies": {
-        "@vue/devtools-api": "^6.5.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/posva"
-      },
-      "peerDependencies": {
-        "vue": "^3.2.0"
-      }
-    },
     "node_modules/vue-template-compiler": {
       "version": "2.7.14",
       "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
@@ -3880,13 +3991,13 @@
       }
     },
     "node_modules/vue-tsc": {
-      "version": "1.8.6",
-      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.6.tgz",
-      "integrity": "sha512-8ffD4NGfwyATjw/s40Lw2EgB7L2/PAqnGlJBaVQLgblr3SU4EYdhJ67TNXXuDD8NMbDAFSM24V8i3ZIJgTs32Q==",
+      "version": "1.8.8",
+      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.8.tgz",
+      "integrity": "sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ==",
       "dev": true,
       "dependencies": {
-        "@vue/language-core": "1.8.6",
-        "@vue/typescript": "1.8.6",
+        "@vue/language-core": "1.8.8",
+        "@vue/typescript": "1.8.8",
         "semver": "^7.3.8"
       },
       "bin": {
@@ -4162,9 +4273,9 @@
       "dev": true
     },
     "@eslint/eslintrc": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
-      "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz",
+      "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==",
       "dev": true,
       "requires": {
         "ajv": "^6.12.4",
@@ -4179,9 +4290,9 @@
       }
     },
     "@eslint/js": {
-      "version": "8.44.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
-      "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
+      "version": "8.46.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz",
+      "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==",
       "dev": true
     },
     "@humanwhocodes/config-array": {
@@ -4257,9 +4368,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "18.17.1",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz",
-      "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==",
+      "version": "20.4.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
+      "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==",
       "dev": true
     },
     "@types/semver": {
@@ -4384,30 +4495,30 @@
       "requires": {}
     },
     "@volar/language-core": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.9.2.tgz",
-      "integrity": "sha512-9GTes/IUPOl0YoV5RQWhCP5a4EDFFfJZGwZn1xA5ug1FO0G6GOVoJI6tQatujtcQmDOQlOM5/0NewnlumygPkQ==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.0.tgz",
+      "integrity": "sha512-ddyWwSYqcbEZNFHm+Z3NZd6M7Ihjcwl/9B5cZd8kECdimVXUFdFi60XHWD27nrWtUQIsUYIG7Ca1WBwV2u2LSQ==",
       "dev": true,
       "requires": {
-        "@volar/source-map": "1.9.2"
+        "@volar/source-map": "1.10.0"
       }
     },
     "@volar/source-map": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.9.2.tgz",
-      "integrity": "sha512-rYTvV/HMf2CSRkd6oiVxcjX4rnSxEsVfJmw1KTmD4VTBXlz1+b16VIysQX4+1p/eZd2TyCeFblyylIxbZ+YOGg==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.0.tgz",
+      "integrity": "sha512-/ibWdcOzDGiq/GM1JU2eX8fH1bvAhl66hfe8yEgLEzg9txgr6qb5sQ/DEz5PcDL75tF5H5sCRRwn8Eu8ezi9mw==",
       "dev": true,
       "requires": {
         "muggle-string": "^0.3.1"
       }
     },
     "@volar/typescript": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.9.2.tgz",
-      "integrity": "sha512-l4DA+S3ZVOWGACDdRNVSYZ41nuTWOH8OMS/yVeFV2fTmr/IuD37+3wzzGnjIPwCUa0w+fpg8vJPalzYetmlFTQ==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.0.tgz",
+      "integrity": "sha512-OtqGtFbUKYC0pLNIk3mHQp5xWnvL1CJIUc9VE39VdZ/oqpoBh5jKfb9uJ45Y4/oP/WYTrif/Uxl1k8VTPz66Gg==",
       "dev": true,
       "requires": {
-        "@volar/language-core": "1.9.2"
+        "@volar/language-core": "1.10.0"
       }
     },
     "@vue/compiler-core": {
@@ -4473,13 +4584,13 @@
       }
     },
     "@vue/language-core": {
-      "version": "1.8.6",
-      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.6.tgz",
-      "integrity": "sha512-PyYDMArbR7hnhqw9OEupr0s4ut0/ZfITp7WEjigF58cd2R0lRLNM1HPvzFMuULpy3ImBEOZI11KRIDirqOe+tQ==",
+      "version": "1.8.8",
+      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.8.tgz",
+      "integrity": "sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw==",
       "dev": true,
       "requires": {
-        "@volar/language-core": "~1.9.0",
-        "@volar/source-map": "~1.9.0",
+        "@volar/language-core": "~1.10.0",
+        "@volar/source-map": "~1.10.0",
         "@vue/compiler-dom": "^3.3.0",
         "@vue/reactivity": "^3.3.0",
         "@vue/shared": "^3.3.0",
@@ -4568,13 +4679,13 @@
       "dev": true
     },
     "@vue/typescript": {
-      "version": "1.8.6",
-      "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.6.tgz",
-      "integrity": "sha512-sDQ5tltrSVS3lAkE3JtMRGJo91CLIxcWPy7yms/DT+ssxXpwxbVRD5Gok68HenEZBA4Klq7nW99sG/nTRnpPuQ==",
+      "version": "1.8.8",
+      "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.8.tgz",
+      "integrity": "sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==",
       "dev": true,
       "requires": {
-        "@volar/typescript": "~1.9.0",
-        "@vue/language-core": "1.8.6"
+        "@volar/typescript": "~1.10.0",
+        "@vue/language-core": "1.8.8"
       }
     },
     "acorn": {
@@ -4760,6 +4871,11 @@
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
       "dev": true
     },
+    "core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+    },
     "cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -4951,27 +5067,27 @@
       "dev": true
     },
     "eslint": {
-      "version": "8.45.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
-      "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
+      "version": "8.46.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz",
+      "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==",
       "dev": true,
       "requires": {
         "@eslint-community/eslint-utils": "^4.2.0",
-        "@eslint-community/regexpp": "^4.4.0",
-        "@eslint/eslintrc": "^2.1.0",
-        "@eslint/js": "8.44.0",
+        "@eslint-community/regexpp": "^4.6.1",
+        "@eslint/eslintrc": "^2.1.1",
+        "@eslint/js": "^8.46.0",
         "@humanwhocodes/config-array": "^0.11.10",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
-        "ajv": "^6.10.0",
+        "ajv": "^6.12.4",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
         "debug": "^4.3.2",
         "doctrine": "^3.0.0",
         "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.2.0",
-        "eslint-visitor-keys": "^3.4.1",
-        "espree": "^9.6.0",
+        "eslint-scope": "^7.2.2",
+        "eslint-visitor-keys": "^3.4.2",
+        "espree": "^9.6.1",
         "esquery": "^1.4.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
@@ -4996,9 +5112,9 @@
       },
       "dependencies": {
         "eslint-scope": {
-          "version": "7.2.1",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz",
-          "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==",
+          "version": "7.2.2",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+          "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
           "dev": true,
           "requires": {
             "esrecurse": "^4.3.0",
@@ -5014,17 +5130,17 @@
       }
     },
     "eslint-plugin-vue": {
-      "version": "9.15.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz",
-      "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==",
+      "version": "9.16.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.16.0.tgz",
+      "integrity": "sha512-SonAuvQXFm9HBV9ww/YkYZVYPMR8ptxxuJwcKGnG9A65SyvNANP9MKlfnC49L6DIwt/YEQZwZWEMHOkL1d5s1A==",
       "dev": true,
       "requires": {
-        "@eslint-community/eslint-utils": "^4.3.0",
+        "@eslint-community/eslint-utils": "^4.4.0",
         "natural-compare": "^1.4.0",
-        "nth-check": "^2.0.1",
-        "postcss-selector-parser": "^6.0.9",
-        "semver": "^7.3.5",
-        "vue-eslint-parser": "^9.3.0",
+        "nth-check": "^2.1.1",
+        "postcss-selector-parser": "^6.0.13",
+        "semver": "^7.5.4",
+        "vue-eslint-parser": "^9.3.1",
         "xml-name-validator": "^4.0.0"
       }
     },
@@ -5039,9 +5155,9 @@
       }
     },
     "eslint-visitor-keys": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
-      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz",
+      "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==",
       "dev": true
     },
     "espree": {
@@ -5149,9 +5265,9 @@
       "dev": true
     },
     "fast-xml-parser": {
-      "version": "4.2.6",
-      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz",
-      "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==",
+      "version": "4.2.7",
+      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz",
+      "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==",
       "requires": {
         "strnum": "^1.0.5"
       }
@@ -5437,6 +5553,11 @@
       "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
       "dev": true
     },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+    },
     "import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -5466,8 +5587,7 @@
     "inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
     "internal-slot": {
       "version": "1.0.5",
@@ -5676,6 +5796,17 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "jszip": {
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "requires": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
     "levn": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -5686,6 +5817,14 @@
         "type-check": "~0.4.0"
       }
     },
+    "lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "requires": {
+        "immediate": "~3.0.5"
+      }
+    },
     "load-json-file": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@@ -6033,6 +6172,11 @@
         "p-limit": "^3.0.2"
       }
     },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
     "parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -6105,6 +6249,23 @@
       "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
       "dev": true
     },
+    "pinia": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
+      "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
+      "requires": {
+        "@vue/devtools-api": "^6.5.0",
+        "vue-demi": ">=0.14.5"
+      },
+      "dependencies": {
+        "vue-demi": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
+          "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+          "requires": {}
+        }
+      }
+    },
     "postcss": {
       "version": "8.4.27",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
@@ -6131,6 +6292,11 @@
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
       "dev": true
     },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
     "proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -6170,6 +6336,27 @@
         }
       }
     },
+    "readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+        }
+      }
+    },
     "regexp.prototype.flags": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
@@ -6243,6 +6430,11 @@
         "isarray": "^2.0.5"
       }
     },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
     "safe-regex-test": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -6263,6 +6455,11 @@
         "lru-cache": "^6.0.0"
       }
     },
+    "setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+    },
     "shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -6338,6 +6535,14 @@
       "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==",
       "dev": true
     },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "string.prototype.padend": {
       "version": "3.1.4",
       "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz",
@@ -6519,7 +6724,7 @@
       "version": "5.1.6",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
       "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
-      "dev": true
+      "devOptional": true
     },
     "unbox-primitive": {
       "version": "1.0.2",
@@ -6545,8 +6750,7 @@
     "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
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
     "validate-npm-package-license": {
       "version": "3.0.4",
@@ -6615,14 +6819,6 @@
         }
       }
     },
-    "vue-router": {
-      "version": "4.2.4",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz",
-      "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==",
-      "requires": {
-        "@vue/devtools-api": "^6.5.0"
-      }
-    },
     "vue-template-compiler": {
       "version": "2.7.14",
       "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
@@ -6634,13 +6830,13 @@
       }
     },
     "vue-tsc": {
-      "version": "1.8.6",
-      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.6.tgz",
-      "integrity": "sha512-8ffD4NGfwyATjw/s40Lw2EgB7L2/PAqnGlJBaVQLgblr3SU4EYdhJ67TNXXuDD8NMbDAFSM24V8i3ZIJgTs32Q==",
+      "version": "1.8.8",
+      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.8.tgz",
+      "integrity": "sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ==",
       "dev": true,
       "requires": {
-        "@vue/language-core": "1.8.6",
-        "@vue/typescript": "1.8.6",
+        "@vue/language-core": "1.8.8",
+        "@vue/typescript": "1.8.8",
         "semver": "^7.3.8"
       }
     },

+ 9 - 9
package.json

@@ -11,24 +11,24 @@
     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
   },
   "dependencies": {
-    "axios": "^1.4.0",
-    "fast-xml-parser": "^4.2.6",
-    "vue": "^3.3.4",
-    "vue-router": "^4.2.4"
+    "fast-xml-parser": "^4.2.7",
+    "jszip": "^3.10.1",
+    "pinia": "^2.1.6",
+    "vue": "^3.3.4"
   },
   "devDependencies": {
     "@rushstack/eslint-patch": "^1.3.2",
     "@tsconfig/node18": "^18.2.0",
-    "@types/node": "^18.17.1",
+    "@types/node": "^20.4.5",
     "@types/xml2js": "^0.4.11",
     "@vitejs/plugin-vue": "^4.2.3",
     "@vue/eslint-config-typescript": "^11.0.3",
     "@vue/tsconfig": "^0.4.0",
-    "eslint": "^8.45.0",
-    "eslint-plugin-vue": "^9.15.1",
+    "eslint": "^8.46.0",
+    "eslint-plugin-vue": "^9.16.0",
     "npm-run-all": "^4.1.5",
     "typescript": "~5.1.6",
-    "vite": "^4.4.6",
-    "vue-tsc": "^1.8.6"
+    "vite": "^4.4.7",
+    "vue-tsc": "^1.8.8"
   }
 }

+ 37 - 70
src/App.vue

@@ -1,80 +1,47 @@
 <script setup lang="ts">
-import { RouterLink } from 'vue-router'
+import TheModeler from './components/TheModeler.vue'
+import TheDetail from './components/TheDetail.vue'
+import TheModels from './components/TheModels.vue'
+import { useStore } from './util/store';
+import { AddressSpace } from './ua/AddressSpace';
+import { assert } from './util/assert';
+
+async function load(): Promise<AddressSpace> {
+    const files=['nodesets/Opc.Ua.NodeSet2.xml',
+               'nodesets/Opc.Ua.Di.NodeSet2.xml',
+               'nodesets/Opc.Ua.Ia.NodeSet2.xml',
+               'nodesets/Opc.Ua.Machinery.NodeSet2.xml',
+               'nodesets/Opc.Ua.MachineTool.Nodeset2.xml',
+               'nodesets/emco_machine_tools.xml']
+    const as=await AddressSpace.load(files);
+    return as;
+  }
 
+  const loadData = async () => {
+    const store = useStore();
+    store.addressSpace=await load();
+    assert(store.addressSpace)
+    const rootNode=store.addressSpace.findNode("ns=0;i=84");
+    assert(rootNode)
+    store.rootNode=rootNode;
+  }
+  loadData();
 </script>
 
 <template>
-  <header>
-    <div class="wrapper">
-      <nav>
-        <RouterLink to="/">Home</RouterLink>
-        <RouterLink to="/about">About</RouterLink>
-      </nav>
+  <div class="container text-left">
+    <div class="row align-items-start">
+      <div class="col col-6">
+        <TheModels />
+        <TheModeler />
+      </div>
+      <div class="col col-6">
+        <TheDetail />
+      </div>
     </div>
-  </header>
-  <RouterView />
+  </div>
 </template>
 
-<style scoped>
-header {
-  line-height: 1.5;
-  max-height: 100vh;
-}
-
-.logo {
-  display: block;
-  margin: 0 auto 2rem;
-}
-
-nav {
-  width: 100%;
-  font-size: 12px;
-  text-align: center;
-  margin-top: 2rem;
-}
-
-nav a.router-link-exact-active {
-  color: var(--color-text);
-}
-
-nav a.router-link-exact-active:hover {
-  background-color: transparent;
-}
-
-nav a {
-  display: inline-block;
-  padding: 0 1rem;
-  border-left: 1px solid var(--color-border);
-}
-
-nav a:first-of-type {
-  border: 0;
-}
+<style>
 
-@media (min-width: 1024px) {
-  header {
-    display: flex;
-    place-items: center;
-    padding-right: calc(var(--section-gap) / 2);
-  }
-
-  .logo {
-    margin: 0 2rem 0 0;
-  }
-
-  header .wrapper {
-    display: flex;
-    place-items: flex-start;
-    flex-wrap: wrap;
-  }
-
-  nav {
-    text-align: left;
-    margin-left: -1rem;
-    font-size: 1rem;
-
-    padding: 1rem 0;
-    margin-top: 1rem;
-  }
-}
 </style>

+ 0 - 61
src/UANodeSet.ts

@@ -1,61 +0,0 @@
-import {XMLParser, type X2jOptions} from 'fast-xml-parser';
-import axios from 'axios';
-import { UAObject } from './UAObject';
-import type { UABaseNode } from './UABaseNode';
-import { UAVariable } from './UAVariable';
-import { NamespaceTable } from './NameSpaceTable';
-
-export class UANodeSet {
-
-    constructor(public nodes: UABaseNode[],
-                public nameSpaceTable: NamespaceTable) {
-    }
-
-    reIndex(nst: NamespaceTable) {
-        for(const value of this.nameSpaceTable.nsMap.getValues()) {
-            nst.addUri(value);
-        }
-        for(const node of this.nodes) {
-            node.reIndex(nst, this.nameSpaceTable);
-        }
-    }
-
-    resolveChildren(nm: Map<string, UABaseNode>) {
-        for(const node of this.nodes) {
-            node.resolveChildren(nm);
-        }    
-    }
-
-    static async load(url: string) {
-        const xml= (await axios.get(url)).data;     
-        const parseOptions:Partial<X2jOptions>={
-            ignoreAttributes: false,
-            // eslint-disable-next-line @typescript-eslint/no-unused-vars
-            isArray: (name, jpath, isLeafNode, isAttribute):boolean => { 
-                if(jpath=='UANodeSet.NamespaceUris.Uri') return true;
-                if(jpath=='UANodeSet.UAObject.References.Reference') return true;
-                if(jpath=='UANodeSet.UAVariable.References.Reference') return true;
-                return false;
-            }
-        }
-        const parser = new XMLParser(parseOptions);
-        const jObj = parser.parse(xml);
-        const nodes:UABaseNode[]=[];
-        const uaObjects=jObj['UANodeSet']['UAObject'];
-        for(const uaObject of uaObjects) {
-            nodes.push(UAObject.parse(uaObject));
-        }
-        const uaVariables=jObj['UANodeSet']['UAVariable'];
-        for(const uaVariable of uaVariables) {
-            nodes.push(UAVariable.parse(uaVariable));
-        }
-        const uaNamespaceUris=jObj['UANodeSet']['NamespaceUris'];
-        const nst=new NamespaceTable();
-        if(uaNamespaceUris) {
-            for(const nsUri of uaNamespaceUris['Uri']) {
-                nst.addUri(nsUri)
-            }
-        }
-        return new UANodeSet(nodes, nst);
-    }
-}

+ 0 - 73
src/assets/base.css

@@ -1,73 +0,0 @@
-/* color palette from <https://github.com/vuejs/theme> */
-:root {
-  --vt-c-white: #ffffff;
-  --vt-c-white-soft: #f8f8f8;
-  --vt-c-white-mute: #f2f2f2;
-
-  --vt-c-black: #181818;
-  --vt-c-black-soft: #222222;
-  --vt-c-black-mute: #282828;
-
-  --vt-c-indigo: #2c3e50;
-
-  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
-  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
-  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
-  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
-
-  --vt-c-text-light-1: var(--vt-c-indigo);
-  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
-  --vt-c-text-dark-1: var(--vt-c-white);
-  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
-}
-
-/* semantic color variables for this project */
-:root {
-  --color-background: var(--vt-c-white);
-  --color-background-soft: var(--vt-c-white-soft);
-  --color-background-mute: var(--vt-c-white-mute);
-
-  --color-border: var(--vt-c-divider-light-2);
-  --color-border-hover: var(--vt-c-divider-light-1);
-
-  --color-heading: var(--vt-c-text-light-1);
-  --color-text: var(--vt-c-text-light-1);
-
-  --section-gap: 160px;
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --color-background: var(--vt-c-black);
-    --color-background-soft: var(--vt-c-black-soft);
-    --color-background-mute: var(--vt-c-black-mute);
-
-    --color-border: var(--vt-c-divider-dark-2);
-    --color-border-hover: var(--vt-c-divider-dark-1);
-
-    --color-heading: var(--vt-c-text-dark-1);
-    --color-text: var(--vt-c-text-dark-2);
-  }
-}
-
-*,
-*::before,
-*::after {
-  box-sizing: border-box;
-  margin: 0;
-  font-weight: normal;
-}
-
-body {
-  min-height: 100vh;
-  color: var(--color-text);
-  background: var(--color-background);
-  transition: color 0.5s, background-color 0.5s;
-  line-height: 1.6;
-  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
-    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-  font-size: 15px;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}

File diff suppressed because it is too large
+ 4124 - 0
src/assets/bootstrap/bootstrap-grid.css


+ 488 - 0
src/assets/bootstrap/bootstrap-reboot.css

@@ -0,0 +1,488 @@
+/*!
+ * Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)
+ * Copyright 2011-2022 The Bootstrap Authors
+ * Copyright 2011-2022 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+:root {
+  --bs-blue: #0d6efd;
+  --bs-indigo: #6610f2;
+  --bs-purple: #6f42c1;
+  --bs-pink: #d63384;
+  --bs-red: #dc3545;
+  --bs-orange: #fd7e14;
+  --bs-yellow: #ffc107;
+  --bs-green: #198754;
+  --bs-teal: #20c997;
+  --bs-cyan: #0dcaf0;
+  --bs-black: #000;
+  --bs-white: #fff;
+  --bs-gray: #6c757d;
+  --bs-gray-dark: #343a40;
+  --bs-gray-100: #f8f9fa;
+  --bs-gray-200: #e9ecef;
+  --bs-gray-300: #dee2e6;
+  --bs-gray-400: #ced4da;
+  --bs-gray-500: #adb5bd;
+  --bs-gray-600: #6c757d;
+  --bs-gray-700: #495057;
+  --bs-gray-800: #343a40;
+  --bs-gray-900: #212529;
+  --bs-primary: #0d6efd;
+  --bs-secondary: #6c757d;
+  --bs-success: #198754;
+  --bs-info: #0dcaf0;
+  --bs-warning: #ffc107;
+  --bs-danger: #dc3545;
+  --bs-light: #f8f9fa;
+  --bs-dark: #212529;
+  --bs-primary-rgb: 13, 110, 253;
+  --bs-secondary-rgb: 108, 117, 125;
+  --bs-success-rgb: 25, 135, 84;
+  --bs-info-rgb: 13, 202, 240;
+  --bs-warning-rgb: 255, 193, 7;
+  --bs-danger-rgb: 220, 53, 69;
+  --bs-light-rgb: 248, 249, 250;
+  --bs-dark-rgb: 33, 37, 41;
+  --bs-white-rgb: 255, 255, 255;
+  --bs-black-rgb: 0, 0, 0;
+  --bs-body-color-rgb: 33, 37, 41;
+  --bs-body-bg-rgb: 255, 255, 255;
+  --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
+  --bs-body-font-family: var(--bs-font-sans-serif);
+  --bs-body-font-size: 1rem;
+  --bs-body-font-weight: 400;
+  --bs-body-line-height: 1.5;
+  --bs-body-color: #212529;
+  --bs-body-bg: #fff;
+  --bs-border-width: 1px;
+  --bs-border-style: solid;
+  --bs-border-color: #dee2e6;
+  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);
+  --bs-border-radius: 0.375rem;
+  --bs-border-radius-sm: 0.25rem;
+  --bs-border-radius-lg: 0.5rem;
+  --bs-border-radius-xl: 1rem;
+  --bs-border-radius-2xl: 2rem;
+  --bs-border-radius-pill: 50rem;
+  --bs-link-color: #0d6efd;
+  --bs-link-hover-color: #0a58ca;
+  --bs-code-color: #d63384;
+  --bs-highlight-bg: #fff3cd;
+}
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  :root {
+    scroll-behavior: smooth;
+  }
+}
+
+body {
+  margin: 0;
+  font-family: var(--bs-body-font-family);
+  font-size: var(--bs-body-font-size);
+  font-weight: var(--bs-body-font-weight);
+  line-height: var(--bs-body-line-height);
+  color: var(--bs-body-color);
+  text-align: var(--bs-body-text-align);
+  background-color: var(--bs-body-bg);
+  -webkit-text-size-adjust: 100%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+hr {
+  margin: 1rem 0;
+  color: inherit;
+  border: 0;
+  border-top: 1px solid;
+  opacity: 0.25;
+}
+
+h6, h5, h4, h3, h2, h1 {
+  margin-top: 0;
+  margin-bottom: 0.5rem;
+  font-weight: 500;
+  line-height: 1.2;
+}
+
+h1 {
+  font-size: calc(1.375rem + 1.5vw);
+}
+@media (min-width: 1200px) {
+  h1 {
+    font-size: 2.5rem;
+  }
+}
+
+h2 {
+  font-size: calc(1.325rem + 0.9vw);
+}
+@media (min-width: 1200px) {
+  h2 {
+    font-size: 2rem;
+  }
+}
+
+h3 {
+  font-size: calc(1.3rem + 0.6vw);
+}
+@media (min-width: 1200px) {
+  h3 {
+    font-size: 1.75rem;
+  }
+}
+
+h4 {
+  font-size: calc(1.275rem + 0.3vw);
+}
+@media (min-width: 1200px) {
+  h4 {
+    font-size: 1.5rem;
+  }
+}
+
+h5 {
+  font-size: 1.25rem;
+}
+
+h6 {
+  font-size: 1rem;
+}
+
+p {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+abbr[title] {
+  -webkit-text-decoration: underline dotted;
+  text-decoration: underline dotted;
+  cursor: help;
+  -webkit-text-decoration-skip-ink: none;
+  text-decoration-skip-ink: none;
+}
+
+address {
+  margin-bottom: 1rem;
+  font-style: normal;
+  line-height: inherit;
+}
+
+ol,
+ul {
+  padding-left: 2rem;
+}
+
+ol,
+ul,
+dl {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+  margin-bottom: 0;
+}
+
+dt {
+  font-weight: 700;
+}
+
+dd {
+  margin-bottom: 0.5rem;
+  margin-left: 0;
+}
+
+blockquote {
+  margin: 0 0 1rem;
+}
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+small {
+  font-size: 0.875em;
+}
+
+mark {
+  padding: 0.1875em;
+  background-color: var(--bs-highlight-bg);
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 0.75em;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+a {
+  color: var(--bs-link-color);
+  text-decoration: underline;
+}
+a:hover {
+  color: var(--bs-link-hover-color);
+}
+
+a:not([href]):not([class]), a:not([href]):not([class]):hover {
+  color: inherit;
+  text-decoration: none;
+}
+
+pre,
+code,
+kbd,
+samp {
+  font-family: var(--bs-font-monospace);
+  font-size: 1em;
+}
+
+pre {
+  display: block;
+  margin-top: 0;
+  margin-bottom: 1rem;
+  overflow: auto;
+  font-size: 0.875em;
+}
+pre code {
+  font-size: inherit;
+  color: inherit;
+  word-break: normal;
+}
+
+code {
+  font-size: 0.875em;
+  color: var(--bs-code-color);
+  word-wrap: break-word;
+}
+a > code {
+  color: inherit;
+}
+
+kbd {
+  padding: 0.1875rem 0.375rem;
+  font-size: 0.875em;
+  color: var(--bs-body-bg);
+  background-color: var(--bs-body-color);
+  border-radius: 0.25rem;
+}
+kbd kbd {
+  padding: 0;
+  font-size: 1em;
+}
+
+figure {
+  margin: 0 0 1rem;
+}
+
+img,
+svg {
+  vertical-align: middle;
+}
+
+table {
+  caption-side: bottom;
+  border-collapse: collapse;
+}
+
+caption {
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+  color: #6c757d;
+  text-align: left;
+}
+
+th {
+  text-align: inherit;
+  text-align: -webkit-match-parent;
+}
+
+thead,
+tbody,
+tfoot,
+tr,
+td,
+th {
+  border-color: inherit;
+  border-style: solid;
+  border-width: 0;
+}
+
+label {
+  display: inline-block;
+}
+
+button {
+  border-radius: 0;
+}
+
+button:focus:not(:focus-visible) {
+  outline: 0;
+}
+
+input,
+button,
+select,
+optgroup,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+[role=button] {
+  cursor: pointer;
+}
+
+select {
+  word-wrap: normal;
+}
+select:disabled {
+  opacity: 1;
+}
+
+[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
+  display: none !important;
+}
+
+button,
+[type=button],
+[type=reset],
+[type=submit] {
+  -webkit-appearance: button;
+}
+button:not(:disabled),
+[type=button]:not(:disabled),
+[type=reset]:not(:disabled),
+[type=submit]:not(:disabled) {
+  cursor: pointer;
+}
+
+::-moz-focus-inner {
+  padding: 0;
+  border-style: none;
+}
+
+textarea {
+  resize: vertical;
+}
+
+fieldset {
+  min-width: 0;
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  float: left;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 0.5rem;
+  font-size: calc(1.275rem + 0.3vw);
+  line-height: inherit;
+}
+@media (min-width: 1200px) {
+  legend {
+    font-size: 1.5rem;
+  }
+}
+legend + * {
+  clear: left;
+}
+
+::-webkit-datetime-edit-fields-wrapper,
+::-webkit-datetime-edit-text,
+::-webkit-datetime-edit-minute,
+::-webkit-datetime-edit-hour-field,
+::-webkit-datetime-edit-day-field,
+::-webkit-datetime-edit-month-field,
+::-webkit-datetime-edit-year-field {
+  padding: 0;
+}
+
+::-webkit-inner-spin-button {
+  height: auto;
+}
+
+[type=search] {
+  outline-offset: -2px;
+  -webkit-appearance: textfield;
+}
+
+/* rtl:raw:
+[type="tel"],
+[type="url"],
+[type="email"],
+[type="number"] {
+  direction: ltr;
+}
+*/
+::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+::-webkit-color-swatch-wrapper {
+  padding: 0;
+}
+
+::-webkit-file-upload-button {
+  font: inherit;
+  -webkit-appearance: button;
+}
+
+::file-selector-button {
+  font: inherit;
+  -webkit-appearance: button;
+}
+
+output {
+  display: inline-block;
+}
+
+iframe {
+  border: 0;
+}
+
+summary {
+  display: list-item;
+  cursor: pointer;
+}
+
+progress {
+  vertical-align: baseline;
+}
+
+[hidden] {
+  display: none !important;
+}
+
+/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because it is too large
+ 4266 - 0
src/assets/bootstrap/bootstrap-utilities.css


File diff suppressed because it is too large
+ 10878 - 0
src/assets/bootstrap/bootstrap.css


+ 3 - 29
src/assets/main.css

@@ -1,35 +1,9 @@
-@import './base.css';
+@import './bootstrap/bootstrap.css';
+@import './bootstrap/bootstrap-grid.css';
+@import './bootstrap/bootstrap-reboot.css';
 
 #app {
-  max-width: 1280px;
   margin: 0 auto;
   padding: 2rem;
-
   font-weight: normal;
 }
-
-a,
-.green {
-  text-decoration: none;
-  color: hsla(160, 100%, 37%, 1);
-  transition: 0.4s;
-}
-
-@media (hover: hover) {
-  a:hover {
-    background-color: hsla(160, 100%, 37%, 0.2);
-  }
-}
-
-@media (min-width: 1024px) {
-  body {
-    display: flex;
-    place-items: center;
-  }
-
-  #app {
-    display: grid;
-    grid-template-columns: 1fr 1fr;
-    padding: 0 2rem;
-  }
-}

+ 31 - 0
src/components/TheDetail.vue

@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import { useStore } from '@/util/store'
+const store = useStore()
+</script>
+
+<template>
+    <div class="card">
+    <div class="card-body" v-if="store.selectedNode != null" :elem="store.selectedNode">
+      <h5 class="card-title">Node Editor</h5>
+      <div class="card-text" >
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">DisplayName</span>
+          </div>
+          <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.selectedNode.displayName">
+        </div>
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">BrowseName</span>
+          </div>
+          <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.selectedNode.browseName">
+        </div>
+      </div>
+      </div>
+    </div>
+</template>
+
+
+<style scoped>
+
+</style>

+ 20 - 24
src/components/TheModeler.vue

@@ -1,30 +1,26 @@
 <script setup lang="ts">
-import { AddressSpace } from '@/AddressSpace';
-import type { UABaseNode } from '@/UABaseNode';
 import TreeItem from './TreeItem.vue'
+import {useStore} from '@/util/store'
+const store = useStore()
+</script>
 
+<template>
+  <div class="card">
+  <div class="card-body" v-if="store.addressSpace">
+    <h5 class="card-title">Addressspace</h5>
+    <p class="card-text">
+      <ul>
+        <TreeItem class="item" :model="store.rootNode" v-if="store.rootNode!=null"></TreeItem>
+      </ul>
+    </p>
+  </div>
+</div>
 
+</template>
 
-async function load(): Promise<UABaseNode|undefined> {
-  let files=['nodesets/Opc.Ua.NodeSet2.xml',
-             'nodesets/Opc.Ua.Di.NodeSet2.xml',
-             'nodesets/Opc.Ua.Ia.NodeSet2.xml',
-             'nodesets/Opc.Ua.Machinery.NodeSet2.xml',
-             'nodesets/Opc.Ua.MachineTool.Nodeset2.xml',
-             'nodesets/emco_machine_tools.xml']
-  const start = new Date().getTime();
-  const as=await AddressSpace.load(files);
-  let elapsed = new Date().getTime() - start;
-  console.log(elapsed);
-  const rootNode=as.findNode("ns=0;i=84");
-  return rootNode;
-}
 
-const rootNode=await load();
-</script>
-
-<template>
-  <ul>
-    <TreeItem class="item" :model="rootNode"></TreeItem>
-  </ul>
-</template>
+<style scoped>
+  #ul {
+    margin-left: 10px;
+  }
+</style>

+ 26 - 0
src/components/TheModels.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import {useStore} from '@/util/store'
+const store = useStore()
+</script>
+
+<template>
+ <div v-if="store.addressSpace">
+  <div class="card">
+    <div class="card-body" v-if="store.addressSpace">
+      <h5 class="card-title">Models</h5>
+      <p class="card-text" >
+        <ul class="list-group list-group-flush" v-for="nodeset in store.addressSpace.nodesets" v-bind:key="nodeset.models[0].modelUri">
+          <div v-for="model in nodeset.models" v-bind:key="model.modelUri">
+            <li class="list-group-item">{{ model.modelUri }}</li>
+          </div>
+        </ul>
+      </p>
+    </div>
+  </div>
+</div>
+</template>
+
+
+<style>
+
+</style>

+ 38 - 8
src/components/TreeItem.vue

@@ -1,34 +1,64 @@
-<script lang="ts" setup>
+<script setup lang="ts" >
+import { UABaseNode } from '@/ua/UABaseNode';
+import { useStore } from '@/util/store';
 import { ref, computed } from 'vue'
+const store = useStore()
 
 const props = defineProps({
-  model: Object
+  model: UABaseNode
 })
 
 const isOpen = ref(false)
 const isFolder = computed(() => {
-  return props.model.children && props.model.children.length
+  return props.model?.children && props.model.children.length
 })
 
 function toggle() {
   isOpen.value = !isOpen.value
 }
+
+function selectNode(node: UABaseNode) {
+  store.selectedNode=node;
+}
 </script>
 
 <template>
   <li>
-    <div
+    <div v-if="model"
       :class="{ bold: isFolder }"
       @click="toggle">
       {{ model.displayName }}
       <span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
     </div>
-    <ul v-show="isOpen" v-if="isFolder">
+    <ul v-show="isOpen" v-if="isFolder && model">
       <TreeItem
         class="item"
-        v-for="model in model.children"
-        :model="model">
+        @click.stop="selectNode(child)"
+        v-for="child in model.children"
+        v-bind:key="child.nodeId.toString()"
+        :model="child">
       </TreeItem>
     </ul>
   </li>
-</template>
+</template>
+
+
+<style scoped>
+  body {
+    font-family: Menlo, Consolas, monospace;
+    color: #444;
+  }
+  .item {
+    cursor: pointer;
+    text-align: left;
+    list-style-type: none;
+  }
+  .bold {
+    font-weight: bold;
+  }
+  ul {
+    padding-left: 1em;
+    line-height: 1.5em;
+    list-style-type: dot;
+  }
+</style>

+ 3 - 4
src/main.ts

@@ -2,10 +2,9 @@ import './assets/main.css'
 
 import { createApp } from 'vue'
 import App from './App.vue'
-import router from './router'
+import { createPinia } from 'pinia'
 
 const app = createApp(App)
-
-app.use(router)
-
+const pinia = createPinia()
+app.use(pinia)
 app.mount('#app')

+ 0 - 23
src/router/index.ts

@@ -1,23 +0,0 @@
-import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
-
-const router = createRouter({
-  history: createWebHistory(import.meta.env.BASE_URL),
-  routes: [
-    {
-      path: '/',
-      name: 'home',
-      component: HomeView
-    },
-    {
-      path: '/about',
-      name: 'about',
-      // route level code-splitting
-      // this generates a separate chunk (About.[hash].js) for this route
-      // which is lazy-loaded when the route is visited.
-      component: () => import('../views/AboutView.vue')
-    }
-  ]
-})
-
-export default router

+ 23 - 6
src/AddressSpace.ts

@@ -1,6 +1,7 @@
 import { UANodeSet } from "./UANodeSet";
 import { UABaseNode } from "./UABaseNode";
 import { NamespaceTable } from "./NameSpaceTable";
+import JSZip from "jszip";
 
 export class AddressSpace{
 
@@ -12,6 +13,11 @@ export class AddressSpace{
         this.resolveChildren(nodesets, this.nodeMap);
     }
 
+    public findNode(nodeId: string):UABaseNode|undefined {
+        return this.nodeMap.get(nodeId)
+    }
+
+
     private reIndex(nodesets:UANodeSet[]) {
         const nst=new NamespaceTable();
         for(const nodeset of nodesets) {
@@ -35,16 +41,27 @@ export class AddressSpace{
         }
     }
 
-    public findNode(nodeId: string):UABaseNode|undefined {
-        return this.nodeMap.get(nodeId)
-    }
 
     static async load(files: string[]) {
-        const sets:UANodeSet[]= [];
+        const promises:Promise<UANodeSet>[] = []
         for(const file of files) {
-            const ns=await UANodeSet.load(file);
-            sets.push(ns);
+            promises.push(UANodeSet.load(file));
+        }
+        const sets:UANodeSet[]= [];
+        for(const p of promises) {
+            sets.push(await p)
         }
         return new AddressSpace(sets);
     }
+
+    public export() {
+        const zip = new JSZip();
+        const fileNames:string[]=[];
+        for(const ns of this.nodesets) {
+            fileNames.push(ns.fileName);
+            zip.file(ns.fileName, ns.toXML().toString());
+        }
+        zip.file("project.json", JSON.stringify(fileNames));
+        return zip.generateAsync({type:'blob'});
+    }
 }

+ 23 - 0
src/ua/Model.ts

@@ -0,0 +1,23 @@
+import { XMLElem, type IToXML } from "@/util/XmlElem";
+
+export class Model implements IToXML{
+    constructor(public modelUri: string,
+                public publicationDate: string,
+                public version: string,
+                public xmlSchemaUri: string)
+    {
+
+    }
+
+    toXML(): XMLElem {
+        return new XMLElem('Model')
+                .attr('ModelUri', this.modelUri)
+                .attr('XmlSchemaUri', this.xmlSchemaUri)
+                .attr('Version', this.version)
+                .attr('PublicationDate', this.publicationDate)
+    }
+
+    static fromXML(xmlObject: any): Model{
+        return new Model(xmlObject['@_ModelUri'], xmlObject['@_PublicationDate'], xmlObject['@_Version'], xmlObject['@_XmlSchemaUri']);
+    }
+}

+ 1 - 1
src/NameSpaceTable.ts

@@ -1,4 +1,4 @@
-import { BiMap } from "./util/bi-map";
+import { BiMap } from "@/util/bi-map";
 
 
 export class NamespaceTable {

+ 2 - 1
src/NodeId.ts

@@ -1,4 +1,4 @@
-import { assert } from "./util/assert";
+import { assert } from "@/util/assert";
 
 
 export class NodeId  {
@@ -30,6 +30,7 @@ export class NodeId  {
             case NodeIdType.NUMERIC: return 0;
         }
     }
+    
 
     public toString(): string {
         let str;

+ 21 - 16
src/UABaseNode.ts

@@ -1,9 +1,10 @@
 import type { NamespaceTable } from "./NameSpaceTable";
 import { NodeId } from "./NodeId";
 import { ReferencyType, UAReference } from "./UAReference";
-import { assert } from "./util/assert";
+import { assert } from "@/util/assert";
+import { XMLElem, type IToXML } from "@/util/XmlElem";
 
-export class UABaseNode {
+export class UABaseNode implements IToXML{
     children: UABaseNode[]=[];
     constructor(public nodeId: NodeId,
                 public browseName: string,
@@ -30,28 +31,32 @@ export class UABaseNode {
                 //TODO: parse all types/nodes
                 continue;
             }
-            if(ref.referenceType==ReferencyType.HasComponent && ref.isForward) {
-                this.children.push(node);
-            }
-            if(ref.referenceType==ReferencyType.HasComponent && ref.isForward==false) {
-                node.children.push(this);
-            }
-            if(ref.referenceType==ReferencyType.Organizes && ref.isForward) {
-                this.children.push(node);
-            }
-            if(ref.referenceType==ReferencyType.Organizes && ref.isForward==false) {
-                node.children.push(this);
-            }
+            switch(ref.referenceType) {
+                case ReferencyType.HasComponent:
+                case ReferencyType.Organizes:
+                case ReferencyType.HasProperty:
+                case ReferencyType.HasSubtype:
+                    if(ref.isForward) {
+                        this.children.push(node);
+                    } else {
+                        node.children.push(this);
+                    }
+                    break;
+                }
         }
     }
 
-    static  parse(xmlObject: any): UABaseNode{
+    static  fromXML(xmlObject: any): UABaseNode{
         const xmlReferences=xmlObject['References'];
         const references:UAReference[]=[];
         for(const xmlref of xmlReferences.Reference) {
-            references.push(UAReference.parse(xmlref));
+            references.push(UAReference.fromXML(xmlref));
         }
         const ua=new UABaseNode(NodeId.coerceNodeId(xmlObject['@_NodeId']), xmlObject['@_BrowseName'], xmlObject['DisplayName'], references);
         return ua;
     }
+
+    toXML(): XMLElem {
+        throw new Error("UABaseNode has no xml rep; implement in subtype.");
+    }
 }

+ 88 - 0
src/ua/UANodeSet.ts

@@ -0,0 +1,88 @@
+import {XMLParser, type X2jOptions} from 'fast-xml-parser';
+import { UAObject } from './UAObject';
+import type { UABaseNode } from './UABaseNode';
+import { UAVariable } from './UAVariable';
+import { NamespaceTable } from './NameSpaceTable';
+import { Model } from './Model';
+import { XMLElem, type IToXML } from '@/util/XmlElem';
+import axios from 'axios';
+
+export class UANodeSet implements IToXML{
+
+    constructor(public fileName: string,
+                public models: Model[],
+                public nodes: UABaseNode[],
+                public nameSpaceTable: NamespaceTable) {
+    }
+
+    reIndex(nst: NamespaceTable) {
+        //add all missing namespaces to addressspace ns table
+        for(const value of this.nameSpaceTable.nsMap.getValues()) {
+            nst.addUri(value);
+        }
+        for(const node of this.nodes) {
+            node.reIndex(nst, this.nameSpaceTable);
+        }
+    }
+
+    resolveChildren(nm: Map<string, UABaseNode>) {
+        for(const node of this.nodes) {
+            node.resolveChildren(nm);
+        }    
+    }
+
+    toXML(): XMLElem {
+        const elem =new XMLElem('UANodeSet');
+        const xmlModels=elem.add(new XMLElem('Models'));
+        for(const model of this.models) {
+            xmlModels.add(model.toXML())
+        }
+        return elem;
+    }
+
+    static async load(url: string) {
+        const parseOptions:Partial<X2jOptions>={
+            ignoreAttributes: false,
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
+            isArray: (name, jpath, isLeafNode, isAttribute):boolean => { 
+                switch(jpath) {
+                    case 'UANodeSet.NamespaceUris.Uri':
+                    case 'UANodeSet.UAObject.References.Reference':
+                    case 'UANodeSet.UAObject':
+                    case 'UANodeSet.UAVariable':
+                    case 'UANodeSet.UAVariable.References.Reference':
+                    case 'UANodeSet.Models.Model':
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        }
+        const parser = new XMLParser(parseOptions);
+        const xml= await (await fetch(url)).text()
+        const xmlObj = parser.parse(xml);
+        const models: Model[]=[];
+        const xmlModels=xmlObj['UANodeSet']['Models'];
+        for(const xmlModel of xmlModels.Model) {
+            models.push(Model.fromXML(xmlModel));
+        }
+        const nodes:UABaseNode[]=[];
+        const xmlObjects=xmlObj['UANodeSet']['UAObject'];
+        for(const xmlObject of xmlObjects) {
+            nodes.push(UAObject.fromXML(xmlObject));
+        }
+        const xmlVariables=xmlObj['UANodeSet']['UAVariable'];
+        for(const xmlVariable of xmlVariables) {
+            nodes.push(UAVariable.parse(xmlVariable));
+        }
+        const uaNamespaceUris=xmlObj['UANodeSet']['NamespaceUris'];
+        const nst=new NamespaceTable();
+        if(uaNamespaceUris) {
+            for(const nsUri of uaNamespaceUris['Uri']) {
+                nst.addUri(nsUri)
+            }
+        }
+        const fileName= url.split('/').pop()||url
+        return new UANodeSet(fileName, models, nodes, nst);
+    }
+}

+ 3 - 2
src/UAObject.ts

@@ -10,8 +10,9 @@ export class UAObject extends UABaseNode {
                     super(nodeId, browseName, displayName, references);
     }
 
-    static  parse(uaObject: any): UAObject{
-        const bn=super.parse(uaObject)
+    static  fromXML(uaObject: any): UAObject{
+        const bn=super.fromXML(uaObject)
         return new UAObject(bn.nodeId, bn.browseName, bn.displayName, bn.references);
     }
+    
 }

+ 15 - 4
src/UAReference.ts

@@ -1,13 +1,15 @@
+import { XMLElem, type IToXML } from "@/util/XmlElem";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { NodeId } from "./NodeId";
-import { assert } from "./util/assert";
+import { assert } from "@/util/assert";
 
-export class UAReference {
+export class UAReference implements IToXML{
     constructor(public referenceType: ReferencyType,
                 public ref: NodeId,
                 public isForward: boolean) {
     }
 
+
     reIndex(nst: NamespaceTable, onst: NamespaceTable) {
         const nsName=onst.getUri(this.ref.namespace);
         assert(nsName!=undefined)
@@ -16,7 +18,14 @@ export class UAReference {
         this.ref.namespace=newIndex;    
     }
 
-    static parse(uaReference: any): UAReference {
+    toXML(): XMLElem {
+        return new XMLElem('Reference', this.ref.toString())
+            .attr('ReferenceType', this.referenceType.toString())
+            .attr('IsForward',this.isForward);
+        
+    }
+
+    static fromXML(uaReference: any): UAReference {
         return new UAReference(ReferencyType[uaReference['@_ReferenceType'] as keyof typeof ReferencyType], 
                                 NodeId.coerceNodeId(uaReference['#text']), 
                                 uaReference['@_IsForward']!="false");
@@ -35,5 +44,7 @@ export enum ReferencyType {
     HasDescription =8,
     GeneratesEvent =9,
     AlwaysGeneratesEvent=10,
-    HasNotifier=11
+    HasNotifier=11,
+    HasSubtype=12
+
 }

+ 14 - 1
src/UAVariable.ts

@@ -1,3 +1,4 @@
+import { XMLElem } from "@/util/XmlElem";
 import type { NodeId } from "./NodeId";
 import { UABaseNode } from "./UABaseNode";
 import { UAReference } from "./UAReference";
@@ -11,7 +12,19 @@ export class UAVariable extends UABaseNode {
     }
 
     static  parse(uaObject: any): UAVariable{
-        const bn=super.parse(uaObject)
+        const bn=super.fromXML(uaObject)
         return new UAVariable(bn.nodeId, bn.browseName, bn.displayName, bn.references);
     }
+
+    toXML(): XMLElem {
+        const elem =new XMLElem('UAVariable');
+        elem.attr('NodeID', this.nodeId.toString())
+            .attr('BrowseName', this.browseName)
+            .attr('DisplayName', this.displayName);
+        const refs=elem.add(new XMLElem('References'))
+        for(const ref of this.references) {
+            refs.add(ref.toXML());
+        }
+        return elem;
+    }
 }

+ 50 - 0
src/util/XmlElem.ts

@@ -0,0 +1,50 @@
+export class XMLElem {
+    name: string;
+    value: string;
+    elements: XMLElem[]=[];
+    attributes: XmlAttr[]=[];
+
+    constructor(name: string, value?:string) {
+        this.name=name;
+        this.value=value||'';
+    }
+
+    public add(elem: XMLElem): XMLElem {
+        this.elements.push(elem);
+        return elem;
+    }
+
+    public attr(name:string, value: string|boolean): XMLElem {
+        this.attributes.push(new XmlAttr(name, value?.toString()));
+        return this;
+    }
+
+    public toString(level: number=0): string {
+        let s=" ".repeat(level+1) + `<${this.name} `;
+        for(const attr of this.attributes) 
+            s+=`${attr.name}="${attr.value}" `;
+        s=s.slice(0, -1);
+        s+=`>`
+        for(const elem of this.elements) {
+            s+="\n"+" ".repeat(level) + elem.toString(level+1);
+        }
+        if(this.value && this.elements.length==0) {
+            s+=this.value;
+        }
+        if(this.elements.length>0)
+            s+=" ".repeat(level);
+        s+=`</${this.name}>\n`
+        return s;
+    }
+}
+
+class XmlAttr {
+    constructor(public name: string, 
+                public value: string) {
+                }
+
+}
+
+export interface IToXML {
+    toXML() :XMLElem;
+}

+ 0 - 57
src/util/bi-map.ts

@@ -1,25 +1,3 @@
-/*
- * Created by Trevor Sears <trevor@trevorsears.com> (https://trevorsears.com/).
- * 1:04 PM -- September 14th, 2019.
- * Project: @jsdsl/bimap
- * 
- * @jsdsl/bimap - A bidirectional map written in TypeScript.
- * Copyright (C) 2021 Trevor Sears
- * 
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
 /**
  * A bidirectional map written in TypeScript.
  *
@@ -28,16 +6,12 @@
  * @since v0.1.0
  */
 export class BiMap<K = any, V = any> {
-	
 	private primaryMap: Map<K, V>;
-	
 	private secondaryMap: Map<V, K>;
 	
 	public constructor() {
-	
 		this.primaryMap = new Map<K, V>();
 		this.secondaryMap = new Map<V, K>();
-	
 	}
 
 	public getValues(): IterableIterator<V> {
@@ -45,89 +19,58 @@ export class BiMap<K = any, V = any> {
 	}
 	
 	public get(key: K): V | undefined {
-		
 		return this.getFromKey(key);
-		
 	}
 	
 	public set(key: K, value: V): void {
-		
 		this.setFromKey(key, value);
-		
 	}
 	
 	public getFromKey(key: K): V | undefined {
-		
 		return this.primaryMap.get(key);
-		
 	}
 	
 	public getFromValue(value: V): K | undefined {
-		
 		return this.secondaryMap.get(value);
-		
 	}
 	
 	public setFromKey(key: K, value: V): void {
-	
 		this.primaryMap.set(key, value);
 		this.secondaryMap.set(value, key);
-	
 	}
 	
 	public setFromValue(value: V, key: K): void {
-		
 		this.setFromKey(key, value);
-		
 	}
 	
 	public removeByKey(key: K): V | undefined {
-	
 		if (this.primaryMap.has(key)) {
-			
 			const value: V = this.primaryMap.get(key) as V;
-			
 			this.primaryMap.delete(key);
 			this.secondaryMap.delete(value);
-			
 			return value;
-			
 		} else return undefined;
-	
 	}
 	
 	public removeByValue(value: V): K | undefined {
-		
 		if (this.secondaryMap.has(value)) {
-			
 			const key: K = this.secondaryMap.get(value) as K;
-			
 			this.primaryMap.delete(key);
 			this.secondaryMap.delete(value);
-			
 			return key;
-			
 		} else return undefined;
-	
 	}
 	
 	public hasKey(key: K): boolean {
-	
 		return this.primaryMap.has(key);
-	
 	}
 	
 	public hasValue(value: V): boolean {
-	
 		return this.secondaryMap.has(value);
-	
 	}
 	
 	public clear(): void {
-		
 		this.primaryMap.clear();
 		this.secondaryMap.clear();
-		
 	}
-	
 }

+ 13 - 0
src/util/store.ts

@@ -0,0 +1,13 @@
+import type { AddressSpace } from '@/ua/AddressSpace'
+import { UABaseNode } from '@/ua/UABaseNode'
+import { defineStore } from 'pinia'
+
+export const useStore = defineStore('user', {
+  state: () => {
+    return {
+      addressSpace: null as AddressSpace | null,
+      rootNode: null as UABaseNode | null,
+      selectedNode: null as UABaseNode | null
+    }
+  },
+})

+ 0 - 15
src/views/AboutView.vue

@@ -1,15 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>
-
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
-}
-</style>

+ 0 - 14
src/views/HomeView.vue

@@ -1,14 +0,0 @@
-<script setup lang="ts">
-import TheWelcome from '../components/TheModeler.vue'
-</script>
-
-<template>
-  <main>
-    <Suspense>
-      <TheWelcome />
-    <template #fallback>
-      Loading...
-    </template>
-  </Suspense>
-  </main>
-</template>