diff --git a/web/.gitignore b/web/.gitignore index b1c5a5c90..284e0a69e 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -26,7 +26,6 @@ postprocess.js /.angular /.idea -/.ionic /.sass-cache /.sourcemaps /.versions @@ -39,4 +38,4 @@ postprocess.js /plugins config.json -proxy.conf.json \ No newline at end of file +proxy.conf.json diff --git a/web/angular.json b/web/angular.json index 9e99210cd..cd18f4523 100644 --- a/web/angular.json +++ b/web/angular.json @@ -26,11 +26,6 @@ "input": "projects/shared/assets", "output": "assets" }, - { - "glob": "**/*.svg", - "input": "node_modules/ionicons/dist/svg", - "output": "./svg" - }, { "glob": "**/*", "input": "node_modules/monaco-editor", @@ -46,11 +41,6 @@ "glob": "ngsw.json", "input": "dist/raw/ui", "output": "projects/ui/src" - }, - { - "glob": "**/*", - "input": "node_modules/@taiga-ui/icons/src", - "output": "assets/taiga-ui/icons" } ], "styles": [ diff --git a/web/package-lock.json b/web/package-lock.json index 9537c894e..917caaf61 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -24,15 +24,15 @@ "@start9labs/argon2": "^0.1.0", "@start9labs/emver": "^0.1.5", "@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2", - "@taiga-ui/addon-charts": "3.65.0", - "@taiga-ui/addon-commerce": "3.65.0", - "@taiga-ui/addon-mobile": "3.65.0", - "@taiga-ui/cdk": "3.65.0", - "@taiga-ui/core": "3.65.0", - "@taiga-ui/experimental": "3.65.0", - "@taiga-ui/icons": "3.65.0", - "@taiga-ui/kit": "3.65.0", - "@taiga-ui/styles": "3.65.0", + "@taiga-ui/addon-charts": "3.68.0", + "@taiga-ui/addon-commerce": "3.68.0", + "@taiga-ui/addon-mobile": "3.68.0", + "@taiga-ui/cdk": "3.68.0", + "@taiga-ui/core": "3.68.0", + "@taiga-ui/experimental": "3.68.0", + "@taiga-ui/icons": "3.68.0", + "@taiga-ui/kit": "3.68.0", + "@taiga-ui/styles": "3.68.0", "@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-event-plugins": "3.1.0", "ansi-to-html": "^0.7.2", @@ -4672,9 +4672,9 @@ } }, "node_modules/@taiga-ui/addon-charts": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.65.0.tgz", - "integrity": "sha512-HNKUeK0ippIvLRF6wsuCiyJ4d98K4uIhkGwK1fWaTVOCN26Z+AnFKk9AryTyhocEZctyc4PMpJ7BP7h3CA4dZA==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.68.0.tgz", + "integrity": "sha512-f19w8EikXSQuF2f/M8e3yZoXBzunugbLZlz/W0Fiw8ykGE2tZPWXmcX4VKHa2yuI/VPwSUClVcF/n7MgNque0w==", "dependencies": { "tslib": "2.6.2" }, @@ -4682,15 +4682,15 @@ "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@ng-web-apis/common": "3.0.6", - "@taiga-ui/cdk": "^3.65.0", - "@taiga-ui/core": "^3.65.0", + "@taiga-ui/cdk": "^3.68.0", + "@taiga-ui/core": "^3.68.0", "@tinkoff/ng-polymorpheus": "4.3.0" } }, "node_modules/@taiga-ui/addon-commerce": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.65.0.tgz", - "integrity": "sha512-D98M3nkPKVFz9TFiMxCmMtmJs9vDc69RlPv5M03ZF+qXHqbthfpVss/p2MSzs4Cr2vgoECaZWPLNcWBOO5mzCw==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.68.0.tgz", + "integrity": "sha512-HyBqU9WRbty4mXloawhO+0E2tfNwwD7yZ1DOx8kEiRrGWwIT+11Io/PspkShZzY4mTyPt0iuoBkwe9rpdoxieA==", "dependencies": { "tslib": "2.6.2" }, @@ -4702,18 +4702,18 @@ "@maskito/core": "1.9.0", "@maskito/kit": "1.9.0", "@ng-web-apis/common": "3.0.6", - "@taiga-ui/cdk": "^3.65.0", - "@taiga-ui/core": "^3.65.0", - "@taiga-ui/i18n": "^3.65.0", - "@taiga-ui/kit": "^3.65.0", + "@taiga-ui/cdk": "^3.68.0", + "@taiga-ui/core": "^3.68.0", + "@taiga-ui/i18n": "^3.68.0", + "@taiga-ui/kit": "^3.68.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/addon-mobile": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.65.0.tgz", - "integrity": "sha512-nKEf5Lb7yfR7vqkAIQQLoUEzSpKftdPpAsmco6FNfN4FDlvDFYTKE8MqqXAxzEqrXviDXv8/CKPv+nc6xd4VXg==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.68.0.tgz", + "integrity": "sha512-ssX+dO+aPF7q49YiuW2X//N9X01XqkJB8NlTKM4kYPxOQi423JR4tewafmcHoRjdT9OnUo/pkq72GRE5UECEAg==", "dependencies": { "tslib": "2.6.2" }, @@ -4722,17 +4722,17 @@ "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@ng-web-apis/common": "3.0.6", - "@taiga-ui/cdk": "^3.65.0", - "@taiga-ui/core": "^3.65.0", - "@taiga-ui/kit": "^3.65.0", + "@taiga-ui/cdk": "^3.68.0", + "@taiga-ui/core": "^3.68.0", + "@taiga-ui/kit": "^3.68.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/cdk": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.65.0.tgz", - "integrity": "sha512-hiFC9RlRng7pUv84YPZbqieKIYsFEzsMKCjMIckHBASBBU6qQ4OY6irKszFvTGqMe9KJgBh6sJU1hkQOBwFSaA==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.68.0.tgz", + "integrity": "sha512-GB1wJaJGJMkK2+Njl3qePy3o4tu6w2MIBGcPkFE65sqgomIDNhM5jVB3ldQI5XtvTHsAAoV8m6UpyKRDQJdw6g==", "dependencies": { "@ng-web-apis/common": "3.0.6", "@ng-web-apis/mutation-observer": "3.1.0", @@ -4760,11 +4760,11 @@ "optional": true }, "node_modules/@taiga-ui/core": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.65.0.tgz", - "integrity": "sha512-zNctTTsrW73fhmYirWE/mZs32UUvv6gV5CoIFm0BzVos0X7ZkN+x7PLXd9R+3CEgL6Kv/OxY92p+pJRvqc5jHg==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.68.0.tgz", + "integrity": "sha512-KBS7ZM8i/h0ReZcoD5xQP/AJYmrabg26f0n/fr4pZmDKgZGaaGvB54vwTxq6vh7QW1VOGc7LuJQ2MshEE9iOmQ==", "dependencies": { - "@taiga-ui/i18n": "^3.65.0", + "@taiga-ui/i18n": "^3.68.0", "tslib": "2.6.2" }, "peerDependencies": { @@ -4776,35 +4776,35 @@ "@angular/router": ">=12.0.0", "@ng-web-apis/common": "3.0.6", "@ng-web-apis/mutation-observer": "3.1.0", - "@taiga-ui/cdk": "^3.65.0", - "@taiga-ui/i18n": "^3.65.0", + "@taiga-ui/cdk": "^3.68.0", + "@taiga-ui/i18n": "^3.68.0", "@tinkoff/ng-event-plugins": "3.1.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/experimental": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.65.0.tgz", - "integrity": "sha512-LZYR+XeJ2n+vE4AHBiIolzlqDrDGUx/bmE0ypmKO7dPgvHWu5Al8OXRrnhyqmAVO48FNpkSZ07YoqCG/aoxu6g==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.68.0.tgz", + "integrity": "sha512-gZyD+S7af1Z11Sx0dQUZBGoyWsN2ykZsguTYCCtVF4iynoCiqwCsTTWhjaA7scNVuTl/H7ekwabw+U6+G3TPfg==", "dependencies": { "tslib": "2.6.2" }, "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", - "@taiga-ui/addon-commerce": "^3.65.0", - "@taiga-ui/cdk": "^3.65.0", - "@taiga-ui/core": "^3.65.0", - "@taiga-ui/kit": "^3.65.0", + "@taiga-ui/addon-commerce": "^3.68.0", + "@taiga-ui/cdk": "^3.68.0", + "@taiga-ui/core": "^3.68.0", + "@taiga-ui/kit": "^3.68.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/i18n": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.65.0.tgz", - "integrity": "sha512-lHy9VDKc5IXbm40eJnnAyOlmm3vDgmWhGbr5woGe9bV/tTqsBBDATY7Rkhz7Bu1nbX7X+MI0TDfQh9ayoCCKRQ==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.68.0.tgz", + "integrity": "sha512-AyJDYm3nD0mNfEnqXubGFgsHqUTCs8W8/P2Td/TF1JCsp7Zjo+qc9uocqWXzlf2Zd0w26d0oYbBsVjpnrMlVnw==", "dependencies": { "tslib": "2.6.2" }, @@ -4815,20 +4815,20 @@ } }, "node_modules/@taiga-ui/icons": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.65.0.tgz", - "integrity": "sha512-8iE6EuK+QBzcNiRM1ThZOOkZpal7V6dBouMXMj+QphRWiIp8Znj58mtY3L+uwQFpGnxt3DRs4p4eEA9ZuGFssw==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.68.0.tgz", + "integrity": "sha512-krRHxz4I74hwYfD1/zOQRMUlzpYyAyXCxudYm9lTS8G2Yy7QeVzs0d7FkdpUvCYMqfQjBLqepckykxD5qPwoSw==", "dependencies": { "tslib": "2.6.2" }, "peerDependencies": { - "@taiga-ui/cdk": "^3.65.0" + "@taiga-ui/cdk": "^3.68.0" } }, "node_modules/@taiga-ui/kit": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.65.0.tgz", - "integrity": "sha512-Nh6pMSAFR7yScF7acj8WdCpKQUgDatW2jObqts0z4hy9BJ8gl9BAWRBgSlbp3Oen5c2WAIC316Gb9OcttC8nbw==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.68.0.tgz", + "integrity": "sha512-s+GMHy9C5pX8FdyYdTrnue5H/OH41214qgIjXF1aPYzNveDsDTF1H1pFwJcDXYQO2wE3UEH0sV4nEyKXXyNjBA==", "dependencies": { "@maskito/angular": "1.9.0", "@maskito/core": "1.9.0", @@ -4845,19 +4845,19 @@ "@ng-web-apis/common": "3.0.6", "@ng-web-apis/mutation-observer": "3.1.0", "@ng-web-apis/resize-observer": "3.0.6", - "@taiga-ui/cdk": "^3.65.0", - "@taiga-ui/core": "^3.65.0", - "@taiga-ui/i18n": "^3.65.0", + "@taiga-ui/cdk": "^3.68.0", + "@taiga-ui/core": "^3.68.0", + "@taiga-ui/i18n": "^3.68.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/styles": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.65.0.tgz", - "integrity": "sha512-HO2sZPxNOGj2BPQpWkrM6HgZV/QxaEMEemye3sJvsfuttvk6bmxoL8NF331I63tlp/Zx7woD8AusH5ATuUniqg==", + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.68.0.tgz", + "integrity": "sha512-7iC+T2ManhjCzPqw3e3H2vt3Bn9op555tio6WROhnsxakOdUrZbPiVZGaKcIlZ27ZjPbYKxL1NeCmaiviuphOw==", "peerDependencies": { - "@taiga-ui/cdk": "^3.65.0", + "@taiga-ui/cdk": "^3.68.0", "tslib": "2.6.2" } }, diff --git a/web/package.json b/web/package.json index e80feeea9..20a75be0b 100644 --- a/web/package.json +++ b/web/package.json @@ -46,15 +46,15 @@ "@start9labs/argon2": "^0.1.0", "@start9labs/emver": "^0.1.5", "@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2", - "@taiga-ui/addon-charts": "3.65.0", - "@taiga-ui/addon-commerce": "3.65.0", - "@taiga-ui/addon-mobile": "3.65.0", - "@taiga-ui/cdk": "3.65.0", - "@taiga-ui/core": "3.65.0", - "@taiga-ui/experimental": "3.65.0", - "@taiga-ui/icons": "3.65.0", - "@taiga-ui/kit": "3.65.0", - "@taiga-ui/styles": "3.65.0", + "@taiga-ui/addon-charts": "3.68.0", + "@taiga-ui/addon-commerce": "3.68.0", + "@taiga-ui/addon-mobile": "3.68.0", + "@taiga-ui/cdk": "3.68.0", + "@taiga-ui/core": "3.68.0", + "@taiga-ui/experimental": "3.68.0", + "@taiga-ui/icons": "3.68.0", + "@taiga-ui/kit": "3.68.0", + "@taiga-ui/styles": "3.68.0", "@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-event-plugins": "3.1.0", "ansi-to-html": "^0.7.2", diff --git a/web/projects/shared/src/components/ticker/ticker.component.scss b/web/projects/shared/src/components/ticker/ticker.component.scss index a94230795..0c1fbd505 100644 --- a/web/projects/shared/src/components/ticker/ticker.component.scss +++ b/web/projects/shared/src/components/ticker/ticker.component.scss @@ -1,4 +1,5 @@ :host { + max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/web/projects/shared/src/directives/enter/enter.directive.ts b/web/projects/shared/src/directives/enter/enter.directive.ts deleted file mode 100644 index 8d3d6a399..000000000 --- a/web/projects/shared/src/directives/enter/enter.directive.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Directive, HostListener, Inject } from '@angular/core' -import { DOCUMENT } from '@angular/common' -import { debounce } from '../../util/misc.util' - -@Directive({ - selector: '[appEnter]', -}) -export class EnterDirective { - constructor(@Inject(DOCUMENT) private readonly document: Document) {} - - @HostListener('document:keydown.enter') - @debounce() - handleKeyboardEvent() { - const elems = this.document.querySelectorAll('.enter-click') - const elem = elems[elems.length - 1] as HTMLButtonElement - - if (elem && !elem.classList.contains('no-click') && !elem.disabled) { - elem.click() - } - } -} diff --git a/web/projects/shared/src/directives/enter/enter.module.ts b/web/projects/shared/src/directives/enter/enter.module.ts deleted file mode 100644 index 776f2eb38..000000000 --- a/web/projects/shared/src/directives/enter/enter.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from '@angular/core' - -import { EnterDirective } from './enter.directive' - -@NgModule({ - declarations: [EnterDirective], - exports: [EnterDirective], -}) -export class EnterModule {} diff --git a/web/projects/shared/src/public-api.ts b/web/projects/shared/src/public-api.ts index 61d4e76fd..78ba1abd8 100644 --- a/web/projects/shared/src/public-api.ts +++ b/web/projects/shared/src/public-api.ts @@ -18,8 +18,6 @@ export * from './components/drive.component' export * from './directives/drag-scroller.directive' export * from './directives/safe-links.directive' -export * from './directives/enter/enter.directive' -export * from './directives/enter/enter.module' export * from './mocks/get-setup-status' @@ -42,11 +40,6 @@ export * from './services/http.service' export * from './services/setup.service' export * from './services/setup-logs.service' -export * from './themes/dark-theme/dark-theme.component' -export * from './themes/dark-theme/dark-theme.module' -export * from './themes/light-theme/light-theme.component' -export * from './themes/light-theme/light-theme.module' - export * from './types/api' export * from './types/constructor' export * from './types/http.types' diff --git a/web/projects/shared/src/themes/dark-theme/dark-theme.component.scss b/web/projects/shared/src/themes/dark-theme/dark-theme.component.scss deleted file mode 100644 index 48e6526df..000000000 --- a/web/projects/shared/src/themes/dark-theme/dark-theme.component.scss +++ /dev/null @@ -1 +0,0 @@ -@import '../../../styles/variables'; diff --git a/web/projects/shared/src/themes/dark-theme/dark-theme.component.ts b/web/projects/shared/src/themes/dark-theme/dark-theme.component.ts deleted file mode 100644 index 2f80a5efb..000000000 --- a/web/projects/shared/src/themes/dark-theme/dark-theme.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ViewEncapsulation, -} from '@angular/core' -import { AbstractTuiThemeSwitcher } from '@taiga-ui/cdk' - -@Component({ - selector: 'dark-theme', - template: '', - styleUrls: ['./dark-theme.component.scss'], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class DarkThemeComponent extends AbstractTuiThemeSwitcher {} diff --git a/web/projects/shared/src/themes/dark-theme/dark-theme.module.ts b/web/projects/shared/src/themes/dark-theme/dark-theme.module.ts deleted file mode 100644 index f383e201f..000000000 --- a/web/projects/shared/src/themes/dark-theme/dark-theme.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from '@angular/core' - -import { DarkThemeComponent } from './dark-theme.component' - -@NgModule({ - declarations: [DarkThemeComponent], - exports: [DarkThemeComponent], -}) -export class DarkThemeModule {} diff --git a/web/projects/shared/src/themes/light-theme/light-theme.component.scss b/web/projects/shared/src/themes/light-theme/light-theme.component.scss deleted file mode 100644 index 33be8c8a9..000000000 --- a/web/projects/shared/src/themes/light-theme/light-theme.component.scss +++ /dev/null @@ -1,95 +0,0 @@ -// Ionic Variables and Theming. For more info, please see: -// http://ionicframework.com/docs/theming/ - -/** Ionic CSS Variables **/ -:root { - --ion-color-primary: #0075e1; - --ion-color-primary-rgb: 66, 140, 255; - --ion-color-primary-contrast: #ffffff; - --ion-color-primary-contrast-rgb: 255, 255, 255; - --ion-color-primary-shade: #3a7be0; - --ion-color-primary-tint: #5598ff; - - --ion-color-secondary: #50c8ff; - --ion-color-secondary-rgb: 80, 200, 255; - --ion-color-secondary-contrast: #ffffff; - --ion-color-secondary-contrast-rgb: 255, 255, 255; - --ion-color-secondary-shade: #46b0e0; - --ion-color-secondary-tint: #62ceff; - - --ion-color-tertiary: #6a64ff; - --ion-color-tertiary-rgb: 106, 100, 255; - --ion-color-tertiary-contrast: #ffffff; - --ion-color-tertiary-contrast-rgb: 255, 255, 255; - --ion-color-tertiary-shade: #5d58e0; - --ion-color-tertiary-tint: #7974ff; - - --ion-color-success: #2fdf75; - --ion-color-success-rgb: 47, 223, 117; - --ion-color-success-contrast: #000000; - --ion-color-success-contrast-rgb: 0, 0, 0; - --ion-color-success-shade: #29c467; - --ion-color-success-tint: #44e283; - - --ion-color-warning: #ffb506; - --ion-color-warning-rgb: 255, 213, 52; - --ion-color-warning-contrast: #000000; - --ion-color-warning-contrast-rgb: 0, 0, 0; - --ion-color-warning-shade: #e0bb2e; - --ion-color-warning-tint: #ffd534; - - --ion-color-danger: #ff4961; - --ion-color-danger-rgb: 255, 73, 97; - --ion-color-danger-contrast: #ffffff; - --ion-color-danger-contrast-rgb: 255, 255, 255; - --ion-color-danger-shade: #e04055; - --ion-color-danger-tint: #ff5b71; - - //--ion-color-light: #f4f5f8; - //--ion-color-light-rgb: 244, 245, 248; - //--ion-color-light-contrast: #000000; - //--ion-color-light-contrast-rgb: 0, 0, 0; - //--ion-color-light-shade: #d7d8da; - //--ion-color-light-tint: #f5f6f9; - // - //--ion-color-medium: #f4f5f8; - //--ion-color-medium-rgb: 244, 245, 248; - //--ion-color-medium-contrast: #000000; - //--ion-color-medium-contrast-rgb: 0, 0, 0; - //--ion-color-medium-shade: #d7d8da; - //--ion-color-medium-tint: #f5f6f9; - // - //--ion-color-dark: #92949c; - //--ion-color-dark-rgb: 146, 148, 156; - //--ion-color-dark-contrast: #ffffff; - //--ion-color-dark-contrast-rgb: 255, 255, 255; - //--ion-color-dark-shade: #808289; - //--ion-color-dark-tint: #9d9fa6; - - --ion-color-step-50: #f2f2f2; - --ion-color-step-100: #e6e6e6; - --ion-color-step-150: #d9d9d9; - --ion-color-step-200: #cccccc; - --ion-color-step-250: #bfbfbf; - --ion-color-step-300: #b3b3b3; - --ion-color-step-350: #a6a6a6; - --ion-color-step-400: #999999; - --ion-color-step-450: #8c8c8c; - --ion-color-step-500: #808080; - --ion-color-step-550: #737373; - --ion-color-step-600: #666666; - --ion-color-step-650: #595959; - --ion-color-step-700: #4d4d4d; - --ion-color-step-750: #404040; - --ion-color-step-800: #333333; - --ion-color-step-850: #262626; - --ion-color-step-900: #191919; - --ion-color-step-950: #0d0d0d; - - --alt-red: #ff4961; - --alt-orange: #f89248; - --alt-yellow: #e5d53e; - --alt-green: #3dcf6f; - --alt-blue: #00a8a8; - --alt-purple: #9747ff; -} diff --git a/web/projects/shared/src/themes/light-theme/light-theme.component.ts b/web/projects/shared/src/themes/light-theme/light-theme.component.ts deleted file mode 100644 index 1c3d7d366..000000000 --- a/web/projects/shared/src/themes/light-theme/light-theme.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ViewEncapsulation, -} from '@angular/core' -import { AbstractTuiThemeSwitcher } from '@taiga-ui/cdk' - -@Component({ - selector: 'light-theme', - template: '', - styleUrls: ['./light-theme.component.scss'], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class LightThemeComponent extends AbstractTuiThemeSwitcher {} diff --git a/web/projects/shared/src/themes/light-theme/light-theme.module.ts b/web/projects/shared/src/themes/light-theme/light-theme.module.ts deleted file mode 100644 index a9f9554e9..000000000 --- a/web/projects/shared/src/themes/light-theme/light-theme.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from '@angular/core' - -import { LightThemeComponent } from './light-theme.component' - -@NgModule({ - declarations: [LightThemeComponent], - exports: [LightThemeComponent], -}) -export class LightThemeModule {} diff --git a/web/projects/ui/src/app/app.component.html b/web/projects/ui/src/app/app.component.html index c588d56ac..b8024eef8 100644 --- a/web/projects/ui/src/app/app.component.html +++ b/web/projects/ui/src/app/app.component.html @@ -1,101 +1,31 @@ - - - - - - - - - - - + + + + - - - - - - - - -
- -
-
- - - - -
- -
+ +
- -
-
- - - -
+
- - - - - - - - + + +@if (auth.isVerified$ | async) { + @switch (theme$ | async) { + @case ('Dark') { + + } + } +} @else { - - +} diff --git a/web/projects/ui/src/app/app.component.scss b/web/projects/ui/src/app/app.component.scss index 295b01263..2cf185479 100644 --- a/web/projects/ui/src/app/app.component.scss +++ b/web/projects/ui/src/app/app.component.scss @@ -7,16 +7,6 @@ tui-root { height: 100%; } -.left-menu { - --side-max-width: 280px; -} - -.menu { - :host-context(body[data-theme='Light']) & { - --ion-color-base: #f4f4f5 !important; - } -} - .container { max-width: 100%; transition: filter 0.3s; @@ -24,110 +14,4 @@ tui-root { &_offline { filter: saturate(0.75) contrast(0.85); } - - @media screen and (max-width: 991.499px) { - --widgets-width: 0px; - } -} - -.right-menu { - --side-max-width: 600px; - - position: fixed; - z-index: 1000; - right: 0; - left: auto; - top: 74px; - - // For some reason *ngIf is broken upon first login - &_hidden { - display: none; - } -} - -.divider { - height: 100%; - width: 10px; - pointer-events: none; - - position: absolute; - left: 0; - top: 0; - bottom: 0; - - background: #e2e2e2; - - z-index: 10; - opacity: 0.2; - transition: opacity 0.3s; - - &:before, - &:after { - content: ''; - position: absolute; - top: 50%; - margin-top: -78px; - left: 10px; - width: 60px; - height: 50px; - border-bottom-left-radius: 14px; - box-shadow: -14px 0 0 -1px #e2e2e2; - } - - &:after { - margin-top: 28px; - border-radius: 0; - border-top-left-radius: 14px; - } - - &:hover { - opacity: 0.4; - } -} - -.widgets-button { - position: absolute; - top: 50%; - font-size: 0; - left: 100%; - width: 16px; - height: 60px; - margin-top: -30px; - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; - background: inherit; - pointer-events: auto; - - &:before, - &:after { - content: ''; - position: absolute; - top: 50%; - left: 3px; - width: 2px; - height: 8px; - background: black; - transform: rotate(-45deg); - border-radius: 2px; - } - - &:before { - margin-top: -5px; - transform: rotate(45deg); - } - - &_collapse:before { - transform: rotate(-45deg); - } - - &_collapse:after { - transform: rotate(45deg); - } -} - -.definitions { - position: absolute; - width: 0; - height: 0; - visibility: hidden; } diff --git a/web/projects/ui/src/app/app.component.ts b/web/projects/ui/src/app/app.component.ts index aed5b066a..93ff394da 100644 --- a/web/projects/ui/src/app/app.component.ts +++ b/web/projects/ui/src/app/app.component.ts @@ -1,50 +1,36 @@ -import { Component, inject, OnDestroy } from '@angular/core' -import { Router } from '@angular/router' -import { combineLatest, map, merge, startWith } from 'rxjs' -import { AuthService } from './services/auth.service' -import { SplitPaneTracker } from './services/split-pane.service' -import { PatchDataService } from './services/patch-data.service' -import { PatchMonitorService } from './services/patch-monitor.service' -import { ConnectionService } from './services/connection.service' +import { Component, inject, OnInit } from '@angular/core' +import { takeUntilDestroyed } from '@angular/core/rxjs-interop' import { Title } from '@angular/platform-browser' -import { - ClientStorageService, - WidgetDrawer, -} from './services/client-storage.service' -import { ThemeSwitcherService } from './services/theme-switcher.service' import { THEME } from '@start9labs/shared' import { PatchDB } from 'patch-db-client' +import { combineLatest, map, merge, startWith } from 'rxjs' +import { AuthService } from './services/auth.service' +import { ConnectionService } from './services/connection.service' +import { PatchDataService } from './services/patch-data.service' import { DataModel } from './services/patch-db/data-model' -import { slideInAnimation } from './route-animation' - -function hasNavigation(url: string): boolean { - return ( - !url.startsWith('/loading') && - !url.startsWith('/diagnostic') && - !url.startsWith('/portal') - ) -} +import { PatchMonitorService } from './services/patch-monitor.service' @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.scss'], - animations: [slideInAnimation], }) -export class AppComponent implements OnDestroy { - readonly subscription = merge(this.patchData, this.patchMonitor).subscribe() - readonly sidebarOpen$ = this.splitPane.sidebarOpen$ - readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$ +export class AppComponent implements OnInit { + private readonly title = inject(Title) + private readonly patch = inject(PatchDB) + + readonly auth = inject(AuthService) readonly theme$ = inject(THEME) - // @TODO theres a bug here disabling the side menu from appearing on first login; refresh fixes - readonly navigation$ = combineLatest([ - this.authService.isVerified$, - this.router.events.pipe(map(() => hasNavigation(this.router.url))), - ]).pipe(map(([isVerified, hasNavigation]) => isVerified && hasNavigation)) + readonly subscription = merge( + inject(PatchDataService), + inject(PatchMonitorService), + ) + .pipe(takeUntilDestroyed()) + .subscribe() readonly offline$ = combineLatest([ - this.authService.isVerified$, - this.connection.connected$, + inject(ConnectionService).connected$, + this.auth.isVerified$, this.patch .watch$('server-info', 'status-info') .pipe(startWith({ restarting: false, 'shutting-down': false })), @@ -56,37 +42,9 @@ export class AppComponent implements OnDestroy { ), ) - constructor( - private readonly router: Router, - private readonly titleService: Title, - private readonly patchData: PatchDataService, - private readonly patchMonitor: PatchMonitorService, - private readonly splitPane: SplitPaneTracker, - private readonly patch: PatchDB, - readonly authService: AuthService, - readonly connection: ConnectionService, - readonly clientStorageService: ClientStorageService, - readonly themeSwitcher: ThemeSwitcherService, - ) {} - async ngOnInit() { this.patch .watch$('ui', 'name') - .subscribe(name => this.titleService.setTitle(name || 'StartOS')) - } - - splitPaneVisible({ detail }: any) { - this.splitPane.sidebarOpen$.next(detail.visible) - } - - onResize(drawer: WidgetDrawer) { - this.clientStorageService.updateWidgetDrawer({ - ...drawer, - width: drawer.width === 400 ? 600 : 400, - }) - } - - ngOnDestroy() { - this.subscription.unsubscribe() + .subscribe(name => this.title.setTitle(name || 'StartOS')) } } diff --git a/web/projects/ui/src/app/app.module.ts b/web/projects/ui/src/app/app.module.ts index 9a42a28e4..2457c07bd 100644 --- a/web/projects/ui/src/app/app.module.ts +++ b/web/projects/ui/src/app/app.module.ts @@ -3,15 +3,7 @@ import { NgModule } from '@angular/core' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { ServiceWorkerModule } from '@angular/service-worker' import { IonicModule } from '@ionic/angular' -import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' -import { - DarkThemeModule, - EnterModule, - LightThemeModule, - LoadingModule, - MarkdownModule, - SharedPipesModule, -} from '@start9labs/shared' +import { LoadingModule } from '@start9labs/shared' import { TuiAlertModule, TuiDialogModule, @@ -19,17 +11,12 @@ import { TuiRootModule, TuiThemeNightModule, } from '@taiga-ui/core' +import { SidebarHostComponent } from 'src/app/common/sidebar-host.component' +import { SvgDefinitionsComponent } from 'src/app/common/svg-definitions.component' +import { ToastContainerComponent } from 'src/app/common/toast-container/toast-container.component' import { environment } from '../environments/environment' import { AppComponent } from './app.component' import { APP_PROVIDERS } from './app.providers' -import { ConnectionBarComponentModule } from './app/connection-bar/connection-bar.component.module' -import { FooterModule } from './app/footer/footer.module' -import { MenuModule } from './app/menu/menu.module' -import { PreloaderModule } from './app/preloader/preloader.module' -import { SidebarHostComponent } from './app/sidebar-host.component' -import { OSWelcomePageModule } from './common/os-welcome/os-welcome.module' -import { QRComponentModule } from './common/qr/qr.module' -import { ToastContainerModule } from './common/toast-container/toast-container.module' import { RoutingModule } from './routing.module' @NgModule({ @@ -41,23 +28,12 @@ import { RoutingModule } from './routing.module' mode: 'md', }), RoutingModule, - MenuModule, - PreloaderModule, - FooterModule, - EnterModule, - OSWelcomePageModule, - MarkdownModule, - MonacoEditorModule, - SharedPipesModule, - ToastContainerModule, - ConnectionBarComponentModule, + ToastContainerComponent, TuiRootModule, TuiDialogModule, TuiAlertModule, TuiModeModule, TuiThemeNightModule, - DarkThemeModule, - LightThemeModule, ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.useServiceWorker, // Register the ServiceWorker as soon as the application is stable @@ -65,8 +41,8 @@ import { RoutingModule } from './routing.module' registrationStrategy: 'registerWhenStable:30000', }), LoadingModule, - QRComponentModule, SidebarHostComponent, + SvgDefinitionsComponent, ], providers: APP_PROVIDERS, bootstrap: [AppComponent], diff --git a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.html b/web/projects/ui/src/app/app/connection-bar/connection-bar.component.html deleted file mode 100644 index 3af0f2120..000000000 --- a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.html +++ /dev/null @@ -1,16 +0,0 @@ - -
- -

{{ connection.message }}

- -
-
diff --git a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.module.ts b/web/projects/ui/src/app/app/connection-bar/connection-bar.component.module.ts deleted file mode 100644 index c67816642..000000000 --- a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { ConnectionBarComponent } from './connection-bar.component' - -@NgModule({ - declarations: [ConnectionBarComponent], - imports: [CommonModule, IonicModule], - exports: [ConnectionBarComponent], -}) -export class ConnectionBarComponentModule {} diff --git a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.scss b/web/projects/ui/src/app/app/connection-bar/connection-bar.component.scss deleted file mode 100644 index 1f9826193..000000000 --- a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.connection-toolbar { - padding: 0 24px; - --min-height: 36px; -} - -.icon { - font-size: 23px; - padding-right: 12px; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.ts b/web/projects/ui/src/app/app/connection-bar/connection-bar.component.ts deleted file mode 100644 index 9c4b07b7f..000000000 --- a/web/projects/ui/src/app/app/connection-bar/connection-bar.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { combineLatest, map, Observable, startWith } from 'rxjs' -import { ConnectionService } from 'src/app/services/connection.service' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'connection-bar', - templateUrl: './connection-bar.component.html', - styleUrls: ['./connection-bar.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ConnectionBarComponent { - private readonly websocket$ = this.connectionService.websocketConnected$ - - readonly connection$: Observable<{ - message: string - color: string - icon: string - dots: boolean - }> = combineLatest([ - this.connectionService.networkConnected$, - this.websocket$.pipe(startWith(false)), - this.patch - .watch$('server-info', 'status-info') - .pipe(startWith({ restarting: false, 'shutting-down': false })), - ]).pipe( - map(([network, websocket, status]) => { - if (!network) - return { - message: 'No Internet', - color: 'danger', - icon: 'cloud-offline-outline', - dots: false, - } - if (!websocket) - return { - message: 'Connecting', - color: 'warning', - icon: 'cloud-offline-outline', - dots: true, - } - if (status['shutting-down']) - return { - message: 'Shutting Down', - color: 'dark', - icon: 'power', - dots: true, - } - if (status.restarting) - return { - message: 'Restarting', - color: 'dark', - icon: 'power', - dots: true, - } - - return { - message: 'Connected', - color: 'success', - icon: 'cloud-done', - dots: false, - } - }), - ) - - constructor( - private readonly connectionService: ConnectionService, - private readonly patch: PatchDB, - ) {} -} diff --git a/web/projects/ui/src/app/app/footer/footer.component.html b/web/projects/ui/src/app/app/footer/footer.component.html deleted file mode 100644 index 306ceca67..000000000 --- a/web/projects/ui/src/app/app/footer/footer.component.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Downloading: - {{ getProgress(progress.size, progress.downloaded) }}% - - - - - - - Calculating download size - - - - - diff --git a/web/projects/ui/src/app/app/footer/footer.component.scss b/web/projects/ui/src/app/app/footer/footer.component.scss deleted file mode 100644 index cd737e850..000000000 --- a/web/projects/ui/src/app/app/footer/footer.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.list { - box-shadow: inset 0 1px var(--ion-color-dark); - box-sizing: border-box; -} - -.progress { - width: auto; - margin: 0 16px 16px 16px; -} diff --git a/web/projects/ui/src/app/app/footer/footer.component.ts b/web/projects/ui/src/app/app/footer/footer.component.ts deleted file mode 100644 index c630418fd..000000000 --- a/web/projects/ui/src/app/app/footer/footer.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { map } from 'rxjs' -import { heightCollapse } from 'src/app/util/animations' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'footer[appFooter]', - templateUrl: 'footer.component.html', - styleUrls: ['footer.component.scss'], - animations: [heightCollapse], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FooterComponent { - readonly progress$ = this.patch - .watch$('server-info', 'status-info', 'update-progress') - .pipe(map(a => a && { ...a })) - - readonly animation = { - value: '', - params: { - duration: 1000, - delay: 50, - }, - } - - constructor(private readonly patch: PatchDB) {} - - getProgress(size: number, downloaded: number): number { - return Math.round((100 * downloaded) / (size || 1)) - } -} diff --git a/web/projects/ui/src/app/app/footer/footer.module.ts b/web/projects/ui/src/app/app/footer/footer.module.ts deleted file mode 100644 index c76e4b26e..000000000 --- a/web/projects/ui/src/app/app/footer/footer.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { IonicModule } from '@ionic/angular' - -import { FooterComponent } from './footer.component' - -@NgModule({ - imports: [CommonModule, IonicModule], - declarations: [FooterComponent], - exports: [FooterComponent], -}) -export class FooterModule {} diff --git a/web/projects/ui/src/app/app/menu/menu.component.html b/web/projects/ui/src/app/app/menu/menu.component.html deleted file mode 100644 index 58892db0d..000000000 --- a/web/projects/ui/src/app/app/menu/menu.component.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - {{ page.title }} - - - - - {{ updateCount }} - - - {{ notificaitonCount }} - - - - -Play Snake - - - diff --git a/web/projects/ui/src/app/app/menu/menu.component.scss b/web/projects/ui/src/app/app/menu/menu.component.scss deleted file mode 100644 index a7c8c762f..000000000 --- a/web/projects/ui/src/app/app/menu/menu.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -:host { - display: block; -} - -.logo { - display: block; - width: 36%; - margin: 0 auto; - padding: 16px 16px 0 16px; -} - -.menu { - padding: 30px 0; -} - -.link { - --border-radius: 0; - - :host-context(body[data-theme='Light']) &_selected { - --ion-color-base: #333; - --ion-color-contrast: #fff; - } -} - -.icon { - margin-left: 10px; -} - -.label { - color: var(--ion-color-dark-shade); - - &_selected { - color: var(--ion-color-dark); - font-weight: bold; - } -} - -.snek { - position: absolute; - bottom: 56px; - right: 20px; - width: 20px; - cursor: pointer; -} - -.bottom { - position: absolute; - bottom: 0; -} diff --git a/web/projects/ui/src/app/app/menu/menu.component.ts b/web/projects/ui/src/app/app/menu/menu.component.ts deleted file mode 100644 index 268a7167d..000000000 --- a/web/projects/ui/src/app/app/menu/menu.component.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - Inject, -} from '@angular/core' -import { EOSService } from 'src/app/services/eos.service' -import { PatchDB } from 'patch-db-client' -import { - combineLatest, - filter, - first, - map, - Observable, - pairwise, - startWith, - switchMap, -} from 'rxjs' -import { AbstractMarketplaceService } from '@start9labs/marketplace' -import { MarketplaceService } from 'src/app/services/marketplace.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { SplitPaneTracker } from 'src/app/services/split-pane.service' -import { Emver, THEME } from '@start9labs/shared' -import { ConnectionService } from 'src/app/services/connection.service' -import { ConfigService } from 'src/app/services/config.service' - -@Component({ - selector: 'app-menu', - templateUrl: 'menu.component.html', - styleUrls: ['menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MenuComponent { - readonly pages = [ - { - title: 'Services', - url: '/services', - icon: 'grid-outline', - }, - { - title: 'Marketplace', - url: '/marketplace', - icon: 'storefront-outline', - }, - { - title: 'Updates', - url: '/updates', - icon: 'globe-outline', - }, - { - title: 'Backups', - url: '/backups', - icon: 'save-outline', - }, - { - title: 'Notifications', - url: '/notifications', - icon: 'notifications-outline', - }, - { - title: 'System', - url: '/system', - icon: 'construct-outline', - }, - ] - - readonly notificationCount$ = this.patch.watch$( - 'server-info', - 'unreadNotifications', - 'count', - ) - - readonly snekScore$ = this.patch.watch$('ui', 'gaming', 'snake', 'high-score') - - readonly showEOSUpdate$ = this.eosService.showUpdate$ - - private readonly local$ = this.connectionService.connected$.pipe( - filter(Boolean), - switchMap(() => this.patch.watch$('package-data').pipe(first())), - switchMap(outer => - this.patch.watch$('package-data').pipe( - pairwise(), - filter(([prev, curr]) => - Object.values(prev).some(p => { - const c = curr[p.manifest.id] - return !c || (p['install-progress'] && !c['install-progress']) - }), - ), - map(([_, curr]) => curr), - startWith(outer), - ), - ), - ) - - readonly updateCount$: Observable = combineLatest([ - this.marketplaceService.getMarketplace$(true), - this.local$, - ]).pipe( - map(([marketplace, local]) => - Object.entries(marketplace).reduce((list, [_, store]) => { - store?.packages.forEach(({ manifest: { id, version } }) => { - if (this.emver.compare(version, local[id]?.manifest.version) === 1) - list.add(id) - }) - return list - }, new Set()), - ), - map(list => list.size), - ) - - readonly sidebarOpen$ = this.splitPane.sidebarOpen$ - - readonly theme$ = inject(THEME) - - readonly warning$ = this.patch - .watch$('server-info', 'ntp-synced') - .pipe(map(synced => !synced)) - - constructor( - private readonly patch: PatchDB, - private readonly eosService: EOSService, - @Inject(AbstractMarketplaceService) - private readonly marketplaceService: MarketplaceService, - private readonly splitPane: SplitPaneTracker, - private readonly emver: Emver, - private readonly connectionService: ConnectionService, - private readonly config: ConfigService, - ) {} -} diff --git a/web/projects/ui/src/app/app/menu/menu.module.ts b/web/projects/ui/src/app/app/menu/menu.module.ts deleted file mode 100644 index 72a3fec9d..000000000 --- a/web/projects/ui/src/app/app/menu/menu.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { MenuComponent } from './menu.component' -import { SnekModule } from '../snek/snek.module' -import { ConnectionBarComponentModule } from '../connection-bar/connection-bar.component.module' - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule, - SnekModule, - ConnectionBarComponentModule, - ], - declarations: [MenuComponent], - exports: [MenuComponent], -}) -export class MenuModule {} diff --git a/web/projects/ui/src/app/app/preloader/preloader.component.html b/web/projects/ui/src/app/app/preloader/preloader.component.html deleted file mode 100644 index b26815f2c..000000000 --- a/web/projects/ui/src/app/app/preloader/preloader.component.html +++ /dev/null @@ -1,82 +0,0 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - load bold font - - - - - - - - - - - - -
- -
- -

a

-

a

-

a

-

a

-

a

-

a

-

a

-

a

-

a

-
diff --git a/web/projects/ui/src/app/app/preloader/preloader.component.ts b/web/projects/ui/src/app/app/preloader/preloader.component.ts deleted file mode 100644 index de77bb500..000000000 --- a/web/projects/ui/src/app/app/preloader/preloader.component.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -// TODO: Turn into DI token if this is needed someplace else too -const ICONS = [ - 'add', - 'alarm-outline', - 'alert-outline', - 'alert-circle-outline', - 'aperture-outline', - 'archive-outline', - 'arrow-back', - 'arrow-forward', - 'arrow-up', - 'brush-outline', - 'bookmark-outline', - 'cellular-outline', - 'chatbubbles-outline', - 'checkmark', - 'chevron-down', - 'chevron-up', - 'chevron-forward', - 'close', - 'close-circle-outline', - 'cloud-outline', - 'cloud-done', - 'cloud-done-outline', - 'cloud-download-outline', - 'cloud-offline-outline', - 'cloud-upload-outline', - 'code-outline', - 'color-wand-outline', - 'construct-outline', - 'copy-outline', - 'desktop-outline', - 'download-outline', - 'duplicate-outline', - 'earth-outline', - 'ellipsis-horizontal', - 'eye-off-outline', - 'eye-outline', - 'file-tray-stacked-outline', - 'finger-print-outline', - 'flash-outline', - 'flask-outline', - 'flash-off-outline', - 'folder-open-outline', - 'globe-outline', - 'grid-outline', - 'hammer-outline', - 'help-circle-outline', - 'hammer-outline', - 'information-circle-outline', - 'key-outline', - 'list-outline', - 'log-out-outline', - 'logo-bitcoin', - 'mail-outline', - 'map-outline', - 'medkit-outline', - 'notifications-outline', - 'open-outline', - 'options-outline', - 'pencil', - 'phone-portrait-outline', - 'play-circle-outline', - 'play-outline', - 'power', - 'pricetag-outline', - 'pulse', - 'push-outline', - 'qr-code-outline', - 'radio-outline', - 'receipt-outline', - 'refresh', - 'reload', - 'remove', - 'remove-circle-outline', - 'remove-outline', - 'repeat-outline', - 'ribbon-outline', - 'rocket-outline', - 'save-outline', - 'server-outline', - 'settings-outline', - 'shield-outline', - 'shuffle-outline', - 'stop-outline', - 'stopwatch-outline', - 'storefront-outline', - 'swap-vertical', - 'terminal-outline', - 'trail-sign-outline', - 'trash', - 'trash-outline', - 'warning-outline', - 'wifi', -] - -@Component({ - selector: 'section[appPreloader]', - templateUrl: 'preloader.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class PreloaderComponent { - readonly icons = ICONS -} diff --git a/web/projects/ui/src/app/app/preloader/preloader.module.ts b/web/projects/ui/src/app/app/preloader/preloader.module.ts deleted file mode 100644 index b1496e638..000000000 --- a/web/projects/ui/src/app/app/preloader/preloader.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CommonModule } from '@angular/common' -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core' -import { IonicModule } from '@ionic/angular' -import { QrCodeModule } from 'ng-qrcode' -import { PreloaderComponent } from './preloader.component' - -@NgModule({ - imports: [CommonModule, IonicModule, QrCodeModule], - declarations: [PreloaderComponent], - exports: [PreloaderComponent], - schemas: [CUSTOM_ELEMENTS_SCHEMA], -}) -export class PreloaderModule {} diff --git a/web/projects/ui/src/app/app/snek/snake.page.html b/web/projects/ui/src/app/app/snek/snake.page.html deleted file mode 100644 index 1fc48c47c..000000000 --- a/web/projects/ui/src/app/app/snek/snake.page.html +++ /dev/null @@ -1,8 +0,0 @@ -
- -
-
- Score: {{ score }} - High Score: {{ highScore }} - -
diff --git a/web/projects/ui/src/app/app/snek/snake.page.scss b/web/projects/ui/src/app/app/snek/snake.page.scss deleted file mode 100644 index 50605f1dc..000000000 --- a/web/projects/ui/src/app/app/snek/snake.page.scss +++ /dev/null @@ -1,14 +0,0 @@ -.canvas-center { - min-height: 50vh; - padding-top: 20px; - display: flex; - align-items: center; - justify-content: center; -} - -.footer { - display: flex; - align-items: center; - justify-content: space-between; - padding-top: 32px; -} diff --git a/web/projects/ui/src/app/app/snek/snake.page.ts b/web/projects/ui/src/app/app/snek/snake.page.ts deleted file mode 100644 index eeadb9df0..000000000 --- a/web/projects/ui/src/app/app/snek/snake.page.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { - AfterViewInit, - Component, - HostListener, - Inject, - OnDestroy, -} from '@angular/core' -import { pauseFor } from '@start9labs/shared' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' -import { DOCUMENT } from '@angular/common' - -@Component({ - selector: 'snake', - templateUrl: './snake.page.html', - styleUrls: ['./snake.page.scss'], -}) -export class SnakePage implements AfterViewInit, OnDestroy { - highScore = this.dialog.data.highScore - - score = 0 - - private readonly speed = 45 - private readonly width = 40 - private readonly height = 26 - private grid = NaN - - private readonly startingLength = 4 - - private xDown?: number - private yDown?: number - private canvas!: HTMLCanvasElement - private image!: HTMLImageElement - private context!: CanvasRenderingContext2D - - private snake: any - private bitcoin: { x: number; y: number } = { x: NaN, y: NaN } - - private moveQueue: String[] = [] - private destroyed = false - - constructor( - @Inject(DOCUMENT) private readonly document: Document, - @Inject(POLYMORPHEUS_CONTEXT) - private readonly dialog: TuiDialogContext, - ) {} - - dismiss() { - this.dialog.completeWith(this.highScore) - } - - @HostListener('document:keydown', ['$event']) - keyEvent(e: KeyboardEvent) { - this.moveQueue.push(e.key) - } - - @HostListener('touchstart', ['$event']) - touchStart(e: TouchEvent) { - this.handleTouchStart(e) - } - - @HostListener('touchmove', ['$event']) - touchMove(e: TouchEvent) { - this.handleTouchMove(e) - } - - @HostListener('window:resize') - sizeChange() { - this.init() - } - - ngOnDestroy() { - this.destroyed = true - } - - ngAfterViewInit() { - this.init() - - this.image = new Image() - this.image.onload = () => { - requestAnimationFrame(async () => await this.loop()) - } - this.image.src = '../../../../../../assets/img/icons/bitcoin.svg' - } - - init() { - this.canvas = this.document.querySelector('canvas#game')! - this.canvas.style.border = '1px solid #e0e0e0' - this.context = this.canvas.getContext('2d')! - const container = this.document.querySelector('.canvas-center')! - this.grid = Math.min( - Math.floor(container.clientWidth / this.width), - Math.floor(container.clientHeight / this.height), - ) - this.snake = { - x: this.grid * (Math.floor(this.width / 2) - this.startingLength), - y: this.grid * Math.floor(this.height / 2), - // snake velocity. moves one grid length every frame in either the x or y direction - dx: this.grid, - dy: 0, - // keep track of all grids the snake body occupies - cells: [], - // length of the snake. grows when eating an bitcoin - maxCells: this.startingLength, - } - this.bitcoin = { - x: this.getRandomInt(0, this.width) * this.grid, - y: this.getRandomInt(0, this.height) * this.grid, - } - - this.canvas.width = this.grid * this.width - this.canvas.height = this.grid * this.height - this.context.imageSmoothingEnabled = false - } - - getTouches(evt: TouchEvent) { - return evt.touches - } - - handleTouchStart(evt: TouchEvent) { - const firstTouch = this.getTouches(evt)[0] - this.xDown = firstTouch.clientX - this.yDown = firstTouch.clientY - } - - handleTouchMove(evt: TouchEvent) { - if (!this.xDown || !this.yDown) { - return - } - - var xUp = evt.touches[0].clientX - var yUp = evt.touches[0].clientY - - var xDiff = this.xDown - xUp - var yDiff = this.yDown - yUp - - if (Math.abs(xDiff) > Math.abs(yDiff)) { - /*most significant*/ - if (xDiff > 0) { - this.moveQueue.push('ArrowLeft') - } else { - this.moveQueue.push('ArrowRight') - } - } else { - if (yDiff > 0) { - this.moveQueue.push('ArrowUp') - } else { - this.moveQueue.push('ArrowDown') - } - } - /* reset values */ - this.xDown = undefined - this.yDown = undefined - } - - // game loop - async loop() { - if (this.destroyed) return - - await pauseFor(this.speed) - - requestAnimationFrame(async () => await this.loop()) - - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height) - - // move snake by its velocity - this.snake.x += this.snake.dx - this.snake.y += this.snake.dy - - if (this.moveQueue.length) { - const move = this.moveQueue.shift() - // left arrow key - if (move === 'ArrowLeft' && this.snake.dx === 0) { - this.snake.dx = -this.grid - this.snake.dy = 0 - } - // up arrow key - else if (move === 'ArrowUp' && this.snake.dy === 0) { - this.snake.dy = -this.grid - this.snake.dx = 0 - } - // right arrow key - else if (move === 'ArrowRight' && this.snake.dx === 0) { - this.snake.dx = this.grid - this.snake.dy = 0 - } - // down arrow key - else if (move === 'ArrowDown' && this.snake.dy === 0) { - this.snake.dy = this.grid - this.snake.dx = 0 - } - } - - // edge death - if ( - this.snake.x < 0 || - this.snake.y < 0 || - this.snake.x >= this.canvas.width || - this.snake.y >= this.canvas.height - ) { - this.death() - } - - // keep track of where snake has been. front of the array is always the head - this.snake.cells.unshift({ x: this.snake.x, y: this.snake.y }) - - // remove cells as we move away from them - if (this.snake.cells.length > this.snake.maxCells) { - this.snake.cells.pop() - } - - // draw bitcoin - this.context.fillStyle = '#ff4961' - this.context.drawImage( - this.image, - this.bitcoin.x - 1, - this.bitcoin.y - 1, - this.grid + 2, - this.grid + 2, - ) - - // draw snake one cell at a time - this.context.fillStyle = '#2fdf75' - - const firstCell = this.snake.cells[0] - - for (let index = 0; index < this.snake.cells.length; index++) { - const cell = this.snake.cells[index] - - // drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is - this.context.fillRect(cell.x, cell.y, this.grid - 1, this.grid - 1) - - // snake ate bitcoin - if (cell.x === this.bitcoin.x && cell.y === this.bitcoin.y) { - this.score++ - this.highScore = Math.max(this.score, this.highScore) - this.snake.maxCells++ - - this.bitcoin.x = this.getRandomInt(0, this.width) * this.grid - this.bitcoin.y = this.getRandomInt(0, this.height) * this.grid - } - - if (index > 0) { - // check collision with all cells after this one (modified bubble sort) - // snake occupies same space as a body part. reset game - if ( - firstCell.x === this.snake.cells[index].x && - firstCell.y === this.snake.cells[index].y - ) { - this.death() - } - } - } - } - - death() { - this.snake.x = - this.grid * (Math.floor(this.width / 2) - this.startingLength) - this.snake.y = this.grid * Math.floor(this.height / 2) - this.snake.cells = [] - this.snake.maxCells = this.startingLength - this.snake.dx = this.grid - this.snake.dy = 0 - - this.bitcoin.x = this.getRandomInt(0, 25) * this.grid - this.bitcoin.y = this.getRandomInt(0, 25) * this.grid - this.score = 0 - } - - getRandomInt(min: number, max: number) { - return Math.floor(Math.random() * (max - min)) + min - } -} diff --git a/web/projects/ui/src/app/app/snek/snek.directive.ts b/web/projects/ui/src/app/app/snek/snek.directive.ts deleted file mode 100644 index 246db7c09..000000000 --- a/web/projects/ui/src/app/app/snek/snek.directive.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Directive, HostListener, Input } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { TuiDialogService } from '@taiga-ui/core' -import { filter } from 'rxjs' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { SnakePage } from './snake.page' - -@Directive({ - selector: 'img[appSnek]', -}) -export class SnekDirective { - @Input() - appSnekHighScore = 0 - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly embassyApi: ApiService, - ) {} - - @HostListener('click') - async onClick() { - this.dialogs - .open(new PolymorpheusComponent(SnakePage), { - label: 'Snake!', - closeable: false, - dismissible: false, - data: { - highScore: this.appSnekHighScore, - }, - }) - .pipe(filter(score => score > this.appSnekHighScore)) - .subscribe(async score => { - const loader = this.loader.open('Saving high score...').subscribe() - - try { - await this.embassyApi.setDbValue( - ['gaming', 'snake', 'high-score'], - score, - ) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - }) - } -} diff --git a/web/projects/ui/src/app/app/snek/snek.module.ts b/web/projects/ui/src/app/app/snek/snek.module.ts deleted file mode 100644 index a678b8395..000000000 --- a/web/projects/ui/src/app/app/snek/snek.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { TuiButtonModule } from '@taiga-ui/experimental' - -import { SnekDirective } from './snek.directive' -import { SnakePage } from './snake.page' - -@NgModule({ - imports: [CommonModule, IonicModule, TuiButtonModule], - declarations: [SnekDirective, SnakePage], - exports: [SnekDirective, SnakePage], -}) -export class SnekModule {} diff --git a/web/projects/ui/src/app/apps/diagnostic/home/home.page.scss b/web/projects/ui/src/app/apps/diagnostic/home/home.page.scss index 15ec44f64..52f6f34c8 100644 --- a/web/projects/ui/src/app/apps/diagnostic/home/home.page.scss +++ b/web/projects/ui/src/app/apps/diagnostic/home/home.page.scss @@ -1,19 +1,20 @@ :host { display: block; - padding: 32px; + padding: 2rem; overflow: auto; + background: var(--tui-base-01); } .title { text-align: center; - padding-bottom: 24px; - font-size: calc(2vw + 14px); + padding-bottom: 1.5rem; + font-size: calc(2vw + 1rem); } .subtitle { - padding-top: 16px; - padding-bottom: 16px; - font-size: calc(1vw + 12px); + padding-top: 1rem; + padding-bottom: 1rem; + font-size: calc(1vw + 0.75rem); font-weight: bold; } @@ -21,8 +22,8 @@ display: block; color: var(--tui-success-fill); background: rgb(69, 69, 69); - padding: 1px 16px; - margin-bottom: 32px; + padding: 1px 1rem; + margin-bottom: 2rem; } .warning { @@ -31,5 +32,6 @@ .buttons { display: flex; - gap: 16px; + flex-wrap: wrap; + gap: 1rem; } diff --git a/web/projects/ui/src/app/apps/diagnostic/logs/logs.module.ts b/web/projects/ui/src/app/apps/diagnostic/logs/logs.module.ts index 7cb2cc2e1..97c1b8f90 100644 --- a/web/projects/ui/src/app/apps/diagnostic/logs/logs.module.ts +++ b/web/projects/ui/src/app/apps/diagnostic/logs/logs.module.ts @@ -1,7 +1,11 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' +import { IntersectionObserverModule } from '@ng-web-apis/intersection-observer' +import { MutationObserverModule } from '@ng-web-apis/mutation-observer' +import { TuiLoaderModule, TuiScrollbarModule } from '@taiga-ui/core' +import { TuiBadgeModule, TuiButtonModule } from '@taiga-ui/experimental' +import { NgDompurifyModule } from '@tinkoff/ng-dompurify' import { LogsPage } from './logs.page' const ROUTES: Routes = [ @@ -12,7 +16,17 @@ const ROUTES: Routes = [ ] @NgModule({ - imports: [CommonModule, IonicModule, RouterModule.forChild(ROUTES)], + imports: [ + CommonModule, + RouterModule.forChild(ROUTES), + IntersectionObserverModule, + MutationObserverModule, + NgDompurifyModule, + TuiBadgeModule, + TuiButtonModule, + TuiLoaderModule, + TuiScrollbarModule, + ], declarations: [LogsPage], }) export class LogsPageModule {} diff --git a/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.html b/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.html index 6abfaa929..129d34713 100644 --- a/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.html +++ b/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.html @@ -1,57 +1,23 @@ - - - - - - Logs - - - - - + +
- - - -
-
-
- -
- -
- - - -
- + @if (loading) { + + } +
+ @for (log of logs; track log) { +

+  }
+
diff --git a/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.ts b/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.ts index 7aaf0f519..a8304b168 100644 --- a/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.ts +++ b/web/projects/ui/src/app/apps/diagnostic/logs/logs.page.ts @@ -1,94 +1,79 @@ -import { Component, ViewChild } from '@angular/core' -import { IonContent } from '@ionic/angular' -import { ErrorService, toLocalIsoString } from '@start9labs/shared' -import { DiagnosticService } from '../services/diagnostic.service' - -const Convert = require('ansi-to-html') -const convert = new Convert({ - bg: 'transparent', -}) +import { Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core' +import { INTERSECTION_ROOT } from '@ng-web-apis/intersection-observer' +import { convertAnsi, ErrorService } from '@start9labs/shared' +import { TuiScrollbarComponent } from '@taiga-ui/core' +import { DiagnosticService } from 'src/app/apps/diagnostic/services/diagnostic.service' @Component({ selector: 'logs', templateUrl: './logs.page.html', + styles: ` + :host { + max-height: 100vh; + display: flex; + flex-direction: column; + justify-content: flex-start; + padding: 1rem; + gap: 1rem; + background: var(--tui-base-01); + } + `, + providers: [ + { + provide: INTERSECTION_ROOT, + useExisting: ElementRef, + }, + ], }) -export class LogsPage { - @ViewChild(IonContent) private content?: IonContent - loading = true - needInfinite = true +export class LogsPage implements OnInit { + @ViewChild(TuiScrollbarComponent, { read: ElementRef }) + private readonly scrollbar?: ElementRef + private readonly api = inject(DiagnosticService) + private readonly errorService = inject(ErrorService) + startCursor?: string - limit = 200 - isOnBottom = true + loading = false + logs: string[] = [] + scrollTop = 0 - constructor( - private readonly api: DiagnosticService, - private readonly errorService: ErrorService, - ) {} - - async ngOnInit() { - await this.getLogs() - this.loading = false + ngOnInit() { + this.getLogs() } - scrollEnd() { - const bottomDiv = document.getElementById('bottom-div') - this.isOnBottom = - !!bottomDiv && - bottomDiv.getBoundingClientRect().top - 420 < window.innerHeight + onTop(top: boolean) { + if (top) this.getLogs() } - scrollToBottom() { - this.content?.scrollToBottom(500) - } + restoreScroll() { + if (this.loading || !this.scrollbar) return - async doInfinite(e: any): Promise { - await this.getLogs() - e.target.complete() + const scrollbar = this.scrollbar.nativeElement + const offset = scrollbar.querySelector('pre')?.clientHeight || 0 + + scrollbar.scrollTop = this.scrollTop + offset } private async getLogs() { + if (this.loading) return + + this.loading = true + try { - const { 'start-cursor': startCursor, entries } = await this.api.getLogs({ + const response = await this.api.getLogs({ cursor: this.startCursor, before: !!this.startCursor, - limit: this.limit, + limit: 200, }) - if (!entries.length) return + if (!response.entries.length) return - this.startCursor = startCursor - - const container = document.getElementById('container') - const newLogs = document.getElementById('template')?.cloneNode(true) - - if (!(newLogs instanceof HTMLElement)) return - - newLogs.innerHTML = entries - .map( - entry => - `${toLocalIsoString( - new Date(entry.timestamp), - )} ${convert.toHtml(entry.message)}`, - ) - .join('\n') - - const beforeContainerHeight = container?.scrollHeight || 0 - container?.prepend(newLogs) - const afterContainerHeight = container?.scrollHeight || 0 - - // scroll down - setTimeout(() => { - this.content?.scrollToPoint( - 0, - afterContainerHeight - beforeContainerHeight, - ) - }, 50) - - if (entries.length < this.limit) { - this.needInfinite = false - } + this.startCursor = response['start-cursor'] + this.logs = [convertAnsi(response.entries), ...this.logs] + this.scrollTop = this.scrollbar?.nativeElement.scrollTop || 0 } catch (e: any) { this.errorService.handleError(e) + } finally { + this.loading = false } } } diff --git a/web/projects/ui/src/app/apps/loading/loading.page.ts b/web/projects/ui/src/app/apps/loading/loading.page.ts index 028c5f1a4..4702c7626 100644 --- a/web/projects/ui/src/app/apps/loading/loading.page.ts +++ b/web/projects/ui/src/app/apps/loading/loading.page.ts @@ -1,5 +1,5 @@ import { Component, inject } from '@angular/core' -import { NavController } from '@ionic/angular' +import { Router } from '@angular/router' import { InitializingComponent, provideSetupLogsService, @@ -13,8 +13,8 @@ import { ApiService } from 'src/app/services/api/embassy-api.service' template: ` + (finished)="router.navigate(['login'])" + /> `, providers: [ provideSetupService(ApiService), @@ -23,5 +23,5 @@ import { ApiService } from 'src/app/services/api/embassy-api.service' imports: [InitializingComponent], }) export class LoadingPage { - readonly navCtrl = inject(NavController) + readonly router = inject(Router) } diff --git a/web/projects/ui/src/app/apps/login/login.page.scss b/web/projects/ui/src/app/apps/login/login.page.scss index 65bc582b1..8d6b6d2d0 100644 --- a/web/projects/ui/src/app/apps/login/login.page.scss +++ b/web/projects/ui/src/app/apps/login/login.page.scss @@ -9,7 +9,7 @@ overflow: visible; align-items: center; text-align: center; - width: max(50%, 20rem); + width: max(33%, 20rem); } .logo { diff --git a/web/projects/ui/src/app/apps/login/login.page.ts b/web/projects/ui/src/app/apps/login/login.page.ts index b910c0c62..29b4a8ab1 100644 --- a/web/projects/ui/src/app/apps/login/login.page.ts +++ b/web/projects/ui/src/app/apps/login/login.page.ts @@ -1,5 +1,4 @@ import { Component, Inject } from '@angular/core' -import { getPlatforms } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' import { AuthService } from 'src/app/services/auth.service' import { Router } from '@angular/router' @@ -45,7 +44,8 @@ export class LoginPage { } await this.api.login({ password: this.password, - metadata: { platforms: getPlatforms() }, + // TODO: get platforms metadata + metadata: { platforms: [] }, }) this.password = '' diff --git a/web/projects/ui/src/app/apps/portal/components/header/corner.component.ts b/web/projects/ui/src/app/apps/portal/components/header/corner.component.ts index 80652611a..1a49954a2 100644 --- a/web/projects/ui/src/app/apps/portal/components/header/corner.component.ts +++ b/web/projects/ui/src/app/apps/portal/components/header/corner.component.ts @@ -18,7 +18,7 @@ import { import { Subject } from 'rxjs' import { HeaderMenuComponent } from './menu.component' import { HeaderNotificationsComponent } from './notifications.component' -import { SidebarDirective } from '../../../../app/sidebar-host.component' +import { SidebarDirective } from 'src/app/common/sidebar-host.component' import { NotificationService } from '../../services/notification.service' @Component({ diff --git a/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts b/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts index 33a2d5f62..2f0f362b5 100644 --- a/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts +++ b/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts @@ -14,7 +14,7 @@ import { TuiTitleModule, } from '@taiga-ui/experimental' import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { QRComponent } from 'src/app/common/qr/qr.component' +import { QRComponent } from 'src/app/common/qr.component' @Component({ standalone: true, diff --git a/web/projects/ui/src/app/apps/portal/routes/service/components/status.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/components/status.component.ts index c5e8aeb22..db021af59 100644 --- a/web/projects/ui/src/app/apps/portal/routes/service/components/status.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/components/status.component.ts @@ -7,7 +7,7 @@ import { } from '@angular/core' import { InstallProgress } from 'src/app/services/patch-db/data-model' import { StatusRendering } from 'src/app/services/pkg-status-rendering.service' -import { InstallProgressPipeModule } from 'src/app/common/install-progress/install-progress.module' +import { InstallProgressPipe } from '../pipes/install-progress.pipe' @Component({ selector: 'service-status', @@ -18,7 +18,7 @@ import { InstallProgressPipeModule } from 'src/app/common/install-progress/insta - + Installing {{ progress }} @@ -36,7 +36,7 @@ import { InstallProgressPipeModule } from 'src/app/common/install-progress/insta ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, InstallProgressPipeModule], + imports: [CommonModule, InstallProgressPipe], }) export class ServiceStatusComponent { @Input({ required: true }) diff --git a/web/projects/ui/src/app/apps/portal/routes/service/modals/credentials.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/modals/credentials.component.ts index 613d76e06..d155947ab 100644 --- a/web/projects/ui/src/app/apps/portal/routes/service/modals/credentials.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/modals/credentials.component.ts @@ -1,26 +1,25 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { ErrorService, SharedPipesModule } from '@start9labs/shared' -import { TuiForModule } from '@taiga-ui/cdk' +import { ErrorService } from '@start9labs/shared' +import { TuiLoaderModule } from '@taiga-ui/core' import { TuiButtonModule } from '@taiga-ui/experimental' import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' import { BehaviorSubject } from 'rxjs' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { SkeletonListComponentModule } from 'src/app/common/skeleton-list/skeleton-list.component.module' import { ServiceCredentialComponent } from '../components/credential.component' @Component({ template: ` - - - - - No credentials - `, @@ -36,11 +35,9 @@ import { ServiceCredentialComponent } from '../components/credential.component' standalone: true, imports: [ CommonModule, - TuiForModule, TuiButtonModule, - SharedPipesModule, - SkeletonListComponentModule, ServiceCredentialComponent, + TuiLoaderModule, ], }) export class ServiceCredentialsModal { diff --git a/web/projects/ui/src/app/common/install-progress/install-progress.pipe.ts b/web/projects/ui/src/app/apps/portal/routes/service/pipes/install-progress.pipe.ts similarity index 52% rename from web/projects/ui/src/app/common/install-progress/install-progress.pipe.ts rename to web/projects/ui/src/app/apps/portal/routes/service/pipes/install-progress.pipe.ts index d4ce5298a..44e10fb1b 100644 --- a/web/projects/ui/src/app/common/install-progress/install-progress.pipe.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/pipes/install-progress.pipe.ts @@ -1,11 +1,12 @@ import { Pipe, PipeTransform } from '@angular/core' -import { InstallProgress } from '../../services/patch-db/data-model' -import { packageLoadingProgress } from '../../util/package-loading-progress' +import { InstallProgress } from 'src/app/services/patch-db/data-model' +import { packageLoadingProgress } from 'src/app/util/package-loading-progress' @Pipe({ - name: 'installProgressDisplay', + standalone: true, + name: 'installProgress', }) -export class InstallProgressDisplayPipe implements PipeTransform { +export class InstallProgressPipe implements PipeTransform { transform(installProgress?: InstallProgress): string { const totalProgress = packageLoadingProgress(installProgress)?.totalProgress || 0 diff --git a/web/projects/ui/src/app/apps/portal/routes/service/pipes/to-menu.pipe.ts b/web/projects/ui/src/app/apps/portal/routes/service/pipes/to-menu.pipe.ts index 1b4a19a18..12e35d2b8 100644 --- a/web/projects/ui/src/app/apps/portal/routes/service/pipes/to-menu.pipe.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/pipes/to-menu.pipe.ts @@ -56,11 +56,12 @@ export class ToMenuPipe implements PipeTransform { name: 'Credentials', description: `Password, keys, or other credentials of interest`, action: () => - this.showDialog( - `${manifest.title} credentials`, - manifest.id, - ServiceCredentialsModal, - ), + this.dialogs + .open(new PolymorpheusComponent(ServiceCredentialsModal), { + label: `${manifest.title} credentials`, + data: manifest.id, + }) + .subscribe(), }, { icon: 'tuiIconZapLarge', @@ -116,16 +117,6 @@ export class ToMenuPipe implements PipeTransform { .subscribe() } - private showDialog(label: string, data: any, modal: Type) { - this.dialogs - .open(new PolymorpheusComponent(modal), { - size: 'l', - label, - data, - }) - .subscribe() - } - private openConfig({ title, id }: Manifest) { this.formDialog.open(ServiceConfigModal, { label: `${title} configuration`, diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/primary-ip.pipe.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/primary-ip.pipe.ts new file mode 100644 index 000000000..777194183 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/primary-ip.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { IpInfo } from 'src/app/services/patch-db/data-model' + +@Pipe({ + standalone: true, + name: 'primaryIp', +}) +export class PrimaryIpPipe implements PipeTransform { + transform(ipInfo: IpInfo): string { + return Object.values(ipInfo) + .filter(iface => iface.ipv4) + .sort((a, b) => (a.wireless ? -1 : 1))[0].ipv4! + } +} diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts index 0fe99fe61..b34be8390 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts @@ -3,8 +3,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { TuiTextfieldControllerModule } from '@taiga-ui/core' import { PatchDB } from 'patch-db-client' import { DataModel } from 'src/app/services/patch-db/data-model' -import { PrimaryIpPipeModule } from 'src/app/common/primary-ip/primary-ip.module' import { RouterInfoComponent } from './info.component' +import { PrimaryIpPipe } from './primary-ip.pipe' import { RouterPortComponent } from './table.component' @Component({ @@ -58,10 +58,10 @@ import { RouterPortComponent } from './table.component' standalone: true, imports: [ CommonModule, - PrimaryIpPipeModule, RouterInfoComponent, RouterPortComponent, TuiTextfieldControllerModule, + PrimaryIpPipe, ], }) export class SettingsRouterComponent { diff --git a/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts index 1f8de4437..a1b7116b4 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts @@ -4,7 +4,7 @@ import { AbstractMarketplaceService, StoreIconComponentModule, } from '@start9labs/marketplace' -import { TuiForModule } from '@taiga-ui/cdk' +import { TuiAvatarModule, TuiCellModule } from '@taiga-ui/experimental' import { PatchDB } from 'patch-db-client' import { combineLatest } from 'rxjs' import { MarketplaceService } from 'src/app/services/marketplace.service' @@ -12,66 +12,64 @@ import { DataModel } from 'src/app/services/patch-db/data-model' import { ConfigService } from 'src/app/services/config.service' import { FilterUpdatesPipe } from './pipes/filter-updates.pipe' import { UpdatesItemComponent } from './components/item.component' -import { SkeletonListComponent } from '../../../components/skeleton-list.component' @Component({ template: ` - -
+ @if (data$ | async; as data) { + @for (host of data.hosts; track host) {

- + {{ host.name }}

-

- Request Failed -

- -
-
-

All services are up to date!

- - - + @if (data.errors.includes(host.url)) { +

Request Failed

+ } + @if (data.mp[host.url]?.packages | filterUpdates: data.local; as pkgs) { + @for (pkg of pkgs; track pkg) { + + } @empty { +

All services are up to date!

+ } + } @else { + @for (i of [0, 1, 2]; track i) { +
+ + Loading update item + + Loading actions + +
+ } + } + } + } `, host: { class: 'g-page' }, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ CommonModule, - TuiForModule, + TuiCellModule, + TuiAvatarModule, StoreIconComponentModule, FilterUpdatesPipe, UpdatesItemComponent, - SkeletonListComponent, ], }) export default class UpdatesComponent { - private readonly marketplace = inject( + private readonly service = inject( AbstractMarketplaceService, ) as MarketplaceService - readonly config = inject(ConfigService) - + readonly mp = inject(ConfigService).marketplace readonly data$ = combineLatest({ - hosts: this.marketplace.getKnownHosts$(true), - mp: this.marketplace.getMarketplace$(), + hosts: this.service.getKnownHosts$(true), + mp: this.service.getMarketplace$(), local: inject(PatchDB).watch$('package-data'), - errors: this.marketplace.getRequestErrors$(), + errors: this.service.getRequestErrors$(), }) } diff --git a/web/projects/ui/src/app/common/install-progress/install-progress.module.ts b/web/projects/ui/src/app/common/install-progress/install-progress.module.ts deleted file mode 100644 index 68d0330b5..000000000 --- a/web/projects/ui/src/app/common/install-progress/install-progress.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from '@angular/core' -import { InstallProgressDisplayPipe } from './install-progress.pipe' - -@NgModule({ - declarations: [InstallProgressDisplayPipe], - exports: [InstallProgressDisplayPipe], -}) -export class InstallProgressPipeModule {} diff --git a/web/projects/ui/src/app/common/primary-ip/primary-ip.module.ts b/web/projects/ui/src/app/common/primary-ip/primary-ip.module.ts deleted file mode 100644 index 941518ab2..000000000 --- a/web/projects/ui/src/app/common/primary-ip/primary-ip.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from '@angular/core' -import { PrimaryIpPipe } from './primary-ip.pipe' - -@NgModule({ - declarations: [PrimaryIpPipe], - exports: [PrimaryIpPipe], -}) -export class PrimaryIpPipeModule {} diff --git a/web/projects/ui/src/app/common/primary-ip/primary-ip.pipe.ts b/web/projects/ui/src/app/common/primary-ip/primary-ip.pipe.ts deleted file mode 100644 index 4cfa98552..000000000 --- a/web/projects/ui/src/app/common/primary-ip/primary-ip.pipe.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { IpInfo } from '../../services/patch-db/data-model' - -@Pipe({ - name: 'primaryIp', -}) -export class PrimaryIpPipe implements PipeTransform { - transform(ipInfo: IpInfo): string { - return getPrimaryIp(ipInfo) - } -} - -export function getPrimaryIp(ipInfo: IpInfo): string { - return Object.values(ipInfo) - .filter(iface => iface.ipv4) - .sort((a, b) => (a.wireless ? -1 : 1))[0].ipv4! -} diff --git a/web/projects/ui/src/app/common/qr.component.ts b/web/projects/ui/src/app/common/qr.component.ts new file mode 100644 index 000000000..dee6cbb7e --- /dev/null +++ b/web/projects/ui/src/app/common/qr.component.ts @@ -0,0 +1,16 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { TuiDialogContext } from '@taiga-ui/core' +import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' +import { QrCodeModule } from 'ng-qrcode' + +@Component({ + standalone: true, + selector: 'qr', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [QrCodeModule], +}) +export class QRComponent { + readonly context = + inject>(POLYMORPHEUS_CONTEXT) +} diff --git a/web/projects/ui/src/app/common/qr/qr.component.ts b/web/projects/ui/src/app/common/qr/qr.component.ts deleted file mode 100644 index a87e43863..000000000 --- a/web/projects/ui/src/app/common/qr/qr.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' - -@Component({ - selector: 'qr', - template: '', -}) -export class QRComponent { - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - readonly context: TuiDialogContext, - ) {} -} diff --git a/web/projects/ui/src/app/common/qr/qr.module.ts b/web/projects/ui/src/app/common/qr/qr.module.ts deleted file mode 100644 index aa5086b28..000000000 --- a/web/projects/ui/src/app/common/qr/qr.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { QrCodeModule } from 'ng-qrcode' - -import { QRComponent } from './qr.component' - -@NgModule({ - declarations: [QRComponent], - imports: [CommonModule, QrCodeModule], - exports: [QRComponent], -}) -export class QRComponentModule {} diff --git a/web/projects/ui/src/app/app/sidebar-host.component.ts b/web/projects/ui/src/app/common/sidebar-host.component.ts similarity index 100% rename from web/projects/ui/src/app/app/sidebar-host.component.ts rename to web/projects/ui/src/app/common/sidebar-host.component.ts diff --git a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.html b/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.html deleted file mode 100644 index 5b875fa2c..000000000 --- a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.module.ts b/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.module.ts deleted file mode 100644 index 23e8b446e..000000000 --- a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { SkeletonListComponent } from './skeleton-list.component' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' - -@NgModule({ - declarations: [SkeletonListComponent], - imports: [CommonModule, IonicModule, RouterModule], - exports: [SkeletonListComponent], -}) -export class SkeletonListComponentModule {} diff --git a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.ts b/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.ts deleted file mode 100644 index 4f6c369d0..000000000 --- a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, Input } from '@angular/core' - -@Component({ - selector: 'skeleton-list', - templateUrl: './skeleton-list.component.html', -}) -export class SkeletonListComponent { - @Input() groups = 0 - @Input() rows = 3 - @Input() showAvatar = false - groupsArr: number[] = [] - rowsArr: number[] = [] - - ngOnInit() { - this.groupsArr = Array(this.groups).fill(0) - this.rowsArr = Array(this.rows).fill(0) - } -} diff --git a/web/projects/ui/src/app/common/svg-definitions.component.ts b/web/projects/ui/src/app/common/svg-definitions.component.ts new file mode 100644 index 000000000..36d6462b9 --- /dev/null +++ b/web/projects/ui/src/app/common/svg-definitions.component.ts @@ -0,0 +1,32 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' + +@Component({ + standalone: true, + selector: 'svg-definitions', + template: ` + + + + + + + + + + `, + styles: ` + :host { + position: absolute; + width: 0; + height: 0; + visibility: hidden; + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SvgDefinitionsComponent {} diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast.component.ts b/web/projects/ui/src/app/common/toast-container/notifications-toast.component.ts new file mode 100644 index 000000000..eceb6cc62 --- /dev/null +++ b/web/projects/ui/src/app/common/toast-container/notifications-toast.component.ts @@ -0,0 +1,42 @@ +import { AsyncPipe } from '@angular/common' +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { RouterLink } from '@angular/router' +import { TuiAlertModule } from '@taiga-ui/core' +import { PatchDB } from 'patch-db-client' +import { Observable, Subject, merge, pairwise, map, endWith } from 'rxjs' +import { DataModel } from 'src/app/services/patch-db/data-model' + +@Component({ + standalone: true, + selector: 'notifications-toast', + template: ` + + New notifications + View + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiAlertModule, RouterLink, AsyncPipe], +}) +export class NotificationsToastComponent { + private readonly dismiss$ = new Subject() + + readonly visible$: Observable = merge( + this.dismiss$, + inject(PatchDB) + .watch$('server-info', 'unreadNotifications', 'count') + .pipe( + pairwise(), + map(([prev, cur]) => cur > prev), + endWith(false), + ), + ) + + onDismiss() { + this.dismiss$.next(false) + } +} diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.html b/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.html deleted file mode 100644 index d75364715..000000000 --- a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.html +++ /dev/null @@ -1,8 +0,0 @@ - - New notifications - View - diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.ts b/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.ts deleted file mode 100644 index 65d4241c2..000000000 --- a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { Observable, Subject, merge } from 'rxjs' - -import { NotificationsToastService } from './notifications-toast.service' - -@Component({ - selector: 'notifications-toast', - templateUrl: './notifications-toast.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class NotificationsToastComponent { - private readonly dismiss$ = new Subject() - - readonly visible$: Observable = merge( - this.dismiss$, - this.notifications$, - ) - - constructor( - @Inject(NotificationsToastService) - private readonly notifications$: Observable, - ) {} - - onDismiss() { - this.dismiss$.next(false) - } -} diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.service.ts b/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.service.ts deleted file mode 100644 index 9dd3671f7..000000000 --- a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { endWith, map, pairwise, Observable } from 'rxjs' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Injectable({ providedIn: 'root' }) -export class NotificationsToastService extends Observable { - private readonly stream$ = this.patch - .watch$('server-info', 'unreadNotifications', 'count') - .pipe( - pairwise(), - map(([prev, cur]) => cur > prev), - endWith(false), - ) - - constructor(private readonly patch: PatchDB) { - super(subscriber => this.stream$.subscribe(subscriber)) - } -} diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert.component.ts b/web/projects/ui/src/app/common/toast-container/refresh-alert.component.ts new file mode 100644 index 000000000..49aabce15 --- /dev/null +++ b/web/projects/ui/src/app/common/toast-container/refresh-alert.component.ts @@ -0,0 +1,90 @@ +import { AsyncPipe } from '@angular/common' +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { SwUpdate } from '@angular/service-worker' +import { Emver, LoadingService } from '@start9labs/shared' +import { TuiAutoFocusModule } from '@taiga-ui/cdk' +import { TuiDialogModule } from '@taiga-ui/core' +import { TuiButtonModule } from '@taiga-ui/experimental' +import { PatchDB } from 'patch-db-client' +import { debounceTime, endWith, map, merge, Subject } from 'rxjs' +import { ConfigService } from 'src/app/services/config.service' +import { DataModel } from 'src/app/services/patch-db/data-model' + +@Component({ + standalone: true, + selector: 'refresh-alert', + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiDialogModule, AsyncPipe, TuiButtonModule, TuiAutoFocusModule], +}) +export class RefreshAlertComponent { + private readonly updates = inject(SwUpdate) + private readonly loader = inject(LoadingService) + private readonly emver = inject(Emver) + private readonly config = inject(ConfigService) + private readonly dismiss$ = new Subject() + + readonly show$ = merge( + this.dismiss$, + inject(PatchDB) + .watch$('server-info', 'version') + .pipe( + map(version => !!this.emver.compare(this.config.version, version)), + endWith(false), + ), + ).pipe(debounceTime(0)) + + // @TODO use this like we did on 0344 + onPwa = false + + ngOnInit() { + this.onPwa = window.matchMedia('(display-mode: standalone)').matches + } + + async pwaReload() { + const loader = this.loader.open('Reloading PWA...').subscribe() + + try { + // attempt to update to the latest client version available + await this.updates.activateUpdate() + } catch (e) { + console.error('Error activating update from service worker: ', e) + } finally { + loader.unsubscribe() + // always reload, as this resolves most out of sync cases + window.location.reload() + } + } + + onDismiss() { + this.dismiss$.next(false) + } +} diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.html b/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.html deleted file mode 100644 index cb412aae9..000000000 --- a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.ts b/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.ts deleted file mode 100644 index 708b9ca22..000000000 --- a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { Observable, Subject, merge, debounceTime } from 'rxjs' - -import { RefreshAlertService } from './refresh-alert.service' -import { SwUpdate } from '@angular/service-worker' -import { LoadingController } from '@ionic/angular' - -@Component({ - selector: 'refresh-alert', - templateUrl: './refresh-alert.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class RefreshAlertComponent { - private readonly dismiss$ = new Subject() - - readonly show$ = merge(this.dismiss$, this.refresh$).pipe(debounceTime(0)) - - // @TODO use this like we did on 0344 - onPwa = false - - constructor( - @Inject(RefreshAlertService) private readonly refresh$: Observable, - private readonly updates: SwUpdate, - private readonly loadingCtrl: LoadingController, - ) {} - - ngOnInit() { - this.onPwa = window.matchMedia('(display-mode: standalone)').matches - } - - async pwaReload() { - const loader = await this.loadingCtrl.create({ - message: 'Reloading PWA...', - }) - await loader.present() - try { - // attempt to update to the latest client version available - await this.updates.activateUpdate() - } catch (e) { - console.error('Error activating update from service worker: ', e) - } finally { - loader.dismiss() - // always reload, as this resolves most out of sync cases - window.location.reload() - } - } - - onDismiss() { - this.dismiss$.next(false) - } -} diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.service.ts b/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.service.ts deleted file mode 100644 index 134add795..000000000 --- a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable } from '@angular/core' -import { Emver } from '@start9labs/shared' -import { PatchDB } from 'patch-db-client' -import { endWith, map, Observable } from 'rxjs' -import { ConfigService } from 'src/app/services/config.service' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Injectable({ providedIn: 'root' }) -export class RefreshAlertService extends Observable { - private readonly stream$ = this.patch.watch$('server-info', 'version').pipe( - map(version => !!this.emver.compare(this.config.version, version)), - endWith(false), - ) - - constructor( - private readonly patch: PatchDB, - private readonly emver: Emver, - private readonly config: ConfigService, - ) { - super(subscriber => this.stream$.subscribe(subscriber)) - } -} diff --git a/web/projects/ui/src/app/common/toast-container/toast-container.component.html b/web/projects/ui/src/app/common/toast-container/toast-container.component.html deleted file mode 100644 index 3b7ca2cf1..000000000 --- a/web/projects/ui/src/app/common/toast-container/toast-container.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/projects/ui/src/app/common/toast-container/toast-container.component.ts b/web/projects/ui/src/app/common/toast-container/toast-container.component.ts index 161ddc076..edf74664f 100644 --- a/web/projects/ui/src/app/common/toast-container/toast-container.component.ts +++ b/web/projects/ui/src/app/common/toast-container/toast-container.component.ts @@ -1,8 +1,21 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' +import { NotificationsToastComponent } from './notifications-toast.component' +import { RefreshAlertComponent } from './refresh-alert.component' +import { UpdateToastComponent } from './update-toast.component' @Component({ + standalone: true, selector: 'toast-container', - templateUrl: './toast-container.component.html', + template: ` + + + + `, changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + NotificationsToastComponent, + UpdateToastComponent, + RefreshAlertComponent, + ], }) export class ToastContainerComponent {} diff --git a/web/projects/ui/src/app/common/toast-container/toast-container.module.ts b/web/projects/ui/src/app/common/toast-container/toast-container.module.ts deleted file mode 100644 index bf24027d3..000000000 --- a/web/projects/ui/src/app/common/toast-container/toast-container.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { RouterModule } from '@angular/router' -import { TuiAutoFocusModule } from '@taiga-ui/cdk' -import { TuiAlertModule, TuiDialogModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' - -import { ToastContainerComponent } from './toast-container.component' -import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component' -import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component' -import { UpdateToastComponent } from './update-toast/update-toast.component' - -@NgModule({ - imports: [ - CommonModule, - RouterModule, - TuiDialogModule, - TuiButtonModule, - TuiAutoFocusModule, - TuiAlertModule, - ], - declarations: [ - ToastContainerComponent, - NotificationsToastComponent, - RefreshAlertComponent, - UpdateToastComponent, - ], - exports: [ToastContainerComponent], -}) -export class ToastContainerModule {} diff --git a/web/projects/ui/src/app/common/toast-container/update-toast.component.ts b/web/projects/ui/src/app/common/toast-container/update-toast.component.ts new file mode 100644 index 000000000..6396398fa --- /dev/null +++ b/web/projects/ui/src/app/common/toast-container/update-toast.component.ts @@ -0,0 +1,79 @@ +import { AsyncPipe } from '@angular/common' +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { ErrorService, LoadingService } from '@start9labs/shared' +import { TuiAlertModule } from '@taiga-ui/core' +import { TuiButtonModule } from '@taiga-ui/experimental' +import { PatchDB } from 'patch-db-client' +import { + distinctUntilChanged, + endWith, + filter, + merge, + Observable, + Subject, +} from 'rxjs' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { DataModel } from 'src/app/services/patch-db/data-model' + +@Component({ + standalone: true, + selector: 'update-toast', + template: ` + + Restart your server for these updates to take effect. It can take several + minutes to come back online. +
+ +
+
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiButtonModule, TuiAlertModule, AsyncPipe], +}) +export class UpdateToastComponent { + private readonly api = inject(ApiService) + private readonly errorService = inject(ErrorService) + private readonly loader = inject(LoadingService) + private readonly dismiss$ = new Subject() + + readonly visible$: Observable = merge( + this.dismiss$, + inject(PatchDB) + .watch$('server-info', 'status-info', 'updated') + .pipe(distinctUntilChanged(), filter(Boolean), endWith(false)), + ) + + onDismiss() { + this.dismiss$.next(false) + } + + async restart(): Promise { + this.onDismiss() + + const loader = this.loader.open('Restarting...').subscribe() + + try { + await this.api.restartServer({}) + } catch (e: any) { + await this.errorService.handleError(e) + } finally { + await loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.html b/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.html deleted file mode 100644 index 8cd93b647..000000000 --- a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - Restart your server for these updates to take effect. It can take several - minutes to come back online. -
- -
-
diff --git a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.ts b/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.ts deleted file mode 100644 index 0b02faa4e..000000000 --- a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { Observable, Subject, merge } from 'rxjs' - -import { UpdateToastService } from './update-toast.service' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - selector: 'update-toast', - templateUrl: './update-toast.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class UpdateToastComponent { - private readonly dismiss$ = new Subject() - - readonly visible$: Observable = merge(this.dismiss$, this.update$) - - constructor( - @Inject(UpdateToastService) private readonly update$: Observable, - private readonly embassyApi: ApiService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - ) {} - - onDismiss() { - this.dismiss$.next(false) - } - - async restart(): Promise { - this.onDismiss() - - const loader = this.loader.open('Restarting...').subscribe() - - try { - await this.embassyApi.restartServer({}) - } catch (e: any) { - await this.errorService.handleError(e) - } finally { - await loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.service.ts b/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.service.ts deleted file mode 100644 index 77afbe736..000000000 --- a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable } from '@angular/core' -import { distinctUntilChanged, filter, endWith, Observable } from 'rxjs' -import { PatchDB } from 'patch-db-client' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Injectable({ providedIn: 'root' }) -export class UpdateToastService extends Observable { - private readonly stream$ = this.patch - .watch$('server-info', 'status-info', 'updated') - .pipe(distinctUntilChanged(), filter(Boolean), endWith(false)) - - constructor(private readonly patch: PatchDB) { - super(subscriber => this.stream$.subscribe(subscriber)) - } -} diff --git a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.html b/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.html deleted file mode 100644 index 67a2956f5..000000000 --- a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - diff --git a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.scss b/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.scss deleted file mode 100644 index 81759f278..000000000 --- a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -a { - text-decoration: none; - color: unset; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.ts b/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.ts deleted file mode 100644 index 0e8d6f67d..000000000 --- a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - Component, - Input, - ChangeDetectionStrategy, - OnInit, -} from '@angular/core' - -@Component({ - selector: 'any-link', - templateUrl: './any-link.component.html', - styleUrls: ['./any-link.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AnyLinkComponent implements OnInit { - @Input({ required: true }) link!: string - @Input() qp?: Record - externalLink = false - - ngOnInit() { - try { - const _ = new URL(this.link) - this.externalLink = true - } catch { - this.externalLink = false - } - } -} diff --git a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.html b/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.html deleted file mode 100644 index dec4bc5e1..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.html +++ /dev/null @@ -1,28 +0,0 @@ -
-
- - - - {{ cardDetails.title }} - - - - - -

{{ cardDetails.description }}

-
-
-
-
-
diff --git a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.scss b/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.scss deleted file mode 100644 index 687370f0f..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.scss +++ /dev/null @@ -1,68 +0,0 @@ -ion-card { - background: rgba(70, 70, 70, 0.31); - box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); - border-radius: 44px; - margin: auto; - max-height: 100%; - max-width: 100%; - text-align: center; - transition: all 350ms ease; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - - &:hover { - transition-property: transform; - transform: scale(1.05); - transition-delay: 40ms; - } - - ion-card-title { - font-family: 'Open Sans', sans-serif; - padding: 0.6rem; - font-weight: 600; - height: 2.4rem; - } - - ion-card-content { - min-height: 8rem; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - ion-icon { - font-size: calc(90px + 0.4vw); - --ionicon-stroke-width: 1rem; - } - } - - ion-footer { - padding: 0 1rem; - font-family: 'Open Sans'; - font-size: clamp(1rem, calc(12px + 0.5vw), 1.3rem); - height: 4.5rem; - width: clamp(13rem, 80%, 18rem); - margin: 0 auto; - * { - max-width: 100%; - } - p { - margin-top: 0; - } - } - - .footer-md::before { - background-image: none; - } -} - -@media (max-width: 900px) { - ion-footer { - width: 10rem; - } -} - -@media (max-width: 1200px) { - ion-footer { - width: 14rem; - } -} diff --git a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.ts b/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.ts deleted file mode 100644 index 5b1ba93e9..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - HostListener, - Input, - ViewChild, -} from '@angular/core' - -@Component({ - selector: 'widget-card', - templateUrl: './widget-card.component.html', - styleUrls: ['./widget-card.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class WidgetCardComponent { - @Input({ required: true }) cardDetails!: Card - @Input({ required: true }) containerDimensions!: Dimension - @ViewChild('outerWrapper') outerWrapper: ElementRef = - {} as ElementRef - @ViewChild('innerWrapper') innerWrapper: ElementRef = - {} as ElementRef - @HostListener('window:resize', ['$event']) - onResize() { - this.resize() - } - maxHeight = 0 - maxWidth = 0 - innerTransform = '' - outerWidth: any - outerHeight: any - - ngAfterViewInit() { - this.maxHeight = ( ( - this.innerWrapper.nativeElement - )).getBoundingClientRect().height - this.maxWidth = ( ( - this.innerWrapper.nativeElement - )).getBoundingClientRect().width - this.resize() - } - - resize() { - const height = this.containerDimensions.height - const width = this.containerDimensions.width - const isMax = width >= this.maxWidth && height >= this.maxHeight - const scale = Math.min(width / this.maxWidth, height / this.maxHeight) - this.innerTransform = isMax ? '' : 'scale(' + scale + ')' - this.outerWidth = isMax ? '' : this.maxWidth * scale - this.outerHeight = isMax ? '' : this.maxHeight * scale - } -} - -export interface Dimension { - height: number - width: number -} - -export interface Card { - title: string - icon: string - color: string - description: string - link: string - qp?: Record -} diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.html b/web/projects/ui/src/app/common/widget-list/widget-list.component.html deleted file mode 100644 index 2c013d1c3..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-list.component.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - - - - - - -
diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.module.ts b/web/projects/ui/src/app/common/widget-list/widget-list.component.module.ts deleted file mode 100644 index b75bad20b..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-list.component.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' -import { AnyLinkComponent } from './any-link/any-link.component' -import { WidgetListComponent } from './widget-list.component' -import { WidgetCardComponent } from './widget-card/widget-card.component' - -@NgModule({ - declarations: [WidgetListComponent, WidgetCardComponent, AnyLinkComponent], - imports: [CommonModule, IonicModule, RouterModule], - exports: [WidgetListComponent], -}) -export class WidgetListComponentModule {} diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.scss b/web/projects/ui/src/app/common/widget-list/widget-list.component.scss deleted file mode 100644 index 843e0d9a5..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-list.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -ion-col { - max-width: 22rem !important; - --ion-grid-column-padding: 1rem; -} - -@media (min-width: 1700px) { - div { - padding: 0 7%; - } - ion-col { - max-width: 24rem !important; - } -} - -@media (min-width: 2000px) { - div { - padding: 0 12%; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.ts b/web/projects/ui/src/app/common/widget-list/widget-list.component.ts deleted file mode 100644 index f6514e4ee..000000000 --- a/web/projects/ui/src/app/common/widget-list/widget-list.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - HostListener, - ViewChild, -} from '@angular/core' -import { Card, Dimension } from './widget-card/widget-card.component' - -@Component({ - selector: 'widget-list', - templateUrl: './widget-list.component.html', - styleUrls: ['./widget-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class WidgetListComponent { - @ViewChild('gridContent') - gridContent: ElementRef = {} as ElementRef - @HostListener('window:resize', ['$event']) - onResize() { - this.setContainerDimensions() - } - - containerDimensions: Dimension = {} as Dimension - - ngAfterViewInit() { - this.setContainerDimensions() - } - - setContainerDimensions() { - this.containerDimensions.height = ( ( - this.gridContent.nativeElement - )).getBoundingClientRect().height - this.containerDimensions.width = ( ( - this.gridContent.nativeElement - )).getBoundingClientRect().width - } - - cards: Card[] = [ - { - title: 'Server Info', - icon: 'information-circle-outline', - color: 'var(--alt-green)', - description: 'View information about your server', - link: '/system/specs', - }, - { - title: 'Browse', - icon: 'storefront-outline', - color: 'var(--alt-purple)', - description: 'Browse for services to install', - link: '/marketplace', - qp: { back: 'true' }, - }, - { - title: 'Create Backup', - icon: 'duplicate-outline', - color: 'var(--alt-blue)', - description: 'Back up StartOS and service data', - link: '/system/backup', - }, - { - title: 'Monitor', - icon: 'pulse-outline', - color: 'var(--alt-orange)', - description: `View your system resource usage`, - link: '/system/metrics', - }, - { - title: 'User Manual', - icon: 'map-outline', - color: 'var(--alt-yellow)', - description: 'Discover what StartOS can do', - link: 'https://docs.start9.com/0.3.5.x/user-manual/index', - }, - { - title: 'Contact Support', - icon: 'chatbubbles-outline', - color: 'var(--alt-red)', - description: 'Get help from the Start9 community', - link: 'https://start9.com/contact', - }, - ] -} diff --git a/web/projects/ui/src/app/route-animation.ts b/web/projects/ui/src/app/route-animation.ts deleted file mode 100644 index 17d6e2b75..000000000 --- a/web/projects/ui/src/app/route-animation.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - animate, - group, - query, - style, - transition, - trigger, -} from '@angular/animations' -export const slideInAnimation = trigger('routeAnimations', [ - transition('* => *', [ - query(':enter, :leave', style({ position: 'fixed', width: '100%' }), { - optional: true, - }), - group([ - query( - ':enter', - [ - style({ transform: 'translateX(-100%)' }), - animate('1s ease-in-out', style({ transform: 'translateX(0%)' })), - ], - { optional: true }, - ), - query( - ':leave', - [ - style({ transform: 'translateX(0%)' }), - animate('1s ease-in-out', style({ transform: 'translateX(100%)' })), - ], - { optional: true }, - ), - ]), - ]), -])