show available marketplace updates in menu (#1613)

* show service updates in menu
This commit is contained in:
Lucy C
2022-07-05 13:02:32 -06:00
committed by GitHub
parent e2d58c2959
commit 88afb756f5
17 changed files with 162 additions and 107 deletions

View File

@@ -7,5 +7,7 @@
(click)="switchCategory(cat)"
>
{{ cat }}
<span *ngIf="cat === 'updates'"> &nbsp; ({{ updatesAvailable }}) </span>
<span *ngIf="cat === 'updates' && updatesAvailable">
&nbsp; ({{ updatesAvailable }})
</span>
</ion-button>

View File

@@ -23,7 +23,7 @@ export class CategoriesComponent {
category = ''
@Input()
updatesAvailable? = 0
updatesAvailable = 0
@Output()
readonly categoryChange = new EventEmitter<string>()

View File

@@ -7,7 +7,7 @@ export abstract class AbstractMarketplaceService {
abstract getReleaseNotes(id: string): Observable<Record<string, string>>
abstract getCategories(): Observable<string[]>
abstract getCategories(): Observable<Set<string>>
abstract getPackages(): Observable<MarketplacePkg[]>

View File

@@ -14,10 +14,12 @@ import { GlobalErrorHandler } from './services/global-error-handler.service'
import { AuthService } from './services/auth.service'
import { LocalStorageService } from './services/local-storage.service'
import { DataModel } from './services/patch-db/data-model'
import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
export const APP_PROVIDERS: Provider[] = [
FilterPackagesPipe,
FormBuilder,
IonNav,
{

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core'
import { Inject, Injectable } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { Observable, of } from 'rxjs'
import { filter, share, switchMap, take, tap } from 'rxjs/operators'
@@ -11,6 +11,8 @@ import { OSWelcomePage } from 'src/app/modals/os-welcome/os-welcome.page'
import { ConfigService } from 'src/app/services/config.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchMonitorService } from './patch-monitor.service'
import { MarketplaceService } from 'src/app/services/marketplace.service'
import { AbstractMarketplaceService } from '../../../../../../marketplace/src/services/marketplace.service'
// Get data from PatchDb after is starts and act upon it
@Injectable({
@@ -24,8 +26,8 @@ export class PatchDataService extends Observable<DataModel | null> {
filter(obj => !isEmptyObject(obj)),
take(1),
tap(({ ui }) => {
// check for updates to EOS
this.checkForEosUpdate(ui)
// check for updates to EOS and services
this.checkForUpdates(ui)
// show eos welcome message
this.showEosWelcome(ui['ack-welcome'])
}),
@@ -42,13 +44,17 @@ export class PatchDataService extends Observable<DataModel | null> {
private readonly config: ConfigService,
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
@Inject(AbstractMarketplaceService)
private readonly marketplaceService: MarketplaceService,
) {
super(subscriber => this.stream$.subscribe(subscriber))
}
private checkForEosUpdate(ui: UIData): void {
private checkForUpdates(ui: UIData): void {
if (ui['auto-check-updates'] !== false) {
this.eosService.getEOS()
this.marketplaceService.getPackages().pipe(take(1)).subscribe()
this.marketplaceService.getCategories().pipe(take(1)).subscribe()
}
}

View File

@@ -1,19 +1,11 @@
import { Injectable } from '@angular/core'
import { Router } from '@angular/router'
import {
LoadingController,
ToastController,
ToastOptions,
} from '@ionic/angular'
import { EMPTY, merge, Observable, ObservableInput } from 'rxjs'
import { ToastController, ToastOptions } from '@ionic/angular'
import { EMPTY, Observable, ObservableInput } from 'rxjs'
import { filter, pairwise, switchMap, tap } from 'rxjs/operators'
import { ErrorToastService } from '@start9labs/shared'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ConfigService } from 'src/app/services/config.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchDataService } from './patch-data.service'
import { DataModel, ServerInfo } from 'src/app/services/patch-db/data-model'
import { DataModel } from 'src/app/services/patch-db/data-model'
// Watch unread notification count to display toast
@Injectable()
@@ -64,8 +56,6 @@ export class UnreadToastService extends Observable<unknown> {
private readonly router: Router,
private readonly patchData: PatchDataService,
private readonly patch: PatchDbService,
private readonly config: ConfigService,
private readonly embassyApi: ApiService,
private readonly toastCtrl: ToastController,
) {
super(subscriber => this.stream$.subscribe(subscriber))

View File

@@ -5,9 +5,7 @@
<ion-item-group class="menu">
<ion-menu-toggle *ngFor="let page of pages; let i = index" auto-hide="false">
<ion-item
*ngIf="
page.url !== '/developer' || (localStorageService.showDevTools$ | async)
"
*ngIf="page.url !== '/developer' || (showDevTools$ | async)"
button
class="link"
color="transparent"
@@ -26,19 +24,27 @@
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/embassy' && (eosService.showUpdate$ | async)"
*ngIf="page.url === '/embassy' && (showEOSUpdate$ | async)"
color="success"
size="small"
name="rocket-outline"
></ion-icon>
<ion-badge
*ngIf="
page.url === '/notifications' && (notification$ | async) as count
page.url === '/marketplace' && (updateCount$ | async) as updateCount
"
color="success"
>
{{ updateCount }}
</ion-badge>
<ion-badge
*ngIf="
page.url === '/notifications' &&
(notificationCount$ | async) as notificaitonCount
"
color="danger"
class="badge"
>
{{ count }}
{{ notificaitonCount }}
</ion-badge>
</ion-item>
</ion-menu-toggle>

View File

@@ -25,10 +25,6 @@
}
}
.badge {
margin-right: 3%;
}
.snek {
position: absolute;
bottom: 90px;

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { AlertController } from '@ionic/angular'
import { ConfigService } from '../../services/config.service'
import { LocalStorageService } from '../../services/local-storage.service'
@@ -6,6 +6,10 @@ import { EOSService } from '../../services/eos.service'
import { ApiService } from '../../services/api/embassy-api.service'
import { AuthService } from '../../services/auth.service'
import { PatchDbService } from '../../services/patch-db/patch-db.service'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { AbstractMarketplaceService } from '@start9labs/marketplace'
import { MarketplaceService } from 'src/app/services/marketplace.service'
@Component({
selector: 'app-menu',
@@ -42,19 +46,29 @@ export class MenuComponent {
},
]
readonly notification$ = this.patch.watch$(
readonly notificationCount$ = this.patch.watch$(
'server-info',
'unread-notification-count',
)
readonly showEOSUpdate$ = this.eosService.showUpdate$
readonly showDevTools$ = this.localStorageService.showDevTools$
readonly updateCount$: Observable<number> = this.marketplaceService
.getUpdates()
.pipe(map(pkgs => pkgs.length))
constructor(
private readonly config: ConfigService,
private readonly alertCtrl: AlertController,
private readonly embassyApi: ApiService,
private readonly authService: AuthService,
private readonly patch: PatchDbService,
public readonly localStorageService: LocalStorageService,
public readonly eosService: EOSService,
private readonly localStorageService: LocalStorageService,
private readonly eosService: EOSService,
@Inject(AbstractMarketplaceService)
private readonly marketplaceService: MarketplaceService,
) {}
get href(): string {

View File

@@ -2,7 +2,6 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { MenuComponent } from './menu.component'
import { SnekModule } from '../snek/snek.module'

View File

@@ -24,11 +24,7 @@ export class MarketplaceListPage {
startWith({}),
)
readonly categories$ = this.marketplaceService
.getCategories()
.pipe(
map(categories => new Set(['featured', 'updates', ...categories, 'all'])),
)
readonly categories$ = this.marketplaceService.getCategories()
readonly pkgs$: Observable<MarketplacePkg[]> = this.patch
.watch$('server-info')

View File

@@ -7,23 +7,25 @@
</ion-toolbar>
</ion-header>
<ion-content *ngIf="ui$ | async as ui" class="ion-padding-top">
<ion-item-group *ngIf="server$ | async as server">
<ion-item-divider>General</ion-item-divider>
<ion-item button (click)="presentModalName('Embassy-' + server.id)">
<ion-label>Device Name</ion-label>
<ion-note slot="end">{{ ui.name || 'Embassy-' + server.id }}</ion-note>
</ion-item>
<ion-content class="ion-padding-top">
<ng-container *ngIf="ui$ | async as ui">
<ion-item-group *ngIf="server$ | async as server">
<ion-item-divider>General</ion-item-divider>
<ion-item button (click)="presentModalName('Embassy-' + server.id)">
<ion-label>Device Name</ion-label>
<ion-note slot="end">{{ ui.name || 'Embassy-' + server.id }}</ion-note>
</ion-item>
<ion-item-divider>Marketplace</ion-item-divider>
<ion-item
button
(click)="serverConfig.presentAlert('auto-check-updates', ui['auto-check-updates'] !== false)"
>
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">
{{ ui['auto-check-updates'] !== false ? 'Enabled' : 'Disabled' }}
</ion-note>
</ion-item>
</ion-item-group>
<ion-item-divider>Marketplace</ion-item-divider>
<ion-item
button
(click)="serverConfig.presentAlert('auto-check-updates', ui['auto-check-updates'] !== false)"
>
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">
{{ ui['auto-check-updates'] !== false ? 'Enabled' : 'Disabled' }}
</ion-note>
</ion-item>
</ion-item-group>
</ng-container>
</ion-content>

View File

@@ -1,7 +1,6 @@
import { Component, ViewChild } from '@angular/core'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import {
IonContent,
LoadingController,
ModalController,
ToastController,
@@ -20,7 +19,6 @@ import { LocalStorageService } from '../../../services/local-storage.service'
styleUrls: ['./preferences.page.scss'],
})
export class PreferencesPage {
@ViewChild(IonContent) content: IonContent
clicks = 0
readonly ui$ = this.patch.watch$('ui')
@@ -36,10 +34,6 @@ export class PreferencesPage {
readonly serverConfig: ServerConfigService,
) {}
ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1)
}
async presentModalName(placeholder: string): Promise<void> {
const options: GenericInputOptions = {
title: 'Edit Device Name',

View File

@@ -223,7 +223,7 @@ export class MockApiService extends ApiService {
if (path === '/package/v0/info') {
return {
name: 'Dark9',
name: 'Dark69',
categories: [
'featured',
'bitcoin',

View File

@@ -14,6 +14,15 @@ export const mockPatchData: DataModel = {
'auto-check-updates': true,
'pkg-order': [],
'ack-welcome': '1.0.0',
marketplace: {
'selected-id': '1234',
'known-hosts': {
'1234': {
name: 'Dark9',
url: 'https://test-marketplace.com',
},
},
},
},
'server-info': {
id: 'abcdefgh',

View File

@@ -1,12 +1,13 @@
import { Injectable } from '@angular/core'
import { LoadingController } from '@ionic/angular'
import { Emver, ErrorToastService } from '@start9labs/shared'
import {
MarketplacePkg,
AbstractMarketplaceService,
Marketplace,
FilterPackagesPipe,
MarketplaceData,
} from '@start9labs/marketplace'
import { from, Observable, of } from 'rxjs'
import { from, Observable, of, Subject } from 'rxjs'
import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service'
@@ -29,12 +30,13 @@ import {
@Injectable()
export class MarketplaceService extends AbstractMarketplaceService {
private readonly notes = new Map<string, Record<string, string>>()
private readonly hasPackages$ = new Subject<boolean>()
private readonly altMarketplaceData$: Observable<
private readonly uiMarketplaceData$: Observable<
UIMarketplaceData | undefined
> = this.patch.watch$('ui', 'marketplace').pipe(shareReplay(1))
private readonly marketplace$ = this.altMarketplaceData$.pipe(
private readonly marketplace$ = this.uiMarketplaceData$.pipe(
map(data => this.toMarketplace(data)),
)
@@ -42,48 +44,80 @@ export class MarketplaceService extends AbstractMarketplaceService {
.watch$('server-info')
.pipe(take(1), shareReplay())
private readonly categories$: Observable<string[]> = this.marketplace$.pipe(
switchMap(({ url }) =>
this.serverInfo$.pipe(
switchMap(({ id }) =>
from(this.getMarketplaceData({ 'server-id': id }, url)),
),
),
),
map(({ categories }) => categories),
shareReplay(1),
)
private readonly pkg$: Observable<MarketplacePkg[]> =
this.altMarketplaceData$.pipe(
switchMap(data =>
private readonly registryData$: Observable<MarketplaceData> =
this.uiMarketplaceData$.pipe(
switchMap(uiMarketplaceData =>
this.serverInfo$.pipe(
switchMap(info =>
switchMap(({ id }) =>
from(
this.getMarketplacePkgs(
{ page: 1, 'per-page': 100 },
this.toMarketplace(data).url,
info['eos-version-compat'],
this.getMarketplaceData(
{ 'server-id': id },
this.toMarketplace(uiMarketplaceData).url,
),
).pipe(tap(() => this.onPackages(data))),
).pipe(tap(({ name }) => this.updateName(uiMarketplaceData, name))),
),
),
),
catchError(e => {
this.errToast.present(e)
return of([])
}),
shareReplay(1),
)
private readonly categories$: Observable<Set<string>> =
this.registryData$.pipe(
map(
({ categories }) =>
new Set(['featured', 'updates', ...categories, 'all']),
),
)
private readonly pkgs$: Observable<MarketplacePkg[]> = this.marketplace$.pipe(
switchMap(({ url }) =>
this.serverInfo$.pipe(
switchMap(info =>
from(
this.getMarketplacePkgs(
{ page: 1, 'per-page': 100 },
url,
info['eos-version-compat'],
),
).pipe(tap(() => this.hasPackages$.next(true))),
),
),
),
catchError(e => {
this.errToast.present(e)
return of([])
}),
shareReplay(1),
)
private readonly updates$: Observable<MarketplacePkg[]> =
this.hasPackages$.pipe(
switchMap(() =>
this.patch.watch$('package-data').pipe(
switchMap(localPkgs =>
this.pkgs$.pipe(
map(pkgs => {
return this.filterPkgsPipe.transform(
pkgs,
'',
'updates',
localPkgs,
)
}),
),
),
),
),
)
constructor(
private readonly api: ApiService,
private readonly patch: PatchDbService,
private readonly config: ConfigService,
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly emver: Emver,
private readonly filterPkgsPipe: FilterPackagesPipe,
) {
super()
}
@@ -93,15 +127,15 @@ export class MarketplaceService extends AbstractMarketplaceService {
}
getAltMarketplace(): Observable<UIMarketplaceData | undefined> {
return this.altMarketplaceData$
return this.uiMarketplaceData$
}
getCategories(): Observable<string[]> {
getCategories(): Observable<Set<string>> {
return this.categories$
}
getPackages(): Observable<MarketplacePkg[]> {
return this.pkg$
return this.pkgs$
}
getPackage(id: string, version: string): Observable<MarketplacePkg | null> {
@@ -133,6 +167,10 @@ export class MarketplaceService extends AbstractMarketplaceService {
)
}
getUpdates(): Observable<MarketplacePkg[]> {
return this.updates$
}
getReleaseNotes(id: string): Observable<Record<string, string>> {
if (this.notes.has(id)) {
return of(this.notes.get(id) || {})
@@ -216,15 +254,16 @@ export class MarketplaceService extends AbstractMarketplaceService {
)
}
private onPackages(data?: UIMarketplaceData) {
const { name } = this.toMarketplace(data)
if (!data?.['selected-id']) {
private updateName(
uiMarketplaceData: UIMarketplaceData | undefined,
name: string,
) {
if (!uiMarketplaceData?.['selected-id']) {
return
}
const selectedId = data['selected-id']
const knownHosts = data['known-hosts']
const selectedId = uiMarketplaceData['selected-id']
const knownHosts = uiMarketplaceData['known-hosts']
if (knownHosts[selectedId].name !== name) {
this.api.setDbValue({

View File

@@ -113,7 +113,7 @@ export const serverConfig: ConfigSpec = {
type: 'boolean',
name: 'Auto Check for Updates',
description:
'If enabled, EmbassyOS will automatically check for updates of itself. Updating will still require your approval and action. Updates will never be performed automatically.',
'If enabled, EmbassyOS will automatically check for updates of itself and installed services. Updating will still require your approval and action. Updates will never be performed automatically.',
default: true,
},
}