mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
feat(portal): basis for drawer and cards (#2370)
This commit is contained in:
@@ -59,3 +59,32 @@ tui-hint[data-appearance='onDark'] {
|
|||||||
color: var(--tui-link-hover) !important;
|
color: var(--tui-link-hover) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[tuiWrapper][data-appearance='drawer'] {
|
||||||
|
// TODO: Theme
|
||||||
|
background: rgb(81 80 83 / 86%);
|
||||||
|
border-radius: 10rem;
|
||||||
|
|
||||||
|
&._focused::after {
|
||||||
|
color: var(--tui-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tui-dropdown[data-appearance='start-os'][data-appearance='start-os'] {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0 0.25rem 0.25rem rgb(0 0 0 / 25%);
|
||||||
|
// TODO: Replace --tui-elevation-02 when Taiga UI is updated
|
||||||
|
background: rgb(63 63 63 / 95%);
|
||||||
|
|
||||||
|
tui-opt-group {
|
||||||
|
&::before {
|
||||||
|
background: var(--tui-clear);
|
||||||
|
box-shadow: 1rem 0 var(--tui-clear), -1rem 0 var(--tui-clear);
|
||||||
|
padding-top: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
(ionSplitPaneVisible)="splitPaneVisible($event)"
|
(ionSplitPaneVisible)="splitPaneVisible($event)"
|
||||||
>
|
>
|
||||||
<ion-menu
|
<ion-menu
|
||||||
|
*ngIf="navigation$ | async"
|
||||||
contentId="main-content"
|
contentId="main-content"
|
||||||
type="overlay"
|
type="overlay"
|
||||||
side="start"
|
side="start"
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<span class="link">
|
||||||
|
<img alt="" class="icon" [src]="appCard.icon" />
|
||||||
|
<label ticker class="title">{{ appCard.title }}</label>
|
||||||
|
</span>
|
||||||
|
<span class="side">
|
||||||
|
<tui-hosted-dropdown [content]="content" (click.stop.prevent)="(0)">
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="outline"
|
||||||
|
shape="rounded"
|
||||||
|
size="xs"
|
||||||
|
icon="tuiIconMoreHorizontal"
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</button>
|
||||||
|
<ng-template #content>
|
||||||
|
<!-- TODO: Move menu to a separate component -->
|
||||||
|
<tui-data-list>
|
||||||
|
<h3 class="menu-title">{{ appCard.title }}</h3>
|
||||||
|
<tui-opt-group label="LAUNCH">
|
||||||
|
<button tuiOption class="menu-item">
|
||||||
|
<tui-svg src="tuiIconLogOut" class="menu-icon"></tui-svg>
|
||||||
|
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>
|
||||||
|
</tui-hosted-dropdown>
|
||||||
|
</span>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
height: 5.5rem;
|
||||||
|
width: 12.5rem;
|
||||||
|
border-radius: var(--tui-radius-l);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 0.25rem 0.25rem rgb(0 0 0 / 25%);
|
||||||
|
// TODO: Theme
|
||||||
|
background: rgb(111 109 109 / 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
font: var(--tui-font-text-m);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
box-shadow: 0.25rem 0.25rem 0.25rem rgb(0 0 0 / 25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side {
|
||||||
|
width: 3rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 0.25rem 0.25rem rgb(0 0 0 / 25%);
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
HostListener,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
|
import { TickerModule } from '@start9labs/shared'
|
||||||
|
import {
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiDataListModule,
|
||||||
|
TuiHostedDropdownModule,
|
||||||
|
TuiSvgModule,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
NavigationItem,
|
||||||
|
NavigationService,
|
||||||
|
} from '../navigation/navigation.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: '[appCard]',
|
||||||
|
templateUrl: 'card.component.html',
|
||||||
|
styleUrls: ['card.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [
|
||||||
|
RouterLink,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiHostedDropdownModule,
|
||||||
|
TuiDataListModule,
|
||||||
|
TuiSvgModule,
|
||||||
|
TickerModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CardComponent {
|
||||||
|
private readonly navigation = inject(NavigationService)
|
||||||
|
|
||||||
|
@Input({ required: true })
|
||||||
|
appCard!: NavigationItem
|
||||||
|
|
||||||
|
@HostListener('click')
|
||||||
|
onClick() {
|
||||||
|
this.navigation.addTab(this.appCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<div class="content" (tuiActiveZoneChange)="open = $event">
|
||||||
|
<button class="toggle" (click)="open = !open" (mousedown.prevent)="(0)">
|
||||||
|
<tui-svg src="tuiIconArrowUpCircleLarge" class="icon"></tui-svg>
|
||||||
|
Toggle drawer
|
||||||
|
</button>
|
||||||
|
<tui-input
|
||||||
|
class="search"
|
||||||
|
tuiTextfieldAppearance="drawer"
|
||||||
|
tuiTextfieldSize="m"
|
||||||
|
tuiTextfieldIconLeft="tuiIconSearchLarge"
|
||||||
|
[tuiTextfieldLabelOutside]="true"
|
||||||
|
[(ngModel)]="search"
|
||||||
|
>
|
||||||
|
Enter service name
|
||||||
|
</tui-input>
|
||||||
|
<h2 class="title">System Utilities</h2>
|
||||||
|
<div class="items">
|
||||||
|
<a
|
||||||
|
*ngFor="let item of system | tuiFilter : bySearch : search; empty: empty"
|
||||||
|
[appCard]="item"
|
||||||
|
[routerLink]="item.routerLink"
|
||||||
|
(click)="open = false"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">Installed services</h2>
|
||||||
|
<div class="items">
|
||||||
|
<a
|
||||||
|
*ngFor="
|
||||||
|
let item of (services$ | async) || [] | tuiFilter : bySearch : search;
|
||||||
|
empty: empty
|
||||||
|
"
|
||||||
|
[appCard]="item"
|
||||||
|
[routerLink]="item.routerLink"
|
||||||
|
(click)="open = false"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<ng-template #empty>Nothing found</ng-template>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
@include transition(top);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100% - 10.25rem);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// TODO: Theme
|
||||||
|
background: #2d2d2d;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&._open {
|
||||||
|
top: 10.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: -2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
width: 25rem;
|
||||||
|
max-width: 100vw;
|
||||||
|
left: 50%;
|
||||||
|
background: inherit;
|
||||||
|
color: inherit;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-top-left-radius: var(--tui-radius-xl);
|
||||||
|
border-top-right-radius: var(--tui-radius-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@include transition(transform);
|
||||||
|
|
||||||
|
:host._open & {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
max-width: 41rem;
|
||||||
|
margin: 6rem auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 5rem 0 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font: var(--tui-font-text-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 2rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
HostBinding,
|
||||||
|
inject,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
|
import {
|
||||||
|
TUI_DEFAULT_MATCHER,
|
||||||
|
TuiActiveZoneModule,
|
||||||
|
TuiFilterPipeModule,
|
||||||
|
TuiForModule,
|
||||||
|
} from '@taiga-ui/cdk'
|
||||||
|
import { 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'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-drawer',
|
||||||
|
templateUrl: 'drawer.component.html',
|
||||||
|
styleUrls: ['drawer.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
TuiSvgModule,
|
||||||
|
TuiActiveZoneModule,
|
||||||
|
TuiInputModule,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
TuiForModule,
|
||||||
|
TuiFilterPipeModule,
|
||||||
|
CardComponent,
|
||||||
|
RouterLink,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class DrawerComponent {
|
||||||
|
@HostBinding('class._open')
|
||||||
|
open = false
|
||||||
|
|
||||||
|
search = ''
|
||||||
|
|
||||||
|
readonly system = SYSTEM_UTILITIES
|
||||||
|
readonly services$ = inject(ServicesService).pipe(
|
||||||
|
map(services => services.map(toNavigationItem)),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly bySearch = (item: NavigationItem, search: string): boolean =>
|
||||||
|
search.length < 2 || TUI_DEFAULT_MATCHER(item.title, search)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { NavigationItem } from '../navigation/navigation.service'
|
||||||
|
|
||||||
|
export const SYSTEM_UTILITIES: readonly NavigationItem[] = [
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 4.5rem;
|
height: 4.5rem;
|
||||||
background: rgb(51 51 51 / 74%);
|
|
||||||
padding: 0 1rem 0 2rem;
|
padding: 0 1rem 0 2rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
// TODO: Theme
|
||||||
|
background: rgb(51 51 51 / 74%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<a
|
<a
|
||||||
class="tab"
|
class="tab"
|
||||||
routerLink="services"
|
routerLink="desktop"
|
||||||
routerLinkActive="tab_active"
|
routerLinkActive="tab_active"
|
||||||
[routerLinkActiveOptions]="{ exact: true }"
|
[routerLinkActiveOptions]="{ exact: true }"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
:host {
|
:host {
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
// TODO: Theme
|
||||||
background: rgb(97 95 95 / 75%);
|
background: rgb(97 95 95 / 75%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
width: 7.5rem;
|
width: 7.5rem;
|
||||||
|
|
||||||
&_active {
|
&_active {
|
||||||
|
// TODO: Theme
|
||||||
background: #373a3f;
|
background: #373a3f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,3 +3,4 @@
|
|||||||
<main>
|
<main>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
|
<app-drawer></app-drawer>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
:host {
|
:host {
|
||||||
|
// TODO: Theme
|
||||||
background: url(/assets/img/background_dark.jpeg);
|
background: url(/assets/img/background_dark.jpeg);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
import { tuiDropdownOptionsProvider } from '@taiga-ui/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'portal.component.html',
|
templateUrl: 'portal.component.html',
|
||||||
styleUrls: ['portal.component.scss'],
|
styleUrls: ['portal.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [
|
||||||
|
// TODO: Move to global
|
||||||
|
tuiDropdownOptionsProvider({
|
||||||
|
appearance: 'start-os',
|
||||||
|
}),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class PortalComponent {}
|
export class PortalComponent {}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router'
|
|||||||
import { HeaderComponent } from './components/header/header.component'
|
import { HeaderComponent } from './components/header/header.component'
|
||||||
import { PortalComponent } from './portal.component'
|
import { PortalComponent } from './portal.component'
|
||||||
import { NavigationComponent } from './components/navigation/navigation.component'
|
import { NavigationComponent } from './components/navigation/navigation.component'
|
||||||
|
import { DrawerComponent } from './components/drawer/drawer.component'
|
||||||
|
|
||||||
const ROUTES: Routes = [
|
const ROUTES: Routes = [
|
||||||
{
|
{
|
||||||
@@ -10,10 +11,15 @@ const ROUTES: Routes = [
|
|||||||
component: PortalComponent,
|
component: PortalComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
redirectTo: 'services',
|
redirectTo: 'desktop',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
path: '',
|
path: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'desktop',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./routes/desktop/desktop.module').then(m => m.DesktopModule),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'services',
|
path: 'services',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
@@ -30,6 +36,7 @@ const ROUTES: Routes = [
|
|||||||
RouterModule.forChild(ROUTES),
|
RouterModule.forChild(ROUTES),
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
NavigationComponent,
|
NavigationComponent,
|
||||||
|
DrawerComponent,
|
||||||
],
|
],
|
||||||
declarations: [PortalComponent],
|
declarations: [PortalComponent],
|
||||||
exports: [PortalComponent],
|
exports: [PortalComponent],
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<a
|
||||||
|
*ngFor="let service of services$ | async"
|
||||||
|
[appCard]="service | toNavigationItem"
|
||||||
|
[routerLink]="(service | toNavigationItem).routerLink"
|
||||||
|
></a>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 56rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
|
import { ServicesService } from '../../services/services.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: 'desktop.component.html',
|
||||||
|
styleUrls: ['desktop.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DesktopComponent {
|
||||||
|
// TODO: Only show services added to desktop
|
||||||
|
readonly services$ = inject(ServicesService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
|
import { DesktopComponent } from './desktop.component'
|
||||||
|
import { CardComponent } from '../../components/card/card.component'
|
||||||
|
import { ToNavigationItemPipe } from '../../pipes/to-navigation-item'
|
||||||
|
|
||||||
|
const ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: DesktopComponent,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
CardComponent,
|
||||||
|
ToNavigationItemPipe,
|
||||||
|
RouterModule.forChild(ROUTES),
|
||||||
|
],
|
||||||
|
declarations: [DesktopComponent],
|
||||||
|
exports: [DesktopComponent],
|
||||||
|
})
|
||||||
|
export class DesktopModule {}
|
||||||
@@ -4,7 +4,7 @@ import { getPkgId } from '@start9labs/shared'
|
|||||||
import { PatchDB } from 'patch-db-client'
|
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'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'service.component.html',
|
templateUrl: 'service.component.html',
|
||||||
@@ -27,7 +27,7 @@ export class ServiceComponent {
|
|||||||
} else {
|
} else {
|
||||||
this.navigation.addTab({
|
this.navigation.addTab({
|
||||||
title: pkg.manifest.title,
|
title: pkg.manifest.title,
|
||||||
routerLink: ['services', pkg.manifest.id].join('/'),
|
routerLink: `/portal/services/${pkg.manifest.id}`,
|
||||||
icon: pkg.icon,
|
icon: pkg.icon,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { ServiceComponent } from './service.component'
|
|
||||||
|
|
||||||
const ROUTES: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: ServiceComponent,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule, RouterModule.forChild(ROUTES)],
|
|
||||||
declarations: [ServiceComponent],
|
|
||||||
exports: [ServiceComponent],
|
|
||||||
})
|
|
||||||
export class ServiceModule {}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<a
|
|
||||||
*ngFor="let service of services$ | async"
|
|
||||||
[routerLink]="service.manifest.id"
|
|
||||||
(click)="onClick(service)"
|
|
||||||
>
|
|
||||||
{{ service.manifest.title }}
|
|
||||||
</a>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { NavigationService } from '../../components/navigation/navigation.service'
|
|
||||||
import { ServicesService } from './services.service'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: 'services.component.html',
|
|
||||||
styleUrls: ['services.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class ServicesComponent {
|
|
||||||
private readonly navigation = inject(NavigationService)
|
|
||||||
|
|
||||||
readonly services$ = inject(ServicesService)
|
|
||||||
|
|
||||||
onClick({ manifest, icon }: PackageDataEntry) {
|
|
||||||
this.navigation.addTab({
|
|
||||||
title: manifest.title,
|
|
||||||
routerLink: ['services', manifest.id].join('/'),
|
|
||||||
icon,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
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 { ServicesComponent } from './services.component'
|
import { ServiceComponent } from './service.component'
|
||||||
|
|
||||||
const ROUTES: Routes = [
|
const ROUTES: Routes = [
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: ServicesComponent,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: ':pkgId',
|
path: ':pkgId',
|
||||||
loadChildren: () =>
|
component: ServiceComponent,
|
||||||
import('./service/service.module').then(m => m.ServiceModule),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, RouterModule.forChild(ROUTES)],
|
imports: [CommonModule, RouterModule.forChild(ROUTES)],
|
||||||
declarations: [ServicesComponent],
|
declarations: [ServiceComponent],
|
||||||
exports: [ServicesComponent],
|
exports: [ServiceComponent],
|
||||||
})
|
})
|
||||||
export class ServicesModule {}
|
export class ServicesModule {}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user