mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
finish setup wizard and ui language-keyboard feature
This commit is contained in:
52
web/package-lock.json
generated
52
web/package-lock.json
generated
@@ -386,7 +386,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz",
|
||||||
"integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==",
|
"integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "8.17.1",
|
"ajv": "8.17.1",
|
||||||
"ajv-formats": "3.0.1",
|
"ajv-formats": "3.0.1",
|
||||||
@@ -414,7 +413,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.13.tgz",
|
||||||
"integrity": "sha512-hdMKY4rUTko8xqeWYGnwwDYDomkeOoLsYsP6SdaHWK7hpGvzWsT6Q/aIv8J8NrCYkLu+M+5nLiKOooweUZu3GQ==",
|
"integrity": "sha512-hdMKY4rUTko8xqeWYGnwwDYDomkeOoLsYsP6SdaHWK7hpGvzWsT6Q/aIv8J8NrCYkLu+M+5nLiKOooweUZu3GQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/core": "20.3.13",
|
"@angular-devkit/core": "20.3.13",
|
||||||
"jsonc-parser": "3.3.1",
|
"jsonc-parser": "3.3.1",
|
||||||
@@ -451,7 +449,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.15.tgz",
|
||||||
"integrity": "sha512-ikyKfhkxoqQA6JcBN0B9RaN6369sM1XYX81Id0lI58dmWCe7gYfrTp8ejqxxKftl514psQO3pkW8Gn1nJ131Gw==",
|
"integrity": "sha512-ikyKfhkxoqQA6JcBN0B9RaN6369sM1XYX81Id0lI58dmWCe7gYfrTp8ejqxxKftl514psQO3pkW8Gn1nJ131Gw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -566,7 +563,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz",
|
||||||
"integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==",
|
"integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parse5": "^8.0.0",
|
"parse5": "^8.0.0",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
@@ -583,7 +579,6 @@
|
|||||||
"integrity": "sha512-G78I/HDJULloS2LSqfUfbmBlhDCbcWujIRWfuMnGsRf82TyGA2OEPe3IA/F8MrJfeOzPQim2fMyn24MqHL40Vg==",
|
"integrity": "sha512-G78I/HDJULloS2LSqfUfbmBlhDCbcWujIRWfuMnGsRf82TyGA2OEPe3IA/F8MrJfeOzPQim2fMyn24MqHL40Vg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/architect": "0.2003.13",
|
"@angular-devkit/architect": "0.2003.13",
|
||||||
"@angular-devkit/core": "20.3.13",
|
"@angular-devkit/core": "20.3.13",
|
||||||
@@ -618,7 +613,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.15.tgz",
|
||||||
"integrity": "sha512-k4mCXWRFiOHK3bUKfWkRQQ8KBPxW8TAJuKLYCsSHPCpMz6u0eA1F0VlrnOkZVKWPI792fOaEAWH2Y4PTaXlUHw==",
|
"integrity": "sha512-k4mCXWRFiOHK3bUKfWkRQQ8KBPxW8TAJuKLYCsSHPCpMz6u0eA1F0VlrnOkZVKWPI792fOaEAWH2Y4PTaXlUHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -635,7 +629,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.15.tgz",
|
||||||
"integrity": "sha512-lMicIAFAKZXa+BCZWs3soTjNQPZZXrF/WMVDinm8dQcggNarnDj4UmXgKSyXkkyqK5SLfnLsXVzrX6ndVT6z7A==",
|
"integrity": "sha512-lMicIAFAKZXa+BCZWs3soTjNQPZZXrF/WMVDinm8dQcggNarnDj4UmXgKSyXkkyqK5SLfnLsXVzrX6ndVT6z7A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -649,7 +642,6 @@
|
|||||||
"integrity": "sha512-8sJoxodxsfyZ8eJ5r6Bx7BCbazXYgsZ1+dE8t5u5rTQ6jNggwNtYEzkyReoD5xvP+MMtRkos3xpwq4rtFnpI6A==",
|
"integrity": "sha512-8sJoxodxsfyZ8eJ5r6Bx7BCbazXYgsZ1+dE8t5u5rTQ6jNggwNtYEzkyReoD5xvP+MMtRkos3xpwq4rtFnpI6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.28.3",
|
"@babel/core": "7.28.3",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||||
@@ -682,7 +674,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.15.tgz",
|
||||||
"integrity": "sha512-NMbX71SlTZIY9+rh/SPhRYFJU0pMJYW7z/TBD4lqiO+b0DTOIg1k7Pg9ydJGqSjFO1Z4dQaA6TteNuF99TJCNw==",
|
"integrity": "sha512-NMbX71SlTZIY9+rh/SPhRYFJU0pMJYW7z/TBD4lqiO+b0DTOIg1k7Pg9ydJGqSjFO1Z4dQaA6TteNuF99TJCNw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -708,7 +699,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.15.tgz",
|
||||||
"integrity": "sha512-gS5hQkinq52pm/7mxz4yHPCzEcmRWjtUkOVddPH0V1BW/HMni/p4Y6k2KqKBeGb9p8S5EAp6PDxDVLOPukp3mg==",
|
"integrity": "sha512-gS5hQkinq52pm/7mxz4yHPCzEcmRWjtUkOVddPH0V1BW/HMni/p4Y6k2KqKBeGb9p8S5EAp6PDxDVLOPukp3mg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -737,7 +727,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.15.tgz",
|
||||||
"integrity": "sha512-TxRM/wTW/oGXv/3/Iohn58yWoiYXOaeEnxSasiGNS1qhbkcKtR70xzxW6NjChBUYAixz2ERkLURkpx3pI8Q6Dw==",
|
"integrity": "sha512-TxRM/wTW/oGXv/3/Iohn58yWoiYXOaeEnxSasiGNS1qhbkcKtR70xzxW6NjChBUYAixz2ERkLURkpx3pI8Q6Dw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -802,7 +791,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.15.tgz",
|
||||||
"integrity": "sha512-6+qgk8swGSoAu7ISSY//GatAyCP36hEvvUgvjbZgkXLLH9yUQxdo77ij05aJ5s0OyB25q/JkqS8VTY0z1yE9NQ==",
|
"integrity": "sha512-6+qgk8swGSoAu7ISSY//GatAyCP36hEvvUgvjbZgkXLLH9yUQxdo77ij05aJ5s0OyB25q/JkqS8VTY0z1yE9NQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -821,7 +809,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-20.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-20.3.15.tgz",
|
||||||
"integrity": "sha512-HCptODPVWg30XJwSueOz2zqsJjQ1chSscTs7FyIQcfuCTTthO35Lvz2Gtct8/GNHel9QNvvVwA5jrLjsU4dt1A==",
|
"integrity": "sha512-HCptODPVWg30XJwSueOz2zqsJjQ1chSscTs7FyIQcfuCTTthO35Lvz2Gtct8/GNHel9QNvvVwA5jrLjsU4dt1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -867,7 +854,6 @@
|
|||||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -1832,7 +1818,6 @@
|
|||||||
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
|
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inquirer/checkbox": "^4.2.1",
|
"@inquirer/checkbox": "^4.2.1",
|
||||||
"@inquirer/confirm": "^5.1.14",
|
"@inquirer/confirm": "^5.1.14",
|
||||||
@@ -3451,7 +3436,6 @@
|
|||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3971,7 +3955,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.66.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.66.0.tgz",
|
||||||
"integrity": "sha512-tRWyuqK5j5nEjlk0x5HaeLArgVpAIJZNeMiPy//95v4/8tlHdQLM4gh3qcvwS70GN5fnlFXINWhnblvxSDv2dw==",
|
"integrity": "sha512-tRWyuqK5j5nEjlk0x5HaeLArgVpAIJZNeMiPy//95v4/8tlHdQLM4gh3qcvwS70GN5fnlFXINWhnblvxSDv2dw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
},
|
},
|
||||||
@@ -4037,7 +4020,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.66.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.66.0.tgz",
|
||||||
"integrity": "sha512-5DFbwHo7JHKBjgizbGTaIRJsai20+ZknhOQ1SRYwRTc9+6C1HbY/gGC+cjJTLmEQvk14rOoz8qbeWzJx88BU2Q==",
|
"integrity": "sha512-5DFbwHo7JHKBjgizbGTaIRJsai20+ZknhOQ1SRYwRTc9+6C1HbY/gGC+cjJTLmEQvk14rOoz8qbeWzJx88BU2Q==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
},
|
},
|
||||||
@@ -4069,7 +4051,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.66.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.66.0.tgz",
|
||||||
"integrity": "sha512-AjjH+xhgonjf9Xnx3SHNrP5VbsS9jdtGB3BCTQbicYd6QuujQBKldK0fnYMjCY3L0+lboI2OPCVg9PTliOdJ8A==",
|
"integrity": "sha512-AjjH+xhgonjf9Xnx3SHNrP5VbsS9jdtGB3BCTQbicYd6QuujQBKldK0fnYMjCY3L0+lboI2OPCVg9PTliOdJ8A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
},
|
},
|
||||||
@@ -4109,7 +4090,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.7.0.tgz",
|
||||||
"integrity": "sha512-j3HPRPR7XxKxgMeytb+r/CNUoLBMVrfdfL8KJr1XiFO9jyEvoC4chFXDXWlkGyUHJIC6wy5VIXlIlI/kpqOiGg==",
|
"integrity": "sha512-j3HPRPR7XxKxgMeytb+r/CNUoLBMVrfdfL8KJr1XiFO9jyEvoC4chFXDXWlkGyUHJIC6wy5VIXlIlI/kpqOiGg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -4168,7 +4148,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.66.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.66.0.tgz",
|
||||||
"integrity": "sha512-uqY3wslMs7KiBceaHPwCyWVrP8IPqb3OgAy1zd5DHosoUj/ciUl4JWVdx+QdsDypV/Cs4EZrqcIUtMDKQ/Zk0g==",
|
"integrity": "sha512-uqY3wslMs7KiBceaHPwCyWVrP8IPqb3OgAy1zd5DHosoUj/ciUl4JWVdx+QdsDypV/Cs4EZrqcIUtMDKQ/Zk0g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
},
|
},
|
||||||
@@ -4197,7 +4176,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.66.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.66.0.tgz",
|
||||||
"integrity": "sha512-D6REwySoaPGZlkdqTfrWahMqziXOY7GGTm1pXWVYDi5kEcSP9+F8ojo6saHDlwhN+V4/2jlMrkseSPlfXbmngQ==",
|
"integrity": "sha512-D6REwySoaPGZlkdqTfrWahMqziXOY7GGTm1pXWVYDi5kEcSP9+F8ojo6saHDlwhN+V4/2jlMrkseSPlfXbmngQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
},
|
},
|
||||||
@@ -4216,7 +4194,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.9.0.tgz",
|
||||||
"integrity": "sha512-TbIIwslbEnxunKuL9OyPZdmefrvJEK6HYiADEKQHUMUs4Pk2UbhMckUieURo83yPDamk/Mww+Nu/g60J/4uh2w==",
|
"integrity": "sha512-TbIIwslbEnxunKuL9OyPZdmefrvJEK6HYiADEKQHUMUs4Pk2UbhMckUieURo83yPDamk/Mww+Nu/g60J/4uh2w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
@@ -4343,7 +4320,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
|
||||||
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
|
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/trusted-types": "*"
|
"@types/trusted-types": "*"
|
||||||
}
|
}
|
||||||
@@ -4393,9 +4369,8 @@
|
|||||||
"version": "22.19.3",
|
"version": "22.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
|
||||||
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
|
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -4864,7 +4839,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -5142,7 +5116,6 @@
|
|||||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readdirp": "^4.0.1"
|
"readdirp": "^4.0.1"
|
||||||
},
|
},
|
||||||
@@ -5788,8 +5761,7 @@
|
|||||||
"version": "3.1.7",
|
"version": "3.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
|
||||||
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==",
|
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/domutils": {
|
"node_modules/domutils": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
@@ -5861,7 +5833,6 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5872,7 +5843,6 @@
|
|||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6155,7 +6125,6 @@
|
|||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -7699,7 +7668,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copy-anything": "^2.0.1",
|
"copy-anything": "^2.0.1",
|
||||||
"parse-node-version": "^1.0.1",
|
"parse-node-version": "^1.0.1",
|
||||||
@@ -8057,7 +8025,6 @@
|
|||||||
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
|
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cli-truncate": "^4.0.0",
|
"cli-truncate": "^4.0.0",
|
||||||
"colorette": "^2.0.20",
|
"colorette": "^2.0.20",
|
||||||
@@ -8897,7 +8864,6 @@
|
|||||||
"integrity": "sha512-yW5ME0hqTz38r/th/7zVwX5oSIw1FviSA2PUlGZdVjghDme/KX6iiwmOBmlt9E9whNmwijEC6Gn3KKbrsBx8ig==",
|
"integrity": "sha512-yW5ME0hqTz38r/th/7zVwX5oSIw1FviSA2PUlGZdVjghDme/KX6iiwmOBmlt9E9whNmwijEC6Gn3KKbrsBx8ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.3.0",
|
"@ampproject/remapping": "^2.3.0",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
@@ -10704,7 +10670,6 @@
|
|||||||
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
|
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -10850,7 +10815,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
@@ -10874,7 +10838,6 @@
|
|||||||
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
|
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"immutable": "^5.0.2",
|
"immutable": "^5.0.2",
|
||||||
@@ -11791,8 +11754,7 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD",
|
"license": "0BSD"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/tslint": {
|
"node_modules/tslint": {
|
||||||
"version": "6.1.3",
|
"version": "6.1.3",
|
||||||
@@ -12084,7 +12046,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -12097,7 +12058,7 @@
|
|||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/union": {
|
"node_modules/union": {
|
||||||
@@ -12245,7 +12206,6 @@
|
|||||||
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -12713,7 +12673,6 @@
|
|||||||
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -12732,8 +12691,7 @@
|
|||||||
"version": "0.15.1",
|
"version": "0.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
|
||||||
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
|
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ export class AppComponent {
|
|||||||
|
|
||||||
switch (status.status) {
|
switch (status.status) {
|
||||||
case 'needs-install':
|
case 'needs-install':
|
||||||
|
// Restore keyboard from status if it was previously set
|
||||||
|
if (status.keyboard) {
|
||||||
|
this.stateService.keyboard = status.keyboard.layout
|
||||||
|
}
|
||||||
// Start the install flow
|
// Start the install flow
|
||||||
await this.router.navigate(['/language'])
|
await this.router.navigate(['/language'])
|
||||||
break
|
break
|
||||||
@@ -39,6 +43,10 @@ export class AppComponent {
|
|||||||
// Store the data drive info from status
|
// Store the data drive info from status
|
||||||
this.stateService.dataDriveGuid = status.guid
|
this.stateService.dataDriveGuid = status.guid
|
||||||
this.stateService.attach = status.attach
|
this.stateService.attach = status.attach
|
||||||
|
// Restore keyboard from status if it was previously set
|
||||||
|
if (status.keyboard) {
|
||||||
|
this.stateService.keyboard = status.keyboard.layout
|
||||||
|
}
|
||||||
|
|
||||||
await this.router.navigate(['/language'])
|
await this.router.navigate(['/language'])
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject, signal } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
import {
|
||||||
|
getAllKeyboardsSorted,
|
||||||
|
i18nPipe,
|
||||||
|
Keyboard,
|
||||||
|
LanguageCode,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
||||||
import { TuiChevron, TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit'
|
import {
|
||||||
|
TuiButtonLoading,
|
||||||
|
TuiChevron,
|
||||||
|
TuiDataListWrapper,
|
||||||
|
TuiSelect,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||||
|
import { ApiService } from '../services/api.service'
|
||||||
import { StateService } from '../services/state.service'
|
import { StateService } from '../services/state.service'
|
||||||
import { Keyboard, getKeyboardsForLanguage } from '../utils/languages'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@@ -36,30 +46,22 @@ import { Keyboard, getKeyboardsForLanguage } from '../utils/languages'
|
|||||||
</tui-textfield>
|
</tui-textfield>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<button tuiButton [disabled]="!selected" (click)="continue()">
|
<button
|
||||||
|
tuiButton
|
||||||
|
[disabled]="!selected"
|
||||||
|
[loading]="saving()"
|
||||||
|
(click)="continue()"
|
||||||
|
>
|
||||||
{{ 'Continue' | i18n }}
|
{{ 'Continue' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
`,
|
`,
|
||||||
styles: `
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
TuiCardLarge,
|
TuiCardLarge,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
|
TuiButtonLoading,
|
||||||
TuiTextfield,
|
TuiTextfield,
|
||||||
TuiChevron,
|
TuiChevron,
|
||||||
TuiSelect,
|
TuiSelect,
|
||||||
@@ -71,24 +73,38 @@ import { Keyboard, getKeyboardsForLanguage } from '../utils/languages'
|
|||||||
})
|
})
|
||||||
export default class KeyboardPage {
|
export default class KeyboardPage {
|
||||||
private readonly router = inject(Router)
|
private readonly router = inject(Router)
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
private readonly stateService = inject(StateService)
|
private readonly stateService = inject(StateService)
|
||||||
|
|
||||||
protected readonly mobile = inject(TUI_IS_MOBILE)
|
protected readonly mobile = inject(TUI_IS_MOBILE)
|
||||||
readonly keyboards = getKeyboardsForLanguage(this.stateService.language)
|
// All keyboards, with language-specific keyboards at the top
|
||||||
|
readonly keyboards = getAllKeyboardsSorted(
|
||||||
|
this.stateService.language as LanguageCode,
|
||||||
|
)
|
||||||
selected =
|
selected =
|
||||||
this.keyboards.find(k => k.code === this.stateService.keyboard) ||
|
this.keyboards.find(k => k.code === this.stateService.keyboard) ||
|
||||||
this.keyboards[0]
|
this.keyboards[0]!
|
||||||
|
|
||||||
|
readonly saving = signal(false)
|
||||||
|
|
||||||
readonly stringify = (kb: Keyboard) => kb.name
|
readonly stringify = (kb: Keyboard) => kb.name
|
||||||
|
|
||||||
async back() {
|
|
||||||
await this.router.navigate(['/language'])
|
|
||||||
}
|
|
||||||
|
|
||||||
async continue() {
|
async continue() {
|
||||||
if (this.selected) {
|
this.saving.set(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send keyboard to backend
|
||||||
|
await this.api.setKeyboard({
|
||||||
|
layout: this.selected.code,
|
||||||
|
model: null,
|
||||||
|
variant: null,
|
||||||
|
options: [],
|
||||||
|
})
|
||||||
|
|
||||||
this.stateService.keyboard = this.selected.code
|
this.stateService.keyboard = this.selected.code
|
||||||
await this.navigateToNextStep()
|
await this.navigateToNextStep()
|
||||||
|
} finally {
|
||||||
|
this.saving.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
import { Component, computed, inject, signal } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { i18nPipe, i18nService } from '@start9labs/shared'
|
import { i18nPipe, i18nService, Language, LANGUAGES } from '@start9labs/shared'
|
||||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
||||||
import { TuiChevron, TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit'
|
|
||||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
|
||||||
import { StateService } from '../services/state.service'
|
|
||||||
import {
|
import {
|
||||||
LANGUAGES,
|
TuiButtonLoading,
|
||||||
Language,
|
TuiChevron,
|
||||||
getDefaultKeyboard,
|
TuiDataListWrapper,
|
||||||
needsKeyboardSelection,
|
TuiSelect,
|
||||||
} from '../utils/languages'
|
} from '@taiga-ui/kit'
|
||||||
|
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||||
|
import { ApiService } from '../services/api.service'
|
||||||
|
import { StateService } from '../services/state.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@@ -60,25 +60,23 @@ import {
|
|||||||
@let lang = asLanguage(item);
|
@let lang = asLanguage(item);
|
||||||
<div class="language-item">
|
<div class="language-item">
|
||||||
<span>{{ lang.nativeName }}</span>
|
<span>{{ lang.nativeName }}</span>
|
||||||
<small>{{ lang.tuiName | i18n }}</small>
|
<small>{{ lang.name | i18n }}</small>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<button tuiButton [disabled]="!selected" (click)="continue()">
|
<button
|
||||||
|
tuiButton
|
||||||
|
[disabled]="!selected"
|
||||||
|
[loading]="loading()"
|
||||||
|
(click)="continue()"
|
||||||
|
>
|
||||||
{{ 'Continue' | i18n }}
|
{{ 'Continue' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-item {
|
.language-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -92,6 +90,7 @@ import {
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
TuiCardLarge,
|
TuiCardLarge,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
|
TuiButtonLoading,
|
||||||
TuiTextfield,
|
TuiTextfield,
|
||||||
TuiChevron,
|
TuiChevron,
|
||||||
TuiSelect,
|
TuiSelect,
|
||||||
@@ -103,6 +102,7 @@ import {
|
|||||||
})
|
})
|
||||||
export default class LanguagePage {
|
export default class LanguagePage {
|
||||||
private readonly router = inject(Router)
|
private readonly router = inject(Router)
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
private readonly stateService = inject(StateService)
|
private readonly stateService = inject(StateService)
|
||||||
private readonly i18nService = inject(i18nService)
|
private readonly i18nService = inject(i18nService)
|
||||||
|
|
||||||
@@ -112,18 +112,23 @@ export default class LanguagePage {
|
|||||||
selected =
|
selected =
|
||||||
LANGUAGES.find(l => l.code === this.stateService.language) || LANGUAGES[0]
|
LANGUAGES.find(l => l.code === this.stateService.language) || LANGUAGES[0]
|
||||||
|
|
||||||
|
private readonly saving = signal(false)
|
||||||
|
|
||||||
|
// Show loading when either language is loading or saving is in progress
|
||||||
|
readonly loading = computed(() => this.i18nService.loading() || this.saving())
|
||||||
|
|
||||||
readonly stringify = (lang: Language) => lang.nativeName
|
readonly stringify = (lang: Language) => lang.nativeName
|
||||||
readonly asLanguage = (item: unknown): Language => item as Language
|
readonly asLanguage = (item: unknown): Language => item as Language
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
this.i18nService.setLanguage(this.selected.tuiName)
|
this.i18nService.setLanguage(this.selected.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLanguageChange(language: Language) {
|
onLanguageChange(language: Language) {
|
||||||
if (language) {
|
if (language) {
|
||||||
this.i18nService.setLanguage(language.tuiName)
|
this.i18nService.setLanguage(language.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,31 +136,16 @@ export default class LanguagePage {
|
|||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
this.stateService.language = this.selected.code
|
this.stateService.language = this.selected.code
|
||||||
|
|
||||||
if (this.stateService.kiosk) {
|
// Save language to backend
|
||||||
if (needsKeyboardSelection(this.selected.code)) {
|
this.saving.set(true)
|
||||||
await this.router.navigate(['/keyboard'])
|
|
||||||
} else {
|
|
||||||
this.stateService.keyboard = getDefaultKeyboard(
|
|
||||||
this.selected.code,
|
|
||||||
).code
|
|
||||||
await this.navigateToNextStep()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.navigateToNextStep()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async navigateToNextStep() {
|
try {
|
||||||
if (this.stateService.dataDriveGuid) {
|
await this.api.setLanguage({ language: this.selected.name })
|
||||||
if (this.stateService.attach) {
|
// Always go to keyboard selection
|
||||||
this.stateService.setupType = 'attach'
|
await this.router.navigate(['/keyboard'])
|
||||||
await this.router.navigate(['/password'])
|
} finally {
|
||||||
} else {
|
this.saving.set(false)
|
||||||
await this.router.navigate(['/home'])
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
await this.router.navigate(['/drives'])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import * as jose from 'node-jose'
|
import * as jose from 'node-jose'
|
||||||
import { DiskInfo, FollowLogsRes, StartOSDiskInfo } from '@start9labs/shared'
|
import {
|
||||||
|
DiskInfo,
|
||||||
|
FollowLogsRes,
|
||||||
|
FullKeyboard,
|
||||||
|
SetLanguageParams,
|
||||||
|
StartOSDiskInfo,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
@@ -21,6 +27,8 @@ export abstract class ApiService {
|
|||||||
// Status & Setup
|
// Status & Setup
|
||||||
abstract getStatus(): Promise<SetupStatusRes> // setup.status
|
abstract getStatus(): Promise<SetupStatusRes> // setup.status
|
||||||
abstract getPubKey(): Promise<void> // setup.get-pubkey
|
abstract getPubKey(): Promise<void> // setup.get-pubkey
|
||||||
|
abstract setKeyboard(params: FullKeyboard): Promise<null> // setup.set-keyboard
|
||||||
|
abstract setLanguage(params: SetLanguageParams): Promise<null> // setup.set-language
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
abstract getDisks(): Promise<DiskInfo[]> // setup.disk.list
|
abstract getDisks(): Promise<DiskInfo[]> // setup.disk.list
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import {
|
|||||||
DiskInfo,
|
DiskInfo,
|
||||||
encodeBase64,
|
encodeBase64,
|
||||||
FollowLogsRes,
|
FollowLogsRes,
|
||||||
|
FullKeyboard,
|
||||||
HttpService,
|
HttpService,
|
||||||
isRpcError,
|
isRpcError,
|
||||||
RpcError,
|
RpcError,
|
||||||
RPCOptions,
|
RPCOptions,
|
||||||
|
SetLanguageParams,
|
||||||
StartOSDiskInfo,
|
StartOSDiskInfo,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
@@ -64,6 +66,20 @@ export class LiveApiService extends ApiService {
|
|||||||
this.pubkey = response
|
this.pubkey = response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setKeyboard(params: FullKeyboard): Promise<null> {
|
||||||
|
return this.rpcRequest({
|
||||||
|
method: 'setup.set-keyboard',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLanguage(params: SetLanguageParams): Promise<null> {
|
||||||
|
return this.rpcRequest({
|
||||||
|
method: 'setup.set-language',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async getDisks() {
|
async getDisks() {
|
||||||
return this.rpcRequest<DiskInfo[]>({
|
return this.rpcRequest<DiskInfo[]>({
|
||||||
method: 'setup.disk.list',
|
method: 'setup.disk.list',
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import {
|
|||||||
DiskInfo,
|
DiskInfo,
|
||||||
encodeBase64,
|
encodeBase64,
|
||||||
FollowLogsRes,
|
FollowLogsRes,
|
||||||
|
FullKeyboard,
|
||||||
pauseFor,
|
pauseFor,
|
||||||
|
SetLanguageParams,
|
||||||
StartOSDiskInfo,
|
StartOSDiskInfo,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
@@ -68,8 +70,13 @@ export class MockApiService extends ApiService {
|
|||||||
this.statusIndex++
|
this.statusIndex++
|
||||||
|
|
||||||
if (this.statusIndex === 1) {
|
if (this.statusIndex === 1) {
|
||||||
// return { status: 'needs-install' }
|
// return { status: 'needs-install', keyboard: null }
|
||||||
return { status: 'incomplete', attach: false, guid: 'mock-data-guid' }
|
return {
|
||||||
|
status: 'incomplete',
|
||||||
|
attach: false,
|
||||||
|
guid: 'mock-data-guid',
|
||||||
|
keyboard: null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.statusIndex > 3) {
|
if (this.statusIndex > 3) {
|
||||||
@@ -93,6 +100,16 @@ export class MockApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setKeyboard(_params: FullKeyboard): Promise<null> {
|
||||||
|
await pauseFor(300)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLanguage(params: SetLanguageParams): Promise<null> {
|
||||||
|
await pauseFor(300)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async getDisks(): Promise<DiskInfo[]> {
|
async getDisks(): Promise<DiskInfo[]> {
|
||||||
await pauseFor(500)
|
await pauseFor(500)
|
||||||
return MOCK_DISKS
|
return MOCK_DISKS
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class StateService {
|
|||||||
|
|
||||||
// Set during install flow, or loaded from status response
|
// Set during install flow, or loaded from status response
|
||||||
language = ''
|
language = ''
|
||||||
keyboard = '' // only used if kiosk
|
keyboard = ''
|
||||||
|
|
||||||
// From install response or status response (incomplete)
|
// From install response or status response (incomplete)
|
||||||
dataDriveGuid = ''
|
dataDriveGuid = ''
|
||||||
@@ -52,8 +52,6 @@ export class StateService {
|
|||||||
await this.api.attach({
|
await this.api.attach({
|
||||||
guid: this.dataDriveGuid,
|
guid: this.dataDriveGuid,
|
||||||
startOsPassword: password ? await this.api.encrypt(password) : null,
|
startOsPassword: password ? await this.api.encrypt(password) : null,
|
||||||
language: this.language,
|
|
||||||
kiosk: this.kiosk ? { keyboard: this.keyboard } : null,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +79,6 @@ export class StateService {
|
|||||||
await this.api.execute({
|
await this.api.execute({
|
||||||
startOsLogicalname: this.dataDriveGuid,
|
startOsLogicalname: this.dataDriveGuid,
|
||||||
startOsPassword: password ? await this.api.encrypt(password) : null,
|
startOsPassword: password ? await this.api.encrypt(password) : null,
|
||||||
language: this.language,
|
|
||||||
kiosk: this.kiosk ? { keyboard: this.keyboard } : null,
|
|
||||||
recoverySource,
|
recoverySource,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { DiskInfo, PartitionInfo, StartOSDiskInfo } from '@start9labs/shared'
|
import {
|
||||||
|
DiskInfo,
|
||||||
|
FullKeyboard,
|
||||||
|
PartitionInfo,
|
||||||
|
StartOSDiskInfo,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
// === Echo ===
|
// === Echo ===
|
||||||
@@ -10,8 +15,13 @@ export type EchoReq = {
|
|||||||
// === Setup Status ===
|
// === Setup Status ===
|
||||||
|
|
||||||
export type SetupStatusRes =
|
export type SetupStatusRes =
|
||||||
| { status: 'needs-install' }
|
| { status: 'needs-install'; keyboard: FullKeyboard | null }
|
||||||
| { status: 'incomplete'; guid: string; attach: boolean }
|
| {
|
||||||
|
status: 'incomplete'
|
||||||
|
guid: string
|
||||||
|
attach: boolean
|
||||||
|
keyboard: FullKeyboard | null
|
||||||
|
}
|
||||||
| { status: 'running'; progress: T.FullProgress; guid: string }
|
| { status: 'running'; progress: T.FullProgress; guid: string }
|
||||||
| { status: 'complete' }
|
| { status: 'complete' }
|
||||||
|
|
||||||
@@ -35,8 +45,6 @@ export interface InstallOsRes {
|
|||||||
export interface AttachParams {
|
export interface AttachParams {
|
||||||
startOsPassword: T.EncryptedWire | null
|
startOsPassword: T.EncryptedWire | null
|
||||||
guid: string // data drive
|
guid: string // data drive
|
||||||
language: string
|
|
||||||
kiosk: { keyboard: string } | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Execute ===
|
// === Execute ===
|
||||||
@@ -44,8 +52,6 @@ export interface AttachParams {
|
|||||||
export interface SetupExecuteParams {
|
export interface SetupExecuteParams {
|
||||||
startOsLogicalname: string
|
startOsLogicalname: string
|
||||||
startOsPassword: T.EncryptedWire | null // null = keep existing password (for restore/transfer)
|
startOsPassword: T.EncryptedWire | null // null = keep existing password (for restore/transfer)
|
||||||
language: string
|
|
||||||
kiosk: { keyboard: string } | null
|
|
||||||
recoverySource:
|
recoverySource:
|
||||||
| {
|
| {
|
||||||
type: 'migrate'
|
type: 'migrate'
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import { i18nKey } from '@start9labs/shared'
|
|
||||||
|
|
||||||
export interface Keyboard {
|
|
||||||
code: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Language {
|
|
||||||
code: string
|
|
||||||
tuiName: i18nKey
|
|
||||||
nativeName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LANGUAGES: Language[] = [
|
|
||||||
{ code: 'en', tuiName: 'english', nativeName: 'English' },
|
|
||||||
{ code: 'es', tuiName: 'spanish', nativeName: 'Español' },
|
|
||||||
{ code: 'de', tuiName: 'german', nativeName: 'Deutsch' },
|
|
||||||
{ code: 'fr', tuiName: 'french', nativeName: 'Français' },
|
|
||||||
{ code: 'pl', tuiName: 'polish', nativeName: 'Polski' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const KEYBOARDS_BY_LANGUAGE: Record<string, Keyboard[]> = {
|
|
||||||
en: [
|
|
||||||
{ code: 'us', name: 'US English' },
|
|
||||||
{ code: 'gb', name: 'UK English' },
|
|
||||||
],
|
|
||||||
es: [
|
|
||||||
{ code: 'es', name: 'Spanish' },
|
|
||||||
{ code: 'latam', name: 'Latin American' },
|
|
||||||
],
|
|
||||||
de: [{ code: 'de', name: 'German' }],
|
|
||||||
fr: [{ code: 'fr', name: 'French' }],
|
|
||||||
pl: [{ code: 'pl', name: 'Polish' }],
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available keyboards for a language.
|
|
||||||
* Returns array of keyboards (may be 1 or more).
|
|
||||||
*/
|
|
||||||
export function getKeyboardsForLanguage(languageCode: string): Keyboard[] {
|
|
||||||
return (
|
|
||||||
KEYBOARDS_BY_LANGUAGE[languageCode] || [{ code: 'us', name: 'US English' }]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if keyboard selection is needed for a language.
|
|
||||||
* Returns true if there are multiple keyboard options.
|
|
||||||
*/
|
|
||||||
export function needsKeyboardSelection(languageCode: string): boolean {
|
|
||||||
const keyboards = getKeyboardsForLanguage(languageCode)
|
|
||||||
return keyboards.length > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default keyboard for a language.
|
|
||||||
* Returns the first keyboard option.
|
|
||||||
*/
|
|
||||||
export function getDefaultKeyboard(languageCode: string): Keyboard {
|
|
||||||
return getKeyboardsForLanguage(languageCode)[0]!
|
|
||||||
}
|
|
||||||
@@ -673,4 +673,6 @@ export default {
|
|||||||
709: 'Laufwerk',
|
709: 'Laufwerk',
|
||||||
710: 'Übertragen',
|
710: 'Übertragen',
|
||||||
711: 'Die Liste ist leer',
|
711: 'Die Liste ist leer',
|
||||||
|
712: 'Jetzt neu starten',
|
||||||
|
713: 'Später',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -673,4 +673,6 @@ export const ENGLISH = {
|
|||||||
'Drive': 709, // as in, a storage device
|
'Drive': 709, // as in, a storage device
|
||||||
'Transfer': 710, // the verb
|
'Transfer': 710, // the verb
|
||||||
'The list is empty': 711,
|
'The list is empty': 711,
|
||||||
|
'Restart now': 712,
|
||||||
|
'Later': 713, // as in, (do it) later
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -673,4 +673,6 @@ export default {
|
|||||||
709: 'Unidad',
|
709: 'Unidad',
|
||||||
710: 'Transferir',
|
710: 'Transferir',
|
||||||
711: 'La lista está vacía',
|
711: 'La lista está vacía',
|
||||||
|
712: 'Reiniciar ahora',
|
||||||
|
713: 'Más tarde',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -673,4 +673,6 @@ export default {
|
|||||||
709: 'Disque',
|
709: 'Disque',
|
||||||
710: 'Transférer',
|
710: 'Transférer',
|
||||||
711: 'La liste est vide',
|
711: 'La liste est vide',
|
||||||
|
712: 'Redémarrer maintenant',
|
||||||
|
713: 'Plus tard',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -673,4 +673,6 @@ export default {
|
|||||||
709: 'Dysk',
|
709: 'Dysk',
|
||||||
710: 'Przenieś',
|
710: 'Przenieś',
|
||||||
711: 'Lista jest pusta',
|
711: 'Lista jest pusta',
|
||||||
|
712: 'Uruchom ponownie teraz',
|
||||||
|
713: 'Później',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -58,3 +58,5 @@ export * from './util/rpc.util'
|
|||||||
export * from './util/to-guid'
|
export * from './util/to-guid'
|
||||||
export * from './util/to-local-iso-string'
|
export * from './util/to-local-iso-string'
|
||||||
export * from './util/unused'
|
export * from './util/unused'
|
||||||
|
export * from './util/keyboards'
|
||||||
|
export * from './util/languages'
|
||||||
|
|||||||
90
web/projects/shared/src/util/keyboards.ts
Normal file
90
web/projects/shared/src/util/keyboards.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { LanguageCode } from './languages'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard layout codes
|
||||||
|
*/
|
||||||
|
export type KeyboardCode = 'us' | 'gb' | 'es' | 'latam' | 'de' | 'fr' | 'pl'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard layout display names
|
||||||
|
*/
|
||||||
|
export type KeyboardName =
|
||||||
|
| 'US English'
|
||||||
|
| 'UK English'
|
||||||
|
| 'Spanish'
|
||||||
|
| 'Latin American'
|
||||||
|
| 'German'
|
||||||
|
| 'French'
|
||||||
|
| 'Polish'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard layout definition
|
||||||
|
*/
|
||||||
|
export interface Keyboard {
|
||||||
|
code: KeyboardCode
|
||||||
|
name: KeyboardName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full keyboard configuration for backend API
|
||||||
|
*/
|
||||||
|
export interface FullKeyboard {
|
||||||
|
layout: string
|
||||||
|
model: string | null
|
||||||
|
variant: string | null
|
||||||
|
options: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard layouts grouped by language code
|
||||||
|
*/
|
||||||
|
export const KEYBOARDS_BY_LANGUAGE: Record<LanguageCode, Keyboard[]> = {
|
||||||
|
en: [
|
||||||
|
{ code: 'us', name: 'US English' },
|
||||||
|
{ code: 'gb', name: 'UK English' },
|
||||||
|
],
|
||||||
|
es: [
|
||||||
|
{ code: 'es', name: 'Spanish' },
|
||||||
|
{ code: 'latam', name: 'Latin American' },
|
||||||
|
],
|
||||||
|
de: [{ code: 'de', name: 'German' }],
|
||||||
|
fr: [{ code: 'fr', name: 'French' }],
|
||||||
|
pl: [{ code: 'pl', name: 'Polish' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All available keyboard layouts
|
||||||
|
*/
|
||||||
|
export const ALL_KEYBOARDS: Keyboard[] = [
|
||||||
|
{ code: 'us', name: 'US English' },
|
||||||
|
{ code: 'gb', name: 'UK English' },
|
||||||
|
{ code: 'es', name: 'Spanish' },
|
||||||
|
{ code: 'latam', name: 'Latin American' },
|
||||||
|
{ code: 'de', name: 'German' },
|
||||||
|
{ code: 'fr', name: 'French' },
|
||||||
|
{ code: 'pl', name: 'Polish' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all keyboards sorted with language-specific keyboards first,
|
||||||
|
* then remaining keyboards alphabetically by name.
|
||||||
|
*/
|
||||||
|
export function getAllKeyboardsSorted(languageCode: LanguageCode): Keyboard[] {
|
||||||
|
const languageKeyboards = KEYBOARDS_BY_LANGUAGE[languageCode]
|
||||||
|
const languageKeyboardCodes = new Set(languageKeyboards.map(kb => kb.code))
|
||||||
|
const otherKeyboards = ALL_KEYBOARDS.filter(
|
||||||
|
kb => !languageKeyboardCodes.has(kb.code),
|
||||||
|
).sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
return [...languageKeyboards, ...otherKeyboards]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display name for a keyboard code.
|
||||||
|
*/
|
||||||
|
export function getKeyboardName(
|
||||||
|
code: KeyboardCode | string,
|
||||||
|
): KeyboardName | string {
|
||||||
|
const keyboard = ALL_KEYBOARDS.find(kb => kb.code === code)
|
||||||
|
if (keyboard) return keyboard.name
|
||||||
|
return code // fallback to the code itself if not found
|
||||||
|
}
|
||||||
44
web/projects/shared/src/util/languages.ts
Normal file
44
web/projects/shared/src/util/languages.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Languages } from '../i18n/i18n.service'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISO language codes
|
||||||
|
*/
|
||||||
|
export type LanguageCode = 'en' | 'es' | 'de' | 'fr' | 'pl'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language definition with metadata
|
||||||
|
*/
|
||||||
|
export interface Language {
|
||||||
|
code: LanguageCode
|
||||||
|
name: Languages
|
||||||
|
nativeName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available languages with their metadata
|
||||||
|
*/
|
||||||
|
export const LANGUAGES: Language[] = [
|
||||||
|
{ code: 'en', name: 'english', nativeName: 'English' },
|
||||||
|
{ code: 'es', name: 'spanish', nativeName: 'Español' },
|
||||||
|
{ code: 'de', name: 'german', nativeName: 'Deutsch' },
|
||||||
|
{ code: 'fr', name: 'french', nativeName: 'Français' },
|
||||||
|
{ code: 'pl', name: 'polish', nativeName: 'Polski' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps i18n language names to ISO language codes
|
||||||
|
*/
|
||||||
|
export const LANGUAGE_TO_CODE: Record<Languages, LanguageCode> = {
|
||||||
|
english: 'en',
|
||||||
|
spanish: 'es',
|
||||||
|
german: 'de',
|
||||||
|
french: 'fr',
|
||||||
|
polish: 'pl',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params for setting language via API
|
||||||
|
*/
|
||||||
|
export interface SetLanguageParams {
|
||||||
|
language: Languages
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ export class AppComponent {
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
readonly ui = inject<PatchDB<DataModel>>(PatchDB)
|
readonly ui = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
.watch$('ui', 'language')
|
.watch$('serverInfo', 'language')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe(language => {
|
.subscribe(language => {
|
||||||
this.i18n.setLanguage(language || 'english')
|
this.i18n.setLanguage(language || 'english')
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
I18N_PROVIDERS,
|
I18N_PROVIDERS,
|
||||||
I18N_STORAGE,
|
I18N_STORAGE,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
Languages,
|
||||||
RELATIVE_URL,
|
RELATIVE_URL,
|
||||||
VERSION,
|
VERSION,
|
||||||
WorkspaceConfig,
|
WorkspaceConfig,
|
||||||
@@ -128,7 +129,7 @@ export const APP_PROVIDERS = [
|
|||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
const api = inject(ApiService)
|
const api = inject(ApiService)
|
||||||
|
|
||||||
return (language: string) => api.setDbValue(['language'], language)
|
return (language: Languages) => api.setLanguage({ language })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,11 +12,16 @@ import { RouterLink } from '@angular/router'
|
|||||||
import {
|
import {
|
||||||
DialogService,
|
DialogService,
|
||||||
ErrorService,
|
ErrorService,
|
||||||
|
getAllKeyboardsSorted,
|
||||||
|
getKeyboardName,
|
||||||
i18nKey,
|
i18nKey,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
Keyboard,
|
||||||
|
KeyboardCode,
|
||||||
languages,
|
languages,
|
||||||
Languages,
|
Languages,
|
||||||
|
LANGUAGE_TO_CODE,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||||
@@ -49,6 +54,7 @@ import { TitleDirective } from 'src/app/services/title.service'
|
|||||||
import { SnekDirective } from './snek.directive'
|
import { SnekDirective } from './snek.directive'
|
||||||
import { UPDATE } from './update.component'
|
import { UPDATE } from './update.component'
|
||||||
import { SystemWipeComponent } from './wipe.component'
|
import { SystemWipeComponent } from './wipe.component'
|
||||||
|
import { KeyboardSelectComponent } from './keyboard-select.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@@ -134,20 +140,38 @@ import { SystemWipeComponent } from './wipe.component'
|
|||||||
<span tuiTitle>
|
<span tuiTitle>
|
||||||
<strong>
|
<strong>
|
||||||
{{ 'Kiosk Mode' | i18n }}
|
{{ 'Kiosk Mode' | i18n }}
|
||||||
<tui-badge size="m" appearance="primary-grayscale">
|
<tui-badge
|
||||||
|
size="m"
|
||||||
|
[appearance]="server.kiosk ? 'warning' : 'primary-grayscale'"
|
||||||
|
>
|
||||||
{{ server.kiosk ? ('Enabled' | i18n) : ('Disabled' | i18n) }}
|
{{ server.kiosk ? ('Enabled' | i18n) : ('Disabled' | i18n) }}
|
||||||
</tui-badge>
|
</tui-badge>
|
||||||
</strong>
|
</strong>
|
||||||
<span tuiSubtitle>
|
<span tuiSubtitle [class.warning-text]="server.kiosk">
|
||||||
{{
|
@if (server.kiosk === null) {
|
||||||
server.kiosk === true
|
{{ 'Kiosk Mode is unavailable on this device' | i18n }}
|
||||||
? ('Disable Kiosk Mode unless you need to attach a monitor'
|
} @else {
|
||||||
| i18n)
|
{{
|
||||||
: server.kiosk === false
|
server.kiosk
|
||||||
? ('Enable Kiosk Mode if you need to attach a monitor' | i18n)
|
? ('Disable Kiosk Mode unless you need to attach a monitor'
|
||||||
: ('Kiosk Mode is unavailable on this device' | i18n)
|
| i18n)
|
||||||
}}
|
: ('Enable Kiosk Mode if you need to attach a monitor' | i18n)
|
||||||
|
}}
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
|
@if (server.kiosk !== null && server.keyboard?.layout; as layout) {
|
||||||
|
<span tuiSubtitle class="keyboard-info">
|
||||||
|
<tui-icon icon="@tui.keyboard" />
|
||||||
|
{{ getKeyboardName(layout) }}
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="icon"
|
||||||
|
iconStart="@tui.pencil"
|
||||||
|
size="xs"
|
||||||
|
(click)="onChangeKeyboard()"
|
||||||
|
></button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
@if (server.kiosk !== null) {
|
@if (server.kiosk !== null) {
|
||||||
<button tuiButton appearance="primary" (click)="toggleKiosk()">
|
<button tuiButton appearance="primary" (click)="toggleKiosk()">
|
||||||
@@ -214,6 +238,21 @@ import { SystemWipeComponent } from './wipe.component'
|
|||||||
[tuiAnimated].tui-leave {
|
[tuiAnimated].tui-leave {
|
||||||
animation-name: tuiFade, tuiScale;
|
animation-name: tuiFade, tuiScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.keyboard-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
tui-icon {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text,
|
||||||
|
[tuiSubtitle].warning-text {
|
||||||
|
color: var(--tui-status-warning) !important;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
providers: [tuiCellOptionsProvider({ height: 'spacious' })],
|
providers: [tuiCellOptionsProvider({ height: 'spacious' })],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -271,6 +310,51 @@ export default class SystemGeneralComponent {
|
|||||||
return this.languages.find(lang => lang === this.i18nService.language)
|
return this.languages.find(lang => lang === this.i18nService.language)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose shared utilities for template use
|
||||||
|
readonly getKeyboardName = getKeyboardName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open keyboard selection dialog to change keyboard layout
|
||||||
|
*/
|
||||||
|
onChangeKeyboard() {
|
||||||
|
const server = this.server()
|
||||||
|
if (!server) return
|
||||||
|
|
||||||
|
const keyboards = getAllKeyboardsSorted(LANGUAGE_TO_CODE[server.language])
|
||||||
|
const currentKeyboard = (server.keyboard?.layout as KeyboardCode) || null
|
||||||
|
|
||||||
|
this.dialog
|
||||||
|
.openComponent<KeyboardCode | null>(
|
||||||
|
new PolymorpheusComponent(KeyboardSelectComponent, this.injector),
|
||||||
|
{
|
||||||
|
label: 'Select Keyboard Layout',
|
||||||
|
size: 's',
|
||||||
|
data: { keyboards, currentKeyboard },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(filter((code): code is KeyboardCode => code !== null))
|
||||||
|
.subscribe(keyboardCode => {
|
||||||
|
this.saveKeyboard(keyboardCode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveKeyboard(keyboardCode: KeyboardCode) {
|
||||||
|
const loader = this.loader.open('Saving').subscribe()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.setKeyboard({
|
||||||
|
layout: keyboardCode,
|
||||||
|
model: null,
|
||||||
|
variant: null,
|
||||||
|
options: [],
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onUpdate() {
|
onUpdate() {
|
||||||
if (this.server()?.statusInfo.updated) {
|
if (this.server()?.statusInfo.updated) {
|
||||||
this.restart()
|
this.restart()
|
||||||
@@ -352,19 +436,51 @@ export default class SystemGeneralComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async toggleKiosk() {
|
async toggleKiosk() {
|
||||||
const kiosk = this.server()?.kiosk
|
const server = this.server()
|
||||||
|
if (!server) return
|
||||||
|
|
||||||
const loader = this.loader
|
const kiosk = server.kiosk
|
||||||
.open(kiosk ? 'Disabling' : 'Enabling')
|
|
||||||
.subscribe()
|
// If disabling kiosk, just disable it
|
||||||
|
if (kiosk) {
|
||||||
|
await this.disableKiosk()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabling kiosk - check if keyboard is already set
|
||||||
|
if (server.keyboard) {
|
||||||
|
// Keyboard already set, just enable kiosk
|
||||||
|
await this.enableKiosk()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No keyboard set - prompt user to select from all keyboards
|
||||||
|
const keyboards = getAllKeyboardsSorted(LANGUAGE_TO_CODE[server.language])
|
||||||
|
this.promptKeyboardSelection(keyboards)
|
||||||
|
}
|
||||||
|
|
||||||
|
private promptKeyboardSelection(keyboards: Keyboard[]) {
|
||||||
|
this.dialog
|
||||||
|
.openComponent<KeyboardCode | null>(
|
||||||
|
new PolymorpheusComponent(KeyboardSelectComponent, this.injector),
|
||||||
|
{
|
||||||
|
label: 'Select Keyboard Layout',
|
||||||
|
size: 's',
|
||||||
|
data: { keyboards, currentKeyboard: null },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(filter((code): code is KeyboardCode => code !== null))
|
||||||
|
.subscribe(keyboardCode => {
|
||||||
|
this.enableKioskWithKeyboard(keyboardCode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enableKiosk() {
|
||||||
|
const loader = this.loader.open('Enabling').subscribe()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.toggleKiosk(!kiosk)
|
await this.api.toggleKiosk(true)
|
||||||
this.dialog
|
this.promptRestart()
|
||||||
.openAlert('This change will take effect after the next boot', {
|
|
||||||
label: 'Restart to apply',
|
|
||||||
})
|
|
||||||
.subscribe()
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -372,6 +488,52 @@ export default class SystemGeneralComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async enableKioskWithKeyboard(keyboardCode: KeyboardCode) {
|
||||||
|
const loader = this.loader.open('Enabling').subscribe()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.setKeyboard({
|
||||||
|
layout: keyboardCode,
|
||||||
|
model: null,
|
||||||
|
variant: null,
|
||||||
|
options: [],
|
||||||
|
})
|
||||||
|
await this.api.toggleKiosk(true)
|
||||||
|
this.promptRestart()
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async disableKiosk() {
|
||||||
|
const loader = this.loader.open('Disabling').subscribe()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.toggleKiosk(false)
|
||||||
|
this.promptRestart()
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private promptRestart() {
|
||||||
|
this.dialog
|
||||||
|
.openConfirm({
|
||||||
|
label: 'Restart to apply',
|
||||||
|
data: {
|
||||||
|
content: 'This change will take effect after the next boot',
|
||||||
|
yes: 'Restart now',
|
||||||
|
no: 'Later',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(filter(Boolean))
|
||||||
|
.subscribe(() => this.restart())
|
||||||
|
}
|
||||||
|
|
||||||
private async resetTor(wipeState: boolean) {
|
private async resetTor(wipeState: boolean) {
|
||||||
const loader = this.loader.open().subscribe()
|
const loader = this.loader.open().subscribe()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { i18nPipe, Keyboard, KeyboardCode } from '@start9labs/shared'
|
||||||
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
|
import { TuiButton, TuiDialogContext, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiChevron, TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit'
|
||||||
|
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<tui-textfield
|
||||||
|
tuiChevron
|
||||||
|
[stringify]="stringify"
|
||||||
|
[tuiTextfieldCleaner]="false"
|
||||||
|
>
|
||||||
|
<label tuiLabel>{{ 'Keyboard' | i18n }}</label>
|
||||||
|
@if (mobile) {
|
||||||
|
<select tuiSelect [(ngModel)]="selected" [items]="keyboards"></select>
|
||||||
|
} @else {
|
||||||
|
<input tuiSelect [(ngModel)]="selected" />
|
||||||
|
}
|
||||||
|
@if (!mobile) {
|
||||||
|
<tui-data-list-wrapper new *tuiTextfieldDropdown [items]="keyboards" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
<footer>
|
||||||
|
<button tuiButton appearance="secondary" (click)="cancel()">
|
||||||
|
{{ 'Cancel' | i18n }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
[disabled]="!selected || selected.code === initialCode"
|
||||||
|
(click)="confirm()"
|
||||||
|
>
|
||||||
|
{{ 'Confirm' | i18n }}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiButton,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiChevron,
|
||||||
|
TuiSelect,
|
||||||
|
TuiDataListWrapper,
|
||||||
|
i18nPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class KeyboardSelectComponent {
|
||||||
|
private readonly context =
|
||||||
|
injectContext<
|
||||||
|
TuiDialogContext<
|
||||||
|
KeyboardCode | null,
|
||||||
|
{ keyboards: Keyboard[]; currentKeyboard: KeyboardCode | null }
|
||||||
|
>
|
||||||
|
>()
|
||||||
|
|
||||||
|
protected readonly mobile = inject(TUI_IS_MOBILE)
|
||||||
|
readonly keyboards = this.context.data.keyboards
|
||||||
|
readonly initialCode = this.context.data.currentKeyboard
|
||||||
|
selected =
|
||||||
|
this.keyboards.find(kb => kb.code === this.initialCode) ||
|
||||||
|
this.keyboards[0]!
|
||||||
|
|
||||||
|
readonly stringify = (kb: Keyboard) => kb.name
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.context.completeWith(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
this.context.completeWith(this.selected.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Dump } from 'patch-db-client'
|
import { Dump } from 'patch-db-client'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { StartOSDiskInfo, FetchLogsReq, FetchLogsRes } from '@start9labs/shared'
|
import {
|
||||||
|
FetchLogsReq,
|
||||||
|
FetchLogsRes,
|
||||||
|
FullKeyboard,
|
||||||
|
SetLanguageParams,
|
||||||
|
StartOSDiskInfo,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { IST, T } from '@start9labs/start-sdk'
|
import { IST, T } from '@start9labs/start-sdk'
|
||||||
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||||
import {
|
import {
|
||||||
@@ -120,6 +126,12 @@ export namespace RR {
|
|||||||
} // net.tor.reset
|
} // net.tor.reset
|
||||||
export type ResetTorRes = null
|
export type ResetTorRes = null
|
||||||
|
|
||||||
|
export type SetKeyboardReq = FullKeyboard // server.set-keyboard
|
||||||
|
export type SetKeyboardRes = null
|
||||||
|
|
||||||
|
export type SetLanguageReq = SetLanguageParams // server.set-language
|
||||||
|
export type SetLanguageRes = null
|
||||||
|
|
||||||
// smtp
|
// smtp
|
||||||
|
|
||||||
export type SetSMTPReq = T.SmtpValue // server.set-smtp
|
export type SetSMTPReq = T.SmtpValue // server.set-smtp
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ export abstract class ApiService {
|
|||||||
|
|
||||||
abstract toggleKiosk(enable: boolean): Promise<null>
|
abstract toggleKiosk(enable: boolean): Promise<null>
|
||||||
|
|
||||||
|
abstract setKeyboard(params: RR.SetKeyboardReq): Promise<RR.SetKeyboardRes>
|
||||||
|
|
||||||
|
abstract setLanguage(params: RR.SetLanguageReq): Promise<RR.SetLanguageRes>
|
||||||
|
|
||||||
abstract setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes>
|
abstract setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes>
|
||||||
|
|
||||||
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
|
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
|
||||||
|
|||||||
@@ -256,6 +256,14 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setKeyboard(params: RR.SetKeyboardReq): Promise<RR.SetKeyboardRes> {
|
||||||
|
return this.rpcRequest({ method: 'server.set-keyboard', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLanguage(params: RR.SetLanguageReq): Promise<RR.SetLanguageRes> {
|
||||||
|
return this.rpcRequest({ method: 'server.set-language', params })
|
||||||
|
}
|
||||||
|
|
||||||
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
|
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
|
||||||
return this.rpcRequest({
|
return this.rpcRequest({
|
||||||
method: 'net.dns.set-static',
|
method: 'net.dns.set-static',
|
||||||
|
|||||||
@@ -453,6 +453,41 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setKeyboard(params: RR.SetKeyboardReq): Promise<RR.SetKeyboardRes> {
|
||||||
|
await pauseFor(1000)
|
||||||
|
|
||||||
|
const patch = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/serverInfo/keyboard',
|
||||||
|
value: {
|
||||||
|
layout: params.layout,
|
||||||
|
model: params.model,
|
||||||
|
variant: params.variant,
|
||||||
|
options: params.options,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLanguage(params: RR.SetLanguageReq): Promise<RR.SetLanguageRes> {
|
||||||
|
await pauseFor(1000)
|
||||||
|
|
||||||
|
const patch = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/serverInfo/language',
|
||||||
|
value: params.language,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
|
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
startosRegistry: 'https://registry.start9.com/',
|
startosRegistry: 'https://registry.start9.com/',
|
||||||
snakeHighScore: 0,
|
snakeHighScore: 0,
|
||||||
language: 'english',
|
|
||||||
},
|
},
|
||||||
serverInfo: {
|
serverInfo: {
|
||||||
arch: 'x86_64',
|
arch: 'x86_64',
|
||||||
@@ -220,6 +219,14 @@ export const mockPatchData: DataModel = {
|
|||||||
ram: 8 * 1024 * 1024 * 1024,
|
ram: 8 * 1024 * 1024 * 1024,
|
||||||
devices: [],
|
devices: [],
|
||||||
kiosk: true,
|
kiosk: true,
|
||||||
|
language: 'english',
|
||||||
|
keyboard: {
|
||||||
|
layout: 'us',
|
||||||
|
model: null,
|
||||||
|
variant: null,
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
// keyboard: null,
|
||||||
},
|
},
|
||||||
packageData: {
|
packageData: {
|
||||||
lnd: {
|
lnd: {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { Languages } from '@start9labs/shared'
|
import { FullKeyboard, Languages } from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
export type DataModel = T.Public & {
|
export type DataModel = {
|
||||||
ui: UIData
|
ui: UIData
|
||||||
|
serverInfo: T.ServerInfo & {
|
||||||
|
language: Languages
|
||||||
|
keyboard: FullKeyboard | null
|
||||||
|
}
|
||||||
packageData: AllPackageData
|
packageData: AllPackageData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +15,6 @@ export type UIData = {
|
|||||||
registries: Record<string, string | null>
|
registries: Record<string, string | null>
|
||||||
snakeHighScore: number
|
snakeHighScore: number
|
||||||
startosRegistry: string
|
startosRegistry: string
|
||||||
language: Languages
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PackageDataEntry<T extends StateInfo = StateInfo> =
|
export type PackageDataEntry<T extends StateInfo = StateInfo> =
|
||||||
|
|||||||
Reference in New Issue
Block a user