mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Update Marketplace (#2742)
* update abstract marketplace for usage accuracy andrename store to registry * use new abstract functions * fix(marketplace): get rid of `AbstractMarketplaceService` * bump shared marketplace lib * update marketplace to use query params for registry url; comment out updates page - will be refactored * cleanup * cleanup duplicate * cleanup unused imports * rework setting registry url when loading marketplace * cleanup marketplace service * fix background --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: waterplea <alexander@inkin.ru> Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
"check:setup": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck",
|
"check:setup": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck",
|
||||||
"check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck",
|
"check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck",
|
||||||
"build:deps": "rm -rf .angular/cache && (cd ../patch-db/client && npm ci && npm run build) && (cd ../sdk && make bundle)",
|
"build:deps": "rm -rf .angular/cache && (cd ../patch-db/client && npm ci && npm run build) && (cd ../sdk && make bundle)",
|
||||||
|
"build:deps:win": "rimraf .angular/cache && (cd ../sdk && npm ci && npm run build) && (cd ../patch-db/client && npm ci && npm run build)",
|
||||||
"build:install": "ng run install-wizard:build",
|
"build:install": "ng run install-wizard:build",
|
||||||
"build:setup": "ng run setup-wizard:build",
|
"build:setup": "ng run setup-wizard:build",
|
||||||
"build:ui": "ng run ui:build",
|
"build:ui": "ng run ui:build",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/marketplace",
|
"name": "@start9labs/marketplace",
|
||||||
"version": "0.3.32",
|
"version": "0.3.36",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=13.2.0",
|
"@angular/common": ">=13.2.0",
|
||||||
"@angular/core": ">=13.2.0",
|
"@angular/core": ">=13.2.0",
|
||||||
|
|||||||
@@ -1,113 +1,108 @@
|
|||||||
<header>
|
<header>
|
||||||
<ng-container *tuiLet="store$ | async as store">
|
<div class="title">
|
||||||
<div class="title">
|
<store-icon
|
||||||
<store-icon
|
[class.tui-skeleton]="!registry"
|
||||||
[class.tui-skeleton]="!store"
|
[class.tui-skeleton_rounded]="!registry"
|
||||||
[class.tui-skeleton_rounded]="!store"
|
size="60px"
|
||||||
size="60px"
|
[url]="registry?.url || ''"
|
||||||
[url]="store?.url || ''"
|
[marketplace]="iconConfig"
|
||||||
[marketplace]="iconConfig"
|
/>
|
||||||
/>
|
<h1 [class.tui-skeleton]="!registry">
|
||||||
<h1 [class.tui-skeleton]="!store">
|
{{ registry?.info?.name || 'Unnamed Registry' }}
|
||||||
{{ store?.info?.name || 'Loading store...' }}
|
</h1>
|
||||||
</h1>
|
<!-- change registry modal -->
|
||||||
<!-- change registry modal -->
|
<ng-content select="[slot=desktop]"></ng-content>
|
||||||
<ng-content select="[slot=desktop]"></ng-content>
|
</div>
|
||||||
</div>
|
<!-- mobile nav -->
|
||||||
<!-- mobile nav -->
|
<div class="nav-mobile">
|
||||||
<div class="nav-mobile">
|
<div class="nav-mobile-bar">
|
||||||
<div class="nav-mobile-bar">
|
<!-- mobile search -->
|
||||||
<!-- mobile search -->
|
|
||||||
<marketplace-search
|
|
||||||
[(query)]="query"
|
|
||||||
(queryChange)="onQueryChange($event)"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
type="button"
|
|
||||||
appearance="link"
|
|
||||||
(click)="toggleMenu(true)"
|
|
||||||
(tuiActiveZoneChange)="toggleMenu($event)"
|
|
||||||
[style.--tui-padding]="'1.2rem'"
|
|
||||||
>
|
|
||||||
<store-icon
|
|
||||||
size="42px"
|
|
||||||
[style.height]="'42px'"
|
|
||||||
[style.border-radius]="'100%'"
|
|
||||||
[url]="store?.url || ''"
|
|
||||||
[marketplace]="iconConfig"
|
|
||||||
[class.tui-skeleton]="!store"
|
|
||||||
[class.tui-skeleton_rounded]="!store"
|
|
||||||
/>
|
|
||||||
<nav
|
|
||||||
*tuiSidebar="open; direction: 'right'; autoWidth: true"
|
|
||||||
class="nav-mobile-sidebar divide-bar"
|
|
||||||
>
|
|
||||||
<div class="nav-mobile-sidebar-top">
|
|
||||||
<h1 [class.tui-skeleton]="!store">
|
|
||||||
{{ store?.info?.name }}
|
|
||||||
</h1>
|
|
||||||
<button
|
|
||||||
[style.--tui-padding]="0"
|
|
||||||
tuiButton
|
|
||||||
type="button"
|
|
||||||
appearance="icon"
|
|
||||||
iconStart="@tui.x"
|
|
||||||
(tuiActiveZoneChange)="toggleMenu($event)"
|
|
||||||
(click)="toggleMenu(false)"
|
|
||||||
></button>
|
|
||||||
</div>
|
|
||||||
<!-- change registry modal -->
|
|
||||||
<ng-content select="[slot=mobile]"></ng-content>
|
|
||||||
<div class="nav-mobile-sidebar-bottom divide-bar">
|
|
||||||
<marketplace-categories
|
|
||||||
[categories]="store?.info?.categories"
|
|
||||||
[category]="query ? '' : category"
|
|
||||||
(categoryChange)="onCategoryChange($event); toggleMenu(false)"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<!-- link to store for brochure -->
|
|
||||||
<ng-content select="[slot=store-mobile]" />
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
href="https://docs.start9.com/0.3.5.x/developer-docs/"
|
|
||||||
>
|
|
||||||
<span>Package a service</span>
|
|
||||||
<tui-icon tuiAppearance="icon" icon="@tui.external-link" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- desktop nav -->
|
|
||||||
<nav class="nav-desktop">
|
|
||||||
<!-- desktop search -->
|
|
||||||
<marketplace-search
|
<marketplace-search
|
||||||
[query]="query"
|
[(query)]="query"
|
||||||
(queryChange)="onQueryChange($event)"
|
(queryChange)="onQueryChange($event)"
|
||||||
/>
|
/>
|
||||||
<div class="nav-desktop-container">
|
<button
|
||||||
<marketplace-categories
|
tuiButton
|
||||||
[categories]="store?.info?.categories"
|
type="button"
|
||||||
[category]="query ? '' : category"
|
appearance="link"
|
||||||
(categoryChange)="onCategoryChange($event)"
|
(click)="toggleMenu(true)"
|
||||||
|
(tuiActiveZoneChange)="toggleMenu($event)"
|
||||||
|
[style.--tui-padding]="'1.2rem'"
|
||||||
|
>
|
||||||
|
<store-icon
|
||||||
|
size="42px"
|
||||||
|
[style.height]="'42px'"
|
||||||
|
[style.border-radius]="'100%'"
|
||||||
|
[url]="registry?.url || ''"
|
||||||
|
[marketplace]="iconConfig"
|
||||||
|
[class.tui-skeleton]="!registry"
|
||||||
|
[class.tui-skeleton_rounded]="!registry"
|
||||||
/>
|
/>
|
||||||
<div>
|
<nav
|
||||||
<!-- link to store for brochure -->
|
*tuiSidebar="open; direction: 'right'; autoWidth: true"
|
||||||
<ng-content select="[slot=store]" />
|
class="nav-mobile-sidebar divide-bar"
|
||||||
<a
|
>
|
||||||
target="_blank"
|
<div class="nav-mobile-sidebar-top">
|
||||||
rel="noreferrer"
|
<h1 [class.tui-skeleton]="!registry">
|
||||||
href="https://docs.start9.com/0.3.5.x/developer-docs/"
|
{{ registry?.info?.name }}
|
||||||
>
|
</h1>
|
||||||
<span>Package a service</span>
|
<button
|
||||||
<tui-icon tuiAppearance="icon" icon="@tui.external-link" />
|
[style.--tui-padding]="0"
|
||||||
</a>
|
tuiButton
|
||||||
</div>
|
type="button"
|
||||||
|
appearance="icon"
|
||||||
|
iconStart="@tui.x"
|
||||||
|
(tuiActiveZoneChange)="toggleMenu($event)"
|
||||||
|
(click)="toggleMenu(false)"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
<!-- change registry modal -->
|
||||||
|
<ng-content select="[slot=mobile]"></ng-content>
|
||||||
|
<div class="nav-mobile-sidebar-bottom divide-bar">
|
||||||
|
<marketplace-categories
|
||||||
|
[categories]="registry?.info?.categories"
|
||||||
|
[category]="query ? '' : category"
|
||||||
|
(categoryChange)="onCategoryChange($event); toggleMenu(false)"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<!-- link to store for brochure -->
|
||||||
|
<ng-content select="[slot=store-mobile]" />
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
href="https://docs.start9.com/0.3.5.x/developer-docs/"
|
||||||
|
>
|
||||||
|
<span>Package a service</span>
|
||||||
|
<tui-icon tuiAppearance="icon" icon="@tui.external-link" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- desktop nav -->
|
||||||
|
<nav class="nav-desktop">
|
||||||
|
<!-- desktop search -->
|
||||||
|
<marketplace-search [query]="query" (queryChange)="onQueryChange($event)" />
|
||||||
|
<div class="nav-desktop-container">
|
||||||
|
<marketplace-categories
|
||||||
|
[categories]="registry?.info?.categories"
|
||||||
|
[category]="query ? '' : category"
|
||||||
|
(categoryChange)="onCategoryChange($event)"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<!-- link to store for brochure -->
|
||||||
|
<ng-content select="[slot=store]" />
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
href="https://docs.start9.com/0.3.5.x/developer-docs/"
|
||||||
|
>
|
||||||
|
<span>Package a service</span>
|
||||||
|
<tui-icon tuiAppearance="icon" icon="@tui.external-link" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
</ng-container>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { combineLatest, map, Subject, takeUntil } from 'rxjs'
|
|
||||||
import { StoreIdentity } from '../../types'
|
|
||||||
import { AbstractMarketplaceService } from '../../services/marketplace.service'
|
|
||||||
import { AbstractCategoryService } from '../../services/category.service'
|
|
||||||
import { MarketplaceConfig } from '@start9labs/shared'
|
import { MarketplaceConfig } from '@start9labs/shared'
|
||||||
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
|
import { AbstractCategoryService } from '../../services/category.service'
|
||||||
|
import { StoreDataWithUrl } from '../../types'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'menu',
|
selector: 'menu',
|
||||||
@@ -21,19 +20,11 @@ export class MenuComponent implements OnDestroy {
|
|||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
iconConfig!: MarketplaceConfig
|
iconConfig!: MarketplaceConfig
|
||||||
|
|
||||||
|
@Input({ required: true })
|
||||||
|
registry!: StoreDataWithUrl | null
|
||||||
|
|
||||||
private destroy$ = new Subject<void>()
|
private destroy$ = new Subject<void>()
|
||||||
private readonly marketplaceService = inject(AbstractMarketplaceService)
|
|
||||||
private readonly categoryService = inject(AbstractCategoryService)
|
private readonly categoryService = inject(AbstractCategoryService)
|
||||||
readonly store$ = this.marketplaceService.getSelectedStoreWithCategories$()
|
|
||||||
readonly alt$ = combineLatest([
|
|
||||||
this.marketplaceService.getKnownHosts$(),
|
|
||||||
this.marketplaceService.getSelectedHost$(),
|
|
||||||
]).pipe(
|
|
||||||
map(([stores, selected]) =>
|
|
||||||
stores.filter(({ url }) => url != selected.url),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
private hosts?: StoreIdentity[]
|
|
||||||
category = ''
|
category = ''
|
||||||
query = ''
|
query = ''
|
||||||
open = false
|
open = false
|
||||||
@@ -52,13 +43,6 @@ export class MenuComponent implements OnDestroy {
|
|||||||
.subscribe(val => {
|
.subscribe(val => {
|
||||||
this.category = val
|
this.category = val
|
||||||
})
|
})
|
||||||
|
|
||||||
this.marketplaceService
|
|
||||||
.getKnownHosts$()
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(hosts => {
|
|
||||||
this.hosts = hosts
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCategoryChange(category: string): void {
|
onCategoryChange(category: string): void {
|
||||||
@@ -66,7 +50,6 @@ export class MenuComponent implements OnDestroy {
|
|||||||
this.query = ''
|
this.query = ''
|
||||||
this.categoryService.resetQuery()
|
this.categoryService.resetQuery()
|
||||||
this.categoryService.changeCategory(category)
|
this.categoryService.changeCategory(category)
|
||||||
this.categoryService.handleNavigation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onQueryChange(query: string): void {
|
onQueryChange(query: string): void {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { MarketplacePkg } from '../../src/types'
|
|
||||||
import { Exver, MarkdownPipeModule } from '@start9labs/shared'
|
import { Exver, MarkdownPipeModule } from '@start9labs/shared'
|
||||||
import { TuiButton, TuiDialogContext, TuiLoader } from '@taiga-ui/core'
|
import { TuiButton, TuiDialogContext, TuiLoader } from '@taiga-ui/core'
|
||||||
import { TuiAccordion } from '@taiga-ui/kit'
|
import { TuiAccordion } from '@taiga-ui/kit'
|
||||||
@@ -8,26 +7,21 @@ import {
|
|||||||
POLYMORPHEUS_CONTEXT,
|
POLYMORPHEUS_CONTEXT,
|
||||||
PolymorpheusComponent,
|
PolymorpheusComponent,
|
||||||
} from '@taiga-ui/polymorpheus'
|
} from '@taiga-ui/polymorpheus'
|
||||||
import { map } from 'rxjs'
|
import { MarketplacePkg } from '../../src/types'
|
||||||
import { AbstractMarketplaceService } from '../services/marketplace.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
template: `
|
template: `
|
||||||
@if (notes$ | async; as notes) {
|
<tui-accordion>
|
||||||
<tui-accordion>
|
@for (note of notes | keyvalue: asIsOrder; track $index) {
|
||||||
@for (note of notes | keyvalue: asIsOrder; track $index) {
|
<tui-accordion-item>
|
||||||
<tui-accordion-item>
|
{{ note.key }}
|
||||||
{{ note.key }}
|
<ng-template tuiAccordionItemContent>
|
||||||
<ng-template tuiAccordionItemContent>
|
<div [innerHTML]="note.value | markdown"></div>
|
||||||
<div [innerHTML]="note.value | markdown"></div>
|
</ng-template>
|
||||||
</ng-template>
|
</tui-accordion-item>
|
||||||
</tui-accordion-item>
|
}
|
||||||
}
|
</tui-accordion>
|
||||||
</tui-accordion>
|
|
||||||
} @else {
|
|
||||||
<tui-loader textContent="Loading Release Notes" />
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
@@ -43,26 +37,20 @@ export class ReleaseNotesComponent {
|
|||||||
private readonly pkg =
|
private readonly pkg =
|
||||||
inject<TuiDialogContext<void, MarketplacePkg>>(POLYMORPHEUS_CONTEXT).data
|
inject<TuiDialogContext<void, MarketplacePkg>>(POLYMORPHEUS_CONTEXT).data
|
||||||
|
|
||||||
readonly notes$ = inject(AbstractMarketplaceService)
|
readonly notes = Object.entries(this.pkg.otherVersions)
|
||||||
.getSelectedStore$()
|
.filter(
|
||||||
.pipe(
|
([v, _]) =>
|
||||||
map(s => {
|
this.exver.getFlavor(v) === this.pkg.flavor &&
|
||||||
return Object.entries(this.pkg.otherVersions)
|
this.exver.compareExver(this.pkg.version, v) === 1,
|
||||||
.filter(
|
)
|
||||||
([v, _]) =>
|
.reduce(
|
||||||
this.exver.getFlavor(v) === this.pkg.flavor &&
|
(obj, [version, info]) => ({
|
||||||
this.exver.compareExver(this.pkg.version, v) === 1,
|
...obj,
|
||||||
)
|
[version]: info.releaseNotes,
|
||||||
.reduce(
|
|
||||||
(obj, [version, info]) => ({
|
|
||||||
...obj,
|
|
||||||
[version]: info.releaseNotes,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
[`${this.pkg.version} (current)`]: this.pkg.releaseNotes,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
|
{
|
||||||
|
[`${this.pkg.version} (current)`]: this.pkg.releaseNotes,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
asIsOrder(a: any, b: any) {
|
asIsOrder(a: any, b: any) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<!-- license -->
|
<!-- license -->
|
||||||
<marketplace-additional-item
|
<marketplace-additional-item
|
||||||
(click)="presentModalMd('License')"
|
(click)="static.emit('License')"
|
||||||
[data]="pkg.license"
|
[data]="pkg.license"
|
||||||
label="License"
|
label="License"
|
||||||
icon="@tui.chevron-right"
|
icon="@tui.chevron-right"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
/>
|
/>
|
||||||
<!-- instructions -->
|
<!-- instructions -->
|
||||||
<marketplace-additional-item
|
<marketplace-additional-item
|
||||||
(click)="presentModalMd('Instructions')"
|
(click)="static.emit('Instructions')"
|
||||||
data="Click to view instructions"
|
data="Click to view instructions"
|
||||||
label="Instructions"
|
label="Instructions"
|
||||||
icon="@tui.chevron-right"
|
icon="@tui.chevron-right"
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
inject,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
|
Output,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { CopyService } from '@start9labs/shared'
|
||||||
import { TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
|
||||||
import { CopyService, MarkdownComponent } from '@start9labs/shared'
|
|
||||||
import { MarketplacePkg } from '../../../types'
|
import { MarketplacePkg } from '../../../types'
|
||||||
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-additional',
|
selector: 'marketplace-additional',
|
||||||
@@ -21,7 +20,8 @@ export class AdditionalComponent {
|
|||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
pkg!: MarketplacePkg
|
pkg!: MarketplacePkg
|
||||||
|
|
||||||
private readonly marketplaceService = inject(AbstractMarketplaceService)
|
@Output()
|
||||||
|
readonly static = new EventEmitter<string>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly copyService: CopyService,
|
readonly copyService: CopyService,
|
||||||
@@ -30,19 +30,4 @@ export class AdditionalComponent {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
|
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
|
||||||
|
|
||||||
presentModalMd(label: string) {
|
|
||||||
this.dialogs
|
|
||||||
.open(new PolymorpheusComponent(MarkdownComponent), {
|
|
||||||
label,
|
|
||||||
size: 'l',
|
|
||||||
data: {
|
|
||||||
content: this.marketplaceService.fetchStatic$(
|
|
||||||
this.pkg,
|
|
||||||
label === 'License' ? 'LICENSE.md' : 'instructions.md',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.subscribe()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { MarketplacePkg } from '../../../types'
|
|||||||
tuiCell
|
tuiCell
|
||||||
[routerLink]="[]"
|
[routerLink]="[]"
|
||||||
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
|
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
|
||||||
|
queryParamsHandling="merge"
|
||||||
>
|
>
|
||||||
<tui-avatar [src]="pkg.icon | trustUrl" />
|
<tui-avatar [src]="pkg.icon | trustUrl" />
|
||||||
<span tuiTitle>
|
<span tuiTitle>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export * from './components/menu/menu.component.module'
|
|||||||
export * from './components/menu/menu.component'
|
export * from './components/menu/menu.component'
|
||||||
export * from './components/registry.component'
|
export * from './components/registry.component'
|
||||||
|
|
||||||
export * from './services/marketplace.service'
|
|
||||||
export * from './services/category.service'
|
export * from './services/category.service'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Observable } from 'rxjs'
|
|
||||||
import { Marketplace, MarketplacePkg, StoreData, StoreIdentity } from '../types'
|
|
||||||
|
|
||||||
export abstract class AbstractMarketplaceService {
|
|
||||||
abstract getKnownHosts$(): Observable<StoreIdentity[]>
|
|
||||||
|
|
||||||
abstract getSelectedHost$(): Observable<StoreIdentity>
|
|
||||||
|
|
||||||
abstract getMarketplace$(): Observable<Marketplace>
|
|
||||||
|
|
||||||
abstract getSelectedStore$(): Observable<StoreData>
|
|
||||||
|
|
||||||
abstract getSelectedStoreWithCategories$(): Observable<
|
|
||||||
StoreIdentity & StoreData
|
|
||||||
>
|
|
||||||
|
|
||||||
abstract getPackage$(
|
|
||||||
id: string,
|
|
||||||
version: string | null,
|
|
||||||
flavor: string | null,
|
|
||||||
url?: string,
|
|
||||||
): Observable<MarketplacePkg>
|
|
||||||
|
|
||||||
abstract fetchStatic$(
|
|
||||||
pkg: MarketplacePkg,
|
|
||||||
type: 'LICENSE.md' | 'instructions.md',
|
|
||||||
): Observable<string>
|
|
||||||
}
|
|
||||||
@@ -37,3 +37,5 @@ export type MarketplacePkg = T.PackageVersionInfo &
|
|||||||
version: string
|
version: string
|
||||||
flavor: string | null
|
flavor: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StoreDataWithUrl = StoreData & { url: string }
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { UntypedFormBuilder } from '@angular/forms'
|
|||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
AbstractCategoryService,
|
AbstractCategoryService,
|
||||||
AbstractMarketplaceService,
|
|
||||||
FilterPackagesPipe,
|
FilterPackagesPipe,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
|
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
|
||||||
@@ -35,7 +34,6 @@ import { CategoryService } from './services/category.service'
|
|||||||
import { ClientStorageService } from './services/client-storage.service'
|
import { ClientStorageService } from './services/client-storage.service'
|
||||||
import { DateTransformerService } from './services/date-transformer.service'
|
import { DateTransformerService } from './services/date-transformer.service'
|
||||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
||||||
import { MarketplaceService } from './services/marketplace.service'
|
|
||||||
import { StorageService } from './services/storage.service'
|
import { StorageService } from './services/storage.service'
|
||||||
import { ThemeSwitcherService } from './services/theme-switcher.service'
|
import { ThemeSwitcherService } from './services/theme-switcher.service'
|
||||||
|
|
||||||
@@ -90,10 +88,6 @@ export const APP_PROVIDERS: Provider[] = [
|
|||||||
provide: THEME,
|
provide: THEME,
|
||||||
useExisting: ThemeSwitcherService,
|
useExisting: ThemeSwitcherService,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: AbstractMarketplaceService,
|
|
||||||
useClass: MarketplaceService,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: AbstractCategoryService,
|
provide: AbstractCategoryService,
|
||||||
useClass: CategoryService,
|
useClass: CategoryService,
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import {
|
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
AbstractMarketplaceService,
|
|
||||||
MarketplacePkg,
|
|
||||||
} from '@start9labs/marketplace'
|
|
||||||
import {
|
import {
|
||||||
Exver,
|
Exver,
|
||||||
ErrorService,
|
ErrorService,
|
||||||
@@ -106,12 +103,7 @@ export class MarketplaceControlsComponent {
|
|||||||
private readonly loader = inject(LoadingService)
|
private readonly loader = inject(LoadingService)
|
||||||
private readonly exver = inject(Exver)
|
private readonly exver = inject(Exver)
|
||||||
private readonly router = inject(Router)
|
private readonly router = inject(Router)
|
||||||
private readonly marketplace = inject(
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
AbstractMarketplaceService,
|
|
||||||
) as MarketplaceService
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
url?: string
|
|
||||||
|
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
pkg!: MarketplacePkg
|
pkg!: MarketplacePkg
|
||||||
@@ -125,19 +117,19 @@ export class MarketplaceControlsComponent {
|
|||||||
readonly showDevTools$ = inject(ClientStorageService).showDevTools$
|
readonly showDevTools$ = inject(ClientStorageService).showDevTools$
|
||||||
|
|
||||||
async tryInstall() {
|
async tryInstall() {
|
||||||
const current = await firstValueFrom(this.marketplace.getSelectedHost$())
|
const currentUrl = await firstValueFrom(
|
||||||
const url = this.url || current.url
|
this.marketplaceService.getRegistryUrl$(),
|
||||||
|
)
|
||||||
const originalUrl = this.localPkg?.registry || ''
|
const originalUrl = this.localPkg?.registry || ''
|
||||||
|
|
||||||
if (!this.localPkg) {
|
if (!this.localPkg) {
|
||||||
if (await this.alerts.alertInstall(this.pkg)) this.install(url)
|
if (await this.alerts.alertInstall(this.pkg)) this.install(currentUrl)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!sameUrl(url, originalUrl) &&
|
!sameUrl(currentUrl, originalUrl) &&
|
||||||
!(await this.alerts.alertMarketplace(url, originalUrl))
|
!(await this.alerts.alertMarketplace(currentUrl, originalUrl))
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -148,9 +140,9 @@ export class MarketplaceControlsComponent {
|
|||||||
hasCurrentDeps(localManifest.id, await getAllPackages(this.patch)) &&
|
hasCurrentDeps(localManifest.id, await getAllPackages(this.patch)) &&
|
||||||
this.exver.compareExver(localManifest.version, this.pkg.version) !== 0
|
this.exver.compareExver(localManifest.version, this.pkg.version) !== 0
|
||||||
) {
|
) {
|
||||||
this.dryInstall(url)
|
this.dryInstall(currentUrl)
|
||||||
} else {
|
} else {
|
||||||
this.install(url)
|
this.install(currentUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +170,7 @@ export class MarketplaceControlsComponent {
|
|||||||
const { id, version } = this.pkg
|
const { id, version } = this.pkg
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.marketplace.installPackage(id, version, url)
|
await this.marketplaceService.installPackage(id, version, url)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import { MenuModule } from '@start9labs/marketplace'
|
import { MenuModule } from '@start9labs/marketplace'
|
||||||
import {
|
import {
|
||||||
TuiDialogService,
|
TuiDialogService,
|
||||||
@@ -8,12 +9,13 @@ import {
|
|||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
||||||
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
selector: 'marketplace-menu',
|
selector: 'marketplace-menu',
|
||||||
template: `
|
template: `
|
||||||
<menu [iconConfig]="marketplace">
|
<menu [iconConfig]="marketplace" [registry]="registry$ | async">
|
||||||
<button
|
<button
|
||||||
slot="desktop"
|
slot="desktop"
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
@@ -45,11 +47,13 @@ import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
|||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [MenuModule, TuiButton, TuiIcon, TuiAppearance],
|
imports: [CommonModule, MenuModule, TuiButton, TuiIcon, TuiAppearance],
|
||||||
})
|
})
|
||||||
export class MarketplaceMenuComponent {
|
export class MarketplaceMenuComponent {
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
readonly marketplace = inject(ConfigService).marketplace
|
readonly marketplace = inject(ConfigService).marketplace
|
||||||
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
|
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||||
|
|
||||||
changeRegistry() {
|
changeRegistry() {
|
||||||
this.dialogs
|
this.dialogs
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ export class MarketplaceTileComponent {
|
|||||||
id: open ? this.pkg().id : null,
|
id: open ? this.pkg().id : null,
|
||||||
flavor: open ? this.pkg().flavor : null,
|
flavor: open ? this.pkg().flavor : null,
|
||||||
},
|
},
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
AbstractCategoryService,
|
AbstractCategoryService,
|
||||||
AbstractMarketplaceService,
|
|
||||||
FilterPackagesPipe,
|
FilterPackagesPipe,
|
||||||
|
FilterPackagesPipeModule,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { combineLatest, map } from 'rxjs'
|
import { tap, withLatestFrom } from 'rxjs'
|
||||||
import { MarketplaceNotificationComponent } from './components/notification.component'
|
import { MarketplaceNotificationComponent } from './components/notification.component'
|
||||||
import { MarketplaceMenuComponent } from './components/menu.component'
|
import { MarketplaceMenuComponent } from './components/menu.component'
|
||||||
import { MarketplaceTileComponent } from './components/tile.component'
|
import { MarketplaceTileComponent } from './components/tile.component'
|
||||||
import { MarketplaceControlsComponent } from './components/controls.component'
|
import { MarketplaceControlsComponent } from './components/controls.component'
|
||||||
import { MarketplacePreviewComponent } from './modals/preview.component'
|
import { MarketplacePreviewComponent } from './modals/preview.component'
|
||||||
import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
||||||
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -21,15 +26,19 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
|||||||
<tui-scrollbar>
|
<tui-scrollbar>
|
||||||
<div class="marketplace-content-wrapper">
|
<div class="marketplace-content-wrapper">
|
||||||
<div class="marketplace-content-inner">
|
<div class="marketplace-content-inner">
|
||||||
<marketplace-notification [url]="(details$ | async)?.url || ''" />
|
<marketplace-notification [url]="(url$ | async) || ''" />
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<h1>
|
<h1>
|
||||||
{{ category$ | async | titlecase }}
|
{{ category$ | async | titlecase }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@if (filtered$ | async; as filtered) {
|
@if (registry$ | async; as registry) {
|
||||||
<section class="marketplace-content-list">
|
<section class="marketplace-content-list">
|
||||||
@for (pkg of filtered; track $index) {
|
@for (
|
||||||
|
pkg of registry.packages
|
||||||
|
| filterPackages: (query$ | async) : (category$ | async);
|
||||||
|
track $index
|
||||||
|
) {
|
||||||
<marketplace-tile
|
<marketplace-tile
|
||||||
[pkg]="pkg"
|
[pkg]="pkg"
|
||||||
[style.--animation-order]="$index"
|
[style.--animation-order]="$index"
|
||||||
@@ -58,6 +67,7 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
background: rgb(55 58 63 / 90%)
|
background: rgb(55 58 63 / 90%)
|
||||||
url('/assets/img/background_marketplace.png') no-repeat top right;
|
url('/assets/img/background_marketplace.png') no-repeat top right;
|
||||||
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.marketplace-content {
|
.marketplace-content {
|
||||||
@@ -127,6 +137,7 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(tui-root._mobile) {
|
:host-context(tui-root._mobile) {
|
||||||
@@ -145,20 +156,34 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
|||||||
MarketplacePreviewComponent,
|
MarketplacePreviewComponent,
|
||||||
MarketplaceSidebarsComponent,
|
MarketplaceSidebarsComponent,
|
||||||
TuiScrollbar,
|
TuiScrollbar,
|
||||||
|
FilterPackagesPipeModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MarketplaceComponent {
|
export class MarketplaceComponent {
|
||||||
private readonly pipe = inject(FilterPackagesPipe)
|
|
||||||
private readonly categoryService = inject(AbstractCategoryService)
|
private readonly categoryService = inject(AbstractCategoryService)
|
||||||
private readonly marketplaceService = inject(AbstractMarketplaceService)
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
|
private readonly router = inject(Router)
|
||||||
|
private readonly patch = inject(PatchDB<DataModel>)
|
||||||
|
private readonly route = inject(ActivatedRoute)
|
||||||
|
.queryParamMap.pipe(
|
||||||
|
takeUntilDestroyed(),
|
||||||
|
withLatestFrom(this.patch.watch$('ui', 'marketplace', 'selectedUrl')),
|
||||||
|
tap(([params, selectedUrl]) => {
|
||||||
|
const registry = params.get('registry')
|
||||||
|
if (!registry) {
|
||||||
|
this.router.navigate([], {
|
||||||
|
queryParams: { registry: selectedUrl },
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.marketplaceService.setRegistryUrl(registry)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe()
|
||||||
|
|
||||||
readonly details$ = this.marketplaceService.getSelectedHost$()
|
readonly url$ = this.marketplaceService.getRegistryUrl$()
|
||||||
readonly category$ = this.categoryService.getCategory$()
|
readonly category$ = this.categoryService.getCategory$()
|
||||||
readonly filtered$ = combineLatest([
|
readonly query$ = this.categoryService.getQuery$()
|
||||||
this.marketplaceService
|
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||||
.getSelectedStore$()
|
|
||||||
.pipe(map(({ packages }) => packages)),
|
|
||||||
this.categoryService.getQuery$(),
|
|
||||||
this.category$,
|
|
||||||
]).pipe(map(args => this.pipe.transform(...args)))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { FormsModule } from '@angular/forms'
|
|||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
AboutModule,
|
AboutModule,
|
||||||
AbstractMarketplaceService,
|
|
||||||
AdditionalModule,
|
AdditionalModule,
|
||||||
FlavorsComponent,
|
FlavorsComponent,
|
||||||
MarketplaceAdditionalItemComponent,
|
MarketplaceAdditionalItemComponent,
|
||||||
@@ -35,6 +34,7 @@ import {
|
|||||||
startWith,
|
startWith,
|
||||||
switchMap,
|
switchMap,
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-preview',
|
selector: 'marketplace-preview',
|
||||||
@@ -186,8 +186,8 @@ export class MarketplacePreviewComponent {
|
|||||||
private readonly dialogs = inject(TuiDialogService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
private readonly exver = inject(Exver)
|
private readonly exver = inject(Exver)
|
||||||
private readonly router = inject(Router)
|
private readonly router = inject(Router)
|
||||||
private readonly marketplaceService = inject(AbstractMarketplaceService)
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
private readonly version$ = new BehaviorSubject<string>('*')
|
private readonly version$ = new BehaviorSubject<string | null>(null)
|
||||||
private readonly flavor$ = this.router.routerState.root.queryParamMap.pipe(
|
private readonly flavor$ = this.router.routerState.root.queryParamMap.pipe(
|
||||||
map(paramMap => paramMap.get('flavor')),
|
map(paramMap => paramMap.get('flavor')),
|
||||||
)
|
)
|
||||||
@@ -202,7 +202,7 @@ export class MarketplacePreviewComponent {
|
|||||||
|
|
||||||
readonly flavors$ = this.flavor$.pipe(
|
readonly flavors$ = this.flavor$.pipe(
|
||||||
switchMap(current =>
|
switchMap(current =>
|
||||||
this.marketplaceService.getSelectedStore$().pipe(
|
this.marketplaceService.getRegistry$().pipe(
|
||||||
map(({ packages }) =>
|
map(({ packages }) =>
|
||||||
packages.filter(
|
packages.filter(
|
||||||
({ id, flavor }) => id === this.pkgId && flavor !== current,
|
({ id, flavor }) => id === this.pkgId && flavor !== current,
|
||||||
|
|||||||
@@ -9,12 +9,20 @@ import {
|
|||||||
toUrl,
|
toUrl,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
|
||||||
StoreIconComponentModule,
|
StoreIconComponentModule,
|
||||||
MarketplaceRegistryComponent,
|
MarketplaceRegistryComponent,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { TuiDialogService, TuiIcon, TuiTitle, TuiButton } from '@taiga-ui/core'
|
import {
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
TuiDialogService,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTitle,
|
||||||
|
TuiButton,
|
||||||
|
TuiDialogContext,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
PolymorpheusComponent,
|
||||||
|
POLYMORPHEUS_CONTEXT,
|
||||||
|
} from '@taiga-ui/polymorpheus'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
|
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
|
||||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||||
@@ -24,6 +32,7 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
|||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -90,9 +99,10 @@ export class MarketplaceRegistryModal {
|
|||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
private readonly formDialog = inject(FormDialogService)
|
private readonly formDialog = inject(FormDialogService)
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
private readonly marketplace = inject(
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
AbstractMarketplaceService,
|
private readonly context = inject<TuiDialogContext>(POLYMORPHEUS_CONTEXT)
|
||||||
) as MarketplaceService
|
private readonly route = inject(ActivatedRoute)
|
||||||
|
private readonly router = inject(Router)
|
||||||
private readonly hosts$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
private readonly hosts$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||||
'ui',
|
'ui',
|
||||||
'marketplace',
|
'marketplace',
|
||||||
@@ -101,13 +111,13 @@ export class MarketplaceRegistryModal {
|
|||||||
readonly marketplaceConfig = inject(ConfigService).marketplace
|
readonly marketplaceConfig = inject(ConfigService).marketplace
|
||||||
|
|
||||||
readonly stores$ = combineLatest([
|
readonly stores$ = combineLatest([
|
||||||
this.marketplace.getKnownHosts$(),
|
this.marketplaceService.getKnownHosts$(),
|
||||||
this.marketplace.getSelectedHost$(),
|
this.marketplaceService.getRegistryUrl$(),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([stores, selected]) =>
|
map(([stores, selectedUrl]) =>
|
||||||
stores.map(s => ({
|
stores.map(s => ({
|
||||||
...s,
|
...s,
|
||||||
selected: sameUrl(s.url, selected.url),
|
selected: sameUrl(s.url, selectedUrl),
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
// 0 and 1 are prod and community, 2 and beyond are alts
|
// 0 and 1 are prod and community, 2 and beyond are alts
|
||||||
@@ -170,9 +180,14 @@ export class MarketplaceRegistryModal {
|
|||||||
loader.unsubscribe()
|
loader.unsubscribe()
|
||||||
loader.closed = false
|
loader.closed = false
|
||||||
loader.add(this.loader.open('Changing Registry...').subscribe())
|
loader.add(this.loader.open('Changing Registry...').subscribe())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.setDbValue<string>(['marketplace', 'selectedUrl'], url)
|
this.marketplaceService.setRegistryUrl(url)
|
||||||
|
this.router.navigate([], {
|
||||||
|
queryParams: { registry: url },
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
})
|
||||||
|
this.api.setDbValue<string>(['marketplace', 'selectedUrl'], url)
|
||||||
|
this.context.$implicit.complete()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -210,7 +225,9 @@ export class MarketplaceRegistryModal {
|
|||||||
loader.closed = false
|
loader.closed = false
|
||||||
loader.add(this.loader.open('Validating marketplace...').subscribe())
|
loader.add(this.loader.open('Validating marketplace...').subscribe())
|
||||||
|
|
||||||
const { name } = await firstValueFrom(this.marketplace.fetchInfo$(url))
|
const { name } = await firstValueFrom(
|
||||||
|
this.marketplaceService.fetchInfo$(url),
|
||||||
|
)
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
loader.unsubscribe()
|
loader.unsubscribe()
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ const ROUTES: Routes = [
|
|||||||
loadComponent: () => import('./sideload/sideload.component'),
|
loadComponent: () => import('./sideload/sideload.component'),
|
||||||
data: toNavigationItem('/portal/system/sideload'),
|
data: toNavigationItem('/portal/system/sideload'),
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: systemTabResolver,
|
// title: systemTabResolver,
|
||||||
path: 'updates',
|
// path: 'updates',
|
||||||
loadComponent: () => import('./updates/updates.component'),
|
// loadComponent: () => import('./updates/updates.component'),
|
||||||
data: toNavigationItem('/portal/system/updates'),
|
// data: toNavigationItem('/portal/system/updates'),
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: systemTabResolver,
|
title: systemTabResolver,
|
||||||
path: 'metrics',
|
path: 'metrics',
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
// import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
import { Exver } from '@start9labs/shared'
|
// import { Exver } from '@start9labs/shared'
|
||||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
// import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
import {
|
// import {
|
||||||
InstalledState,
|
// InstalledState,
|
||||||
PackageDataEntry,
|
// PackageDataEntry,
|
||||||
UpdatingState,
|
// UpdatingState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
// } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@Pipe({
|
// @Pipe({
|
||||||
name: 'filterUpdates',
|
// name: 'filterUpdates',
|
||||||
standalone: true,
|
// standalone: true,
|
||||||
})
|
// })
|
||||||
export class FilterUpdatesPipe implements PipeTransform {
|
// export class FilterUpdatesPipe implements PipeTransform {
|
||||||
private readonly exver = inject(Exver)
|
// private readonly exver = inject(Exver)
|
||||||
|
|
||||||
transform(
|
// transform(
|
||||||
pkgs?: MarketplacePkg[],
|
// pkgs?: MarketplacePkg[],
|
||||||
local: Record<
|
// local: Record<
|
||||||
string,
|
// string,
|
||||||
PackageDataEntry<InstalledState | UpdatingState>
|
// PackageDataEntry<InstalledState | UpdatingState>
|
||||||
> = {},
|
// > = {},
|
||||||
): MarketplacePkg[] | null {
|
// ): MarketplacePkg[] | null {
|
||||||
return (
|
// return (
|
||||||
pkgs?.filter(
|
// pkgs?.filter(
|
||||||
({ id, version, flavor }) =>
|
// ({ id, version, flavor }) =>
|
||||||
local[id] &&
|
// local[id] &&
|
||||||
this.exver.getFlavor(getVersion(local, id)) === flavor &&
|
// this.exver.getFlavor(getVersion(local, id)) === flavor &&
|
||||||
this.exver.compareExver(version, getVersion(local, id)) === 1,
|
// this.exver.compareExver(version, getVersion(local, id)) === 1,
|
||||||
) || null
|
// ) || null
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
function getVersion(
|
// function getVersion(
|
||||||
local: Record<string, PackageDataEntry<InstalledState | UpdatingState>>,
|
// local: Record<string, PackageDataEntry<InstalledState | UpdatingState>>,
|
||||||
id: string,
|
// id: string,
|
||||||
): string {
|
// ): string {
|
||||||
return local[id].stateInfo.manifest.version
|
// return local[id].stateInfo.manifest.version
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,202 +1,199 @@
|
|||||||
import { Component, inject, Input } from '@angular/core'
|
// import { Component, inject, Input } from '@angular/core'
|
||||||
import { RouterLink } from '@angular/router'
|
// import { RouterLink } from '@angular/router'
|
||||||
import {
|
// import {
|
||||||
AbstractMarketplaceService,
|
// MarketplacePkg,
|
||||||
MarketplacePkg,
|
// } from '@start9labs/marketplace'
|
||||||
} from '@start9labs/marketplace'
|
// import {
|
||||||
import {
|
// MarkdownPipeModule,
|
||||||
MarkdownPipeModule,
|
// SafeLinksDirective,
|
||||||
SafeLinksDirective,
|
// SharedPipesModule,
|
||||||
SharedPipesModule,
|
// } from '@start9labs/shared'
|
||||||
} from '@start9labs/shared'
|
// import {
|
||||||
import {
|
// TuiDialogService,
|
||||||
TuiDialogService,
|
// TuiLoader,
|
||||||
TuiLoader,
|
// TuiIcon,
|
||||||
TuiIcon,
|
// TuiLink,
|
||||||
TuiLink,
|
// TuiButton,
|
||||||
TuiButton,
|
// } from '@taiga-ui/core'
|
||||||
} from '@taiga-ui/core'
|
// import {
|
||||||
import {
|
// TuiProgress,
|
||||||
TuiProgress,
|
// TuiAccordion,
|
||||||
TuiAccordion,
|
// TuiAvatar,
|
||||||
TuiAvatar,
|
// TUI_CONFIRM,
|
||||||
TUI_CONFIRM,
|
// } from '@taiga-ui/kit'
|
||||||
} from '@taiga-ui/kit'
|
// import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
// import { PatchDB } from 'patch-db-client'
|
||||||
import { PatchDB } from 'patch-db-client'
|
// import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
||||||
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
// import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
// import {
|
||||||
import {
|
// DataModel,
|
||||||
DataModel,
|
// InstalledState,
|
||||||
InstalledState,
|
// PackageDataEntry,
|
||||||
PackageDataEntry,
|
// UpdatingState,
|
||||||
UpdatingState,
|
// } from 'src/app/services/patch-db/data-model'
|
||||||
} from 'src/app/services/patch-db/data-model'
|
// import { getAllPackages } from 'src/app/utils/get-package-data'
|
||||||
import { getAllPackages } from 'src/app/utils/get-package-data'
|
// import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||||
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
|
||||||
|
|
||||||
@Component({
|
// @Component({
|
||||||
selector: 'updates-item',
|
// selector: 'updates-item',
|
||||||
template: `
|
// template: `
|
||||||
<tui-accordion-item borders="top-bottom">
|
// <tui-accordion-item borders="top-bottom">
|
||||||
<div class="g-action">
|
// <div class="g-action">
|
||||||
<tui-avatar size="s">
|
// <tui-avatar size="s">
|
||||||
<img alt="" [src]="marketplacePkg.icon" />
|
// <img alt="" [src]="marketplacePkg.icon" />
|
||||||
</tui-avatar>
|
// </tui-avatar>
|
||||||
<div [style.flex]="1" [style.overflow]="'hidden'">
|
// <div [style.flex]="1" [style.overflow]="'hidden'">
|
||||||
<strong>{{ marketplacePkg.title }}</strong>
|
// <strong>{{ marketplacePkg.title }}</strong>
|
||||||
<div>
|
// <div>
|
||||||
{{ localPkg.stateInfo.manifest.version }}
|
// {{ localPkg.stateInfo.manifest.version }}
|
||||||
<tui-icon icon="@tui.arrow-right" [style.font-size.rem]="1" />
|
// <tui-icon icon="@tui.arrow-right" [style.font-size.rem]="1" />
|
||||||
<span [style.color]="'var(--tui-text-positive)'">
|
// <span [style.color]="'var(--tui-text-positive)'">
|
||||||
{{ marketplacePkg.version }}
|
// {{ marketplacePkg.version }}
|
||||||
</span>
|
// </span>
|
||||||
</div>
|
// </div>
|
||||||
<div [style.color]="'var(--tui-text-negative)'">{{ errors }}</div>
|
// <div [style.color]="'var(--tui-text-negative)'">{{ errors }}</div>
|
||||||
</div>
|
// </div>
|
||||||
@if (localPkg.stateInfo.state === 'updating') {
|
// @if (localPkg.stateInfo.state === 'updating') {
|
||||||
<tui-progress-circle
|
// <tui-progress-circle
|
||||||
class="g-success"
|
// class="g-success"
|
||||||
size="s"
|
// size="s"
|
||||||
[max]="1"
|
// [max]="1"
|
||||||
[value]="
|
// [value]="
|
||||||
(localPkg.stateInfo.installingInfo.progress.overall
|
// (localPkg.stateInfo.installingInfo.progress.overall
|
||||||
| installingProgress) || 0
|
// | installingProgress) || 0
|
||||||
"
|
// "
|
||||||
/>
|
// />
|
||||||
} @else {
|
// } @else {
|
||||||
@if (ready) {
|
// @if (ready) {
|
||||||
<button
|
// <button
|
||||||
tuiButton
|
// tuiButton
|
||||||
size="s"
|
// size="s"
|
||||||
[appearance]="errors ? 'destructive' : 'primary'"
|
// [appearance]="errors ? 'destructive' : 'primary'"
|
||||||
(click.stop)="onClick()"
|
// (click.stop)="onClick()"
|
||||||
>
|
// >
|
||||||
{{ errors ? 'Retry' : 'Update' }}
|
// {{ errors ? 'Retry' : 'Update' }}
|
||||||
</button>
|
// </button>
|
||||||
} @else {
|
// } @else {
|
||||||
<tui-loader [style.width.rem]="2" [inheritColor]="true" />
|
// <tui-loader [style.width.rem]="2" [inheritColor]="true" />
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
</div>
|
// </div>
|
||||||
<ng-template tuiAccordionItemContent>
|
// <ng-template tuiAccordionItemContent>
|
||||||
<strong>What's new</strong>
|
// <strong>What's new</strong>
|
||||||
<p
|
// <p
|
||||||
safeLinks
|
// safeLinks
|
||||||
[innerHTML]="marketplacePkg.releaseNotes | markdown | dompurify"
|
// [innerHTML]="marketplacePkg.releaseNotes | markdown | dompurify"
|
||||||
></p>
|
// ></p>
|
||||||
<a
|
// <a
|
||||||
tuiLink
|
// tuiLink
|
||||||
iconEnd="@tui.external-link"
|
// iconEnd="@tui.external-link"
|
||||||
routerLink="/marketplace"
|
// routerLink="/marketplace"
|
||||||
[queryParams]="{ url: url, id: marketplacePkg.id }"
|
// [queryParams]="{ url: url, id: marketplacePkg.id }"
|
||||||
>
|
// >
|
||||||
View listing
|
// View listing
|
||||||
</a>
|
// </a>
|
||||||
</ng-template>
|
// </ng-template>
|
||||||
</tui-accordion-item>
|
// </tui-accordion-item>
|
||||||
`,
|
// `,
|
||||||
styles: [
|
// styles: [
|
||||||
`
|
// `
|
||||||
:host {
|
// :host {
|
||||||
display: block;
|
// display: block;
|
||||||
--tui-background-neutral-1-hover: transparent;
|
// --tui-background-neutral-1-hover: transparent;
|
||||||
|
|
||||||
&:not(:last-child) {
|
// &:not(:last-child) {
|
||||||
border-bottom: 1px solid var(--tui-background-neutral-1);
|
// border-bottom: 1px solid var(--tui-background-neutral-1);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
`,
|
// `,
|
||||||
],
|
// ],
|
||||||
standalone: true,
|
// standalone: true,
|
||||||
imports: [
|
// imports: [
|
||||||
RouterLink,
|
// RouterLink,
|
||||||
MarkdownPipeModule,
|
// MarkdownPipeModule,
|
||||||
NgDompurifyModule,
|
// NgDompurifyModule,
|
||||||
SafeLinksDirective,
|
// SafeLinksDirective,
|
||||||
SharedPipesModule,
|
// SharedPipesModule,
|
||||||
TuiProgress,
|
// TuiProgress,
|
||||||
TuiAccordion,
|
// TuiAccordion,
|
||||||
TuiAvatar,
|
// TuiAvatar,
|
||||||
TuiIcon,
|
// TuiIcon,
|
||||||
TuiButton,
|
// TuiButton,
|
||||||
TuiLink,
|
// TuiLink,
|
||||||
TuiLoader,
|
// TuiLoader,
|
||||||
InstallingProgressPipe,
|
// InstallingProgressPipe,
|
||||||
],
|
// ],
|
||||||
})
|
// })
|
||||||
export class UpdatesItemComponent {
|
// export class UpdatesItemComponent {
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
// private readonly dialogs = inject(TuiDialogService)
|
||||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
// private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
private readonly marketplace = inject(
|
// private readonly marketplaceService = inject(MarketplaceService)
|
||||||
AbstractMarketplaceService,
|
|
||||||
) as MarketplaceService
|
|
||||||
|
|
||||||
@Input({ required: true })
|
// @Input({ required: true })
|
||||||
marketplacePkg!: MarketplacePkg
|
// marketplacePkg!: MarketplacePkg
|
||||||
|
|
||||||
@Input({ required: true })
|
// @Input({ required: true })
|
||||||
localPkg!: PackageDataEntry<InstalledState | UpdatingState>
|
// localPkg!: PackageDataEntry<InstalledState | UpdatingState>
|
||||||
|
|
||||||
@Input({ required: true })
|
// @Input({ required: true })
|
||||||
url!: string
|
// url!: string
|
||||||
|
|
||||||
get pkgId(): string {
|
// get pkgId(): string {
|
||||||
return this.marketplacePkg.id
|
// return this.marketplacePkg.id
|
||||||
}
|
// }
|
||||||
|
|
||||||
get errors(): string {
|
// get errors(): string {
|
||||||
return this.marketplace.updateErrors[this.pkgId]
|
// return this.marketplaceService.updateErrors[this.pkgId]
|
||||||
}
|
// }
|
||||||
|
|
||||||
get ready(): boolean {
|
// get ready(): boolean {
|
||||||
return !this.marketplace.updateQueue[this.pkgId]
|
// return !this.marketplaceService.updateQueue[this.pkgId]
|
||||||
}
|
// }
|
||||||
|
|
||||||
async onClick() {
|
// async onClick() {
|
||||||
const { id } = this.marketplacePkg
|
// const { id } = this.marketplacePkg
|
||||||
|
|
||||||
delete this.marketplace.updateErrors[id]
|
// delete this.marketplaceService.updateErrors[id]
|
||||||
this.marketplace.updateQueue[id] = true
|
// this.marketplaceService.updateQueue[id] = true
|
||||||
|
|
||||||
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
|
// if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
|
||||||
const proceed = await this.alert()
|
// const proceed = await this.alert()
|
||||||
|
|
||||||
if (proceed) {
|
// if (proceed) {
|
||||||
await this.update()
|
// await this.update()
|
||||||
} else {
|
// } else {
|
||||||
delete this.marketplace.updateQueue[id]
|
// delete this.marketplaceService.updateQueue[id]
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
await this.update()
|
// await this.update()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async update() {
|
// private async update() {
|
||||||
const { id, version } = this.marketplacePkg
|
// const { id, version } = this.marketplacePkg
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
await this.marketplace.installPackage(id, version, this.url)
|
// await this.marketplaceService.installPackage(id, version, this.url)
|
||||||
delete this.marketplace.updateQueue[id]
|
// delete this.marketplaceService.updateQueue[id]
|
||||||
} catch (e: any) {
|
// } catch (e: any) {
|
||||||
delete this.marketplace.updateQueue[id]
|
// delete this.marketplaceService.updateQueue[id]
|
||||||
this.marketplace.updateErrors[id] = e.message
|
// this.marketplaceService.updateErrors[id] = e.message
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async alert(): Promise<boolean> {
|
// private async alert(): Promise<boolean> {
|
||||||
return new Promise(async resolve => {
|
// return new Promise(async resolve => {
|
||||||
this.dialogs
|
// this.dialogs
|
||||||
.open<boolean>(TUI_CONFIRM, {
|
// .open<boolean>(TUI_CONFIRM, {
|
||||||
label: 'Warning',
|
// label: 'Warning',
|
||||||
size: 's',
|
// size: 's',
|
||||||
data: {
|
// data: {
|
||||||
content: `Services that depend on ${this.localPkg.stateInfo.manifest.title} will no longer work properly and may crash`,
|
// content: `Services that depend on ${this.localPkg.stateInfo.manifest.title} will no longer work properly and may crash`,
|
||||||
yes: 'Continue',
|
// yes: 'Continue',
|
||||||
no: 'Cancel',
|
// no: 'Cancel',
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
.subscribe(response => resolve(response))
|
// .subscribe(response => resolve(response))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,98 +1,95 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
// import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
// import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import {
|
// import {
|
||||||
AbstractMarketplaceService,
|
// StoreIconComponentModule,
|
||||||
StoreIconComponentModule,
|
// } from '@start9labs/marketplace'
|
||||||
} from '@start9labs/marketplace'
|
// import { TuiAvatar } from '@taiga-ui/kit'
|
||||||
import { TuiAvatar } from '@taiga-ui/kit'
|
// import { TuiCell } from '@taiga-ui/layout'
|
||||||
import { TuiCell } from '@taiga-ui/layout'
|
// import { PatchDB } from 'patch-db-client'
|
||||||
import { PatchDB } from 'patch-db-client'
|
// import { combineLatest, map } from 'rxjs'
|
||||||
import { combineLatest, map } from 'rxjs'
|
// import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/system/updates/filter-updates.pipe'
|
||||||
import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/system/updates/filter-updates.pipe'
|
// import { UpdatesItemComponent } from 'src/app/routes/portal/routes/system/updates/item.component'
|
||||||
import { UpdatesItemComponent } from 'src/app/routes/portal/routes/system/updates/item.component'
|
// import { ConfigService } from 'src/app/services/config.service'
|
||||||
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 {
|
||||||
import {
|
// DataModel,
|
||||||
DataModel,
|
// InstalledState,
|
||||||
InstalledState,
|
// PackageDataEntry,
|
||||||
PackageDataEntry,
|
// UpdatingState,
|
||||||
UpdatingState,
|
// } from 'src/app/services/patch-db/data-model'
|
||||||
} from 'src/app/services/patch-db/data-model'
|
// import { isInstalled, isUpdating } from 'src/app/utils/get-package-data'
|
||||||
import { isInstalled, isUpdating } from 'src/app/utils/get-package-data'
|
|
||||||
|
|
||||||
@Component({
|
// @Component({
|
||||||
template: `
|
// template: `
|
||||||
@if (data$ | async; as data) {
|
// @if (data$ | async; as data) {
|
||||||
@for (host of data.hosts; track host) {
|
// @for (host of data.hosts; track host) {
|
||||||
<h3 class="g-title">
|
// <h3 class="g-title">
|
||||||
<store-icon [url]="host.url" [marketplace]="mp" size="26px" />
|
// <store-icon [url]="host.url" [marketplace]="mp" size="26px" />
|
||||||
{{ host.name }}
|
// {{ host.name }}
|
||||||
</h3>
|
// </h3>
|
||||||
@if (data.errors.includes(host.url)) {
|
// @if (data.errors.includes(host.url)) {
|
||||||
<p class="g-error">Request Failed</p>
|
// <p class="g-error">Request Failed</p>
|
||||||
}
|
// }
|
||||||
@if (data.mp[host.url]?.packages | filterUpdates: data.local; as pkgs) {
|
// @if (data.mp[host.url]?.packages | filterUpdates: data.local; as pkgs) {
|
||||||
@for (pkg of pkgs; track pkg) {
|
// @for (pkg of pkgs; track pkg) {
|
||||||
<updates-item
|
// <updates-item
|
||||||
[marketplacePkg]="pkg"
|
// [marketplacePkg]="pkg"
|
||||||
[localPkg]="data.local[pkg.id]"
|
// [localPkg]="data.local[pkg.id]"
|
||||||
[url]="host.url"
|
// [url]="host.url"
|
||||||
/>
|
// />
|
||||||
} @empty {
|
// } @empty {
|
||||||
<p>All services are up to date!</p>
|
// <p>All services are up to date!</p>
|
||||||
}
|
// }
|
||||||
} @else {
|
// } @else {
|
||||||
@for (i of [0, 1, 2]; track i) {
|
// @for (i of [0, 1, 2]; track i) {
|
||||||
<section tuiCell>
|
// <section tuiCell>
|
||||||
<tui-avatar class="tui-skeleton" />
|
// <tui-avatar class="tui-skeleton" />
|
||||||
<span class="tui-skeleton">Loading update item</span>
|
// <span class="tui-skeleton">Loading update item</span>
|
||||||
<span class="tui-skeleton" [style.margin-left]="'auto'">
|
// <span class="tui-skeleton" [style.margin-left]="'auto'">
|
||||||
Loading actions
|
// Loading actions
|
||||||
</span>
|
// </span>
|
||||||
</section>
|
// </section>
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
`,
|
// `,
|
||||||
host: { class: 'g-page' },
|
// host: { class: 'g-page' },
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
// changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
// standalone: true,
|
||||||
imports: [
|
// imports: [
|
||||||
CommonModule,
|
// CommonModule,
|
||||||
TuiCell,
|
// TuiCell,
|
||||||
TuiAvatar,
|
// TuiAvatar,
|
||||||
StoreIconComponentModule,
|
// StoreIconComponentModule,
|
||||||
FilterUpdatesPipe,
|
// FilterUpdatesPipe,
|
||||||
UpdatesItemComponent,
|
// UpdatesItemComponent,
|
||||||
],
|
// ],
|
||||||
})
|
// })
|
||||||
export default class UpdatesComponent {
|
// export default class UpdatesComponent {
|
||||||
private readonly service = inject(
|
// private readonly marketplaceService = inject(MarketplaceService)
|
||||||
AbstractMarketplaceService,
|
|
||||||
) as MarketplaceService
|
|
||||||
|
|
||||||
readonly mp = inject(ConfigService).marketplace
|
// readonly mp = inject(ConfigService).marketplace
|
||||||
readonly data$ = combineLatest({
|
// readonly data$ = combineLatest({
|
||||||
hosts: this.service.getKnownHosts$(true),
|
// hosts: this.marketplaceService.getKnownHosts$(true),
|
||||||
mp: this.service.getMarketplace$(),
|
// mp: this.marketplaceService.getMarketplace$(),
|
||||||
local: inject<PatchDB<DataModel>>(PatchDB)
|
// local: inject<PatchDB<DataModel>>(PatchDB)
|
||||||
.watch$('packageData')
|
// .watch$('packageData')
|
||||||
.pipe(
|
// .pipe(
|
||||||
map(pkgs =>
|
// map(pkgs =>
|
||||||
Object.entries(pkgs).reduce(
|
// Object.entries(pkgs).reduce(
|
||||||
(acc, [id, val]) => {
|
// (acc, [id, val]) => {
|
||||||
if (isInstalled(val) || isUpdating(val))
|
// if (isInstalled(val) || isUpdating(val))
|
||||||
return { ...acc, [id]: val }
|
// return { ...acc, [id]: val }
|
||||||
return acc
|
// return acc
|
||||||
},
|
// },
|
||||||
{} as Record<
|
// {} as Record<
|
||||||
string,
|
// string,
|
||||||
PackageDataEntry<InstalledState | UpdatingState>
|
// PackageDataEntry<InstalledState | UpdatingState>
|
||||||
>,
|
// >,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
errors: this.service.getRequestErrors$(),
|
// errors: this.marketplaceService.getRequestErrors$(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
|
||||||
import { Exver } from '@start9labs/shared'
|
import { Exver } from '@start9labs/shared'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
pairwise,
|
pairwise,
|
||||||
shareReplay,
|
|
||||||
startWith,
|
startWith,
|
||||||
switchMap,
|
switchMap,
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
@@ -32,9 +30,7 @@ export class BadgeService {
|
|||||||
this.patch.watch$('serverInfo', 'ntpSynced'),
|
this.patch.watch$('serverInfo', 'ntpSynced'),
|
||||||
inject(EOSService).updateAvailable$,
|
inject(EOSService).updateAvailable$,
|
||||||
]).pipe(map(([synced, update]) => Number(!synced) + Number(update)))
|
]).pipe(map(([synced, update]) => Number(!synced) + Number(update)))
|
||||||
private readonly marketplace = inject(
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
AbstractMarketplaceService,
|
|
||||||
) as MarketplaceService
|
|
||||||
|
|
||||||
private readonly local$ = inject(ConnectionService).pipe(
|
private readonly local$ = inject(ConnectionService).pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
@@ -58,35 +54,35 @@ export class BadgeService {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
private readonly updates$ = combineLatest([
|
// private readonly updates$ = combineLatest([
|
||||||
this.marketplace.getMarketplace$(true),
|
// this.marketplaceService.getMarketplace$(true),
|
||||||
this.local$,
|
// this.local$,
|
||||||
]).pipe(
|
// ]).pipe(
|
||||||
map(
|
// map(
|
||||||
([marketplace, local]) =>
|
// ([marketplace, local]) =>
|
||||||
Object.entries(marketplace).reduce(
|
// Object.entries(marketplace).reduce(
|
||||||
(list, [_, store]) =>
|
// (list, [_, store]) =>
|
||||||
store?.packages.reduce(
|
// store?.packages.reduce(
|
||||||
(result, { id, version }) =>
|
// (result, { id, version }) =>
|
||||||
local[id] &&
|
// local[id] &&
|
||||||
this.exver.compareExver(
|
// this.exver.compareExver(
|
||||||
version,
|
// version,
|
||||||
getManifest(local[id]).version,
|
// getManifest(local[id]).version,
|
||||||
) === 1
|
// ) === 1
|
||||||
? result.add(id)
|
// ? result.add(id)
|
||||||
: result,
|
// : result,
|
||||||
list,
|
// list,
|
||||||
) || list,
|
// ) || list,
|
||||||
new Set<string>(),
|
// new Set<string>(),
|
||||||
).size,
|
// ).size,
|
||||||
),
|
// ),
|
||||||
shareReplay(1),
|
// shareReplay(1),
|
||||||
)
|
// )
|
||||||
|
|
||||||
getCount(id: string): Observable<number> {
|
getCount(id: string): Observable<number> {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case '/portal/system/updates':
|
// case '/portal/system/updates':
|
||||||
return this.updates$
|
// return this.updates$
|
||||||
case '/portal/system/settings':
|
case '/portal/system/settings':
|
||||||
return this.settings$
|
return this.settings$
|
||||||
case '/portal/system/notifications':
|
case '/portal/system/notifications':
|
||||||
|
|||||||
@@ -1,42 +1,65 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
|
||||||
Marketplace,
|
|
||||||
StoreIdentity,
|
StoreIdentity,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
GetPackageRes,
|
GetPackageRes,
|
||||||
StoreData,
|
StoreDataWithUrl,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
catchError,
|
catchError,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
distinctUntilKeyChanged,
|
|
||||||
filter,
|
filter,
|
||||||
from,
|
from,
|
||||||
map,
|
map,
|
||||||
mergeMap,
|
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
scan,
|
|
||||||
pairwise,
|
|
||||||
shareReplay,
|
shareReplay,
|
||||||
startWith,
|
|
||||||
switchMap,
|
switchMap,
|
||||||
take,
|
distinctUntilChanged,
|
||||||
|
ReplaySubject,
|
||||||
tap,
|
tap,
|
||||||
} 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, UIStore } from 'src/app/services/patch-db/data-model'
|
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { Exver, sameUrl } from '@start9labs/shared'
|
import { Exver } from '@start9labs/shared'
|
||||||
import { ClientStorageService } from './client-storage.service'
|
import { ClientStorageService } from './client-storage.service'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({
|
||||||
export class MarketplaceService implements AbstractMarketplaceService {
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class MarketplaceService {
|
||||||
|
private readonly registryUrlSubject$ = new ReplaySubject<string>(1)
|
||||||
|
private readonly registryUrl$ = this.registryUrlSubject$.pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private readonly registry$: Observable<StoreDataWithUrl> =
|
||||||
|
this.registryUrl$.pipe(
|
||||||
|
switchMap(url => this.fetchRegistry$(url)),
|
||||||
|
filter(Boolean),
|
||||||
|
// @TODO is updateStoreName needed?
|
||||||
|
map(registry => {
|
||||||
|
registry.info.categories = {
|
||||||
|
all: {
|
||||||
|
name: 'All',
|
||||||
|
description: {
|
||||||
|
short: 'All registry packages',
|
||||||
|
long: 'An unfiltered list of all packages available on this registry.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...registry.info.categories,
|
||||||
|
}
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}),
|
||||||
|
shareReplay(1),
|
||||||
|
)
|
||||||
|
|
||||||
private readonly knownHosts$: Observable<StoreIdentity[]> = this.patch
|
private readonly knownHosts$: Observable<StoreIdentity[]> = this.patch
|
||||||
.watch$('ui', 'marketplace', 'knownHosts')
|
.watch$('ui', 'marketplace', 'knownHosts')
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -69,71 +92,6 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
private readonly selectedHost$: Observable<StoreIdentity> = this.patch
|
|
||||||
.watch$('ui', 'marketplace')
|
|
||||||
.pipe(
|
|
||||||
distinctUntilKeyChanged('selectedUrl'),
|
|
||||||
map(({ selectedUrl: url, knownHosts: hosts }) =>
|
|
||||||
toStoreIdentity(url, hosts[url]),
|
|
||||||
),
|
|
||||||
shareReplay(1),
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly marketplace$ = this.knownHosts$.pipe(
|
|
||||||
startWith<StoreIdentity[]>([]),
|
|
||||||
pairwise(),
|
|
||||||
mergeMap(([prev, curr]) =>
|
|
||||||
curr.filter(c => !prev.find(p => sameUrl(c.url, p.url))),
|
|
||||||
),
|
|
||||||
mergeMap(({ url, name }) =>
|
|
||||||
this.fetchStore$(url).pipe(
|
|
||||||
tap(data => {
|
|
||||||
if (data?.info.name) this.updateStoreName(url, name, data.info.name)
|
|
||||||
}),
|
|
||||||
map<StoreData | null, [string, StoreData | null]>(data => [url, data]),
|
|
||||||
startWith<[string, StoreData | null]>([url, null]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
scan<[string, StoreData | null], Record<string, StoreData | null>>(
|
|
||||||
(requests, [url, store]) => {
|
|
||||||
requests[url] = store
|
|
||||||
|
|
||||||
return requests
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
shareReplay(1),
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly filteredMarketplace$ = combineLatest([
|
|
||||||
this.clientStorageService.showDevTools$,
|
|
||||||
this.marketplace$,
|
|
||||||
]).pipe(
|
|
||||||
map(([devMode, marketplace]) =>
|
|
||||||
Object.entries(marketplace).reduce(
|
|
||||||
(filtered, [url, store]) =>
|
|
||||||
!devMode && (url.includes('alpha') || url.includes('beta'))
|
|
||||||
? filtered
|
|
||||||
: {
|
|
||||||
[url]: store,
|
|
||||||
...filtered,
|
|
||||||
},
|
|
||||||
{} as Marketplace,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly selectedStore$: Observable<StoreData> =
|
|
||||||
this.selectedHost$.pipe(
|
|
||||||
switchMap(({ url }) =>
|
|
||||||
this.marketplace$.pipe(
|
|
||||||
map(m => m[url]),
|
|
||||||
filter(Boolean),
|
|
||||||
take(1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -149,33 +107,17 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
return filtered ? this.filteredKnownHosts$ : this.knownHosts$
|
return filtered ? this.filteredKnownHosts$ : this.knownHosts$
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedHost$(): Observable<StoreIdentity> {
|
getRegistryUrl$() {
|
||||||
return this.selectedHost$
|
return this.registryUrl$
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarketplace$(filtered = false): Observable<Marketplace> {
|
setRegistryUrl(url: string | null) {
|
||||||
// option to filter out hosts containing 'alpha' or 'beta' substrings in registryURL
|
const registryUrl = url || this.config.marketplace.start9
|
||||||
return filtered ? this.filteredMarketplace$ : this.marketplace$
|
this.registryUrlSubject$.next(registryUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedStore$(): Observable<StoreData> {
|
getRegistry$(): Observable<StoreDataWithUrl> {
|
||||||
return this.selectedStore$
|
return this.registry$
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedStoreWithCategories$() {
|
|
||||||
return this.selectedHost$.pipe(
|
|
||||||
switchMap(({ url }) =>
|
|
||||||
this.marketplace$.pipe(
|
|
||||||
map(m => m[url]),
|
|
||||||
filter(Boolean),
|
|
||||||
map(({ info, packages }) => ({
|
|
||||||
url,
|
|
||||||
info,
|
|
||||||
packages,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPackage$(
|
getPackage$(
|
||||||
@@ -184,47 +126,20 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
flavor: string | null,
|
flavor: string | null,
|
||||||
registryUrl?: string,
|
registryUrl?: string,
|
||||||
): Observable<MarketplacePkg> {
|
): Observable<MarketplacePkg> {
|
||||||
return this.selectedHost$.pipe(
|
return this.registry$.pipe(
|
||||||
switchMap(selected =>
|
switchMap(registry => {
|
||||||
this.marketplace$.pipe(
|
const url = registryUrl || registry.url
|
||||||
switchMap(m => {
|
const pkg = registry.packages.find(
|
||||||
const url = registryUrl || selected.url
|
p =>
|
||||||
const pkg = m[url]?.packages.find(
|
p.id === id &&
|
||||||
p =>
|
p.flavor === flavor &&
|
||||||
p.id === id &&
|
(!version || this.exver.compareExver(p.version, version) === 0),
|
||||||
p.flavor === flavor &&
|
)
|
||||||
(!version || this.exver.compareExver(p.version, version) === 0),
|
return pkg ? of(pkg) : this.fetchPackage$(url, id, version, flavor)
|
||||||
)
|
}),
|
||||||
|
|
||||||
return pkg ? of(pkg) : this.fetchPackage$(url, id, version, flavor)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI only
|
|
||||||
readonly updateErrors: Record<string, string> = {}
|
|
||||||
readonly updateQueue: Record<string, boolean> = {}
|
|
||||||
|
|
||||||
getRequestErrors$(): Observable<string[]> {
|
|
||||||
return this.requestErrors$
|
|
||||||
}
|
|
||||||
|
|
||||||
async installPackage(
|
|
||||||
id: string,
|
|
||||||
version: string,
|
|
||||||
url: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const params: RR.InstallPackageReq = {
|
|
||||||
id,
|
|
||||||
version,
|
|
||||||
registry: url,
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.api.installPackage(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchInfo$(url: string): Observable<T.RegistryInfo> {
|
fetchInfo$(url: string): Observable<T.RegistryInfo> {
|
||||||
return from(this.api.getRegistryInfo(url)).pipe(
|
return from(this.api.getRegistryInfo(url)).pipe(
|
||||||
map(info => ({
|
map(info => ({
|
||||||
@@ -232,7 +147,10 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
categories: {
|
categories: {
|
||||||
all: {
|
all: {
|
||||||
name: 'All',
|
name: 'All',
|
||||||
description: { short: 'All services', long: 'All services' },
|
description: {
|
||||||
|
short: 'All services',
|
||||||
|
long: 'An unfiltered list of all services available on this registry.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...info.categories,
|
...info.categories,
|
||||||
},
|
},
|
||||||
@@ -240,16 +158,17 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchStatic$(
|
getStatic$(
|
||||||
pkg: MarketplacePkg,
|
pkg: MarketplacePkg,
|
||||||
type: 'LICENSE.md' | 'instructions.md',
|
type: 'LICENSE.md' | 'instructions.md',
|
||||||
): Observable<string> {
|
): Observable<string> {
|
||||||
return from(this.api.getStaticProxy(pkg, type))
|
return from(this.api.getStaticProxy(pkg, type))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchStore$(url: string): Observable<StoreData | null> {
|
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {
|
||||||
|
console.log('FETCHING REGISTRY: ', url)
|
||||||
return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe(
|
return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe(
|
||||||
map(([info, packages]) => ({ info, packages })),
|
map(([info, packages]) => ({ info, packages, url })),
|
||||||
catchError(e => {
|
catchError(e => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
this.requestErrors$.next(this.requestErrors$.value.concat(url))
|
this.requestErrors$.next(this.requestErrors$.value.concat(url))
|
||||||
@@ -275,7 +194,22 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToMarketplacePkg(
|
private fetchPackage$(
|
||||||
|
url: string,
|
||||||
|
id: string,
|
||||||
|
version: string | null,
|
||||||
|
flavor: string | null,
|
||||||
|
): Observable<MarketplacePkg> {
|
||||||
|
return from(
|
||||||
|
this.api.getRegistryPackage(url, id, version ? `=${version}` : null),
|
||||||
|
).pipe(
|
||||||
|
map(pkgInfo =>
|
||||||
|
this.convertToMarketplacePkg(id, version, flavor, pkgInfo),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertToMarketplacePkg(
|
||||||
id: string,
|
id: string,
|
||||||
version: string | null | undefined,
|
version: string | null | undefined,
|
||||||
flavor: string | null,
|
flavor: string | null,
|
||||||
@@ -297,26 +231,6 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchPackage$(
|
|
||||||
url: string,
|
|
||||||
id: string,
|
|
||||||
version: string | null,
|
|
||||||
flavor: string | null,
|
|
||||||
): Observable<MarketplacePkg> {
|
|
||||||
return from(
|
|
||||||
this.api.getRegistryPackage(url, id, version ? `=${version}` : null),
|
|
||||||
).pipe(
|
|
||||||
map(pkgInfo =>
|
|
||||||
this.convertToMarketplacePkg(
|
|
||||||
id,
|
|
||||||
version === '*' ? null : version,
|
|
||||||
flavor,
|
|
||||||
pkgInfo,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateStoreName(
|
private async updateStoreName(
|
||||||
url: string,
|
url: string,
|
||||||
oldName: string | undefined,
|
oldName: string | undefined,
|
||||||
@@ -329,6 +243,28 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UI only
|
||||||
|
readonly updateErrors: Record<string, string> = {}
|
||||||
|
readonly updateQueue: Record<string, boolean> = {}
|
||||||
|
|
||||||
|
getRequestErrors$(): Observable<string[]> {
|
||||||
|
return this.requestErrors$
|
||||||
|
}
|
||||||
|
|
||||||
|
async installPackage(
|
||||||
|
id: string,
|
||||||
|
version: string,
|
||||||
|
url: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const params: RR.InstallPackageReq = {
|
||||||
|
id,
|
||||||
|
version,
|
||||||
|
registry: url,
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.api.installPackage(params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { Inject, Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
|
||||||
import { TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
import { filter, share, switchMap, take, Observable, map } from 'rxjs'
|
import { filter, share, switchMap, Observable, map } from 'rxjs'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { EOSService } from 'src/app/services/eos.service'
|
import { EOSService } from 'src/app/services/eos.service'
|
||||||
import { WelcomeComponent } from 'src/app/components/welcome.component'
|
import { WelcomeComponent } from 'src/app/components/welcome.component'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
import { LocalStorageBootstrap } from './patch-db/local-storage-bootstrap'
|
import { LocalStorageBootstrap } from './patch-db/local-storage-bootstrap'
|
||||||
@@ -40,8 +38,6 @@ export class PatchDataService extends Observable<void> {
|
|||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
private readonly dialogs: TuiDialogService,
|
private readonly dialogs: TuiDialogService,
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
@Inject(AbstractMarketplaceService)
|
|
||||||
private readonly marketplaceService: MarketplaceService,
|
|
||||||
private readonly connection$: ConnectionService,
|
private readonly connection$: ConnectionService,
|
||||||
private readonly bootstrapper: LocalStorageBootstrap,
|
private readonly bootstrapper: LocalStorageBootstrap,
|
||||||
) {
|
) {
|
||||||
@@ -50,7 +46,7 @@ export class PatchDataService extends Observable<void> {
|
|||||||
|
|
||||||
private checkForUpdates(): void {
|
private checkForUpdates(): void {
|
||||||
this.eosService.loadEos()
|
this.eosService.loadEos()
|
||||||
this.marketplaceService.getMarketplace$().pipe(take(1)).subscribe()
|
// this.marketplaceService.getMarketplace$().pipe(take(1)).subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
private showEosWelcome(ackVersion: string) {
|
private showEosWelcome(ackVersion: string) {
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ export const SYSTEM_UTILITIES: Record<string, { icon: string; title: string }> =
|
|||||||
icon: '@tui.shopping-cart',
|
icon: '@tui.shopping-cart',
|
||||||
title: 'Marketplace',
|
title: 'Marketplace',
|
||||||
},
|
},
|
||||||
'/portal/system/updates': {
|
// '/portal/system/updates': {
|
||||||
icon: '@tui.globe',
|
// icon: '@tui.globe',
|
||||||
title: 'Updates',
|
// title: 'Updates',
|
||||||
},
|
// },
|
||||||
'/portal/system/sideload': {
|
'/portal/system/sideload': {
|
||||||
icon: '@tui.upload',
|
icon: '@tui.upload',
|
||||||
title: 'Sideload',
|
title: 'Sideload',
|
||||||
|
|||||||
Reference in New Issue
Block a user