mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Feat/community marketplace (#1790)
* add community marketplace * Update embassy-mock-api.service.ts * expect ui/marketplace to be undefined * possible undefined from getpackage * fix marketplace pages * rework marketplace infrastructure * fix bugs Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
This commit is contained in:
committed by
Aiden McClelland
parent
e2db3d84d8
commit
9998ed177b
@@ -161,10 +161,7 @@ export class AppActionsPage {
|
||||
try {
|
||||
await this.embassyApi.uninstallPackage({ id: this.pkgId })
|
||||
this.embassyApi
|
||||
.setDbValue({
|
||||
pointer: `/ack-instructions/${this.pkgId}`,
|
||||
value: false,
|
||||
})
|
||||
.setDbValue(['ack-instructions', this.pkgId], false)
|
||||
.catch(e => console.error('Failed to mark instructions as unseen', e))
|
||||
this.navCtrl.navigateRoot('/services')
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -8,15 +8,10 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<!-- loading -->
|
||||
<ng-container *ngIf="loading else loaded">
|
||||
<text-spinner text="Connecting to Embassy"></text-spinner>
|
||||
</ng-container>
|
||||
|
||||
<!-- loaded -->
|
||||
<ng-template #loaded>
|
||||
<ng-container *ngIf="pkgs$ | async as pkgs; else loading">
|
||||
<app-list-empty
|
||||
*ngIf="empty; else list"
|
||||
*ngIf="!pkgs.length; else list"
|
||||
class="ion-text-center ion-padding"
|
||||
></app-list-empty>
|
||||
|
||||
@@ -36,5 +31,10 @@
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<!-- loading -->
|
||||
<ng-template #loading>
|
||||
<text-spinner text="Connecting to Embassy"></text-spinner>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,46 +1,27 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { filter, takeUntil, tap } from 'rxjs/operators'
|
||||
import { DestroyService } from '@start9labs/shared'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { filter, map, pairwise, startWith } from 'rxjs/operators'
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: './app-list.page.html',
|
||||
styleUrls: ['./app-list.page.scss'],
|
||||
providers: [DestroyService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AppListPage {
|
||||
loading = true
|
||||
pkgs: readonly PackageDataEntry[] = []
|
||||
readonly pkgs$ = this.patch.watch$('package-data').pipe(
|
||||
map(pkgs => Object.values(pkgs)),
|
||||
startWith([]),
|
||||
pairwise(),
|
||||
filter(([prev, next]) => {
|
||||
const length = next.length
|
||||
return !length || prev.length !== length
|
||||
}),
|
||||
map(([_, pkgs]) => {
|
||||
return pkgs.sort((a, b) => (b.manifest.title > a.manifest.title ? -1 : 1))
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly destroy$: DestroyService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
get empty(): boolean {
|
||||
return !this.pkgs.length
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.patch
|
||||
.watch$('package-data')
|
||||
.pipe(
|
||||
filter(pkgs => Object.keys(pkgs).length !== this.pkgs.length),
|
||||
tap(pkgs => {
|
||||
this.loading = false
|
||||
this.pkgs = Object.values(pkgs).sort((a, b) =>
|
||||
b.manifest.title > a.manifest.title ? -1 : 1,
|
||||
)
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import { ToButtonsPipe } from './pipes/to-buttons.pipe'
|
||||
import { ToDependenciesPipe } from './pipes/to-dependencies.pipe'
|
||||
import { ToStatusPipe } from './pipes/to-status.pipe'
|
||||
import { ProgressDataPipe } from './pipes/progress-data.pipe'
|
||||
import { ActionMarketplaceComponentModule } from 'src/app/modals/action-marketplace/action-marketplace.component.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -54,7 +53,6 @@ const routes: Routes = [
|
||||
EmverPipesModule,
|
||||
LaunchablePipeModule,
|
||||
UiPipeModule,
|
||||
ActionMarketplaceComponentModule,
|
||||
],
|
||||
})
|
||||
export class AppShowPageModule {}
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
[dependencies]="dependencies"
|
||||
></app-show-dependencies>
|
||||
<!-- ** menu ** -->
|
||||
<app-show-menu
|
||||
[buttons]="pkg | toButtons: (currentMarketplace$ | async): (altMarketplaceData$ | async)"
|
||||
></app-show-menu>
|
||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { NavController } from '@ionic/angular'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
import { filter, tap } from 'rxjs/operators'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
|
||||
const STATES = [
|
||||
PackageState.Installing,
|
||||
@@ -49,16 +47,10 @@ export class AppShowPage {
|
||||
),
|
||||
)
|
||||
|
||||
readonly currentMarketplace$ = this.marketplaceService.getMarketplace()
|
||||
|
||||
readonly altMarketplaceData$ = this.marketplaceService.getAltMarketplaceData()
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
) {}
|
||||
|
||||
isInstalled({ state }: PackageDataEntry): boolean {
|
||||
|
||||
@@ -2,17 +2,14 @@ import { Inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
||||
import { getUrlHostname, MarkdownComponent } from '@start9labs/shared'
|
||||
import { MarkdownComponent } from '@start9labs/shared'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
UIMarketplaceData,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { from, map, Observable } from 'rxjs'
|
||||
import { Marketplace } from '@start9labs/marketplace'
|
||||
import { ActionMarketplaceComponent } from 'src/app/modals/action-marketplace/action-marketplace.component'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
|
||||
export interface Button {
|
||||
@@ -39,11 +36,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
transform(
|
||||
pkg: PackageDataEntry,
|
||||
currentMarketplace: Marketplace | null,
|
||||
altMarketplaces: UIMarketplaceData | null | undefined,
|
||||
): Button[] {
|
||||
transform(pkg: PackageDataEntry): Button[] {
|
||||
const pkgTitle = pkg.manifest.title
|
||||
|
||||
return [
|
||||
@@ -103,7 +96,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
icon: 'receipt-outline',
|
||||
},
|
||||
// view in marketplace
|
||||
this.viewInMarketplaceButton(pkg, currentMarketplace, altMarketplaces),
|
||||
this.viewInMarketplaceButton(pkg),
|
||||
// donate
|
||||
{
|
||||
action: () => this.donate(pkg),
|
||||
@@ -116,10 +109,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
|
||||
private async presentModalInstructions(pkg: PackageDataEntry) {
|
||||
this.apiService
|
||||
.setDbValue({
|
||||
pointer: `/ack-instructions/${pkg.manifest.id}`,
|
||||
value: true,
|
||||
})
|
||||
.setDbValue(['ack-instructions', pkg.manifest.id], true)
|
||||
.catch(e => console.error('Failed to mark instructions as seen', e))
|
||||
|
||||
const modal = await this.modalCtrl.create({
|
||||
@@ -135,51 +125,27 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
private viewInMarketplaceButton(
|
||||
pkg: PackageDataEntry,
|
||||
currentMarketplace: Marketplace | null,
|
||||
altMarketplaces: UIMarketplaceData | null | undefined,
|
||||
): Button {
|
||||
const pkgMarketplaceUrl = pkg.installed?.['marketplace-url']
|
||||
// default button if package marketplace and current marketplace are the same
|
||||
private viewInMarketplaceButton(pkg: PackageDataEntry): Button {
|
||||
const url = pkg.installed?.['marketplace-url']
|
||||
const queryParams = url ? { url } : {}
|
||||
|
||||
let button: Button = {
|
||||
title: 'Marketplace',
|
||||
icon: 'storefront-outline',
|
||||
action: () =>
|
||||
this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`]),
|
||||
this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`], {
|
||||
queryParams,
|
||||
}),
|
||||
disabled: false,
|
||||
description: 'View service in marketplace',
|
||||
}
|
||||
if (!pkgMarketplaceUrl) {
|
||||
|
||||
if (!url) {
|
||||
button.disabled = true
|
||||
button.description = 'This package was not installed from a marketplace.'
|
||||
button.action = () => {}
|
||||
} else if (
|
||||
pkgMarketplaceUrl &&
|
||||
currentMarketplace &&
|
||||
getUrlHostname(pkgMarketplaceUrl) !==
|
||||
getUrlHostname(currentMarketplace.url)
|
||||
) {
|
||||
// attempt to get name for pkg marketplace
|
||||
let pkgMarketplaceName = getUrlHostname(pkgMarketplaceUrl)
|
||||
if (altMarketplaces) {
|
||||
const pkgMarketplaces = Object.values(
|
||||
altMarketplaces['known-hosts'],
|
||||
).filter(m => getUrlHostname(m.url) === pkgMarketplaceName)
|
||||
if (pkgMarketplaces.length) {
|
||||
// if multiple of the same url exist, they will have the same name, so fine to grab first
|
||||
pkgMarketplaceName = pkgMarketplaces[0].name
|
||||
}
|
||||
}
|
||||
|
||||
button.action = () =>
|
||||
this.differentMarketplaceAction(
|
||||
pkgMarketplaceName,
|
||||
currentMarketplace.name,
|
||||
pkg.manifest.id,
|
||||
)
|
||||
button.description = 'Service was installed from a different marketplace'
|
||||
}
|
||||
|
||||
return button
|
||||
}
|
||||
|
||||
@@ -195,22 +161,4 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
await alert.present()
|
||||
}
|
||||
}
|
||||
|
||||
private async differentMarketplaceAction(
|
||||
packageMarketplace: string,
|
||||
currentMarketplace: string,
|
||||
pkgId: string,
|
||||
) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: ActionMarketplaceComponent,
|
||||
componentProps: {
|
||||
title: 'Marketplace Conflict',
|
||||
packageMarketplace,
|
||||
currentMarketplace,
|
||||
pkgId,
|
||||
},
|
||||
cssClass: 'medium-modal',
|
||||
})
|
||||
await modal.present()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||
import { filter, map, startWith } from 'rxjs/operators'
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
import { map, startWith } from 'rxjs/operators'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { Observable } from 'rxjs'
|
||||
|
||||
@@ -27,7 +27,6 @@ export class ToHealthChecksPipe implements PipeTransform {
|
||||
const healthChecks$ = this.patch
|
||||
.watch$('package-data', pkg.manifest.id, 'installed', 'status', 'main')
|
||||
.pipe(
|
||||
filter(obj => exists(obj)),
|
||||
map(main => {
|
||||
// Question: is this ok or do we have to use Object.keys
|
||||
// to maintain order and the keys initially present in pkg?
|
||||
|
||||
@@ -69,10 +69,7 @@ export class DevConfigPage {
|
||||
async save() {
|
||||
this.saving = true
|
||||
try {
|
||||
await this.api.setDbValue({
|
||||
pointer: `/dev/${this.projectId}/config`,
|
||||
value: this.code,
|
||||
})
|
||||
await this.api.setDbValue(['dev', this.projectId, 'config'], this.code)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -56,10 +56,10 @@ export class DevInstructionsPage {
|
||||
async save() {
|
||||
this.saving = true
|
||||
try {
|
||||
await this.api.setDbValue({
|
||||
pointer: `/dev/${this.projectId}/instructions`,
|
||||
value: this.code,
|
||||
})
|
||||
await this.api.setDbValue(
|
||||
['dev', this.projectId, 'instructions'],
|
||||
this.code,
|
||||
)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -148,7 +148,7 @@ export class DeveloperListPage {
|
||||
.replace(/warning:/g, '# Optional\n warning:')
|
||||
|
||||
const def = { name, config, instructions: SAMPLE_INSTUCTIONS }
|
||||
await this.api.setDbValue({ pointer: `/dev/${id}`, value: def })
|
||||
await this.api.setDbValue(['dev', id], def)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
@@ -184,7 +184,7 @@ export class DeveloperListPage {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.setDbValue({ pointer: `/dev/${id}/name`, value: newName })
|
||||
await this.api.setDbValue(['dev', id, 'name'], newName)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
@@ -201,7 +201,7 @@ export class DeveloperListPage {
|
||||
try {
|
||||
const devDataToSave: DevData = JSON.parse(JSON.stringify(this.devData))
|
||||
delete devDataToSave[id]
|
||||
await this.api.setDbValue({ pointer: `/dev`, value: devDataToSave })
|
||||
await this.api.setDbValue(['dev'], devDataToSave)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -55,10 +55,10 @@ export class DeveloperMenuPage {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.setDbValue({
|
||||
pointer: `/dev/${this.projectId}/basic-info`,
|
||||
value: basicInfo,
|
||||
})
|
||||
await this.api.setDbValue(
|
||||
['dev', this.projectId, 'basic-info'],
|
||||
basicInfo,
|
||||
)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Marketplace</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
@@ -28,14 +29,13 @@
|
||||
</ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col size="12">
|
||||
<ng-container *ngIf="pkgs$ | async as pkgs; else loading">
|
||||
<ng-container *ngIf="marketplace$ | async as marketplace; else loading">
|
||||
<ng-container *ngIf="localPkgs$ | async as localPkgs">
|
||||
<marketplace-categories
|
||||
*ngIf="categories$ | async as categories"
|
||||
[categories]="categories"
|
||||
[categories]="marketplace.categories"
|
||||
[category]="category"
|
||||
[updatesAvailable]="
|
||||
(pkgs | filterPackages: '':'updates':localPkgs).length
|
||||
(marketplace.pkgs | filterPackages: '':'updates':localPkgs).length
|
||||
"
|
||||
(categoryChange)="onCategoryChange($event)"
|
||||
></marketplace-categories>
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<ion-grid
|
||||
*ngIf="pkgs | filterPackages: query:category:localPkgs as filtered"
|
||||
*ngIf="marketplace.pkgs | filterPackages: query:category:localPkgs as filtered"
|
||||
>
|
||||
<div
|
||||
*ngIf="!filtered.length && category === 'updates'"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { getUrlHostname } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs'
|
||||
import { combineLatest, map } from 'rxjs'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
@@ -12,25 +12,45 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceListPage {
|
||||
private readonly pkgs$ = this.marketplaceService.getPackages$()
|
||||
|
||||
private readonly categories$ = this.marketplaceService
|
||||
.getMarketplaceInfo$()
|
||||
.pipe(
|
||||
map(({ categories }) => {
|
||||
const set = new Set<string>()
|
||||
if (categories.includes('featured')) set.add('featured')
|
||||
set.add('updates')
|
||||
categories.forEach(c => set.add(c))
|
||||
set.add('all')
|
||||
return set
|
||||
}),
|
||||
)
|
||||
|
||||
readonly marketplace$ = combineLatest([this.pkgs$, this.categories$]).pipe(
|
||||
map(arr => {
|
||||
return { pkgs: arr[0], categories: arr[1] }
|
||||
}),
|
||||
)
|
||||
|
||||
readonly localPkgs$ = this.patch.watch$('package-data')
|
||||
readonly categories$ = this.marketplaceService.getCategories()
|
||||
readonly pkgs$ = this.marketplaceService.getPackages()
|
||||
readonly details$ = this.marketplaceService.getMarketplace().pipe(
|
||||
map(d => {
|
||||
|
||||
readonly details$ = this.marketplaceService.getUiMarketplace$().pipe(
|
||||
map(({ url, name }) => {
|
||||
let color: string
|
||||
let description: string
|
||||
switch (getUrlHostname(d.url)) {
|
||||
case 'registry.start9.com':
|
||||
switch (url) {
|
||||
case 'https://registry.start9.com/':
|
||||
color = 'success'
|
||||
description =
|
||||
'Services in this marketplace are packaged and maintained by the Start9 team. If you experience an issue or have a questions related to a service in this marketplace, one of our dedicated support staff will be happy to assist you.'
|
||||
break
|
||||
case 'beta-registry-0-3.start9labs.com':
|
||||
case 'https://beta-registry-0-3.start9labs.com/':
|
||||
color = 'primary'
|
||||
description =
|
||||
'Services in this marketplace are undergoing active testing and may contain bugs. <b>Install at your own risk</b>. If you discover a bug or have a suggestion for improvement, please report it to the Start9 team in our community testing channel on Matrix.'
|
||||
break
|
||||
case 'community.start9labs.com':
|
||||
case 'https://community.start9labs.com/':
|
||||
color = 'tertiary'
|
||||
description =
|
||||
'Services in this marketplace are packaged and maintained by members of the Start9 community. <b>Install at your own risk</b>. If you experience an issue or have a question related to a service in this marketplace, please reach out to the package developer for assistance.'
|
||||
@@ -43,7 +63,8 @@ export class MarketplaceListPage {
|
||||
}
|
||||
|
||||
return {
|
||||
...d,
|
||||
name,
|
||||
url,
|
||||
color,
|
||||
description,
|
||||
}
|
||||
@@ -52,7 +73,8 @@ export class MarketplaceListPage {
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly marketplaceService: AbstractMarketplaceService,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
) {}
|
||||
|
||||
category = 'featured'
|
||||
|
||||
@@ -31,6 +31,9 @@ import { firstValueFrom } from 'rxjs'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceShowControlsComponent {
|
||||
@Input()
|
||||
url?: string
|
||||
|
||||
@Input()
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
@@ -58,22 +61,81 @@ export class MarketplaceShowControlsComponent {
|
||||
}
|
||||
|
||||
async tryInstall() {
|
||||
const currentMarketplace = await firstValueFrom(
|
||||
this.marketplaceService.getUiMarketplace$(),
|
||||
)
|
||||
const url = this.url || currentMarketplace.url
|
||||
|
||||
if (!this.localPkg) {
|
||||
this.alertInstall()
|
||||
this.alertInstall(url)
|
||||
} else {
|
||||
const originalUrl = this.localPkg.installed?.['marketplace-url']
|
||||
|
||||
if (url !== originalUrl) {
|
||||
const proceed = await this.presentAlertDifferentMarketplace(
|
||||
url,
|
||||
originalUrl,
|
||||
)
|
||||
if (!proceed) return
|
||||
}
|
||||
|
||||
if (
|
||||
this.emver.compare(this.localVersion, this.pkg.manifest.version) !==
|
||||
0 &&
|
||||
hasCurrentDeps(this.localPkg)
|
||||
) {
|
||||
this.dryInstall()
|
||||
this.dryInstall(url)
|
||||
} else {
|
||||
this.install()
|
||||
this.install(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async dryInstall() {
|
||||
private async presentAlertDifferentMarketplace(
|
||||
url: string,
|
||||
originalUrl: string | null | undefined,
|
||||
): Promise<boolean> {
|
||||
const marketplaces = await firstValueFrom(
|
||||
this.patch.watch$('ui', 'marketplace'),
|
||||
)
|
||||
|
||||
const name = marketplaces['known-hosts'][url] || url
|
||||
|
||||
let originalName: string | undefined
|
||||
if (originalUrl) {
|
||||
originalName = marketplaces['known-hosts'][originalUrl] || originalUrl
|
||||
}
|
||||
|
||||
return new Promise(async resolve => {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Warning',
|
||||
message: `This service was originally ${
|
||||
originalName ? 'installed from ' + originalName : 'side loaded'
|
||||
}, but you are currently connected to ${name}. To install from ${name} anyway, click "Continue".`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
resolve(false)
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Continue',
|
||||
handler: () => {
|
||||
resolve(true)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
cssClass: 'alert-warning-message',
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
})
|
||||
}
|
||||
|
||||
private async dryInstall(url: string) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Checking dependent services...',
|
||||
})
|
||||
@@ -88,12 +150,12 @@ export class MarketplaceShowControlsComponent {
|
||||
})
|
||||
|
||||
if (isEmptyObject(breakages)) {
|
||||
this.install(loader)
|
||||
this.install(url, loader)
|
||||
} else {
|
||||
await loader.dismiss()
|
||||
const proceed = await this.presentAlertBreakages(breakages)
|
||||
if (proceed) {
|
||||
this.install()
|
||||
this.install(url)
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -101,10 +163,10 @@ export class MarketplaceShowControlsComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private async alertInstall() {
|
||||
private async alertInstall(url: string) {
|
||||
const installAlert = this.pkg.manifest.alerts.install
|
||||
|
||||
if (!installAlert) return this.install()
|
||||
if (!installAlert) return this.install(url)
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Alert',
|
||||
@@ -117,7 +179,7 @@ export class MarketplaceShowControlsComponent {
|
||||
{
|
||||
text: 'Install',
|
||||
handler: () => {
|
||||
this.install()
|
||||
this.install(url)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
@@ -126,7 +188,7 @@ export class MarketplaceShowControlsComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async install(loader?: HTMLIonLoadingElement) {
|
||||
private async install(url: string, loader?: HTMLIonLoadingElement) {
|
||||
const message = 'Beginning Install...'
|
||||
if (loader) {
|
||||
loader.message = message
|
||||
@@ -138,12 +200,7 @@ export class MarketplaceShowControlsComponent {
|
||||
const { id, version } = this.pkg.manifest
|
||||
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this.marketplaceService.installPackage({
|
||||
id,
|
||||
'version-spec': `=${version}`,
|
||||
}),
|
||||
)
|
||||
await this.marketplaceService.installPackage(id, version, url)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<ng-container *ngIf="!(pkg | empty)">
|
||||
<marketplace-package [pkg]="pkg"></marketplace-package>
|
||||
<marketplace-show-controls
|
||||
[url]="url"
|
||||
[pkg]="pkg"
|
||||
[localPkg]="localPkg$ | async"
|
||||
></marketplace-show-controls>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||
import {
|
||||
MarketplacePkg,
|
||||
AbstractMarketplaceService,
|
||||
} from '@start9labs/marketplace'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs'
|
||||
import { catchError, filter, shareReplay, switchMap } from 'rxjs/operators'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { filter, shareReplay, switchMap } from 'rxjs/operators'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
@@ -18,6 +15,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
})
|
||||
export class MarketplaceShowPage {
|
||||
private readonly pkgId = getPkgId(this.route)
|
||||
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
|
||||
|
||||
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
||||
|
||||
@@ -25,26 +23,15 @@ export class MarketplaceShowPage {
|
||||
.watch$('package-data', this.pkgId)
|
||||
.pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true }))
|
||||
|
||||
readonly pkg$: Observable<MarketplacePkg | null> = this.loadVersion$.pipe(
|
||||
readonly pkg$ = this.loadVersion$.pipe(
|
||||
switchMap(version =>
|
||||
this.marketplaceService.getPackage(this.pkgId, version),
|
||||
this.marketplaceService.getPackage(this.pkgId, version, this.url),
|
||||
),
|
||||
// TODO: Better fallback
|
||||
catchError(e => {
|
||||
this.errToast.present(e)
|
||||
|
||||
return of({} as MarketplacePkg)
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly marketplaceService: AbstractMarketplaceService,
|
||||
) {}
|
||||
|
||||
getIcon(icon: string): string {
|
||||
return `data:image/png;base64,${icon}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,34 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
<ion-item-group>
|
||||
<ion-item-divider>Saved Marketplaces</ion-item-divider>
|
||||
<ion-item-group *ngIf="marketplace$ | async as m">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
Connect to a standard marketplaces or an alternative marketplace.
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Standard Marketplaces</ion-item-divider>
|
||||
<ion-item
|
||||
*ngFor="let s of m.standard"
|
||||
detail="false"
|
||||
[button]="s.url !== m.selected"
|
||||
(click)="s.url === m.selected ? '' : presentAction(s)"
|
||||
>
|
||||
<ion-icon
|
||||
*ngIf="s.url === m.selected"
|
||||
slot="end"
|
||||
size="large"
|
||||
name="checkmark"
|
||||
color="success"
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ s.name }}</h2>
|
||||
<p>{{ s.url }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Alt Marketplaces</ion-item-divider>
|
||||
<ion-item button detail="false" (click)="presentModalAdd()">
|
||||
<ion-icon slot="start" name="add" color="dark"></ion-icon>
|
||||
<ion-label>
|
||||
@@ -20,22 +46,21 @@
|
||||
</ion-item>
|
||||
|
||||
<ion-item
|
||||
*ngFor="let mp of marketplaces"
|
||||
*ngFor="let a of m.alt"
|
||||
detail="false"
|
||||
[button]="mp.id !== selectedId"
|
||||
(click)="presentAction(mp.id)"
|
||||
[button]="a.url !== m.selected"
|
||||
(click)="a.url === m.selected ? '' : presentAction(a, true)"
|
||||
>
|
||||
<div *ngIf="mp.id !== selectedId" slot="start" class="padding"></div>
|
||||
<ion-icon
|
||||
*ngIf="mp.id === selectedId"
|
||||
slot="start"
|
||||
*ngIf="a.url === m.selected"
|
||||
slot="end"
|
||||
size="large"
|
||||
name="checkmark"
|
||||
color="success"
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ mp.name }}</h2>
|
||||
<p>{{ mp.url }}</p>
|
||||
<h2>{{ a.name }}</h2>
|
||||
<p>{{ a.url }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Inject } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import {
|
||||
ActionSheetController,
|
||||
AlertController,
|
||||
@@ -6,47 +6,44 @@ import {
|
||||
ModalController,
|
||||
} from '@ionic/angular'
|
||||
import { ActionSheetButton } from '@ionic/core'
|
||||
import {
|
||||
DestroyService,
|
||||
ErrorToastService,
|
||||
getUrlHostname,
|
||||
} from '@start9labs/shared'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { v4 } from 'uuid'
|
||||
import {
|
||||
DataModel,
|
||||
UIMarketplaceData,
|
||||
} from '../../../services/patch-db/data-model'
|
||||
import { ConfigService } from '../../../services/config.service'
|
||||
import { DataModel } from '../../../services/patch-db/data-model'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
finalize,
|
||||
first,
|
||||
takeUntil,
|
||||
} from 'rxjs/operators'
|
||||
import { getServerInfo } from '../../../util/get-server-info'
|
||||
import { getMarketplace } from '../../../util/get-marketplace'
|
||||
|
||||
type Marketplaces = {
|
||||
id: string | null
|
||||
name: string
|
||||
url: string
|
||||
}[]
|
||||
import { map } from 'rxjs/operators'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplaces',
|
||||
templateUrl: 'marketplaces.page.html',
|
||||
styleUrls: ['marketplaces.page.scss'],
|
||||
providers: [DestroyService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplacesPage {
|
||||
selectedId: string | null = null
|
||||
marketplaces: Marketplaces = []
|
||||
marketplace$ = this.patch.watch$('ui', 'marketplace').pipe(
|
||||
map(m => {
|
||||
const selected = m['selected-url']
|
||||
const hosts = Object.entries(m['known-hosts'])
|
||||
|
||||
const standard = hosts
|
||||
.map(([url, name]) => {
|
||||
return { url, name }
|
||||
})
|
||||
.slice(0, 2) // 0 and 1 will always be prod and community
|
||||
|
||||
const alt = hosts
|
||||
.map(([url, name]) => {
|
||||
return { url, name }
|
||||
})
|
||||
.slice(2) // 2 and beyond will always be alts
|
||||
|
||||
return { selected, standard, alt }
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
@@ -56,55 +53,28 @@ export class MarketplacesPage {
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly destroy$: DestroyService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.patch
|
||||
.watch$('ui', 'marketplace')
|
||||
.pipe(distinctUntilChanged(), takeUntil(this.destroy$))
|
||||
.subscribe((mp: UIMarketplaceData) => {
|
||||
let marketplaces: Marketplaces = [
|
||||
{
|
||||
id: null,
|
||||
name: this.config.marketplace.name,
|
||||
url: this.config.marketplace.url,
|
||||
},
|
||||
]
|
||||
this.selectedId = mp['selected-id']
|
||||
const alts = Object.entries(mp['known-hosts']).map(([k, v]) => {
|
||||
return {
|
||||
id: k,
|
||||
name: v.name,
|
||||
url: v.url,
|
||||
}
|
||||
})
|
||||
marketplaces = marketplaces.concat(alts)
|
||||
this.marketplaces = marketplaces
|
||||
})
|
||||
}
|
||||
|
||||
async presentModalAdd() {
|
||||
const marketplaceSpec = getMarketplaceValueSpec()
|
||||
const { name, spec } = getMarketplaceValueSpec()
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: GenericFormPage,
|
||||
componentProps: {
|
||||
title: marketplaceSpec.name,
|
||||
spec: marketplaceSpec.spec,
|
||||
title: name,
|
||||
spec,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save for Later',
|
||||
handler: (value: { url: string }) => {
|
||||
this.save(value.url)
|
||||
this.saveOnly(new URL(value.url))
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Save and Connect',
|
||||
handler: (value: { url: string }) => {
|
||||
this.saveAndConnect(value.url)
|
||||
this.saveAndConnect(new URL(value.url))
|
||||
},
|
||||
isSubmit: true,
|
||||
},
|
||||
@@ -116,32 +86,31 @@ export class MarketplacesPage {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async presentAction(id: string | null) {
|
||||
// no need to view actions if is selected marketplace
|
||||
const marketplace = await getMarketplace(this.patch)
|
||||
if (id === marketplace['selected-id']) return
|
||||
|
||||
async presentAction(
|
||||
{ url, name }: { url: string; name: string },
|
||||
canDelete = false,
|
||||
) {
|
||||
const buttons: ActionSheetButton[] = [
|
||||
{
|
||||
text: 'Connect',
|
||||
handler: () => {
|
||||
this.connect(id)
|
||||
this.connect(url)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if (id) {
|
||||
if (canDelete) {
|
||||
buttons.unshift({
|
||||
text: 'Delete',
|
||||
role: 'destructive',
|
||||
handler: () => {
|
||||
this.presentAlertDelete(id)
|
||||
this.presentAlertDelete(url, name)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const action = await this.actionCtrl.create({
|
||||
header: this.marketplaces.find(mp => mp.id === id)?.name,
|
||||
header: name,
|
||||
mode: 'ios',
|
||||
buttons,
|
||||
})
|
||||
@@ -149,55 +118,7 @@ export class MarketplacesPage {
|
||||
await action.present()
|
||||
}
|
||||
|
||||
private async connect(id: string | null): Promise<void> {
|
||||
const marketplace = await getMarketplace(this.patch)
|
||||
|
||||
const url = id
|
||||
? marketplace['known-hosts'][id].url
|
||||
: this.config.marketplace.url
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Validating Marketplace...',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
const { id } = await getServerInfo(this.patch)
|
||||
await this.marketplaceService.getMarketplaceData({ 'server-id': id }, url)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
loader.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
loader.message = 'Changing Marketplace...'
|
||||
|
||||
const value: UIMarketplaceData = {
|
||||
...marketplace,
|
||||
'selected-id': id,
|
||||
}
|
||||
|
||||
try {
|
||||
await this.api.setDbValue({ pointer: `/marketplace`, value })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
loader.dismiss()
|
||||
}
|
||||
|
||||
loader.message = 'Syncing store...'
|
||||
|
||||
this.marketplaceService
|
||||
.getPackages()
|
||||
.pipe(
|
||||
first(),
|
||||
finalize(() => loader.dismiss()),
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
private async presentAlertDelete(id: string) {
|
||||
const name = this.marketplaces.find(m => m.id === id)?.name
|
||||
|
||||
private async presentAlertDelete(url: string, name: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Confirm',
|
||||
message: `Are you sure you want to delete ${name}?`,
|
||||
@@ -208,7 +129,7 @@ export class MarketplacesPage {
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
handler: () => this.delete(id),
|
||||
handler: () => this.delete(url),
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
@@ -217,125 +138,104 @@ export class MarketplacesPage {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async delete(id: string): Promise<void> {
|
||||
const data = await getMarketplace(this.patch)
|
||||
const marketplace: UIMarketplaceData = JSON.parse(JSON.stringify(data))
|
||||
private async connect(
|
||||
url: string,
|
||||
loader?: HTMLIonLoadingElement,
|
||||
): Promise<void> {
|
||||
const message = 'Changing Marketplace...'
|
||||
if (!loader) {
|
||||
loader = await this.loadingCtrl.create({ message })
|
||||
await loader.present()
|
||||
} else {
|
||||
loader.message = message
|
||||
}
|
||||
|
||||
try {
|
||||
await this.api.setDbValue(['marketplace', 'selected-url'], url)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async saveOnly(url: URL): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create()
|
||||
|
||||
try {
|
||||
await this.validateAndSave(url, loader)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async saveAndConnect(url: URL): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create()
|
||||
|
||||
try {
|
||||
await this.validateAndSave(url, loader)
|
||||
await this.connect(url.toString(), loader)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async validateAndSave(
|
||||
urlObj: URL,
|
||||
loader: HTMLIonLoadingElement,
|
||||
): Promise<void> {
|
||||
const url = urlObj.toString()
|
||||
// Error on duplicates
|
||||
const hosts = await firstValueFrom(
|
||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
||||
)
|
||||
const currentUrls = Object.keys(hosts)
|
||||
if (currentUrls.includes(url)) throw new Error('marketplace already added')
|
||||
|
||||
// Validate
|
||||
loader.message = 'Validating marketplace...'
|
||||
await loader.present()
|
||||
|
||||
const name = await this.marketplaceService.validateMarketplace(url)
|
||||
|
||||
// Save
|
||||
loader.message = 'Saving...'
|
||||
|
||||
await this.api.setDbValue(['marketplace', 'known-hosts', url], name)
|
||||
}
|
||||
|
||||
private async delete(url: string): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Deleting...',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
const hosts = await firstValueFrom(
|
||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
||||
)
|
||||
|
||||
const filtered = Object.keys(hosts)
|
||||
.filter(key => key !== url)
|
||||
.reduce((prev, curr) => {
|
||||
const name = hosts[curr]
|
||||
return {
|
||||
...prev,
|
||||
[curr]: name,
|
||||
}
|
||||
}, {})
|
||||
|
||||
try {
|
||||
delete marketplace['known-hosts'][id]
|
||||
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
|
||||
await this.api.setDbValue(['marketplace', 'known-hosts'], filtered)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async save(url: string): Promise<void> {
|
||||
const data = await getMarketplace(this.patch)
|
||||
const marketplace: UIMarketplaceData = data
|
||||
? JSON.parse(JSON.stringify(data))
|
||||
: {
|
||||
'selected-id': null,
|
||||
'known-hosts': {},
|
||||
}
|
||||
|
||||
// no-op on duplicates
|
||||
const currentUrls = this.marketplaces.map(mp => getUrlHostname(mp.url))
|
||||
if (currentUrls.includes(getUrlHostname(url))) return
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Validating Marketplace...',
|
||||
})
|
||||
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
const id = v4()
|
||||
const { id: serverId } = await getServerInfo(this.patch)
|
||||
const { name } = await this.marketplaceService.getMarketplaceData(
|
||||
{ 'server-id': serverId },
|
||||
url,
|
||||
)
|
||||
marketplace['known-hosts'][id] = { name, url }
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
loader.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
loader.message = 'Saving...'
|
||||
|
||||
try {
|
||||
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async saveAndConnect(url: string): Promise<void> {
|
||||
const data = await getMarketplace(this.patch)
|
||||
const marketplace: UIMarketplaceData = data
|
||||
? JSON.parse(JSON.stringify(data))
|
||||
: {
|
||||
'selected-id': null,
|
||||
'known-hosts': {},
|
||||
}
|
||||
|
||||
// no-op on duplicates
|
||||
const currentUrls = this.marketplaces.map(mp => getUrlHostname(mp.url))
|
||||
if (currentUrls.includes(getUrlHostname(url))) {
|
||||
this.errToast.present({ message: 'Marketplace already added' })
|
||||
return
|
||||
}
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Validating Marketplace...',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
const id = v4()
|
||||
const { id: serverId } = await getServerInfo(this.patch)
|
||||
const { name } = await this.marketplaceService.getMarketplaceData(
|
||||
{ 'server-id': serverId },
|
||||
url,
|
||||
)
|
||||
marketplace['known-hosts'][id] = { name, url }
|
||||
marketplace['selected-id'] = id
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
loader.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
loader.message = 'Saving...'
|
||||
|
||||
try {
|
||||
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
loader.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
loader.message = 'Syncing marketplace data...'
|
||||
|
||||
this.marketplaceService
|
||||
.getPackages()
|
||||
.pipe(
|
||||
first(),
|
||||
finalize(() => loader.dismiss()),
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
function getMarketplaceValueSpec(): ValueSpecObject {
|
||||
|
||||
@@ -73,7 +73,7 @@ export class PreferencesPage {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.setDbValue({ pointer: `/${key}`, value })
|
||||
await this.api.setDbValue([key], value)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
|
||||
@@ -48,9 +48,6 @@ export class ServerShowPage {
|
||||
|
||||
async updateEos(): Promise<void> {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
releaseNotes: this.eosService.eos?.['release-notes'],
|
||||
},
|
||||
component: OSUpdatePage,
|
||||
})
|
||||
modal.present()
|
||||
|
||||
Reference in New Issue
Block a user