mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Hard code registry icons (#1951)
* component for store icons * order registries and abstract registry urls * use helper functionm
This commit is contained in:
committed by
Aiden McClelland
parent
4f9fe7245b
commit
eec8c41e20
@@ -6,6 +6,11 @@
|
|||||||
"url": "rpc",
|
"url": "rpc",
|
||||||
"version": "v1"
|
"version": "v1"
|
||||||
},
|
},
|
||||||
|
"marketplace": {
|
||||||
|
"start9": "https://registry.start9.com/",
|
||||||
|
"community": "https://community-registry.start9.com/",
|
||||||
|
"beta": "https://beta-registry.start9.com/"
|
||||||
|
},
|
||||||
"mocks": {
|
"mocks": {
|
||||||
"maskAs": "tor",
|
"maskAs": "tor",
|
||||||
"skipStartupAlerts": true
|
"skipStartupAlerts": true
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import {
|
import { MarketplacePkg, Marketplace, StoreData, StoreIdentity } from '../types'
|
||||||
MarketplacePkg,
|
|
||||||
Marketplace,
|
|
||||||
StoreURL,
|
|
||||||
StoreData,
|
|
||||||
StoreIdentifier,
|
|
||||||
} from '../types'
|
|
||||||
|
|
||||||
export abstract class AbstractMarketplaceService {
|
export abstract class AbstractMarketplaceService {
|
||||||
abstract getKnownHosts$(): Observable<Record<StoreURL, StoreIdentifier>>
|
abstract getKnownHosts$(): Observable<StoreIdentity[]>
|
||||||
|
|
||||||
abstract getSelectedHost$(): Observable<StoreIdentifier & { url: string }>
|
abstract getSelectedHost$(): Observable<StoreIdentity>
|
||||||
|
|
||||||
abstract getMarketplace$(): Observable<Marketplace>
|
abstract getMarketplace$(): Observable<Marketplace>
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import { Url } from '@start9labs/shared'
|
|||||||
|
|
||||||
export type StoreURL = string
|
export type StoreURL = string
|
||||||
export type StoreName = string
|
export type StoreName = string
|
||||||
export type StoreIcon = string // base64
|
|
||||||
|
|
||||||
export interface StoreIdentifier {
|
export interface StoreIdentity {
|
||||||
|
url: StoreURL
|
||||||
name?: StoreName
|
name?: StoreName
|
||||||
icon?: StoreIcon // base64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Marketplace = Record<StoreURL, StoreData | null>
|
export type Marketplace = Record<StoreURL, StoreData | null>
|
||||||
|
|
||||||
export interface StoreData {
|
export interface StoreData {
|
||||||
@@ -18,7 +16,6 @@ export interface StoreData {
|
|||||||
|
|
||||||
export interface StoreInfo {
|
export interface StoreInfo {
|
||||||
name: StoreName
|
name: StoreName
|
||||||
icon?: StoreIcon
|
|
||||||
categories: string[]
|
categories: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
frontend/projects/shared/assets/img/community-store.png
Normal file
BIN
frontend/projects/shared/assets/img/community-store.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
@@ -8,6 +8,11 @@ export type WorkspaceConfig = {
|
|||||||
url: string
|
url: string
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
|
marketplace: {
|
||||||
|
start9: 'https://registry.start9.com/'
|
||||||
|
community: 'https://community-registry.start9.com/'
|
||||||
|
beta: 'https://beta-registry.start9.com/'
|
||||||
|
}
|
||||||
mocks: {
|
mocks: {
|
||||||
maskAs: 'tor' | 'lan'
|
maskAs: 'tor' | 'lan'
|
||||||
skipStartupAlerts: boolean
|
skipStartupAlerts: boolean
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<img *ngIf="url | getIcon as icon; else noIcon" [src]="icon" alt="" />
|
||||||
|
<ng-template #noIcon>
|
||||||
|
<ion-icon name="storefront-outline"></ion-icon>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
import { GetIconPipe, StoreIconComponent } from './store-icon.component'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [StoreIconComponent, GetIconPipe],
|
||||||
|
imports: [CommonModule, IonicModule],
|
||||||
|
exports: [StoreIconComponent],
|
||||||
|
})
|
||||||
|
export class StoreIconComponentModule {}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
Pipe,
|
||||||
|
PipeTransform,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'store-icon',
|
||||||
|
templateUrl: './store-icon.component.html',
|
||||||
|
styleUrls: ['./store-icon.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class StoreIconComponent {
|
||||||
|
@Input() url: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'getIcon',
|
||||||
|
})
|
||||||
|
export class GetIconPipe implements PipeTransform {
|
||||||
|
constructor(private readonly config: ConfigService) {}
|
||||||
|
|
||||||
|
transform(url: string): string | null {
|
||||||
|
const { start9, community } = this.config.marketplace
|
||||||
|
|
||||||
|
if (url === start9) {
|
||||||
|
return 'assets/img/icon.png'
|
||||||
|
} else if (url === community) {
|
||||||
|
return 'assets/img/community-store.png'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,15 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { MarketplaceSettingsPage } from './marketplace-settings.page'
|
import { MarketplaceSettingsPage } from './marketplace-settings.page'
|
||||||
import { SharedPipesModule } from '@start9labs/shared'
|
import { SharedPipesModule } from '@start9labs/shared'
|
||||||
|
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, IonicModule, SharedPipesModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
SharedPipesModule,
|
||||||
|
StoreIconComponentModule,
|
||||||
|
],
|
||||||
declarations: [MarketplaceSettingsPage],
|
declarations: [MarketplaceSettingsPage],
|
||||||
})
|
})
|
||||||
export class MarketplaceSettingsPageModule {}
|
export class MarketplaceSettingsPageModule {}
|
||||||
|
|||||||
@@ -10,23 +10,23 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ion-item-group *ngIf="marketplace$ | async as m">
|
<ion-item-group *ngIf="stores$ | async as stores">
|
||||||
<ion-item-divider>Default Registries</ion-item-divider>
|
<ion-item-divider>Default Registries</ion-item-divider>
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngFor="let s of m.standard"
|
*ngFor="let s of stores.standard"
|
||||||
detail="false"
|
detail="false"
|
||||||
[button]="s.url !== m.selected"
|
[button]="!s.selected"
|
||||||
(click)="s.url === m.selected ? '' : presentAction(s)"
|
(click)="s.selected ? '' : presentAction(s)"
|
||||||
>
|
>
|
||||||
<ion-avatar slot="start">
|
<ion-avatar slot="start">
|
||||||
<img [src]="'data:image/png;base64,' + s.icon | trustUrl" alt="" />
|
<store-icon [url]="s.url"></store-icon>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ s.name }}</h2>
|
<h2>{{ s.name }}</h2>
|
||||||
<p>{{ s.url }}</p>
|
<p>{{ s.url }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="s.url === m.selected"
|
*ngIf="s.selected"
|
||||||
slot="end"
|
slot="end"
|
||||||
size="large"
|
size="large"
|
||||||
name="checkmark"
|
name="checkmark"
|
||||||
@@ -45,18 +45,22 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngFor="let a of m.alt"
|
*ngFor="let a of stores.alt"
|
||||||
detail="false"
|
detail="false"
|
||||||
[button]="a.url !== m.selected"
|
[button]="!a.selected"
|
||||||
(click)="a.url === m.selected ? '' : presentAction(a, true)"
|
(click)="a.selected ? '' : presentAction(a, true)"
|
||||||
>
|
>
|
||||||
<ion-icon slot="start" name="storefront-outline"></ion-icon>
|
<store-icon
|
||||||
|
slot="start"
|
||||||
|
[url]="a.url"
|
||||||
|
style="font-size: 36px"
|
||||||
|
></store-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ a.name }}</h2>
|
<h2>{{ a.name }}</h2>
|
||||||
<p>{{ a.url }}</p>
|
<p>{{ a.url }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="a.url === m.selected"
|
*ngIf="a.selected"
|
||||||
slot="end"
|
slot="end"
|
||||||
size="large"
|
size="large"
|
||||||
name="checkmark"
|
name="checkmark"
|
||||||
|
|||||||
@@ -7,18 +7,15 @@ import {
|
|||||||
} from '@ionic/angular'
|
} from '@ionic/angular'
|
||||||
import { ActionSheetButton } from '@ionic/core'
|
import { ActionSheetButton } from '@ionic/core'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
import {
|
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||||
AbstractMarketplaceService,
|
|
||||||
StoreIdentifier,
|
|
||||||
} from '@start9labs/marketplace'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
||||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { map } from 'rxjs/operators'
|
import { map } from 'rxjs/operators'
|
||||||
import { firstValueFrom } from 'rxjs'
|
import { combineLatest, firstValueFrom } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-settings',
|
selector: 'marketplace-settings',
|
||||||
@@ -27,24 +24,21 @@ import { firstValueFrom } from 'rxjs'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class MarketplaceSettingsPage {
|
export class MarketplaceSettingsPage {
|
||||||
marketplace$ = this.patch.watch$('ui', 'marketplace').pipe(
|
stores$ = combineLatest([
|
||||||
map(m => {
|
this.marketplaceService.getKnownHosts$(),
|
||||||
const selected = m['selected-url']
|
this.marketplaceService.getSelectedHost$(),
|
||||||
const hosts = Object.entries(m['known-hosts'])
|
]).pipe(
|
||||||
|
map(([stores, selected]) => {
|
||||||
|
const hmmm = stores.map(s => ({
|
||||||
|
...s,
|
||||||
|
selected: s.url === selected.url,
|
||||||
|
}))
|
||||||
|
// 0 and 1 are prod and community
|
||||||
|
const standard = hmmm.slice(0, 2)
|
||||||
|
// 2 and beyond are alts
|
||||||
|
const alt = hmmm.slice(2)
|
||||||
|
|
||||||
const standard = hosts
|
return { standard, alt }
|
||||||
.map(([url, info]) => {
|
|
||||||
return { url, ...info }
|
|
||||||
})
|
|
||||||
.slice(0, 2) // 0 and 1 will always be prod and community
|
|
||||||
|
|
||||||
const alt = hosts
|
|
||||||
.map(([url, info]) => {
|
|
||||||
return { url, ...info }
|
|
||||||
})
|
|
||||||
.slice(2) // 2 and beyond will always be alts
|
|
||||||
|
|
||||||
return { selected, standard, alt }
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -207,16 +201,16 @@ export class MarketplaceSettingsPage {
|
|||||||
loader.message = 'Validating marketplace...'
|
loader.message = 'Validating marketplace...'
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|
||||||
const { name, icon } = await firstValueFrom(
|
const { name } = await firstValueFrom(
|
||||||
this.marketplaceService.fetchInfo$(url),
|
this.marketplaceService.fetchInfo$(url),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
loader.message = 'Saving...'
|
loader.message = 'Saving...'
|
||||||
|
|
||||||
await this.api.setDbValue<StoreIdentifier>(
|
await this.api.setDbValue<{ name: string }>(
|
||||||
['marketplace', 'known-hosts', url],
|
['marketplace', 'known-hosts', url],
|
||||||
{ name, icon },
|
{ name },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +224,7 @@ export class MarketplaceSettingsPage {
|
|||||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const filtered: { [url: string]: StoreIdentifier } = Object.keys(hosts)
|
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
||||||
.filter(key => key !== url)
|
.filter(key => key !== url)
|
||||||
.reduce((prev, curr) => {
|
.reduce((prev, curr) => {
|
||||||
const name = hosts[curr]
|
const name = hosts[curr]
|
||||||
@@ -241,7 +235,7 @@ export class MarketplaceSettingsPage {
|
|||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.setDbValue<{ [url: string]: StoreIdentifier }>(
|
await this.api.setDbValue<{ [url: string]: UIStore }>(
|
||||||
['marketplace', 'known-hosts'],
|
['marketplace', 'known-hosts'],
|
||||||
filtered,
|
filtered,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/b
|
|||||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||||
import { MarketplaceListPage } from './marketplace-list.page'
|
import { MarketplaceListPage } from './marketplace-list.page'
|
||||||
import { MarketplaceSettingsPageModule } from 'src/app/modals/marketplace-settings/marketplace-settings.module'
|
import { MarketplaceSettingsPageModule } from 'src/app/modals/marketplace-settings/marketplace-settings.module'
|
||||||
|
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -39,6 +40,7 @@ const routes: Routes = [
|
|||||||
SearchModule,
|
SearchModule,
|
||||||
SkeletonModule,
|
SkeletonModule,
|
||||||
MarketplaceSettingsPageModule,
|
MarketplaceSettingsPageModule,
|
||||||
|
StoreIconComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [MarketplaceListPage],
|
declarations: [MarketplaceListPage],
|
||||||
exports: [MarketplaceListPage],
|
exports: [MarketplaceListPage],
|
||||||
|
|||||||
@@ -24,14 +24,7 @@
|
|||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col size="12">
|
<ion-col size="12">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<img
|
<store-icon class="icon" [url]="details.url"></store-icon>
|
||||||
*ngIf="details.icon; else noIcon"
|
|
||||||
[src]="'data:image/png;base64,' + details.icon | trustUrl"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<ng-template #noIcon>
|
|
||||||
<ion-icon name="storefront-outline"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
<h1 class="montserrat ion-text-center">{{ details.name }}</h1>
|
<h1 class="montserrat ion-text-center">{{ details.name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<ion-button fill="clear" (click)="presentModalMarketplaceSettings()">
|
<ion-button fill="clear" (click)="presentModalMarketplaceSettings()">
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
.heading {
|
.heading {
|
||||||
$icon-size: 64px;
|
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
img {
|
|
||||||
max-width: $icon-size;
|
|
||||||
}
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
}
|
}
|
||||||
ion-icon {
|
}
|
||||||
font-size: $icon-size;
|
|
||||||
}
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 80px;
|
||||||
|
font-size: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
|||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, map } from 'rxjs'
|
import { filter, map } from 'rxjs'
|
||||||
import { MarketplaceSettingsPage } from 'src/app/modals/marketplace-settings/marketplace-settings.page'
|
import { MarketplaceSettingsPage } from 'src/app/modals/marketplace-settings/marketplace-settings.page'
|
||||||
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@@ -30,25 +31,26 @@ export class MarketplaceListPage {
|
|||||||
readonly localPkgs$ = this.patch.watch$('package-data')
|
readonly localPkgs$ = this.patch.watch$('package-data')
|
||||||
|
|
||||||
readonly details$ = this.marketplaceService.getSelectedHost$().pipe(
|
readonly details$ = this.marketplaceService.getSelectedHost$().pipe(
|
||||||
map(({ url, name, icon }) => {
|
map(({ url, name }) => {
|
||||||
|
const { start9, community, beta } = this.config.marketplace
|
||||||
let color: string
|
let color: string
|
||||||
let description: string
|
let description: string
|
||||||
switch (url) {
|
switch (url) {
|
||||||
case 'https://registry.start9.com/':
|
case start9:
|
||||||
color = 'success'
|
color = 'success'
|
||||||
description =
|
description =
|
||||||
'Services from this registry are packaged and maintained by the Start9 team. If you experience an issue or have a questions related to a service from this registry, one of our dedicated support staff will be happy to assist you.'
|
'Services from this registry are packaged and maintained by the Start9 team. If you experience an issue or have a questions related to a service from this registry, one of our dedicated support staff will be happy to assist you.'
|
||||||
break
|
break
|
||||||
case 'https://beta-registry.start9.com/':
|
case community:
|
||||||
color = 'primary'
|
|
||||||
description =
|
|
||||||
'Services from this registry 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 'https://community-registry.start9.com/':
|
|
||||||
color = 'tertiary'
|
color = 'tertiary'
|
||||||
description =
|
description =
|
||||||
'Services from this registry 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.'
|
'Services from this registry 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.'
|
||||||
break
|
break
|
||||||
|
case beta:
|
||||||
|
color = 'primary'
|
||||||
|
description =
|
||||||
|
'Services from this registry 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
|
||||||
default:
|
default:
|
||||||
// alt marketplace
|
// alt marketplace
|
||||||
color = 'warning'
|
color = 'warning'
|
||||||
@@ -59,7 +61,6 @@ export class MarketplaceListPage {
|
|||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
icon,
|
|
||||||
color,
|
color,
|
||||||
description,
|
description,
|
||||||
}
|
}
|
||||||
@@ -71,6 +72,7 @@ export class MarketplaceListPage {
|
|||||||
@Inject(AbstractMarketplaceService)
|
@Inject(AbstractMarketplaceService)
|
||||||
private readonly marketplaceService: MarketplaceService,
|
private readonly marketplaceService: MarketplaceService,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
|
private readonly config: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
category = 'featured'
|
category = 'featured'
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { MarkdownPipeModule, SharedPipesModule } from '@start9labs/shared'
|
|||||||
import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module'
|
import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module'
|
||||||
import { RoundProgressModule } from 'angular-svg-round-progressbar'
|
import { RoundProgressModule } from 'angular-svg-round-progressbar'
|
||||||
import { InstallProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
import { InstallProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||||
|
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -27,6 +28,7 @@ const routes: Routes = [
|
|||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
RoundProgressModule,
|
RoundProgressModule,
|
||||||
InstallProgressPipeModule,
|
InstallProgressPipeModule,
|
||||||
|
StoreIconComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [UpdatesPage, FilterUpdatesPipe],
|
declarations: [UpdatesPage, FilterUpdatesPipe],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,26 +9,24 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<ion-item-group *ngIf="data$ | async as data">
|
<ion-item-group *ngIf="data$ | async as data">
|
||||||
<ng-container *ngFor="let host of data.hosts | keyvalue">
|
<ng-container *ngFor="let host of data.hosts">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
{{ host.value.name }}
|
{{ host.name }}
|
||||||
<img
|
<div style="max-width: 16px">
|
||||||
style="max-width: 24px"
|
<store-icon [url]="host.url"></store-icon>
|
||||||
[src]="'data:image/png;base64,' + host.value.icon | trustUrl"
|
</div>
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
|
|
||||||
<div class="ion-padding-start ion-padding-bottom">
|
<div class="ion-padding-start ion-padding-bottom">
|
||||||
<ion-item *ngIf="data.errors.includes(host.key)">
|
<ion-item *ngIf="data.errors.includes(host.url)">
|
||||||
<ion-text color="danger">Request Failed</ion-text>
|
<ion-text color="danger">Request Failed</ion-text>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="data.marketplace[host.key]?.packages as packages else loading"
|
*ngIf="data.marketplace[host.url]?.packages as packages else loading"
|
||||||
>
|
>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="packages | filterUpdates : data.localPkgs : host.key as updates"
|
*ngIf="packages | filterUpdates : data.localPkgs : host.url as updates"
|
||||||
>
|
>
|
||||||
<ion-item *ngFor="let pkg of updates">
|
<ion-item *ngFor="let pkg of updates">
|
||||||
<ng-container *ngIf="data.localPkgs[pkg.manifest.id] as local">
|
<ng-container *ngIf="data.localPkgs[pkg.manifest.id] as local">
|
||||||
@@ -64,7 +62,7 @@
|
|||||||
></ion-spinner>
|
></ion-spinner>
|
||||||
<ng-template #updateBtn>
|
<ng-template #updateBtn>
|
||||||
<ion-button
|
<ion-button
|
||||||
(click)="update(pkg.manifest.id, host.key)"
|
(click)="update(pkg.manifest.id, host.url)"
|
||||||
color="dark"
|
color="dark"
|
||||||
strong
|
strong
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
Marketplace,
|
Marketplace,
|
||||||
MarketplaceManifest,
|
MarketplaceManifest,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
StoreIdentifier,
|
StoreIdentity,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { Emver } from '@start9labs/shared'
|
import { Emver } from '@start9labs/shared'
|
||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
@@ -20,7 +20,7 @@ import { combineLatest, Observable } from 'rxjs'
|
|||||||
import { PrimaryRendering } from '../../services/pkg-status-rendering.service'
|
import { PrimaryRendering } from '../../services/pkg-status-rendering.service'
|
||||||
|
|
||||||
interface UpdatesData {
|
interface UpdatesData {
|
||||||
hosts: Record<string, StoreIdentifier>
|
hosts: StoreIdentity[]
|
||||||
marketplace: Marketplace
|
marketplace: Marketplace
|
||||||
localPkgs: Record<string, PackageDataEntry>
|
localPkgs: Record<string, PackageDataEntry>
|
||||||
errors: string[]
|
errors: string[]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -22,8 +22,6 @@ import { PatchDB, pathFromArray, Update } from 'patch-db-client'
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LiveApiService extends ApiService {
|
export class LiveApiService extends ApiService {
|
||||||
readonly eosMarketplaceUrl = 'https://registry.start9.com/'
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DOCUMENT) private readonly document: Document,
|
@Inject(DOCUMENT) private readonly document: Document,
|
||||||
private readonly http: HttpService,
|
private readonly http: HttpService,
|
||||||
@@ -136,7 +134,7 @@ export class LiveApiService extends ApiService {
|
|||||||
|
|
||||||
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
||||||
const params = {
|
const params = {
|
||||||
'marketplace-url': url || this.eosMarketplaceUrl,
|
'marketplace-url': url || this.config.marketplace.start9,
|
||||||
}
|
}
|
||||||
return this.rpcRequest({ method: 'server.update', params })
|
return this.rpcRequest({ method: 'server.update', params })
|
||||||
}
|
}
|
||||||
@@ -180,7 +178,7 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.marketplaceProxy(
|
return this.marketplaceProxy(
|
||||||
'/eos/v0/latest',
|
'/eos/v0/latest',
|
||||||
params,
|
params,
|
||||||
this.eosMarketplaceUrl,
|
this.config.marketplace.start9,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
|||||||
import { AuthService } from '../auth.service'
|
import { AuthService } from '../auth.service'
|
||||||
import { ConnectionService } from '../connection.service'
|
import { ConnectionService } from '../connection.service'
|
||||||
import { StoreInfo } from '@start9labs/marketplace'
|
import { StoreInfo } from '@start9labs/marketplace'
|
||||||
import { COMMUNITY_REGISTRY, START9_REGISTRY } from './api-icons'
|
|
||||||
|
|
||||||
const PROGRESS: InstallProgress = {
|
const PROGRESS: InstallProgress = {
|
||||||
size: 120,
|
size: 120,
|
||||||
@@ -284,7 +283,6 @@ export class MockApiService extends ApiService {
|
|||||||
if (path === '/package/v0/info') {
|
if (path === '/package/v0/info') {
|
||||||
const info: StoreInfo = {
|
const info: StoreInfo = {
|
||||||
name: 'Start9 Registry',
|
name: 'Start9 Registry',
|
||||||
icon: START9_REGISTRY,
|
|
||||||
categories: [
|
categories: [
|
||||||
'bitcoin',
|
'bitcoin',
|
||||||
'lightning',
|
'lightning',
|
||||||
|
|||||||
@@ -7,24 +7,19 @@ import {
|
|||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
PackageState,
|
PackageState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { COMMUNITY_REGISTRY, START9_REGISTRY } from './api-icons'
|
|
||||||
import { Mock } from './api.fixures'
|
import { Mock } from './api.fixures'
|
||||||
|
|
||||||
export const mockPatchData: DataModel = {
|
export const mockPatchData: DataModel = {
|
||||||
ui: {
|
ui: {
|
||||||
name: `Matt's Embassy`,
|
name: `Matt's Embassy`,
|
||||||
'pkg-order': [],
|
|
||||||
'ack-welcome': '1.0.0',
|
'ack-welcome': '1.0.0',
|
||||||
marketplace: {
|
marketplace: {
|
||||||
'selected-url': 'https://registry.start9.com/',
|
'selected-url': 'https://registry.start9.com/',
|
||||||
'known-hosts': {
|
'known-hosts': {
|
||||||
'https://registry.start9.com/': {
|
'https://registry.start9.com/': {
|
||||||
name: 'Start9 Registry',
|
name: 'Start9 Registry',
|
||||||
icon: START9_REGISTRY,
|
|
||||||
},
|
|
||||||
'https://community-registry.start9.com/': {
|
|
||||||
icon: COMMUNITY_REGISTRY,
|
|
||||||
},
|
},
|
||||||
|
'https://community-registry.start9.com/': {},
|
||||||
'https://dark9-marketplace.com/': {
|
'https://dark9-marketplace.com/': {
|
||||||
name: 'Dark9',
|
name: 'Dark9',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const {
|
|||||||
targetArch,
|
targetArch,
|
||||||
gitHash,
|
gitHash,
|
||||||
useMocks,
|
useMocks,
|
||||||
ui: { api, mocks },
|
ui: { api, marketplace, mocks },
|
||||||
} = require('../../../../../config.json') as WorkspaceConfig
|
} = require('../../../../../config.json') as WorkspaceConfig
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -28,6 +28,7 @@ export class ConfigService {
|
|||||||
targetArch = targetArch
|
targetArch = targetArch
|
||||||
gitHash = gitHash
|
gitHash = gitHash
|
||||||
api = api
|
api = api
|
||||||
|
marketplace = marketplace
|
||||||
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
|
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
|
||||||
isConsulate = (window as any)['platform'] === 'ios'
|
isConsulate = (window as any)['platform'] === 'ios'
|
||||||
supportsWebSockets = !!window.WebSocket || this.isConsulate
|
supportsWebSockets = !!window.WebSocket || this.isConsulate
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
StoreData,
|
StoreData,
|
||||||
Marketplace,
|
Marketplace,
|
||||||
StoreInfo,
|
StoreInfo,
|
||||||
StoreIdentifier,
|
StoreIdentity,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
import { RR } from 'src/app/services/api/api.types'
|
import { RR } from 'src/app/services/api/api.types'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
@@ -29,35 +29,53 @@ import {
|
|||||||
shareReplay,
|
shareReplay,
|
||||||
startWith,
|
startWith,
|
||||||
switchMap,
|
switchMap,
|
||||||
|
take,
|
||||||
|
tap,
|
||||||
} from 'rxjs/operators'
|
} from 'rxjs/operators'
|
||||||
import { getNewEntries } from '@start9labs/shared'
|
import { ConfigService } from './config.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MarketplaceService implements AbstractMarketplaceService {
|
export class MarketplaceService implements AbstractMarketplaceService {
|
||||||
private readonly knownHosts$ = this.patch.watch$(
|
private readonly knownHosts$: Observable<StoreIdentity[]> = this.patch
|
||||||
'ui',
|
.watch$('ui', 'marketplace', 'known-hosts')
|
||||||
'marketplace',
|
.pipe(
|
||||||
'known-hosts',
|
map(hosts => {
|
||||||
)
|
const { start9, community } = this.config.marketplace
|
||||||
|
let arr = [
|
||||||
|
toStoreIdentity(start9, hosts[start9]),
|
||||||
|
toStoreIdentity(community, hosts[community]),
|
||||||
|
]
|
||||||
|
|
||||||
private readonly selectedHost$ = this.patch.watch$('ui', 'marketplace').pipe(
|
return arr.concat(
|
||||||
distinctUntilKeyChanged('selected-url'),
|
Object.entries(hosts)
|
||||||
map(data => ({
|
.filter(([url, _]) => ![start9, community].includes(url as any))
|
||||||
url: data['selected-url'],
|
.map(([url, store]) => toStoreIdentity(url, store)),
|
||||||
...data['known-hosts'][data['selected-url']],
|
)
|
||||||
})),
|
}),
|
||||||
shareReplay(1),
|
)
|
||||||
)
|
|
||||||
|
private readonly selectedHost$: Observable<StoreIdentity> = this.patch
|
||||||
|
.watch$('ui', 'marketplace')
|
||||||
|
.pipe(
|
||||||
|
distinctUntilKeyChanged('selected-url'),
|
||||||
|
map(({ 'selected-url': url, 'known-hosts': hosts }) =>
|
||||||
|
toStoreIdentity(url, hosts[url]),
|
||||||
|
),
|
||||||
|
shareReplay(1),
|
||||||
|
)
|
||||||
|
|
||||||
private readonly marketplace$ = this.knownHosts$.pipe(
|
private readonly marketplace$ = this.knownHosts$.pipe(
|
||||||
startWith<Record<string, StoreIdentifier>>({}),
|
startWith<StoreIdentity[]>([]),
|
||||||
pairwise(),
|
pairwise(),
|
||||||
mergeMap(([prev, curr]) => from(Object.entries(getNewEntries(prev, curr)))),
|
mergeMap(([prev, curr]) =>
|
||||||
mergeMap(([url, registry]) =>
|
curr.filter(c => !prev.find(p => c.url === p.url)),
|
||||||
|
),
|
||||||
|
mergeMap(({ url, name }) =>
|
||||||
this.fetchStore$(url).pipe(
|
this.fetchStore$(url).pipe(
|
||||||
|
tap(data => {
|
||||||
|
if (data?.info) this.updateStoreName(url, name, data.info.name)
|
||||||
|
}),
|
||||||
map<StoreData | null, [string, StoreData | null]>(data => {
|
map<StoreData | null, [string, StoreData | null]>(data => {
|
||||||
if (data?.info) this.updateStoreIdentifier(url, registry, data.info)
|
|
||||||
|
|
||||||
return [url, data]
|
return [url, data]
|
||||||
}),
|
}),
|
||||||
startWith<[string, StoreData | null]>([url, null]),
|
startWith<[string, StoreData | null]>([url, null]),
|
||||||
@@ -74,22 +92,30 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
shareReplay(1),
|
shareReplay(1),
|
||||||
)
|
)
|
||||||
|
|
||||||
private readonly selectedStore$ = this.selectedHost$.pipe(
|
private readonly selectedStore$: Observable<StoreData | null> =
|
||||||
switchMap(({ url }) => this.marketplace$.pipe(map(m => m[url]))),
|
this.selectedHost$.pipe(
|
||||||
)
|
switchMap(({ url }) =>
|
||||||
|
this.marketplace$.pipe(
|
||||||
|
filter(m => !!m[url]),
|
||||||
|
take(1),
|
||||||
|
map(m => m[url]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
private readonly patch: PatchDB<DataModel>,
|
private readonly patch: PatchDB<DataModel>,
|
||||||
|
private readonly config: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getKnownHosts$(): Observable<Record<string, StoreIdentifier>> {
|
getKnownHosts$(): Observable<StoreIdentity[]> {
|
||||||
return this.knownHosts$
|
return this.knownHosts$
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedHost$(): Observable<StoreIdentifier & { url: string }> {
|
getSelectedHost$(): Observable<StoreIdentity> {
|
||||||
return this.selectedHost$
|
return this.selectedHost$
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,16 +260,23 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateStoreIdentifier(
|
private async updateStoreName(
|
||||||
url: string,
|
url: string,
|
||||||
oldInfo: StoreIdentifier,
|
oldName: string | undefined,
|
||||||
newInfo: StoreIdentifier,
|
newName: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (oldInfo.name !== newInfo.name || oldInfo.icon !== newInfo.icon) {
|
if (oldName !== newName) {
|
||||||
this.api.setDbValue<StoreIdentifier>(
|
this.api.setDbValue<string>(
|
||||||
['marketplace', 'known-hosts', url],
|
['marketplace', 'known-hosts', url, 'name'],
|
||||||
newInfo,
|
newName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
...uiStore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||||
import { Url } from '@start9labs/shared'
|
import { Url } from '@start9labs/shared'
|
||||||
import { MarketplaceManifest, StoreIdentifier } from '@start9labs/marketplace'
|
import { MarketplaceManifest } from '@start9labs/marketplace'
|
||||||
import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info'
|
import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info'
|
||||||
|
|
||||||
export interface DataModel {
|
export interface DataModel {
|
||||||
@@ -11,7 +11,6 @@ export interface DataModel {
|
|||||||
|
|
||||||
export interface UIData {
|
export interface UIData {
|
||||||
name: string | null
|
name: string | null
|
||||||
'pkg-order': string[]
|
|
||||||
'ack-welcome': string // eOS emver
|
'ack-welcome': string // eOS emver
|
||||||
marketplace: UIMarketplaceData
|
marketplace: UIMarketplaceData
|
||||||
dev: DevData
|
dev: DevData
|
||||||
@@ -26,12 +25,16 @@ export interface UIData {
|
|||||||
export interface UIMarketplaceData {
|
export interface UIMarketplaceData {
|
||||||
'selected-url': string
|
'selected-url': string
|
||||||
'known-hosts': {
|
'known-hosts': {
|
||||||
'https://registry.start9.com/': StoreIdentifier
|
'https://registry.start9.com/': UIStore
|
||||||
'https://community-registry.start9.com/': StoreIdentifier
|
'https://community-registry.start9.com/': UIStore
|
||||||
[url: string]: StoreIdentifier
|
[url: string]: UIStore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UIStore {
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface DevData {
|
export interface DevData {
|
||||||
[id: string]: DevProjectData
|
[id: string]: DevProjectData
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user