From 9f5a90ee9c9655436f6c1b9a3e19de839b90d1d8 Mon Sep 17 00:00:00 2001 From: Alex Inkin Date: Thu, 27 Jul 2023 22:51:15 +0400 Subject: [PATCH] feat(portal): implement adding/removing to desktop (#2374) * feat(portal): implement adding/removing to desktop, reordering desktop items, baseline for system utils * chore: fix comments --------- Co-authored-by: Matt Hill --- frontend/package-lock.json | 97 ++++++++++--------- frontend/package.json | 10 +- .../src/directives/alert/alert.directive.ts | 22 ----- .../src/directives/alert/alert.module.ts | 8 -- frontend/projects/shared/src/public-api.ts | 2 - .../components/actions/actions.component.html | 17 ++++ .../components/actions/actions.component.scss | 16 +++ .../components/actions/actions.component.ts | 26 +++++ .../components/card/card.component.html | 37 +++---- .../components/card/card.component.scss | 17 ---- .../portal/components/card/card.component.ts | 30 ++++-- .../components/drawer/drawer.component.html | 55 ++++++----- .../components/drawer/drawer.component.scss | 13 ++- .../components/drawer/drawer.component.ts | 24 +++-- .../portal/components/drawer/drawer.const.ts | 41 ++++---- .../apps/portal/pipes/to-desktop-actions.ts | 38 ++++++++ .../app/apps/portal/pipes/to-desktop-item.ts | 35 +++++++ .../apps/portal/pipes/to-navigation-item.ts | 14 --- .../src/app/apps/portal/portal.component.scss | 1 + .../ui/src/app/apps/portal/portal.module.ts | 5 + .../routes/desktop/desktop.component.html | 30 +++++- .../routes/desktop/desktop.component.scss | 9 +- .../routes/desktop/desktop.component.ts | 29 +++++- .../portal/routes/desktop/desktop.module.ts | 8 +- .../portal/routes/desktop/desktop.service.ts | 52 ++++++++++ .../routes/services/service.component.ts | 5 +- .../routes/system/snek/snek.component.ts | 8 ++ .../portal/routes/system/system.module.ts | 17 ++++ .../apps/portal/utils/to-navigation-item.ts | 13 --- .../app/apps/portal/utils/to-router-link.ts | 3 + .../toast-container/toast-container.module.ts | 9 +- .../ui/src/app/services/api/mock-patch.ts | 1 + .../src/app/services/patch-db/data-model.ts | 2 +- 33 files changed, 462 insertions(+), 232 deletions(-) delete mode 100644 frontend/projects/shared/src/directives/alert/alert.directive.ts delete mode 100644 frontend/projects/shared/src/directives/alert/alert.module.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.html create mode 100644 frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.scss create mode 100644 frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-actions.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-item.ts delete mode 100644 frontend/projects/ui/src/app/apps/portal/pipes/to-navigation-item.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.service.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/routes/system/system.module.ts delete mode 100644 frontend/projects/ui/src/app/apps/portal/utils/to-navigation-item.ts create mode 100644 frontend/projects/ui/src/app/apps/portal/utils/to-router-link.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6c7c2a25d..d0ea5b0e3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,11 +23,11 @@ "@start9labs/argon2": "^0.1.0", "@start9labs/emver": "^0.1.5", "@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5", - "@taiga-ui/addon-charts": "3.36.0", - "@taiga-ui/cdk": "3.36.0", - "@taiga-ui/core": "3.36.0", - "@taiga-ui/icons": "3.36.0", - "@taiga-ui/kit": "3.36.0", + "@taiga-ui/addon-charts": "3.38.0", + "@taiga-ui/cdk": "3.38.0", + "@taiga-ui/core": "3.38.0", + "@taiga-ui/icons": "3.38.0", + "@taiga-ui/kit": "3.38.0", "@tinkoff/ng-dompurify": "4.0.0", "ansi-to-html": "^0.7.2", "base64-js": "^1.5.1", @@ -3553,9 +3553,9 @@ "dev": true }, "node_modules/@maskito/angular": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-1.2.0.tgz", - "integrity": "sha512-2YD/MWxESVn5/nckZj4F3GArzxjN3M4V8SHhtxI4c3wtg1m8ewoO8r7o3HYk/4aVLxxR0y2bz6cOWJtawt4KoQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-1.3.0.tgz", + "integrity": "sha512-SAuhTl3OkZ1Ff9TAksO+yLHgsv8N4LZTVOaFLyeYUQyLH/8nNcKTDMU/w1pRhoS0+7sXHH6/YzQ4CEHLgguHRA==", "dependencies": { "tslib": "^2.3.0" }, @@ -3563,21 +3563,21 @@ "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@angular/forms": ">=12.0.0", - "@maskito/core": "^1.2.0", + "@maskito/core": "^1.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@maskito/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@maskito/core/-/core-1.2.0.tgz", - "integrity": "sha512-RFSydWYujxbVBbMzQVZ0zR77ROY3MbcuyKFWLomJWw3rDujl65M2ppz5KMeDSogAGkKnqzWudozjmBAQf2DgcA==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@maskito/core/-/core-1.3.0.tgz", + "integrity": "sha512-JFSUHJw+dB7yFzaX45S+t4ivPznOlsAqRorgGr4Gx3CR0DU8CZhZsSVCIeSNABsrIgtHPtlhiAv3Jw6EaqShTg==" }, "node_modules/@maskito/kit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-1.2.0.tgz", - "integrity": "sha512-sMUZ3vMp3RCAcw+H/TuxyrJDgz6J5TTUCc+2/inTCE1gr33FsmhzLqoi5PaYrD146VcOKdtAxd3NJ1RK/g1ZHw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-1.3.0.tgz", + "integrity": "sha512-DwYIEE7+fh/6q05KTzPEs+qnJp8jsXQa6h9UBk2Zlnp97PerPO56HGhvm2kAm/LSYtDzTCeurrvCAqncUSSOIg==", "peerDependencies": { - "@maskito/core": "^1.2.0" + "@maskito/core": "^1.3.0" } }, "node_modules/@materia-ui/ngx-monaco-editor": { @@ -3998,9 +3998,9 @@ } }, "node_modules/@taiga-ui/addon-charts": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.36.0.tgz", - "integrity": "sha512-GZqhXUNNBtjX0jqPuYtYLjALTP0boV3cORnYt9/pXZ1DSXje6AyjLAmYXY/u7vlgcWAggLPd6A1GXszSOBDdIA==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.38.0.tgz", + "integrity": "sha512-3/8M/FTKZ7OU1CdTInHrNSueQrHPqlas7+gvkj6jKCHuhqqe5MsBWYBIh8jywvbI6lbMGhXoNXYqrVzpvX2YNA==", "dependencies": { "tslib": ">=2.0.0" }, @@ -4008,15 +4008,15 @@ "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@ng-web-apis/common": ">=3.0.0", - "@taiga-ui/cdk": ">=3.36.0", - "@taiga-ui/core": ">=3.36.0", + "@taiga-ui/cdk": ">=3.38.0", + "@taiga-ui/core": ">=3.38.0", "@tinkoff/ng-polymorpheus": ">=4.0.0" } }, "node_modules/@taiga-ui/cdk": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.36.0.tgz", - "integrity": "sha512-ipoL6/P8OqsVXTcP1kXP5qeQ4Dtno6893xioHmke+SQpoOYO7u9JUZgj9exdL8Zyy4SdXF456EzB9qib79GN6g==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.38.0.tgz", + "integrity": "sha512-932i9DTnCJN4KlUDazVet+30C/iUnCX5ldrC5nJMglbn42/4/lW1Rlh8RNHhXlL61iz8+FqGkMSE+YAKhKKl0w==", "dependencies": { "@ng-web-apis/common": "3.0.1", "@ng-web-apis/mutation-observer": "3.0.1", @@ -4038,11 +4038,11 @@ } }, "node_modules/@taiga-ui/core": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.36.0.tgz", - "integrity": "sha512-X1l9kQLdVkN5oVNHgiFtKmCtPOneOtgI8SdPFgrhlTdNI9ve3cy4vhLWtVq441QYnTM/MIDPsTNXgRend/dDsg==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.38.0.tgz", + "integrity": "sha512-7j5u15d5J8iOEVQY/xUGSvmoHkgRbBrzner6kCj4ZSsgP7Mu+yamDQmSJdRzORqpG/oOBwtjuXZTr9ic8NWEXQ==", "dependencies": { - "@taiga-ui/i18n": "^3.36.0", + "@taiga-ui/i18n": "^3.38.0", "tslib": ">=2.0.0" }, "peerDependencies": { @@ -4054,41 +4054,44 @@ "@angular/router": ">=12.0.0", "@ng-web-apis/common": ">=3.0.0", "@ng-web-apis/mutation-observer": ">=3.0.0", - "@taiga-ui/cdk": ">=3.36.0", - "@taiga-ui/i18n": ">=3.36.0", + "@taiga-ui/cdk": ">=3.38.0", + "@taiga-ui/i18n": ">=3.38.0", "@tinkoff/ng-event-plugins": ">=3.1.0", "@tinkoff/ng-polymorpheus": ">=4.0.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/i18n": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.36.0.tgz", - "integrity": "sha512-vl7rXDYR0LvDJrOimN+wR+7bZww7Cv1JxwsZpbrt5hxXhX5Ih36bMtBqJMEfziCL2XOuFbor2KjegllXreEHPA==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.38.0.tgz", + "integrity": "sha512-jejqnDjLHbm23sZ0ypRoy7bWrL9W57ISH74ArRNa1fV0Z+H0oHlkgz7JxDwEF8qmOOdZoYOAIkgZLRCEs3Cz+w==", "dependencies": { "tslib": ">=2.0.0" }, "peerDependencies": { "@angular/core": ">=12.0.0", - "rxjs": ">=6.0.0" + "@taiga-ui/cdk": ">=3.38.0" } }, "node_modules/@taiga-ui/icons": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.36.0.tgz", - "integrity": "sha512-naXB46KRDfxYFxKllrpexy/+zQ1ki3IkhBfHhoFhi0WuSW3pZ2GV8kDpFI6B49FDHMQTM2FcZ2oHAC5HEGKjKw==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.38.0.tgz", + "integrity": "sha512-SRhcQaNG08a+MbISCMXBvu79mHrl7H7MCUSoP3fMy8Y3yyJqE0cchnaYZosijrEFR9mRzn0JrQ75Hpo1FaJf5w==", "dependencies": { - "tslib": "^2.2.0" + "tslib": ">=2.0.0" + }, + "peerDependencies": { + "@taiga-ui/cdk": ">=3.38.0" } }, "node_modules/@taiga-ui/kit": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.36.0.tgz", - "integrity": "sha512-8aTKchdKmUfb6ud0iFsVnhQRg+d1zCla0coV+7n0GaHkfPd4Pp5DGiYaJMs6p9rixZM4sUyvRtYxO6p2bKaPQQ==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.38.0.tgz", + "integrity": "sha512-CdsYhxNhiQfQPfxbAtbZYEcKR1VbyVvtFG071Ry8/DwhjyaC6BkYyyARJqjjxT6cn2gNmb8njbcuuENqGf/ZXw==", "dependencies": { - "@maskito/angular": "1.2.0", - "@maskito/core": "1.2.0", - "@maskito/kit": "1.2.0", + "@maskito/angular": "1.3.0", + "@maskito/core": "1.3.0", + "@maskito/kit": "1.3.0", "@ng-web-apis/intersection-observer": "3.1.1", "text-mask-core": "5.1.2", "tslib": ">=2.0.0" @@ -4101,9 +4104,9 @@ "@ng-web-apis/common": ">=3.0.0", "@ng-web-apis/mutation-observer": ">=3.0.0", "@ng-web-apis/resize-observer": ">=3.0.0", - "@taiga-ui/cdk": ">=3.36.0", - "@taiga-ui/core": ">=3.36.0", - "@taiga-ui/i18n": ">=3.36.0", + "@taiga-ui/cdk": ">=3.38.0", + "@taiga-ui/core": ">=3.38.0", + "@taiga-ui/i18n": ">=3.38.0", "@tinkoff/ng-polymorpheus": ">=4.0.0", "rxjs": ">=6.0.0" } diff --git a/frontend/package.json b/frontend/package.json index 816cd4c86..247cb2985 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,11 +44,11 @@ "@materia-ui/ngx-monaco-editor": "^6.0.0", "@start9labs/argon2": "^0.1.0", "@start9labs/emver": "^0.1.5", - "@taiga-ui/addon-charts": "3.36.0", - "@taiga-ui/cdk": "3.36.0", - "@taiga-ui/core": "3.36.0", - "@taiga-ui/icons": "3.36.0", - "@taiga-ui/kit": "3.36.0", + "@taiga-ui/addon-charts": "3.38.0", + "@taiga-ui/cdk": "3.38.0", + "@taiga-ui/core": "3.38.0", + "@taiga-ui/icons": "3.38.0", + "@taiga-ui/kit": "3.38.0", "@tinkoff/ng-dompurify": "4.0.0", "ansi-to-html": "^0.7.2", "base64-js": "^1.5.1", diff --git a/frontend/projects/shared/src/directives/alert/alert.directive.ts b/frontend/projects/shared/src/directives/alert/alert.directive.ts deleted file mode 100644 index c9c7e3b4a..000000000 --- a/frontend/projects/shared/src/directives/alert/alert.directive.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Directive } from '@angular/core' -import { - AbstractTuiDialogDirective, - AbstractTuiDialogService, -} from '@taiga-ui/cdk' -import { TuiAlertOptions, TuiAlertService } from '@taiga-ui/core' - -// TODO: Move to Taiga UI -@Directive({ - selector: 'ng-template[tuiAlert]', - providers: [ - { - provide: AbstractTuiDialogService, - useExisting: TuiAlertService, - }, - ], - inputs: ['options: tuiAlertOptions', 'open: tuiAlert'], - outputs: ['openChange: tuiAlertChange'], -}) -export class TuiAlertDirective extends AbstractTuiDialogDirective< - TuiAlertOptions -> {} diff --git a/frontend/projects/shared/src/directives/alert/alert.module.ts b/frontend/projects/shared/src/directives/alert/alert.module.ts deleted file mode 100644 index 75791bd29..000000000 --- a/frontend/projects/shared/src/directives/alert/alert.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from '@angular/core' -import { TuiAlertDirective } from './alert.directive' - -@NgModule({ - declarations: [TuiAlertDirective], - exports: [TuiAlertDirective], -}) -export class TuiAlertModule {} diff --git a/frontend/projects/shared/src/public-api.ts b/frontend/projects/shared/src/public-api.ts index 2f0dea166..dcf29257b 100644 --- a/frontend/projects/shared/src/public-api.ts +++ b/frontend/projects/shared/src/public-api.ts @@ -18,8 +18,6 @@ export * from './components/text-spinner/text-spinner.component.module' export * from './components/ticker/ticker.component' export * from './components/ticker/ticker.module' -export * from './directives/alert/alert.directive' -export * from './directives/alert/alert.module' export * from './directives/responsive-col/responsive-col.directive' export * from './directives/responsive-col/responsive-col.module' export * from './directives/responsive-col/responsive-col-viewport.directive' diff --git a/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.html b/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.html new file mode 100644 index 000000000..9be2e154e --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.html @@ -0,0 +1,17 @@ + +

+ + + +
diff --git a/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.scss b/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.scss new file mode 100644 index 000000000..2cfd2d78a --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.scss @@ -0,0 +1,16 @@ +.title { + margin: 0; + padding: 0 0.5rem 0.25rem; + white-space: nowrap; + font: var(--tui-font-text-l); + font-weight: bold; +} + +.item { + justify-content: flex-start; + gap: 0.75rem; +} + +.icon { + opacity: var(--tui-disabled-opacity); +} diff --git a/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.ts b/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.ts new file mode 100644 index 000000000..d560545a7 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/components/actions/actions.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { TuiDataListModule, TuiSvgModule } from '@taiga-ui/core' +import { CommonModule } from '@angular/common' + +export interface Action { + icon: string + label: string + action: () => void +} + +@Component({ + selector: 'app-actions', + templateUrl: './actions.component.html', + styleUrls: ['./actions.component.scss'], + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiDataListModule, TuiSvgModule, CommonModule], +}) +export class ActionsComponent { + @Input() + actions: Record = {} + + asIsOrder(a: any, b: any) { + return 0 + } +} diff --git a/frontend/projects/ui/src/app/apps/portal/components/card/card.component.html b/frontend/projects/ui/src/app/apps/portal/components/card/card.component.html index 66943d832..bc0afe0f5 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/card/card.component.html +++ b/frontend/projects/ui/src/app/apps/portal/components/card/card.component.html @@ -1,9 +1,14 @@ - - + + - + - - - - - - + + {{ title }} + diff --git a/frontend/projects/ui/src/app/apps/portal/components/card/card.component.scss b/frontend/projects/ui/src/app/apps/portal/components/card/card.component.scss index d47636ae5..b171fcad5 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/card/card.component.scss +++ b/frontend/projects/ui/src/app/apps/portal/components/card/card.component.scss @@ -43,20 +43,3 @@ // TODO: Theme background: #4b4a4a; } - -.menu-title { - margin: 0; - padding: 0 0.5rem 0.25rem; - white-space: nowrap; - font: var(--tui-font-text-l); - font-weight: bold; -} - -.menu-item { - justify-content: flex-start; - gap: 0.75rem; -} - -.menu-icon { - opacity: var(--tui-disabled-opacity); -} diff --git a/frontend/projects/ui/src/app/apps/portal/components/card/card.component.ts b/frontend/projects/ui/src/app/apps/portal/components/card/card.component.ts index 5be666df3..3675f19e3 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/card/card.component.ts +++ b/frontend/projects/ui/src/app/apps/portal/components/card/card.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, @@ -13,10 +14,10 @@ import { TuiHostedDropdownModule, TuiSvgModule, } from '@taiga-ui/core' -import { - NavigationItem, - NavigationService, -} from '../navigation/navigation.service' +import { NavigationService } from '../navigation/navigation.service' +import { Action, ActionsComponent } from '../actions/actions.component' +import { ToDesktopActionsPipe } from '../../pipes/to-desktop-actions' +import { toRouterLink } from '../../utils/to-router-link' @Component({ selector: '[appCard]', @@ -25,22 +26,37 @@ import { standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ + CommonModule, RouterLink, TuiButtonModule, TuiHostedDropdownModule, TuiDataListModule, TuiSvgModule, TickerModule, + ActionsComponent, + ToDesktopActionsPipe, ], }) export class CardComponent { private readonly navigation = inject(NavigationService) - @Input({ required: true }) - appCard!: NavigationItem + @Input() + id = '' + + @Input() + icon = '' + + @Input() + title = '' + + @Input() + actions: Record = {} @HostListener('click') onClick() { - this.navigation.addTab(this.appCard) + const { id, icon, title } = this + const routerLink = toRouterLink(id) + + this.navigation.addTab({ icon, title, routerLink }) } } diff --git a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.html b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.html index 309d5357b..976e204e6 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.html +++ b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.html @@ -13,26 +13,37 @@ > Enter service name -

