diff --git a/ui/angular.json b/ui/angular.json index f189c14..ee919f9 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -3,22 +3,16 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "display": { + "gui-ui": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "standalone": false - }, - "@schematics/angular:directive": { - "standalone": false - }, - "@schematics/angular:pipe": { - "standalone": false + "standalone": true } }, "root": "", "sourceRoot": "src", - "prefix": "app", + "prefix": "core", "architect": { "build": { "builder": "@angular/build:application", @@ -64,10 +58,10 @@ "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "display:build:production" + "buildTarget": "gui-ui:build:production" }, "development": { - "buildTarget": "display:build:development" + "buildTarget": "gui-ui:build:development" } }, "defaultConfiguration": "development" diff --git a/ui/package-lock.json b/ui/package-lock.json index cacf80e..23ee400 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { - "name": "display", - "version": "0.0.0", + "name": "@core/gui-ui", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "display", - "version": "0.0.0", + "name": "@core/gui-ui", + "version": "0.1.0", "dependencies": { "@angular/common": "^20.3.0", "@angular/compiler": "^20.3.0", @@ -916,6 +916,278 @@ "node": ">=0.1.90" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", @@ -933,6 +1205,159 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@inquirer/ansi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", @@ -1446,6 +1871,62 @@ "listr2": "9.0.1" } }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", + "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", + "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", + "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", + "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@lmdb/lmdb-linux-x64": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", @@ -1460,6 +1941,34 @@ "linux" ] }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", + "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", + "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.17.3", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", @@ -1508,6 +2017,62 @@ "dev": true, "license": "MIT" }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", @@ -1522,6 +2087,20 @@ "linux" ] }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@napi-rs/nice": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", @@ -1556,6 +2135,208 @@ "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/nice-linux-x64-gnu": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", @@ -1590,6 +2371,74 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@npmcli/agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", @@ -1923,6 +2772,186 @@ "@parcel/watcher-win32-x64": "2.5.1" } }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@parcel/watcher-linux-x64-glibc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", @@ -1965,6 +2994,69 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@parcel/watcher/node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -1998,6 +3090,243 @@ "node": ">=14" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", + "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", + "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", + "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", + "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", + "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", + "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", + "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", + "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", + "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", + "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", + "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", + "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", + "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", + "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", + "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.52.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", @@ -2026,6 +3355,76 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", + "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", + "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", + "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", + "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", + "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@schematics/angular": { "version": "20.3.9", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.9.tgz", @@ -3868,6 +5267,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/ui/package.json b/ui/package.json index 125d43e..3c955e4 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { - "name": "display", - "version": "0.0.0", + "name": "@core/gui-ui", + "version": "0.1.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/ui/src/app/app-module.ts b/ui/src/app/app-module.ts index 77615dc..bf50b48 100644 --- a/ui/src/app/app-module.ts +++ b/ui/src/app/app-module.ts @@ -1,3 +1,5 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + import { DoBootstrap, Injector, NgModule, provideBrowserGlobalErrorListeners } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { createCustomElement } from '@angular/elements'; @@ -5,13 +7,8 @@ import { createCustomElement } from '@angular/elements'; import { App } from './app'; @NgModule({ - imports: [ - BrowserModule, - App - ], - providers: [ - provideBrowserGlobalErrorListeners() - ] + imports: [BrowserModule, App], + providers: [provideBrowserGlobalErrorListeners()], }) export class AppModule implements DoBootstrap { constructor(private injector: Injector) { diff --git a/ui/src/components/provider-host.component.ts b/ui/src/components/provider-host.component.ts new file mode 100644 index 0000000..6527cb2 --- /dev/null +++ b/ui/src/components/provider-host.component.ts @@ -0,0 +1,96 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { + Component, + CUSTOM_ELEMENTS_SCHEMA, + ElementRef, + Input, + OnChanges, + OnInit, + Renderer2, + ViewChild, +} from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ApiConfigService } from '../services/api-config.service'; +import { ProviderDiscoveryService } from '../services/provider-discovery.service'; + +/** + * ProviderHostComponent renders any custom element by tag name using + * Angular's Renderer2 for safe DOM manipulation. It reads the :provider + * route parameter to look up the element tag from the discovery service. + */ +@Component({ + selector: 'provider-host', + standalone: true, + schemas: [CUSTOM_ELEMENTS_SCHEMA], + template: '
', + styles: [ + ` + :host { + display: block; + width: 100%; + height: 100%; + } + .provider-host { + width: 100%; + height: 100%; + } + `, + ], +}) +export class ProviderHostComponent implements OnInit, OnChanges { + /** The custom element tag to render. Can be set via input or route param. */ + @Input() tag = ''; + + /** API URL attribute passed to the custom element. */ + @Input() apiUrl = ''; + + @ViewChild('container', { static: true }) container!: ElementRef; + + constructor( + private renderer: Renderer2, + private route: ActivatedRoute, + private apiConfig: ApiConfigService, + private providerService: ProviderDiscoveryService, + ) {} + + ngOnInit(): void { + this.route.params.subscribe((params) => { + const providerName = params['provider']; + if (providerName) { + const provider = this.providerService + .providers() + .find((p) => p.name.toLowerCase() === providerName.toLowerCase()); + if (provider?.element?.tag) { + this.tag = provider.element.tag; + this.renderElement(); + } + } + }); + } + + ngOnChanges(): void { + this.renderElement(); + } + + private renderElement(): void { + if (!this.tag || !this.container) { + return; + } + + const native = this.container.nativeElement; + + // Clear previous element safely + while (native.firstChild) { + this.renderer.removeChild(native, native.firstChild); + } + + // Create and append the custom element + const el = this.renderer.createElement(this.tag); + const url = this.apiUrl || this.apiConfig.baseUrl; + if (url) { + this.renderer.setAttribute(el, 'api-url', url); + } + this.renderer.appendChild(native, el); + } +} diff --git a/ui/src/components/provider-nav.component.ts b/ui/src/components/provider-nav.component.ts new file mode 100644 index 0000000..ee66143 --- /dev/null +++ b/ui/src/components/provider-nav.component.ts @@ -0,0 +1,132 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Component, computed, Input, signal } from '@angular/core'; +import { RouterLink, RouterLinkActive } from '@angular/router'; +import { ProviderDiscoveryService, ProviderInfo } from '../services/provider-discovery.service'; + +export interface NavItem { + name: string; + href: string; + icon: string; + element?: { tag: string; source: string }; +} + +/** + * ProviderNavComponent renders the sidebar navigation built dynamically + * from the provider discovery service. Shows icon-only in collapsed mode, + * expands on click. + */ +@Component({ + selector: 'provider-nav', + standalone: true, + imports: [RouterLink, RouterLinkActive], + template: ` + + `, + styles: [ + ` + .provider-nav { + display: flex; + flex-direction: column; + flex: 1; + } + + .nav-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + } + + .nav-list li { + width: 100%; + } + + .nav-item { + display: flex; + justify-content: center; + align-items: center; + gap: 0.75rem; + padding: 1rem; + border-radius: 0.375rem; + color: #9ca3af; + text-decoration: none; + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5; + transition: background 0.15s, color 0.15s; + height: 4rem; + } + + .nav-item:hover { + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + } + + .nav-item.active { + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + } + + .nav-item i { + flex-shrink: 0; + } + + .nav-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + `, + ], +}) +export class ProviderNavComponent { + /** Additional static navigation items to prepend. */ + @Input() staticItems: NavItem[] = []; + + readonly expanded = signal(false); + + constructor(private providerService: ProviderDiscoveryService) {} + + /** Dynamic navigation built from discovered providers and static items. */ + readonly navItems = computed(() => { + const dynamicItems = this.providerService + .providers() + .filter((p: ProviderInfo) => p.element) + .map((p: ProviderInfo) => ({ + name: p.name, + href: p.name.toLowerCase(), + icon: 'fa-regular fa-puzzle-piece fa-2xl', + element: p.element, + })); + + return [...this.staticItems, ...dynamicItems]; + }); + + /** Toggle sidebar expansion. */ + toggle(): void { + this.expanded.update((v) => !v); + } +} diff --git a/ui/src/components/status-bar.component.ts b/ui/src/components/status-bar.component.ts new file mode 100644 index 0000000..705be71 --- /dev/null +++ b/ui/src/components/status-bar.component.ts @@ -0,0 +1,122 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Component, Input, OnDestroy, OnInit, signal } from '@angular/core'; +import { ProviderDiscoveryService } from '../services/provider-discovery.service'; +import { WebSocketService } from '../services/websocket.service'; + +/** + * StatusBarComponent renders the footer bar showing time, version, + * provider count, and connection status. + */ +@Component({ + selector: 'status-bar', + standalone: true, + template: ` + + `, + styles: [ + ` + .status-bar { + position: fixed; + inset-inline: 0; + bottom: 0; + z-index: 40; + height: 2.5rem; + border-top: 1px solid rgb(229 231 235); + background: #ffffff; + display: flex; + align-items: center; + justify-content: space-between; + padding-inline: 1rem; + } + + :host-context(.dark) .status-bar { + border-color: rgba(255, 255, 255, 0.1); + background: rgb(17 24 39); + } + + .status-left, + .status-right { + display: flex; + align-items: center; + gap: 1rem; + } + + .status-item { + font-size: 0.875rem; + color: rgb(107 114 128); + } + + :host-context(.dark) .status-item { + color: rgb(156 163 175); + } + + .status-item i { + margin-right: 0.25rem; + } + + .status-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: rgb(107 114 128); + margin-right: 0.375rem; + } + + .connection.connected .status-dot { + background: rgb(34 197 94); + box-shadow: 0 0 4px rgb(34 197 94); + } + + .time { + font-family: 'JetBrains Mono', 'Fira Code', monospace; + } + `, + ], +}) +export class StatusBarComponent implements OnInit, OnDestroy { + @Input() version = 'v0.1.0'; + @Input() sidebarWidth = '5rem'; + + readonly time = signal(''); + private intervalId: ReturnType | undefined; + + constructor( + private providerService: ProviderDiscoveryService, + private wsService: WebSocketService, + ) {} + + readonly providerCount = () => this.providerService.providers().length; + readonly wsConnected = () => this.wsService.connected(); + + ngOnInit(): void { + this.updateTime(); + this.intervalId = setInterval(() => this.updateTime(), 1000); + } + + ngOnDestroy(): void { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + + private updateTime(): void { + this.time.set(new Date().toLocaleTimeString()); + } +} diff --git a/ui/src/frame/application-frame.component.html b/ui/src/frame/application-frame.component.html new file mode 100644 index 0000000..5c2612e --- /dev/null +++ b/ui/src/frame/application-frame.component.html @@ -0,0 +1,179 @@ + + + +
+
+ + + + + +
+
+ + +
+
+ + + + + + +
+ + @if (userMenuOpen) { +
+ @for (item of userNavigation; track item.name) { + + + {{ item.name }} + + } +
+ } +
+
+
+
+ + + +
+
+
+ +
+
+
+ +
+
+ + {{ version }} + + {{ providerCount() }} + + + + {{ time }} + +
+
+
diff --git a/ui/src/frame/application-frame.component.ts b/ui/src/frame/application-frame.component.ts new file mode 100644 index 0000000..6a10409 --- /dev/null +++ b/ui/src/frame/application-frame.component.ts @@ -0,0 +1,137 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; +import { TranslationService } from '../services/translation.service'; +import { ProviderDiscoveryService } from '../services/provider-discovery.service'; +import { WebSocketService } from '../services/websocket.service'; + +interface NavItem { + name: string; + href: string; + icon: string; +} + +/** + * ApplicationFrameComponent is the HLCRF (Header, Left nav, Content, Right, Footer) + * shell for all Core Wails applications. It provides: + * + * - Dynamic sidebar navigation populated from ProviderDiscoveryService + * - Content area rendered via router-outlet for child routes + * - Footer with time, version, and provider status + * - Mobile-responsive sidebar with expand/collapse + * - Dark mode support + * + * Ported from core-gui/cmd/lthn-desktop/frontend/src/frame/application.frame.ts + * with navigation made dynamic via provider discovery. + */ +@Component({ + selector: 'application-frame', + standalone: true, + schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], + templateUrl: './application-frame.component.html', + styles: [ + ` + .application-frame { + min-height: 100vh; + } + + .frame-main { + min-height: calc(100vh - 6.5rem); + } + + .connection-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: rgb(107 114 128); + margin-right: 0.375rem; + vertical-align: middle; + } + + .connection-dot.connected { + background: rgb(34 197 94); + box-shadow: 0 0 4px rgb(34 197 94); + } + `, + ], +}) +export class ApplicationFrameComponent implements OnInit, OnDestroy { + @Input() version = 'v0.1.0'; + + sidebarOpen = false; + userMenuOpen = false; + time = ''; + private intervalId: ReturnType | undefined; + + /** Static navigation items set by the host application. */ + @Input() staticNavigation: NavItem[] = []; + + /** Combined navigation: static + dynamic from providers. */ + navigation: NavItem[] = []; + + userNavigation: NavItem[] = []; + + constructor( + public t: TranslationService, + private providerService: ProviderDiscoveryService, + private wsService: WebSocketService, + ) {} + + /** Provider count from discovery service. */ + readonly providerCount = () => this.providerService.providers().length; + + /** WebSocket connection status. */ + readonly wsConnected = () => this.wsService.connected(); + + async ngOnInit(): Promise { + this.updateTime(); + this.intervalId = setInterval(() => this.updateTime(), 1000); + + await this.t.onReady(); + this.initUserNavigation(); + + // Discover providers and build navigation + await this.providerService.discover(); + this.buildNavigation(); + + // Connect WebSocket for real-time updates + this.wsService.connect(); + } + + ngOnDestroy(): void { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + + private initUserNavigation(): void { + this.userNavigation = [ + { + name: this.t._('menu.settings'), + href: 'settings', + icon: 'fa-regular fa-gear', + }, + ]; + } + + private buildNavigation(): void { + const dynamicItems = this.providerService + .providers() + .filter((p) => p.element) + .map((p) => ({ + name: p.name, + href: p.name.toLowerCase(), + icon: 'fa-regular fa-puzzle-piece fa-2xl shrink-0', + })); + + this.navigation = [...this.staticNavigation, ...dynamicItems]; + } + + private updateTime(): void { + this.time = new Date().toLocaleTimeString(); + } +} diff --git a/ui/src/frame/system-tray-frame.component.ts b/ui/src/frame/system-tray-frame.component.ts new file mode 100644 index 0000000..3c7604c --- /dev/null +++ b/ui/src/frame/system-tray-frame.component.ts @@ -0,0 +1,388 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Component, OnDestroy, OnInit, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ProviderDiscoveryService, ProviderInfo } from '../services/provider-discovery.service'; +import { WebSocketService } from '../services/websocket.service'; + +/** + * SystemTrayFrameComponent is a 380x480 frameless panel showing: + * - Provider status cards from the discovery service + * - Brain connection status + * - MCP server status + * - Quick actions + * + * Ported from core-gui/cmd/lthn-desktop/frontend/src/frame/system-tray.frame.ts + * with dynamic provider status cards. + */ +@Component({ + selector: 'system-tray-frame', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+ +
+ +
+
+ + + @if (settingsMenuOpen) { +
+ @for (item of settingsNavigation; track item.name) { + + } +
+ } + + +
+
+ Connection + + {{ wsConnected() ? 'Connected' : 'Disconnected' }} + +
+
+ Providers + {{ providers().length }} +
+
+ Time + {{ time() }} +
+
+ + +
+
Providers
+
+ @for (provider of providers(); track provider.name) { +
+
+ +
+
+ {{ provider.name }} + {{ provider.basePath }} +
+
+ +
+
+ } @empty { +
No providers registered
+ } +
+
+ + + +
+ `, + styles: [ + ` + :host { + display: block; + width: 100%; + height: 100%; + overflow: hidden; + } + + .tray-container { + display: flex; + flex-direction: column; + height: 100%; + background: rgb(17 24 39); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + color: rgb(156 163 175); + border-radius: 0.375rem; + } + + .tray-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1rem; + background: rgb(31 41 55); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .tray-logo { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.9375rem; + font-weight: 600; + color: #ffffff; + } + + .logo-icon { + color: rgb(129 140 248); + } + + .tray-controls { + display: flex; + gap: 0.5rem; + } + + .control-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 6px; + color: rgb(156 163 175); + cursor: pointer; + transition: all 0.15s ease; + } + + .control-btn:hover { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.2); + color: #ffffff; + } + + .settings-menu { + background: rgb(31 41 55); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.5rem; + } + + .settings-item { + display: block; + width: 100%; + text-align: left; + padding: 0.5rem 0.75rem; + background: transparent; + border: none; + border-radius: 0.25rem; + color: rgb(156 163 175); + font-size: 0.875rem; + cursor: pointer; + } + + .settings-item:hover { + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + } + + .status-section { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.875rem 1rem; + background: rgb(31 41 55); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .status-row { + display: flex; + align-items: center; + justify-content: space-between; + } + + .status-label { + font-size: 0.8125rem; + color: rgb(107 114 128); + } + + .status-value { + font-size: 0.875rem; + font-weight: 600; + color: #ffffff; + } + + .status-value.active { + color: rgb(34 197 94); + } + + .status-value.mono { + font-family: 'JetBrains Mono', 'Fira Code', monospace; + font-size: 0.8125rem; + } + + .providers-section { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + } + + .section-header { + padding: 0.625rem 1rem; + font-size: 0.6875rem; + font-weight: 600; + color: rgb(107 114 128); + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .providers-list { + flex: 1; + overflow-y: auto; + padding: 0.5rem; + } + + .provider-card { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.625rem 0.75rem; + border-radius: 6px; + transition: background 0.15s ease; + } + + .provider-card:hover { + background: rgba(255, 255, 255, 0.05); + } + + .provider-icon { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: rgba(129, 140, 248, 0.15); + border-radius: 6px; + color: rgb(129 140 248); + } + + .provider-info { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + } + + .provider-name { + font-size: 0.8125rem; + font-weight: 500; + color: #ffffff; + } + + .provider-path { + font-size: 0.6875rem; + color: rgb(107 114 128); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .provider-status { + display: flex; + align-items: center; + } + + .status-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgb(107 114 128); + } + + .status-indicator.active { + background: rgb(34 197 94); + box-shadow: 0 0 4px rgb(34 197 94); + } + + .no-providers { + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + color: rgb(107 114 128); + font-size: 0.8125rem; + } + + .tray-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.625rem 1rem; + background: rgb(31 41 55); + border-top: 1px solid rgba(255, 255, 255, 0.1); + } + + .connection-status { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; + color: rgb(107 114 128); + } + + .footer-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: rgb(107 114 128); + } + + .connection-status.connected .footer-dot { + background: rgb(34 197 94); + box-shadow: 0 0 4px rgb(34 197 94); + } + + .connection-status.connected { + color: rgb(34 197 94); + } + `, + ], +}) +export class SystemTrayFrameComponent implements OnInit, OnDestroy { + settingsMenuOpen = false; + readonly time = signal(''); + private intervalId: ReturnType | undefined; + + settingsNavigation = [ + { name: 'Settings', href: '#' }, + { name: 'About', href: '#' }, + { name: 'Check for Updates...', href: '#' }, + ]; + + constructor( + private providerService: ProviderDiscoveryService, + private wsService: WebSocketService, + ) {} + + readonly providers = () => this.providerService.providers(); + readonly wsConnected = () => this.wsService.connected(); + + async ngOnInit(): Promise { + this.updateTime(); + this.intervalId = setInterval(() => this.updateTime(), 1000); + + // Discover providers for status cards + await this.providerService.discover(); + } + + ngOnDestroy(): void { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + + private updateTime(): void { + this.time.set(new Date().toLocaleTimeString()); + } +} diff --git a/ui/src/index.ts b/ui/src/index.ts new file mode 100644 index 0000000..1ae91f4 --- /dev/null +++ b/ui/src/index.ts @@ -0,0 +1,16 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +// Frame components +export { ApplicationFrameComponent } from './frame/application-frame.component'; +export { SystemTrayFrameComponent } from './frame/system-tray-frame.component'; + +// Services +export { ApiConfigService } from './services/api-config.service'; +export { ProviderDiscoveryService, type ProviderInfo, type ElementSpec } from './services/provider-discovery.service'; +export { WebSocketService, type WSMessage } from './services/websocket.service'; +export { TranslationService } from './services/translation.service'; + +// Components +export { ProviderHostComponent } from './components/provider-host.component'; +export { ProviderNavComponent, type NavItem } from './components/provider-nav.component'; +export { StatusBarComponent } from './components/status-bar.component'; diff --git a/ui/src/main.ts b/ui/src/main.ts index 40c6c68..a943f56 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -1,7 +1,10 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + import { platformBrowser } from '@angular/platform-browser'; import { AppModule } from './app/app-module'; -platformBrowser().bootstrapModule(AppModule, { - ngZoneEventCoalescing: true, -}) - .catch(err => console.error(err)); +platformBrowser() + .bootstrapModule(AppModule, { + ngZoneEventCoalescing: true, + }) + .catch((err) => console.error(err)); diff --git a/ui/src/services/api-config.service.ts b/ui/src/services/api-config.service.ts new file mode 100644 index 0000000..dd41b2a --- /dev/null +++ b/ui/src/services/api-config.service.ts @@ -0,0 +1,29 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Injectable } from '@angular/core'; + +/** + * ApiConfigService provides a configurable base URL for all API calls. + * Defaults to the current origin (Wails embedded) but can be overridden + * for development or remote connections. + */ +@Injectable({ providedIn: 'root' }) +export class ApiConfigService { + private _baseUrl = ''; + + /** The API base URL without a trailing slash. */ + get baseUrl(): string { + return this._baseUrl; + } + + /** Override the base URL. Strips trailing slash if present. */ + set baseUrl(url: string) { + this._baseUrl = url.replace(/\/+$/, ''); + } + + /** Build a full URL for the given path. */ + url(path: string): string { + const cleanPath = path.startsWith('/') ? path : `/${path}`; + return `${this._baseUrl}${cleanPath}`; + } +} diff --git a/ui/src/services/provider-discovery.service.ts b/ui/src/services/provider-discovery.service.ts new file mode 100644 index 0000000..2cc8410 --- /dev/null +++ b/ui/src/services/provider-discovery.service.ts @@ -0,0 +1,91 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Injectable, signal } from '@angular/core'; +import { ApiConfigService } from './api-config.service'; + +/** + * Describes the element specification for a renderable provider. + */ +export interface ElementSpec { + tag: string; + source: string; +} + +/** + * Describes a provider as returned by GET /api/v1/providers. + */ +export interface ProviderInfo { + name: string; + basePath: string; + status?: string; + element?: ElementSpec; + channels?: string[]; +} + +/** + * ProviderDiscoveryService fetches the list of registered providers from + * the API server and dynamically loads custom element scripts for any + * Renderable providers. + */ +@Injectable({ providedIn: 'root' }) +export class ProviderDiscoveryService { + private readonly _providers = signal([]); + readonly providers = this._providers.asReadonly(); + + private discovered = false; + + constructor(private apiConfig: ApiConfigService) {} + + /** Fetch providers from the API and load custom element scripts. */ + async discover(): Promise { + if (this.discovered) { + return; + } + + try { + const res = await fetch(this.apiConfig.url('/api/v1/providers')); + if (!res.ok) { + console.warn('ProviderDiscoveryService: failed to fetch providers:', res.statusText); + return; + } + + const data = await res.json(); + const providers: ProviderInfo[] = data.providers ?? []; + this._providers.set(providers); + this.discovered = true; + + // Load custom elements for Renderable providers + for (const p of providers) { + if (p.element?.tag && p.element?.source) { + await this.loadElement(p.element.tag, p.element.source); + } + } + } catch (err) { + console.warn('ProviderDiscoveryService: discovery failed:', err); + } + } + + /** Refresh the provider list (force re-discovery). */ + async refresh(): Promise { + this.discovered = false; + await this.discover(); + } + + /** Dynamically load a custom element script if not already registered. */ + private async loadElement(tag: string, source: string): Promise { + if (customElements.get(tag)) { + return; + } + + const script = document.createElement('script'); + script.type = 'module'; + script.src = source.startsWith('http') ? source : this.apiConfig.url(source); + document.head.appendChild(script); + + try { + await customElements.whenDefined(tag); + } catch (err) { + console.warn(`ProviderDiscoveryService: failed to load element <${tag}>:`, err); + } + } +} diff --git a/ui/src/services/translation.service.ts b/ui/src/services/translation.service.ts new file mode 100644 index 0000000..a904695 --- /dev/null +++ b/ui/src/services/translation.service.ts @@ -0,0 +1,66 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Injectable } from '@angular/core'; +import { ApiConfigService } from './api-config.service'; + +/** + * TranslationService provides a simple key-value translation lookup. + * In production mode it fetches translations from the API; in development + * it falls back to returning the key as-is. + */ +@Injectable({ providedIn: 'root' }) +export class TranslationService { + private translations = new Map(); + private loaded = false; + private loadingPromise: Promise; + + constructor(private apiConfig: ApiConfigService) { + this.loadingPromise = this.loadTranslations('en'); + } + + /** Reload translations for a given language. */ + reload(lang: string): Promise { + this.loaded = false; + this.loadingPromise = this.loadTranslations(lang); + return this.loadingPromise; + } + + /** Translate a key. Returns the key itself if no translation is found. */ + translate(key: string): string { + if (!this.loaded) { + return key; + } + return this.translations.get(key) ?? key; + } + + /** Shorthand for translate(). */ + _ = (key: string): string => this.translate(key); + + /** Wait for the initial translation load to complete. */ + onReady(): Promise { + return this.loadingPromise; + } + + private async loadTranslations(lang: string): Promise { + try { + const res = await fetch(this.apiConfig.url(`/api/v1/i18n/${lang}`)); + if (!res.ok) { + // API not available — translations will fall through to keys + this.loaded = true; + return; + } + + const messages: Record = await res.json(); + this.translations.clear(); + for (const key in messages) { + if (Object.prototype.hasOwnProperty.call(messages, key)) { + this.translations.set(key, messages[key]); + } + } + this.loaded = true; + } catch { + // Silently fall through — key passthrough is acceptable + this.loaded = true; + } + } +} diff --git a/ui/src/services/websocket.service.ts b/ui/src/services/websocket.service.ts new file mode 100644 index 0000000..7c1f179 --- /dev/null +++ b/ui/src/services/websocket.service.ts @@ -0,0 +1,126 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { Injectable, OnDestroy, signal } from '@angular/core'; +import { ApiConfigService } from './api-config.service'; + +export interface WSMessage { + channel: string; + data: unknown; +} + +/** + * WebSocketService manages a persistent WebSocket connection with automatic + * reconnection. Follows the same pattern used by Mining's websocket.service.ts. + */ +@Injectable({ providedIn: 'root' }) +export class WebSocketService implements OnDestroy { + private ws: WebSocket | null = null; + private reconnectTimer: ReturnType | null = null; + private listeners = new Map void>>(); + private reconnectDelay = 1000; + private maxReconnectDelay = 30000; + private shouldReconnect = true; + + readonly connected = signal(false); + + constructor(private apiConfig: ApiConfigService) {} + + /** Open the WebSocket connection. */ + connect(path = '/ws'): void { + if (this.ws) { + return; + } + + this.shouldReconnect = true; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const base = this.apiConfig.baseUrl || window.location.origin; + const wsBase = base.replace(/^http/, 'ws'); + const url = `${wsBase.length > 0 ? wsBase : `${protocol}//${window.location.host}`}${path}`; + + this.ws = new WebSocket(url); + + this.ws.onopen = () => { + this.connected.set(true); + this.reconnectDelay = 1000; + }; + + this.ws.onclose = () => { + this.connected.set(false); + this.ws = null; + this.scheduleReconnect(path); + }; + + this.ws.onerror = () => { + this.ws?.close(); + }; + + this.ws.onmessage = (event) => { + try { + const msg: WSMessage = JSON.parse(event.data as string); + this.dispatch(msg.channel, msg.data); + } catch { + // Silently ignore malformed messages + } + }; + } + + /** Subscribe to a channel. Returns an unsubscribe function. */ + on(channel: string, callback: (data: unknown) => void): () => void { + if (!this.listeners.has(channel)) { + this.listeners.set(channel, new Set()); + } + this.listeners.get(channel)!.add(callback); + + return () => { + const set = this.listeners.get(channel); + if (set) { + set.delete(callback); + if (set.size === 0) { + this.listeners.delete(channel); + } + } + }; + } + + /** Send a message over the WebSocket. */ + send(channel: string, data: unknown): void { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify({ channel, data })); + } + } + + /** Disconnect and stop reconnecting. */ + disconnect(): void { + this.shouldReconnect = false; + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + this.ws?.close(); + this.ws = null; + } + + ngOnDestroy(): void { + this.disconnect(); + } + + private dispatch(channel: string, data: unknown): void { + // Exact match + this.listeners.get(channel)?.forEach((cb) => cb(data)); + // Wildcard match + this.listeners.get('*')?.forEach((cb) => cb({ channel, data })); + } + + private scheduleReconnect(path: string): void { + if (!this.shouldReconnect) { + return; + } + + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = null; + this.connect(path); + }, this.reconnectDelay); + + this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay); + } +} diff --git a/ui/tsconfig.app.json b/ui/tsconfig.app.json index 264f459..4b472ff 100644 --- a/ui/tsconfig.app.json +++ b/ui/tsconfig.app.json @@ -1,5 +1,3 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { diff --git a/ui/tsconfig.json b/ui/tsconfig.json index e4955f2..4fd7eaa 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -1,5 +1,3 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "compileOnSave": false, "compilerOptions": { @@ -13,7 +11,12 @@ "experimentalDecorators": true, "importHelpers": true, "target": "ES2022", - "module": "preserve" + "module": "preserve", + "baseUrl": "./", + "paths": { + "@core/gui-ui": ["src/index.ts"], + "@core/gui-ui/*": ["src/*"] + } }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false,