mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +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;
|
||||
}
|
||||
}
|
||||
|
||||
[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)"
|
||||
>
|
||||
<ion-menu
|
||||
*ngIf="navigation$ | async"
|
||||
contentId="main-content"
|
||||
type="overlay"
|
||||
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;
|
||||
align-items: center;
|
||||
height: 4.5rem;
|
||||
background: rgb(51 51 51 / 74%);
|
||||
padding: 0 1rem 0 2rem;
|
||||
font-size: 1.5rem;
|
||||
// TODO: Theme
|
||||
background: rgb(51 51 51 / 74%);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<a
|
||||
class="tab"
|
||||
routerLink="services"
|
||||
routerLink="desktop"
|
||||
routerLinkActive="tab_active"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
:host {
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
// TODO: Theme
|
||||
background: rgb(97 95 95 / 75%);
|
||||
}
|
||||
|
||||
@@ -12,6 +13,7 @@
|
||||
width: 7.5rem;
|
||||
|
||||
&_active {
|
||||
// TODO: Theme
|
||||
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>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
<app-drawer></app-drawer>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
:host {
|
||||
// TODO: Theme
|
||||
background: url(/assets/img/background_dark.jpeg);
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { tuiDropdownOptionsProvider } from '@taiga-ui/core'
|
||||
|
||||
@Component({
|
||||
templateUrl: 'portal.component.html',
|
||||
styleUrls: ['portal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
// TODO: Move to global
|
||||
tuiDropdownOptionsProvider({
|
||||
appearance: 'start-os',
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class PortalComponent {}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router'
|
||||
import { HeaderComponent } from './components/header/header.component'
|
||||
import { PortalComponent } from './portal.component'
|
||||
import { NavigationComponent } from './components/navigation/navigation.component'
|
||||
import { DrawerComponent } from './components/drawer/drawer.component'
|
||||
|
||||
const ROUTES: Routes = [
|
||||
{
|
||||
@@ -10,10 +11,15 @@ const ROUTES: Routes = [
|
||||
component: PortalComponent,
|
||||
children: [
|
||||
{
|
||||
redirectTo: 'services',
|
||||
redirectTo: 'desktop',
|
||||
pathMatch: 'full',
|
||||
path: '',
|
||||
},
|
||||
{
|
||||
path: 'desktop',
|
||||
loadChildren: () =>
|
||||
import('./routes/desktop/desktop.module').then(m => m.DesktopModule),
|
||||
},
|
||||
{
|
||||
path: 'services',
|
||||
loadChildren: () =>
|
||||
@@ -30,6 +36,7 @@ const ROUTES: Routes = [
|
||||
RouterModule.forChild(ROUTES),
|
||||
HeaderComponent,
|
||||
NavigationComponent,
|
||||
DrawerComponent,
|
||||
],
|
||||
declarations: [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 { tap } from 'rxjs'
|
||||
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({
|
||||
templateUrl: 'service.component.html',
|
||||
@@ -27,7 +27,7 @@ export class ServiceComponent {
|
||||
} else {
|
||||
this.navigation.addTab({
|
||||
title: pkg.manifest.title,
|
||||
routerLink: ['services', pkg.manifest.id].join('/'),
|
||||
routerLink: `/portal/services/${pkg.manifest.id}`,
|
||||
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 { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { ServicesComponent } from './services.component'
|
||||
import { ServiceComponent } from './service.component'
|
||||
|
||||
const ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ServicesComponent,
|
||||
},
|
||||
{
|
||||
path: ':pkgId',
|
||||
loadChildren: () =>
|
||||
import('./service/service.module').then(m => m.ServiceModule),
|
||||
component: ServiceComponent,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule.forChild(ROUTES)],
|
||||
declarations: [ServicesComponent],
|
||||
exports: [ServicesComponent],
|
||||
declarations: [ServiceComponent],
|
||||
exports: [ServiceComponent],
|
||||
})
|
||||
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