System Utilities

-
- -
-

Installed services

-
- -
- Nothing found + +

System Utilities

+
+ +
+

Installed services

+
+ +
+ Nothing found +
diff --git a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.scss b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.scss index 9bbff7d91..49dd3e7ee 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.scss +++ b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.scss @@ -7,7 +7,7 @@ top: 100%; left: 0; width: 100%; - min-height: calc(100% - 10.25rem); + height: calc(100% - 10.25rem); display: flex; flex-direction: column; // TODO: Theme @@ -21,6 +21,9 @@ .content { flex: 1; + height: 100%; + display: flex; + flex-direction: column; background: inherit; } @@ -48,13 +51,17 @@ } } +.scrollbar { + margin-top: 1rem; +} + .search { - max-width: 41rem; + width: 25rem; margin: 6rem auto 0; } .title { - margin: 5rem 0 1.25rem; + margin: 4rem 0 1.25rem; text-align: center; text-transform: uppercase; font: var(--tui-font-text-xl); diff --git a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.ts b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.ts index ed87b50a8..24ad8fd4e 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.ts +++ b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.component.ts @@ -13,14 +13,16 @@ import { TuiFilterPipeModule, TuiForModule, } from '@taiga-ui/cdk' -import { TuiSvgModule, TuiTextfieldControllerModule } from '@taiga-ui/core' +import { + TuiScrollbarModule, + TuiSvgModule, + TuiTextfieldControllerModule, +} from '@taiga-ui/core' import { TuiInputModule } from '@taiga-ui/kit' -import { map } from 'rxjs' import { CardComponent } from '../card/card.component' -import { NavigationItem } from '../navigation/navigation.service' import { ServicesService } from '../../services/services.service' import { SYSTEM_UTILITIES } from './drawer.const' -import { toNavigationItem } from '../../utils/to-navigation-item' +import { toRouterLink } from '../../utils/to-router-link' @Component({ selector: 'app-drawer', @@ -32,6 +34,7 @@ import { toNavigationItem } from '../../utils/to-navigation-item' CommonModule, FormsModule, TuiSvgModule, + TuiScrollbarModule, TuiActiveZoneModule, TuiInputModule, TuiTextfieldControllerModule, @@ -48,10 +51,13 @@ export class DrawerComponent { search = '' readonly system = SYSTEM_UTILITIES - readonly services$ = inject(ServicesService).pipe( - map(services => services.map(toNavigationItem)), - ) + readonly services$ = inject(ServicesService) - readonly bySearch = (item: NavigationItem, search: string): boolean => - search.length < 2 || TUI_DEFAULT_MATCHER(item.title, search) + readonly bySearch = (item: any, search: string): boolean => + search.length < 2 || + TUI_DEFAULT_MATCHER(item.manifest?.title || item.value?.title || '', search) + + getLink(id: string): string { + return toRouterLink(id) + } } diff --git a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.const.ts b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.const.ts index f4859d436..c7e04060c 100644 --- a/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.const.ts +++ b/frontend/projects/ui/src/app/apps/portal/components/drawer/drawer.const.ts @@ -1,24 +1,19 @@ -import { NavigationItem } from '../navigation/navigation.service' - -export const SYSTEM_UTILITIES: readonly NavigationItem[] = [ +export const SYSTEM_UTILITIES: Record = { - title: 'Devices', - routerLink: 'devices', - icon: 'assets/img/icon_transparent.png', - }, - { - title: 'Metrics', - routerLink: 'metrics', - icon: 'assets/img/icon_transparent.png', - }, - { - title: 'User manual', - routerLink: 'manual', - icon: 'assets/img/icon_transparent.png', - }, - { - title: 'Snek', - routerLink: 'snek', - icon: 'assets/img/icon_transparent.png', - }, -] + '/portal/system/devices': { + icon: 'assets/img/icon_transparent.png', + title: 'Devices', + }, + '/portal/system/metrics': { + icon: 'assets/img/icon_transparent.png', + title: 'Metrics', + }, + '/portal/system/manual': { + icon: 'assets/img/icon_transparent.png', + title: 'Manual', + }, + '/portal/system/snek': { + icon: 'assets/img/icon_transparent.png', + title: 'Snek', + }, + } diff --git a/frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-actions.ts b/frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-actions.ts new file mode 100644 index 000000000..00cba35e9 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-actions.ts @@ -0,0 +1,38 @@ +import { inject, Pipe, PipeTransform } from '@angular/core' +import { Action } from '../components/actions/actions.component' +import { filter, map, Observable } from 'rxjs' +import { DesktopService } from '../routes/desktop/desktop.service' + +@Pipe({ + name: 'toDesktopActions', + standalone: true, +}) +export class ToDesktopActionsPipe implements PipeTransform { + private readonly desktop = inject(DesktopService) + + transform( + value: Record, + id: string, + ): Observable> { + return this.desktop.desktop$.pipe( + filter(Boolean), + map(desktop => { + const action = desktop.includes(id) + ? { + icon: 'tuiIconMinus', + label: 'Remove from Desktop', + action: () => this.desktop.remove(id), + } + : { + icon: 'tuiIconPlus', + label: 'Add to Desktop', + action: () => this.desktop.add(id), + } + + return { + manage: [action], + } + }), + ) + } +} diff --git a/frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-item.ts b/frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-item.ts new file mode 100644 index 000000000..edc182208 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/pipes/to-desktop-item.ts @@ -0,0 +1,35 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { SYSTEM_UTILITIES } from '../components/drawer/drawer.const' +import { NavigationItem } from '../components/navigation/navigation.service' +import { toRouterLink } from '../utils/to-router-link' + +@Pipe({ + name: 'toDesktopItem', + standalone: true, +}) +export class ToDesktopItemPipe implements PipeTransform { + private readonly system = SYSTEM_UTILITIES + + transform( + packages: Record, + id: string, + ): NavigationItem { + const item = SYSTEM_UTILITIES[id] + const routerLink = toRouterLink(id) + + if (SYSTEM_UTILITIES[id]) { + return { + icon: item.icon, + title: item.title, + routerLink, + } + } + + return { + icon: packages[id].icon, + title: packages[id].manifest.title, + routerLink, + } + } +} diff --git a/frontend/projects/ui/src/app/apps/portal/pipes/to-navigation-item.ts b/frontend/projects/ui/src/app/apps/portal/pipes/to-navigation-item.ts deleted file mode 100644 index dbf276474..000000000 --- a/frontend/projects/ui/src/app/apps/portal/pipes/to-navigation-item.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { NavigationItem } from '../components/navigation/navigation.service' -import { toNavigationItem } from '../utils/to-navigation-item' - -@Pipe({ - name: 'toNavigationItem', - standalone: true, -}) -export class ToNavigationItemPipe implements PipeTransform { - transform(service: PackageDataEntry): NavigationItem { - return toNavigationItem(service) - } -} diff --git a/frontend/projects/ui/src/app/apps/portal/portal.component.scss b/frontend/projects/ui/src/app/apps/portal/portal.component.scss index 5c4079653..821a6ccf5 100644 --- a/frontend/projects/ui/src/app/apps/portal/portal.component.scss +++ b/frontend/projects/ui/src/app/apps/portal/portal.component.scss @@ -6,4 +6,5 @@ main { flex: 1; + overflow: hidden; } diff --git a/frontend/projects/ui/src/app/apps/portal/portal.module.ts b/frontend/projects/ui/src/app/apps/portal/portal.module.ts index 5da7dba6d..85e49a6fd 100644 --- a/frontend/projects/ui/src/app/apps/portal/portal.module.ts +++ b/frontend/projects/ui/src/app/apps/portal/portal.module.ts @@ -27,6 +27,11 @@ const ROUTES: Routes = [ m => m.ServicesModule, ), }, + { + path: 'system', + loadChildren: () => + import('./routes/system/system.module').then(m => m.SystemModule), + }, ], }, ] diff --git a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.html b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.html index e607345e4..0b1c1f05b 100644 --- a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.html +++ b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.html @@ -1,5 +1,25 @@ - + + + + + + + diff --git a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.scss b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.scss index 079b6cb3f..8272b8ebc 100644 --- a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.scss +++ b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.scss @@ -3,9 +3,16 @@ align-items: center; align-content: center; justify-content: center; - flex-wrap: wrap; height: 100%; max-width: 56rem; margin: 0 auto; + padding: 1rem 0; +} + +.tiles { + width: 100%; + justify-content: center; + grid-template-columns: repeat(auto-fit, 12.5rem); + grid-auto-rows: 5.5rem; gap: 2rem; } diff --git a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.ts b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.ts index f8ed19998..6a638db01 100644 --- a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.ts +++ b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.component.ts @@ -1,5 +1,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { ServicesService } from '../../services/services.service' +import { PatchDB } from 'patch-db-client' +import { tap } from 'rxjs' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { DesktopService } from './desktop.service' @Component({ templateUrl: 'desktop.component.html', @@ -7,6 +10,26 @@ import { ServicesService } from '../../services/services.service' changeDetection: ChangeDetectionStrategy.OnPush, }) export class DesktopComponent { - // TODO: Only show services added to desktop - readonly services$ = inject(ServicesService) + private readonly desktop = inject(DesktopService) + + readonly desktop$ = this.desktop.desktop$.pipe( + tap(() => (this.order = new Map())), + ) + + readonly packages$ = + inject>(PatchDB).watch$('package-data') + + order = new Map() + + onReorder(order: Map, desktop: readonly string[]) { + this.order = order + + const items: string[] = [] + + Array.from(this.order.entries()).forEach(([index, order]) => { + items[order] = desktop[index] + }) + + this.desktop.save(items) + } } diff --git a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.module.ts b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.module.ts index cf5d25409..71adc1ed7 100644 --- a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.module.ts +++ b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.module.ts @@ -1,9 +1,11 @@ import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' +import { TuiTilesModule } from '@taiga-ui/kit' import { DesktopComponent } from './desktop.component' import { CardComponent } from '../../components/card/card.component' -import { ToNavigationItemPipe } from '../../pipes/to-navigation-item' +import { ToDesktopActionsPipe } from '../../pipes/to-desktop-actions' +import { ToDesktopItemPipe } from '../../pipes/to-desktop-item' const ROUTES: Routes = [ { @@ -16,7 +18,9 @@ const ROUTES: Routes = [ imports: [ CommonModule, CardComponent, - ToNavigationItemPipe, + TuiTilesModule, + ToDesktopActionsPipe, + ToDesktopItemPipe, RouterModule.forChild(ROUTES), ], declarations: [DesktopComponent], diff --git a/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.service.ts b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.service.ts new file mode 100644 index 000000000..00c77e070 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/routes/desktop/desktop.service.ts @@ -0,0 +1,52 @@ +import { inject, Injectable } from '@angular/core' +import { TuiAlertService } from '@taiga-ui/core' +import { PatchDB } from 'patch-db-client' +import { BehaviorSubject, first } from 'rxjs' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { ApiService } from 'src/app/services/api/embassy-api.service' + +@Injectable({ + providedIn: 'root', +}) +export class DesktopService { + private readonly alerts = inject(TuiAlertService) + private readonly api = inject(ApiService) + + readonly desktop$ = new BehaviorSubject( + undefined, + ) + + constructor() { + inject>(PatchDB) + .watch$('ui', 'desktop') + .pipe(first()) + .subscribe(desktop => { + if (!this.desktop$.value) { + this.desktop$.next(desktop) + } + }) + } + + add(id: string) { + this.desktop$.next(this.desktop$.value?.concat(id)) + this.save(this.desktop$.value) + } + + remove(id: string) { + this.desktop$.next(this.desktop$.value?.filter(x => x !== id)) + this.save(this.desktop$.value) + } + + save(ids: readonly string[] = []) { + this.api + .setDbValue(['desktop'], ids) + .catch(() => + this.alerts + .open( + 'Desktop might be out of sync. Please refresh the page to fix it.', + { status: 'warning' }, + ) + .subscribe(), + ) + } +} diff --git a/frontend/projects/ui/src/app/apps/portal/routes/services/service.component.ts b/frontend/projects/ui/src/app/apps/portal/routes/services/service.component.ts index 9efc08caf..ab64abb9a 100644 --- a/frontend/projects/ui/src/app/apps/portal/routes/services/service.component.ts +++ b/frontend/projects/ui/src/app/apps/portal/routes/services/service.component.ts @@ -5,6 +5,7 @@ import { PatchDB } from 'patch-db-client' import { tap } from 'rxjs' import { DataModel } from 'src/app/services/patch-db/data-model' import { NavigationService } from '../../components/navigation/navigation.service' +import { toRouterLink } from '../../utils/to-router-link' @Component({ templateUrl: 'service.component.html', @@ -26,9 +27,9 @@ export class ServiceComponent { this.router.navigate(['..'], { relativeTo: this.route }) } else { this.navigation.addTab({ - title: pkg.manifest.title, - routerLink: `/portal/services/${pkg.manifest.id}`, icon: pkg.icon, + title: pkg.manifest.title, + routerLink: toRouterLink(pkg.manifest.id), }) } }), diff --git a/frontend/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts b/frontend/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts new file mode 100644 index 000000000..57f7c2653 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts @@ -0,0 +1,8 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' + +@Component({ + template: 'Here be snek', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SnekComponent {} diff --git a/frontend/projects/ui/src/app/apps/portal/routes/system/system.module.ts b/frontend/projects/ui/src/app/apps/portal/routes/system/system.module.ts new file mode 100644 index 000000000..6732156a7 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/routes/system/system.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' + +const ROUTES: Routes = [ + { + path: 'snek', + loadComponent: () => + import('./snek/snek.component').then(m => m.SnekComponent), + }, +] + +@NgModule({ + imports: [RouterModule.forChild(ROUTES)], + declarations: [], + exports: [], +}) +export class SystemModule {} diff --git a/frontend/projects/ui/src/app/apps/portal/utils/to-navigation-item.ts b/frontend/projects/ui/src/app/apps/portal/utils/to-navigation-item.ts deleted file mode 100644 index 12749a399..000000000 --- a/frontend/projects/ui/src/app/apps/portal/utils/to-navigation-item.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { NavigationItem } from '../components/navigation/navigation.service' - -export function toNavigationItem({ - manifest, - icon, -}: PackageDataEntry): NavigationItem { - return { - title: manifest.title, - routerLink: `/portal/services/${manifest.id}`, - icon, - } -} diff --git a/frontend/projects/ui/src/app/apps/portal/utils/to-router-link.ts b/frontend/projects/ui/src/app/apps/portal/utils/to-router-link.ts new file mode 100644 index 000000000..40a9ce418 --- /dev/null +++ b/frontend/projects/ui/src/app/apps/portal/utils/to-router-link.ts @@ -0,0 +1,3 @@ +export function toRouterLink(id: string): string { + return id.includes('/') ? id : `/portal/services/${id}` +} diff --git a/frontend/projects/ui/src/app/common/toast-container/toast-container.module.ts b/frontend/projects/ui/src/app/common/toast-container/toast-container.module.ts index 86294542a..0029ddada 100644 --- a/frontend/projects/ui/src/app/common/toast-container/toast-container.module.ts +++ b/frontend/projects/ui/src/app/common/toast-container/toast-container.module.ts @@ -1,14 +1,17 @@ import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { RouterModule } from '@angular/router' -import { TuiAlertModule } from '@start9labs/shared' +import { TuiAutoFocusModule } from '@taiga-ui/cdk' +import { + TuiAlertModule, + TuiButtonModule, + TuiDialogModule, +} from '@taiga-ui/core' import { ToastContainerComponent } from './toast-container.component' import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component' import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component' import { UpdateToastComponent } from './update-toast/update-toast.component' -import { TuiButtonModule, TuiDialogModule } from '@taiga-ui/core' -import { TuiAutoFocusModule } from '@taiga-ui/cdk' @NgModule({ imports: [ diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts index 3ea441ed2..240cf6ec9 100644 --- a/frontend/projects/ui/src/app/services/api/mock-patch.ts +++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts @@ -7,6 +7,7 @@ export const mockPatchData: DataModel = { name: `Matt's Server`, 'ack-welcome': '1.0.0', theme: 'Dark', + desktop: ['lnd'], widgets: BUILT_IN_WIDGETS.filter( ({ id }) => id === 'favorites' || diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 00467fdf6..71618f4bb 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -3,7 +3,6 @@ import { Url } from '@start9labs/shared' import { Manifest } from '@start9labs/marketplace' import { BackupJob } from '../api/api.types' import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants' -import { CustomSpec } from 'src/app/apps/ui/pages/system/domains/domain.const' export interface DataModel { 'server-info': ServerInfo @@ -23,6 +22,7 @@ export interface UIData { 'ack-instructions': Record theme: string widgets: readonly Widget[] + desktop: readonly string[] } export interface Widget {