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",
|
||||
"version": "v1"
|
||||
},
|
||||
"marketplace": {
|
||||
"start9": "https://registry.start9.com/",
|
||||
"community": "https://community-registry.start9.com/",
|
||||
"beta": "https://beta-registry.start9.com/"
|
||||
},
|
||||
"mocks": {
|
||||
"maskAs": "tor",
|
||||
"skipStartupAlerts": true
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { Observable } from 'rxjs'
|
||||
import {
|
||||
MarketplacePkg,
|
||||
Marketplace,
|
||||
StoreURL,
|
||||
StoreData,
|
||||
StoreIdentifier,
|
||||
} from '../types'
|
||||
import { MarketplacePkg, Marketplace, StoreData, StoreIdentity } from '../types'
|
||||
|
||||
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>
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@ import { Url } from '@start9labs/shared'
|
||||
|
||||
export type StoreURL = string
|
||||
export type StoreName = string
|
||||
export type StoreIcon = string // base64
|
||||
|
||||
export interface StoreIdentifier {
|
||||
export interface StoreIdentity {
|
||||
url: StoreURL
|
||||
name?: StoreName
|
||||
icon?: StoreIcon // base64
|
||||
}
|
||||
|
||||
export type Marketplace = Record<StoreURL, StoreData | null>
|
||||
|
||||
export interface StoreData {
|
||||
@@ -18,7 +16,6 @@ export interface StoreData {
|
||||
|
||||
export interface StoreInfo {
|
||||
name: StoreName
|
||||
icon?: StoreIcon
|
||||
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
|
||||
version: string
|
||||
}
|
||||
marketplace: {
|
||||
start9: 'https://registry.start9.com/'
|
||||
community: 'https://community-registry.start9.com/'
|
||||
beta: 'https://beta-registry.start9.com/'
|
||||
}
|
||||
mocks: {
|
||||
maskAs: 'tor' | 'lan'
|
||||
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 { MarketplaceSettingsPage } from './marketplace-settings.page'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, IonicModule, SharedPipesModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharedPipesModule,
|
||||
StoreIconComponentModule,
|
||||
],
|
||||
declarations: [MarketplaceSettingsPage],
|
||||
})
|
||||
export class MarketplaceSettingsPageModule {}
|
||||
|
||||
@@ -10,23 +10,23 @@
|
||||
</ion-header>
|
||||
|
||||
<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
|
||||
*ngFor="let s of m.standard"
|
||||
*ngFor="let s of stores.standard"
|
||||
detail="false"
|
||||
[button]="s.url !== m.selected"
|
||||
(click)="s.url === m.selected ? '' : presentAction(s)"
|
||||
[button]="!s.selected"
|
||||
(click)="s.selected ? '' : presentAction(s)"
|
||||
>
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="'data:image/png;base64,' + s.icon | trustUrl" alt="" />
|
||||
<store-icon [url]="s.url"></store-icon>
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ s.name }}</h2>
|
||||
<p>{{ s.url }}</p>
|
||||
</ion-label>
|
||||
<ion-icon
|
||||
*ngIf="s.url === m.selected"
|
||||
*ngIf="s.selected"
|
||||
slot="end"
|
||||
size="large"
|
||||
name="checkmark"
|
||||
@@ -45,18 +45,22 @@
|
||||
</ion-item>
|
||||
|
||||
<ion-item
|
||||
*ngFor="let a of m.alt"
|
||||
*ngFor="let a of stores.alt"
|
||||
detail="false"
|
||||
[button]="a.url !== m.selected"
|
||||
(click)="a.url === m.selected ? '' : presentAction(a, true)"
|
||||
[button]="!a.selected"
|
||||
(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>
|
||||
<h2>{{ a.name }}</h2>
|
||||
<p>{{ a.url }}</p>
|
||||
</ion-label>
|
||||
<ion-icon
|
||||
*ngIf="a.url === m.selected"
|
||||
*ngIf="a.selected"
|
||||
slot="end"
|
||||
size="large"
|
||||
name="checkmark"
|
||||
|
||||
@@ -7,18 +7,15 @@ import {
|
||||
} from '@ionic/angular'
|
||||
import { ActionSheetButton } from '@ionic/core'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import {
|
||||
AbstractMarketplaceService,
|
||||
StoreIdentifier,
|
||||
} from '@start9labs/marketplace'
|
||||
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 { 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 { map } from 'rxjs/operators'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
import { combineLatest, firstValueFrom } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-settings',
|
||||
@@ -27,24 +24,21 @@ import { firstValueFrom } from 'rxjs'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceSettingsPage {
|
||||
marketplace$ = this.patch.watch$('ui', 'marketplace').pipe(
|
||||
map(m => {
|
||||
const selected = m['selected-url']
|
||||
const hosts = Object.entries(m['known-hosts'])
|
||||
stores$ = combineLatest([
|
||||
this.marketplaceService.getKnownHosts$(),
|
||||
this.marketplaceService.getSelectedHost$(),
|
||||
]).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
|
||||
.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 }
|
||||
return { standard, alt }
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -207,16 +201,16 @@ export class MarketplaceSettingsPage {
|
||||
loader.message = 'Validating marketplace...'
|
||||
await loader.present()
|
||||
|
||||
const { name, icon } = await firstValueFrom(
|
||||
const { name } = await firstValueFrom(
|
||||
this.marketplaceService.fetchInfo$(url),
|
||||
)
|
||||
|
||||
// Save
|
||||
loader.message = 'Saving...'
|
||||
|
||||
await this.api.setDbValue<StoreIdentifier>(
|
||||
await this.api.setDbValue<{ name: string }>(
|
||||
['marketplace', 'known-hosts', url],
|
||||
{ name, icon },
|
||||
{ name },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -230,7 +224,7 @@ export class MarketplaceSettingsPage {
|
||||
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)
|
||||
.reduce((prev, curr) => {
|
||||
const name = hosts[curr]
|
||||
@@ -241,7 +235,7 @@ export class MarketplaceSettingsPage {
|
||||
}, {})
|
||||
|
||||
try {
|
||||
await this.api.setDbValue<{ [url: string]: StoreIdentifier }>(
|
||||
await this.api.setDbValue<{ [url: string]: UIStore }>(
|
||||
['marketplace', 'known-hosts'],
|
||||
filtered,
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/b
|
||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||
import { MarketplaceListPage } from './marketplace-list.page'
|
||||
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 = [
|
||||
{
|
||||
@@ -39,6 +40,7 @@ const routes: Routes = [
|
||||
SearchModule,
|
||||
SkeletonModule,
|
||||
MarketplaceSettingsPageModule,
|
||||
StoreIconComponentModule,
|
||||
],
|
||||
declarations: [MarketplaceListPage],
|
||||
exports: [MarketplaceListPage],
|
||||
|
||||
@@ -24,14 +24,7 @@
|
||||
<ion-row>
|
||||
<ion-col size="12">
|
||||
<div class="heading">
|
||||
<img
|
||||
*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>
|
||||
<store-icon class="icon" [url]="details.url"></store-icon>
|
||||
<h1 class="montserrat ion-text-center">{{ details.name }}</h1>
|
||||
</div>
|
||||
<ion-button fill="clear" (click)="presentModalMarketplaceSettings()">
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
.heading {
|
||||
$icon-size: 64px;
|
||||
margin-top: 32px;
|
||||
img {
|
||||
max-width: $icon-size;
|
||||
}
|
||||
h1 {
|
||||
font-size: 42px;
|
||||
}
|
||||
ion-icon {
|
||||
font-size: $icon-size;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
max-width: 80px;
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter, map } from 'rxjs'
|
||||
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 { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@@ -30,25 +31,26 @@ export class MarketplaceListPage {
|
||||
readonly localPkgs$ = this.patch.watch$('package-data')
|
||||
|
||||
readonly details$ = this.marketplaceService.getSelectedHost$().pipe(
|
||||
map(({ url, name, icon }) => {
|
||||
map(({ url, name }) => {
|
||||
const { start9, community, beta } = this.config.marketplace
|
||||
let color: string
|
||||
let description: string
|
||||
switch (url) {
|
||||
case 'https://registry.start9.com/':
|
||||
case start9:
|
||||
color = 'success'
|
||||
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.'
|
||||
break
|
||||
case 'https://beta-registry.start9.com/':
|
||||
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/':
|
||||
case community:
|
||||
color = 'tertiary'
|
||||
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.'
|
||||
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:
|
||||
// alt marketplace
|
||||
color = 'warning'
|
||||
@@ -59,7 +61,6 @@ export class MarketplaceListPage {
|
||||
return {
|
||||
name,
|
||||
url,
|
||||
icon,
|
||||
color,
|
||||
description,
|
||||
}
|
||||
@@ -71,6 +72,7 @@ export class MarketplaceListPage {
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
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 { RoundProgressModule } from 'angular-svg-round-progressbar'
|
||||
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 = [
|
||||
{
|
||||
@@ -27,6 +28,7 @@ const routes: Routes = [
|
||||
MarkdownPipeModule,
|
||||
RoundProgressModule,
|
||||
InstallProgressPipeModule,
|
||||
StoreIconComponentModule,
|
||||
],
|
||||
declarations: [UpdatesPage, FilterUpdatesPipe],
|
||||
})
|
||||
|
||||
@@ -9,26 +9,24 @@
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<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>
|
||||
{{ host.value.name }}
|
||||
<img
|
||||
style="max-width: 24px"
|
||||
[src]="'data:image/png;base64,' + host.value.icon | trustUrl"
|
||||
alt=""
|
||||
/>
|
||||
{{ host.name }}
|
||||
<div style="max-width: 16px">
|
||||
<store-icon [url]="host.url"></store-icon>
|
||||
</div>
|
||||
</ion-item-divider>
|
||||
|
||||
<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-item>
|
||||
|
||||
<ng-container
|
||||
*ngIf="data.marketplace[host.key]?.packages as packages else loading"
|
||||
*ngIf="data.marketplace[host.url]?.packages as packages else loading"
|
||||
>
|
||||
<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">
|
||||
<ng-container *ngIf="data.localPkgs[pkg.manifest.id] as local">
|
||||
@@ -64,7 +62,7 @@
|
||||
></ion-spinner>
|
||||
<ng-template #updateBtn>
|
||||
<ion-button
|
||||
(click)="update(pkg.manifest.id, host.key)"
|
||||
(click)="update(pkg.manifest.id, host.url)"
|
||||
color="dark"
|
||||
strong
|
||||
>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Marketplace,
|
||||
MarketplaceManifest,
|
||||
MarketplacePkg,
|
||||
StoreIdentifier,
|
||||
StoreIdentity,
|
||||
} from '@start9labs/marketplace'
|
||||
import { Emver } from '@start9labs/shared'
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
@@ -20,7 +20,7 @@ import { combineLatest, Observable } from 'rxjs'
|
||||
import { PrimaryRendering } from '../../services/pkg-status-rendering.service'
|
||||
|
||||
interface UpdatesData {
|
||||
hosts: Record<string, StoreIdentifier>
|
||||
hosts: StoreIdentity[]
|
||||
marketplace: Marketplace
|
||||
localPkgs: Record<string, PackageDataEntry>
|
||||
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()
|
||||
export class LiveApiService extends ApiService {
|
||||
readonly eosMarketplaceUrl = 'https://registry.start9.com/'
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private readonly document: Document,
|
||||
private readonly http: HttpService,
|
||||
@@ -136,7 +134,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
||||
const params = {
|
||||
'marketplace-url': url || this.eosMarketplaceUrl,
|
||||
'marketplace-url': url || this.config.marketplace.start9,
|
||||
}
|
||||
return this.rpcRequest({ method: 'server.update', params })
|
||||
}
|
||||
@@ -180,7 +178,7 @@ export class LiveApiService extends ApiService {
|
||||
return this.marketplaceProxy(
|
||||
'/eos/v0/latest',
|
||||
params,
|
||||
this.eosMarketplaceUrl,
|
||||
this.config.marketplace.start9,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||
import { AuthService } from '../auth.service'
|
||||
import { ConnectionService } from '../connection.service'
|
||||
import { StoreInfo } from '@start9labs/marketplace'
|
||||
import { COMMUNITY_REGISTRY, START9_REGISTRY } from './api-icons'
|
||||
|
||||
const PROGRESS: InstallProgress = {
|
||||
size: 120,
|
||||
@@ -284,7 +283,6 @@ export class MockApiService extends ApiService {
|
||||
if (path === '/package/v0/info') {
|
||||
const info: StoreInfo = {
|
||||
name: 'Start9 Registry',
|
||||
icon: START9_REGISTRY,
|
||||
categories: [
|
||||
'bitcoin',
|
||||
'lightning',
|
||||
|
||||
@@ -7,24 +7,19 @@ import {
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { COMMUNITY_REGISTRY, START9_REGISTRY } from './api-icons'
|
||||
import { Mock } from './api.fixures'
|
||||
|
||||
export const mockPatchData: DataModel = {
|
||||
ui: {
|
||||
name: `Matt's Embassy`,
|
||||
'pkg-order': [],
|
||||
'ack-welcome': '1.0.0',
|
||||
marketplace: {
|
||||
'selected-url': 'https://registry.start9.com/',
|
||||
'known-hosts': {
|
||||
'https://registry.start9.com/': {
|
||||
name: 'Start9 Registry',
|
||||
icon: START9_REGISTRY,
|
||||
},
|
||||
'https://community-registry.start9.com/': {
|
||||
icon: COMMUNITY_REGISTRY,
|
||||
},
|
||||
'https://community-registry.start9.com/': {},
|
||||
'https://dark9-marketplace.com/': {
|
||||
name: 'Dark9',
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ const {
|
||||
targetArch,
|
||||
gitHash,
|
||||
useMocks,
|
||||
ui: { api, mocks },
|
||||
ui: { api, marketplace, mocks },
|
||||
} = require('../../../../../config.json') as WorkspaceConfig
|
||||
|
||||
@Injectable({
|
||||
@@ -28,6 +28,7 @@ export class ConfigService {
|
||||
targetArch = targetArch
|
||||
gitHash = gitHash
|
||||
api = api
|
||||
marketplace = marketplace
|
||||
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
|
||||
isConsulate = (window as any)['platform'] === 'ios'
|
||||
supportsWebSockets = !!window.WebSocket || this.isConsulate
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
StoreData,
|
||||
Marketplace,
|
||||
StoreInfo,
|
||||
StoreIdentifier,
|
||||
StoreIdentity,
|
||||
} from '@start9labs/marketplace'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from 'rxjs'
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
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 {
|
||||
catchError,
|
||||
@@ -29,35 +29,53 @@ import {
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
} from 'rxjs/operators'
|
||||
import { getNewEntries } from '@start9labs/shared'
|
||||
import { ConfigService } from './config.service'
|
||||
|
||||
@Injectable()
|
||||
export class MarketplaceService implements AbstractMarketplaceService {
|
||||
private readonly knownHosts$ = this.patch.watch$(
|
||||
'ui',
|
||||
'marketplace',
|
||||
'known-hosts',
|
||||
)
|
||||
private readonly knownHosts$: Observable<StoreIdentity[]> = this.patch
|
||||
.watch$('ui', 'marketplace', 'known-hosts')
|
||||
.pipe(
|
||||
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(
|
||||
distinctUntilKeyChanged('selected-url'),
|
||||
map(data => ({
|
||||
url: data['selected-url'],
|
||||
...data['known-hosts'][data['selected-url']],
|
||||
})),
|
||||
shareReplay(1),
|
||||
)
|
||||
return arr.concat(
|
||||
Object.entries(hosts)
|
||||
.filter(([url, _]) => ![start9, community].includes(url as any))
|
||||
.map(([url, store]) => toStoreIdentity(url, store)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
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(
|
||||
startWith<Record<string, StoreIdentifier>>({}),
|
||||
startWith<StoreIdentity[]>([]),
|
||||
pairwise(),
|
||||
mergeMap(([prev, curr]) => from(Object.entries(getNewEntries(prev, curr)))),
|
||||
mergeMap(([url, registry]) =>
|
||||
mergeMap(([prev, curr]) =>
|
||||
curr.filter(c => !prev.find(p => c.url === p.url)),
|
||||
),
|
||||
mergeMap(({ url, name }) =>
|
||||
this.fetchStore$(url).pipe(
|
||||
tap(data => {
|
||||
if (data?.info) this.updateStoreName(url, name, data.info.name)
|
||||
}),
|
||||
map<StoreData | null, [string, StoreData | null]>(data => {
|
||||
if (data?.info) this.updateStoreIdentifier(url, registry, data.info)
|
||||
|
||||
return [url, data]
|
||||
}),
|
||||
startWith<[string, StoreData | null]>([url, null]),
|
||||
@@ -74,22 +92,30 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
||||
shareReplay(1),
|
||||
)
|
||||
|
||||
private readonly selectedStore$ = this.selectedHost$.pipe(
|
||||
switchMap(({ url }) => this.marketplace$.pipe(map(m => m[url]))),
|
||||
)
|
||||
private readonly selectedStore$: Observable<StoreData | null> =
|
||||
this.selectedHost$.pipe(
|
||||
switchMap(({ url }) =>
|
||||
this.marketplace$.pipe(
|
||||
filter(m => !!m[url]),
|
||||
take(1),
|
||||
map(m => m[url]),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
getKnownHosts$(): Observable<Record<string, StoreIdentifier>> {
|
||||
getKnownHosts$(): Observable<StoreIdentity[]> {
|
||||
return this.knownHosts$
|
||||
}
|
||||
|
||||
getSelectedHost$(): Observable<StoreIdentifier & { url: string }> {
|
||||
getSelectedHost$(): Observable<StoreIdentity> {
|
||||
return this.selectedHost$
|
||||
}
|
||||
|
||||
@@ -234,16 +260,23 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
||||
)
|
||||
}
|
||||
|
||||
private async updateStoreIdentifier(
|
||||
private async updateStoreName(
|
||||
url: string,
|
||||
oldInfo: StoreIdentifier,
|
||||
newInfo: StoreIdentifier,
|
||||
oldName: string | undefined,
|
||||
newName: string,
|
||||
): Promise<void> {
|
||||
if (oldInfo.name !== newInfo.name || oldInfo.icon !== newInfo.icon) {
|
||||
this.api.setDbValue<StoreIdentifier>(
|
||||
['marketplace', 'known-hosts', url],
|
||||
newInfo,
|
||||
if (oldName !== newName) {
|
||||
this.api.setDbValue<string>(
|
||||
['marketplace', 'known-hosts', url, 'name'],
|
||||
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 { 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'
|
||||
|
||||
export interface DataModel {
|
||||
@@ -11,7 +11,6 @@ export interface DataModel {
|
||||
|
||||
export interface UIData {
|
||||
name: string | null
|
||||
'pkg-order': string[]
|
||||
'ack-welcome': string // eOS emver
|
||||
marketplace: UIMarketplaceData
|
||||
dev: DevData
|
||||
@@ -26,12 +25,16 @@ export interface UIData {
|
||||
export interface UIMarketplaceData {
|
||||
'selected-url': string
|
||||
'known-hosts': {
|
||||
'https://registry.start9.com/': StoreIdentifier
|
||||
'https://community-registry.start9.com/': StoreIdentifier
|
||||
[url: string]: StoreIdentifier
|
||||
'https://registry.start9.com/': UIStore
|
||||
'https://community-registry.start9.com/': UIStore
|
||||
[url: string]: UIStore
|
||||
}
|
||||
}
|
||||
|
||||
export interface UIStore {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface DevData {
|
||||
[id: string]: DevProjectData
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user