mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
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 <MattDHill@users.noreply.github.com>
This commit is contained in:
97
frontend/package-lock.json
generated
97
frontend/package-lock.json
generated
@@ -23,11 +23,11 @@
|
|||||||
"@start9labs/argon2": "^0.1.0",
|
"@start9labs/argon2": "^0.1.0",
|
||||||
"@start9labs/emver": "^0.1.5",
|
"@start9labs/emver": "^0.1.5",
|
||||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
|
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
|
||||||
"@taiga-ui/addon-charts": "3.36.0",
|
"@taiga-ui/addon-charts": "3.38.0",
|
||||||
"@taiga-ui/cdk": "3.36.0",
|
"@taiga-ui/cdk": "3.38.0",
|
||||||
"@taiga-ui/core": "3.36.0",
|
"@taiga-ui/core": "3.38.0",
|
||||||
"@taiga-ui/icons": "3.36.0",
|
"@taiga-ui/icons": "3.38.0",
|
||||||
"@taiga-ui/kit": "3.36.0",
|
"@taiga-ui/kit": "3.38.0",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@@ -3553,9 +3553,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/angular": {
|
"node_modules/@maskito/angular": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-1.3.0.tgz",
|
||||||
"integrity": "sha512-2YD/MWxESVn5/nckZj4F3GArzxjN3M4V8SHhtxI4c3wtg1m8ewoO8r7o3HYk/4aVLxxR0y2bz6cOWJtawt4KoQ==",
|
"integrity": "sha512-SAuhTl3OkZ1Ff9TAksO+yLHgsv8N4LZTVOaFLyeYUQyLH/8nNcKTDMU/w1pRhoS0+7sXHH6/YzQ4CEHLgguHRA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -3563,21 +3563,21 @@
|
|||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@angular/forms": ">=12.0.0",
|
"@angular/forms": ">=12.0.0",
|
||||||
"@maskito/core": "^1.2.0",
|
"@maskito/core": "^1.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/core": {
|
"node_modules/@maskito/core": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-1.3.0.tgz",
|
||||||
"integrity": "sha512-RFSydWYujxbVBbMzQVZ0zR77ROY3MbcuyKFWLomJWw3rDujl65M2ppz5KMeDSogAGkKnqzWudozjmBAQf2DgcA=="
|
"integrity": "sha512-JFSUHJw+dB7yFzaX45S+t4ivPznOlsAqRorgGr4Gx3CR0DU8CZhZsSVCIeSNABsrIgtHPtlhiAv3Jw6EaqShTg=="
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/kit": {
|
"node_modules/@maskito/kit": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-1.3.0.tgz",
|
||||||
"integrity": "sha512-sMUZ3vMp3RCAcw+H/TuxyrJDgz6J5TTUCc+2/inTCE1gr33FsmhzLqoi5PaYrD146VcOKdtAxd3NJ1RK/g1ZHw==",
|
"integrity": "sha512-DwYIEE7+fh/6q05KTzPEs+qnJp8jsXQa6h9UBk2Zlnp97PerPO56HGhvm2kAm/LSYtDzTCeurrvCAqncUSSOIg==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@maskito/core": "^1.2.0"
|
"@maskito/core": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@materia-ui/ngx-monaco-editor": {
|
"node_modules/@materia-ui/ngx-monaco-editor": {
|
||||||
@@ -3998,9 +3998,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-charts": {
|
"node_modules/@taiga-ui/addon-charts": {
|
||||||
"version": "3.36.0",
|
"version": "3.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.38.0.tgz",
|
||||||
"integrity": "sha512-GZqhXUNNBtjX0jqPuYtYLjALTP0boV3cORnYt9/pXZ1DSXje6AyjLAmYXY/u7vlgcWAggLPd6A1GXszSOBDdIA==",
|
"integrity": "sha512-3/8M/FTKZ7OU1CdTInHrNSueQrHPqlas7+gvkj6jKCHuhqqe5MsBWYBIh8jywvbI6lbMGhXoNXYqrVzpvX2YNA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.0.0"
|
"tslib": ">=2.0.0"
|
||||||
},
|
},
|
||||||
@@ -4008,15 +4008,15 @@
|
|||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@ng-web-apis/common": ">=3.0.0",
|
"@ng-web-apis/common": ">=3.0.0",
|
||||||
"@taiga-ui/cdk": ">=3.36.0",
|
"@taiga-ui/cdk": ">=3.38.0",
|
||||||
"@taiga-ui/core": ">=3.36.0",
|
"@taiga-ui/core": ">=3.38.0",
|
||||||
"@tinkoff/ng-polymorpheus": ">=4.0.0"
|
"@tinkoff/ng-polymorpheus": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/cdk": {
|
"node_modules/@taiga-ui/cdk": {
|
||||||
"version": "3.36.0",
|
"version": "3.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.38.0.tgz",
|
||||||
"integrity": "sha512-ipoL6/P8OqsVXTcP1kXP5qeQ4Dtno6893xioHmke+SQpoOYO7u9JUZgj9exdL8Zyy4SdXF456EzB9qib79GN6g==",
|
"integrity": "sha512-932i9DTnCJN4KlUDazVet+30C/iUnCX5ldrC5nJMglbn42/4/lW1Rlh8RNHhXlL61iz8+FqGkMSE+YAKhKKl0w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ng-web-apis/common": "3.0.1",
|
"@ng-web-apis/common": "3.0.1",
|
||||||
"@ng-web-apis/mutation-observer": "3.0.1",
|
"@ng-web-apis/mutation-observer": "3.0.1",
|
||||||
@@ -4038,11 +4038,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/core": {
|
"node_modules/@taiga-ui/core": {
|
||||||
"version": "3.36.0",
|
"version": "3.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.38.0.tgz",
|
||||||
"integrity": "sha512-X1l9kQLdVkN5oVNHgiFtKmCtPOneOtgI8SdPFgrhlTdNI9ve3cy4vhLWtVq441QYnTM/MIDPsTNXgRend/dDsg==",
|
"integrity": "sha512-7j5u15d5J8iOEVQY/xUGSvmoHkgRbBrzner6kCj4ZSsgP7Mu+yamDQmSJdRzORqpG/oOBwtjuXZTr9ic8NWEXQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@taiga-ui/i18n": "^3.36.0",
|
"@taiga-ui/i18n": "^3.38.0",
|
||||||
"tslib": ">=2.0.0"
|
"tslib": ">=2.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -4054,41 +4054,44 @@
|
|||||||
"@angular/router": ">=12.0.0",
|
"@angular/router": ">=12.0.0",
|
||||||
"@ng-web-apis/common": ">=3.0.0",
|
"@ng-web-apis/common": ">=3.0.0",
|
||||||
"@ng-web-apis/mutation-observer": ">=3.0.0",
|
"@ng-web-apis/mutation-observer": ">=3.0.0",
|
||||||
"@taiga-ui/cdk": ">=3.36.0",
|
"@taiga-ui/cdk": ">=3.38.0",
|
||||||
"@taiga-ui/i18n": ">=3.36.0",
|
"@taiga-ui/i18n": ">=3.38.0",
|
||||||
"@tinkoff/ng-event-plugins": ">=3.1.0",
|
"@tinkoff/ng-event-plugins": ">=3.1.0",
|
||||||
"@tinkoff/ng-polymorpheus": ">=4.0.0",
|
"@tinkoff/ng-polymorpheus": ">=4.0.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/i18n": {
|
"node_modules/@taiga-ui/i18n": {
|
||||||
"version": "3.36.0",
|
"version": "3.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.38.0.tgz",
|
||||||
"integrity": "sha512-vl7rXDYR0LvDJrOimN+wR+7bZww7Cv1JxwsZpbrt5hxXhX5Ih36bMtBqJMEfziCL2XOuFbor2KjegllXreEHPA==",
|
"integrity": "sha512-jejqnDjLHbm23sZ0ypRoy7bWrL9W57ISH74ArRNa1fV0Z+H0oHlkgz7JxDwEF8qmOOdZoYOAIkgZLRCEs3Cz+w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.0.0"
|
"tslib": ">=2.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"rxjs": ">=6.0.0"
|
"@taiga-ui/cdk": ">=3.38.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/icons": {
|
"node_modules/@taiga-ui/icons": {
|
||||||
"version": "3.36.0",
|
"version": "3.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.38.0.tgz",
|
||||||
"integrity": "sha512-naXB46KRDfxYFxKllrpexy/+zQ1ki3IkhBfHhoFhi0WuSW3pZ2GV8kDpFI6B49FDHMQTM2FcZ2oHAC5HEGKjKw==",
|
"integrity": "sha512-SRhcQaNG08a+MbISCMXBvu79mHrl7H7MCUSoP3fMy8Y3yyJqE0cchnaYZosijrEFR9mRzn0JrQ75Hpo1FaJf5w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.2.0"
|
"tslib": ">=2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@taiga-ui/cdk": ">=3.38.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/kit": {
|
"node_modules/@taiga-ui/kit": {
|
||||||
"version": "3.36.0",
|
"version": "3.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.38.0.tgz",
|
||||||
"integrity": "sha512-8aTKchdKmUfb6ud0iFsVnhQRg+d1zCla0coV+7n0GaHkfPd4Pp5DGiYaJMs6p9rixZM4sUyvRtYxO6p2bKaPQQ==",
|
"integrity": "sha512-CdsYhxNhiQfQPfxbAtbZYEcKR1VbyVvtFG071Ry8/DwhjyaC6BkYyyARJqjjxT6cn2gNmb8njbcuuENqGf/ZXw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maskito/angular": "1.2.0",
|
"@maskito/angular": "1.3.0",
|
||||||
"@maskito/core": "1.2.0",
|
"@maskito/core": "1.3.0",
|
||||||
"@maskito/kit": "1.2.0",
|
"@maskito/kit": "1.3.0",
|
||||||
"@ng-web-apis/intersection-observer": "3.1.1",
|
"@ng-web-apis/intersection-observer": "3.1.1",
|
||||||
"text-mask-core": "5.1.2",
|
"text-mask-core": "5.1.2",
|
||||||
"tslib": ">=2.0.0"
|
"tslib": ">=2.0.0"
|
||||||
@@ -4101,9 +4104,9 @@
|
|||||||
"@ng-web-apis/common": ">=3.0.0",
|
"@ng-web-apis/common": ">=3.0.0",
|
||||||
"@ng-web-apis/mutation-observer": ">=3.0.0",
|
"@ng-web-apis/mutation-observer": ">=3.0.0",
|
||||||
"@ng-web-apis/resize-observer": ">=3.0.0",
|
"@ng-web-apis/resize-observer": ">=3.0.0",
|
||||||
"@taiga-ui/cdk": ">=3.36.0",
|
"@taiga-ui/cdk": ">=3.38.0",
|
||||||
"@taiga-ui/core": ">=3.36.0",
|
"@taiga-ui/core": ">=3.38.0",
|
||||||
"@taiga-ui/i18n": ">=3.36.0",
|
"@taiga-ui/i18n": ">=3.38.0",
|
||||||
"@tinkoff/ng-polymorpheus": ">=4.0.0",
|
"@tinkoff/ng-polymorpheus": ">=4.0.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,11 @@
|
|||||||
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
||||||
"@start9labs/argon2": "^0.1.0",
|
"@start9labs/argon2": "^0.1.0",
|
||||||
"@start9labs/emver": "^0.1.5",
|
"@start9labs/emver": "^0.1.5",
|
||||||
"@taiga-ui/addon-charts": "3.36.0",
|
"@taiga-ui/addon-charts": "3.38.0",
|
||||||
"@taiga-ui/cdk": "3.36.0",
|
"@taiga-ui/cdk": "3.38.0",
|
||||||
"@taiga-ui/core": "3.36.0",
|
"@taiga-ui/core": "3.38.0",
|
||||||
"@taiga-ui/icons": "3.36.0",
|
"@taiga-ui/icons": "3.38.0",
|
||||||
"@taiga-ui/kit": "3.36.0",
|
"@taiga-ui/kit": "3.38.0",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
|||||||
@@ -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<T> extends AbstractTuiDialogDirective<
|
|
||||||
TuiAlertOptions<T>
|
|
||||||
> {}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { TuiAlertDirective } from './alert.directive'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [TuiAlertDirective],
|
|
||||||
exports: [TuiAlertDirective],
|
|
||||||
})
|
|
||||||
export class TuiAlertModule {}
|
|
||||||
@@ -18,8 +18,6 @@ export * from './components/text-spinner/text-spinner.component.module'
|
|||||||
export * from './components/ticker/ticker.component'
|
export * from './components/ticker/ticker.component'
|
||||||
export * from './components/ticker/ticker.module'
|
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.directive'
|
||||||
export * from './directives/responsive-col/responsive-col.module'
|
export * from './directives/responsive-col/responsive-col.module'
|
||||||
export * from './directives/responsive-col/responsive-col-viewport.directive'
|
export * from './directives/responsive-col/responsive-col-viewport.directive'
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<tui-data-list>
|
||||||
|
<h3 class="title"><ng-content></ng-content></h3>
|
||||||
|
<tui-opt-group
|
||||||
|
*ngFor="let group of actions | keyvalue : asIsOrder"
|
||||||
|
[label]="group.key.toUpperCase()"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
*ngFor="let action of group.value"
|
||||||
|
tuiOption
|
||||||
|
class="item"
|
||||||
|
(click)="action.action()"
|
||||||
|
>
|
||||||
|
<tui-svg class="icon" [src]="action.icon"></tui-svg>
|
||||||
|
{{ action.label }}
|
||||||
|
</button>
|
||||||
|
</tui-opt-group>
|
||||||
|
</tui-data-list>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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<string, readonly Action[]> = {}
|
||||||
|
|
||||||
|
asIsOrder(a: any, b: any) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
<span class="link">
|
<span class="link">
|
||||||
<img alt="" class="icon" [src]="appCard.icon" />
|
<img alt="" class="icon" [src]="icon" />
|
||||||
<label ticker class="title">{{ appCard.title }}</label>
|
<label ticker class="title">{{ title }}</label>
|
||||||
</span>
|
</span>
|
||||||
<span class="side">
|
<span class="side">
|
||||||
<tui-hosted-dropdown [content]="content" (click.stop.prevent)="(0)">
|
<tui-hosted-dropdown
|
||||||
|
#dropdown
|
||||||
|
[content]="content"
|
||||||
|
(click.stop.prevent)="(0)"
|
||||||
|
(pointerdown.stop)="(0)"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
appearance="outline"
|
appearance="outline"
|
||||||
@@ -14,26 +19,12 @@
|
|||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
<ng-template #content>
|
<ng-template #content>
|
||||||
<!-- TODO: Move menu to a separate component -->
|
<app-actions
|
||||||
<tui-data-list>
|
[actions]="(actions | toDesktopActions : id | async) || {}"
|
||||||
<h3 class="menu-title">{{ appCard.title }}</h3>
|
(click)="dropdown.openChange.next(false)"
|
||||||
<tui-opt-group label="LAUNCH">
|
>
|
||||||
<button tuiOption class="menu-item">
|
{{ title }}
|
||||||
<tui-svg src="tuiIconLogOut" class="menu-icon"></tui-svg>
|
</app-actions>
|
||||||
Tor
|
|
||||||
</button>
|
|
||||||
</tui-opt-group>
|
|
||||||
<tui-opt-group label="MANAGE">
|
|
||||||
<button tuiOption class="menu-item">
|
|
||||||
<tui-svg src="tuiIconSliders" class="menu-icon"></tui-svg>
|
|
||||||
Console
|
|
||||||
</button>
|
|
||||||
<button tuiOption class="menu-item">
|
|
||||||
<tui-svg src="tuiIconX" class="menu-icon"></tui-svg>
|
|
||||||
Remove from desktop
|
|
||||||
</button>
|
|
||||||
</tui-opt-group>
|
|
||||||
</tui-data-list>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</tui-hosted-dropdown>
|
</tui-hosted-dropdown>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -43,20 +43,3 @@
|
|||||||
// TODO: Theme
|
// TODO: Theme
|
||||||
background: #4b4a4a;
|
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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -13,10 +14,10 @@ import {
|
|||||||
TuiHostedDropdownModule,
|
TuiHostedDropdownModule,
|
||||||
TuiSvgModule,
|
TuiSvgModule,
|
||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import {
|
import { NavigationService } from '../navigation/navigation.service'
|
||||||
NavigationItem,
|
import { Action, ActionsComponent } from '../actions/actions.component'
|
||||||
NavigationService,
|
import { ToDesktopActionsPipe } from '../../pipes/to-desktop-actions'
|
||||||
} from '../navigation/navigation.service'
|
import { toRouterLink } from '../../utils/to-router-link'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[appCard]',
|
selector: '[appCard]',
|
||||||
@@ -25,22 +26,37 @@ import {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
|
CommonModule,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
TuiButtonModule,
|
TuiButtonModule,
|
||||||
TuiHostedDropdownModule,
|
TuiHostedDropdownModule,
|
||||||
TuiDataListModule,
|
TuiDataListModule,
|
||||||
TuiSvgModule,
|
TuiSvgModule,
|
||||||
TickerModule,
|
TickerModule,
|
||||||
|
ActionsComponent,
|
||||||
|
ToDesktopActionsPipe,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CardComponent {
|
export class CardComponent {
|
||||||
private readonly navigation = inject(NavigationService)
|
private readonly navigation = inject(NavigationService)
|
||||||
|
|
||||||
@Input({ required: true })
|
@Input()
|
||||||
appCard!: NavigationItem
|
id = ''
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
icon = ''
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
title = ''
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
actions: Record<string, readonly Action[]> = {}
|
||||||
|
|
||||||
@HostListener('click')
|
@HostListener('click')
|
||||||
onClick() {
|
onClick() {
|
||||||
this.navigation.addTab(this.appCard)
|
const { id, icon, title } = this
|
||||||
|
const routerLink = toRouterLink(id)
|
||||||
|
|
||||||
|
this.navigation.addTab({ icon, title, routerLink })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,26 +13,37 @@
|
|||||||
>
|
>
|
||||||
Enter service name
|
Enter service name
|
||||||
</tui-input>
|
</tui-input>
|
||||||
<h2 class="title">System Utilities</h2>
|
<tui-scrollbar class="scrollbar">
|
||||||
<div class="items">
|
<h2 class="title">System Utilities</h2>
|
||||||
<a
|
<div class="items">
|
||||||
*ngFor="let item of system | tuiFilter : bySearch : search; empty: empty"
|
<a
|
||||||
[appCard]="item"
|
*ngFor="
|
||||||
[routerLink]="item.routerLink"
|
let item of system | keyvalue | tuiFilter : bySearch : search;
|
||||||
(click)="open = false"
|
empty: empty
|
||||||
></a>
|
"
|
||||||
</div>
|
appCard
|
||||||
<h2 class="title">Installed services</h2>
|
[id]="item.key"
|
||||||
<div class="items">
|
[title]="item.value.title"
|
||||||
<a
|
[icon]="item.value.icon"
|
||||||
*ngFor="
|
[routerLink]="item.key"
|
||||||
let item of (services$ | async) || [] | tuiFilter : bySearch : search;
|
(click)="open = false"
|
||||||
empty: empty
|
></a>
|
||||||
"
|
</div>
|
||||||
[appCard]="item"
|
<h2 class="title">Installed services</h2>
|
||||||
[routerLink]="item.routerLink"
|
<div class="items">
|
||||||
(click)="open = false"
|
<a
|
||||||
></a>
|
*ngFor="
|
||||||
</div>
|
let item of (services$ | async) || [] | tuiFilter : bySearch : search;
|
||||||
<ng-template #empty>Nothing found</ng-template>
|
empty: empty
|
||||||
|
"
|
||||||
|
appCard
|
||||||
|
[id]="item.manifest.id"
|
||||||
|
[icon]="item.icon"
|
||||||
|
[title]="item.manifest.title"
|
||||||
|
[routerLink]="getLink(item.manifest.id)"
|
||||||
|
(click)="open = false"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<ng-template #empty>Nothing found</ng-template>
|
||||||
|
</tui-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: calc(100% - 10.25rem);
|
height: calc(100% - 10.25rem);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
// TODO: Theme
|
// TODO: Theme
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: inherit;
|
background: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +51,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scrollbar {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
max-width: 41rem;
|
width: 25rem;
|
||||||
margin: 6rem auto 0;
|
margin: 6rem auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 5rem 0 1.25rem;
|
margin: 4rem 0 1.25rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font: var(--tui-font-text-xl);
|
font: var(--tui-font-text-xl);
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ import {
|
|||||||
TuiFilterPipeModule,
|
TuiFilterPipeModule,
|
||||||
TuiForModule,
|
TuiForModule,
|
||||||
} from '@taiga-ui/cdk'
|
} 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 { TuiInputModule } from '@taiga-ui/kit'
|
||||||
import { map } from 'rxjs'
|
|
||||||
import { CardComponent } from '../card/card.component'
|
import { CardComponent } from '../card/card.component'
|
||||||
import { NavigationItem } from '../navigation/navigation.service'
|
|
||||||
import { ServicesService } from '../../services/services.service'
|
import { ServicesService } from '../../services/services.service'
|
||||||
import { SYSTEM_UTILITIES } from './drawer.const'
|
import { SYSTEM_UTILITIES } from './drawer.const'
|
||||||
import { toNavigationItem } from '../../utils/to-navigation-item'
|
import { toRouterLink } from '../../utils/to-router-link'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-drawer',
|
selector: 'app-drawer',
|
||||||
@@ -32,6 +34,7 @@ import { toNavigationItem } from '../../utils/to-navigation-item'
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
TuiSvgModule,
|
TuiSvgModule,
|
||||||
|
TuiScrollbarModule,
|
||||||
TuiActiveZoneModule,
|
TuiActiveZoneModule,
|
||||||
TuiInputModule,
|
TuiInputModule,
|
||||||
TuiTextfieldControllerModule,
|
TuiTextfieldControllerModule,
|
||||||
@@ -48,10 +51,13 @@ export class DrawerComponent {
|
|||||||
search = ''
|
search = ''
|
||||||
|
|
||||||
readonly system = SYSTEM_UTILITIES
|
readonly system = SYSTEM_UTILITIES
|
||||||
readonly services$ = inject(ServicesService).pipe(
|
readonly services$ = inject(ServicesService)
|
||||||
map(services => services.map(toNavigationItem)),
|
|
||||||
)
|
|
||||||
|
|
||||||
readonly bySearch = (item: NavigationItem, search: string): boolean =>
|
readonly bySearch = (item: any, search: string): boolean =>
|
||||||
search.length < 2 || TUI_DEFAULT_MATCHER(item.title, search)
|
search.length < 2 ||
|
||||||
|
TUI_DEFAULT_MATCHER(item.manifest?.title || item.value?.title || '', search)
|
||||||
|
|
||||||
|
getLink(id: string): string {
|
||||||
|
return toRouterLink(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
import { NavigationItem } from '../navigation/navigation.service'
|
export const SYSTEM_UTILITIES: Record<string, { icon: string; title: string }> =
|
||||||
|
|
||||||
export const SYSTEM_UTILITIES: readonly NavigationItem[] = [
|
|
||||||
{
|
{
|
||||||
title: 'Devices',
|
'/portal/system/devices': {
|
||||||
routerLink: 'devices',
|
icon: 'assets/img/icon_transparent.png',
|
||||||
icon: 'assets/img/icon_transparent.png',
|
title: 'Devices',
|
||||||
},
|
},
|
||||||
{
|
'/portal/system/metrics': {
|
||||||
title: 'Metrics',
|
icon: 'assets/img/icon_transparent.png',
|
||||||
routerLink: 'metrics',
|
title: 'Metrics',
|
||||||
icon: 'assets/img/icon_transparent.png',
|
},
|
||||||
},
|
'/portal/system/manual': {
|
||||||
{
|
icon: 'assets/img/icon_transparent.png',
|
||||||
title: 'User manual',
|
title: 'Manual',
|
||||||
routerLink: 'manual',
|
},
|
||||||
icon: 'assets/img/icon_transparent.png',
|
'/portal/system/snek': {
|
||||||
},
|
icon: 'assets/img/icon_transparent.png',
|
||||||
{
|
title: 'Snek',
|
||||||
title: 'Snek',
|
},
|
||||||
routerLink: 'snek',
|
}
|
||||||
icon: 'assets/img/icon_transparent.png',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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<string, readonly Action[]>,
|
||||||
|
id: string,
|
||||||
|
): Observable<Record<string, readonly Action[]>> {
|
||||||
|
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],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string, PackageDataEntry>,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,4 +6,5 @@
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ const ROUTES: Routes = [
|
|||||||
m => m.ServicesModule,
|
m => m.ServicesModule,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'system',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./routes/system/system.module').then(m => m.SystemModule),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
<a
|
<ng-container *ngIf="desktop$ | async as desktop">
|
||||||
*ngFor="let service of services$ | async"
|
<tui-tiles
|
||||||
[appCard]="service | toNavigationItem"
|
*ngIf="packages$ | async as packages"
|
||||||
[routerLink]="(service | toNavigationItem).routerLink"
|
class="tiles"
|
||||||
></a>
|
[debounce]="500"
|
||||||
|
[order]="order"
|
||||||
|
(orderChange)="onReorder($event, desktop)"
|
||||||
|
>
|
||||||
|
<tui-tile
|
||||||
|
*ngFor="let service of desktop; let index = index"
|
||||||
|
class="item"
|
||||||
|
[style.order]="order.get(index)"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
*ngIf="packages | toDesktopItem : service as item"
|
||||||
|
tuiTileHandle
|
||||||
|
appCard
|
||||||
|
[id]="service"
|
||||||
|
[title]="item.title"
|
||||||
|
[icon]="item.icon"
|
||||||
|
[routerLink]="item.routerLink"
|
||||||
|
></a>
|
||||||
|
</tui-tile>
|
||||||
|
</tui-tiles>
|
||||||
|
</ng-container>
|
||||||
|
|||||||
@@ -3,9 +3,16 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 56rem;
|
max-width: 56rem;
|
||||||
margin: 0 auto;
|
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;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
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({
|
@Component({
|
||||||
templateUrl: 'desktop.component.html',
|
templateUrl: 'desktop.component.html',
|
||||||
@@ -7,6 +10,26 @@ import { ServicesService } from '../../services/services.service'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DesktopComponent {
|
export class DesktopComponent {
|
||||||
// TODO: Only show services added to desktop
|
private readonly desktop = inject(DesktopService)
|
||||||
readonly services$ = inject(ServicesService)
|
|
||||||
|
readonly desktop$ = this.desktop.desktop$.pipe(
|
||||||
|
tap(() => (this.order = new Map())),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly packages$ =
|
||||||
|
inject<PatchDB<DataModel>>(PatchDB).watch$('package-data')
|
||||||
|
|
||||||
|
order = new Map()
|
||||||
|
|
||||||
|
onReorder(order: Map<number, number>, 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
|
import { TuiTilesModule } from '@taiga-ui/kit'
|
||||||
import { DesktopComponent } from './desktop.component'
|
import { DesktopComponent } from './desktop.component'
|
||||||
import { CardComponent } from '../../components/card/card.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 = [
|
const ROUTES: Routes = [
|
||||||
{
|
{
|
||||||
@@ -16,7 +18,9 @@ const ROUTES: Routes = [
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
CardComponent,
|
CardComponent,
|
||||||
ToNavigationItemPipe,
|
TuiTilesModule,
|
||||||
|
ToDesktopActionsPipe,
|
||||||
|
ToDesktopItemPipe,
|
||||||
RouterModule.forChild(ROUTES),
|
RouterModule.forChild(ROUTES),
|
||||||
],
|
],
|
||||||
declarations: [DesktopComponent],
|
declarations: [DesktopComponent],
|
||||||
|
|||||||
@@ -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<readonly string[] | undefined>(
|
||||||
|
undefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
inject<PatchDB<DataModel>>(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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { PatchDB } from 'patch-db-client'
|
|||||||
import { tap } from 'rxjs'
|
import { tap } from 'rxjs'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { NavigationService } from '../../components/navigation/navigation.service'
|
import { NavigationService } from '../../components/navigation/navigation.service'
|
||||||
|
import { toRouterLink } from '../../utils/to-router-link'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'service.component.html',
|
templateUrl: 'service.component.html',
|
||||||
@@ -26,9 +27,9 @@ export class ServiceComponent {
|
|||||||
this.router.navigate(['..'], { relativeTo: this.route })
|
this.router.navigate(['..'], { relativeTo: this.route })
|
||||||
} else {
|
} else {
|
||||||
this.navigation.addTab({
|
this.navigation.addTab({
|
||||||
title: pkg.manifest.title,
|
|
||||||
routerLink: `/portal/services/${pkg.manifest.id}`,
|
|
||||||
icon: pkg.icon,
|
icon: pkg.icon,
|
||||||
|
title: pkg.manifest.title,
|
||||||
|
routerLink: toRouterLink(pkg.manifest.id),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: 'Here be snek',
|
||||||
|
standalone: true,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class SnekComponent {}
|
||||||
@@ -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 {}
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function toRouterLink(id: string): string {
|
||||||
|
return id.includes('/') ? id : `/portal/services/${id}`
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { RouterModule } from '@angular/router'
|
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 { ToastContainerComponent } from './toast-container.component'
|
||||||
import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component'
|
import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component'
|
||||||
import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component'
|
import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component'
|
||||||
import { UpdateToastComponent } from './update-toast/update-toast.component'
|
import { UpdateToastComponent } from './update-toast/update-toast.component'
|
||||||
import { TuiButtonModule, TuiDialogModule } from '@taiga-ui/core'
|
|
||||||
import { TuiAutoFocusModule } from '@taiga-ui/cdk'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const mockPatchData: DataModel = {
|
|||||||
name: `Matt's Server`,
|
name: `Matt's Server`,
|
||||||
'ack-welcome': '1.0.0',
|
'ack-welcome': '1.0.0',
|
||||||
theme: 'Dark',
|
theme: 'Dark',
|
||||||
|
desktop: ['lnd'],
|
||||||
widgets: BUILT_IN_WIDGETS.filter(
|
widgets: BUILT_IN_WIDGETS.filter(
|
||||||
({ id }) =>
|
({ id }) =>
|
||||||
id === 'favorites' ||
|
id === 'favorites' ||
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Url } from '@start9labs/shared'
|
|||||||
import { Manifest } from '@start9labs/marketplace'
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
import { BackupJob } from '../api/api.types'
|
import { BackupJob } from '../api/api.types'
|
||||||
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
||||||
import { CustomSpec } from 'src/app/apps/ui/pages/system/domains/domain.const'
|
|
||||||
|
|
||||||
export interface DataModel {
|
export interface DataModel {
|
||||||
'server-info': ServerInfo
|
'server-info': ServerInfo
|
||||||
@@ -23,6 +22,7 @@ export interface UIData {
|
|||||||
'ack-instructions': Record<string, boolean>
|
'ack-instructions': Record<string, boolean>
|
||||||
theme: string
|
theme: string
|
||||||
widgets: readonly Widget[]
|
widgets: readonly Widget[]
|
||||||
|
desktop: readonly string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Widget {
|
export interface Widget {
|
||||||
|
|||||||
Reference in New Issue
Block a user