fix marketplace dependency show and chaching

This commit is contained in:
Matt Hill
2021-09-28 11:10:03 -06:00
committed by Aiden McClelland
parent a3e307dd38
commit d92bccdc45
17 changed files with 367 additions and 340 deletions

View File

@@ -68,7 +68,7 @@
<ion-icon name="arrow-up"></ion-icon>
<ion-icon name="briefcase-outline"></ion-icon>
<ion-icon name="bookmark-outline"></ion-icon>
<ion-icon name="checkmark-outline"></ion-icon>
<ion-icon name="checkmark"></ion-icon>
<ion-icon name="chevron-down"></ion-icon>
<ion-icon name="chevron-up"></ion-icon>
<ion-icon name="chevron-forward"></ion-icon> <!-- needed for detail="true" on ion-item button -->
@@ -106,8 +106,8 @@
<ion-icon name="qr-code-outline"></ion-icon>
<ion-icon name="receipt-outline"></ion-icon>
<ion-icon name="refresh"></ion-icon>
<ion-icon name="reload-outline"></ion-icon>
<ion-icon name="remove-outline"></ion-icon>
<ion-icon name="reload"></ion-icon>
<ion-icon name="remove"></ion-icon>
<ion-icon name="save-outline"></ion-icon>
<ion-icon name="shield-checkmark-outline"></ion-icon>
<ion-icon name="storefront-outline"></ion-icon>

View File

@@ -63,13 +63,13 @@
<ion-item *ngFor="let health of healthChecks | keyvalue : asIsOrder">
<ng-container *ngIf="$any(health.value).result as result">
<ion-spinner class="icon-spinner" color="warning" slot="start" *ngIf="['starting', 'loading'] | includes : result"></ion-spinner>
<ion-icon slot="start" *ngIf="result === 'success'" name="checkmark-outline" color="success"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'failure'" name="close" color="danger"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'disabled'" name="remove-outline" color="dark"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'success'" name="checkmark" color="success"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'failure'" name="warning" color="warning"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'disabled'" name="remove" color="dark"></ion-icon>
<ion-label>
<p>{{ health.key }}</p>
<h2>{{ result }}</h2>
<p *ngIf="result === 'failure'"><ion-text color="danger">{{ $any(health.value).error }}</ion-text></p>
<h2 style="font-weight: bold;">{{ health.key }}</h2>
<h2>Result: {{ result | titlecase }}</h2>
<p *ngIf="result === 'failure'"><ion-text color="warning">{{ $any(health.value).error }}</ion-text></p>
</ion-label>
</ng-container>
</ion-item>
@@ -84,9 +84,14 @@
<img [src]="dep.value.icon" />
</ion-thumbnail>
<ion-label>
<h2 style="font-family: 'Montserrat'">{{ dep.value.title }}</h2>
<h2 class="inline" style="font-family: 'Montserrat'">
<ion-icon *ngIf="!!dep.value.errorText" slot="start" name="warning" color="warning"></ion-icon>
{{ dep.value.title }}
</h2>
<p>{{ dep.value.version | displayEmver }}</p>
<p><ion-text [color]="!!dep.value.errorText ? 'warning' : 'success'">{{ dep.value.errorText || 'satisfied' }}</ion-text></p>
<p>
<ion-text [color]="!!dep.value.errorText ? 'warning' : 'success'">{{ dep.value.errorText || 'satisfied' }}</ion-text>
</p>
</ion-label>
<ion-spinner *ngIf="dep.value.spinnerColor" slot="end" [color]="dep.value.spinnerColor" style="height: 3vh; width: 3vh"></ion-spinner>
<ion-button *ngIf="dep.value.actionText" slot="end" fill="clear">

View File

@@ -11,4 +11,14 @@
.icon-spinner {
height: 20px;
width: 20px;
}
.inline {
* {
display: inline-block;
vertical-align: middle;
}
ion-icon {
padding-right: 4px;
}
}

View File

@@ -391,7 +391,7 @@ export class AppShowPage {
},
{
action: () => this.donate(),
title: `Donate to ${this.pkg.manifest.title}`,
title: 'Donate',
icon: 'logo-bitcoin',
color: 'danger',
},

View File

