From 513fb3428a2573516904abe9031ae056faf176ce Mon Sep 17 00:00:00 2001 From: Alex Inkin Date: Tue, 13 Feb 2024 20:03:09 +0400 Subject: [PATCH] feat: implement mobile header (#2559) * feat: implement mobile header * chore: remove remaining ties to old ui project * chore: remove ionic from login page * chore: address comments --- web/package-lock.json | 195 ++++++------ web/package.json | 17 +- web/projects/marketplace/package.json | 1 - .../store-icon/store-icon.component.html | 10 - .../store-icon/store-icon.component.module.ts | 4 +- .../store-icon/store-icon.component.scss | 0 .../store-icon/store-icon.component.ts | 14 +- .../list/categories/categories.module.ts | 3 +- .../list/skeleton/skeleton.component.html | 69 ---- .../pages/list/skeleton/skeleton.component.ts | 8 - .../pages/list/skeleton/skeleton.module.ts | 13 - .../release-notes.component.html | 6 +- .../release-notes/release-notes.module.ts | 17 +- .../show/screenshots/screenshots.component.ts | 11 +- .../initializing/initializing.component.html | 43 +-- .../initializing/initializing.component.scss | 23 +- .../initializing/initializing.module.ts | 4 +- .../markdown/markdown.component.html | 10 +- .../markdown/markdown.component.module.ts | 7 +- web/projects/shared/styles/taiga.scss | 1 + web/projects/ui/src/app/app.module.ts | 51 ++- web/projects/ui/src/app/app.providers.ts | 22 +- .../ui/src/app/apps/loading/loading.module.ts | 4 +- .../ui/src/app/apps/loading/loading.page.html | 4 - .../ui/src/app/apps/loading/loading.page.ts | 10 +- .../login/ca-wizard/ca-wizard.component.html | 202 ++++++------ .../login/ca-wizard/ca-wizard.component.scss | 91 ++---- .../login/ca-wizard/ca-wizard.component.ts | 43 +-- .../ui/src/app/apps/login/login.module.ts | 31 +- .../ui/src/app/apps/login/login.page.html | 115 ++----- .../ui/src/app/apps/login/login.page.scss | 85 ++--- .../ui/src/app/apps/login/login.page.ts | 8 - .../src/app/apps/login/warning.component.ts | 60 ++++ .../apps/portal/components/form.component.ts | 166 ++++++++++ .../components/header/connection.component.ts | 14 + .../components/header/header.component.ts | 97 +++++- .../components/header/menu.component.ts | 17 +- .../components/header/mobile.component.ts | 68 ++++ .../components/header/snek.component.ts | 300 ++++++++++++++++++ .../components/header/snek.directive.ts | 47 +++ .../apps/portal/modals/prompt.component.ts | 124 ++++++++ .../routes/service/modals/config.component.ts | 16 +- .../service/routes/actions.component.ts | 4 +- .../backups/components/physical.component.ts | 4 +- .../backups/modals/targets.component.ts | 10 +- .../backups/services/restore.service.ts | 6 +- .../routes/system/backups/types/target.ts | 121 +++++++ .../components/sidebars.component.ts | 2 +- .../marketplace/modals/registry.component.ts | 4 +- .../routes/domains/domains.component.ts | 18 +- .../settings/routes/email/email.component.ts | 42 ++- .../settings/routes/proxies/menu.component.ts | 7 +- .../routes/proxies/proxies.component.ts | 7 +- .../settings/routes/ssh/table.component.ts | 6 +- .../settings/routes/wifi/table.component.ts | 7 +- .../settings/routes/wifi/wifi.component.ts | 13 +- .../system/settings/settings.service.ts | 6 +- .../routes/system/snek/snek.component.ts | 8 - .../portal/routes/system/system.module.ts | 6 - .../portal/services/breadcrumbs.service.ts | 2 +- .../app/services/patch-db/patch-db.module.ts | 20 -- .../services/patch-db/patch-db.providers.ts | 16 + web/projects/ui/src/styles.scss | 10 +- 63 files changed, 1551 insertions(+), 799 deletions(-) delete mode 100644 web/projects/marketplace/src/components/store-icon/store-icon.component.html delete mode 100644 web/projects/marketplace/src/components/store-icon/store-icon.component.scss delete mode 100644 web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html delete mode 100644 web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts delete mode 100644 web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts delete mode 100644 web/projects/ui/src/app/apps/loading/loading.page.html create mode 100644 web/projects/ui/src/app/apps/login/warning.component.ts create mode 100644 web/projects/ui/src/app/apps/portal/components/form.component.ts create mode 100644 web/projects/ui/src/app/apps/portal/components/header/mobile.component.ts create mode 100644 web/projects/ui/src/app/apps/portal/components/header/snek.component.ts create mode 100644 web/projects/ui/src/app/apps/portal/components/header/snek.directive.ts create mode 100644 web/projects/ui/src/app/apps/portal/modals/prompt.component.ts create mode 100644 web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts delete mode 100644 web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts delete mode 100644 web/projects/ui/src/app/services/patch-db/patch-db.module.ts create mode 100644 web/projects/ui/src/app/services/patch-db/patch-db.providers.ts diff --git a/web/package-lock.json b/web/package-lock.json index f77d5be5e..0a28b4f8f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -24,14 +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.57.0", - "@taiga-ui/addon-mobile": "3.57.0", - "@taiga-ui/cdk": "3.57.0", - "@taiga-ui/core": "3.57.0", - "@taiga-ui/experimental": "3.57.0", - "@taiga-ui/icons": "3.57.0", - "@taiga-ui/kit": "3.57.0", - "@taiga-ui/styles": "3.57.0", + "@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", "@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-event-plugins": "3.1.0", "ansi-to-html": "^0.7.2", @@ -3983,9 +3984,9 @@ } }, "node_modules/@ng-web-apis/intersection-observer": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.1.6.tgz", - "integrity": "sha512-Pzk0ycnYpq+EUf60kz+/A7nvCmhYzThc4ArwONwZzJqRF5xOS97CVWObs8hesorXxQdqlsrDNiu+JWuGxEvpzQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.0.tgz", + "integrity": "sha512-EhwqEZJFKR9pz55TWp82qyWTXdg8TZeMP6bUw26bVHz8CTkgrpzaXdtxurMTvJ/+gwuFy4JSJLjBeV9nfZ/SXA==", "dependencies": { "tslib": "^2.2.0" }, @@ -3995,9 +3996,9 @@ } }, "node_modules/@ng-web-apis/mutation-observer": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.0.6.tgz", - "integrity": "sha512-UW1qoUi2whH0uWkVz5qpdYCLs1u2T0E0QoCMQKZfLEkBpsWRTkT0PTCa9WWX/BhehaSPg23nZZm8BEixd6PI9w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.1.0.tgz", + "integrity": "sha512-MFN0TLLBMFJJPpXkGFe9ChRCSOKvMHZRRtBq5jHWS7tv5/CtdUkqW5CU7RC9KTzZjGeMzYe0cXO4JRkjL5aZ9g==", "dependencies": { "tslib": "^2.2.0" }, @@ -4671,9 +4672,9 @@ } }, "node_modules/@taiga-ui/addon-charts": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.57.0.tgz", - "integrity": "sha512-/x/yVHafSmNA3GSR9cSo6KSkzDNzDGew0JCD5cCNOb6vRZOe1QiXUQwMHvdj9eZlrjZzbV67eiV13NNvWolGwg==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.65.0.tgz", + "integrity": "sha512-HNKUeK0ippIvLRF6wsuCiyJ4d98K4uIhkGwK1fWaTVOCN26Z+AnFKk9AryTyhocEZctyc4PMpJ7BP7h3CA4dZA==", "dependencies": { "tslib": "2.6.2" }, @@ -4681,16 +4682,15 @@ "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@ng-web-apis/common": "3.0.6", - "@taiga-ui/cdk": ">=3.57.0", - "@taiga-ui/core": ">=3.57.0", + "@taiga-ui/cdk": "^3.65.0", + "@taiga-ui/core": "^3.65.0", "@tinkoff/ng-polymorpheus": "4.3.0" } }, "node_modules/@taiga-ui/addon-commerce": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.57.0.tgz", - "integrity": "sha512-9msjwr/8tzlmbcv6MDxZTYvW9+sbURAq2Olrs4zJhHvw69tA3XYt2MCrDYUSrej/oj5jE2GFjtyio7wqQviLzA==", - "peer": true, + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.65.0.tgz", + "integrity": "sha512-D98M3nkPKVFz9TFiMxCmMtmJs9vDc69RlPv5M03ZF+qXHqbthfpVss/p2MSzs4Cr2vgoECaZWPLNcWBOO5mzCw==", "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.57.0", - "@taiga-ui/core": ">=3.57.0", - "@taiga-ui/i18n": ">=3.57.0", - "@taiga-ui/kit": ">=3.57.0", + "@taiga-ui/cdk": "^3.65.0", + "@taiga-ui/core": "^3.65.0", + "@taiga-ui/i18n": "^3.65.0", + "@taiga-ui/kit": "^3.65.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/addon-mobile": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.57.0.tgz", - "integrity": "sha512-fxtmOqf8qcWbDeKKHukP+Iw0Ida8onKlg0L4UkeqF5AHT1f9QnpDtIQxyEUw4DXwUQdFhMT+64FWeA4acwa+kA==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.65.0.tgz", + "integrity": "sha512-nKEf5Lb7yfR7vqkAIQQLoUEzSpKftdPpAsmco6FNfN4FDlvDFYTKE8MqqXAxzEqrXviDXv8/CKPv+nc6xd4VXg==", "dependencies": { "tslib": "2.6.2" }, @@ -4722,27 +4722,27 @@ "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@ng-web-apis/common": "3.0.6", - "@taiga-ui/cdk": ">=3.57.0", - "@taiga-ui/core": ">=3.57.0", - "@taiga-ui/kit": ">=3.57.0", + "@taiga-ui/cdk": "^3.65.0", + "@taiga-ui/core": "^3.65.0", + "@taiga-ui/kit": "^3.65.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/cdk": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.57.0.tgz", - "integrity": "sha512-igfgPZh7sUaElX4dehDPkbPL66LFc6qmirfEQ6f2deNnezYm4EZTTIdHebU1ibiKTqWBxWVTHKC2pQ3nxhwkNA==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.65.0.tgz", + "integrity": "sha512-hiFC9RlRng7pUv84YPZbqieKIYsFEzsMKCjMIckHBASBBU6qQ4OY6irKszFvTGqMe9KJgBh6sJU1hkQOBwFSaA==", "dependencies": { "@ng-web-apis/common": "3.0.6", - "@ng-web-apis/mutation-observer": "3.0.6", + "@ng-web-apis/mutation-observer": "3.1.0", "@ng-web-apis/resize-observer": "3.0.6", "@tinkoff/ng-event-plugins": "3.1.0", "@tinkoff/ng-polymorpheus": "4.3.0", "tslib": "2.6.2" }, "optionalDependencies": { - "ng-morph": "4.0.3", + "ng-morph": "4.0.5", "parse5": "6.0.1" }, "peerDependencies": { @@ -4760,11 +4760,11 @@ "optional": true }, "node_modules/@taiga-ui/core": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.57.0.tgz", - "integrity": "sha512-RUO//9C9+CdEPX1nV0hKRhKu3vmuAx+miszZHv5LAITK3jpTJKpd2jQ2Ib2z5O20v5vKrwkJ0DawT7EWU2v2TA==", + "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==", "dependencies": { - "@taiga-ui/i18n": "^3.57.0", + "@taiga-ui/i18n": "^3.65.0", "tslib": "2.6.2" }, "peerDependencies": { @@ -4775,36 +4775,36 @@ "@angular/platform-browser": ">=12.0.0", "@angular/router": ">=12.0.0", "@ng-web-apis/common": "3.0.6", - "@ng-web-apis/mutation-observer": "3.0.6", - "@taiga-ui/cdk": ">=3.57.0", - "@taiga-ui/i18n": ">=3.57.0", + "@ng-web-apis/mutation-observer": "3.1.0", + "@taiga-ui/cdk": "^3.65.0", + "@taiga-ui/i18n": "^3.65.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.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.57.0.tgz", - "integrity": "sha512-A0u/Cn0tHUjl6sTSfF6G9YvzuEJafBoVbHuDpw1mnSOw/TkWLXpkoYzeyJ6U9+nBIFBOSl4o+14olrYvsUg9CA==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.65.0.tgz", + "integrity": "sha512-LZYR+XeJ2n+vE4AHBiIolzlqDrDGUx/bmE0ypmKO7dPgvHWu5Al8OXRrnhyqmAVO48FNpkSZ07YoqCG/aoxu6g==", "dependencies": { "tslib": "2.6.2" }, "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", - "@taiga-ui/addon-commerce": ">=3.57.0", - "@taiga-ui/cdk": ">=3.57.0", - "@taiga-ui/core": ">=3.57.0", - "@taiga-ui/kit": ">=3.57.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", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/i18n": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.57.0.tgz", - "integrity": "sha512-FhE3eMD5g+i0/SbRgq4zoA7pBVY1mw4/gwndOJvCFkcWhjYxOJXJ4g/sjTyQmZ4QXmONM0OeIOMBxuMMCeJkGQ==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.65.0.tgz", + "integrity": "sha512-lHy9VDKc5IXbm40eJnnAyOlmm3vDgmWhGbr5woGe9bV/tTqsBBDATY7Rkhz7Bu1nbX7X+MI0TDfQh9ayoCCKRQ==", "dependencies": { "tslib": "2.6.2" }, @@ -4815,25 +4815,25 @@ } }, "node_modules/@taiga-ui/icons": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.57.0.tgz", - "integrity": "sha512-uXch0AX8DQMCrv2ldQIYmUUjMmhP2tgOLxX08GrPZCWsz6zHq8stgUJR6kgvZYwO1JZSFlwvCgv/32MoDEdI3w==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.65.0.tgz", + "integrity": "sha512-8iE6EuK+QBzcNiRM1ThZOOkZpal7V6dBouMXMj+QphRWiIp8Znj58mtY3L+uwQFpGnxt3DRs4p4eEA9ZuGFssw==", "dependencies": { "tslib": "2.6.2" }, "peerDependencies": { - "@taiga-ui/cdk": ">=3.57.0" + "@taiga-ui/cdk": "^3.65.0" } }, "node_modules/@taiga-ui/kit": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.57.0.tgz", - "integrity": "sha512-vZNMKlPku5E1wCiPdRV3GbNKba4molyd6F2Zc4EMJvHAlWybFpVPQOw67Z7VsSLGwWpzZXBC+5M5Ov3UNOUSJQ==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.65.0.tgz", + "integrity": "sha512-Nh6pMSAFR7yScF7acj8WdCpKQUgDatW2jObqts0z4hy9BJ8gl9BAWRBgSlbp3Oen5c2WAIC316Gb9OcttC8nbw==", "dependencies": { "@maskito/angular": "1.9.0", "@maskito/core": "1.9.0", "@maskito/kit": "1.9.0", - "@ng-web-apis/intersection-observer": "3.1.6", + "@ng-web-apis/intersection-observer": "3.2.0", "text-mask-core": "5.1.2", "tslib": "2.6.2" }, @@ -4843,21 +4843,21 @@ "@angular/forms": ">=12.0.0", "@angular/router": ">=12.0.0", "@ng-web-apis/common": "3.0.6", - "@ng-web-apis/mutation-observer": "3.0.6", + "@ng-web-apis/mutation-observer": "3.1.0", "@ng-web-apis/resize-observer": "3.0.6", - "@taiga-ui/cdk": ">=3.57.0", - "@taiga-ui/core": ">=3.57.0", - "@taiga-ui/i18n": ">=3.57.0", + "@taiga-ui/cdk": "^3.65.0", + "@taiga-ui/core": "^3.65.0", + "@taiga-ui/i18n": "^3.65.0", "@tinkoff/ng-polymorpheus": "4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/styles": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.57.0.tgz", - "integrity": "sha512-jxSAZHL+QrjTHH5tEQ2EJXmNpRvKUN/8fHh5Fw+O71Y50I9IENN2CVXdB01YmjtNvn0tQE2sUy1RFausIQLyBA==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.65.0.tgz", + "integrity": "sha512-HO2sZPxNOGj2BPQpWkrM6HgZV/QxaEMEemye3sJvsfuttvk6bmxoL8NF331I63tlp/Zx7woD8AusH5ATuUniqg==", "peerDependencies": { - "@taiga-ui/cdk": ">=3.57.0", + "@taiga-ui/cdk": "^3.65.0", "tslib": "2.6.2" } }, @@ -4910,36 +4910,37 @@ } }, "node_modules/@ts-morph/common": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.21.0.tgz", - "integrity": "sha512-ES110Mmne5Vi4ypUKrtVQfXFDtCsDXiUiGxF6ILVlE90dDD4fdpC1LSjydl/ml7xJWKSDZwUYD2zkOePMSrPBA==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", + "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", "optional": true, "dependencies": { - "fast-glob": "^3.2.12", - "minimatch": "^7.4.3", - "mkdirp": "^2.1.6", + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", "path-browserify": "^1.0.1" } }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "node_modules/@ts-morph/common/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "optional": true, "dependencies": { - "brace-expansion": "^2.0.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8.6.0" } }, "node_modules/@ts-morph/common/node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "optional": true, "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -11981,15 +11982,15 @@ } }, "node_modules/ng-morph": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.3.tgz", - "integrity": "sha512-4voBApzsUs0/1zJsV2sSVPoOKxWt0gBd+3yqE/q8oYOX87RN3HwcJmqQLtWEU4tbmhk11gSRIFIl61+z32cnNw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.5.tgz", + "integrity": "sha512-5tnlb5WrGKeo2E7VRcV7ZHhScyNgliYqpbXqt103kynmfj6Ic8kzhJAhHu9iLkF1yRnKv2kyCE+O7UGZx5RraQ==", "optional": true, "dependencies": { "jsonc-parser": "3.2.0", "minimatch": "9.0.3", "multimatch": "5.0.0", - "ts-morph": "20.0.0", + "ts-morph": "21.0.1", "tslib": "2.6.2" }, "peerDependencies": { @@ -16605,12 +16606,12 @@ "integrity": "sha512-kXrY75F0s0WD15N2bWKDScKlKgwnusN6dTRzGs1N7LlxQRnazrsBISC1HL4sy2adsyk65Zbx3Ui3IGN8leAFOQ==" }, "node_modules/ts-morph": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-20.0.0.tgz", - "integrity": "sha512-JVmEJy2Wow5n/84I3igthL9sudQ8qzjh/6i4tmYCm6IqYyKFlNbJZi7oBdjyqcWSWYRu3CtL0xbT6fS03ESZIg==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", + "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", "optional": true, "dependencies": { - "@ts-morph/common": "~0.21.0", + "@ts-morph/common": "~0.22.0", "code-block-writer": "^12.0.0" } }, diff --git a/web/package.json b/web/package.json index d2a401bd3..a6498057f 100644 --- a/web/package.json +++ b/web/package.json @@ -46,14 +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.57.0", - "@taiga-ui/addon-mobile": "3.57.0", - "@taiga-ui/cdk": "3.57.0", - "@taiga-ui/core": "3.57.0", - "@taiga-ui/experimental": "3.57.0", - "@taiga-ui/icons": "3.57.0", - "@taiga-ui/kit": "3.57.0", - "@taiga-ui/styles": "3.57.0", + "@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", "@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-event-plugins": "3.1.0", "ansi-to-html": "^0.7.2", diff --git a/web/projects/marketplace/package.json b/web/projects/marketplace/package.json index 87d7c574d..8df1f559b 100644 --- a/web/projects/marketplace/package.json +++ b/web/projects/marketplace/package.json @@ -4,7 +4,6 @@ "peerDependencies": { "@angular/common": ">=13.2.0", "@angular/core": ">=13.2.0", - "@ionic/angular": ">=6.0.0", "@start9labs/shared": ">=0.3.2", "@taiga-ui/cdk": ">=3.0.0", "@tinkoff/ng-dompurify": ">=4.0.0", diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.html b/web/projects/marketplace/src/components/store-icon/store-icon.component.html deleted file mode 100644 index 2000f287d..000000000 --- a/web/projects/marketplace/src/components/store-icon/store-icon.component.html +++ /dev/null @@ -1,10 +0,0 @@ -Service Icon - - - diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts b/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts index 5006663eb..e9d24c022 100644 --- a/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts +++ b/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts @@ -1,11 +1,11 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' +import { TuiIconModule } from '@taiga-ui/experimental' import { StoreIconComponent } from './store-icon.component' @NgModule({ declarations: [StoreIconComponent], - imports: [CommonModule, IonicModule], + imports: [CommonModule, TuiIconModule], exports: [StoreIconComponent], }) export class StoreIconComponentModule {} diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.scss b/web/projects/marketplace/src/components/store-icon/store-icon.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.ts b/web/projects/marketplace/src/components/store-icon/store-icon.component.ts index b8e13c7ad..97e0088ea 100644 --- a/web/projects/marketplace/src/components/store-icon/store-icon.component.ts +++ b/web/projects/marketplace/src/components/store-icon/store-icon.component.ts @@ -3,8 +3,18 @@ import { MarketplaceConfig, sameUrl } from '@start9labs/shared' @Component({ selector: 'store-icon', - templateUrl: './store-icon.component.html', - styleUrls: ['./store-icon.component.scss'], + template: ` + Service Icon + + + + `, changeDetection: ChangeDetectionStrategy.OnPush, }) export class StoreIconComponent { diff --git a/web/projects/marketplace/src/pages/list/categories/categories.module.ts b/web/projects/marketplace/src/pages/list/categories/categories.module.ts index b792ae891..567a16467 100644 --- a/web/projects/marketplace/src/pages/list/categories/categories.module.ts +++ b/web/projects/marketplace/src/pages/list/categories/categories.module.ts @@ -1,11 +1,10 @@ import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' -import { IonicModule } from '@ionic/angular' import { CategoriesComponent } from './categories.component' @NgModule({ - imports: [CommonModule, IonicModule], + imports: [CommonModule], declarations: [CategoriesComponent], exports: [CategoriesComponent], }) diff --git a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html b/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html deleted file mode 100644 index 0e8fdfa2a..000000000 --- a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html +++ /dev/null @@ -1,69 +0,0 @@ -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts b/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts deleted file mode 100644 index 8903a8e3e..000000000 --- a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - selector: 'marketplace-skeleton', - templateUrl: 'skeleton.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class SkeletonComponent {} diff --git a/web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts b/web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts deleted file mode 100644 index 9df2a3596..000000000 --- a/web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { IonicModule } from '@ionic/angular' -import { ResponsiveColDirective } from '@start9labs/shared' - -import { SkeletonComponent } from './skeleton.component' - -@NgModule({ - imports: [CommonModule, IonicModule, ResponsiveColDirective], - declarations: [SkeletonComponent], - exports: [SkeletonComponent], -}) -export class SkeletonModule {} diff --git a/web/projects/marketplace/src/pages/release-notes/release-notes.component.html b/web/projects/marketplace/src/pages/release-notes/release-notes.component.html index bb9e750e1..b4d6baa79 100644 --- a/web/projects/marketplace/src/pages/release-notes/release-notes.component.html +++ b/web/projects/marketplace/src/pages/release-notes/release-notes.component.html @@ -10,7 +10,7 @@ > Latest Release  -  - {{ published | date : 'medium' }} + {{ published | date: 'medium' }}

@@ -41,7 +41,7 @@ {{ note.key | displayEmver }} @@ -53,6 +53,6 @@ - + diff --git a/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts b/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts index 88d5b074e..816b0c709 100644 --- a/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts +++ b/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts @@ -1,26 +1,25 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' import { EmverPipesModule, MarkdownPipeModule, SafeLinksDirective, - TextSpinnerComponentModule, -} from "@start9labs/shared"; -import { NgDompurifyModule } from "@tinkoff/ng-dompurify"; -import { ReleaseNotesComponent } from "./release-notes.component"; -import { TuiButtonModule } from "@taiga-ui/core"; -import { TuiAccordionModule } from "@taiga-ui/kit"; +} from '@start9labs/shared' +import { TuiAccordionModule } from '@taiga-ui/kit' +import { TuiButtonModule, TuiLoaderModule } from '@taiga-ui/core' +import { NgDompurifyModule } from '@tinkoff/ng-dompurify' +import { ReleaseNotesComponent } from './release-notes.component' @NgModule({ imports: [ CommonModule, - TextSpinnerComponentModule, EmverPipesModule, MarkdownPipeModule, NgDompurifyModule, SafeLinksDirective, TuiButtonModule, TuiAccordionModule, + TuiLoaderModule, ], declarations: [ReleaseNotesComponent], exports: [ReleaseNotesComponent], diff --git a/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts b/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts index 28d748f8e..a7d3c4fb2 100644 --- a/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts +++ b/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts @@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, - Inject, + inject, Input, } from '@angular/core' +import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { TuiButtonModule, TuiDialogContext, @@ -13,7 +14,6 @@ import { import { TuiCarouselModule } from '@taiga-ui/kit' import { MarketplacePkg } from '../../../types' import { PolymorpheusContent } from '@tinkoff/ng-polymorpheus' -import { isPlatform } from '@ionic/angular' @Component({ selector: 'marketplace-package-screenshots', @@ -77,15 +77,14 @@ import { isPlatform } from '@ionic/angular' imports: [CommonModule, TuiCarouselModule, TuiButtonModule], }) export class MarketplacePackageScreenshotComponent { + private readonly dialogs = inject(TuiDialogService) + @Input({ required: true }) pkg!: MarketplacePkg - constructor( - @Inject(TuiDialogService) private readonly dialogs: TuiDialogService, - ) {} index = 0 - isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android') + isMobile = inject(TUI_IS_MOBILE) presentModalImg(content: PolymorpheusContent) { this.dialogs diff --git a/web/projects/shared/src/components/initializing/initializing.component.html b/web/projects/shared/src/components/initializing/initializing.component.html index 0722943bf..836caf38c 100644 --- a/web/projects/shared/src/components/initializing/initializing.component.html +++ b/web/projects/shared/src/components/initializing/initializing.component.html @@ -1,32 +1,15 @@ - - - - - - - Initializing StartOS -
- - Progress: {{ (progress * 100).toFixed(0) }}% - -
-
+
+

Initializing StartOS

+
+ Progress: {{ (progress * 100).toFixed(0) }}% +
- - -

{{ getMessage(progress) }}

-
- + +

{{ getMessage(progress) }}

+
- -
-
-
-
+ diff --git a/web/projects/shared/src/components/initializing/initializing.component.scss b/web/projects/shared/src/components/initializing/initializing.component.scss index e394fa18e..0675add7d 100644 --- a/web/projects/shared/src/components/initializing/initializing.component.scss +++ b/web/projects/shared/src/components/initializing/initializing.component.scss @@ -1,11 +1,22 @@ -ion-card-title { - font-size: 42px; +.card { + border-radius: 0.25rem; + padding: 1rem; + margin: 1.5rem; + text-align: center; + // TODO: Theme + background: #e0e0e0; + color: #333; + --tui-clear-inverse: rgba(0, 0, 0, 0.1); +} + +.title { + font-size: 2.5rem; + margin: 1rem; } .progress { - max-width: 700px; - padding-bottom: 20px; - margin: auto auto 40px; + max-width: 40rem; + margin: 1rem auto; } .logs { @@ -13,7 +24,7 @@ ion-card-title { flex-direction: column; height: 18rem; padding: 1rem; - margin: 1.5rem 0.75rem; + margin: 0 1.5rem auto; text-align: left; overflow: hidden; border-radius: 2rem; diff --git a/web/projects/shared/src/components/initializing/initializing.module.ts b/web/projects/shared/src/components/initializing/initializing.module.ts index c72a7e978..93c5c99d4 100644 --- a/web/projects/shared/src/components/initializing/initializing.module.ts +++ b/web/projects/shared/src/components/initializing/initializing.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' import { TuiLetModule } from '@taiga-ui/cdk' +import { TuiProgressModule } from '@taiga-ui/kit' import { LogsWindowComponent } from './logs-window.component' import { InitializingComponent } from './initializing.component' @NgModule({ - imports: [CommonModule, IonicModule, TuiLetModule, LogsWindowComponent], + imports: [CommonModule, TuiLetModule, LogsWindowComponent, TuiProgressModule], declarations: [InitializingComponent], exports: [InitializingComponent], }) diff --git a/web/projects/shared/src/components/markdown/markdown.component.html b/web/projects/shared/src/components/markdown/markdown.component.html index 45271946d..8e5e7b026 100644 --- a/web/projects/shared/src/components/markdown/markdown.component.html +++ b/web/projects/shared/src/components/markdown/markdown.component.html @@ -1,8 +1,6 @@ - - - {{ error }} - - + + {{ error }} +
- + diff --git a/web/projects/shared/src/components/markdown/markdown.component.module.ts b/web/projects/shared/src/components/markdown/markdown.component.module.ts index e643ee2e8..ddb1722bc 100644 --- a/web/projects/shared/src/components/markdown/markdown.component.module.ts +++ b/web/projects/shared/src/components/markdown/markdown.component.module.ts @@ -1,22 +1,21 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' +import { TuiLoaderModule, TuiNotificationModule } from '@taiga-ui/core' import { NgDompurifyModule } from '@tinkoff/ng-dompurify' import { MarkdownPipeModule } from '../../pipes/markdown/markdown.module' import { SafeLinksDirective } from '../../directives/safe-links.directive' -import { TextSpinnerComponentModule } from '../text-spinner/text-spinner.component.module' import { MarkdownComponent } from './markdown.component' @NgModule({ declarations: [MarkdownComponent], imports: [ CommonModule, - IonicModule, MarkdownPipeModule, - TextSpinnerComponentModule, SafeLinksDirective, NgDompurifyModule, + TuiLoaderModule, + TuiNotificationModule, ], exports: [MarkdownComponent], }) diff --git a/web/projects/shared/styles/taiga.scss b/web/projects/shared/styles/taiga.scss index f2566adfa..ed89a5be9 100644 --- a/web/projects/shared/styles/taiga.scss +++ b/web/projects/shared/styles/taiga.scss @@ -40,6 +40,7 @@ [tuiAppearance][data-appearance='primary'] { @include appearance-disabled { background: #eaecee; + color: #333; } } diff --git a/web/projects/ui/src/app/app.module.ts b/web/projects/ui/src/app/app.module.ts index e573debd4..1f4ae70f1 100644 --- a/web/projects/ui/src/app/app.module.ts +++ b/web/projects/ui/src/app/app.module.ts @@ -1,3 +1,18 @@ +import { HttpClientModule } from '@angular/common/http' +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, + ResponsiveColViewportDirective, + SharedPipesModule, +} from '@start9labs/shared' import { TuiAlertModule, TuiDialogModule, @@ -5,36 +20,19 @@ import { TuiRootModule, TuiThemeNightModule, } from '@taiga-ui/core' -import { HttpClientModule } from '@angular/common/http' -import { NgModule } from '@angular/core' -import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { IonicModule } from '@ionic/angular' -import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' -import { - DarkThemeModule, - SharedPipesModule, - LightThemeModule, - LoadingModule, - ResponsiveColViewportDirective, - EnterModule, - MarkdownModule, -} from '@start9labs/shared' - +import { WidgetsPageModule } from 'src/app/apps/ui/pages/widgets/widgets.module' +import { environment } from '../environments/environment' import { AppComponent } from './app.component' -import { RoutingModule } from './routing.module' -import { OSWelcomePageModule } from './common/os-welcome/os-welcome.module' -import { QRComponentModule } from './common/qr/qr.module' -import { PreloaderModule } from './app/preloader/preloader.module' +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 { APP_PROVIDERS } from './app.providers' -import { PatchDbModule } from './services/patch-db/patch-db.module' -import { ToastContainerModule } from './common/toast-container/toast-container.module' -import { ConnectionBarComponentModule } from './app/connection-bar/connection-bar.component.module' -import { WidgetsPageModule } from 'src/app/apps/ui/pages/widgets/widgets.module' -import { ServiceWorkerModule } from '@angular/service-worker' -import { environment } from '../environments/environment' +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({ declarations: [AppComponent], @@ -53,7 +51,6 @@ import { SidebarHostComponent } from './app/sidebar-host.component' MarkdownModule, MonacoEditorModule, SharedPipesModule, - PatchDbModule, ToastContainerModule, ConnectionBarComponentModule, TuiRootModule, diff --git a/web/projects/ui/src/app/app.providers.ts b/web/projects/ui/src/app/app.providers.ts index 2c612f320..400fa0e92 100644 --- a/web/projects/ui/src/app/app.providers.ts +++ b/web/projects/ui/src/app/app.providers.ts @@ -1,7 +1,13 @@ import { APP_INITIALIZER, Provider } from '@angular/core' import { UntypedFormBuilder } from '@angular/forms' -import { Router, RouteReuseStrategy } from '@angular/router' +import { Router } from '@angular/router' import { IonNav } from '@ionic/angular' +import { + AbstractCategoryService, + AbstractMarketplaceService, + FilterPackagesPipe, +} from '@start9labs/marketplace' +import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared' import { TUI_DATE_FORMAT, TUI_DATE_SEPARATOR } from '@taiga-ui/cdk' import { tuiNumberFormatProvider, @@ -12,22 +18,17 @@ import { TUI_DATE_TIME_VALUE_TRANSFORMER, TUI_DATE_VALUE_TRANSFORMER, } from '@taiga-ui/kit' -import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared' -import { - AbstractCategoryService, - AbstractMarketplaceService, -} from '@start9labs/marketplace' +import { PATCH_DB_PROVIDERS } from 'src/app/services/patch-db/patch-db.providers' import { ApiService } from './services/api/embassy-api.service' -import { MockApiService } from './services/api/embassy-mock-api.service' import { LiveApiService } from './services/api/embassy-live-api.service' +import { MockApiService } from './services/api/embassy-mock-api.service' import { AuthService } from './services/auth.service' +import { CategoryService } from './services/category.service' import { ClientStorageService } from './services/client-storage.service' -import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe' -import { ThemeSwitcherService } from './services/theme-switcher.service' import { DateTransformerService } from './services/date-transformer.service' import { DatetimeTransformerService } from './services/datetime-transformer.service' import { MarketplaceService } from './services/marketplace.service' -import { CategoryService } from './services/category.service' +import { ThemeSwitcherService } from './services/theme-switcher.service' const { useMocks, @@ -35,6 +36,7 @@ const { } = require('../../../../config.json') as WorkspaceConfig export const APP_PROVIDERS: Provider[] = [ + PATCH_DB_PROVIDERS, FilterPackagesPipe, UntypedFormBuilder, IonNav, diff --git a/web/projects/ui/src/app/apps/loading/loading.module.ts b/web/projects/ui/src/app/apps/loading/loading.module.ts index 3de110846..223f8db47 100644 --- a/web/projects/ui/src/app/apps/loading/loading.module.ts +++ b/web/projects/ui/src/app/apps/loading/loading.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' -import { InitializingModule } from '@start9labs/shared' import { LoadingPage } from './loading.page' const routes: Routes = [ @@ -11,7 +10,6 @@ const routes: Routes = [ ] @NgModule({ - imports: [InitializingModule, RouterModule.forChild(routes)], - declarations: [LoadingPage], + imports: [RouterModule.forChild(routes)], }) export class LoadingPageModule {} diff --git a/web/projects/ui/src/app/apps/loading/loading.page.html b/web/projects/ui/src/app/apps/loading/loading.page.html deleted file mode 100644 index c4ac56866..000000000 --- a/web/projects/ui/src/app/apps/loading/loading.page.html +++ /dev/null @@ -1,4 +0,0 @@ - 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 14fcbeb22..da6970de8 100644 --- a/web/projects/ui/src/app/apps/loading/loading.page.ts +++ b/web/projects/ui/src/app/apps/loading/loading.page.ts @@ -1,6 +1,7 @@ import { Component, inject } from '@angular/core' import { NavController } from '@ionic/angular' import { + InitializingModule, provideSetupLogsService, provideSetupService, } from '@start9labs/shared' @@ -8,11 +9,18 @@ import { import { ApiService } from 'src/app/services/api/embassy-api.service' @Component({ - templateUrl: 'loading.page.html', + standalone: true, + template: ` + + `, providers: [ provideSetupService(ApiService), provideSetupLogsService(ApiService), ], + imports: [InitializingModule], }) export class LoadingPage { readonly navCtrl = inject(NavController) diff --git a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html index 73f0b1e21..adf9c75ed 100644 --- a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html +++ b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html @@ -1,100 +1,106 @@ -
- - - -

Trust Your Root CA

-

- Download and trust your server's Root Certificate Authority to establish - a secure (HTTPS) connection. You will need to repeat this on every - device you use to connect to your server. -

-
    -
  1. - Bookmark this page - - Save this page so you can access it later. You can also find the - address in the - StartOS-info.html - file downloaded at the end of initial setup. -
  2. -
  3. - Download your server's Root CA - - Your server uses its Root CA to generate SSL/TLS certificates for - itself and installed services. These certificates are then used to - encrypt network traffic with your client devices. -
    - - Download - - -
  4. -
  5. - Trust your server's Root CA - - Follow instructions for your OS. By trusting your server's Root CA, - your device can verify the authenticity of encrypted communications - with your server. -
    - - View Instructions - - -
  6. -
  7. - Test - - Refresh the page. If refreshing the page does not work, you may need - to quit and re-open your browser, then revisit this page. -
    - - Refresh - - -
  8. -
- - Skip - - - (not recommended) -
-
- - - - -

Root CA Trusted!

-

- You have successfully trusted your server's Root CA and may now log in - securely. -

- - Go to login - - -
-
+
+ +

Trust Your Root CA

+

+ Download and trust your server's Root Certificate Authority to establish a + secure (HTTPS) connection. You will need to repeat this on every device you + use to connect to your server. +

+
    +
  1. + Bookmark this page + - Save this page so you can access it later. You can also find the address + in the + StartOS-info.html + file downloaded at the end of initial setup. +
  2. +
  3. + Download your server's Root CA + - Your server uses its Root CA to generate SSL/TLS certificates for itself + and installed services. These certificates are then used to encrypt + network traffic with your client devices. +
    + + Download + +
  4. +
  5. + Trust your server's Root CA + - Follow instructions for your OS. By trusting your server's Root CA, your + device can verify the authenticity of encrypted communications with your + server. +
    + + View Instructions + +
  6. +
  7. + Test + - Refresh the page. If refreshing the page does not work, you may need to + quit and re-open your browser, then revisit this page. +
    + +
  8. +
+ +
(not recommended)
- + +
+ +

Root CA Trusted!

+

+ You have successfully trusted your server's Root CA and may now log in + securely. +

+ +
+
diff --git a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss index 48b198525..9ee892c79 100644 --- a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss +++ b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss @@ -1,12 +1,4 @@ -#trusted { - max-width: 40%; -} - -#untrusted { - max-width: 50%; -} - -.center-container { +:host { padding: 1rem; display: flex; flex-direction: column; @@ -15,69 +7,38 @@ min-height: 100vh; } -ion-card { - color: var(--ion-color-dark); - background: #414141; - box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144); - border-radius: 35px; - padding: 1.5rem; - width: 100%; - - h1 { - font-weight: bold; - font-size: 1.5rem; - padding-bottom: 1.5rem; - } - - p { - font-size: 21px; - line-height: 25px; - margin-bottom: 30px; - margin-top: 0; - } +[tuiButton] { + text-transform: uppercase; + font-weight: bold; + border-radius: 10rem; + margin-top: 1rem; } -.text-center { +.card { + max-width: max(70%, 40rem); text-align: center; + align-items: center !important; + gap: 0 !important; +} + +h1 { + margin: 1rem; + font-weight: bold; + font-size: 1.5rem; +} + +p { + font-size: 1.3rem; + line-height: 1.5rem; + margin: 0 0 2rem; } ol { - font-size: 17px; - line-height: 25px; + font-size: 1rem; + line-height: 1.5rem; text-align: left; - - li { - padding-bottom: 24px; - } - - ion-button { - margin-top: 10px; - } } -.refresh { - --background: var(--ion-color-success-shade); +li { + padding-bottom: 1.5rem; } - -.wiz-icon { - font-size: 64px; -} - -.skip_detail { - display: block; - font-size: 0.8rem; - margin-top: -13px; - padding-bottom: 0.5rem; -} - -@media (max-width: 700px) { - #trusted, #untrusted { - max-width: 100%; - } -} - -@media (min-width: 701px) and (max-width: 1200px) { - #trusted, #untrusted { - max-width: 75%; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts index fde1c968f..0d6b71b6b 100644 --- a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts +++ b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts @@ -1,25 +1,37 @@ -import { Component, Inject } from '@angular/core' +import { CommonModule, DOCUMENT } from '@angular/common' +import { Component, inject } from '@angular/core' +import { RELATIVE_URL } from '@start9labs/shared' +import { + TuiAppearanceModule, + TuiButtonModule, + TuiCardModule, + TuiIconModule, + TuiSurfaceModule, +} from '@taiga-ui/experimental' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ConfigService } from 'src/app/services/config.service' -import { RELATIVE_URL } from '@start9labs/shared' -import { DOCUMENT } from '@angular/common' -import { WINDOW } from '@ng-web-apis/common' @Component({ + standalone: true, selector: 'ca-wizard', templateUrl: './ca-wizard.component.html', styleUrls: ['./ca-wizard.component.scss'], + imports: [ + CommonModule, + TuiIconModule, + TuiButtonModule, + TuiAppearanceModule, + TuiCardModule, + TuiSurfaceModule, + ], }) export class CAWizardComponent { - caTrusted = false + private readonly api = inject(ApiService) + private readonly relativeUrl = inject(RELATIVE_URL) + private readonly document = inject(DOCUMENT) - constructor( - private readonly api: ApiService, - public readonly config: ConfigService, - @Inject(RELATIVE_URL) private readonly relativeUrl: string, - @Inject(DOCUMENT) public readonly document: Document, - @Inject(WINDOW) private readonly windowRef: Window, - ) {} + readonly config = inject(ConfigService) + caTrusted = false async ngOnInit() { await this.testHttps().catch(e => @@ -27,17 +39,12 @@ export class CAWizardComponent { ) } - download() { - this.document.getElementById('install-cert')?.click() - } - refresh() { this.document.location.reload() } launchHttps() { - const host = this.config.getHost() - this.windowRef.open(`https://${host}`, '_self') + this.document.defaultView?.open(`https://${this.config.getHost()}`, '_self') } private async testHttps() { diff --git a/web/projects/ui/src/app/apps/login/login.module.ts b/web/projects/ui/src/app/apps/login/login.module.ts index 753bfe94e..82b683ff7 100644 --- a/web/projects/ui/src/app/apps/login/login.module.ts +++ b/web/projects/ui/src/app/apps/login/login.module.ts @@ -1,12 +1,17 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' import { CommonModule } from '@angular/common' +import { NgModule } from '@angular/core' import { FormsModule } from '@angular/forms' -import { IonicModule } from '@ionic/angular' -import { LoginPage } from './login.page' +import { RouterModule, Routes } from '@angular/router' +import { TuiErrorModule, TuiTextfieldControllerModule } from '@taiga-ui/core' +import { + TuiButtonModule, + TuiCardModule, + TuiSurfaceModule, +} from '@taiga-ui/experimental' +import { TuiInputPasswordModule } from '@taiga-ui/kit' import { CAWizardComponent } from './ca-wizard/ca-wizard.component' -import { SharedPipesModule } from '@start9labs/shared' -import { TuiHintModule, TuiTooltipModule } from '@taiga-ui/core' +import { LoginPage } from './login.page' +import { LoginWarningComponent } from './warning.component' const routes: Routes = [ { @@ -19,12 +24,16 @@ const routes: Routes = [ imports: [ CommonModule, FormsModule, - IonicModule, - SharedPipesModule, + CAWizardComponent, + LoginWarningComponent, + TuiButtonModule, + TuiCardModule, + TuiSurfaceModule, + TuiInputPasswordModule, + TuiTextfieldControllerModule, + TuiErrorModule, RouterModule.forChild(routes), - TuiTooltipModule, - TuiHintModule, ], - declarations: [LoginPage, CAWizardComponent], + declarations: [LoginPage], }) export class LoginPageModule {} diff --git a/web/projects/ui/src/app/apps/login/login.page.html b/web/projects/ui/src/app/apps/login/login.page.html index 99f6abbe8..1343a9316 100644 --- a/web/projects/ui/src/app/apps/login/login.page.html +++ b/web/projects/ui/src/app/apps/login/login.page.html @@ -1,93 +1,26 @@ - - - - - + + - - - + + + - - - - - StartOS Icon - - Login to StartOS - - -
- - - - - - - -

- {{ error }} -

- -
-
-
-
-
-
-
-
+
+ +

Login to StartOS

+
+ + Password + + + + +
+ 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 690d97591..65bc582b1 100644 --- a/web/projects/ui/src/app/apps/login/login.page.scss +++ b/web/projects/ui/src/app/apps/login/login.page.scss @@ -1,76 +1,35 @@ -.content { - --background: #333333; +@import '@taiga-ui/core/styles/taiga-ui-local'; + +:host { + background: var(--tui-base-02); } -.grid { - height: 100%; - max-width: 540px; -} - -.row { - height: 100%; +.card { + @include center-all(); + overflow: visible; align-items: center; text-align: center; + width: max(50%, 20rem); } -.banner { - position: absolute; - padding: 20px; - width: 100%; - display: inline-block; - - ion-item { - max-width: 800px; - margin: auto; - } -} - -ion-card { - background: #414141; - box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144); - border-radius: 35px; - min-height: 16rem; - contain: unset; - overflow: unset; - position: relative; -} - -ion-item { - --background: transparent; - --border-radius: 0px; -} - -.title { - padding-top: 55px; - color: #e0e0e0; - font-size: 1.3rem; +.logo { + @include center-left(); + top: -17%; + width: 6rem; } .header { - &-icon { - width: 100px; - position: absolute; - left: 50%; - margin-left: -50px; - top: -17%; - z-index: 100; - } -} - -.login-button { - height: 45px; - width: 120px; - --border-radius: 50px; - margin: 0 auto; - margin-top: 27px; - margin-bottom: 10px; -} - -.item-interactive { - --highlight-background: #5260ff !important; + margin: 2rem 0 1rem; + text-align: center; + font-size: 2rem; } .error { - display: block; - padding-top: 4px; + min-height: 2.5rem; +} + +.button { + width: 10rem; + border-radius: 10rem; + margin-bottom: 1rem; } 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 86430ff36..b910c0c62 100644 --- a/web/projects/ui/src/app/apps/login/login.page.ts +++ b/web/projects/ui/src/app/apps/login/login.page.ts @@ -8,7 +8,6 @@ import { LoadingService } from '@start9labs/shared' import { TuiDestroyService } from '@taiga-ui/cdk' import { takeUntil } from 'rxjs' import { DOCUMENT } from '@angular/common' -import { WINDOW } from '@ng-web-apis/common' @Component({ selector: 'login', @@ -18,7 +17,6 @@ import { WINDOW } from '@ng-web-apis/common' }) export class LoginPage { password = '' - unmasked = false error = '' constructor( @@ -29,14 +27,8 @@ export class LoginPage { private readonly api: ApiService, public readonly config: ConfigService, @Inject(DOCUMENT) public readonly document: Document, - @Inject(WINDOW) private readonly windowRef: Window, ) {} - launchHttps() { - const host = this.config.getHost() - this.windowRef.open(`https://${host}`, '_self') - } - async submit() { this.error = '' diff --git a/web/projects/ui/src/app/apps/login/warning.component.ts b/web/projects/ui/src/app/apps/login/warning.component.ts new file mode 100644 index 000000000..9166a3b9a --- /dev/null +++ b/web/projects/ui/src/app/apps/login/warning.component.ts @@ -0,0 +1,60 @@ +import { NgIf } from '@angular/common' +import { Component, inject } from '@angular/core' +import { WINDOW } from '@ng-web-apis/common' +import { TuiNotificationModule } from '@taiga-ui/core' +import { TuiButtonModule, TuiIconsModule } from '@taiga-ui/experimental' +import { ConfigService } from 'src/app/services/config.service' + +@Component({ + standalone: true, + selector: 'login-warning', + template: ` + + +

Http detected

+

+ Tor is faster over https. Your Root CA must be trusted. + + View instructions + +

+
+ `, + styles: [ + ` + @import '@taiga-ui/core/styles/taiga-ui-local'; + + :host { + @include center-left(); + top: 1rem; + width: max(50%, 20rem); + } + + button { + float: right; + margin: 0.5rem 0 0.5rem 1rem; + } + `, + ], + imports: [NgIf, TuiButtonModule, TuiIconsModule, TuiNotificationModule], +}) +export class LoginWarningComponent { + private readonly windowRef = inject(WINDOW) + readonly config = inject(ConfigService) + + launchHttps() { + this.windowRef.open(`https://${this.config.getHost()}`, '_self') + } +} diff --git a/web/projects/ui/src/app/apps/portal/components/form.component.ts b/web/projects/ui/src/app/apps/portal/components/form.component.ts new file mode 100644 index 000000000..9c7295520 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/form.component.ts @@ -0,0 +1,166 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + inject, + Input, + OnInit, +} from '@angular/core' +import { FormGroup, ReactiveFormsModule } from '@angular/forms' +import { RouterModule } from '@angular/router' +import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes' +import { + tuiMarkControlAsTouchedAndValidate, + TuiValueChangesModule, +} from '@taiga-ui/cdk' +import { TuiDialogContext, TuiModeModule } from '@taiga-ui/core' +import { TuiButtonModule } from '@taiga-ui/experimental' +import { TuiDialogFormService } from '@taiga-ui/kit' +import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' +import { compare, Operation } from 'fast-json-patch' +import { FormModule } from 'src/app/common/form/form.module' +import { InvalidService } from 'src/app/common/form/invalid.service' +import { FormService } from 'src/app/services/form.service' + +export interface ActionButton { + text: string + handler?: (value: T) => Promise | void + link?: string +} + +export interface FormContext { + spec: InputSpec + buttons: ActionButton[] + value?: T + patch?: Operation[] +} + +@Component({ + standalone: true, + selector: 'app-form', + template: ` +
+ + + + `, + styles: [ + ` + footer { + position: sticky; + bottom: 0; + z-index: 10; + display: flex; + justify-content: flex-end; + padding: 1rem 0; + margin: 1rem 0 -1rem; + gap: 1rem; + background: var(--tui-elevation-01); + border-top: 1px solid var(--tui-base-02); + } + `, + ], + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule, + TuiValueChangesModule, + TuiButtonModule, + TuiModeModule, + FormModule, + ], + providers: [InvalidService], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FormComponent> implements OnInit { + private readonly dialogFormService = inject(TuiDialogFormService) + private readonly formService = inject(FormService) + private readonly invalidService = inject(InvalidService) + private readonly context = inject>>( + POLYMORPHEUS_CONTEXT, + { optional: true }, + ) + + @Input() spec = this.context?.data.spec || {} + @Input() buttons = this.context?.data.buttons || [] + @Input() patch = this.context?.data.patch || [] + @Input() value?: T = this.context?.data.value + + form = new FormGroup({}) + + ngOnInit() { + this.dialogFormService.markAsPristine() + this.form = this.formService.createForm(this.spec, this.value) + this.process(this.patch) + } + + onReset() { + const { value } = this.form + + this.form = this.formService.createForm(this.spec) + this.process(compare(this.form.value, value)) + tuiMarkControlAsTouchedAndValidate(this.form) + this.markAsDirty() + } + + async onClick(handler: Required>['handler']) { + tuiMarkControlAsTouchedAndValidate(this.form) + this.invalidService.scrollIntoView() + + if (this.form.valid && (await handler(this.form.value as T))) { + this.close() + } + } + + markAsDirty() { + this.dialogFormService.markAsDirty() + } + + close() { + this.context?.$implicit.complete() + } + + private process(patch: Operation[]) { + patch.forEach(({ op, path }) => { + const control = this.form.get(path.substring(1).split('/')) + + if (!control || !control.parent) return + + if (op !== 'remove') { + control.markAsDirty() + control.markAsTouched() + } + + control.parent.markAsDirty() + control.parent.markAsTouched() + }) + } +} diff --git a/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts b/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts index cdf9a4377..3bf249263 100644 --- a/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts +++ b/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts @@ -12,9 +12,12 @@ import { DataModel } from 'src/app/services/patch-db/data-model' template: ` @if (connection$ | async; as connection) { + {{ connection.message }} } @@ -27,6 +30,11 @@ import { DataModel } from 'src/app/services/patch-db/data-model' gap: 0.5rem; padding: 0 2rem; } + + :host-context(tui-root._mobile) { + display: none; + font-size: 1rem; + } `, ], changeDetection: ChangeDetectionStrategy.OnPush, @@ -37,6 +45,7 @@ export class HeaderConnectionComponent { message: string color: string icon: string + status: string }> = combineLatest([ inject(ConnectionService).networkConnected$, inject(ConnectionService).websocketConnected$.pipe(startWith(false)), @@ -50,30 +59,35 @@ export class HeaderConnectionComponent { message: 'No Internet', color: 'var(--tui-error-fill)', icon: 'tuiIconCloudOff', + status: 'error', } if (!websocket) return { message: 'Connecting', color: 'var(--tui-warning-fill)', icon: 'tuiIconCloudOff', + status: 'warning', } if (status['shutting-down']) return { message: 'Shutting Down', color: 'var(--tui-neutral-fill)', icon: 'tuiIconPower', + status: 'neutral', } if (status.restarting) return { message: 'Restarting', color: 'var(--tui-neutral-fill)', icon: 'tuiIconPower', + status: 'neutral', } return { message: 'Connected', color: 'var(--tui-success-fill)', icon: 'tuiIconCloud', + status: 'success', } }), ) diff --git a/web/projects/ui/src/app/apps/portal/components/header/header.component.ts b/web/projects/ui/src/app/apps/portal/components/header/header.component.ts index 8d9599943..3da3472d6 100644 --- a/web/projects/ui/src/app/apps/portal/components/header/header.component.ts +++ b/web/projects/ui/src/app/apps/portal/components/header/header.component.ts @@ -1,31 +1,47 @@ import { AsyncPipe } from '@angular/common' import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { RouterLink, RouterLinkActive } from '@angular/router' +import { + IsActiveMatchOptions, + RouterLink, + RouterLinkActive, +} from '@angular/router' +import { PatchDB } from 'patch-db-client' import { HeaderConnectionComponent } from './connection.component' import { HeaderHomeComponent } from './home.component' import { HeaderCornerComponent } from './corner.component' import { HeaderBreadcrumbComponent } from './breadcrumb.component' +import { HeaderSnekDirective } from './snek.directive' +import { HeaderMobileComponent } from './mobile.component' +import { DataModel } from 'src/app/services/patch-db/data-model' import { BreadcrumbsService } from '../../services/breadcrumbs.service' @Component({ selector: 'header[appHeader]', template: ` -
+
@for (item of breadcrumbs$ | async; track $index) { -
+
} -
-
-
+
+
+ Play Snake +
+
+
`, styles: [ ` @@ -53,9 +69,43 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service' backdrop-filter: blur(2rem) brightness(0.75) saturate(0.75); } } + + &:has([data-connection='error']) { + --status: var(--tui-error-fill); + } + + &:has([data-connection='warning']) { + --status: var(--tui-warning-fill); + } + + &:has([data-connection='neutral']) { + --status: var(--tui-neutral-fill); + } + + &:has([data-connection='success']) { + --status: var(--tui-success-fill); + } } - .plank { + header-connection .plaque::before { + box-shadow: + inset 0 1px rgba(255, 255, 255, 0.25), + inset 0 -0.25rem var(--tui-success-fill); + } + + :host-context(tui-root._mobile) { + a { + display: none; + } + + header-corner .plaque::before { + box-shadow: + inset 0 1px rgb(255 255 255 / 25%), + inset -0.375rem 0 var(--status); + } + } + + .plaque { @include transition(opacity); position: absolute; inset: 0; @@ -83,6 +133,19 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service' box-shadow: inset 0 1px rgb(255 255 255 / 25%); } } + + .snek { + @include center-top(); + @include transition(opacity); + right: 2rem; + width: 1rem; + opacity: 0.2; + cursor: pointer; + + &:hover { + opacity: 1; + } + } `, ], standalone: true, @@ -90,13 +153,29 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service' imports: [ RouterLink, RouterLinkActive, + AsyncPipe, HeaderConnectionComponent, HeaderHomeComponent, HeaderCornerComponent, - AsyncPipe, + HeaderSnekDirective, HeaderBreadcrumbComponent, + HeaderMobileComponent, ], }) export class HeaderComponent { + readonly options = OPTIONS readonly breadcrumbs$ = inject(BreadcrumbsService) + readonly snekScore$ = inject(PatchDB).watch$( + 'ui', + 'gaming', + 'snake', + 'high-score', + ) +} + +const OPTIONS: IsActiveMatchOptions = { + paths: 'exact', + queryParams: 'ignored', + fragment: 'ignored', + matrixParams: 'ignored', } diff --git a/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts b/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts index 253a3b0a7..0df040ae8 100644 --- a/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts +++ b/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts @@ -16,6 +16,7 @@ import { AuthService } from 'src/app/services/auth.service' import { ABOUT } from './about.component' import { getAllPackages } from 'src/app/util/get-package-data' import { DataModel } from 'src/app/services/patch-db/data-model' +import { HeaderConnectionComponent } from './connection.component' @Component({ selector: 'header-menu', @@ -26,7 +27,9 @@ import { DataModel } from 'src/app/services/patch-db/data-model' -

StartOS

+ +

StartOS

+
+ + `, + styles: [ + ` + .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; + } + `, + ], + imports: [TuiButtonModule], +}) +export class HeaderSnekComponent implements AfterViewInit, OnDestroy { + private readonly document = inject(DOCUMENT) + private readonly dialog = inject(POLYMORPHEUS_CONTEXT) as TuiDialogContext< + number, + number + > + + highScore: number = this.dialog.data + 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 + + 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/apps/portal/components/header/snek.directive.ts b/web/projects/ui/src/app/apps/portal/components/header/snek.directive.ts new file mode 100644 index 000000000..d7321d508 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/header/snek.directive.ts @@ -0,0 +1,47 @@ +import { Directive, HostListener, inject, Input } from '@angular/core' +import { ErrorService, LoadingService } from '@start9labs/shared' +import { TuiDialogService } from '@taiga-ui/core' +import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' +import { filter } from 'rxjs' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { HeaderSnekComponent } from './snek.component' + +@Directive({ + standalone: true, + selector: 'img[appSnek]', +}) +export class HeaderSnekDirective { + private readonly dialogs = inject(TuiDialogService) + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + + @Input() + appSnek = 0 + + @HostListener('click') + async onClick() { + this.dialogs + .open(new PolymorpheusComponent(HeaderSnekComponent), { + label: 'Snake!', + closeable: false, + dismissible: false, + data: this.appSnek, + }) + .pipe(filter(score => score > this.appSnek)) + .subscribe(async score => { + const loader = this.loader.open('Saving high score...').subscribe() + + try { + await this.api.setDbValue( + ['gaming', 'snake', 'high-score'], + score, + ) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + }) + } +} diff --git a/web/projects/ui/src/app/apps/portal/modals/prompt.component.ts b/web/projects/ui/src/app/apps/portal/modals/prompt.component.ts new file mode 100644 index 000000000..b039048e5 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/modals/prompt.component.ts @@ -0,0 +1,124 @@ +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' +import { FormsModule } from '@angular/forms' +import { TuiAutoFocusModule } from '@taiga-ui/cdk' +import { TuiDialogContext, TuiTextfieldControllerModule } from '@taiga-ui/core' +import { TuiButtonModule } from '@taiga-ui/experimental' +import { TuiInputModule } from '@taiga-ui/kit' +import { + POLYMORPHEUS_CONTEXT, + PolymorpheusComponent, +} from '@tinkoff/ng-polymorpheus' + +@Component({ + standalone: true, + template: ` +

{{ options.message }}

+

{{ options.warning }}

+
+ + {{ options.label }} + * + + +
+ + +
+
+ + + + + `, + styles: [ + ` + .warning { + color: var(--tui-warning-fill); + } + + .button { + pointer-events: auto; + margin-left: 0.25rem; + } + + .masked { + font-family: text-security-disc; + -webkit-text-security: disc; + } + `, + ], + imports: [ + CommonModule, + FormsModule, + TuiInputModule, + TuiButtonModule, + TuiTextfieldControllerModule, + TuiAutoFocusModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PromptComponent { + masked = this.options.useMask + value = this.options.initialValue || '' + + constructor( + @Inject(POLYMORPHEUS_CONTEXT) + private readonly context: TuiDialogContext, + ) {} + + get options(): PromptOptions { + return this.context.data + } + + cancel() { + this.context.$implicit.complete() + } + + submit(value: string) { + if (value || !this.options.required) { + this.context.$implicit.next(value) + } + } +} + +export const PROMPT = new PolymorpheusComponent(PromptComponent) + +export interface PromptOptions { + message: string + label?: string + warning?: string + buttonText?: string + placeholder?: string + required?: boolean + useMask?: boolean + initialValue?: string | null +} diff --git a/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts index f5c095f35..43f874c47 100644 --- a/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts @@ -29,8 +29,10 @@ import { hasCurrentDeps } from 'src/app/util/has-deps' import { getAllPackages, getPackage } from 'src/app/util/get-package-data' import { Breakages } from 'src/app/services/api/api.types' import { InvalidService } from 'src/app/common/form/invalid.service' -import { ActionButton, FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module' +import { + ActionButton, + FormComponent, +} from 'src/app/apps/portal/components/form.component' import { PackageConfigData } from '../types/package-config-data' import { ConfigDepComponent } from '../components/config-dep.component' @@ -68,7 +70,7 @@ import { ConfigDepComponent } from '../components/config-dep.component' {{ pkg.manifest.version }}. - Reset Defaults - + `, styles: [ @@ -97,7 +99,7 @@ import { ConfigDepComponent } from '../components/config-dep.component' standalone: true, imports: [ CommonModule, - FormPageModule, + FormComponent, TuiLoaderModule, TuiNotificationModule, TuiButtonModule, @@ -107,8 +109,8 @@ import { ConfigDepComponent } from '../components/config-dep.component' providers: [InvalidService], }) export class ServiceConfigModal { - @ViewChild(FormPage) - private readonly form?: FormPage> + @ViewChild(FormComponent) + private readonly form?: FormComponent> readonly pkgId = this.context.data.pkgId readonly dependentInfo = this.context.data.dependentInfo diff --git a/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts index 3a2856529..52e6371da 100644 --- a/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts @@ -13,6 +13,7 @@ import { TUI_PROMPT } from '@taiga-ui/kit' import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' import { PatchDB } from 'patch-db-client' import { filter, switchMap, timer } from 'rxjs' +import { FormComponent } from 'src/app/apps/portal/components/form.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { Action, @@ -22,7 +23,6 @@ import { } from 'src/app/services/patch-db/data-model' import { hasCurrentDeps } from 'src/app/util/has-deps' import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' import { ServiceActionComponent } from '../components/action.component' import { ServiceActionSuccessComponent } from '../components/action-success.component' import { DesktopService } from '../../../services/desktop.service' @@ -97,7 +97,7 @@ export class ServiceActionsRoute { .subscribe() } else { if (action['input-spec'] && !isEmptyObject(action['input-spec'])) { - this.formDialog.open(FormPage, { + this.formDialog.open(FormComponent, { label: action.name, data: { spec: action['input-spec'], diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts index 019db58ee..5b6357993 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts @@ -9,9 +9,8 @@ import { import { TuiForModule } from '@taiga-ui/cdk' import { TuiSvgModule } from '@taiga-ui/core' import { TuiButtonModule } from '@taiga-ui/experimental' -import { UnknownDisk } from 'src/app/services/api/api.types' -import { IonicModule } from '@ionic/angular' import { UnitConversionPipesModule } from '@start9labs/shared' +import { UnknownDisk } from 'src/app/services/api/api.types' @Component({ selector: 'table[backupsPhysical]', @@ -69,7 +68,6 @@ import { UnitConversionPipesModule } from '@start9labs/shared' TuiForModule, TuiSvgModule, TuiButtonModule, - IonicModule, UnitConversionPipesModule, ], }) diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts index 4c93e3a0d..ce89c9566 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts @@ -9,7 +9,7 @@ import { TuiNotificationModule } from '@taiga-ui/core' import { TuiButtonModule, TuiFadeModule } from '@taiga-ui/experimental' import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' import { BehaviorSubject } from 'rxjs' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' +import { FormComponent } from 'src/app/apps/portal/components/form.component' import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' import { cifsSpec, @@ -17,7 +17,7 @@ import { dropboxSpec, googleDriveSpec, remoteBackupTargetSpec, -} from 'src/app/apps/ui/pages/backups/types/target-types' +} from '../types/target' import { FormDialogService } from 'src/app/services/form-dialog.service' import { BackupTarget, @@ -126,7 +126,7 @@ export class BackupsTargetsModal implements OnInit { } async onUpdate(value: BackupTarget) { - this.formDialog.open(FormPage, { + this.formDialog.open(FormComponent, { label: 'Update Target', data: { value, @@ -147,7 +147,7 @@ export class BackupsTargetsModal implements OnInit { } async addPhysical(disk: UnknownDisk) { - this.formDialog.open(FormPage, { + this.formDialog.open(FormComponent, { label: 'New Physical Target', data: { spec: await configBuilderToSpec(diskBackupTargetSpec), @@ -173,7 +173,7 @@ export class BackupsTargetsModal implements OnInit { } async addRemote() { - this.formDialog.open(FormPage, { + this.formDialog.open(FormComponent, { label: 'New Remote Target', data: { spec: await configBuilderToSpec(remoteBackupTargetSpec), diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts index b1f6b3faf..72c37c877 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts @@ -14,12 +14,12 @@ import { take, tap, } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { BackupTarget } from 'src/app/services/api/api.types' import { PROMPT, PromptOptions, -} from 'src/app/apps/ui/modals/prompt/prompt.component' +} from 'src/app/apps/portal/modals/prompt.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { BackupTarget } from 'src/app/services/api/api.types' import { TARGET, TARGET_RESTORE } from '../modals/target.component' import { RECOVER } from '../modals/recover.component' import { RecoverData } from '../types/recover-data' diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts new file mode 100644 index 000000000..fa129fdef --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts @@ -0,0 +1,121 @@ +import { Config } from '@start9labs/start-sdk/lib/config/builder/config' +import { Value } from '@start9labs/start-sdk/lib/config/builder/value' +import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants' + +export const dropboxSpec = Config.of({ + name: Value.text({ + name: 'Name', + description: 'A friendly name for this Dropbox target', + placeholder: 'My Dropbox', + required: { default: null }, + }), + token: Value.text({ + name: 'Access Token', + description: 'The secret access token for your custom Dropbox app', + required: { default: null }, + masked: true, + }), + path: Value.text({ + name: 'Path', + description: 'The fully qualified path to the backup directory', + placeholder: 'e.g. /Desktop/my-folder', + required: { default: null }, + }), +}) + +export const googleDriveSpec = Config.of({ + name: Value.text({ + name: 'Name', + description: 'A friendly name for this Google Drive target', + placeholder: 'My Google Drive', + required: { default: null }, + }), + path: Value.text({ + name: 'Path', + description: 'The fully qualified path to the backup directory', + placeholder: 'e.g. /Desktop/my-folder', + required: { default: null }, + }), + key: Value.file({ + name: 'Private Key File', + description: + 'Your Google Drive service account private key file (.json file)', + required: { default: null }, + extensions: ['json'], + }), +}) + +export const cifsSpec = Config.of({ + name: Value.text({ + name: 'Name', + description: 'A friendly name for this Network Folder', + placeholder: 'My Network Folder', + required: { default: null }, + }), + hostname: Value.text({ + name: 'Hostname', + description: + 'The hostname of your target device on the Local Area Network.', + warning: null, + placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, + required: { default: null }, + patterns: [], + }), + path: Value.text({ + name: 'Path', + description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`, + placeholder: 'e.g. my-shared-folder or /Desktop/my-folder', + required: { default: null }, + }), + username: Value.text({ + name: 'Username', + description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`, + required: { default: null }, + placeholder: 'My Network Folder', + }), + password: Value.text({ + name: 'Password', + description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`, + required: false, + masked: true, + placeholder: 'My Network Folder', + }), +}) + +export const remoteBackupTargetSpec = Config.of({ + type: Value.union( + { + name: 'Target Type', + required: { default: 'dropbox' }, + }, + Variants.of({ + dropbox: { + name: 'Dropbox', + spec: dropboxSpec, + }, + 'google-drive': { + name: 'Google Drive', + spec: googleDriveSpec, + }, + cifs: { + name: 'Network Folder', + spec: cifsSpec, + }, + }), + ), +}) + +export const diskBackupTargetSpec = Config.of({ + name: Value.text({ + name: 'Name', + description: 'A friendly name for this physical target', + placeholder: 'My Physical Target', + required: { default: null }, + }), + path: Value.text({ + name: 'Path', + description: 'The fully qualified path to the backup directory', + placeholder: 'e.g. /Backups/my-folder', + required: { default: null }, + }), +}) diff --git a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts index 2a74e64b8..71439d651 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts @@ -13,7 +13,7 @@ import { MarketplaceSidebarService } from '../services/sidebar.service' ` :host { position: fixed; - inset: 7.5rem 0 0; + inset: 3.5rem 0 0; pointer-events: none; transform: translate3d(0, 0, 0); } diff --git a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts index 79c1150e0..d6c656507 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts @@ -21,11 +21,11 @@ import { TUI_PROMPT } from '@taiga-ui/kit' import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' import { PatchDB } from 'patch-db-client' import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs' +import { FormComponent } from 'src/app/apps/portal/components/form.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { DataModel, UIStore } from 'src/app/services/patch-db/data-model' import { MarketplaceService } from 'src/app/services/marketplace.service' import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' import { MarketplaceRegistryComponent } from '../components/registry.component' import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry' @@ -112,7 +112,7 @@ export class MarketplaceRegistryModal { add() { const { name, spec } = getMarketplaceValueSpec() - this.formDialog.open(FormPage, { + this.formDialog.open(FormComponent, { label: name, data: { spec, diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts index 9bff2a680..0ec33bbf9 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts @@ -6,14 +6,16 @@ import { TuiButtonModule } from '@taiga-ui/experimental' import { TUI_PROMPT } from '@taiga-ui/kit' import { PatchDB } from 'patch-db-client' import { filter, firstValueFrom, map } from 'rxjs' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { FormDialogService } from 'src/app/services/form-dialog.service' +import { + FormComponent, + FormContext, +} from 'src/app/apps/portal/components/form.component' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { getCustomSpec } from 'src/app/apps/ui/pages/system/domains/domain.const' -import { getStart9ToSpec } from './constants' -import { DomainsTableComponent } from './table.component' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { getCustomSpec, getStart9ToSpec } from './constants' import { DomainsInfoComponent } from './info.component' +import { DomainsTableComponent } from './table.component' @Component({ template: ` @@ -121,7 +123,7 @@ export class SettingsDomainsComponent { }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } async claim() { @@ -146,7 +148,7 @@ export class SettingsDomainsComponent { }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } private getNetworkStrategy(strategy: any) { diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts index f70e30f2a..c29a9213b 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts @@ -24,24 +24,22 @@ import { EmailInfoComponent } from './info.component' template: ` -
+

SMTP Credentials

-
- -
+
-
+

Test Email

-
- -
+
`, diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts index 66f7b2474..bff56bfcc 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts @@ -17,9 +17,12 @@ import { } from '@taiga-ui/core' import { TUI_PROMPT } from '@taiga-ui/kit' import { filter } from 'rxjs' +import { + FormComponent, + FormContext, +} from 'src/app/apps/portal/components/form.component' import { Proxy } from 'src/app/services/patch-db/data-model' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page' import { FormDialogService } from 'src/app/services/form-dialog.service' import { DELETE_OPTIONS, ProxyUpdate } from './constants' @@ -132,6 +135,6 @@ export class ProxiesMenuComponent { }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } } diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts index 6ca2f6595..3dfc3ae29 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts @@ -4,8 +4,11 @@ import { ErrorService, LoadingService } from '@start9labs/shared' import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core' import { TuiButtonModule } from '@taiga-ui/experimental' import { PatchDB } from 'patch-db-client' +import { + FormComponent, + FormContext, +} from 'src/app/apps/portal/components/form.component' import { DataModel } from 'src/app/services/patch-db/data-model' -import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page' import { FormDialogService } from 'src/app/services/form-dialog.service' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ProxiesTableComponent } from './table.component' @@ -58,7 +61,7 @@ export class SettingsProxiesComponent { }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } private async save({ name, config }: WireguardSpec): Promise { diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts index e9195ccb6..729baaae1 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts @@ -12,11 +12,11 @@ import { TuiLinkModule, } from '@taiga-ui/core' import { TuiButtonModule } from '@taiga-ui/experimental' +import { PROMPT } from 'src/app/apps/portal/modals/prompt.component' import { SSHKey } from 'src/app/services/api/api.types' -import { PROMPT } from '../../../../../../ui/modals/prompt/prompt.component' import { filter, take } from 'rxjs' import { ErrorService, LoadingService } from '@start9labs/shared' -import { ApiService } from '../../../../../../../services/api/embassy-api.service' +import { ApiService } from 'src/app/services/api/embassy-api.service' import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit' import { TuiForModule } from '@taiga-ui/cdk' @@ -35,7 +35,7 @@ import { TuiForModule } from '@taiga-ui/cdk' {{ key.hostname }} - {{ key['created-at'] | date : 'medium' }} + {{ key['created-at'] | date: 'medium' }} {{ key.alg }} {{ key.fingerprint }} diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts index 8e9e0cd16..c25991f45 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts @@ -15,9 +15,12 @@ import { TuiIconModule, TuiTitleModule, } from '@taiga-ui/experimental' +import { + FormComponent, + FormContext, +} from 'src/app/apps/portal/components/form.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page' import { Wifi, WiFiForm, wifiSpec } from './utils' import { SettingsWifiComponent } from './wifi.component' @@ -142,7 +145,7 @@ export class WifiTableComponent { }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } } } diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts index 53c7ee378..68c8ef5ef 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts @@ -25,18 +25,17 @@ import { } from '@taiga-ui/experimental' import { PatchDB } from 'patch-db-client' import { catchError, defer, merge, Observable, of, Subject, map } from 'rxjs' +import { + FormComponent, + FormContext, +} from 'src/app/apps/portal/components/form.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { DataModel } from 'src/app/services/patch-db/data-model' import { WifiInfoComponent } from './info.component' import { WifiTableComponent } from './table.component' import { parseWifi, WifiData, WiFiForm } from './utils' -import { RR } from '../../../../../../../services/api/api.types' -import { - FormContext, - FormPage, -} from '../../../../../../ui/modals/form/form.page' import { wifiSpec } from '../../../../../../ui/pages/system/wifi/wifi.const' -import { FormDialogService } from '../../../../../../../services/form-dialog.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' @Component({ template: ` @@ -143,7 +142,7 @@ export class SettingsWifiComponent { }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } async saveAndConnect(ssid: string, password?: string): Promise { diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts index e069ab3d3..f8884cd70 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts @@ -6,14 +6,14 @@ import { TUI_PROMPT } from '@taiga-ui/kit' import { PatchDB } from 'patch-db-client' import { filter, from, take } from 'rxjs' import { switchMap } from 'rxjs/operators' +import { FormComponent } from 'src/app/apps/portal/components/form.component' +import { PROMPT } from 'src/app/apps/portal/modals/prompt.component' import { ProxyService } from 'src/app/services/proxy.service' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' import { getServerInfo } from 'src/app/util/get-server-info' import { FormDialogService } from 'src/app/services/form-dialog.service' import { DataModel } from 'src/app/services/patch-db/data-model' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component' import { passwordSpec, PasswordSpec, SettingBtn } from './settings.types' @@ -160,7 +160,7 @@ export class SettingsService { switchMap(() => from(configBuilderToSpec(passwordSpec))), ) .subscribe(spec => { - this.formDialog.open(FormPage, { + this.formDialog.open(FormComponent, { label: 'Change Master Password', data: { spec, diff --git a/web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts deleted file mode 100644 index 6eb12a532..000000000 --- a/web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - template: 'Here be snek', - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export default class SnekComponent {} diff --git a/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts b/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts index 92691e6e2..9b0c6768c 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts @@ -46,12 +46,6 @@ const ROUTES: Routes = [ loadComponent: () => import('./updates/updates.component'), data: toNavigationItem('/portal/system/updates'), }, - { - title: systemTabResolver, - path: 'snek', - loadComponent: () => import('./snek/snek.component'), - data: toNavigationItem('/portal/system/snek'), - }, ] @NgModule({ imports: [RouterModule.forChild(ROUTES)] }) diff --git a/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts b/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts index 631ee0605..89593a545 100644 --- a/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts +++ b/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts @@ -30,7 +30,7 @@ export class BreadcrumbsService extends BehaviorSubject { const packages = await getAllPackages(this.patch) try { - this.next(toBreadcrumbs(page, packages)) + this.next(toBreadcrumbs(page.split('?')[0], packages)) } catch (e) { this.next([]) } diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.module.ts b/web/projects/ui/src/app/services/patch-db/patch-db.module.ts deleted file mode 100644 index 3c816e339..000000000 --- a/web/projects/ui/src/app/services/patch-db/patch-db.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PatchDB } from 'patch-db-client' -import { Injector, NgModule } from '@angular/core' -import { PATCH_SOURCE, sourceFactory } from './patch-db.factory' - -// This module is purely for providers organization purposes -@NgModule({ - providers: [ - { - provide: PATCH_SOURCE, - deps: [Injector], - useFactory: sourceFactory, - }, - { - provide: PatchDB, - deps: [PATCH_SOURCE], - useClass: PatchDB, - }, - ], -}) -export class PatchDbModule {} diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts b/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts new file mode 100644 index 000000000..e47f1a9a5 --- /dev/null +++ b/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts @@ -0,0 +1,16 @@ +import { PatchDB } from 'patch-db-client' +import { Injector } from '@angular/core' +import { PATCH_SOURCE, sourceFactory } from './patch-db.factory' + +export const PATCH_DB_PROVIDERS = [ + { + provide: PATCH_SOURCE, + deps: [Injector], + useFactory: sourceFactory, + }, + { + provide: PatchDB, + deps: [PATCH_SOURCE], + useClass: PatchDB, + }, +] diff --git a/web/projects/ui/src/styles.scss b/web/projects/ui/src/styles.scss index 0ebb45238..2a92038a5 100644 --- a/web/projects/ui/src/styles.scss +++ b/web/projects/ui/src/styles.scss @@ -369,15 +369,21 @@ ul { } .g-page { + @include customize-scroll(); + display: block; - height: 100%; + height: calc(100% - 0.375rem); padding: 1px 2rem 3rem; box-sizing: border-box; overflow: auto; isolation: isolate; + backdrop-filter: blur(2rem); + margin: 0 0.375rem; + border-radius: 0.375rem; // TODO: Theme - background: #373a3f; + background: rgb(55 58 63 / 90%); + box-shadow: inset 0 1px rgb(255 255 255 / 10%); } .g-edged {