chore: update Taiga to 5 (#3136)

* chore: update Taiga to 5

* chore: fix
This commit is contained in:
Alex Inkin
2026-03-15 19:51:50 +04:00
committed by GitHub
parent be921b7865
commit a90b96cddd
184 changed files with 1508 additions and 1958 deletions

View File

@@ -98,7 +98,8 @@ this.dialog.openComponent(new PolymorpheusComponent(MyComponent, injector), { la
### Errors & Tooltips ### Errors & Tooltips
```html ```html
<tui-error [error]="[] | tuiFieldError | async" /> <tui-error formControlName="controlName" />
<tui-error [error]="'Error text'" />
<tui-icon [tuiTooltip]="'Hint text'" /> <tui-icon [tuiTooltip]="'Hint text'" />
``` ```

View File

@@ -47,7 +47,7 @@
"projects/ui/src/manifest.webmanifest" "projects/ui/src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", "node_modules/@taiga-ui/styles/taiga-ui-theme.less",
"projects/shared/styles/taiga.scss", "projects/shared/styles/taiga.scss",
"projects/shared/styles/shared.scss", "projects/shared/styles/shared.scss",
"projects/ui/src/styles.scss" "projects/ui/src/styles.scss"
@@ -158,7 +158,7 @@
} }
], ],
"styles": [ "styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", "node_modules/@taiga-ui/styles/taiga-ui-theme.less",
"projects/shared/styles/taiga.scss", "projects/shared/styles/taiga.scss",
"projects/shared/styles/shared.scss", "projects/shared/styles/shared.scss",
"projects/setup-wizard/src/styles.scss" "projects/setup-wizard/src/styles.scss"
@@ -258,8 +258,8 @@
} }
], ],
"styles": [ "styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", "node_modules/@taiga-ui/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less", "node_modules/@taiga-ui/styles/taiga-ui-fonts.less",
"projects/shared/styles/shared.scss", "projects/shared/styles/shared.scss",
"projects/start-tunnel/src/styles.scss" "projects/start-tunnel/src/styles.scss"
], ],