@@ -24,15 +24,19 @@ export class AppReleaseNotes {
async ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
try {
const promises = []
if (!this.marketplaceService.releaseNotes[this.pkgId]) {
await this.marketplaceService.getReleaseNotes(this.pkgId)
promises.push(this.marketplaceService.getReleaseNotes(this.pkgId))
}
if (!this.marketplaceService.pkgs.length) {
promises.push(this.marketplaceService.load())
}
await Promise.all(promises)
} catch (e) {
this.errToast.present(e)
} finally {
this.loading = false
}
}
ngAfterViewInit () {

View File

@@ -11,19 +11,22 @@
<ion-grid style="padding-bottom: 32px;">
<ion-row>
<ion-col sizeMd="6" offset-md="3">
<ion-col sizeSm="8" offset-sm="2">
<ion-toolbar color="transparent">
<ion-searchbar enterkeyhint="search" color="dark" (keyup.enter)="search()" debounce="300" [(ngModel)]="query"></ion-searchbar>
<ion-button style="height: 42px;" slot="end" color="primary" fill="solid" (click)="search()">
<ion-icon slot="icon-only" name="search-outline"></ion-icon>
</ion-button>
<ion-searchbar
enterkeyhint="search"
color="dark"
debounce="250"
[(ngModel)]="query"
(ionChange)="search()"
></ion-searchbar>
</ion-toolbar>
</ion-col>
</ion-row>
</ion-grid>
<!-- page loading -->
<ng-container *ngIf="pageLoading; else pageLoaded">
<!-- loading -->
<ng-container *ngIf="loading; else pageLoaded">
<div class="scrollable ion-text-center">
<ion-button *ngFor="let cat of ['', '', '', '', '', '', '']" fill="clear">
<ion-skeleton-text animated style="width: 80px; border-radius: 0;"></ion-skeleton-text>
@@ -33,11 +36,11 @@
<div class="divider" style="margin: 24px 0;"></div>
</ng-container>
<!-- page loaded -->
<!-- loaded -->
<ng-template #pageLoaded>
<div class="scrollable ion-text-center">
<ion-button
*ngFor="let cat of data.categories"
*ngFor="let cat of categories"
fill="clear"
[class]="cat === category ? 'selected' : 'dim'"
(click)="switchCategory(cat)"
@@ -49,8 +52,8 @@
<div class="divider" style="margin: 24px;"></div>
</ng-template>
<!-- packages loading -->
<ng-container *ngIf="pkgsLoading; else pkgsLoaded">
<!-- loading -->
<ng-container *ngIf="loading; else pkgsLoaded">
<ion-grid>
<ion-row>
<ion-col *ngFor="let pkg of ['', '', '', '']" sizeXs="12" sizeSm="12" sizeMd="6">
@@ -73,15 +76,15 @@
<ng-template #pkgsLoaded>
<ion-grid>
<ion-row>
<ion-col *ngIf="eos && category === 'featured'" sizeXs="12" sizeSm="12" sizeMd="6">
<ion-col *ngIf="marketplaceService.eos && category === 'featured'" sizeXs="12" sizeSm="12" sizeMd="6">
<ion-item button class="eos-item" (click)="updateEos()">
<ion-thumbnail slot="start">
<img src="assets/img/icon.png" />
</ion-thumbnail>
<ion-label>
<h3>Now Available...</h3>
<h2>Embassy OS {{ eos.version }}</h2>
<p>{{ eos.headline }}</p>
<h2>Embassy OS {{ marketplaceService.eos.version }}</h2>
<p>{{ marketplaceService.eos.headline }}</p>
</ion-label>
</ion-item>
</ion-col>

View File

@@ -1,5 +1,5 @@
import { Component, ViewChild } from '@angular/core'
import { MarketplaceData, MarketplaceEOS, MarketplacePkg } from 'src/app/services/api/api.types'
import { MarketplacePkg } from 'src/app/services/api/api.types'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { IonContent, ModalController } from '@ionic/angular'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
@@ -7,9 +7,7 @@ import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-m
import { Subscription } from 'rxjs'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { MarketplaceService } from '../marketplace.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { pauseFor } from 'src/app/util/misc.util'
@Component({
selector: 'marketplace-list',
@@ -17,30 +15,25 @@ import { pauseFor } from 'src/app/util/misc.util'
styleUrls: ['./marketplace-list.page.scss'],
})
export class MarketplaceListPage {
PackageState = PackageState
@ViewChild(IonContent) content: IonContent
pkgs: MarketplacePkg[] = []
categories: string[]
localPkgs: { [id: string]: PackageDataEntry } = { }
pageLoading = true
pkgsLoading = true
category = 'featured'
query: string
data: MarketplaceData
eos: MarketplaceEOS
pkgs: MarketplacePkg[] = []
PackageState = PackageState
loading = true
subs: Subscription[] = []
constructor (
private readonly marketplaceService: MarketplaceService,
private readonly api: ApiService,
private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService,
private readonly wizardBaker: WizardBaker,
public readonly patch: PatchDbService,
private readonly patch: PatchDbService,
public readonly marketplaceService: MarketplaceService,
) { }
async ngOnInit () {
@@ -54,26 +47,21 @@ export class MarketplaceListPage {
]
try {
const [data, eos] = await Promise.all([
this.api.getMarketplaceData({ }),
this.api.getEos({ }),
this.marketplaceService.getAllPkgs(),
])
this.eos = eos
this.data = data
this.pkgsLoading = false
this.getPkgs()
if (!this.marketplaceService.pkgs.length) {
await this.marketplaceService.load()
}
// category should start as first item in array
// remove here then add at beginning
const filterdCategories = this.data.categories.filter(cat => this.category !== cat)
this.data.categories = [this.category, 'updates'].concat(filterdCategories).concat(['all'])
const filterdCategories = this.marketplaceService.data.categories.filter(cat => this.category !== cat)
this.categories = [this.category, 'updates'].concat(filterdCategories).concat(['all'])
this.filterPkgs()
} catch (e) {
this.errToast.present(e)
} finally {
this.pageLoading = false
this.loading = false
}
}
@@ -86,35 +74,44 @@ export class MarketplaceListPage {
}
async search (): Promise<void> {
// you can actually press enter and run code before the data binds to this.category
await pauseFor(200)
this.category = undefined
await this.getPkgs()
await this.filterPkgs()
}
async switchCategory (category: string): Promise<void> {
this.category = category
this.query = undefined
this.filterPkgs()
}
async updateEos (): Promise<void> {
const { version, headline, 'release-notes': releaseNotes } = this.marketplaceService.eos
await wizardModal(
this.modalCtrl,
this.wizardBaker.updateOS({
version: this.eos.version,
headline: this.eos.headline,
releaseNotes: this.eos['release-notes'],
version,
headline,
releaseNotes,
}),
)
}
private async getPkgs (): Promise<void> {
private async filterPkgs (): Promise<void> {
if (this.category === 'updates') {
this.pkgs = this.marketplaceService.allPkgs.filter(pkg => {
return this.localPkgs[pkg.manifest.id] && pkg.manifest.version !== this.localPkgs[pkg.manifest.id].manifest.version
this.pkgs = this.marketplaceService.pkgs.filter(pkg => {
const { id, version } = pkg.manifest
return this.localPkgs[id] && version !== this.localPkgs[id].manifest.version
})
} else {
this.pkgs = this.marketplaceService.allPkgs.filter(pkg => {
this.pkgs = this.marketplaceService.pkgs.filter(pkg => {
const { id, title, description } = pkg.manifest
if (this.query) {
return pkg.manifest.id.toUpperCase().includes(this.query.toUpperCase()) ||
pkg.manifest.title.toUpperCase().includes(this.query.toUpperCase()) ||
pkg.manifest.description.short.toUpperCase().includes(this.query.toUpperCase()) ||
pkg.manifest.description.long.toUpperCase().includes(this.query.toUpperCase())
const query = this.query.toUpperCase()
return id.toUpperCase().includes(query) ||
title.toUpperCase().includes(query) ||
description.short.toUpperCase().includes(query) ||
description.long.toUpperCase().includes(query)
} else {
if (this.category === 'all' || !this.category) {
return true
@@ -125,10 +122,4 @@ export class MarketplaceListPage {
})
}
}
async switchCategory (category: string): Promise<void> {
this.category = category
this.query = undefined
await this.getPkgs()
}
}

View File

@@ -12,192 +12,190 @@
<text-spinner *ngIf="loading; else loaded" text="Loading Package"></text-spinner>
<ng-template #loaded>
<ng-container *ngIf="marketplaceService.pkgs[pkgId] as pkg">
<ion-grid>
<ion-row>
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
<div class="header">
<img [src]="pkg.icon" />
<div class="header-text">
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
<div class="header-status">
<!-- no localPkg -->
<p *ngIf="!localPkg; else local">
Not Installed
<ion-grid>
<ion-row>
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
<div class="header">
<img [src]="pkg.icon" />
<div class="header-text">
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
<div class="header-status">
<!-- no localPkg -->
<p *ngIf="!localPkg; else local">Not Installed</p>
<!-- localPkg -->
<ng-template #local>
<!-- installed -->
<p *ngIf="localPkg.state === PackageState.Installed">
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
</p>
<!-- localPkg -->
<ng-template #local>
<!-- installed -->
<p *ngIf="localPkg.state === PackageState.Installed">
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
</p>
<!-- installing, updating -->
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
<ion-text color="primary">{{ localPkg.state | titlecase }}...{{ (localPkg['install-progress'] | installState).totalProgress }}%</ion-text>
</p>
<!-- removing -->
<p *ngIf="localPkg.state === PackageState.Removing">
<ion-text color="warning">{{ localPkg.state | titlecase }}</ion-text>
<ion-spinner name="dots" color="warning"></ion-spinner>
</p>
</ng-template>
</div>
<!-- installing, updating -->
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
<ion-text color="primary">{{ localPkg.state | titlecase }}...{{ (localPkg['install-progress'] | installState).totalProgress }}%</ion-text>
</p>
<!-- removing -->
<p *ngIf="localPkg.state === PackageState.Removing">
<ion-text color="warning">{{ localPkg.state | titlecase }}</ion-text>
<ion-spinner name="dots" color="warning"></ion-spinner>
</p>
</ng-template>
</div>
</div>
</ion-col>
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
<!-- no localPkg -->
<ion-button *ngIf="!localPkg" expand="block" (click)="tryInstall()">
Install
</ion-button>
<!-- localPkg -->
<ng-container *ngIf="localPkg">
<!-- not installing, updating, or removing -->
<ng-container *ngIf="localPkg.state === PackageState.Installed">
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === -1" expand="block" (click)="presentModal('update')">
Update
</ion-button>
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === 1" expand="block" color="warning" (click)="presentModal('downgrade')">
Downgrade
</ion-button>
</ng-container>
</div>
</ion-col>
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
<!-- no localPkg -->
<ion-button *ngIf="!localPkg" expand="block" (click)="tryInstall()">
Install
</ion-button>
<!-- localPkg -->
<ng-container *ngIf="localPkg">
<!-- not installing, updating, or removing -->
<ng-container *ngIf="localPkg.state === PackageState.Installed">
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === -1" expand="block" (click)="presentModal('update')">
Update
</ion-button>
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === 1" expand="block" color="warning" (click)="presentModal('downgrade')">
Downgrade
</ion-button>
</ng-container>
</ng-container>
</ion-col>
</ion-row>
<ion-row *ngIf="localPkg">
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
<ion-button expand="block" fill="outline" color="primary" [routerLink]="['/services', pkg.manifest.id]">
View Service
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
<!-- recommendation -->
<ion-item *ngIf="rec && showRec" class="rec-item">
<ion-label>
<h2 style="display: flex; align-items: center;">
<ion-thumbnail style="height: 3vh; width: 3vh; margin: 5px" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">
<p style="color: var(--ion-color-dark); font-size: small">{{ rec.description }}</p>
<p *ngIf="pkg.manifest.version | satisfiesEmver: rec.version" class="recommendation-text">{{ pkg.manifest.title }} version {{ pkg.manifest.version | displayEmver }} is compatible.</p>
<p *ngIf="!(pkg.manifest.version | satisfiesEmver: rec.version)" class="recommendation-text recommendation-error">{{ pkg.manifest.title }} version {{ pkg.manifest.version | displayEmver }} is NOT compatible.</p>
<ion-button style="position: absolute; right: 0; top: 0" fill="clear" (click)="dismissRec()">
<ion-icon name="close"></ion-icon>
</ion-button>
</div>
</ion-label>
</ion-item>
<ion-item-group>
<!-- release notes -->
<ion-item-divider>
New in {{ pkg.manifest.version | displayEmver }}
<ion-button [routerLink]="['notes']" style="position: absolute; right: 10px;" fill="clear" color="dark">
All Release Notes
<ion-icon slot="end" name="arrow-forward-outline"></ion-icon>
</ion-button>
</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div id='release-notes' [innerHTML]="pkg.manifest['release-notes'] | markdown"></div>
</ion-label>
</ion-item>
<!-- description -->
<ion-item-divider>Description</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div id="release-notes" class="release-notes">{{ pkg.manifest.description.long }}</div>
</ion-label>
</ion-item>
<!-- dependencies -->
<ng-container *ngIf="!(pkg.manifest.dependencies | empty)">
<ion-item-divider>Dependencies</ion-item-divider>
<ion-grid>
<ion-row>
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeSm="12" sizeMd="6">
<ion-item [routerLink]="['/marketplace', dep.key]">
<ion-thumbnail slot="start">
<img [src]="pkg['dependency-metadata'][dep.key].icon" />
</ion-thumbnail>
<ion-label>
<h2>
{{ pkg['dependency-metadata'][dep.key].title }}
<span *ngIf="dep.value.requirement.type === 'required'"> (required)</span>
<span *ngIf="dep.value.requirement.type === 'opt-out'"> (required by default)</span>
<span *ngIf="dep.value.requirement.type === 'opt-in'"> (optional)</span>
</h2>
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
<p>{{ dep.value.description }}</p>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>
</ion-item-group>
<ion-item-divider>Additional Info</ion-item-divider>
<ion-card>
<ion-grid>
<ion-row>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item button detail="false" (click)="presentAlertVersions()">
<ion-label>
<h2>Other Versions</h2>
<p>Click to view other versions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('license')">
<ion-label>
<h2>License</h2>
<p>{{ pkg.manifest.license }}</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('instructions')">
<ion-label>
<h2>Instructions</h2>
<p>Click to view instructions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
</ion-row>
<ion-row *ngIf="localPkg">
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
<ion-button expand="block" fill="outline" color="primary" [routerLink]="['/services', pkg.manifest.id]">
View Service
</ion-button>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item [href]="pkg.manifest['upstream-repo']" target="_blank" rel="noreferrer" detail="false">
<ion-label>
<h2>Source Repository</h2>
<p>{{ pkg.manifest['upstream-repo'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item [href]="pkg.manifest['wrapper-repo']" target="_blank" rel="noreferrer" detail="false">
<ion-label>
<h2>Wrapper Repository</h2>
<p>{{ pkg.manifest['wrapper-repo'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item [href]="pkg.manifest['support-site']" target="_blank" rel="noreferrer" detail="false">
<ion-label>
<h2>Support Site</h2>
<p>{{ pkg.manifest['support-site'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
</ion-row>
</ion-grid>
<!-- recommendation -->
<ion-item *ngIf="rec && showRec" class="rec-item">
<ion-label>
<h2 style="display: flex; align-items: center;">
<ion-thumbnail style="height: 3vh; width: 3vh; margin: 5px" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">
<p style="color: var(--ion-color-dark); font-size: small">{{ rec.description }}</p>
<p *ngIf="pkg.manifest.version | satisfiesEmver: rec.version" class="recommendation-text">{{ pkg.manifest.title }} version {{ pkg.manifest.version | displayEmver }} is compatible.</p>
<p *ngIf="!(pkg.manifest.version | satisfiesEmver: rec.version)" class="recommendation-text recommendation-error">{{ pkg.manifest.title }} version {{ pkg.manifest.version | displayEmver }} is NOT compatible.</p>
<ion-button style="position: absolute; right: 0; top: 0" fill="clear" (click)="dismissRec()">
<ion-icon name="close"></ion-icon>
</ion-button>
</div>
</ion-label>
</ion-item>
<ion-item-group>
<!-- release notes -->
<ion-item-divider>
New in {{ pkg.manifest.version | displayEmver }}
<ion-button [routerLink]="['notes']" style="position: absolute; right: 10px;" fill="clear" color="dark">
All Release Notes
<ion-icon slot="end" name="arrow-forward-outline"></ion-icon>
</ion-button>
</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div id='release-notes' [innerHTML]="pkg.manifest['release-notes'] | markdown"></div>
</ion-label>
</ion-item>
<!-- description -->
<ion-item-divider>Description</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div id="release-notes" class="release-notes">{{ pkg.manifest.description.long }}</div>
</ion-label>
</ion-item>
<!-- dependencies -->
<ng-container *ngIf="!(pkg.manifest.dependencies | empty)">
<ion-item-divider>Dependencies</ion-item-divider>
<ion-grid>
<ion-row>
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeSm="12" sizeMd="6">
<ion-item *ngIf="!dep.value.optional" [routerLink]="['/marketplace', dep.key]">
<ion-thumbnail slot="start">
<img [src]="pkg['dependency-metadata'][dep.key].icon" />
</ion-thumbnail>
<ion-label>
<h2>
{{ pkg['dependency-metadata'][dep.key].title }}
<span *ngIf="dep.value.recommended"> (recommended)</span>
</h2>
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
<p>{{ dep.value.description }}</p>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>
</ion-item-group>
<ion-item-divider>Additional Info</ion-item-divider>
<ion-card>
<ion-grid>
<ion-row>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item button detail="false" (click)="presentAlertVersions()">
<ion-label>
<h2>Other Versions</h2>
<p>Click to view other versions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('license')">
<ion-label>
<h2>License</h2>
<p>{{ pkg.manifest.license }}</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('instructions')">
<ion-label>
<h2>Instructions</h2>
<p>Click to view instructions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item [href]="pkg.manifest['upstream-repo']" target="_blank" rel="noreferrer" detail="false">
<ion-label>
<h2>Source Repository</h2>
<p>{{ pkg.manifest['upstream-repo'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item [href]="pkg.manifest['wrapper-repo']" target="_blank" rel="noreferrer" detail="false">
<ion-label>
<h2>Wrapper Repository</h2>
<p>{{ pkg.manifest['wrapper-repo'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item [href]="pkg.manifest['support-site']" target="_blank" rel="noreferrer" detail="false">
<ion-label>
<h2>Support Site</h2>
<p>{{ pkg.manifest['support-site'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
</ion-row>
</ion-grid>
</ion-card>
</ng-container>
</ion-card>
</ng-template>
</ion-content>

View File

@@ -13,6 +13,7 @@ import { MarketplaceService } from '../marketplace.service'
import { Subscription } from 'rxjs'
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { MarketplacePkg } from 'src/app/services/api/api.types'
@Component({
selector: 'marketplace-show',
@@ -23,11 +24,11 @@ export class MarketplaceShowPage {
@ViewChild(IonContent) content: IonContent
loading = true
pkgId: string
pkg: MarketplacePkg
localPkg: PackageDataEntry
PackageState = PackageState
rec: Recommendation | null = null
showRec = true
subs: Subscription[] = []
constructor (
@@ -41,7 +42,7 @@ export class MarketplaceShowPage {
private readonly emver: Emver,
private readonly patch: PatchDbService,
private readonly embassyApi: ApiService,
public readonly marketplaceService: MarketplaceService,
private readonly marketplaceService: MarketplaceService,
) { }
async ngOnInit () {
@@ -57,10 +58,18 @@ export class MarketplaceShowPage {
}),
]
if (this.marketplaceService.pkgs[this.pkgId]) {
try {
if (!this.marketplaceService.pkgs.length) {
await this.marketplaceService.load()
}
this.pkg = this.marketplaceService.pkgs.find(pkg => pkg.manifest.id === this.pkgId)
if (!this.pkg) {
throw new Error(`Service with ID "${this.pkgId}" not found.`)
}
} catch (e) {
this.errToast.present(e)
} finally {
this.loading = false
} else {
this.getPkg()
}
}
@@ -72,27 +81,16 @@ export class MarketplaceShowPage {
this.subs.forEach(sub => sub.unsubscribe())
}
async getPkg (version?: string): Promise<void> {
try {
await this.marketplaceService.getPkg(this.pkgId, version)
} catch (e) {
this.errToast.present(e)
} finally {
await pauseFor(100)
this.loading = false
}
}
async presentAlertVersions () {
const alert = await this.alertCtrl.create({
header: 'Versions',
inputs: this.marketplaceService.pkgs[this.pkgId].versions.sort((a, b) => -1 * this.emver.compare(a, b)).map(v => {
inputs: this.pkg.versions.sort((a, b) => -1 * this.emver.compare(a, b)).map(v => {
return {
name: v, // for CSS
type: 'radio',
label: displayEmver(v), // appearance on screen
value: v, // literal SEM version value
checked: this.marketplaceService.pkgs[this.pkgId].manifest.version === v,
checked: this.pkg.manifest.version === v,
}
}),
buttons: [
@@ -116,7 +114,7 @@ export class MarketplaceShowPage {
const modal = await this.modalCtrl.create({
componentProps: {
title,
contentUrl: this.marketplaceService.pkgs[this.pkgId][title],
contentUrl: this.pkg[title],
},
component: MarkdownPage,
})
@@ -125,7 +123,7 @@ export class MarketplaceShowPage {
}
async tryInstall () {
const { id, title, version, alerts } = this.marketplaceService.pkgs[this.pkgId].manifest
const { id, title, version, alerts } = this.pkg.manifest
if (!alerts.install) {
await this.install(id, version)
@@ -152,7 +150,7 @@ export class MarketplaceShowPage {
}
async presentModal (action: 'update' | 'downgrade') {
const { id, title, version, dependencies, alerts } = this.marketplaceService.pkgs[this.pkgId].manifest
const { id, title, version, dependencies, alerts } = this.pkg.manifest
const value = {
id,
title,
@@ -177,6 +175,18 @@ export class MarketplaceShowPage {
this.showRec = false
}
private async getPkg (version?: string): Promise<void> {
this.loading = true
try {
this.pkg = await this.marketplaceService.getPkg(this.pkgId, version)
} catch (e) {
this.errToast.present(e)
} finally {
await pauseFor(100)
this.loading = false
}
}
private async install (id: string, version?: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { MarketplacePkg } from 'src/app/services/api/api.types'
import { MarketplaceData, MarketplaceEOS, MarketplacePkg } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { Emver } from 'src/app/services/emver.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
@@ -8,9 +8,9 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
providedIn: 'root',
})
export class MarketplaceService {
allPkgs: MarketplacePkg[] = []
pkgs: { [id: string]: MarketplacePkg } = { }
updates: MarketplacePkg[] = []
data: MarketplaceData
eos: MarketplaceEOS
pkgs: MarketplacePkg[] = []
releaseNotes: { [id: string]: {
[version: string]: string
} } = { }
@@ -20,61 +20,55 @@ export class MarketplaceService {
private readonly emver: Emver,
) { }
async getUpdates (localPkgs: { [id: string]: PackageDataEntry }) : Promise<void> {
const idAndCurrentVersions = Object.keys(localPkgs).map(key => ({ id: key, version: localPkgs[key].manifest.version }))
const latestPkgs = (await this.api.getMarketplacePkgs({
ids: idAndCurrentVersions,
}))
async load (): Promise<void> {
const [data, eos, pkgs] = await Promise.all([
this.api.getMarketplaceData({ }),
this.api.getEos({ }),
this.getPkgs(1, 100),
])
this.data = data
this.eos = eos
this.pkgs = pkgs
}
const updates = latestPkgs.filter(latestPkg => {
async getUpdates (localPkgs: { [id: string]: PackageDataEntry }) : Promise<MarketplacePkg[]> {
const idAndCurrentVersions = Object.keys(localPkgs).map(key => ({ id: key, version: localPkgs[key].manifest.version }))
const latestPkgs = await this.api.getMarketplacePkgs({
ids: idAndCurrentVersions,
})
return latestPkgs.filter(latestPkg => {
const latestVersion = latestPkg.manifest.version
const curVersion = localPkgs[latestPkg.manifest.id]?.manifest.version
return !!curVersion && this.emver.compare(latestVersion, curVersion) === 1
})
this.updates = updates
}
async getAllPkgs (): Promise<void> {
if (this.allPkgs.length) return
this.allPkgs = await this.getPkgs(
undefined,
null,
1,
100000,
)
}
async getPkgs (category: string, query: string, page: number, perPage: number) : Promise<MarketplacePkg[]> {
const pkgs = await this.api.getMarketplacePkgs({
category: category !== 'all' ? category : undefined,
query,
page: String(page),
'per-page': String(perPage),
})
this.pkgs = pkgs.reduce((cur, val) => {
cur[val.manifest.id] = val
return cur
}, { })
return pkgs
}
async getPkg (id: string, version?: string): Promise<void> {
async getPkg (id: string, version?: string): Promise<MarketplacePkg> {
const pkgs = await this.api.getMarketplacePkgs({
ids: [{ id, version: version || '*' }],
})
const pkg = pkgs.find(pkg => pkg.manifest.id == id)
if (pkg) {
this.pkgs[id] = pkg
if (!pkg) {
throw new Error(`No results for ${id}${version ? ' ' + version : ''}`)
} else {
throw new Error(`No results for ${id}${version ? ' ' + version : ''}.`)
return pkg
}
}
async getReleaseNotes (id: string): Promise<void> {
this.releaseNotes[id] = await this.api.getReleaseNotes({ id })
}
private async getPkgs (page: number, perPage: number) : Promise<MarketplacePkg[]> {
const pkgs = await this.api.getMarketplacePkgs({
page: String(page),
'per-page': String(perPage),
})
return pkgs
}
}

View File

@@ -12,7 +12,7 @@
<ion-item-group>
<ion-item button (click)="presentModalName()">
<ion-label>{{ fields['name'].name }}</ion-label>
<ion-note slot="end">{{ patch.data.ui.name }}</ion-note>
<ion-note slot="end">{{ patch.data.ui.name || defaultName }}</ion-note>
</ion-item>
</ion-item-group>

View File

@@ -14,6 +14,7 @@ import { ErrorToastService } from 'src/app/services/error-toast.service'
export class PreferencesPage {
@ViewChild(IonContent) content: IonContent
fields = fields
defaultName: string
constructor (
private readonly modalCtrl: ModalController,
@@ -23,6 +24,10 @@ export class PreferencesPage {
public readonly patch: PatchDbService,
) { }
ngOnInit () {
this.defaultName = `Embassy-${this.patch.data['server-info'].id}`
}
ngAfterViewInit () {
this.content.scrollToPoint(undefined, 1)
}
@@ -34,11 +39,11 @@ export class PreferencesPage {
message: 'This is for your reference only.',
label: 'Device Name',
useMask: false,
placeholder: this.patch.data['server-info'].id,
placeholder: this.defaultName,
nullable: true,
value: this.patch.data.ui.name,
buttonText: 'Save',
submitFn: async (value: string) => await this.setDbValue('name', value || this.patch.data['server-info'].id),
submitFn: async (value: string) => await this.setDbValue('name', value || this.defaultName),
},
cssClass: 'alertlike-modal',
presentingElement: await this.modalCtrl.getTop(),

View File

@@ -1,6 +1,6 @@
<ion-header>
<ion-toolbar>
<ion-title>{{ patch.data.ui.name || patch.data['server-info'].id }}</ion-title>
<ion-title>{{ patch.data.ui.name || "Embassy-" + patch.data['server-info'].id }}</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>

View File

@@ -162,7 +162,7 @@ export class ServerShowPage {
'Power': [
{
title: 'Restart',
icon: 'reload-outline',
icon: 'reload',
action: () => this.presentAlertRestart(),
detail: false,
},

View File

@@ -262,9 +262,11 @@ export module Mock {
dependencies: {
'bitcoind': {
version: '=0.21.0',
requirement: {
type: 'opt-out',
how: 'You can use an external node if you prefer.',
},
description: 'LND needs bitcoin to live.',
optional: null,
recommended: true,
critical: true,
config: {
check: {
@@ -294,8 +296,10 @@ export module Mock {
'bitcoin-proxy': {
version: '>=0.2.2',
description: 'As long as Bitcoin is pruned, LND needs Bitcoin Proxy to fetch block over the P2P network.',
optional: null,
recommended: true,
requirement: {
type: 'opt-in',
how: 'You can choose to use Bitcoin Proxy for permission management',
},
critical: true,
config: {
check: {
@@ -410,8 +414,9 @@ export module Mock {
'bitcoind': {
version: '>=0.20.0',
description: 'Bitcoin Proxy requires a Bitcoin node.',
optional: null,
recommended: true,
requirement: {
type: 'required',
},
critical: false,
config: {
check: {

View File

@@ -344,8 +344,15 @@ export interface DependencyInfo {
export interface DependencyEntry {
version: string
optional: string | null
recommended: boolean
requirement: {
type: 'opt-in'
how: string
} | {
type: 'opt-out'
how: string
} | {
type: 'required'
}
description: string | null
critical: boolean,
config: {

View File

@@ -38,21 +38,18 @@ export class StartupAlertsService {
shouldRun: () => this.shouldRunOsWelcome(),
check: async () => true,
display: () => this.displayOsWelcome(),
hasRun: this.config.skipStartupAlerts,
}
const osUpdate: Check<RR.GetMarketplaceEOSRes | undefined> = {
name: 'osUpdate',
shouldRun: () => this.shouldRunOsUpdateCheck(),
check: () => this.osUpdateCheck(),
display: pkg => this.displayOsUpdateCheck(pkg),
hasRun: this.config.skipStartupAlerts,
}
const pkgsUpdate: Check<boolean> = {
name: 'pkgsUpdate',
shouldRun: () => this.shouldRunAppsCheck(),
check: () => this.appsCheck(),
display: () => this.displayAppsCheck(),
hasRun: this.config.skipStartupAlerts,
}
this.checks = [osWelcome, osUpdate, pkgsUpdate]
}
@@ -70,7 +67,7 @@ export class StartupAlertsService {
.subscribe(async data => {
this.data = data
await this.checks
.filter(c => !c.hasRun && c.shouldRun())
.filter(c => !this.config.skipStartupAlerts && c.shouldRun())
// returning true in the below block means to continue to next modal
// returning false means to skip all subsequent modals
.reduce(async (previousDisplay, c) => {
@@ -81,7 +78,7 @@ export class StartupAlertsService {
console.error(`Exception in ${c.name} check:`, e)
return true
}
c.hasRun = true
const displayRes = await previousDisplay
if (!checkRes) return true
@@ -113,8 +110,8 @@ export class StartupAlertsService {
}
private async appsCheck (): Promise<boolean> {
await this.marketplaceService.getUpdates(this.data['package-data'])
return !!this.marketplaceService.updates.length
const updates = await this.marketplaceService.getUpdates(this.data['package-data'])
return !!updates.length
}
private async displayOsWelcome (): Promise<boolean> {
@@ -220,8 +217,6 @@ type Check<T> = {
// display an alert based on the result of the check.
// return false if subsequent modals should not be displayed
display: (a: T) => Promise<boolean>
// tracks if this check has run in this app instance.
hasRun: boolean
// for logging purposes
name: string
}