489
web/package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "0.4.0-alpha.20", "version": "0.4.0-alpha.20",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@angular/animations": "^21.2.1",
"@angular/cdk": "^21.2.1", "@angular/cdk": "^21.2.1",
"@angular/common": "^21.2.1", "@angular/common": "^21.2.1",
"@angular/compiler": "^21.2.1", "@angular/compiler": "^21.2.1",
@@ -24,19 +23,19 @@
"@noble/hashes": "^1.4.0", "@noble/hashes": "^1.4.0",
"@start9labs/argon2": "^0.3.0", "@start9labs/argon2": "^0.3.0",
"@start9labs/start-sdk": "file:../sdk/baseDist", "@start9labs/start-sdk": "file:../sdk/baseDist",
"@taiga-ui/addon-charts": "4.73.0", "@taiga-ui/addon-charts": "5.0.0-rc.4",
"@taiga-ui/addon-commerce": "4.73.0", "@taiga-ui/addon-commerce": "5.0.0-rc.4",
"@taiga-ui/addon-mobile": "4.73.0", "@taiga-ui/addon-mobile": "5.0.0-rc.4",
"@taiga-ui/addon-table": "4.73.0", "@taiga-ui/addon-table": "5.0.0-rc.4",
"@taiga-ui/cdk": "4.73.0", "@taiga-ui/cdk": "5.0.0-rc.4",
"@taiga-ui/core": "4.73.0", "@taiga-ui/core": "5.0.0-rc.4",
"@taiga-ui/dompurify": "4.1.11", "@taiga-ui/dompurify": "5.0.1",
"@taiga-ui/event-plugins": "4.7.0", "@taiga-ui/event-plugins": "5.0.0",
"@taiga-ui/experimental": "4.73.0", "@taiga-ui/experimental": "5.0.0-rc.4",
"@taiga-ui/icons": "4.73.0", "@taiga-ui/icons": "5.0.0-rc.4",
"@taiga-ui/kit": "4.73.0", "@taiga-ui/kit": "5.0.0-rc.4",
"@taiga-ui/layout": "4.73.0", "@taiga-ui/layout": "5.0.0-rc.4",
"@taiga-ui/polymorpheus": "4.9.0", "@taiga-ui/polymorpheus": "5.0.0",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -46,7 +45,7 @@
"cron": "^2.2.0", "cron": "^2.2.0",
"cronstrue": "^2.21.0", "cronstrue": "^2.21.0",
"deep-equality-data-structures": "1.5.1", "deep-equality-data-structures": "1.5.1",
"dompurify": "^3.1.7", "dompurify": "^3.3.2",
"fast-json-patch": "^3.1.1", "fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"jose": "^4.9.0", "jose": "^4.9.0",
@@ -71,7 +70,6 @@
"@angular/cli": "^21.2.1", "@angular/cli": "^21.2.1",
"@angular/compiler-cli": "^21.2.1", "@angular/compiler-cli": "^21.2.1",
"@angular/language-service": "^21.2.1", "@angular/language-service": "^21.2.1",
"@types/dompurify": "3.0.5",
"@types/estree": "^0.0.51", "@types/estree": "^0.0.51",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/marked": "^4.0.3", "@types/marked": "^4.0.3",
@@ -448,22 +446,6 @@
"hawkeye": "index.mjs" "hawkeye": "index.mjs"
} }
}, },
"node_modules/@angular/animations": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.2.1.tgz",
"integrity": "sha512-zT/S29pUTbziCLvZ2itBdNWd5i8tsXexofH7KA4n2yvYmK1EhNpE7TlHRjghmsHgtDt4VnGiMW4zXEyrl05Dwg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/core": "21.2.1"
}
},
"node_modules/@angular/build": { "node_modules/@angular/build": {
"version": "21.2.1", "version": "21.2.1",
"resolved": "https://registry.npmjs.org/@angular/build/-/build-21.2.1.tgz", "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.2.1.tgz",
@@ -2200,46 +2182,46 @@
] ]
}, },
"node_modules/@maskito/angular": { "node_modules/@maskito/angular": {
"version": "3.11.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.11.1.tgz", "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-5.1.1.tgz",
"integrity": "sha512-+OZzbRJj/9fOGhgPr0xYctSHe/Ngahip3VdNWBslRTpt7g+UTBYcB8vU9J4cHfpdXYeLM3tM0tnKksc3Eis0+Q==", "integrity": "sha512-NCKvUpIF3KCJDRpDWcI0DAeVQaYgsJmGAycq7h/LVu6h7P0nSd5xanYpoM2/F1MdXvGh/aEAF37KooTsfwUqog==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/forms": ">=16.0.0", "@angular/forms": ">=19.0.0",
"@maskito/core": "^3.11.1" "@maskito/core": "^5.1.1"
} }
}, },
"node_modules/@maskito/core": { "node_modules/@maskito/core": {
"version": "3.11.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.11.1.tgz", "resolved": "https://registry.npmjs.org/@maskito/core/-/core-5.1.1.tgz",
"integrity": "sha512-zN5k/BiZXblo8mEFhsGnnXBCqKMkjEGArorOOcpB1/ymZyqF12Dk6IipEsSE6abMnWw4YF2tukzfq73BFZKz8A==", "integrity": "sha512-grxipbOGKBs++kgAM/5K/lCghY/AfkSKNcRDSp8Nspf9dngxWiraMiPQVFsS8E0sab1wHEWlMXBdqEa3WyQXPA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true "peer": true
}, },
"node_modules/@maskito/kit": { "node_modules/@maskito/kit": {
"version": "3.11.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.11.1.tgz", "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-5.1.1.tgz",
"integrity": "sha512-KOBUqxRz383xJWCoe+Emwxv2oAzUrZobIN+Gntmi5Py2S10XbqYnGX/6W7QHN8CUK2Nx11d3HsxbEQaq5Hinjg==", "integrity": "sha512-YlMPGzyX/zuYaAxWRFTwBYoP3bV1WzMhrW8D+7Tn6nUfCBUTogvnJrVhVyOi6dZzXGX8qP1yRVT6W/IVt79F5g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"peerDependencies": { "peerDependencies": {
"@maskito/core": "^3.11.1" "@maskito/core": "^5.1.1"
} }
}, },
"node_modules/@maskito/phone": { "node_modules/@maskito/phone": {
"version": "3.11.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.11.1.tgz", "resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-5.1.1.tgz",
"integrity": "sha512-ptNDPIZQs/v598qydBa9cnvoCE8+k2Sv07kKKVx3vG0V40DQnIlEL+LYKrJJbMIiPOB6CH90hB9eaA9KKReZ6w==", "integrity": "sha512-yBb42/7FZpEjlAvvT5SabWX8jZdMjhGv4g7G6McarVM1RcG6vcV+kZd0xjAdDvOv8s5zGww+pN39Scn0tt+QMg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"peerDependencies": { "peerDependencies": {
"@maskito/core": "^3.11.1", "@maskito/core": "^5.1.1",
"@maskito/kit": "^3.11.1", "@maskito/kit": "^5.1.1",
"libphonenumber-js": ">=1.0.0" "libphonenumber-js": ">=1.0.0"
} }
}, },
@@ -2732,52 +2714,53 @@
} }
}, },
"node_modules/@ng-web-apis/common": { "node_modules/@ng-web-apis/common": {
"version": "4.14.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-4.14.0.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-5.1.0.tgz",
"integrity": "sha512-eLrpy9e8R+eJKRUWv06jauaDCJ/6/9llmHPTvmWH3yTS9HIn+twk5ZmyfNwFqA/r8g9Kt4Ap48YuaydQy00nuw==", "integrity": "sha512-omhjrojnwgJXIPT4H4sLruqCSGlWeLLuIAUkDB+fIARd1shT0vYmA9qugaNd54XmLOnL7Hu97FYkXnULFyT85w==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@types/dom-speech-recognition": "^0.0.7",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@ng-web-apis/intersection-observer": { "node_modules/@ng-web-apis/intersection-observer": {
"version": "4.14.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-4.14.0.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-5.1.0.tgz",
"integrity": "sha512-VDuak0+jDPwPttqQbgIYCD1tM2F7wKSXZWWUjFdNfkV47GspEPzzf12lY1eiltIiCB4cv6asZNs5ijnsxpnb9A==", "integrity": "sha512-Fonur9MS3bxDyCbGX3gcuEsvIRPA/0PrkpOe2EohVJMdrVjs8eB6SbQR4DSKVv5We1maMSzt3jhd1u5KEMp2xA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": ">=4.12.0" "@ng-web-apis/common": "^5.1.0"
} }
}, },
"node_modules/@ng-web-apis/mutation-observer": { "node_modules/@ng-web-apis/mutation-observer": {
"version": "4.14.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-4.14.0.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-5.1.0.tgz",
"integrity": "sha512-AUZDtVWN1rUSZoaB5AWhRYZ8DNSPoPQ2Ld8G8q9ocxfpslDHvjQQjUj5r0zHjAwKe1f9cNuow2nU+n6k3hF6ew==", "integrity": "sha512-X8DepT55KG/xY/WnQ05pnSxpyzv76CWiDrKanzOeUy9SUckrfpjCWkGvyz9fSzUv27mOC4t/ry6Fnu+em28Tzg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": ">=4.12.0" "@ng-web-apis/common": "^5.1.0"
} }
}, },
"node_modules/@ng-web-apis/platform": { "node_modules/@ng-web-apis/platform": {
"version": "4.14.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/platform/-/platform-4.14.0.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/platform/-/platform-5.1.0.tgz",
"integrity": "sha512-zmsbg0cEumppG6VlxIpM+ghJXBJ6G2mlTm+XmC9HYCnRJxYJjqIVRDZbYUQ4pGQHz4iJxRbp2C3VOndre72+3g==", "integrity": "sha512-Jp6BqEDQ0C7/NpQ2TqTvy2ddjcOt1nON9KlCOaIR7L+YY88KToeQLD3dsFRFbxwWB1G1okgpxysMoFunatltYQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -2785,31 +2768,31 @@
} }
}, },
"node_modules/@ng-web-apis/resize-observer": { "node_modules/@ng-web-apis/resize-observer": {
"version": "4.14.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-4.14.0.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-5.1.0.tgz",
"integrity": "sha512-W5VXT/y0Vv68Gx/2urgu/JmIQfv0jPtzfvGtyulgGALfBrnei4oVXvJe2aZmLb0/VkxH65qlYOKVEOGqBrwSwQ==", "integrity": "sha512-cuEPAaZJ2UDOA283S/Ehz7tdDLuCY6tLmdcYHlXTdnMcXiyrqUsu0dKQ04ZoZ0KXfoSjT95jPUTabot5psVPkw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": ">=4.12.0" "@ng-web-apis/common": "^5.1.0"
} }
}, },
"node_modules/@ng-web-apis/screen-orientation": { "node_modules/@ng-web-apis/screen-orientation": {
"version": "4.14.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/screen-orientation/-/screen-orientation-4.14.0.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/screen-orientation/-/screen-orientation-5.1.0.tgz",
"integrity": "sha512-XlXjxhYT7QKW0WKQE4BIJ2OZ7c1EFRXUQ4eXQ41Jwl8ukG0zkixOTrxGlnfy7ijX5df3sX/+0kKCXwsyj/n5BA==", "integrity": "sha512-x6W2C45EAcaHk/0QR5hcWg6bByyPD+E/xPlQjR8sQwLN+AE8ag1hrhmIbkQOyEP2K2kvU81M0ndbre0o4dRo9Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": ">=4.12.0", "@ng-web-apis/common": "^5.1.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
@@ -4182,118 +4165,118 @@
"link": true "link": true
}, },
"node_modules/@taiga-ui/addon-charts": { "node_modules/@taiga-ui/addon-charts": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-5.0.0-rc.4.tgz",
"integrity": "sha512-BZhmupFnngsGICumSvaA7b8jf5lprQ5PD5Nqduq6iQ/pa02qv4VdR7HE2OM07kg4pBm4OX3OtJ0H9V0N/buchQ==", "integrity": "sha512-goStkREzlt2vgK0gRI2o2BEud8YNqMxXAG3NW1YxyAO/P13OwzuxWxGBW013ykzxfpEVynUlswpUPWHyowNa8g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/common": "^5.1.0",
"@taiga-ui/cdk": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0" "@taiga-ui/polymorpheus": "^5.0.0"
} }
}, },
"node_modules/@taiga-ui/addon-commerce": { "node_modules/@taiga-ui/addon-commerce": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-5.0.0-rc.4.tgz",
"integrity": "sha512-jLNGPZgAnqhrLp5jJ2LNaDVuQs7SX1pUitDm4xpt26GnO7GuQmjY2E0SmZtEbBf/9ykROAnPzgWJbaIj6e5V0A==", "integrity": "sha512-5Z/QbxC4NQKJ0F7Y7oJG86o1Ek+4XiBeV61F42/syHmD1/3Y2KEll+1GBvlx++xFHJvjfa0+xFUx3omLJjIGzA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/forms": ">=16.0.0", "@angular/forms": ">=19.0.0",
"@maskito/angular": "^3.11.1", "@maskito/angular": "^5.1.1",
"@maskito/core": "^3.11.1", "@maskito/core": "^5.1.1",
"@maskito/kit": "^3.11.1", "@maskito/kit": "^5.1.1",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/common": "^5.1.0",
"@taiga-ui/cdk": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/i18n": "^4.73.0", "@taiga-ui/i18n": "^5.0.0-rc.4",
"@taiga-ui/kit": "^4.73.0", "@taiga-ui/kit": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/polymorpheus": "^5.0.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/addon-mobile": { "node_modules/@taiga-ui/addon-mobile": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-5.0.0-rc.4.tgz",
"integrity": "sha512-5ohDbwkE2ur57eMYmM6Tl7IOvVNxzPiHnZRB5ZcntmDkFp7UBgSOFptelqraL9DP8w5LEmvrshT6ObygRQNe+Q==", "integrity": "sha512-EkPCgOSnj7+SmgcQu4HVsmTNsBYiJ5SpuoktgxtZPmfeK4GXEmO1z6Pizotb3EHmyuyNBN69jeS4IPkdplvzsw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/cdk": ">=16.0.0", "@angular/cdk": ">=19.0.0",
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/common": "^5.1.0",
"@taiga-ui/cdk": "^4.73.0", "@ng-web-apis/platform": "^5.1.0",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/kit": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/layout": "^4.73.0", "@taiga-ui/kit": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/layout": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^5.0.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/addon-table": { "node_modules/@taiga-ui/addon-table": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-5.0.0-rc.4.tgz",
"integrity": "sha512-fRxnCopvanInTdrVtNNXclY6T5u3fU87De564sngAcV9Y8Q1O44bZCyyDE/+jsoz3IIDZUD+2OEZjOGHMWLFOA==", "integrity": "sha512-j3ICBUoj4XKKvzzIbTQyLdif32+lHSlBozFDQcXhtL1x9C1mWOvidgU9kcsjygs0lEnjoIUTVza59sYMv3UKMQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/intersection-observer": "^4.14.0", "@ng-web-apis/intersection-observer": "^5.1.0",
"@taiga-ui/cdk": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/i18n": "^4.73.0", "@taiga-ui/i18n": "^5.0.0-rc.4",
"@taiga-ui/kit": "^4.73.0", "@taiga-ui/kit": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/polymorpheus": "^5.0.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/cdk": { "node_modules/@taiga-ui/cdk": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-5.0.0-rc.4.tgz",
"integrity": "sha512-bNRr5ORof60KevnMINNhbLbj8U9xgEMNvagzTtrtjVfEvCOJE1/+ufCnWjjkEdT/V0fDnbuhKsu16ngctbOhXQ==", "integrity": "sha512-eZhqZvK8OE+alI5zJ3z0bQdufj77HCtREOC+Wsv5Fjz1mRZz4X19V3hpISfb6arp1m7kBfIscaPcZi0MuTcbUg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"@angular-devkit/core": ">=16.0.0", "@angular-devkit/core": ">=19.0.0",
"@angular-devkit/schematics": ">=16.0.0", "@angular-devkit/schematics": ">=19.0.0",
"@schematics/angular": ">=16.0.0", "@schematics/angular": ">=19.0.0",
"ng-morph": "^4.8.4", "ng-morph": "^4.8.4",
"parse5": "^7.3.0" "parse5": "^7.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/animations": ">=16.0.0", "@angular/cdk": ">=19.0.0",
"@angular/cdk": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/common": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/forms": ">=19.0.0",
"@angular/forms": ">=16.0.0", "@ng-web-apis/common": "^5.1.0",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/mutation-observer": "^5.1.0",
"@ng-web-apis/mutation-observer": "^4.14.0", "@ng-web-apis/platform": "^5.1.0",
"@ng-web-apis/platform": "^4.14.0", "@ng-web-apis/resize-observer": "^5.1.0",
"@ng-web-apis/resize-observer": "^4.14.0", "@ng-web-apis/screen-orientation": "^5.1.0",
"@ng-web-apis/screen-orientation": "^4.14.0", "@taiga-ui/event-plugins": "^5.0.0",
"@taiga-ui/event-plugins": "^4.7.0", "@taiga-ui/font-watcher": "~0.5.0",
"@taiga-ui/font-watcher": "~0.3.0", "@taiga-ui/polymorpheus": "^5.0.0",
"@taiga-ui/polymorpheus": "^4.9.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
@@ -4324,49 +4307,59 @@
} }
}, },
"node_modules/@taiga-ui/core": { "node_modules/@taiga-ui/core": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-5.0.0-rc.4.tgz",
"integrity": "sha512-8EyHWgenkWxdfRP1LtqkE/bV/9a3mK8MtVmFxauEjcbeyafnE6MJ7K2KgD+iU9TZsOdH7nuWiA44Sv14t1wcUA==", "integrity": "sha512-mO1W/4Y743pk3dDg539J3rbAJ5CzNDU+EBCAxYV4wOftpFeSJuCFYvATt6p8XGHtTRCDk6dNXQ1+fkR7PqQ9Yg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/animations": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/common": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/forms": ">=19.0.0",
"@angular/forms": ">=16.0.0", "@angular/platform-browser": ">=19.0.0",
"@angular/platform-browser": ">=16.0.0", "@angular/router": ">=19.0.0",
"@angular/router": ">=16.0.0", "@ng-web-apis/common": "^5.1.0",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/mutation-observer": "^5.1.0",
"@ng-web-apis/mutation-observer": "^4.14.0", "@ng-web-apis/platform": "^5.1.0",
"@taiga-ui/cdk": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/event-plugins": "^4.7.0", "@taiga-ui/event-plugins": "^5.0.0",
"@taiga-ui/i18n": "^4.73.0", "@taiga-ui/i18n": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/polymorpheus": "^5.0.0",
"@taiga-ui/styles": "^5.0.0-rc.4",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/design-tokens": {
"version": "0.283.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/design-tokens/-/design-tokens-0.283.0.tgz",
"integrity": "sha512-fuLJYwY5a499hh4Cw+ABcO5bEdhcVP8l/1t6GQZApV6L1NfFQgmnf16vbtvGe/0TFCALpoydUGnELtZuwnrRiA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
}
},
"node_modules/@taiga-ui/dompurify": { "node_modules/@taiga-ui/dompurify": {
"version": "4.1.11", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@taiga-ui/dompurify/-/dompurify-4.1.11.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/dompurify/-/dompurify-5.0.1.tgz",
"integrity": "sha512-nwskeI/wFe+spuLQdkhVCn/GJBHJamxZ5deZuyto7C4I3O+pF8GaCNJdKM1YG5TZyGz932Qm8uNtBVZ/fZX8sQ==", "integrity": "sha512-5OdVzGRMEiy1g/WZUtbPbBt3f7A1lGDWFOm9a4MZGZrGhONv2++uIKLDBkOQchY8ur09vo+QC/pBSRGCIgeQ+g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/platform-browser": ">=16.0.0", "@angular/platform-browser": ">=19.0.0",
"@types/dompurify": "3.0.5", "dompurify": "^3.2.7"
"dompurify": "3.1.7"
} }
}, },
"node_modules/@taiga-ui/event-plugins": { "node_modules/@taiga-ui/event-plugins": {
"version": "4.7.0", "version": "5.0.0",
"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-5.0.0.tgz",
"integrity": "sha512-j3HPRPR7XxKxgMeytb+r/CNUoLBMVrfdfL8KJr1XiFO9jyEvoC4chFXDXWlkGyUHJIC6wy5VIXlIlI/kpqOiGg==", "integrity": "sha512-8AxIUn/lX4kwuDlIFmhElgBNtGDgQVC9/F+3O2A29IcMSt9693KRi8qKwToe9p3UuUsT9nnj5YeE11BtJMG0wA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -4379,116 +4372,128 @@
} }
}, },
"node_modules/@taiga-ui/experimental": { "node_modules/@taiga-ui/experimental": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-5.0.0-rc.4.tgz",
"integrity": "sha512-OFRO4rhWY780gf/iMip7sJJ1BBT2rWG5663Qqu/+6SAF4pyzcgKq5uIAkULQYFBQwqBNyB0zAx3fZAqdQ8m34A==", "integrity": "sha512-/kw2uLivfeaNse8DkBABPNpTu3GiZ2R7JB0exBK0Flv0xZ3zZLRuIOMK2uHQqoc3v1zgQaFukXTz6CWuzUJYDA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@taiga-ui/addon-commerce": "^4.73.0", "@taiga-ui/addon-commerce": "^5.0.0-rc.4",
"@taiga-ui/cdk": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/kit": "^4.73.0", "@taiga-ui/kit": "^5.0.0-rc.4",
"@taiga-ui/layout": "^4.73.0", "@taiga-ui/layout": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/polymorpheus": "^5.0.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/font-watcher": { "node_modules/@taiga-ui/font-watcher": {
"version": "0.3.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/font-watcher/-/font-watcher-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/font-watcher/-/font-watcher-0.5.0.tgz",
"integrity": "sha512-ldI8XMvpVQEfxtcCzbLKs02QbqyB+qJKHIV/x19Q5mxs+kqrS3Pzm3j4mt8tPnuWhgi4+PHvAk3QLN9zmwcoJg==", "integrity": "sha512-9QBUh3XT6KOS+pKVy4ggr0CxgiZtbDYlterUNcfXw8cswkIkiUl5VGDQdvmta60iPHqOrOd/Z/7aAUom35vX/g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true "peer": true
}, },
"node_modules/@taiga-ui/i18n": { "node_modules/@taiga-ui/i18n": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-5.0.0-rc.4.tgz",
"integrity": "sha512-nU7pXeH3+mwQdWZCnXILDAZhRcQ4L/Gl+Y/G/yFpvkEfoCljhVsY0HZZgMSAEGfOSeS7R0ZhZX+9be6uHUvn4w==", "integrity": "sha512-3hxhfewlY+5vsLPPl47rtlt47iVqnhDI2uD6waUm0ZqL/0YLhTEowkY2/DHhXoFp4+ZVcyTVRCWGf0G7BnJG+Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/common": "^5.1.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/icons": { "node_modules/@taiga-ui/icons": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-5.0.0-rc.4.tgz",
"integrity": "sha512-Ae8qTTf18OlAyFgv04aDkQ6T81J0c8NtFtGWMbNnNc2/CtVt2s0e7z6dcAvs0LODNidzXU6jn/OQT4w4PeAe+w==", "integrity": "sha512-2MZLcSf8x2+5yizWVdW9Ual6++zdzU64MXWeBx8zlNVyeedxTww5AFbVFs16ItaeT4YdZM0uZmRY59TmnCP9Rg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"node_modules/@taiga-ui/kit": { "node_modules/@taiga-ui/kit": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-5.0.0-rc.4.tgz",
"integrity": "sha512-8dypi4hZLi0GEFuraQERLNyKbR56BqQjU7c6p9x7X7iiMt4LpHUVH9+kd+e9irGWMr09SWLx4PFNV+q+T1c8TQ==", "integrity": "sha512-MzK8tSzEl0dkhWcuKxmem/DSF+NnPvsNWJyGEx7hJO3IH0Ukcu806YfsSUld392U/7PZePAFRWKgMADdkQY29g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/forms": ">=16.0.0", "@angular/forms": ">=19.0.0",
"@angular/router": ">=16.0.0", "@angular/router": ">=19.0.0",
"@maskito/angular": "^3.11.1", "@maskito/angular": "^5.1.1",
"@maskito/core": "^3.11.1", "@maskito/core": "^5.1.1",
"@maskito/kit": "^3.11.1", "@maskito/kit": "^5.1.1",
"@maskito/phone": "^3.11.1", "@maskito/phone": "^5.1.1",
"@ng-web-apis/common": "^4.14.0", "@ng-web-apis/common": "^5.1.0",
"@ng-web-apis/intersection-observer": "^4.14.0", "@ng-web-apis/intersection-observer": "^5.1.0",
"@ng-web-apis/mutation-observer": "^4.14.0", "@ng-web-apis/mutation-observer": "^5.1.0",
"@ng-web-apis/resize-observer": "^4.14.0", "@ng-web-apis/platform": "^5.1.0",
"@taiga-ui/cdk": "^4.73.0", "@ng-web-apis/resize-observer": "^5.1.0",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/i18n": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/i18n": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^5.0.0",
"@taiga-ui/styles": "^5.0.0-rc.4",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/layout": { "node_modules/@taiga-ui/layout": {
"version": "4.73.0", "version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.73.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-5.0.0-rc.4.tgz",
"integrity": "sha512-JX2DRCdGw3ayvW07RtI7MjrceO7m7/0wj2RfkjT2Pa93elWooS85lDVy88tlVfcfm/bV2ZGwbemyecAwNjk/yQ==", "integrity": "sha512-sKuksa89xxeIHgx3QchpTorySsjz+XMhcYi5ix/Mh/u+vgDxrcLCYGWAx9fcG/3mGTaCIIEMfaEQDs28jb9Ptw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": ">=2.8.1" "tslib": ">=2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@taiga-ui/cdk": "^4.73.0", "@taiga-ui/cdk": "^5.0.0-rc.4",
"@taiga-ui/core": "^4.73.0", "@taiga-ui/core": "^5.0.0-rc.4",
"@taiga-ui/kit": "^4.73.0", "@taiga-ui/kit": "^5.0.0-rc.4",
"@taiga-ui/polymorpheus": "^4.9.0", "@taiga-ui/polymorpheus": "^5.0.0",
"rxjs": ">=7.0.0" "rxjs": ">=7.0.0"
} }
}, },
"node_modules/@taiga-ui/polymorpheus": { "node_modules/@taiga-ui/polymorpheus": {
"version": "4.9.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-5.0.0.tgz",
"integrity": "sha512-TbIIwslbEnxunKuL9OyPZdmefrvJEK6HYiADEKQHUMUs4Pk2UbhMckUieURo83yPDamk/Mww+Nu/g60J/4uh2w==", "integrity": "sha512-FRus7OgxYyRuETB17g1/YXYs8PAeF4x8+K4ZDfD3Ede8Vxv/XslAYZvf/Ro0wag6Uiy0kAP4bvSaIGPS1Y8Fyg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/platform-browser": ">=16.0.0" "@angular/platform-browser": ">=19.0.0"
}
},
"node_modules/@taiga-ui/styles": {
"version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-5.0.0-rc.4.tgz",
"integrity": "sha512-xAiHSlVFIv6Ti5BEE3AIkjt0zUW5jibeWnB+P7I9D7+E3vV0n/XlpX0SBHKK6DMdrVaPgURzNQnjSrwwtmkWxQ==",
"license": "Apache-2.0",
"peer": true,
"peerDependencies": {
"@taiga-ui/design-tokens": "~0.283.0"
} }
}, },
"node_modules/@ts-morph/common": { "node_modules/@ts-morph/common": {
@@ -4622,15 +4627,12 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/dompurify": { "node_modules/@types/dom-speech-recognition": {
"version": "3.0.5", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.7.tgz",
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", "integrity": "sha512-NjiUoJbBlKhyufNsMZLSp+pbPNtPAFnR738RCJvtZy/HVQ2TZjmqpMyaeOSMXgxdfZM60nt8QGbtfmQrJAH2sw==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true
"dependencies": {
"@types/trusted-types": "*"
}
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "0.0.51", "version": "0.0.51",
@@ -4715,7 +4717,8 @@
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT" "license": "MIT",
"optional": true
}, },
"node_modules/@types/uuid": { "node_modules/@types/uuid": {
"version": "8.3.4", "version": "8.3.4",
@@ -6089,11 +6092,17 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.1.7", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz",
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==", "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==",
"license": "(MPL-2.0 OR Apache-2.0)", "license": "(MPL-2.0 OR Apache-2.0)",
"peer": true "peer": true,
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
}, },
"node_modules/domutils": { "node_modules/domutils": {
"version": "3.2.2", "version": "3.2.2",
@@ -8025,9 +8034,9 @@
} }
}, },
"node_modules/libphonenumber-js": { "node_modules/libphonenumber-js": {
"version": "1.12.36", "version": "1.12.38",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.36.tgz", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.38.tgz",
"integrity": "sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ==", "integrity": "sha512-vwzxmasAy9hZigxtqTbFEwp8ZdZ975TiqVDwj5bKx5sR+zi5ucUQy9mbVTkKM9GzqdLdxux/hTw2nmN5J7POMA==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },

View File

@@ -33,7 +33,6 @@
"format:check": "prettier --check projects/" "format:check": "prettier --check projects/"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "^21.2.1",
"@angular/cdk": "^21.2.1", "@angular/cdk": "^21.2.1",
"@angular/common": "^21.2.1", "@angular/common": "^21.2.1",
"@angular/compiler": "^21.2.1", "@angular/compiler": "^21.2.1",
@@ -48,19 +47,19 @@
"@noble/hashes": "^1.4.0", "@noble/hashes": "^1.4.0",
"@start9labs/argon2": "^0.3.0", "@start9labs/argon2": "^0.3.0",
"@start9labs/start-sdk": "file:../sdk/baseDist", "@start9labs/start-sdk": "file:../sdk/baseDist",
"@taiga-ui/addon-charts": "4.73.0", "@taiga-ui/addon-charts": "5.0.0-rc.4",
"@taiga-ui/addon-commerce": "4.73.0", "@taiga-ui/addon-commerce": "5.0.0-rc.4",
"@taiga-ui/addon-mobile": "4.73.0", "@taiga-ui/addon-mobile": "5.0.0-rc.4",
"@taiga-ui/addon-table": "4.73.0", "@taiga-ui/addon-table": "5.0.0-rc.4",
"@taiga-ui/cdk": "4.73.0", "@taiga-ui/cdk": "5.0.0-rc.4",
"@taiga-ui/core": "4.73.0", "@taiga-ui/core": "5.0.0-rc.4",
"@taiga-ui/dompurify": "4.1.11", "@taiga-ui/dompurify": "5.0.1",
"@taiga-ui/event-plugins": "4.7.0", "@taiga-ui/event-plugins": "5.0.0",
"@taiga-ui/experimental": "4.73.0", "@taiga-ui/experimental": "5.0.0-rc.4",
"@taiga-ui/icons": "4.73.0", "@taiga-ui/icons": "5.0.0-rc.4",
"@taiga-ui/kit": "4.73.0", "@taiga-ui/kit": "5.0.0-rc.4",
"@taiga-ui/layout": "4.73.0", "@taiga-ui/layout": "5.0.0-rc.4",
"@taiga-ui/polymorpheus": "4.9.0", "@taiga-ui/polymorpheus": "5.0.0",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -70,7 +69,7 @@
"cron": "^2.2.0", "cron": "^2.2.0",
"cronstrue": "^2.21.0", "cronstrue": "^2.21.0",
"deep-equality-data-structures": "1.5.1", "deep-equality-data-structures": "1.5.1",
"dompurify": "^3.1.7", "dompurify": "^3.3.2",
"fast-json-patch": "^3.1.1", "fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"jose": "^4.9.0", "jose": "^4.9.0",
@@ -95,7 +94,6 @@
"@angular/cli": "^21.2.1", "@angular/cli": "^21.2.1",
"@angular/compiler-cli": "^21.2.1", "@angular/compiler-cli": "^21.2.1",
"@angular/language-service": "^21.2.1", "@angular/language-service": "^21.2.1",
"@types/dompurify": "3.0.5",
"@types/estree": "^0.0.51", "@types/estree": "^0.0.51",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/marked": "^4.0.3", "@types/marked": "^4.0.3",

View File

@@ -1,4 +1,4 @@
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
margin: 0; margin: 0;

View File

@@ -15,12 +15,9 @@ import { MarketplacePkgBase } from '../../../types'
selector: 'marketplace-dep-item', selector: 'marketplace-dep-item',
template: ` template: `
<div class="outer-container"> <div class="outer-container">
<tui-avatar <span tuiAvatar appearance="action-grayscale" class="dep-img" size="l">
appearance="action-grayscale" <img alt="" [src]="getImage(dep.key)" />
class="dep-img" </span>
size="l"
[src]="getImage(dep.key)"
/>
<div> <div>
<tui-line-clamp [linesLimit]="2" [content]="titleContent" /> <tui-line-clamp [linesLimit]="2" [content]="titleContent" />
<ng-template #titleContent> <ng-template #titleContent>
@@ -107,8 +104,10 @@ export class MarketplaceDepItemComponent {
dep!: KeyValue<string, T.DependencyMetadata> dep!: KeyValue<string, T.DependencyMetadata>
getImage(key: string) { getImage(key: string) {
const icon = this.pkg.dependencyMetadata[key]?.icon return (
return icon ? icon : 'assets/img/service-icons/fallback.png' this.pkg.dependencyMetadata[key]?.icon ||
'assets/img/service-icons/fallback.png'
)
} }
getTitle(key: string): string { getTitle(key: string): string {

View File

@@ -1,9 +1,8 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { i18nPipe, TrustUrlPipe } from '@start9labs/shared' import { i18nPipe, TrustUrlPipe } from '@start9labs/shared'
import { TuiTitle } from '@taiga-ui/core' import { TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiAvatar } from '@taiga-ui/kit' import { TuiAvatar } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { MarketplacePkg } from '../../types' import { MarketplacePkg } from '../../types'
@Component({ @Component({
@@ -21,10 +20,9 @@ import { MarketplacePkg } from '../../types'
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }" [queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
queryParamsHandling="merge" queryParamsHandling="merge"
> >
<tui-avatar <span tuiAvatar appearance="action-grayscale">
appearance="action-grayscale" <img alt="" [src]="pkg.icon | trustUrl" />
[src]="pkg.icon | trustUrl" </span>
/>
<span tuiTitle> <span tuiTitle>
{{ pkg.title }} {{ pkg.title }}
<span tuiSubtitle>{{ pkg.version }}</span> <span tuiSubtitle>{{ pkg.version }}</span>

View File

@@ -4,7 +4,7 @@ import {
inject, inject,
Input, Input,
} from '@angular/core' } from '@angular/core'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TuiButton, TuiDialogContext, TuiDialogService } from '@taiga-ui/core' import { TuiButton, TuiDialogContext, TuiDialogService } from '@taiga-ui/core'
import { TuiCarousel } from '@taiga-ui/kit' import { TuiCarousel } from '@taiga-ui/kit'
import { PolymorpheusContent } from '@taiga-ui/polymorpheus' import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
@@ -130,7 +130,7 @@ export class MarketplacePackageScreenshotComponent {
index = 0 index = 0
isMobile = inject(TUI_IS_MOBILE) isMobile = inject(WA_IS_MOBILE)
presentModalImg(content: PolymorpheusContent<TuiDialogContext>) { presentModalImg(content: PolymorpheusContent<TuiDialogContext>) {
this.dialogs this.dialogs

View File

@@ -8,7 +8,7 @@ import { StateService } from './services/state.service'
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
template: '<tui-root tuiTheme="dark"><router-outlet /></tui-root>', template: '<tui-root><router-outlet /></tui-root>',
imports: [TuiRoot, RouterOutlet], imports: [TuiRoot, RouterOutlet],
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {

View File

@@ -10,7 +10,6 @@ import {
provideZoneChangeDetection, provideZoneChangeDetection,
signal, signal,
} from '@angular/core' } from '@angular/core'
import { provideAnimations } from '@angular/platform-browser/animations'
import { import {
PreloadAllModules, PreloadAllModules,
provideRouter, provideRouter,
@@ -29,8 +28,10 @@ import {
import { import {
tuiButtonOptionsProvider, tuiButtonOptionsProvider,
tuiTextfieldOptionsProvider, tuiTextfieldOptionsProvider,
provideTaiga,
tuiHintOptionsProvider,
tuiDialogOptionsProvider,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { provideEventPlugins } from '@taiga-ui/event-plugins'
import { ROUTES } from './app.routes' import { ROUTES } from './app.routes'
import { ApiService } from './services/api.service' import { ApiService } from './services/api.service'
@@ -47,8 +48,9 @@ const version = require('../../../../package.json').version
export const APP_CONFIG: ApplicationConfig = { export const APP_CONFIG: ApplicationConfig = {
providers: [ providers: [
provideZoneChangeDetection(), provideZoneChangeDetection(),
provideAnimations(), provideTaiga({ mode: 'dark' }),
provideEventPlugins(), tuiHintOptionsProvider({ appearance: 'primary-grayscale' }),
tuiDialogOptionsProvider({ size: 's' }),
provideRouter( provideRouter(
ROUTES, ROUTES,
withDisabledInitialNavigation(), withDisabledInitialNavigation(),

View File

@@ -13,14 +13,10 @@ import {
TuiDialogContext, TuiDialogContext,
TuiError, TuiError,
TuiIcon, TuiIcon,
TuiTextfield, TuiInput,
tuiValidationErrorsProvider,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { import { TuiButtonLoading, TuiPassword } from '@taiga-ui/kit'
TUI_VALIDATION_ERRORS,
TuiButtonLoading,
TuiFieldErrorPipe,
TuiPassword,
} from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from '../services/api.service' import { ApiService } from '../services/api.service'
import { StartOSDiskInfoWithId } from '../types' import { StartOSDiskInfoWithId } from '../types'
@@ -36,42 +32,36 @@ export interface CifsResult {
<tui-textfield> <tui-textfield>
<label tuiLabel>{{ 'Hostname' | i18n }}*</label> <label tuiLabel>{{ 'Hostname' | i18n }}*</label>
<input <input
tuiTextfield tuiInput
formControlName="hostname" formControlName="hostname"
placeholder="e.g. 'My Computer' OR 'my-computer.local'" placeholder="e.g. 'My Computer' OR 'my-computer.local'"
/> />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="hostname" [order]="['required']" />
formControlName="hostname"
[error]="['required'] | tuiFieldError | async"
/>
<tui-textfield class="input"> <tui-textfield class="input">
<label tuiLabel>{{ 'Path' | i18n }}*</label> <label tuiLabel>{{ 'Path' | i18n }}*</label>
<input <input
tuiTextfield tuiInput
formControlName="path" formControlName="path"
placeholder="/Desktop/my-folder" placeholder="/Desktop/my-folder"
/> />
</tui-textfield> </tui-textfield>
<tui-error formControlName="path" [error]="[] | tuiFieldError | async" /> <tui-error formControlName="path" />
<tui-textfield class="input"> <tui-textfield class="input">
<label tuiLabel>{{ 'Username' | i18n }}*</label> <label tuiLabel>{{ 'Username' | i18n }}*</label>
<input <input
tuiTextfield tuiInput
formControlName="username" formControlName="username"
placeholder="Enter username" placeholder="Enter username"
/> />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="username" />
formControlName="username"
[error]="[] | tuiFieldError | async"
/>
<tui-textfield class="input"> <tui-textfield class="input">
<label tuiLabel>{{ 'Password' | i18n }}</label> <label tuiLabel>{{ 'Password' | i18n }}</label>
<input tuiTextfield type="password" formControlName="password" /> <input tuiInput type="password" formControlName="password" />
<tui-icon tuiPassword /> <tui-icon tuiPassword />
</tui-textfield> </tui-textfield>
@@ -107,20 +97,16 @@ export interface CifsResult {
ReactiveFormsModule, ReactiveFormsModule,
TuiButton, TuiButton,
TuiButtonLoading, TuiButtonLoading,
TuiTextfield, TuiInput,
TuiPassword, TuiPassword,
TuiError, TuiError,
TuiFieldErrorPipe,
TuiIcon, TuiIcon,
i18nPipe, i18nPipe,
], ],
providers: [ providers: [
{ tuiValidationErrorsProvider({
provide: TUI_VALIDATION_ERRORS, required: 'This field is required',
useValue: { }),
required: 'This field is required',
},
},
], ],
}) })
export class CifsComponent { export class CifsComponent {
@@ -183,10 +169,7 @@ export class CifsComponent {
this.dialogs this.dialogs
.openAlert( .openAlert(
'Unable to connect to network folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.', 'Unable to connect to network folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.',
{ { label: 'Connection Failed' },
label: 'Connection Failed',
size: 's',
},
) )
.subscribe() .subscribe()
} }

View File

@@ -1,13 +1,19 @@
import { Component, inject } from '@angular/core' import { Component } from '@angular/core'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { TuiButton } from '@taiga-ui/core' import { TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiDialogContext } from '@taiga-ui/core' import { TuiDialogContext } from '@taiga-ui/core'
import { injectContext } from '@taiga-ui/polymorpheus' import { TuiHeader } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
@Component({ @Component({
imports: [TuiButton, i18nPipe], imports: [TuiButton, TuiHeader, TuiTitle, i18nPipe],
template: ` template: `
<p>{{ 'This drive contains existing StartOS data.' | i18n }}</p> <header tuiHeader>
<hgroup tuiTitle>
<h2 [id]="context.id">{{ 'StartOS Data Detected' | i18n }}</h2>
<p>{{ 'This drive contains existing StartOS data.' | i18n }}</p>
</hgroup>
</header>
<ul> <ul>
<li> <li>
<strong class="g-positive">{{ 'Preserve' | i18n }}</strong> <strong class="g-positive">{{ 'Preserve' | i18n }}</strong>
@@ -28,30 +34,19 @@ import { injectContext } from '@taiga-ui/polymorpheus'
</button> </button>
<button <button
tuiButton tuiButton
class="preserve-btn" appearance=""
[style.background]="'var(--tui-status-positive)'"
(click)="context.completeWith(true)" (click)="context.completeWith(true)"
> >
{{ 'Preserve' | i18n }} {{ 'Preserve' | i18n }}
</button> </button>
</footer> </footer>
`, `,
styles: `
p {
margin: 0 0 0.75rem;
}
footer {
display: flex;
margin-top: 2rem;
gap: 0.5rem;
flex-direction: column-reverse;
}
.preserve-btn {
background: var(--tui-status-positive) !important;
}
`,
}) })
export class PreserveOverwriteDialog { export class PreserveOverwriteDialog {
protected readonly context = injectContext<TuiDialogContext<boolean>>() protected readonly context = injectContext<TuiDialogContext<boolean>>()
} }
export const PRESERVE_OVERWRITE = new PolymorpheusComponent(
PreserveOverwriteDialog,
)

View File

@@ -1,8 +1,9 @@
import { Component, inject } from '@angular/core' import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { TuiDialogContext, TuiTextfield } from '@taiga-ui/core' import { TuiDialogContext, TuiTitle } from '@taiga-ui/core'
import { TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit' import { TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit'
import { TuiHeader } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { StartOSDiskInfoWithId } from '../types' import { StartOSDiskInfoWithId } from '../types'
@@ -11,36 +12,48 @@ interface Data {
} }
@Component({ @Component({
imports: [FormsModule, TuiTextfield, TuiSelect, TuiDataListWrapper, i18nPipe], imports: [
FormsModule,
TuiSelect,
TuiDataListWrapper,
i18nPipe,
TuiHeader,
TuiTitle,
],
template: ` template: `
<p>{{ 'Multiple backups found. Select which one to restore.' | i18n }}</p> <header tuiHeader>
<hgroup tuiTitle>
<h2 [id]="context.id">{{ 'Select Network Backup' | i18n }}</h2>
<p>
{{ 'Multiple backups found. Select which one to restore.' | i18n }}
</p>
</hgroup>
</header>
<tui-textfield [stringify]="stringify"> <tui-textfield [stringify]="stringify">
<label tuiLabel>{{ 'Backups' | i18n }}</label> <label tuiLabel>{{ 'Backups' | i18n }}</label>
<input tuiSelect [(ngModel)]="selectedServer" /> <input tuiSelect [(ngModel)]="selectedServer" />
<tui-data-list-wrapper <tui-data-list-wrapper
new *tuiDropdown
*tuiTextfieldDropdown
[items]="context.data.servers" [items]="context.data.servers"
[itemContent]="serverContent" [itemContent]="serverContent"
/> />
</tui-textfield> </tui-textfield>
<ng-template #serverContent let-server> <ng-template #serverContent let-server>
<div class="server-item"> <span tuiTitle>
<span>{{ server.id }}</span> {{ server.id }}
<!-- @TODO eos-version? --> <!-- @TODO eos-version? -->
<small>{{ server['eos-version'] }}</small> @if (server['eos-version']) {
</div> <span tuiSubtitle>
{{ server['eos-version'] }}
</span>
}
</span>
</ng-template> </ng-template>
`, `,
styles: ` styles: `
.server-item { div {
display: flex; margin-block-end: 1rem;
flex-direction: column;
small {
opacity: 0.7;
}
} }
`, `,
}) })

View File

@@ -1,32 +1,27 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { import { TuiButton, TuiDialogContext, TuiIcon, TuiInput } from '@taiga-ui/core'
TuiButton,
TuiDialogContext,
TuiIcon,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiPassword } from '@taiga-ui/kit' import { TuiPassword } from '@taiga-ui/kit'
import { injectContext } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
@Component({ @Component({
imports: [ imports: [FormsModule, TuiButton, TuiInput, TuiPassword, TuiIcon, i18nPipe],
FormsModule,
TuiButton,
TuiTextfield,
TuiPassword,
TuiIcon,
i18nPipe,
],
template: ` template: `
<p> <header tuiHeader>
{{ 'Enter the password that was used to encrypt this backup.' | i18n }} <hgroup tuiTitle>
</p> <h2 [id]="context.id">{{ 'Unlock Backup' | i18n }}</h2>
<p>
{{
'Enter the password that was used to encrypt this backup.' | i18n
}}
</p>
</hgroup>
</header>
<tui-textfield> <tui-textfield>
<label tuiLabel>{{ 'Password' | i18n }}</label> <label tuiLabel>{{ 'Password' | i18n }}</label>
<input <input
tuiTextfield tuiInput
type="password" type="password"
[(ngModel)]="password" [(ngModel)]="password"
(keyup.enter)="unlock()" (keyup.enter)="unlock()"
@@ -62,3 +57,5 @@ export class UnlockPasswordDialog {
} }
} }
} }
export const UNLOCK_PASSWORD = new PolymorpheusComponent(UnlockPasswordDialog)

View File

@@ -12,24 +12,29 @@ import {
ErrorService, ErrorService,
i18nKey, i18nKey,
i18nPipe, i18nPipe,
LoadingService,
toGuid, toGuid,
} from '@start9labs/shared' } from '@start9labs/shared'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { import {
TuiButton, TuiButton,
TuiIcon, TuiIcon,
TuiLoader, TuiLoader,
TuiTextfield, TuiInput,
TuiNotification,
TuiTitle, TuiTitle,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiDataListWrapper, TuiSelect, TuiTooltip } from '@taiga-ui/kit' import {
TuiDataListWrapper,
TuiNotificationMiddleService,
TuiSelect,
TuiTooltip,
} from '@taiga-ui/kit'
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout' import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { filter, Subscription } from 'rxjs' import { filter, Subscription } from 'rxjs'
import { ApiService } from '../services/api.service' import { ApiService } from '../services/api.service'
import { StateService } from '../services/state.service' import { StateService } from '../services/state.service'
import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog' import { PRESERVE_OVERWRITE } from '../components/preserve-overwrite.dialog'
@Component({ @Component({
template: ` template: `
@@ -42,7 +47,7 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
@if (loading) { @if (loading) {
<tui-loader /> <tui-loader />
} @else if (drives.length === 0) { } @else if (drives.length === 0) {
<p class="no-drives"> <p tuiNotification size="m" appearance="warning">
{{ {{
'No drives found. Please connect a drive and click Refresh.' 'No drives found. Please connect a drive and click Refresh.'
| i18n | i18n
@@ -70,8 +75,7 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper
new *tuiDropdown
*tuiTextfieldDropdown
[items]="drives" [items]="drives"
[itemContent]="driveContent" [itemContent]="driveContent"
/> />
@@ -100,36 +104,27 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper
new *tuiDropdown
*tuiTextfieldDropdown
[items]="drives" [items]="drives"
[itemContent]="driveContent" [itemContent]="driveContent"
/> />
} }
@if (preserveData === true) { @if (preserveData === true) {
<tui-icon <tui-icon icon="@tui.database" class="g-positive" />
icon="@tui.database"
style="color: var(--tui-status-positive); pointer-events: none"
/>
} }
@if (preserveData === false) { @if (preserveData === false) {
<tui-icon <tui-icon icon="@tui.database-zap" class="g-negative" />
icon="@tui.database-zap"
style="color: var(--tui-status-negative); pointer-events: none"
/>
} }
<tui-icon [tuiTooltip]="dataDriveTooltip" /> <tui-icon [tuiTooltip]="dataDriveTooltip" />
</tui-textfield> </tui-textfield>
<ng-template #driveContent let-drive> <ng-template #driveContent let-drive>
<div class="drive-item"> <span tuiTitle>
<span class="drive-name"> {{ driveName(drive) }}
{{ driveName(drive) }} <span tuiSubtitle>
</span>
<small>
{{ formatCapacity(drive.capacity) }} · {{ drive.logicalname }} {{ formatCapacity(drive.capacity) }} · {{ drive.logicalname }}
</small> </span>
</div> </span>
</ng-template> </ng-template>
} }
@@ -152,19 +147,8 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
} }
`, `,
styles: ` styles: `
.no-drives { tui-icon:not([tuiTooltip]) {
text-align: center; pointer-events: none;
color: var(--tui-text-secondary);
padding: 2rem;
}
.drive-item {
display: flex;
flex-direction: column;
small {
opacity: 0.7;
}
} }
`, `,
imports: [ imports: [
@@ -173,7 +157,8 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
TuiButton, TuiButton,
TuiIcon, TuiIcon,
TuiLoader, TuiLoader,
TuiTextfield, TuiInput,
TuiNotification,
TuiSelect, TuiSelect,
TuiDataListWrapper, TuiDataListWrapper,
TuiTooltip, TuiTooltip,
@@ -186,13 +171,13 @@ export default class DrivesPage {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly router = inject(Router) private readonly router = inject(Router)
private readonly dialogs = inject(DialogService) private readonly dialogs = inject(DialogService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly stateService = inject(StateService) private readonly stateService = inject(StateService)
private readonly cdr = inject(ChangeDetectorRef) private readonly cdr = inject(ChangeDetectorRef)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)
protected readonly mobile = inject(TUI_IS_MOBILE) protected readonly mobile = inject(WA_IS_MOBILE)
@HostListener('document:keydown', ['$event']) @HostListener('document:keydown', ['$event'])
onKeydown(event: KeyboardEvent) { onKeydown(event: KeyboardEvent) {
@@ -308,38 +293,27 @@ export default class DrivesPage {
private showPreserveOverwriteDialog() { private showPreserveOverwriteDialog() {
let selectionMade = false let selectionMade = false
this.dialogs this.dialogs.openComponent<boolean>(PRESERVE_OVERWRITE).subscribe({
.openComponent<boolean>( next: preserve => {
new PolymorpheusComponent(PreserveOverwriteDialog), selectionMade = true
{ this.preserveData = preserve
label: 'StartOS Data Detected', this.cdr.markForCheck()
size: 's', },
dismissible: true, complete: () => {
closeable: true, if (!selectionMade) {
}, // Dialog was dismissed without selection - clear the data drive
) this.selectedDataDrive = null
.subscribe({ this.preserveData = null
next: preserve => {
selectionMade = true
this.preserveData = preserve
this.cdr.markForCheck() this.cdr.markForCheck()
}, }
complete: () => { },
if (!selectionMade) { })
// Dialog was dismissed without selection - clear the data drive
this.selectedDataDrive = null
this.preserveData = null
this.cdr.markForCheck()
}
},
})
} }
private showOsDriveWarning() { private showOsDriveWarning() {
this.dialogs this.dialogs
.openConfirm({ .openConfirm({
label: 'Warning', label: 'Warning',
size: 's',
data: { data: {
content: `<ul> content: `<ul>
<li class="g-negative">${this.i18n.transform('Data on the OS drive may be overwritten.')}</li> <li class="g-negative">${this.i18n.transform('Data on the OS drive may be overwritten.')}</li>
@@ -363,7 +337,6 @@ export default class DrivesPage {
this.dialogs this.dialogs
.openConfirm({ .openConfirm({
label: 'Warning', label: 'Warning',
size: 's',
data: { data: {
content: message as i18nKey, content: message as i18nKey,
yes: 'Continue', yes: 'Continue',
@@ -397,10 +370,9 @@ export default class DrivesPage {
this.dialogSub = this.dialogs this.dialogSub = this.dialogs
.openAlert('StartOS has been installed successfully.', { .openAlert('StartOS has been installed successfully.', {
label: 'Installation Complete!', label: 'Installation Complete!',
size: 's',
dismissible: false, dismissible: false,
closeable: true, closable: true,
data: { button: this.i18n.transform('Continue to Setup') }, data: this.i18n.transform('Continue to Setup'),
}) })
.subscribe({ .subscribe({
complete: () => { complete: () => {

View File

@@ -1,9 +1,9 @@
import { Component, inject } from '@angular/core' import { Component, inject } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { TuiAppearance, TuiTitle } from '@taiga-ui/core' import { TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiAvatar } from '@taiga-ui/kit' import { TuiAvatar } from '@taiga-ui/kit'
import { TuiCardLarge, TuiCell, TuiHeader } from '@taiga-ui/layout' import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
import { StateService } from '../services/state.service' import { StateService } from '../services/state.service'
@Component({ @Component({
@@ -14,7 +14,7 @@ import { StateService } from '../services/state.service'
</header> </header>
<button tuiCell="l" (click)="startFresh()"> <button tuiCell="l" (click)="startFresh()">
<tui-avatar appearance="positive" src="@tui.plus" /> <span tuiAvatar="@tui.plus" appearance="positive"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Start Fresh' | i18n }} {{ 'Start Fresh' | i18n }}
<div tuiSubtitle>{{ 'Set up a brand new server' | i18n }}</div> <div tuiSubtitle>{{ 'Set up a brand new server' | i18n }}</div>
@@ -22,7 +22,7 @@ import { StateService } from '../services/state.service'
</button> </button>
<button tuiCell="l" (click)="restore()"> <button tuiCell="l" (click)="restore()">
<tui-avatar appearance="warning" src="@tui.archive-restore" /> <span tuiAvatar="@tui.archive-restore" appearance="warning"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Restore from Backup' | i18n }} {{ 'Restore from Backup' | i18n }}
<div tuiSubtitle> <div tuiSubtitle>
@@ -32,7 +32,7 @@ import { StateService } from '../services/state.service'
</button> </button>
<button tuiCell="l" (click)="transfer()"> <button tuiCell="l" (click)="transfer()">
<tui-avatar appearance="info" src="@tui.hard-drive-download" /> <span tuiAvatar="@tui.hard-drive-download" appearance="info"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Transfer' | i18n }} {{ 'Transfer' | i18n }}
<div tuiSubtitle> <div tuiSubtitle>
@@ -42,15 +42,7 @@ import { StateService } from '../services/state.service'
</button> </button>
</div> </div>
`, `,
imports: [ imports: [TuiCardLarge, TuiHeader, TuiCell, TuiTitle, TuiAvatar, i18nPipe],
TuiAppearance,
TuiCardLarge,
TuiHeader,
TuiCell,
TuiTitle,
TuiAvatar,
i18nPipe,
],
}) })
export default class HomePage { export default class HomePage {
private readonly router = inject(Router) private readonly router = inject(Router)

View File

@@ -6,8 +6,8 @@ import {
Keyboard, Keyboard,
LanguageCode, LanguageCode,
} from '@start9labs/shared' } from '@start9labs/shared'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core' import { TuiButton, TuiTitle } from '@taiga-ui/core'
import { import {
TuiButtonLoading, TuiButtonLoading,
TuiChevron, TuiChevron,
@@ -36,11 +36,7 @@ import { StateService } from '../services/state.service'
<input tuiSelect [(ngModel)]="selected" /> <input tuiSelect [(ngModel)]="selected" />
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper *tuiDropdown [items]="keyboards" />
new
*tuiTextfieldDropdown
[items]="keyboards"
/>
} }
</tui-textfield> </tui-textfield>
@@ -61,7 +57,6 @@ import { StateService } from '../services/state.service'
TuiCardLarge, TuiCardLarge,
TuiButton, TuiButton,
TuiButtonLoading, TuiButtonLoading,
TuiTextfield,
TuiChevron, TuiChevron,
TuiSelect, TuiSelect,
TuiDataListWrapper, TuiDataListWrapper,
@@ -74,7 +69,7 @@ export default class KeyboardPage {
private readonly api = inject(ApiService) 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(WA_IS_MOBILE)
// All keyboards, with language-specific keyboards at the top // All keyboards, with language-specific keyboards at the top
readonly keyboards = getAllKeyboardsSorted( readonly keyboards = getAllKeyboardsSorted(
this.stateService.language as LanguageCode, this.stateService.language as LanguageCode,

View File

@@ -2,9 +2,10 @@ 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, Language, LANGUAGES } from '@start9labs/shared' import { i18nPipe, i18nService, Language, LANGUAGES } from '@start9labs/shared'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core' import { TuiButton, TuiCell, TuiTitle } from '@taiga-ui/core'
import { import {
TuiAvatar,
TuiButtonLoading, TuiButtonLoading,
TuiChevron, TuiChevron,
TuiDataListWrapper, TuiDataListWrapper,
@@ -18,13 +19,15 @@ import { StateService } from '../services/state.service'
template: ` template: `
<section tuiCardLarge="compact"> <section tuiCardLarge="compact">
<header tuiHeader> <header tuiHeader>
<h2 tuiTitle> <hgroup tuiTitle>
<span class="inline-title"> <h2 tuiCell="m">
<img src="assets/img/icon.png" alt="Start9" /> <span tuiAvatar>
<img src="assets/img/icon.png" alt="Start9" />
</span>
{{ 'Welcome to' | i18n }} StartOS {{ 'Welcome to' | i18n }} StartOS
</span> </h2>
<span tuiSubtitle>{{ 'Select your language' | i18n }}</span> <p tuiSubtitle>{{ 'Select your language' | i18n }}</p>
</h2> </hgroup>
</header> </header>
<tui-textfield <tui-textfield
tuiChevron tuiChevron
@@ -48,8 +51,7 @@ import { StateService } from '../services/state.service'
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper
*tuiTextfieldDropdown *tuiDropdown
new
[items]="languages" [items]="languages"
[itemContent]="itemContent" [itemContent]="itemContent"
/> />
@@ -57,11 +59,10 @@ import { StateService } from '../services/state.service'
</tui-textfield> </tui-textfield>
<ng-template #itemContent let-item> <ng-template #itemContent let-item>
@let lang = asLanguage(item); <span tuiTitle>
<div class="language-item"> {{ asLanguage(item).nativeName }}
<span>{{ lang.nativeName }}</span> <span tuiSubtitle>{{ asLanguage(item).name | i18n }}</span>
<small>{{ lang.name | i18n }}</small> </span>
</div>
</ng-template> </ng-template>
<footer> <footer>
@@ -76,22 +77,13 @@ import { StateService } from '../services/state.service'
</footer> </footer>
</section> </section>
`, `,
styles: `
.language-item {
display: flex;
flex-direction: column;
small {
opacity: 0.7;
}
}
`,
imports: [ imports: [
FormsModule, FormsModule,
TuiCardLarge, TuiCardLarge,
TuiButton, TuiButton,
TuiButtonLoading, TuiButtonLoading,
TuiTextfield, TuiAvatar,
TuiCell,
TuiChevron, TuiChevron,
TuiSelect, TuiSelect,
TuiDataListWrapper, TuiDataListWrapper,
@@ -106,7 +98,7 @@ export default class LanguagePage {
private readonly stateService = inject(StateService) private readonly stateService = inject(StateService)
private readonly i18nService = inject(i18nService) private readonly i18nService = inject(i18nService)
protected readonly mobile = inject(TUI_IS_MOBILE) protected readonly mobile = inject(WA_IS_MOBILE)
readonly languages = LANGUAGES readonly languages = LANGUAGES
selected = selected =

View File

@@ -12,10 +12,10 @@ import {
getErrorMessage, getErrorMessage,
i18nPipe, i18nPipe,
InitializingComponent, InitializingComponent,
LoadingService,
} from '@start9labs/shared' } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import { TuiButton } from '@taiga-ui/core' import { TuiButton } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { import {
catchError, catchError,
filter, filter,
@@ -64,7 +64,7 @@ import { StateService } from '../services/state.service'
}) })
export default class LoadingPage { export default class LoadingPage {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly dialog = inject(DialogService) private readonly dialog = inject(DialogService)
private readonly router = inject(Router) private readonly router = inject(Router)

View File

@@ -1,4 +1,3 @@
import { AsyncPipe } from '@angular/common'
import { Component, inject } from '@angular/core' import { Component, inject } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { import {
@@ -8,31 +7,28 @@ import {
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from '@angular/forms' } from '@angular/forms'
import { import { ErrorService, i18nPipe, normalizeHostname } from '@start9labs/shared'
ErrorService,
i18nPipe,
LoadingService,
normalizeHostname,
} from '@start9labs/shared'
import { TuiAutoFocus, TuiMapperPipe, TuiValidator } from '@taiga-ui/cdk' import { TuiAutoFocus, TuiMapperPipe, TuiValidator } from '@taiga-ui/cdk'
import { import {
TuiButton, TuiButton,
TuiError, TuiError,
TuiIcon, TuiIcon,
TuiTextfield,
TuiTitle, TuiTitle,
} from '@taiga-ui/core' TuiInput,
import {
TuiFieldErrorPipe,
TuiPassword,
tuiValidationErrorsProvider, tuiValidationErrorsProvider,
} from '@taiga-ui/kit' } from '@taiga-ui/core'
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout' import { TuiNotificationMiddleService, TuiPassword } from '@taiga-ui/kit'
import { TuiCardLarge, TuiForm, TuiHeader } from '@taiga-ui/layout'
import { StateService } from '../services/state.service' import { StateService } from '../services/state.service'
@Component({ @Component({
template: ` template: `
<section tuiCardLarge="compact"> <form
tuiCardLarge="compact"
tuiForm
[formGroup]="form"
(ngSubmit)="submit()"
>
<header tuiHeader> <header tuiHeader>
<h2 tuiTitle> <h2 tuiTitle>
{{ {{
@@ -43,104 +39,80 @@ import { StateService } from '../services/state.service'
</h2> </h2>
</header> </header>
<form [formGroup]="form" (ngSubmit)="submit()"> @if (isFresh) {
@if (isFresh) { <tui-textfield>
<tui-textfield> <label tuiLabel>{{ 'Server Name' | i18n }}</label>
<label tuiLabel>{{ 'Server Name' | i18n }}</label> <input tuiInput tuiAutoFocus formControlName="name" />
<input tuiTextfield tuiAutoFocus formControlName="name" /> </tui-textfield>
</tui-textfield> <tui-error formControlName="name" />
<tui-error @if (form.controls.name.value?.trim()) {
formControlName="name" <tui-error class="g-secondary" error="{{ derivedHostname }}.local" />
[error]="[] | tuiFieldError | async"
/>
@if (form.controls.name.value?.trim()) {
<p class="hostname-preview">{{ derivedHostname }}.local</p>
}
} }
}
<tui-textfield [style.margin-top.rem]="isFresh ? 1 : 0"> <tui-textfield>
<label tuiLabel> <label tuiLabel>
{{ isFresh ? ('Password' | i18n) : ('New Password' | i18n) }} {{ isFresh ? ('Password' | i18n) : ('New Password' | i18n) }}
</label> </label>
<input <input
tuiTextfield tuiInput
type="password" type="password"
[tuiAutoFocus]="!isFresh" [tuiAutoFocus]="!isFresh"
maxlength="64" maxlength="64"
formControlName="password"
/>
<tui-icon tuiPassword />
</tui-textfield>
<tui-error
formControlName="password" formControlName="password"
[error]="[] | tuiFieldError | async"
/> />
<tui-icon tuiPassword />
</tui-textfield>
<tui-error formControlName="password" />
<tui-textfield [style.margin-top.rem]="1"> <tui-textfield>
<label tuiLabel>{{ 'Confirm Password' | i18n }}</label> <label tuiLabel>{{ 'Confirm Password' | i18n }}</label>
<input <input
tuiTextfield tuiInput
type="password" type="password"
formControlName="confirm"
[tuiValidator]="
form.controls.password.value || '' | tuiMapper: validator
"
/>
<tui-icon tuiPassword />
</tui-textfield>
<tui-error
formControlName="confirm" formControlName="confirm"
[error]="[] | tuiFieldError | async" [tuiValidator]="
form.controls.password.value || '' | tuiMapper: validator
"
/> />
<tui-icon tuiPassword />
</tui-textfield>
<tui-error formControlName="confirm" />
<footer> <footer>
<button
tuiButton
size="m"
[disabled]="
isFresh
? form.invalid
: form.controls.password.value && form.invalid
"
>
{{ 'Finish' | i18n }}
</button>
@if (!isFresh) {
<button <button
tuiButton tuiButton
[disabled]=" size="m"
isFresh appearance="secondary"
? form.invalid type="button"
: form.controls.password.value && form.invalid (click)="skip()"
"
> >
{{ 'Finish' | i18n }} {{ 'Skip' | i18n }}
</button> </button>
@if (!isFresh) { }
<button </footer>
tuiButton </form>
appearance="secondary"
type="button"
(click)="skip()"
>
{{ 'Skip' | i18n }}
</button>
}
</footer>
</form>
</section>
`,
styles: `
.hostname-preview {
color: var(--tui-text-secondary);
font: var(--tui-font-text-s);
margin-top: 0.25rem;
}
footer {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1.5rem;
}
`, `,
imports: [ imports: [
AsyncPipe,
ReactiveFormsModule, ReactiveFormsModule,
TuiCardLarge, TuiCardLarge,
TuiButton, TuiButton,
TuiError, TuiError,
TuiAutoFocus, TuiAutoFocus,
TuiFieldErrorPipe, TuiInput,
TuiTextfield, TuiForm,
TuiPassword, TuiPassword,
TuiValidator, TuiValidator,
TuiIcon, TuiIcon,
@@ -160,7 +132,7 @@ import { StateService } from '../services/state.service'
}) })
export default class PasswordPage { export default class PasswordPage {
private readonly router = inject(Router) private readonly router = inject(Router)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly stateService = inject(StateService) private readonly stateService = inject(StateService)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)

View File

@@ -6,11 +6,11 @@ import {
TuiButton, TuiButton,
TuiDataList, TuiDataList,
TuiDropdown, TuiDropdown,
TuiIcon, TuiLink,
TuiLoader, TuiLoader,
TuiOptGroup,
TuiTitle, TuiTitle,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiChevron } from '@taiga-ui/kit'
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout' import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from '../services/api.service' import { ApiService } from '../services/api.service'
@@ -18,22 +18,25 @@ import { StateService } from '../services/state.service'
import { StartOSDiskInfoFull, StartOSDiskInfoWithId } from '../types' import { StartOSDiskInfoFull, StartOSDiskInfoWithId } from '../types'
import { CIFS, CifsResult } from '../components/cifs.component' import { CIFS, CifsResult } from '../components/cifs.component'
import { SELECT_NETWORK_BACKUP } from '../components/select-network-backup.dialog' import { SELECT_NETWORK_BACKUP } from '../components/select-network-backup.dialog'
import { UnlockPasswordDialog } from '../components/unlock-password.dialog' import { UNLOCK_PASSWORD } from '../components/unlock-password.dialog'
@Component({ @Component({
template: ` template: `
<section tuiCardLarge="compact"> <section tuiCardLarge="compact">
<header tuiHeader> <header tuiHeader>
<h2 tuiTitle> <hgroup tuiTitle>
{{ 'Select Backup' | i18n }} <h2>{{ 'Select Backup' | i18n }}</h2>
<span tuiSubtitle> <p tuiSubtitle>
{{ 'Select the StartOS backup you want to restore' | i18n }} {{ 'Select the StartOS backup you want to restore' | i18n }}
<a class="refresh" (click)="refresh()"> <button
<tui-icon icon="@tui.rotate-cw" /> tuiLink
{{ 'Refresh' | i18n }} appearance="action"
</a> iconEnd="@tui.rotate-cw"
</span> [textContent]="'Refresh' | i18n"
</h2> (click)="refresh()"
></button>
</p>
</hgroup>
</header> </header>
@if (loading) { @if (loading) {
@@ -41,82 +44,50 @@ import { UnlockPasswordDialog } from '../components/unlock-password.dialog'
} @else { } @else {
<button <button
tuiButton tuiButton
iconEnd="@tui.chevron-down" tuiChevron
[tuiDropdown]="dropdown" tuiDropdown
[tuiDropdownLimitWidth]="'fixed'" tuiDropdownLimitWidth="fixed"
[(tuiDropdownOpen)]="open" [(tuiDropdownOpen)]="open"
style="width: 100%"
> >
{{ 'Select Backup' | i18n }} {{ 'Select Backup' | i18n }}
</button> <tui-data-list *tuiDropdown>
<button tuiOption iconStart="@tui.folder-plus" (click)="openCifs()">
<ng-template #dropdown> {{ 'Open Network Backup' | i18n }}
<tui-data-list> </button>
<tui-opt-group> <hr />
<button tuiOption new (click)="openCifs()">
<tui-icon icon="@tui.folder-plus" />
{{ 'Open Network Backup' | i18n }}
</button>
</tui-opt-group>
<tui-opt-group [label]="'Physical Backups' | i18n"> <tui-opt-group [label]="'Physical Backups' | i18n">
@for (server of physicalServers; track server.id) { @for (server of physicalServers; track server.id) {
<button tuiOption new (click)="selectPhysicalBackup(server)"> <button tuiOption (click)="selectPhysicalBackup(server)">
<div class="server-item"> <span tuiTitle>
<span>{{ server.id }}</span> {{ server.id }}
<small> <span tuiSubtitle>
{{ server.drive.vendor }} {{ server.drive.model }} · {{ server.drive.vendor }} {{ server.drive.model }} ·
{{ server.partition.logicalname }} {{ server.partition.logicalname }}
</small> </span>
</div> </span>
</button> </button>
} @empty { } @empty {
<div class="no-items">{{ 'No physical backups' | i18n }}</div> <button tuiOption [disabled]="true">
{{ 'No physical backups' | i18n }}
</button>
} }
</tui-opt-group> </tui-opt-group>
</tui-data-list> </tui-data-list>
</ng-template> </button>
} }
</section> </section>
`, `,
styles: `
.refresh {
display: inline-flex;
align-items: center;
gap: 0.25rem;
cursor: pointer;
color: var(--tui-text-action);
tui-icon {
font-size: 0.875rem;
}
}
.server-item {
display: flex;
flex-direction: column;
small {
opacity: 0.7;
}
}
.no-items {
padding: 0.5rem 0.75rem;
color: var(--tui-text-secondary);
font-style: italic;
}
`,
imports: [ imports: [
TuiButton, TuiButton,
TuiCardLarge, TuiCardLarge,
TuiDataList, TuiDataList,
TuiDropdown, TuiDropdown,
TuiLoader, TuiLoader,
TuiIcon,
TuiOptGroup,
TuiTitle, TuiTitle,
TuiHeader, TuiHeader,
i18nPipe, i18nPipe,
TuiLink,
TuiChevron,
], ],
}) })
export default class RestorePage { export default class RestorePage {
@@ -142,10 +113,7 @@ export default class RestorePage {
openCifs() { openCifs() {
this.open = false this.open = false
this.dialogs this.dialogs
.openComponent<CifsResult>(CIFS, { .openComponent<CifsResult>(CIFS, { label: 'Connect Network Folder' })
label: 'Connect Network Folder',
size: 's',
})
.subscribe(result => { .subscribe(result => {
if (result) { if (result) {
this.handleCifsResult(result) this.handleCifsResult(result)
@@ -167,7 +135,7 @@ export default class RestorePage {
type: 'cifs', type: 'cifs',
...result.cifs, ...result.cifs,
}) })
} else if (result.servers.length > 1) { } else {
this.showSelectNetworkBackupDialog(result.cifs, result.servers) this.showSelectNetworkBackupDialog(result.cifs, result.servers)
} }
} }
@@ -178,8 +146,6 @@ export default class RestorePage {
) { ) {
this.dialogs this.dialogs
.openComponent<StartOSDiskInfoWithId | null>(SELECT_NETWORK_BACKUP, { .openComponent<StartOSDiskInfoWithId | null>(SELECT_NETWORK_BACKUP, {
label: 'Select Network Backup',
size: 's',
data: { servers }, data: { servers },
}) })
.subscribe(server => { .subscribe(server => {
@@ -194,13 +160,7 @@ export default class RestorePage {
target: { type: 'disk'; logicalname: string } | ({ type: 'cifs' } & T.Cifs), target: { type: 'disk'; logicalname: string } | ({ type: 'cifs' } & T.Cifs),
) { ) {
this.dialogs this.dialogs
.openComponent<string | null>( .openComponent<string | null>(UNLOCK_PASSWORD)
new PolymorpheusComponent(UnlockPasswordDialog),
{
label: 'Unlock Backup',
size: 's',
},
)
.subscribe(password => { .subscribe(password => {
if (password) { if (password) {
this.stateService.recoverySource = { this.stateService.recoverySource = {

View File

@@ -12,9 +12,9 @@ import {
ErrorService, ErrorService,
i18nPipe, i18nPipe,
} from '@start9labs/shared' } from '@start9labs/shared'
import { TuiIcon, TuiLoader, TuiTitle } from '@taiga-ui/core' import { TuiIcon, TuiLoader, TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiAvatar } from '@taiga-ui/kit' import { TuiAvatar } from '@taiga-ui/kit'
import { TuiCardLarge, TuiCell, TuiHeader } from '@taiga-ui/layout' import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
import { ApiService } from '../services/api.service' import { ApiService } from '../services/api.service'
import { StateService } from '../services/state.service' import { StateService } from '../services/state.service'
import { DocumentationComponent } from '../components/documentation.component' import { DocumentationComponent } from '../components/documentation.component'
@@ -50,7 +50,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
<!-- Step: Download Address Info (non-kiosk only) --> <!-- Step: Download Address Info (non-kiosk only) -->
@if (!stateService.kiosk) { @if (!stateService.kiosk) {
<button tuiCell="l" [disabled]="downloaded" (click)="download()"> <button tuiCell="l" [disabled]="downloaded" (click)="download()">
<tui-avatar appearance="secondary" src="@tui.download" /> <span tuiAvatar="@tui.download" appearance="secondary"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Download Address Info' | i18n }} {{ 'Download Address Info' | i18n }}
<div tuiSubtitle> <div tuiSubtitle>
@@ -74,7 +74,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
[disabled]="(!stateService.kiosk && !downloaded) || usbRemoved" [disabled]="(!stateService.kiosk && !downloaded) || usbRemoved"
(click)="removeMedia()" (click)="removeMedia()"
> >
<tui-avatar appearance="secondary" src="@tui.usb" /> <span tuiAvatar="@tui.usb" appearance="secondary"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Remove Installation Media' | i18n }} {{ 'Remove Installation Media' | i18n }}
<div tuiSubtitle> <div tuiSubtitle>
@@ -96,7 +96,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
[disabled]="!usbRemoved || rebooted || rebooting" [disabled]="!usbRemoved || rebooted || rebooting"
(click)="reboot()" (click)="reboot()"
> >
<tui-avatar appearance="secondary" src="@tui.rotate-cw" /> <span tuiAvatar="@tui.rotate-cw" appearance="secondary"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Restart Server' | i18n }} {{ 'Restart Server' | i18n }}
<div tuiSubtitle> <div tuiSubtitle>
@@ -125,7 +125,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
[disabled]="!canOpenAddress" [disabled]="!canOpenAddress"
(click)="openLocalAddress()" (click)="openLocalAddress()"
> >
<tui-avatar appearance="secondary" src="@tui.external-link" /> <span tuiAvatar="@tui.external-link" appearance="secondary"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Open Local Address' | i18n }} {{ 'Open Local Address' | i18n }}
<div tuiSubtitle>{{ lanAddress }}</div> <div tuiSubtitle>{{ lanAddress }}</div>
@@ -143,7 +143,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
[disabled]="result.needsRestart && !rebooted" [disabled]="result.needsRestart && !rebooted"
(click)="exitKiosk()" (click)="exitKiosk()"
> >
<tui-avatar appearance="secondary" src="@tui.log-in" /> <span tuiAvatar="@tui.log-in" appearance="secondary"></span>
<div tuiTitle> <div tuiTitle>
{{ 'Continue to Login' | i18n }} {{ 'Continue to Login' | i18n }}
<div tuiSubtitle> <div tuiSubtitle>
@@ -233,9 +233,8 @@ export default class SuccessPage implements AfterViewInit {
removeMedia() { removeMedia() {
this.dialogs this.dialogs
.openComponent<boolean>(new PolymorpheusComponent(RemoveMediaDialog), { .openComponent<boolean>(new PolymorpheusComponent(RemoveMediaDialog), {
size: 's',
dismissible: false, dismissible: false,
closeable: false, closable: false,
}) })
.subscribe(() => { .subscribe(() => {
this.usbRemoved = true this.usbRemoved = true

View File

@@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core' import { Component, inject, OnInit } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { import {
DialogService, DialogService,
@@ -11,10 +11,11 @@ import {
TuiButton, TuiButton,
TuiDataList, TuiDataList,
TuiDropdown, TuiDropdown,
TuiIcon, TuiLink,
TuiLoader, TuiLoader,
TuiTitle, TuiTitle,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiChevron } from '@taiga-ui/kit'
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout' import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
import { filter } from 'rxjs' import { filter } from 'rxjs'
import { ApiService } from '../services/api.service' import { ApiService } from '../services/api.service'
@@ -24,18 +25,21 @@ import { StateService } from '../services/state.service'
template: ` template: `
<section tuiCardLarge="compact"> <section tuiCardLarge="compact">
<header tuiHeader> <header tuiHeader>
<h2 tuiTitle> <hgroup tuiTitle>
{{ 'Transfer Data' | i18n }} <h2>{{ 'Transfer Data' | i18n }}</h2>
<span tuiSubtitle> <p tuiSubtitle>
{{ {{
'Select the drive containing your existing StartOS data' | i18n 'Select the drive containing your existing StartOS data' | i18n
}} }}
<a class="refresh" (click)="refresh()"> <button
<tui-icon icon="@tui.rotate-cw" /> tuiLink
{{ 'Refresh' | i18n }} appearance="action"
</a> iconEnd="@tui.rotate-cw"
</span> [textContent]="'Refresh' | i18n"
</h2> (click)="refresh()"
></button>
</p>
</hgroup>
</header> </header>
@if (loading) { @if (loading) {
@@ -43,75 +47,43 @@ import { StateService } from '../services/state.service'
} @else { } @else {
<button <button
tuiButton tuiButton
iconEnd="@tui.chevron-down" tuiChevron
[tuiDropdown]="dropdown" tuiDropdown
[tuiDropdownLimitWidth]="'fixed'" tuiDropdownLimitWidth="fixed"
[(tuiDropdownOpen)]="open" [(tuiDropdownOpen)]="open"
style="width: 100%"
> >
{{ 'Select Drive' | i18n }} {{ 'Select Drive' | i18n }}
</button> <tui-data-list
*tuiDropdown
<ng-template #dropdown> [emptyContent]="'No StartOS data drives found' | i18n"
<tui-data-list> >
@for (drive of drives; track drive.logicalname) { @for (drive of drives; track drive.logicalname) {
<button tuiOption new (click)="select(drive)"> <button tuiOption (click)="select(drive)">
<div class="drive-item"> <span tuiTitle>
<span>{{ drive.vendor }} {{ drive.model }}</span> {{ drive.vendor }} {{ drive.model }}
<small>{{ drive.logicalname }}</small> <span tuiSubtitle>{{ drive.logicalname }}</span>
</div> </span>
</button> </button>
} @empty {
<div class="no-items">
{{ 'No StartOS data drives found' | i18n }}
</div>
} }
</tui-data-list> </tui-data-list>
</ng-template> </button>
} }
</section> </section>
`, `,
styles: `
.refresh {
display: inline-flex;
align-items: center;
gap: 0.25rem;
cursor: pointer;
color: var(--tui-text-action);
tui-icon {
font-size: 0.875rem;
}
}
.drive-item {
display: flex;
flex-direction: column;
small {
opacity: 0.7;
}
}
.no-items {
padding: 0.5rem 0.75rem;
color: var(--tui-text-secondary);
font-style: italic;
}
`,
imports: [ imports: [
TuiButton, TuiButton,
TuiCardLarge, TuiCardLarge,
TuiDataList, TuiDataList,
TuiDropdown, TuiDropdown,
TuiIcon, TuiLink,
TuiChevron,
TuiLoader, TuiLoader,
TuiTitle, TuiTitle,
TuiHeader, TuiHeader,
i18nPipe, i18nPipe,
], ],
}) })
export default class TransferPage { export default class TransferPage implements OnInit {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly router = inject(Router) private readonly router = inject(Router)
private readonly dialogs = inject(DialogService) private readonly dialogs = inject(DialogService)
@@ -137,7 +109,6 @@ export default class TransferPage {
this.dialogs this.dialogs
.openConfirm({ .openConfirm({
label: 'Warning', label: 'Warning',
size: 's',
data: { data: {
content: content:
'After transferring data from this drive, do not attempt to boot into it again as a Start9 Server. This may result in services malfunctioning, data corruption, or loss of funds.', 'After transferring data from this drive, do not attempt to boot into it again as a Start9 Server. This may result in services malfunctioning, data corruption, or loss of funds.',

View File

@@ -12,6 +12,10 @@ tui-root {
height: 100%; height: 100%;
} }
ul {
padding-inline-start: 1rem;
}
router-outlet + * { router-outlet + * {
height: 100%; height: 100%;
max-width: min(35rem, 100vw); max-width: min(35rem, 100vw);
@@ -30,41 +34,11 @@ router-outlet + * {
} }
} }
.inline-title {
display: inline-flex;
align-items: center;
gap: 0.5rem;
:first-child {
width: 2rem;
height: 2rem;
}
}
button:disabled { button:disabled {
opacity: var(--tui-disabled-opacity); opacity: var(--tui-disabled-opacity);
pointer-events: none; pointer-events: none;
} }
header {
position: relative;
display: flex;
flex-direction: column;
text-align: center;
font: var(--tui-font-heading-4);
p {
font: var(--tui-font-text-m);
color: var(--tui-text-secondary);
}
}
h2 {
margin: 0;
font: var(--tui-font-heading-6);
}
.g-positive { .g-positive {
color: var(--tui-status-positive); color: var(--tui-status-positive);
} }
@@ -77,14 +51,27 @@ h2 {
color: var(--tui-status-negative); color: var(--tui-status-negative);
} }
.g-secondary {
color: var(--tui-text-secondary) !important;
}
.g-info { .g-info {
color: var(--tui-status-info); color: var(--tui-status-info);
} }
[tuiCardLarge] footer button { [tuiCardLarge] footer {
width: 100%; display: flex;
button {
flex: 1;
}
} }
[tuiCell]:not(:last-of-type) { [tuiCell]:not([tuiOption]):not(:last-of-type) {
box-shadow: 0 calc(0.5rem + 1px) 0 -0.5rem var(--tui-border-normal); box-shadow: 0 calc(0.5rem + 1px) 0 -0.5rem var(--tui-border-normal);
} }
// TODO: Remove in Taiga v5.0
[tuiButton] {
min-block-size: var(--t-size);
}

View File

@@ -11,7 +11,12 @@ import { getErrorMessage } from '../services/error.service'
@Component({ @Component({
template: ` template: `
@if (error()) { @if (error()) {
<tui-notification appearance="negative" safeLinks [innerHTML]="error()" /> <div
tuiNotification
appearance="negative"
safeLinks
[innerHTML]="error()"
></div>
} }
@if (content(); as result) { @if (content(); as result) {

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { TuiAutoFocus } from '@taiga-ui/cdk' import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiTextfield } from '@taiga-ui/core' import { TuiButton, TuiDialogContext, TuiInput } from '@taiga-ui/core'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { i18nPipe } from '../i18n/i18n.pipe' import { i18nPipe } from '../i18n/i18n.pipe'
import { i18nKey } from '../i18n/i18n.providers' import { i18nKey } from '../i18n/i18n.providers'
@@ -23,7 +23,7 @@ import { i18nKey } from '../i18n/i18n.providers'
</label> </label>
} }
<input <input
tuiTextfield tuiInput
tuiAutoFocus tuiAutoFocus
[ngModelOptions]="{ standalone: true }" [ngModelOptions]="{ standalone: true }"
[(ngModel)]="value" [(ngModel)]="value"
@@ -81,7 +81,7 @@ import { i18nKey } from '../i18n/i18n.providers'
-webkit-text-security: disc; -webkit-text-security: disc;
} }
`, `,
imports: [FormsModule, TuiButton, TuiTextfield, TuiAutoFocus, i18nPipe], imports: [FormsModule, TuiButton, TuiInput, TuiAutoFocus, i18nPipe],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class PromptModal { export class PromptModal {

View File

@@ -1,7 +1,6 @@
import { DatePipe } from '@angular/common' import { DatePipe } from '@angular/common'
import { Component, input } from '@angular/core' import { Component, input } from '@angular/core'
import { TuiIcon, TuiTitle } from '@taiga-ui/core' import { TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { StartOSDiskInfo } from '../types/api' import { StartOSDiskInfo } from '../types/api'
@Component({ @Component({

View File

@@ -5,6 +5,7 @@ import {
InjectionToken, InjectionToken,
input, input,
} from '@angular/core' } from '@angular/core'
import { tuiSetSignal } from '@taiga-ui/cdk'
import { TuiHintDirective } from '@taiga-ui/core' import { TuiHintDirective } from '@taiga-ui/core'
import { i18nPipe } from '../i18n/i18n.pipe' import { i18nPipe } from '../i18n/i18n.pipe'
@@ -33,7 +34,8 @@ export class DocsLinkDirective {
}) })
constructor() { constructor() {
inject(TuiHintDirective).content.set( tuiSetSignal(
inject(TuiHintDirective).content,
inject(i18nPipe).transform('Documentation'), inject(i18nPipe).transform('Documentation'),
) )
} }

View File

@@ -32,7 +32,6 @@ export * from './services/download-html.service'
export * from './services/exver.service' export * from './services/exver.service'
export * from './services/error.service' export * from './services/error.service'
export * from './services/http.service' export * from './services/http.service'
export * from './services/loading.service'
export * from './services/setup-logs.service' export * from './services/setup-logs.service'
export * from './types/api' export * from './types/api'

View File

@@ -1,6 +1,6 @@
import { inject, Injectable } from '@angular/core' import { inject, Injectable } from '@angular/core'
import { Clipboard } from '@angular/cdk/clipboard' import { Clipboard } from '@angular/cdk/clipboard'
import { TuiAlertService } from '@taiga-ui/core' import { TuiNotificationService } from '@taiga-ui/core'
import { i18nPipe } from '../i18n/i18n.pipe' import { i18nPipe } from '../i18n/i18n.pipe'
@@ -8,7 +8,7 @@ import { i18nPipe } from '../i18n/i18n.pipe'
export class CopyService { export class CopyService {
private readonly clipboard = inject(Clipboard) private readonly clipboard = inject(Clipboard)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)
private readonly alerts = inject(TuiAlertService) private readonly alerts = inject(TuiNotificationService)
async copy(text: string) { async copy(text: string) {
const success = this.clipboard.copy(text) const success = this.clipboard.copy(text)

View File

@@ -1,12 +1,12 @@
import { ErrorHandler, inject, Injectable } from '@angular/core' import { ErrorHandler, inject, Injectable } from '@angular/core'
import { TuiAlertService } from '@taiga-ui/core' import { TuiNotificationService } from '@taiga-ui/core'
import { HttpError } from '../classes/http-error' import { HttpError } from '../classes/http-error'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ErrorService extends ErrorHandler { export class ErrorService extends ErrorHandler {
private readonly alerts = inject(TuiAlertService) private readonly alerts = inject(TuiNotificationService)
override handleError(error: HttpError | string, link?: string) { override handleError(error: HttpError | string, link?: string) {
console.error(error) console.error(error)

View File

@@ -1,44 +0,0 @@
import { ChangeDetectionStrategy, Component, Injectable } from '@angular/core'
import { TuiPopoverService } from '@taiga-ui/cdk'
import { TUI_DIALOGS, TuiLoader } from '@taiga-ui/core'
import { injectContext } from '@taiga-ui/polymorpheus'
import { i18nPipe } from '../i18n/i18n.pipe'
import { i18nKey } from '../i18n/i18n.providers'
@Component({
template: '<tui-loader [textContent]="content | i18n" />',
styles: `
:host {
display: flex;
align-items: center;
max-width: 80%;
margin: auto;
padding: 1.5rem;
background: var(--tui-background-elevation-1);
border-radius: var(--tui-radius-m);
box-shadow: var(--tui-shadow-popup);
--tui-background-accent-1: var(--tui-status-warning);
}
tui-loader {
flex-shrink: 0;
min-width: 2rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiLoader, i18nPipe],
})
class LoadingComponent {
readonly content = injectContext<{ content: i18nKey }>().content
}
@Injectable({
providedIn: `root`,
useFactory: () => new LoadingService(TUI_DIALOGS, LoadingComponent),
})
export class LoadingService extends TuiPopoverService<unknown> {
override open<G = void>(textContent: i18nKey | '' = '') {
return super.open<G>(textContent)
}
}

View File

@@ -95,9 +95,6 @@ $wide-modal: 900px;
--tw-color-zinc-800: 39 39 42; --tw-color-zinc-800: 39 39 42;
--tw-color-zinc-900: 24 24 27; --tw-color-zinc-900: 24 24 27;
--tw-color-zinc-950: 9 9 11; --tw-color-zinc-950: 9 9 11;
--tui-font-text: 'Proxima Nova', system-ui;
--tui-font-heading: 'Proxima Nova', system-ui;
} }
body { body {

View File

@@ -1,4 +1,4 @@
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
[tuiTheme='dark'] { [tuiTheme='dark'] {
--tui-background-base: rgba(23, 23, 23, 1); --tui-background-base: rgba(23, 23, 23, 1);
@@ -135,13 +135,13 @@ tui-dropdown[data-appearance='start-os'][data-appearance='start-os'] {
inset 0 0 1rem rgba(0, 0, 0, 0.25), inset 0 0 1rem rgba(0, 0, 0, 0.25),
var(--tui-shadow-medium); var(--tui-shadow-medium);
[tuiOption] { input {
justify-content: flex-start; // Checkmark
gap: 0.5rem; --tui-text-action: var(--tui-text-primary);
} }
} }
// @TODO Alex: Move to Taiga UI // TODO: Remove in Taiga v5.0
a[tuiIconButton]:not([href]) { a[tuiIconButton]:not([href]) {
pointer-events: none; pointer-events: none;
opacity: var(--tui-disabled-opacity); opacity: var(--tui-disabled-opacity);
@@ -160,7 +160,7 @@ tui-textfield [tuiTooltip] {
padding-block: 0.75rem; padding-block: 0.75rem;
[tuiTitle] { [tuiTitle] {
font: var(--tui-font-text-l); font: var(--tui-typography-body-l);
} }
} }
@@ -169,7 +169,16 @@ tui-textfield [tuiTooltip] {
} }
} }
// TODO: Remove after migrating to v5 :root {
[tuiOption] { --tui-typography-family-text: 'Proxima Nova', system-ui;
word-break: break-word; --tui-typography-family-display: 'Proxima Nova', system-ui;
}
tui-notification-middle {
--tui-background-accent-1: var(--tui-status-warning);
}
// TODO: Remove in Taiga v5.0
[tuiButton] {
min-block-size: var(--t-size);
} }

View File

@@ -1,13 +1,14 @@
import { tuiDropdownOptionsProvider } from '@taiga-ui/core' import {
import { provideEventPlugins } from '@taiga-ui/event-plugins' tuiDropdownOptionsProvider,
import { provideAnimations } from '@angular/platform-browser/animations' tuiDialogOptionsProvider,
provideTaiga,
} from '@taiga-ui/core'
import { import {
ApplicationConfig, ApplicationConfig,
provideBrowserGlobalErrorListeners, provideBrowserGlobalErrorListeners,
provideZonelessChangeDetection, provideZonelessChangeDetection,
} from '@angular/core' } from '@angular/core'
import { provideRouter, withRouterConfig } from '@angular/router' import { provideRouter, withRouterConfig } from '@angular/router'
import { tuiDialogOptionsProvider } from '@taiga-ui/experimental'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { import {
PATCH_CACHE, PATCH_CACHE,
@@ -31,11 +32,10 @@ const {
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideAnimations(),
provideBrowserGlobalErrorListeners(), provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(), provideZonelessChangeDetection(),
provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })), provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })),
provideEventPlugins(), provideTaiga({ mode: 'dark' }),
tuiDropdownOptionsProvider({ appearance: 'start-9' }), tuiDropdownOptionsProvider({ appearance: 'start-9' }),
tuiDialogOptionsProvider({ appearance: 'start-9 taiga' }), tuiDialogOptionsProvider({ appearance: 'start-9 taiga' }),
{ {

View File

@@ -29,7 +29,7 @@ import { SidebarService } from 'src/app/services/sidebar.service'
} }
h1 { h1 {
font: var(--tui-font-heading-6); font: var(--tui-typography-heading-h6);
margin-inline-end: auto; margin-inline-end: auto;
} }

View File

@@ -1,8 +1,11 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { Router, RouterLink, RouterLinkActive } from '@angular/router' import { Router, RouterLink, RouterLinkActive } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TuiButton } from '@taiga-ui/core' import { TuiButton } from '@taiga-ui/core'
import { TuiBadgeNotification } from '@taiga-ui/kit' import {
TuiBadgeNotification,
TuiNotificationMiddleService,
} from '@taiga-ui/kit'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
import { AuthService } from 'src/app/services/auth.service' import { AuthService } from 'src/app/services/auth.service'
import { SidebarService } from 'src/app/services/sidebar.service' import { SidebarService } from 'src/app/services/sidebar.service'
@@ -110,7 +113,7 @@ export class Nav {
private readonly router = inject(Router) private readonly router = inject(Router)
protected readonly sidebars = inject(SidebarService) protected readonly sidebars = inject(SidebarService)
protected readonly api = inject(ApiService) protected readonly api = inject(ApiService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
protected readonly update = inject(UpdateService) protected readonly update = inject(UpdateService)
@@ -133,7 +136,7 @@ export class Nav {
] as const ] as const
protected async logout() { protected async logout() {
const loader = this.loader.open().subscribe() const loader = this.loader.open('').subscribe()
try { try {
await this.api.logout() await this.api.logout()
this.service.authenticated.set(false) this.service.authenticated.set(false)

View File

@@ -1,32 +1,26 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { import {
NonNullableFormBuilder, NonNullableFormBuilder,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from '@angular/forms' } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { utils } from '@start9labs/start-sdk' import { utils } from '@start9labs/start-sdk'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { import {
TUI_IS_MOBILE, TuiAnimated,
TuiAutoFocus, TuiAutoFocus,
tuiMarkControlAsTouchedAndValidate, tuiMarkControlAsTouchedAndValidate,
} from '@taiga-ui/cdk' } from '@taiga-ui/cdk'
import { import { TuiButton, TuiDialogContext, TuiError, TuiInput } from '@taiga-ui/core'
TuiButton,
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { import {
TuiChevron, TuiChevron,
TuiDataListWrapper, TuiDataListWrapper,
TuiElasticContainer, TuiNotificationMiddleService,
TuiFieldErrorPipe,
TuiSelect, TuiSelect,
} from '@taiga-ui/kit' } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout' import { TuiForm, TuiElasticContainer } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
@@ -44,9 +38,9 @@ import {
<form tuiForm [formGroup]="form"> <form tuiForm [formGroup]="form">
<tui-textfield> <tui-textfield>
<label tuiLabel>Name</label> <label tuiLabel>Name</label>
<input tuiTextfield tuiAutoFocus formControlName="name" /> <input tuiInput tuiAutoFocus formControlName="name" />
</tui-textfield> </tui-textfield>
<tui-error formControlName="name" [error]="[] | tuiFieldError | async" /> <tui-error formControlName="name" />
@if (!context.data.device) { @if (!context.data.device) {
<tui-textfield tuiChevron [stringify]="stringify"> <tui-textfield tuiChevron [stringify]="stringify">
@@ -63,31 +57,24 @@ import {
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper
*tuiTextfieldDropdown *tuiDropdown
new
[items]="context.data.subnets()" [items]="context.data.subnets()"
(itemClick)="onSubnet($event)" (itemClick)="onSubnet($event)"
/> />
} }
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="subnet" />
formControlName="subnet"
[error]="[] | tuiFieldError | async"
/>
<tui-elastic-container> <tui-elastic-container>
@if (form.controls.subnet.value?.range) { @if (form.controls.subnet.value?.range) {
<tui-textfield> <tui-textfield tuiAnimated>
<label tuiLabel>LAN IP</label> <label tuiLabel>LAN IP</label>
<input tuiTextfield tuiAutoFocus formControlName="ip" /> <input tuiInput tuiAutoFocus formControlName="ip" />
</tui-textfield> </tui-textfield>
} }
</tui-elastic-container> </tui-elastic-container>
@if (form.controls.subnet.value?.range) { @if (form.controls.subnet.value?.range) {
<tui-error <tui-error formControlName="ip" />
formControlName="ip"
[error]="[] | tuiFieldError | async"
/>
} }
} }
<footer> <footer>
@@ -97,27 +84,26 @@ import {
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
AsyncPipe,
ReactiveFormsModule, ReactiveFormsModule,
TuiAutoFocus, TuiAutoFocus,
TuiButton, TuiButton,
TuiDataListWrapper, TuiDataListWrapper,
TuiError, TuiError,
TuiFieldErrorPipe,
TuiForm, TuiForm,
TuiSelect, TuiSelect,
TuiTextfield, TuiInput,
TuiAnimated,
TuiChevron, TuiChevron,
TuiElasticContainer, TuiElasticContainer,
], ],
}) })
export class DevicesAdd { export class DevicesAdd {
private readonly loading = inject(LoadingService) private readonly loading = inject(TuiNotificationMiddleService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiResponsiveDialogService)
protected readonly mobile = inject(TUI_IS_MOBILE) protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly context = protected readonly context =
injectContext<TuiDialogContext<void, DeviceData>>() injectContext<TuiDialogContext<void, DeviceData>>()
@@ -160,7 +146,7 @@ export class DevicesAdd {
return return
} }
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
const { ip, name, subnet } = this.form.getRawValue() const { ip, name, subnet } = this.form.getRawValue()
const data = { ip, name, subnet: subnet?.range || '' } const data = { ip, name, subnet: subnet?.range || '' }

View File

@@ -1,11 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { import { TuiButton, TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
TuiButton,
TuiDialogContext,
TuiIcon,
TuiTextfield,
TuiTitle,
} from '@taiga-ui/core'
import { TuiCopy, TuiSegmented, TuiTextarea } from '@taiga-ui/kit' import { TuiCopy, TuiSegmented, TuiTextarea } from '@taiga-ui/kit'
import { TuiHeader } from '@taiga-ui/layout' import { TuiHeader } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
@@ -60,7 +54,6 @@ import { QrCodeComponent } from 'ng-qrcode'
TuiIcon, TuiIcon,
TuiTitle, TuiTitle,
TuiSegmented, TuiSegmented,
TuiTextfield,
TuiTextarea, TuiTextarea,
TuiCopy, TuiCopy,
], ],

View File

@@ -6,15 +6,10 @@ import {
Signal, Signal,
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
TuiButton, import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
TuiDataList, import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs' import { filter, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
@@ -49,16 +44,15 @@ import { MappedDevice, MappedSubnet } from './utils'
tuiIconButton tuiIconButton
size="xs" size="xs"
tuiDropdown tuiDropdown
tuiDropdownOpen tuiDropdownAuto
appearance="flat-grayscale" appearance="flat-grayscale"
iconStart="@tui.ellipsis-vertical" iconStart="@tui.ellipsis-vertical"
> >
Actions Actions
<tui-data-list *tuiTextfieldDropdown size="s"> <tui-data-list *tuiDropdown size="s">
<button <button
tuiOption tuiOption
iconStart="@tui.pencil" iconStart="@tui.pencil"
new
(click)="onEdit(device)" (click)="onEdit(device)"
> >
Rename Rename
@@ -66,7 +60,6 @@ import { MappedDevice, MappedSubnet } from './utils'
<button <button
tuiOption tuiOption
iconStart="@tui.settings" iconStart="@tui.settings"
new
(click)="onConfig(device)" (click)="onConfig(device)"
> >
View Config View Config
@@ -74,7 +67,6 @@ import { MappedDevice, MappedSubnet } from './utils'
<button <button
tuiOption tuiOption
iconStart="@tui.trash" iconStart="@tui.trash"
new
(click)="onDelete(device)" (click)="onDelete(device)"
> >
Delete Delete
@@ -90,12 +82,12 @@ import { MappedDevice, MappedSubnet } from './utils'
</table> </table>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield], imports: [TuiButton, TuiDropdown, TuiDataList],
}) })
export default class Devices { export default class Devices {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService) private readonly loading = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
protected readonly subnets: Signal<readonly MappedSubnet[]> = toSignal( protected readonly subnets: Signal<readonly MappedSubnet[]> = toSignal(
@@ -145,7 +137,7 @@ export default class Devices {
} }
async onConfig({ subnet, ip }: MappedDevice) { async onConfig({ subnet, ip }: MappedDevice) {
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
try { try {
const data = await this.api.showDeviceConfig({ subnet: subnet.range, ip }) const data = await this.api.showDeviceConfig({ subnet: subnet.range, ip })
@@ -163,7 +155,7 @@ export default class Devices {
.open(TUI_CONFIRM, { label: 'Are you sure?' }) .open(TUI_CONFIRM, { label: 'Are you sure?' })
.pipe(filter(Boolean)) .pipe(filter(Boolean))
.subscribe(async () => { .subscribe(async () => {
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
try { try {
await this.api.deleteDevice({ subnet: subnet.range, ip }) await this.api.deleteDevice({ subnet: subnet.range, ip })
} catch (e: any) { } catch (e: any) {

View File

@@ -1,13 +1,12 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { import {
NonNullableFormBuilder, NonNullableFormBuilder,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from '@angular/forms' } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { import {
TUI_IS_MOBILE,
tuiMarkControlAsTouchedAndValidate, tuiMarkControlAsTouchedAndValidate,
TuiValueChanges, TuiValueChanges,
} from '@taiga-ui/cdk' } from '@taiga-ui/cdk'
@@ -16,18 +15,16 @@ import {
TuiDialogContext, TuiDialogContext,
TuiError, TuiError,
TuiNumberFormat, TuiNumberFormat,
TuiTextfield, TuiCheckbox,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { import {
TuiCheckbox,
TuiChevron, TuiChevron,
TuiDataListWrapper, TuiDataListWrapper,
TuiFieldErrorPipe,
TuiInputNumber, TuiInputNumber,
TuiNotificationMiddleService,
TuiSelect, TuiSelect,
TuiElasticContainer,
} from '@taiga-ui/kit' } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout' import { TuiForm, TuiElasticContainer } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
@@ -49,17 +46,10 @@ import { MappedDevice, PortForwardsData } from './utils'
<input tuiSelect formControlName="externalip" /> <input tuiSelect formControlName="externalip" />
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper *tuiDropdown [items]="context.data.ips()" />
*tuiTextfieldDropdown
new
[items]="context.data.ips()"
/>
} }
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="externalip" />
formControlName="externalip"
[error]="[] | tuiFieldError | async"
/>
<tui-textfield> <tui-textfield>
<label tuiLabel>External Port</label> <label tuiLabel>External Port</label>
<input <input
@@ -71,10 +61,7 @@ import { MappedDevice, PortForwardsData } from './utils'
(tuiValueChanges)="checkShow80()" (tuiValueChanges)="checkShow80()"
/> />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="externalport" />
formControlName="externalport"
[error]="[] | tuiFieldError | async"
/>
<tui-textfield tuiChevron [stringify]="stringify"> <tui-textfield tuiChevron [stringify]="stringify">
<label tuiLabel>Device</label> <label tuiLabel>Device</label>
@if (mobile) { @if (mobile) {
@@ -89,16 +76,12 @@ import { MappedDevice, PortForwardsData } from './utils'
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list-wrapper <tui-data-list-wrapper
*tuiTextfieldDropdown *tuiDropdown
new
[items]="context.data.devices()" [items]="context.data.devices()"
/> />
} }
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="device" />
formControlName="device"
[error]="[] | tuiFieldError | async"
/>
<tui-textfield> <tui-textfield>
<label tuiLabel>Internal Port</label> <label tuiLabel>Internal Port</label>
<input <input
@@ -110,10 +93,7 @@ import { MappedDevice, PortForwardsData } from './utils'
(tuiValueChanges)="checkShow80()" (tuiValueChanges)="checkShow80()"
/> />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="internalport" />
formControlName="internalport"
[error]="[] | tuiFieldError | async"
/>
<tui-elastic-container> <tui-elastic-container>
@if (show80) { @if (show80) {
<label tuiLabel> <label tuiLabel>
@@ -132,7 +112,6 @@ import { MappedDevice, PortForwardsData } from './utils'
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
AsyncPipe,
ReactiveFormsModule, ReactiveFormsModule,
TuiButton, TuiButton,
TuiChevron, TuiChevron,
@@ -140,8 +119,6 @@ import { MappedDevice, PortForwardsData } from './utils'
TuiError, TuiError,
TuiInputNumber, TuiInputNumber,
TuiNumberFormat, TuiNumberFormat,
TuiFieldErrorPipe,
TuiTextfield,
TuiSelect, TuiSelect,
TuiForm, TuiForm,
TuiCheckbox, TuiCheckbox,
@@ -151,12 +128,12 @@ import { MappedDevice, PortForwardsData } from './utils'
}) })
export class PortForwardsAdd { export class PortForwardsAdd {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService) private readonly loading = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
show80 = false show80 = false
protected readonly mobile = inject(TUI_IS_MOBILE) protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly context = protected readonly context =
injectContext<TuiDialogContext<void, PortForwardsData>>() injectContext<TuiDialogContext<void, PortForwardsData>>()
@@ -183,7 +160,7 @@ export class PortForwardsAdd {
return return
} }
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
const { externalip, externalport, device, internalport, also80 } = const { externalip, externalport, device, internalport, also80 } =
this.form.getRawValue() this.form.getRawValue()

View File

@@ -7,11 +7,11 @@ import {
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { ReactiveFormsModule } from '@angular/forms' import { ReactiveFormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { utils } from '@start9labs/start-sdk' import { utils } from '@start9labs/start-sdk'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiButton } from '@taiga-ui/core' import { TuiButton } from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental' import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs' import { filter, map } from 'rxjs'
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add' import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
@@ -65,9 +65,9 @@ import { MappedDevice, MappedForward } from './utils'
imports: [ReactiveFormsModule, TuiButton], imports: [ReactiveFormsModule, TuiButton],
}) })
export default class PortForwards { export default class PortForwards {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService) private readonly loading = inject(TuiNotificationMiddleService)
private readonly patch = inject<PatchDB<TunnelData>>(PatchDB) private readonly patch = inject<PatchDB<TunnelData>>(PatchDB)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
@@ -127,7 +127,7 @@ export default class PortForwards {
.open(TUI_CONFIRM, { label: 'Are you sure?' }) .open(TUI_CONFIRM, { label: 'Are you sure?' })
.pipe(filter(Boolean)) .pipe(filter(Boolean))
.subscribe(async () => { .subscribe(async () => {
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
const source = `${externalip}:${externalport}` const source = `${externalip}:${externalport}`
try { try {

View File

@@ -1,4 +1,3 @@
import { AsyncPipe } from '@angular/common'
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@@ -15,17 +14,14 @@ import {
import { ErrorService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TuiAutoFocus, TuiValidator } from '@taiga-ui/cdk' import { TuiAutoFocus, TuiValidator } from '@taiga-ui/cdk'
import { import {
TuiAlertService,
TuiButton, TuiButton,
TuiDialogContext, TuiDialogContext,
TuiError, TuiError,
TuiTextfield, TuiNotificationService,
} from '@taiga-ui/core' TuiInput,
import {
TuiButtonLoading,
TuiFieldErrorPipe,
tuiValidationErrorsProvider, tuiValidationErrorsProvider,
} from '@taiga-ui/kit' } from '@taiga-ui/core'
import { TuiButtonLoading } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout' import { TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { map } from 'rxjs' import { map } from 'rxjs'
@@ -36,24 +32,18 @@ import { ApiService } from 'src/app/services/api/api.service'
<form tuiForm [formGroup]="form"> <form tuiForm [formGroup]="form">
<tui-textfield> <tui-textfield>
<label tuiLabel>New password</label> <label tuiLabel>New password</label>
<input tuiTextfield tuiAutoFocus formControlName="password" /> <input tuiInput tuiAutoFocus formControlName="password" />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="password" />
formControlName="password"
[error]="[] | tuiFieldError | async"
/>
<tui-textfield> <tui-textfield>
<label tuiLabel>Confirm new password</label> <label tuiLabel>Confirm new password</label>
<input <input
tuiTextfield tuiInput
formControlName="confirm" formControlName="confirm"
[tuiValidator]="matchValidator()" [tuiValidator]="matchValidator()"
/> />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="confirm" />
formControlName="confirm"
[error]="[] | tuiFieldError | async"
/>
<footer> <footer>
<button <button
tuiButton tuiButton
@@ -76,22 +66,20 @@ import { ApiService } from 'src/app/services/api/api.service'
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
AsyncPipe,
ReactiveFormsModule, ReactiveFormsModule,
TuiAutoFocus, TuiAutoFocus,
TuiButton, TuiButton,
TuiButtonLoading, TuiButtonLoading,
TuiError, TuiError,
TuiFieldErrorPipe,
TuiForm, TuiForm,
TuiTextfield, TuiInput,
TuiValidator, TuiValidator,
], ],
}) })
export class ChangePasswordDialog { export class ChangePasswordDialog {
private readonly context = injectContext<TuiDialogContext<void>>() private readonly context = injectContext<TuiDialogContext<void>>()
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly alerts = inject(TuiAlertService) private readonly alerts = inject(TuiNotificationService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
protected readonly loading = signal(false) protected readonly loading = signal(false)

View File

@@ -5,25 +5,25 @@ import {
signal, signal,
} from '@angular/core' } from '@angular/core'
import { ErrorService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core' import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiDialogService } from '@taiga-ui/experimental' import { TuiButton, TuiCell, TuiTitle } from '@taiga-ui/core'
import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit' import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit'
import { TuiCard, TuiCell } from '@taiga-ui/layout' import { TuiCard } from '@taiga-ui/layout'
import { UpdateService } from 'src/app/services/update.service' import { UpdateService } from 'src/app/services/update.service'
import { CHANGE_PASSWORD } from './change-password' import { CHANGE_PASSWORD } from './change-password'
@Component({ @Component({
template: ` template: `
<div tuiCardLarge tuiAppearance="neutral"> <div tuiCardLarge>
<div tuiCell> <div tuiCell>
<span tuiTitle> <span tuiTitle>
<strong> <strong>
Version Version
@if (update.hasUpdate()) { @if (update.hasUpdate()) {
<tui-badge appearance="positive" size="s"> <span tuiBadge appearance="positive" size="s">
Update Available Update Available
</tui-badge> </span>
} }
</strong> </strong>
<span tuiSubtitle>Current: {{ update.installed() ?? '—' }}</span> <span tuiSubtitle>Current: {{ update.installed() ?? '—' }}</span>
@@ -52,19 +52,20 @@ import { CHANGE_PASSWORD } from './change-password'
</div> </div>
</div> </div>
`, `,
styles: `
[tuiCardLarge] {
background: var(--tui-background-neutral-1);
&:not([data-appearance]) {
display: none;
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [TuiCard, TuiCell, TuiTitle, TuiButton, TuiButtonLoading, TuiBadge],
TuiCard,
TuiCell,
TuiTitle,
TuiButton,
TuiButtonLoading,
TuiBadge,
TuiAppearance,
],
}) })
export default class Settings { export default class Settings {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
protected readonly update = inject(UpdateService) protected readonly update = inject(UpdateService)

View File

@@ -1,19 +1,12 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { import {
NonNullableFormBuilder, NonNullableFormBuilder,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from '@angular/forms' } from '@angular/forms'
import { LoadingService } from '@start9labs/shared'
import { TuiAutoFocus, tuiMarkControlAsTouchedAndValidate } from '@taiga-ui/cdk' import { TuiAutoFocus, tuiMarkControlAsTouchedAndValidate } from '@taiga-ui/cdk'
import { import { TuiButton, TuiDialogContext, TuiError, TuiInput } from '@taiga-ui/core'
TuiButton, import { TuiNotificationMiddleService } from '@taiga-ui/kit'
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiFieldErrorPipe } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout' import { TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
@@ -23,18 +16,15 @@ import { ApiService } from 'src/app/services/api/api.service'
<form tuiForm [formGroup]="form"> <form tuiForm [formGroup]="form">
<tui-textfield> <tui-textfield>
<label tuiLabel>Name</label> <label tuiLabel>Name</label>
<input tuiTextfield tuiAutoFocus formControlName="name" /> <input tuiInput tuiAutoFocus formControlName="name" />
</tui-textfield> </tui-textfield>
<tui-error formControlName="name" [error]="[] | tuiFieldError | async" /> <tui-error formControlName="name" />
@if (!context.data.name) { @if (!context.data.name) {
<tui-textfield> <tui-textfield>
<label tuiLabel>IP Range</label> <label tuiLabel>IP Range</label>
<input tuiTextfield formControlName="subnet" /> <input tuiInput formControlName="subnet" />
</tui-textfield> </tui-textfield>
<tui-error <tui-error formControlName="subnet" />
formControlName="subnet"
[error]="[] | tuiFieldError | async"
/>
} }
<footer> <footer>
<button tuiButton (click)="onSave()">Save</button> <button tuiButton (click)="onSave()">Save</button>
@@ -43,19 +33,17 @@ import { ApiService } from 'src/app/services/api/api.service'
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
AsyncPipe,
ReactiveFormsModule, ReactiveFormsModule,
TuiAutoFocus, TuiAutoFocus,
TuiButton, TuiButton,
TuiError, TuiError,
TuiFieldErrorPipe,
TuiForm, TuiForm,
TuiTextfield, TuiInput,
], ],
}) })
export class SubnetsAdd { export class SubnetsAdd {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService) private readonly loading = inject(TuiNotificationMiddleService)
protected readonly context = injectContext<TuiDialogContext<void, Data>>() protected readonly context = injectContext<TuiDialogContext<void, Data>>()
protected readonly form = inject(NonNullableFormBuilder).group({ protected readonly form = inject(NonNullableFormBuilder).group({
@@ -78,7 +66,7 @@ export class SubnetsAdd {
return return
} }
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
const value = this.form.getRawValue() const value = this.form.getRawValue()
try { try {

View File

@@ -1,15 +1,9 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { LoadingService } from '@start9labs/shared'
import { utils } from '@start9labs/start-sdk' import { utils } from '@start9labs/start-sdk'
import { import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
TuiButton, import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
TuiDataList, import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs' import { filter, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
@@ -41,16 +35,15 @@ import { SUBNETS_ADD } from './add'
tuiIconButton tuiIconButton
size="xs" size="xs"
tuiDropdown tuiDropdown
tuiDropdownOpen tuiDropdownAuto
appearance="flat-grayscale" appearance="flat-grayscale"
iconStart="@tui.ellipsis-vertical" iconStart="@tui.ellipsis-vertical"
> >
Actions Actions
<tui-data-list *tuiTextfieldDropdown size="s"> <tui-data-list *tuiDropdown size="s">
<button <button
tuiOption tuiOption
iconStart="@tui.pencil" iconStart="@tui.pencil"
new
(click)="onEdit(subnet)" (click)="onEdit(subnet)"
> >
Rename Rename
@@ -58,7 +51,6 @@ import { SUBNETS_ADD } from './add'
<button <button
tuiOption tuiOption
iconStart="@tui.trash" iconStart="@tui.trash"
new
(click)="onDelete($index)" (click)="onDelete($index)"
> >
Delete Delete
@@ -74,12 +66,12 @@ import { SUBNETS_ADD } from './add'
</table> </table>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield], imports: [TuiButton, TuiDropdown, TuiDataList],
}) })
export default class Subnets { export default class Subnets {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService) private readonly loading = inject(TuiNotificationMiddleService)
protected readonly subnets = toSignal<MappedSubnet[], []>( protected readonly subnets = toSignal<MappedSubnet[], []>(
inject<PatchDB<TunnelData>>(PatchDB) inject<PatchDB<TunnelData>>(PatchDB)
@@ -120,7 +112,7 @@ export default class Subnets {
.pipe(filter(Boolean)) .pipe(filter(Boolean))
.subscribe(async () => { .subscribe(async () => {
const subnet = this.subnets()[index]?.range || '' const subnet = this.subnets()[index]?.range || ''
const loader = this.loading.open().subscribe() const loader = this.loading.open('').subscribe()
try { try {
await this.api.deleteSubnet({ subnet }) await this.api.deleteSubnet({ subnet })

View File

@@ -6,7 +6,7 @@ import {
} from '@angular/core' } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { TuiButton, TuiError, TuiTextfield } from '@taiga-ui/core' import { TuiButton, TuiError, TuiInput } from '@taiga-ui/core'
import { TuiButtonLoading } from '@taiga-ui/kit' import { TuiButtonLoading } from '@taiga-ui/kit'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
import { AuthService } from 'src/app/services/auth.service' import { AuthService } from 'src/app/services/auth.service'
@@ -17,7 +17,7 @@ import { AuthService } from 'src/app/services/auth.service'
<form (ngSubmit)="login()"> <form (ngSubmit)="login()">
<tui-textfield [tuiTextfieldCleaner]="false"> <tui-textfield [tuiTextfieldCleaner]="false">
<input <input
tuiTextfield tuiInput
type="password" type="password"
placeholder="Enter password" placeholder="Enter password"
[ngModelOptions]="{ standalone: true }" [ngModelOptions]="{ standalone: true }"
@@ -59,7 +59,7 @@ import { AuthService } from 'src/app/services/auth.service'
} }
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, TuiTextfield, FormsModule, TuiError, TuiButtonLoading], imports: [TuiButton, TuiInput, FormsModule, TuiError, TuiButtonLoading],
}) })
export default class Login { export default class Login {
private readonly auth = inject(AuthService) private readonly auth = inject(AuthService)

View File

@@ -1,8 +1,8 @@
import { Component, computed, inject, Injectable, signal } from '@angular/core' import { Component, computed, inject, Injectable, signal } from '@angular/core'
import { toObservable } from '@angular/core/rxjs-interop' import { toObservable } from '@angular/core/rxjs-interop'
import { ErrorService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiLoader } from '@taiga-ui/core' import { TuiLoader } from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { import {
catchError, catchError,
@@ -31,7 +31,7 @@ class UpdatingDialog {
export class UpdateService { export class UpdateService {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly auth = inject(AuthService) private readonly auth = inject(AuthService)
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
readonly result = signal<TunnelUpdateResult | null>(null) readonly result = signal<TunnelUpdateResult | null>(null)

View File

@@ -14,7 +14,7 @@
<link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg" />
<link rel="shortcut icon" href="/assets/icons/favicon.ico" /> <link rel="shortcut icon" href="/assets/icons/favicon.ico" />
</head> </head>
<body tuiTheme="dark"> <body>
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>

View File

@@ -6,6 +6,7 @@
--tui-background-accent-1: #428bf9; --tui-background-accent-1: #428bf9;
--tui-background-accent-1-hover: #126df7; --tui-background-accent-1-hover: #126df7;
--tui-background-accent-1-pressed: #156ed4; --tui-background-accent-1-pressed: #156ed4;
--tui-stroke-width: 1.5px;
} }
body { body {
@@ -41,9 +42,21 @@ tui-dropdown[data-appearance='start-9'] {
backdrop-filter: blur(1rem); backdrop-filter: blur(1rem);
} }
tui-dialog[new][data-appearance~='start-9'] { [tuiTheme='dark'] tui-dialog[data-appearance~='start-9'],
background: var(--tui-background-neutral-1); [tuiTheme='dark'] tui-sheet-dialog[data-appearance~='start-9'] .t-sheet {
backdrop-filter: blur(5rem); background:
linear-gradient(45deg, #5240a89c, transparent),
linear-gradient(to bottom, #5240a854, transparent),
color-mix(in hsl, var(--tui-background-elevation-1) 90%, transparent 10%);
background-blend-mode: multiply;
header {
background: transparent;
}
}
tui-notification-middle {
--tui-background-accent-1: var(--tui-status-warning);
} }
.g-table { .g-table {

View File

@@ -14,7 +14,7 @@ import { PatchMonitorService } from './services/patch-monitor.service'
selector: 'app-root', selector: 'app-root',
imports: [TuiRoot, RouterOutlet, ToastContainerComponent], imports: [TuiRoot, RouterOutlet, ToastContainerComponent],
template: ` template: `
<tui-root tuiTheme="dark"> <tui-root>
<router-outlet /> <router-outlet />
<toast-container /> <toast-container />
</tui-root> </tui-root>

View File

@@ -10,7 +10,6 @@ import {
provideZoneChangeDetection, provideZoneChangeDetection,
} from '@angular/core' } from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms' import { UntypedFormBuilder } from '@angular/forms'
import { provideAnimations } from '@angular/platform-browser/animations'
import { import {
ActivationStart, ActivationStart,
PreloadAllModules, PreloadAllModules,
@@ -40,21 +39,22 @@ import {
} from '@start9labs/shared' } from '@start9labs/shared'
import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk' import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
import { import {
TUI_DATE_FORMAT, provideTaiga,
TUI_DIALOGS_CLOSE, TUI_DIALOGS_CLOSE,
TUI_MEDIA, TUI_MEDIA,
tuiAlertOptionsProvider,
tuiButtonOptionsProvider, tuiButtonOptionsProvider,
tuiDateFormatProvider,
tuiDropdownOptionsProvider, tuiDropdownOptionsProvider,
tuiHintOptionsProvider,
tuiNotificationOptionsProvider,
tuiNumberFormatProvider, tuiNumberFormatProvider,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { provideEventPlugins } from '@taiga-ui/event-plugins'
import { import {
TUI_DATE_TIME_VALUE_TRANSFORMER, tuiInputDateOptionsProvider,
TUI_DATE_VALUE_TRANSFORMER, tuiInputDateTimeOptionsProvider,
} from '@taiga-ui/kit' } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { filter, identity, merge, of, pairwise } from 'rxjs' import { filter, identity, merge, pairwise } from 'rxjs'
import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/updates/filter-updates.pipe' import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/updates/filter-updates.pipe'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { LiveApiService } from 'src/app/services/api/embassy-live-api.service' import { LiveApiService } from 'src/app/services/api/embassy-live-api.service'
@@ -63,14 +63,16 @@ import { AuthService } from 'src/app/services/auth.service'
import { CategoryService } from 'src/app/services/category.service' import { CategoryService } from 'src/app/services/category.service'
import { ClientStorageService } from 'src/app/services/client-storage.service' import { ClientStorageService } from 'src/app/services/client-storage.service'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { DateTransformerService } from 'src/app/services/date-transformer.service'
import { DatetimeTransformerService } from 'src/app/services/datetime-transformer.service'
import { import {
PATCH_CACHE, PATCH_CACHE,
PatchDbSource, PatchDbSource,
} from 'src/app/services/patch-db/patch-db-source' } from 'src/app/services/patch-db/patch-db-source'
import { StateService } from 'src/app/services/state.service' import { StateService } from 'src/app/services/state.service'
import { StorageService } from 'src/app/services/storage.service' import { StorageService } from 'src/app/services/storage.service'
import {
DateTransformer,
DatetimeTransformer,
} from 'src/app/utils/value-transformers'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ROUTES } from './app.routes' import { ROUTES } from './app.routes'
@@ -83,8 +85,7 @@ const {
export const APP_CONFIG: ApplicationConfig = { export const APP_CONFIG: ApplicationConfig = {
providers: [ providers: [
provideZoneChangeDetection(), provideZoneChangeDetection(),
provideAnimations(), provideTaiga({ mode: 'dark' }),
provideEventPlugins(),
provideHttpClient(withInterceptorsFromDi(), withFetch()), provideHttpClient(withInterceptorsFromDi(), withFetch()),
provideRouter( provideRouter(
ROUTES, ROUTES,
@@ -107,24 +108,13 @@ export const APP_CONFIG: ApplicationConfig = {
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }), tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
tuiButtonOptionsProvider({ size: 'm' }), tuiButtonOptionsProvider({ size: 'm' }),
tuiDropdownOptionsProvider({ appearance: 'start-os' }), tuiDropdownOptionsProvider({ appearance: 'start-os' }),
tuiAlertOptionsProvider({ tuiNotificationOptionsProvider({
autoClose: appearance => (appearance === 'negative' ? 0 : 3000), autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
}), }),
{ tuiDateFormatProvider({ mode: 'mm/dd/yyyy', separator: '/' }),
provide: TUI_DATE_FORMAT, tuiInputDateOptionsProvider({ valueTransformer: DateTransformer }),
useValue: of({ tuiInputDateTimeOptionsProvider({ valueTransformer: DatetimeTransformer }),
mode: 'MDY', tuiHintOptionsProvider({ appearance: 'primary-grayscale' }),
separator: '/',
}),
},
{
provide: TUI_DATE_VALUE_TRANSFORMER,
useClass: DateTransformerService,
},
{
provide: TUI_DATE_TIME_VALUE_TRANSFORMER,
useClass: DatetimeTransformerService,
},
{ {
provide: ApiService, provide: ApiService,
useClass: useMocks ? MockApiService : LiveApiService, useClass: useMocks ? MockApiService : LiveApiService,

View File

@@ -7,8 +7,7 @@ import {
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { i18nKey, i18nPipe } from '@start9labs/shared' import { i18nKey, i18nPipe } from '@start9labs/shared'
import { TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core' import { TuiDialogContext, TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { map } from 'rxjs' import { map } from 'rxjs'

View File

@@ -2,7 +2,7 @@ import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { TuiAlert, TuiLink } from '@taiga-ui/core' import { TuiLink, TuiNotification } from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs' import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs'
import { DataModel } from 'src/app/services/patch-db/data-model' import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -11,9 +11,9 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
selector: 'notifications-toast', selector: 'notifications-toast',
template: ` template: `
<ng-template <ng-template
[tuiAlert]="!!(visible$ | async)" [tuiNotification]="!!(visible$ | async)"
[tuiAlertOptions]="{ label: 'StartOS' }" [tuiNotificationOptions]="{ label: 'StartOS' }"
(tuiAlertChange)="onDismiss()" (tuiNotificationChange)="onDismiss()"
> >
{{ 'New notifications' | i18n }} {{ 'New notifications' | i18n }}
<a tuiLink routerLink="/notifications" [queryParams]="{ toast: true }"> <a tuiLink routerLink="/notifications" [queryParams]="{ toast: true }">
@@ -22,7 +22,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
</ng-template> </ng-template>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiAlert, RouterLink, AsyncPipe, i18nPipe, TuiLink], imports: [TuiNotification, RouterLink, AsyncPipe, i18nPipe, TuiLink],
}) })
export class NotificationsToastComponent { export class NotificationsToastComponent {
private readonly dismiss$ = new Subject<boolean>() private readonly dismiss$ = new Subject<boolean>()

View File

@@ -2,11 +2,12 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { SwUpdate } from '@angular/service-worker' import { SwUpdate } from '@angular/service-worker'
import { WA_WINDOW } from '@ng-web-apis/common' import { WA_WINDOW } from '@ng-web-apis/common'
import { i18nPipe, LoadingService } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { Version } from '@start9labs/start-sdk' import { Version } from '@start9labs/start-sdk'
import { TuiResponsiveDialog } from '@taiga-ui/addon-mobile' import { TuiResponsiveDialog } from '@taiga-ui/addon-mobile'
import { TuiAutoFocus } from '@taiga-ui/cdk' import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton } from '@taiga-ui/core' import { TuiButton } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { distinctUntilChanged, map, merge, Subject } from 'rxjs' import { distinctUntilChanged, map, merge, Subject } from 'rxjs'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
@@ -74,7 +75,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
export class RefreshAlertComponent { export class RefreshAlertComponent {
private readonly win = inject(WA_WINDOW) private readonly win = inject(WA_WINDOW)
private readonly updates = inject(SwUpdate) private readonly updates = inject(SwUpdate)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly version = Version.parse(inject(ConfigService).version) private readonly version = Version.parse(inject(ConfigService).version)
readonly i18n = inject(i18nPipe) readonly i18n = inject(i18nPipe)

View File

@@ -2,13 +2,9 @@ import { CommonModule } from '@angular/common'
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { WA_WINDOW } from '@ng-web-apis/common' import { WA_WINDOW } from '@ng-web-apis/common'
import { import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
DialogService,
i18nKey,
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { TuiButton } from '@taiga-ui/core' import { TuiButton } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { filter } from 'rxjs' import { filter } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
@@ -29,7 +25,7 @@ export default class HomePage {
} }
constructor( constructor(
private readonly loader: LoadingService, private readonly loader: TuiNotificationMiddleService,
private readonly api: ApiService, private readonly api: ApiService,
private readonly dialog: DialogService, private readonly dialog: DialogService,
@Inject(WA_WINDOW) private readonly window: Window, @Inject(WA_WINDOW) private readonly window: Window,

View File

@@ -1,7 +1,7 @@
import { Component, inject, DOCUMENT } from '@angular/core' import { Component, inject, DOCUMENT } from '@angular/core'
import { DocsLinkDirective, i18nPipe, RELATIVE_URL } from '@start9labs/shared' import { DocsLinkDirective, i18nPipe, RELATIVE_URL } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core' import { TuiButton, TuiIcon } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout' import { TuiCardLarge, TuiSurface } from '@taiga-ui/layout'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'

View File

@@ -11,7 +11,7 @@
<label tuiLabel>{{ 'Password' | i18n }}</label> <label tuiLabel>{{ 'Password' | i18n }}</label>
<input <input
tuiAutoFocus tuiAutoFocus
tuiTextfield tuiInput
type="password" type="password"
[ngModelOptions]="{ standalone: true }" [ngModelOptions]="{ standalone: true }"
[(ngModel)]="password" [(ngModel)]="password"

View File

@@ -1,4 +1,4 @@
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
.card { .card {
@include taiga.center-all(); @include taiga.center-all();

View File

@@ -3,10 +3,10 @@ import { Component, DestroyRef, DOCUMENT, inject, Inject } from '@angular/core'
import { takeUntilDestroyed } from '@angular/core/rxjs-interop' import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { i18nKey, i18nPipe, LoadingService } from '@start9labs/shared' import { i18nKey, i18nPipe } from '@start9labs/shared'
import { TuiAutoFocus } from '@taiga-ui/cdk' import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton, TuiError, TuiIcon, TuiTextfield } from '@taiga-ui/core' import { TuiButton, TuiError, TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiPassword } from '@taiga-ui/kit' import { TuiNotificationMiddleService, TuiPassword } from '@taiga-ui/kit'
import { TuiCardLarge } from '@taiga-ui/layout' import { TuiCardLarge } from '@taiga-ui/layout'
import { CAWizardComponent } from 'src/app/routes/login/ca-wizard/ca-wizard.component' import { CAWizardComponent } from 'src/app/routes/login/ca-wizard/ca-wizard.component'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -23,7 +23,7 @@ import { ConfigService } from 'src/app/services/config.service'
CAWizardComponent, CAWizardComponent,
TuiButton, TuiButton,
TuiCardLarge, TuiCardLarge,
TuiTextfield, TuiInput,
TuiIcon, TuiIcon,
TuiPassword, TuiPassword,
TuiAutoFocus, TuiAutoFocus,
@@ -39,7 +39,7 @@ export default class LoginPage {
constructor( constructor(
private readonly router: Router, private readonly router: Router,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly loader: LoadingService, private readonly loader: TuiNotificationMiddleService,
private readonly api: ApiService, private readonly api: ApiService,
public readonly config: ConfigService, public readonly config: ConfigService,
@Inject(DOCUMENT) public readonly document: Document, @Inject(DOCUMENT) public readonly document: Document,

View File

@@ -14,7 +14,7 @@ import {
} from '@taiga-ui/cdk' } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext } from '@taiga-ui/core' import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
import { TuiConfirmService } from '@taiga-ui/kit' import { TuiConfirmService } from '@taiga-ui/kit'
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus' import { injectContext } from '@taiga-ui/polymorpheus'
import { Operation } from 'fast-json-patch' import { Operation } from 'fast-json-patch'
import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component' import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component'
import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive' import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive'
@@ -76,7 +76,7 @@ export interface FormContext<T> {
styles: ` styles: `
.note { .note {
color: var(--tui-text-secondary); color: var(--tui-text-secondary);
font: var(--tui-font-text-s); font: var(--tui-typography-body-s);
margin-top: 1rem; margin-top: 1rem;
} }
@@ -107,10 +107,9 @@ export class FormComponent<T extends Record<string, any>> implements OnInit {
private readonly confirm = inject(TuiConfirmService, { optional: true }) private readonly confirm = inject(TuiConfirmService, { optional: true })
private readonly formService = inject(FormService) private readonly formService = inject(FormService)
private readonly invalidService = inject(InvalidService) private readonly invalidService = inject(InvalidService)
private readonly context = inject<TuiDialogContext<void, FormContext<T>>>( private readonly context = injectContext<
POLYMORPHEUS_CONTEXT, TuiDialogContext<void, FormContext<T>>
{ optional: true }, >({ optional: true })
)
@Input() spec = this.context?.data.spec || {} @Input() spec = this.context?.data.spec || {}
@Input() buttons = this.context?.data.buttons || [] @Input() buttons = this.context?.data.buttons || []

View File

@@ -1,4 +1,3 @@
import { AsyncPipe } from '@angular/common'
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
@@ -11,19 +10,14 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import { import {
AbstractControl, AbstractControl,
FormArrayName, FormArrayName,
FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared' import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TuiAnimated } from '@taiga-ui/cdk' import { TuiAnimated } from '@taiga-ui/cdk'
import { import { TuiButton, TuiError, TuiIcon, TuiInput, TuiLink } from '@taiga-ui/core'
TuiButton, import { TuiTooltip } from '@taiga-ui/kit'
TuiError,
TuiIcon,
TuiLink,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiFieldErrorPipe, TuiTooltip } from '@taiga-ui/kit'
import { filter } from 'rxjs' import { filter } from 'rxjs'
import { FormService } from 'src/app/services/form.service' import { FormService } from 'src/app/services/form.service'
@@ -51,7 +45,13 @@ import { FormObjectComponent } from './object.component'
+ {{ 'Add' | i18n }} + {{ 'Add' | i18n }}
</button> </button>
</div> </div>
<tui-error [error]="order | tuiFieldError | async" /> <!-- TODO: Remove ngModel in Taiga v5.0 -->
<tui-error
[ngModel]=""
[ngModelOptions]="{ standalone: true }"
[formArray]="array.control"
[order]="order"
/>
@for (item of array.control.controls; track item) { @for (item of array.control.controls; track item) {
<div tuiAnimated class="control"> <div tuiAnimated class="control">
<div> <div>
@@ -92,7 +92,7 @@ import { FormObjectComponent } from './object.component'
} }
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
display: block; display: block;
@@ -102,7 +102,7 @@ import { FormObjectComponent } from './object.component'
.label { .label {
display: flex; display: flex;
align-items: center; align-items: center;
font: var(--tui-font-heading-6); font: var(--tui-typography-heading-h6);
} }
.add { .add {
@@ -173,21 +173,20 @@ import { FormObjectComponent } from './object.component'
`, `,
hostDirectives: [ControlDirective], hostDirectives: [ControlDirective],
imports: [ imports: [
AsyncPipe,
ReactiveFormsModule, ReactiveFormsModule,
TuiIcon, TuiIcon,
TuiTooltip, TuiTooltip,
TuiLink, TuiLink,
TuiError, TuiError,
TuiFieldErrorPipe,
TuiButton, TuiButton,
TuiTextfield, TuiInput,
i18nPipe, i18nPipe,
HintPipe, HintPipe,
MustachePipe, MustachePipe,
FormControlComponent, FormControlComponent,
forwardRef(() => FormObjectComponent), forwardRef(() => FormObjectComponent),
TuiAnimated, TuiAnimated,
FormsModule,
], ],
}) })
export class FormArrayComponent { export class FormArrayComponent {

View File

@@ -1,19 +1,15 @@
import { AsyncPipe } from '@angular/common'
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
inject, inject,
Input, Input,
} from '@angular/core' } from '@angular/core'
import { ReactiveFormsModule } from '@angular/forms'
import { DialogService, i18nPipe } from '@start9labs/shared' import { DialogService, i18nPipe } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { tuiAsControl, TuiControl } from '@taiga-ui/cdk' import { tuiAsControl, TuiControl } from '@taiga-ui/cdk'
import { TuiError } from '@taiga-ui/core' import { TUI_VALIDATION_ERRORS, TuiError } from '@taiga-ui/core'
import { import { TUI_FORMAT_ERROR } from '@taiga-ui/kit'
TUI_FORMAT_ERROR,
TUI_VALIDATION_ERRORS,
TuiFieldErrorPipe,
} from '@taiga-ui/kit'
import { PolymorpheusOutlet } from '@taiga-ui/polymorpheus' import { PolymorpheusOutlet } from '@taiga-ui/polymorpheus'
import { ControlSpec } from '../controls/control' import { ControlSpec } from '../controls/control'
@@ -35,7 +31,7 @@ export const ERRORS = [
selector: 'form-control', selector: 'form-control',
template: ` template: `
<ng-container *polymorpheusOutlet="controls[spec.type]" /> <ng-container *polymorpheusOutlet="controls[spec.type]" />
<tui-error [error]="order | tuiFieldError | async" /> <tui-error [formControl]="$any(control.control)" [order]="order" />
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [ providers: [
@@ -53,7 +49,7 @@ export const ERRORS = [
}, },
], ],
hostDirectives: [ControlDirective], hostDirectives: [ControlDirective],
imports: [AsyncPipe, PolymorpheusOutlet, TuiError, TuiFieldErrorPipe], imports: [PolymorpheusOutlet, TuiError, ReactiveFormsModule],
}) })
export class FormControlComponent< export class FormControlComponent<
T extends ControlSpec, T extends ControlSpec,
@@ -91,7 +87,7 @@ export class FormControlComponent<
.openConfirm({ .openConfirm({
label: 'Warning', label: 'Warning',
data: { content: warning as any, yes: 'Confirm', no: 'Cancel' }, data: { content: warning as any, yes: 'Confirm', no: 'Cancel' },
closeable: false, closable: false,
dismissible: false, dismissible: false,
}) })
.subscribe(confirm => { .subscribe(confirm => {

View File

@@ -3,13 +3,14 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
Input, Input,
signal,
SkipSelf, SkipSelf,
ViewEncapsulation, ViewEncapsulation,
} from '@angular/core' } from '@angular/core'
import { ControlContainer, ReactiveFormsModule } from '@angular/forms' import { ControlContainer, ReactiveFormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core' import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core'
import { identity, of } from 'rxjs' import { identity } from 'rxjs'
import { FilterHiddenPipe } from '../pipes/filter-hidden.pipe' import { FilterHiddenPipe } from '../pipes/filter-hidden.pipe'
import { FormArrayComponent } from './array.component' import { FormArrayComponent } from './array.component'
@@ -92,7 +93,7 @@ import { FormUnionComponent } from './union.component'
viewProviders: [ viewProviders: [
{ {
provide: TUI_DEFAULT_ERROR_MESSAGE, provide: TUI_DEFAULT_ERROR_MESSAGE,
useValue: of('Unknown error'), useValue: signal('Unknown error'),
}, },
{ {
provide: ControlContainer, provide: ControlContainer,

View File

@@ -9,8 +9,7 @@ import {
} from '@angular/core' } from '@angular/core'
import { ControlContainer } from '@angular/forms' import { ControlContainer } from '@angular/forms'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TuiButton, TuiIcon } from '@taiga-ui/core' import { TuiButton, TuiIcon, TuiExpand } from '@taiga-ui/core'
import { TuiExpand } from '@taiga-ui/experimental'
import { TuiTooltip } from '@taiga-ui/kit' import { TuiTooltip } from '@taiga-ui/kit'
import { ControlDirective } from './control.directive' import { ControlDirective } from './control.directive'
@@ -43,7 +42,7 @@ import { FormGroupComponent } from './group.component'
</tui-expand> </tui-expand>
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
display: flex; display: flex;
@@ -57,7 +56,7 @@ import { FormGroupComponent } from './group.component'
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
font: var(--tui-font-text-l); font: var(--tui-typography-body-l);
font-weight: bold; font-weight: bold;
margin: 0 0 -0.75rem; margin: 0 0 -0.75rem;
} }

View File

@@ -1,3 +1,4 @@
import { TuiElasticContainer } from '@taiga-ui/layout'
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@@ -13,7 +14,6 @@ import {
} from '@angular/forms' } from '@angular/forms'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TuiValueChanges } from '@taiga-ui/cdk' import { TuiValueChanges } from '@taiga-ui/cdk'
import { TuiElasticContainer } from '@taiga-ui/kit'
import { FormService } from 'src/app/services/form.service' import { FormService } from 'src/app/services/form.service'
import { FormControlComponent } from './control.component' import { FormControlComponent } from './control.component'
import { FormGroupComponent } from './group.component' import { FormGroupComponent } from './group.component'

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core' import { TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiInputColor, TuiTooltip } from '@taiga-ui/kit' import { TuiInputColor, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control' import { Control } from './control'
@@ -35,7 +35,7 @@ import { HintPipe } from '../pipes/hint.pipe'
`, `,
imports: [ imports: [
FormsModule, FormsModule,
TuiTextfield, TuiInput,
TuiInputColor, TuiInputColor,
TuiIcon, TuiIcon,
TuiTooltip, TuiTooltip,

View File

@@ -8,7 +8,7 @@ import {
TuiMapperPipe, TuiMapperPipe,
TuiTime, TuiTime,
} from '@taiga-ui/cdk' } from '@taiga-ui/cdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core' import { TuiIcon, TuiInput } from '@taiga-ui/core'
import { import {
TuiInputDate, TuiInputDate,
TuiInputDateTime, TuiInputDateTime,
@@ -22,21 +22,23 @@ import { HintPipe } from '../pipes/hint.pipe'
@Component({ @Component({
selector: 'form-datetime', selector: 'form-datetime',
template: ` template: `
<!-- <tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
TODO: Move @switch down to only affect <input ... /> after fix: @if (spec.name) {
https://github.com/taiga-family/taiga-ui/issues/11780 <label tuiLabel>
--> {{ spec.name }}
@switch (spec.inputmode) { @if (spec.required) {
@case ('time') { <span>*</span>
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
} }
</label>
}
@if (spec | hint; as hint) {
<tui-icon [tuiTooltip]="hint" />
}
@if (spec.inputmode !== 'time') {
<tui-calendar *tuiDropdown />
}
@switch (spec.inputmode) {
@case ('time') {
<input <input
tuiInputTime tuiInputTime
type="time" type="time"
@@ -47,21 +49,8 @@ import { HintPipe } from '../pipes/hint.pipe'
(ngModelChange)="value = $event?.toString() || null" (ngModelChange)="value = $event?.toString() || null"
(blur)="control.onTouched()" (blur)="control.onTouched()"
/> />
@if (spec | hint; as hint) { }
<tui-icon [tuiTooltip]="hint" /> @case ('date') {
}
</tui-textfield>
}
@case ('date') {
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
}
<input <input
tuiInputDate tuiInputDate
type="date" type="date"
@@ -73,22 +62,8 @@ import { HintPipe } from '../pipes/hint.pipe'
[(ngModel)]="value" [(ngModel)]="value"
(blur)="control.onTouched()" (blur)="control.onTouched()"
/> />
@if (spec | hint; as hint) { }
<tui-icon [tuiTooltip]="hint" /> @case ('datetime-local') {
}
<tui-calendar *tuiTextfieldDropdown />
</tui-textfield>
}
@case ('datetime-local') {
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
}
<input <input
tuiInputDateTime tuiInputDateTime
type="datetime-local" type="datetime-local"
@@ -100,17 +75,13 @@ import { HintPipe } from '../pipes/hint.pipe'
[(ngModel)]="value" [(ngModel)]="value"
(blur)="control.onTouched()" (blur)="control.onTouched()"
/> />
@if (spec | hint; as hint) { }
<tui-icon [tuiTooltip]="hint" />
}
<tui-calendar *tuiTextfieldDropdown />
</tui-textfield>
} }
} </tui-textfield>
`, `,
imports: [ imports: [
FormsModule, FormsModule,
TuiTextfield, TuiInput,
TuiIcon, TuiIcon,
TuiTooltip, TuiTooltip,
TuiInputTime, TuiInputTime,

View File

@@ -30,7 +30,7 @@ import { Control } from './control'
} }
</div> </div>
@if (value) { @if (value) {
<tui-chip> <span tuiChip>
{{ value.name }} {{ value.name }}
<button <button
tuiIconButton tuiIconButton
@@ -42,7 +42,7 @@ import { Control } from './control'
> >
{{ 'Delete' | i18n }} {{ 'Delete' | i18n }}
</button> </button>
</tui-chip> </span>
} @else { } @else {
<small>{{ 'Click or drop file here' | i18n }}</small> <small>{{ 'Click or drop file here' | i18n }}</small>
} }
@@ -54,7 +54,7 @@ import { Control } from './control'
</label> </label>
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
.template { .template {
@include taiga.transition(opacity); @include taiga.transition(opacity);
@@ -63,7 +63,7 @@ import { Control } from './control'
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 0.5rem; padding: 0 0.5rem;
font: var(--tui-font-text-m); font: var(--tui-typography-body-m);
font-weight: bold; font-weight: bold;
&_hidden { &_hidden {

View File

@@ -29,12 +29,6 @@ import { Control } from './control'
} }
</tui-textfield> </tui-textfield>
`, `,
styles: `
// TODO: Remove after Taiga UI update
:host ::ng-deep .t-input {
pointer-events: none;
}
`,
imports: [ imports: [
FormsModule, FormsModule,
TuiTextfield, TuiTextfield,

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TuiIcon, TuiNumberFormat, TuiTextfield } from '@taiga-ui/core' import { TuiIcon, TuiNumberFormat, TuiInput } from '@taiga-ui/core'
import { TuiInputNumber, TuiTooltip } from '@taiga-ui/kit' import { TuiInputNumber, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control' import { Control } from './control'
@@ -39,7 +39,7 @@ import { HintPipe } from '../pipes/hint.pipe'
`, `,
imports: [ imports: [
FormsModule, FormsModule,
TuiTextfield, TuiInput,
TuiInputNumber, TuiInputNumber,
TuiNumberFormat, TuiNumberFormat,
TuiIcon, TuiIcon,

View File

@@ -1,10 +1,10 @@
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { Component, inject } from '@angular/core' import { Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { Router, RouterLink } from '@angular/router' import { Router, RouterLink } from '@angular/router'
import { invert } from '@start9labs/shared' import { invert } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { TuiDataList, TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiDataList, TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { import {
TuiChevron, TuiChevron,
TuiFluidTypography, TuiFluidTypography,
@@ -50,12 +50,11 @@ import { HintPipe } from '../pipes/hint.pipe'
/> />
} }
@if (!mobile) { @if (!mobile) {
<tui-data-list *tuiTextfieldDropdown> <tui-data-list *tuiDropdown>
@for (item of items; track item) { @for (item of items; track item) {
@if (inverted[item]?.startsWith('~')) { @if (inverted[item]?.startsWith('~')) {
<a <a
tuiOption tuiOption
new
iconEnd="@tui.arrow-right" iconEnd="@tui.arrow-right"
tuiFluidTypography tuiFluidTypography
[routerLink]="inverted[item]?.slice(1)" [routerLink]="inverted[item]?.slice(1)"
@@ -65,7 +64,6 @@ import { HintPipe } from '../pipes/hint.pipe'
} @else { } @else {
<button <button
tuiOption tuiOption
new
tuiFluidTypography tuiFluidTypography
[style.white-space]="'nowrap'" [style.white-space]="'nowrap'"
[value]="item" [value]="item"
@@ -85,7 +83,7 @@ import { HintPipe } from '../pipes/hint.pipe'
imports: [ imports: [
FormsModule, FormsModule,
RouterLink, RouterLink,
TuiTextfield, TuiInput,
TuiSelect, TuiSelect,
TuiDataList, TuiDataList,
TuiFluidTypography, TuiFluidTypography,
@@ -98,7 +96,7 @@ import { HintPipe } from '../pipes/hint.pipe'
export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> { export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
protected readonly router = inject(Router) protected readonly router = inject(Router)
protected readonly inverted = invert(this.spec.values) protected readonly inverted = invert(this.spec.values)
protected readonly mobile = inject(TUI_IS_MOBILE) protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly items = Object.values(this.spec.values) protected readonly items = Object.values(this.spec.values)
protected readonly disabledItemHandler = (item: string) => protected readonly disabledItemHandler = (item: string) =>
Array.isArray(this.spec.disabled) && Array.isArray(this.spec.disabled) &&

View File

@@ -3,7 +3,7 @@ import { FormsModule } from '@angular/forms'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { IST, utils } from '@start9labs/start-sdk' import { IST, utils } from '@start9labs/start-sdk'
import { tuiInjectElement } from '@taiga-ui/cdk' import { tuiInjectElement } from '@taiga-ui/cdk'
import { TuiButton, TuiIcon, TuiTextfield } from '@taiga-ui/core' import { TuiButton, TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiTooltip } from '@taiga-ui/kit' import { TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control' import { Control } from './control'
@@ -22,7 +22,7 @@ import { HintPipe } from '../pipes/hint.pipe'
</label> </label>
} }
<input <input
tuiTextfield tuiInput
[attr.inputmode]="spec.inputmode" [attr.inputmode]="spec.inputmode"
[attr.minLength]="spec.minLength" [attr.minLength]="spec.minLength"
[attr.maxLength]="spec.maxLength" [attr.maxLength]="spec.maxLength"
@@ -86,7 +86,7 @@ import { HintPipe } from '../pipes/hint.pipe'
`, `,
imports: [ imports: [
FormsModule, FormsModule,
TuiTextfield, TuiInput,
TuiButton, TuiButton,
TuiIcon, TuiIcon,
TuiTooltip, TuiTooltip,

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk' import { IST } from '@start9labs/start-sdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core' import { TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiTextarea, TuiTooltip } from '@taiga-ui/kit' import { TuiTextarea, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control' import { Control } from './control'
@@ -37,14 +37,7 @@ import { HintPipe } from '../pipes/hint.pipe'
} }
</tui-textfield> </tui-textfield>
`, `,
imports: [ imports: [FormsModule, TuiInput, TuiTextarea, TuiIcon, TuiTooltip, HintPipe],
FormsModule,
TuiTextfield,
TuiTextarea,
TuiIcon,
TuiTooltip,
HintPipe,
],
}) })
export class FormTextareaComponent extends Control< export class FormTextareaComponent extends Control<
IST.ValueSpecTextarea, IST.ValueSpecTextarea,

View File

@@ -2,9 +2,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { CopyService, i18nPipe } from '@start9labs/shared' import { CopyService, i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import { TuiButton, TuiHint, TuiTitle } from '@taiga-ui/core' import { TuiButton, TuiHint, TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiFade } from '@taiga-ui/kit' import { TuiFade } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'

View File

@@ -22,7 +22,7 @@ import { HeaderStatusComponent } from './status.component'
<header-menu class="item item_corner" /> <header-menu class="item item_corner" />
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
@keyframes connecting { @keyframes connecting {
25%, 25%,
@@ -122,7 +122,7 @@ import { HeaderStatusComponent } from './status.component'
display: flex; display: flex;
height: 100%; height: 100%;
align-items: center; align-items: center;
font: var(--tui-font-text-l); font: var(--tui-typography-body-l);
padding: 1rem; padding: 1rem;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

View File

@@ -5,7 +5,6 @@ import {
DocsLinkDirective, DocsLinkDirective,
ErrorService, ErrorService,
i18nPipe, i18nPipe,
LoadingService,
SafeLinksDirective, SafeLinksDirective,
} from '@start9labs/shared' } from '@start9labs/shared'
import { import {
@@ -15,6 +14,7 @@ import {
TuiHint, TuiHint,
TuiIcon, TuiIcon,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { filter } from 'rxjs' import { filter } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from 'src/app/services/auth.service' import { AuthService } from 'src/app/services/auth.service'
@@ -43,71 +43,60 @@ import { ABOUT } from './about.component'
{{ status().message | i18n }} {{ status().message | i18n }}
</div> </div>
} }
<tui-data-list [style.width.rem]="13"> <tui-data-list safeLinks [style.width.rem]="13">
<tui-opt-group> <button tuiOption iconStart="@tui.info" (click)="about()">
<button tuiOption iconStart="@tui.info" new (click)="about()"> {{ 'About this server' | i18n }}
{{ 'About this server' | i18n }} </button>
</button> <hr />
</tui-opt-group> <a
<tui-opt-group label="" safeLinks> tuiOption
<a docsLink
tuiOption iconStart="@tui.book-open-text"
docsLink path="/start-os/user-manual"
new >
iconStart="@tui.book-open-text" {{ 'User manual' | i18n }}
path="/start-os/user-manual" </a>
> <a
{{ 'User manual' | i18n }} tuiOption
</a> iconStart="@tui.headphones"
<a href="https://start9.com/contact"
tuiOption >
new {{ 'Contact support' | i18n }}
iconStart="@tui.headphones" </a>
href="https://start9.com/contact" <a
> tuiOption
{{ 'Contact support' | i18n }} iconStart="@tui.dollar-sign"
</a> href="https://donate.start9.com"
<a >
tuiOption {{ 'Donate to Start9' | i18n }}
new </a>
iconStart="@tui.dollar-sign" <hr />
href="https://donate.start9.com" <a
> tuiOption
{{ 'Donate to Start9' | i18n }} iconStart="@tui.settings"
</a> routerLink="/system"
</tui-opt-group> (click)="open = false"
<tui-opt-group label=""> >
<a {{ 'System Settings' | i18n }}
tuiOption </a>
new <hr />
iconStart="@tui.settings" <button
routerLink="/system" tuiOption
(click)="open = false" iconStart="@tui.refresh-cw"
> (click)="promptPower('restart')"
{{ 'System Settings' | i18n }} >
</a> {{ 'Restart' | i18n }}
</tui-opt-group> </button>
<tui-opt-group label=""> <button
<button tuiOption
tuiOption iconStart="@tui.power"
new (click)="promptPower('shutdown')"
iconStart="@tui.refresh-cw" >
(click)="promptPower('restart')" {{ 'Shutdown' | i18n }}
> </button>
{{ 'Restart' | i18n }} <button tuiOption iconStart="@tui.log-out" (click)="logout()">
</button> {{ 'Logout' | i18n }}
<button </button>
tuiOption
new
iconStart="@tui.power"
(click)="promptPower('shutdown')"
>
{{ 'Shutdown' | i18n }}
</button>
<button tuiOption new iconStart="@tui.log-out" (click)="logout()">
{{ 'Logout' | i18n }}
</button>
</tui-opt-group>
</tui-data-list> </tui-data-list>
</ng-template> </ng-template>
`, `,
@@ -151,7 +140,7 @@ import { ABOUT } from './about.component'
export class HeaderMenuComponent { export class HeaderMenuComponent {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly auth = inject(AuthService) private readonly auth = inject(AuthService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly dialog = inject(DialogService) private readonly dialog = inject(DialogService)

View File

@@ -32,7 +32,7 @@ import { getMenu } from 'src/app/utils/system-utilities'
} }
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
position: relative; position: relative;

View File

@@ -18,7 +18,7 @@ import { STATUS } from 'src/app/services/status.service'
<span>{{ status().message | i18n }}</span> <span>{{ status().message | i18n }}</span>
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
@include taiga.transition(all); @include taiga.transition(all);

View File

@@ -5,21 +5,21 @@ import {
input, input,
signal, signal,
} from '@angular/core' } from '@angular/core'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { import {
CopyService, CopyService,
DialogService, DialogService,
ErrorService, ErrorService,
i18nPipe, i18nPipe,
LoadingService,
} from '@start9labs/shared' } from '@start9labs/shared'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import { import {
TuiButton, TuiButton,
tuiButtonOptionsProvider, tuiButtonOptionsProvider,
TuiDataList, TuiDataList,
TuiDropdown, TuiDropdown,
TuiTextfield, TuiInput,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { QRModal } from 'src/app/routes/portal/modals/qr.component' import { QRModal } from 'src/app/routes/portal/modals/qr.component'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -114,11 +114,10 @@ import { DomainHealthService } from './domain-health.service'
[(tuiDropdownOpen)]="open" [(tuiDropdownOpen)]="open"
> >
{{ 'Actions' | i18n }} {{ 'Actions' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="open.set(false)"> <tui-data-list *tuiDropdown (click)="open.set(false)">
@if (address().ui) { @if (address().ui) {
<a <a
tuiOption tuiOption
new
iconStart="@tui.external-link" iconStart="@tui.external-link"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
@@ -130,7 +129,6 @@ import { DomainHealthService } from './domain-health.service'
} }
<button <button
tuiOption tuiOption
new
[iconStart]=" [iconStart]="
address().enabled ? '@tui.toggle-right' : '@tui.toggle-left' address().enabled ? '@tui.toggle-right' : '@tui.toggle-left'
" "
@@ -138,12 +136,11 @@ import { DomainHealthService } from './domain-health.service'
> >
{{ (address().enabled ? 'Disable' : 'Enable') | i18n }} {{ (address().enabled ? 'Disable' : 'Enable') | i18n }}
</button> </button>
<button tuiOption new iconStart="@tui.qr-code" (click)="showQR()"> <button tuiOption iconStart="@tui.qr-code" (click)="showQR()">
{{ 'Show QR' | i18n }} {{ 'Show QR' | i18n }}
</button> </button>
<button <button
tuiOption tuiOption
new
iconStart="@tui.copy" iconStart="@tui.copy"
(click)="copyService.copy(address().url)" (click)="copyService.copy(address().url)"
> >
@@ -152,7 +149,6 @@ import { DomainHealthService } from './domain-health.service'
@if (address().hostnameInfo.metadata.kind === 'public-domain') { @if (address().hostnameInfo.metadata.kind === 'public-domain') {
<button <button
tuiOption tuiOption
new
iconStart="@tui.settings" iconStart="@tui.settings"
(click)="showDnsValidation()" (click)="showDnsValidation()"
> >
@@ -162,7 +158,6 @@ import { DomainHealthService } from './domain-health.service'
@if (address().hostnameInfo.metadata.kind === 'private-domain') { @if (address().hostnameInfo.metadata.kind === 'private-domain') {
<button <button
tuiOption tuiOption
new
iconStart="@tui.settings" iconStart="@tui.settings"
(click)="showPrivateDnsValidation()" (click)="showPrivateDnsValidation()"
> >
@@ -175,7 +170,6 @@ import { DomainHealthService } from './domain-health.service'
) { ) {
<button <button
tuiOption tuiOption
new
iconStart="@tui.settings" iconStart="@tui.settings"
(click)="showPortForwardValidation()" (click)="showPortForwardValidation()"
> >
@@ -183,12 +177,7 @@ import { DomainHealthService } from './domain-health.service'
</button> </button>
} }
@if (address().deletable) { @if (address().deletable) {
<button <button tuiOption iconStart="@tui.trash" (click)="deleteDomain()">
tuiOption
new
iconStart="@tui.trash"
(click)="deleteDomain()"
>
{{ 'Delete' | i18n }} {{ 'Delete' | i18n }}
</button> </button>
} }
@@ -224,15 +213,15 @@ import { DomainHealthService } from './domain-health.service'
} }
} }
`, `,
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiTextfield], imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiInput],
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })], providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AddressActionsComponent { export class AddressActionsComponent {
private readonly isMobile = inject(TUI_IS_MOBILE) private readonly isMobile = inject(WA_IS_MOBILE)
private readonly dialog = inject(DialogService) private readonly dialog = inject(DialogService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly domainHealth = inject(DomainHealthService) private readonly domainHealth = inject(DomainHealthService)
readonly copyService = inject(CopyService) readonly copyService = inject(CopyService)
@@ -247,8 +236,8 @@ export class AddressActionsComponent {
showQR() { showQR() {
this.dialog this.dialog
.openComponent(new PolymorpheusComponent(QRModal), { .openComponent(new PolymorpheusComponent(QRModal), {
size: 'auto', size: 's',
closeable: this.isMobile, closable: this.isMobile,
data: this.address().url, data: this.address().url,
}) })
.subscribe() .subscribe()

View File

@@ -5,14 +5,10 @@ import {
input, input,
signal, signal,
} from '@angular/core' } from '@angular/core'
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared' import { ErrorService, i18nPipe } from '@start9labs/shared'
import { ISB, utils } from '@start9labs/start-sdk' import { ISB, utils } from '@start9labs/start-sdk'
import { import { TuiButton, TuiDataList, TuiDropdown, TuiInput } from '@taiga-ui/core'
TuiButton, import { TuiNotificationMiddleService } from '@taiga-ui/kit'
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { firstValueFrom } from 'rxjs' import { firstValueFrom } from 'rxjs'
import { import {
@@ -46,11 +42,11 @@ import { InterfaceAddressItemComponent } from './item.component'
[(tuiDropdownOpen)]="addOpen" [(tuiDropdownOpen)]="addOpen"
> >
{{ 'Add Domain' | i18n }} {{ 'Add Domain' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="addOpen.set(false)"> <tui-data-list *tuiDropdown (click)="addOpen.set(false)">
<button tuiOption new (click)="addPublicDomain()"> <button tuiOption (click)="addPublicDomain()">
{{ 'Public Domain' | i18n }} {{ 'Public Domain' | i18n }}
</button> </button>
<button tuiOption new (click)="addPrivateDomain()"> <button tuiOption (click)="addPrivateDomain()">
{{ 'Private Domain' | i18n }} {{ 'Private Domain' | i18n }}
</button> </button>
</tui-data-list> </tui-data-list>
@@ -97,7 +93,7 @@ import { InterfaceAddressItemComponent } from './item.component'
TuiButton, TuiButton,
TuiDropdown, TuiDropdown,
TuiDataList, TuiDataList,
TuiTextfield, TuiInput,
TableComponent, TableComponent,
PlaceholderComponent, PlaceholderComponent,
i18nPipe, i18nPipe,
@@ -108,7 +104,7 @@ import { InterfaceAddressItemComponent } from './item.component'
export class InterfaceAddressesComponent { export class InterfaceAddressesComponent {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB) private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly formDialog = inject(FormDialogService) private readonly formDialog = inject(FormDialogService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)

View File

@@ -189,7 +189,7 @@ export type DomainValidationData = {
td, td,
th { th {
padding: 0.5rem 0.5rem !important; padding: 0.5rem 0.5rem !important;
font: var(--tui-font-text-s) !important; font: var(--tui-typography-body-s) !important;
color: var(--tui-text-primary) !important; color: var(--tui-text-primary) !important;
font-weight: normal !important; font-weight: normal !important;
} }

View File

@@ -6,11 +6,15 @@ import {
input, input,
signal, signal,
} from '@angular/core' } from '@angular/core'
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared' import { FormsModule } from '@angular/forms'
import { ErrorService, i18nPipe } from '@start9labs/shared'
import { TuiObfuscatePipe } from '@taiga-ui/cdk' import { TuiObfuscatePipe } from '@taiga-ui/cdk'
import { TuiButton, TuiIcon } from '@taiga-ui/core' import { TuiButton, TuiIcon } from '@taiga-ui/core'
import { FormsModule } from '@angular/forms' import {
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit' TuiBadge,
TuiNotificationMiddleService,
TuiSwitch,
} from '@taiga-ui/kit'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { GatewayAddress, MappedServiceInterface } from '../interface.service' import { GatewayAddress, MappedServiceInterface } from '../interface.service'
import { AddressActionsComponent } from './actions.component' import { AddressActionsComponent } from './actions.component'
@@ -45,12 +49,13 @@ import { DomainHealthService } from './domain-health.service'
</span> </span>
</td> </td>
<td class="type"> <td class="type">
<tui-badge <span
tuiBadge
size="s" size="s"
[appearance]="typeAppearance(address.hostnameInfo.metadata.kind)" [appearance]="typeAppearance(address.hostnameInfo.metadata.kind)"
> >
{{ address.type }} {{ address.type }}
</tui-badge> </span>
</td> </td>
<td> <td>
<div class="cert"> <div class="cert">
@@ -177,7 +182,7 @@ import { DomainHealthService } from './domain-health.service'
.access { .access {
padding-right: 0; padding-right: 0;
font: var(--tui-font-text-m); font: var(--tui-typography-body-m);
font-weight: bold; font-weight: bold;
tui-icon { tui-icon {
@@ -186,7 +191,7 @@ import { DomainHealthService } from './domain-health.service'
} }
.type { .type {
font: var(--tui-font-text-m); font: var(--tui-typography-body-m);
font-weight: bold; font-weight: bold;
color: var(--tui-text-primary); color: var(--tui-text-primary);
padding-inline-end: 0.5rem; padding-inline-end: 0.5rem;
@@ -226,7 +231,7 @@ import { DomainHealthService } from './domain-health.service'
export class InterfaceAddressItemComponent { export class InterfaceAddressItemComponent {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly domainHealth = inject(DomainHealthService) private readonly domainHealth = inject(DomainHealthService)
readonly address = input.required<GatewayAddress>() readonly address = input.required<GatewayAddress>()

View File

@@ -1,3 +1,4 @@
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@@ -7,13 +8,7 @@ import {
} from '@angular/core' } from '@angular/core'
import { CopyService, DialogService, i18nPipe } from '@start9labs/shared' import { CopyService, DialogService, i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { TuiButton, TuiDataList, TuiDropdown, TuiInput } from '@taiga-ui/core'
import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
import { TableComponent } from 'src/app/routes/portal/components/table.component' import { TableComponent } from 'src/app/routes/portal/components/table.component'
@@ -109,7 +104,7 @@ import {
> >
{{ 'More' | i18n }} {{ 'More' | i18n }}
<tui-data-list <tui-data-list
*tuiTextfieldDropdown *tuiDropdown
(click)="overflowOpen.set(null)" (click)="overflowOpen.set(null)"
> >
@for ( @for (
@@ -120,7 +115,6 @@ import {
@if (pluginGroup().pluginActions[actionId]; as meta) { @if (pluginGroup().pluginActions[actionId]; as meta) {
<button <button
tuiOption tuiOption
new
(click)="runRowAction(actionId, meta, address)" (click)="runRowAction(actionId, meta, address)"
> >
{{ meta.name }} {{ meta.name }}
@@ -142,7 +136,7 @@ import {
[(tuiDropdownOpen)]="open" [(tuiDropdownOpen)]="open"
> >
{{ 'Actions' | i18n }} {{ 'Actions' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="open.set(false)"> <tui-data-list *tuiDropdown (click)="open.set(false)">
@if (address.hostnameInfo.metadata.kind === 'plugin') { @if (address.hostnameInfo.metadata.kind === 'plugin') {
@if (address.hostnameInfo.metadata.removeAction) { @if (address.hostnameInfo.metadata.removeAction) {
@if ( @if (
@@ -153,7 +147,6 @@ import {
) { ) {
<button <button
tuiOption tuiOption
new
iconStart="@tui.trash" iconStart="@tui.trash"
(click)=" (click)="
runRowAction( runRowAction(
@@ -170,7 +163,6 @@ import {
} }
<button <button
tuiOption tuiOption
new
iconStart="@tui.qr-code" iconStart="@tui.qr-code"
(click)="showQR(address.url)" (click)="showQR(address.url)"
> >
@@ -178,7 +170,6 @@ import {
</button> </button>
<button <button
tuiOption tuiOption
new
iconStart="@tui.copy" iconStart="@tui.copy"
(click)="copyService.copy(address.url)" (click)="copyService.copy(address.url)"
> >
@@ -192,7 +183,6 @@ import {
@if (pluginGroup().pluginActions[actionId]; as meta) { @if (pluginGroup().pluginActions[actionId]; as meta) {
<button <button
tuiOption tuiOption
new
(click)="runRowAction(actionId, meta, address)" (click)="runRowAction(actionId, meta, address)"
> >
{{ meta.name }} {{ meta.name }}
@@ -271,7 +261,7 @@ import {
TuiButton, TuiButton,
TuiDropdown, TuiDropdown,
TuiDataList, TuiDataList,
TuiTextfield, TuiInput,
TableComponent, TableComponent,
PlaceholderComponent, PlaceholderComponent,
i18nPipe, i18nPipe,
@@ -279,7 +269,7 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class PluginAddressesComponent { export class PluginAddressesComponent {
private readonly isMobile = inject(TUI_IS_MOBILE) private readonly isMobile = inject(WA_IS_MOBILE)
private readonly dialog = inject(DialogService) private readonly dialog = inject(DialogService)
private readonly actionService = inject(ActionService) private readonly actionService = inject(ActionService)
readonly copyService = inject(CopyService) readonly copyService = inject(CopyService)
@@ -290,12 +280,12 @@ export class PluginAddressesComponent {
readonly packageId = input('') readonly packageId = input('')
readonly value = input<MappedServiceInterface | undefined>() readonly value = input<MappedServiceInterface | undefined>()
showQR(url: string) { showQR(data: string) {
this.dialog this.dialog
.openComponent(new PolymorpheusComponent(QRModal), { .openComponent(new PolymorpheusComponent(QRModal), {
size: 'auto', size: 's',
closeable: this.isMobile, closable: this.isMobile,
data: url, data,
}) })
.subscribe() .subscribe()
} }

View File

@@ -116,7 +116,7 @@ export type PortForwardValidationData = {
td, td,
th { th {
padding: 0.5rem 0.5rem !important; padding: 0.5rem 0.5rem !important;
font: var(--tui-font-text-s) !important; font: var(--tui-typography-body-s) !important;
color: var(--tui-text-primary) !important; color: var(--tui-text-primary) !important;
font-weight: normal !important; font-weight: normal !important;
} }

View File

@@ -119,7 +119,7 @@ export type PrivateDnsValidationData = {
td, td,
th { th {
padding: 0.5rem 0.5rem !important; padding: 0.5rem 0.5rem !important;
font: var(--tui-font-text-s) !important; font: var(--tui-typography-body-s) !important;
color: var(--tui-text-primary) !important; color: var(--tui-text-primary) !important;
font-weight: normal !important; font-weight: normal !important;
} }

View File

@@ -29,7 +29,7 @@ import { PluginAddressesComponent } from './addresses/plugin.component'
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
color: var(--tui-text-secondary); color: var(--tui-text-secondary);
font: var(--tui-font-text-l); font: var(--tui-typography-body-l);
::ng-deep [tuiSkeleton] { ::ng-deep [tuiSkeleton] {
width: 100%; width: 100%;

View File

@@ -3,9 +3,9 @@ import {
convertAnsi, convertAnsi,
DownloadHTMLService, DownloadHTMLService,
ErrorService, ErrorService,
LoadingService,
} from '@start9labs/shared' } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { LogsComponent } from './logs.component' import { LogsComponent } from './logs.component'
@Directive({ @Directive({
@@ -13,7 +13,7 @@ import { LogsComponent } from './logs.component'
}) })
export class LogsDownloadDirective { export class LogsDownloadDirective {
private readonly component = inject(LogsComponent) private readonly component = inject(LogsComponent)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly downloadHtml = inject(DownloadHTMLService) private readonly downloadHtml = inject(DownloadHTMLService)

View File

@@ -1,5 +1,5 @@
import { Directive, inject, Output } from '@angular/core' import { Directive, inject, Output } from '@angular/core'
import { IntersectionObserveeService } from '@ng-web-apis/intersection-observer' import { WaIntersectionObserveeService } from '@ng-web-apis/intersection-observer'
import { convertAnsi, ErrorService } from '@start9labs/shared' import { convertAnsi, ErrorService } from '@start9labs/shared'
import { catchError, defer, filter, from, map, of, switchMap, tap } from 'rxjs' import { catchError, defer, filter, from, map, of, switchMap, tap } from 'rxjs'
import { LogsComponent } from './logs.component' import { LogsComponent } from './logs.component'
@@ -8,7 +8,7 @@ import { LogsComponent } from './logs.component'
selector: '[logsFetch]', selector: '[logsFetch]',
}) })
export class LogsFetchDirective { export class LogsFetchDirective {
private readonly observer = inject(IntersectionObserveeService) private readonly observer = inject(WaIntersectionObserveeService)
private readonly component = inject(LogsComponent) private readonly component = inject(LogsComponent)
private readonly errors = inject(ErrorService) private readonly errors = inject(ErrorService)

View File

@@ -19,7 +19,7 @@ import { TuiIcon } from '@taiga-ui/core'
justify-content: center; justify-content: center;
text-align: center; text-align: center;
padding: 1rem; padding: 1rem;
font: var(--tui-font-text-l); font: var(--tui-typography-body-l);
color: var(--tui-text-tertiary); color: var(--tui-text-tertiary);
tui-icon { tui-icon {

View File

@@ -75,7 +75,7 @@ const FILTER = ['services', 'system', 'marketplace']
</nav> </nav>
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
display: none; display: none;

View File

@@ -14,14 +14,14 @@ import { tuiIsNumber } from '@taiga-ui/cdk'
selector: 'task-info', selector: 'task-info',
template: ` template: `
@if (diff.length) { @if (diff.length) {
<tui-notification> <div tuiNotification>
{{ 'The following modifications were made' | i18n }}: {{ 'The following modifications were made' | i18n }}:
<ul> <ul>
@for (d of diff; track d) { @for (d of diff; track d) {
<li [innerHTML]="d"></li> <li [innerHTML]="d"></li>
} }
</ul> </ul>
</tui-notification> </div>
} }
`, `,
imports: [TuiNotification, i18nPipe], imports: [TuiNotification, i18nPipe],

View File

@@ -6,9 +6,19 @@ import {
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { RouterOutlet } from '@angular/router' import { RouterOutlet } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiLoader, TuiScrollbar } from '@taiga-ui/core' import {
import { TuiActionBar, TuiProgress } from '@taiga-ui/kit' TuiButton,
TuiIcon,
TuiLoader,
TuiPopup,
TuiScrollbar,
} from '@taiga-ui/core'
import {
TuiActionBar,
TuiNotificationMiddleService,
TuiProgress,
} from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { TabsComponent } from 'src/app/routes/portal/components/tabs.component' import { TabsComponent } from 'src/app/routes/portal/components/tabs.component'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -26,7 +36,7 @@ import { HeaderComponent } from './components/header/header.component'
</main> </main>
<app-tabs /> <app-tabs />
@if (update(); as update) { @if (update(); as update) {
<tui-action-bar *tuiActionBar="bar()"> <tui-action-bar *tuiPopup="bar()">
@if (update === true) { @if (update === true) {
<tui-icon icon="@tui.check" class="g-positive" /> <tui-icon icon="@tui.check" class="g-positive" />
Download complete, restart to apply changes Download complete, restart to apply changes
@@ -52,7 +62,7 @@ import { HeaderComponent } from './components/header/header.component'
} }
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
:host { :host {
height: 100%; height: 100%;
@@ -93,10 +103,11 @@ import { HeaderComponent } from './components/header/header.component'
TuiLoader, TuiLoader,
TuiIcon, TuiIcon,
TuiButton, TuiButton,
TuiPopup,
], ],
}) })
export class PortalComponent { export class PortalComponent {
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB) private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)

View File

@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { TuiDialogService, TuiIcon, TuiTitle } from '@taiga-ui/core' import { TuiDialogService, TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { BackupsUpcomingComponent } from './components/upcoming.component' import { BackupsUpcomingComponent } from './components/upcoming.component'
import { HISTORY } from './modals/history.component' import { HISTORY } from './modals/history.component'
import { JOBS } from './modals/jobs.component' import { JOBS } from './modals/jobs.component'

View File

@@ -2,16 +2,14 @@ import { Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { import {
TuiButton, TuiButton,
TuiCheckbox,
TuiDialogContext, TuiDialogContext,
TuiDialogOptions, TuiDialogOptions,
TuiGroup, TuiGroup,
TuiLoader, TuiLoader,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiBlock, TuiCheckbox } from '@taiga-ui/kit' import { TuiBlock } from '@taiga-ui/kit'
import { import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { firstValueFrom, map } from 'rxjs' import { firstValueFrom, map } from 'rxjs'
import { DataModel } from 'src/app/services/patch-db/data-model' import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -74,9 +72,7 @@ interface Package {
export class BackupsBackupModal { export class BackupsBackupModal {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB) private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
readonly context = readonly context =
inject<TuiDialogContext<string[], { btnText: string } | undefined>>( injectContext<TuiDialogContext<string[], { btnText: string } | undefined>>()
POLYMORPHEUS_CONTEXT,
)
hasSelection = false hasSelection = false

View File

@@ -1,17 +1,21 @@
import { Component, inject } from '@angular/core' import { Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { import {
TuiButton, TuiButton,
TuiDialogContext, TuiDialogContext,
TuiDialogService, TuiDialogService,
TuiTextfield, TuiInput,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit' import {
TuiBadge,
TuiNotificationMiddleService,
TuiSwitch,
} from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { from, map } from 'rxjs' import { from, map } from 'rxjs'
import { T } from '@start9labs/start-sdk'
import { BackupJob } from 'src/app/services/api/api.types' import { BackupJob } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe' import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
@@ -25,7 +29,7 @@ import { TARGET, TARGET_CREATE } from './target.component'
<tui-textfield> <tui-textfield>
<label tuiLabel>Job Name</label> <label tuiLabel>Job Name</label>
<input <input
tuiTextfield tuiInput
name="name" name="name"
[(ngModel)]="job.name" [(ngModel)]="job.name"
placeholder="My Backup Job" placeholder="My Backup Job"
@@ -40,11 +44,12 @@ import { TARGET, TARGET_CREATE } from './target.component'
(click)="selectTarget()" (click)="selectTarget()"
> >
Target Target
<tui-badge <span
tuiBadge
[appearance]="target()?.[job.targetId]?.type ? 'success' : 'warning'" [appearance]="target()?.[job.targetId]?.type ? 'success' : 'warning'"
> >
{{ target()?.[job.targetId]?.type || 'Select target' }} {{ target()?.[job.targetId]?.type || 'Select target' }}
</tui-badge> </span>
</button> </button>
<button <button
tuiButton tuiButton
@@ -55,14 +60,17 @@ import { TARGET, TARGET_CREATE } from './target.component'
(click)="selectPackages()" (click)="selectPackages()"
> >
Packages Packages
<tui-badge [appearance]="job.packageIds.length ? 'success' : 'warning'"> <span
tuiBadge
[appearance]="job.packageIds.length ? 'success' : 'warning'"
>
{{ job.packageIds.length + ' selected' }} {{ job.packageIds.length + ' selected' }}
</tui-badge> </span>
</button> </button>
<tui-textfield> <tui-textfield>
<label tuiLabel>Schedule</label> <label tuiLabel>Schedule</label>
<input <input
tuiTextfield tuiInput
name="cron" name="cron"
[(ngModel)]="job.cron" [(ngModel)]="job.cron"
placeholder="* * * * *" placeholder="* * * * *"
@@ -109,7 +117,7 @@ import { TARGET, TARGET_CREATE } from './target.component'
`, `,
imports: [ imports: [
FormsModule, FormsModule,
TuiTextfield, TuiInput,
TuiSwitch, TuiSwitch,
TuiButton, TuiButton,
TuiBadge, TuiBadge,
@@ -119,7 +127,7 @@ import { TARGET, TARGET_CREATE } from './target.component'
export class BackupsEditModal { export class BackupsEditModal {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiDialogService)
private readonly context = private readonly context =
injectContext<TuiDialogContext<BackupJob, BackupJobBuilder>>() injectContext<TuiDialogContext<BackupJob, BackupJobBuilder>>()

View File

@@ -1,5 +1,3 @@
import { toSignal } from '@angular/core/rxjs-interop'
import { TuiCheckbox, TuiSkeleton } from '@taiga-ui/kit'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
@@ -7,10 +5,18 @@ import {
inject, inject,
signal, signal,
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TUI_TRUE_HANDLER, TUI_FALSE_HANDLER } from '@taiga-ui/cdk' import { TUI_FALSE_HANDLER, TUI_TRUE_HANDLER } from '@taiga-ui/cdk'
import { TuiDialogService, TuiIcon, TuiLink, TuiButton } from '@taiga-ui/core' import {
TuiButton,
TuiCheckbox,
TuiDialogService,
TuiIcon,
TuiLink,
} from '@taiga-ui/core'
import { TuiNotificationMiddleService, TuiSkeleton } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { from } from 'rxjs' import { from } from 'rxjs'
import { REPORT } from 'src/app/components/backup-report.component' import { REPORT } from 'src/app/components/backup-report.component'
@@ -101,7 +107,7 @@ import { HasErrorPipe } from '../pipes/has-error.pipe'
</table> </table>
`, `,
styles: ` styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga; @use '@taiga-ui/styles/utils' as taiga;
tui-icon { tui-icon {
font-size: 1rem; font-size: 1rem;
@@ -170,7 +176,7 @@ export class BackupsHistoryModal {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiDialogService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
readonly targets = toSignal(from(this.api.getBackupTargets({}))) readonly targets = toSignal(from(this.api.getBackupTargets({})))
readonly runs = signal<BackupRun[] | null>(null) readonly runs = signal<BackupRun[] | null>(null)

View File

@@ -1,16 +1,22 @@
import { Component, inject, OnInit } from '@angular/core' import { Component, inject, OnInit } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { import {
TuiButton,
TuiDialogOptions, TuiDialogOptions,
TuiDialogService, TuiDialogService,
TuiIcon, TuiIcon,
TuiButton,
TuiNotification,
TuiLink, TuiLink,
TuiNotification,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiConfirmData, TUI_CONFIRM, TuiSkeleton } from '@taiga-ui/kit' import {
TUI_CONFIRM,
TuiConfirmData,
TuiNotificationMiddleService,
TuiSkeleton,
} from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
import { BehaviorSubject, filter, from } from 'rxjs' import { BehaviorSubject, filter, from } from 'rxjs'
import { BackupJob } from 'src/app/services/api/api.types' import { BackupJob } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -18,18 +24,17 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe' import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
import { BackupJobBuilder } from '../utils/job-builder' import { BackupJobBuilder } from '../utils/job-builder'
import { EDIT } from './edit.component' import { EDIT } from './edit.component'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
template: ` template: `
<tui-notification> <div tuiNotification>
Scheduling automatic backups is an excellent way to ensure your StartOS Scheduling automatic backups is an excellent way to ensure your StartOS
data is safely backed up. StartOS will issue a notification whenever one data is safely backed up. StartOS will issue a notification whenever one
of your scheduled backups succeeds or fails. of your scheduled backups succeeds or fails.
<a tuiLink docsLink path="/start-os/user-manual/backup-create.html"> <a tuiLink docsLink path="/start-os/user-manual/backup-create.html">
View instructions View instructions
</a> </a>
</tui-notification> </div>
<h3 class="g-title"> <h3 class="g-title">
Saved Jobs Saved Jobs
<button tuiButton size="s" iconStart="@tui.plus" (click)="create()"> <button tuiButton size="s" iconStart="@tui.plus" (click)="create()">
@@ -147,7 +152,7 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
}) })
export class BackupsJobsModal implements OnInit { export class BackupsJobsModal implements OnInit {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiDialogService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)

View File

@@ -1,14 +1,19 @@
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiMapperPipe } from '@taiga-ui/cdk' import { TuiMapperPipe } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiGroup } from '@taiga-ui/core' import {
import { TuiBlock, TuiCheckbox } from '@taiga-ui/kit' TuiButton,
TuiCheckbox,
TuiDialogContext,
TuiGroup,
} from '@taiga-ui/core'
import { TuiBlock, TuiNotificationMiddleService } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { take } from 'rxjs' import { take } from 'rxjs'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model' import { DataModel } from 'src/app/services/patch-db/data-model'
import { ToOptionsPipe } from '../pipes/to-options.pipe' import { ToOptionsPipe } from '../pipes/to-options.pipe'
@@ -68,7 +73,7 @@ import { RecoverOption } from '../types/recover-option'
}) })
export class BackupsRecoverModal { export class BackupsRecoverModal {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService) private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly context = private readonly context =
injectContext<TuiDialogContext<void, RecoverData>>() injectContext<TuiDialogContext<void, RecoverData>>()

View File

@@ -15,8 +15,8 @@ import {
TuiIcon, TuiIcon,
TuiLoader, TuiLoader,
TuiTitle, TuiTitle,
TuiCell,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'

Some files were not shown because too many files have changed in this diff Show More