Merge pull request #2719 from Start9Labs/flavor

fix: implement flavor across the app
This commit is contained in:
Matt Hill
2024-08-20 22:09:07 -06:00
committed by GitHub
20 changed files with 283 additions and 236 deletions

View File

@@ -9,7 +9,7 @@ header {
@include scrollbar-hidden(); @include scrollbar-hidden();
// TODO: Theme // TODO: Theme
background: #2B2B2F; background: #2b2b2f;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
position: fixed; position: fixed;
@@ -33,7 +33,7 @@ header {
1; 1;
border-width: 0; border-width: 0;
border-right: 0.125rem solid; border-right: 0.125rem solid;
overflow: visible; overflow: auto;
} }
@media screen and (min-width: 1536px) { @media screen and (min-width: 1536px) {
@@ -133,11 +133,11 @@ header {
&-sidebar { &-sidebar {
background-color: rgb(var(--tw-color-zinc-700) / 0.9); background-color: rgb(var(--tw-color-zinc-700) / 0.9);
height: calc(100vh - var(--portal-header-height));
width: 70vw; width: 70vw;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
overflow: auto;
&-top { &-top {
display: flex; display: flex;

View File

@@ -1,34 +1,30 @@
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
ChangeDetectionStrategy, import { MarketplacePkg } from '@start9labs/marketplace'
Component,
inject,
Input,
} from '@angular/core'
import { Exver, MarkdownPipeModule } from '@start9labs/shared' import { Exver, MarkdownPipeModule } from '@start9labs/shared'
import { TuiButton, TuiLoader } from '@taiga-ui/core' import { TuiButton, TuiDialogContext, TuiLoader } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout' import { TuiAccordion } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { map } from 'rxjs' import { map } from 'rxjs'
import { AbstractMarketplaceService } from '../services/marketplace.service' import { AbstractMarketplaceService } from '../services/marketplace.service'
import { MarketplacePkg } from '../types'
@Component({ @Component({
standalone: true, standalone: true,
template: ` template: `
@if (notes$ | async; as notes) { @if (notes$ | async; as notes) {
@for (note of notes | keyvalue: asIsOrder; track $index) { <tui-accordion>
<button tuiButton (click)="setSelected(note.key)"> @for (note of notes | keyvalue: asIsOrder; track $index) {
{{ note.key }} <tui-accordion-item>
</button> {{ note.key }}
<div <ng-template tuiAccordionItemContent>
tuiCardLarge <div [innerHTML]="note.value | markdown"></div>
#element </ng-template>
[id]="note.key" </tui-accordion-item>
[style.max-height.px]="getDocSize(note.key, element)" }
[innerHTML]="note.value | markdown" </tui-accordion>
></div>
}
} @else { } @else {
<tui-loader textContent="Loading Release Notes" /> <tui-loader textContent="Loading Release Notes" />
} }
@@ -38,15 +34,14 @@ import { MarketplacePkg } from '../types'
CommonModule, CommonModule,
TuiButton, TuiButton,
TuiLoader, TuiLoader,
TuiCardLarge, TuiAccordion,
MarkdownPipeModule, MarkdownPipeModule,
], ],
}) })
export class ReleaseNotesComponent { export class ReleaseNotesComponent {
@Input() pkg!: MarketplacePkg
private selected: string | null = null
private readonly exver = inject(Exver) private readonly exver = inject(Exver)
private readonly pkg =
inject<TuiDialogContext<void, MarketplacePkg>>(POLYMORPHEUS_CONTEXT).data
readonly notes$ = inject(AbstractMarketplaceService) readonly notes$ = inject(AbstractMarketplaceService)
.getSelectedStore$() .getSelectedStore$()
@@ -70,18 +65,6 @@ export class ReleaseNotesComponent {
}), }),
) )
isSelected(key: string): boolean {
return this.selected === key
}
setSelected(selected: string) {
this.selected = this.isSelected(selected) ? null : selected
}
getDocSize(key: string, { scrollHeight }: HTMLElement) {
return this.isSelected(key) ? scrollHeight : 0
}
asIsOrder(a: any, b: any) { asIsOrder(a: any, b: any) {
return 0 return 0
} }

View File

@@ -1,24 +1,15 @@
<button @for (cat of categories || fallback | keyvalue: asIsOrder; track $index) {
*ngFor="let cat of categories || fallback | keyvalue" <button
(click)="switchCategory(cat.key)" (click)="switchCategory(cat.key)"
[class.category_selected]="cat.key === category" [class.category_selected]="cat.key === category"
>
<div
class="category-wrapper"
[class.tui-skeleton]="!categories"
[class.tui-skeleton_rounded]="!categories"
> >
<tui-icon tuiAppearance="icon" [icon]="determineIcon(cat.key)" /> <div class="category-wrapper" [tuiSkeleton]="!categories">
</div> <tui-icon tuiAppearance="icon" [icon]="determineIcon(cat.key)" />
<span </div>
class="category-title" <span class="category-title" [tuiSkeleton]="categories ? false : 3">
[class.tui-skeleton]="!categories" {{
[class.tui-skeleton_rounded]="!categories" cat.key === 'ai' ? (cat.key | uppercase) : (cat.value.name | titlecase)
> }}
{{ </span>
cat.key === 'ai' </button>
? (cat.key | uppercase) }
: (cat.value.name | titlecase) || 'Loading category...'
}}
</span>
</button>

View File

@@ -7,6 +7,21 @@ import {
} from '@angular/core' } from '@angular/core'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
const ICONS: Record<string, string> = {
all: '@tui.layout-grid',
bitcoin: '@tui.bitcoin',
messaging: '@tui.message-circle',
communications: '@tui.message-circle',
data: '@tui.file-text',
'developer tools': '@tui.table-split',
featured: '@tui.star',
lightning: '@tui.zap',
media: '@tui.circle-play',
networking: '@tui.globe',
social: '@tui.users',
ai: '@tui.cpu',
}
@Component({ @Component({
selector: 'marketplace-categories', selector: 'marketplace-categories',
templateUrl: 'categories.component.html', templateUrl: 'categories.component.html',
@@ -37,32 +52,10 @@ export class CategoriesComponent {
} }
determineIcon(category: string): string { determineIcon(category: string): string {
switch (category.toLowerCase()) { return ICONS[category.toLowerCase()] || '@tui.box'
case 'all': }
return '@tui.layout-grid'
case 'bitcoin': asIsOrder(a: any, b: any) {
return '@tui.bitcoin' return 0
case 'messaging':
case 'communications':
return '@tui.message-circle'
case 'data':
return '@tui.file-text'
case 'developer tools':
return '@tui.table-split'
case 'featured':
return '@tui.star'
case 'lightning':
return '@tui.zap'
case 'media':
return '@tui.circle-play'
case 'networking':
return '@tui.globe'
case 'social':
return '@tui.users'
case 'ai':
return '@tui.cpu'
default:
return '@tui.box'
}
} }
} }

View File

@@ -1,12 +1,13 @@
import { TuiIcon, TuiAppearance } from '@taiga-ui/core' import { TuiIcon, TuiAppearance } from '@taiga-ui/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { TuiSkeleton } from '@taiga-ui/kit'
import { CategoriesComponent } from './categories.component' import { CategoriesComponent } from './categories.component'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
@NgModule({ @NgModule({
imports: [RouterModule, CommonModule, TuiAppearance, TuiIcon], imports: [RouterModule, CommonModule, TuiAppearance, TuiIcon, TuiSkeleton],
declarations: [CategoriesComponent], declarations: [CategoriesComponent],
exports: [CategoriesComponent], exports: [CategoriesComponent],
}) })

View File

@@ -5,9 +5,7 @@
<button tuiButton iconEnd="@tui.chevron-right" (click)="onPast()"> <button tuiButton iconEnd="@tui.chevron-right" (click)="onPast()">
Past Release Notes Past Release Notes
</button> </button>
</div> <h2 class="additional-detail-title" [style.margin-top.rem]="2">About</h2>
<div class="box-container">
<h2 class="additional-detail-title">About</h2>
<p>{{ pkg.description.long }}</p> <p>{{ pkg.description.long }}</p>
<a <a
*ngIf="pkg.marketingSite as url" *ngIf="pkg.marketingSite as url"

View File

@@ -3,17 +3,10 @@
border-radius: 0.75rem; border-radius: 0.75rem;
padding: 1.75rem; padding: 1.75rem;
@media (min-width: 1024px) {
grid-column: span 5 / span 5;
}
@media (min-width: 1280px) {
grid-column: span 4 / span 4;
}
p { p {
font-size: 1rem; font-size: 1rem;
line-height: 1.5rem; line-height: 1.5rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
pointer-events: none; pointer-events: none;
} }
} }

View File

@@ -22,7 +22,7 @@ export class AboutComponent {
async onPast() { async onPast() {
this.dialogs this.dialogs
.open(RELEASE_NOTES, { label: 'Past Release Notes' }) .open(RELEASE_NOTES, { label: 'Past Release Notes', data: this.pkg })
.subscribe() .subscribe()
} }
} }

View File

@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { SharedPipesModule } from '@start9labs/shared' import { SharedPipesModule } from '@start9labs/shared'
import { TuiTitle } from '@taiga-ui/core' import { TuiTitle } from '@taiga-ui/core'
import { TuiAvatar } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout' import { TuiCell } from '@taiga-ui/layout'
import { MarketplacePkg } from '../../../types' import { MarketplacePkg } from '../../../types'
@@ -9,23 +10,39 @@ import { MarketplacePkg } from '../../../types'
standalone: true, standalone: true,
selector: 'marketplace-flavors', selector: 'marketplace-flavors',
template: ` template: `
<h2>Alternative Implementations</h2> <div class="background-border box-shadow-lg shadow-color-light">
@for (pkg of pkgs; track $index) { <div class="box-container">
<a <h2 class="additional-detail-title">Alternative Implementations</h2>
tuiCell @for (pkg of pkgs; track $index) {
[routerLink]="['/marketplace', pkg.id]" <a
[queryParams]="{ flavor: pkg.flavor }" tuiCell
> [routerLink]="[]"
<img alt="" style="border-radius: 100%" [src]="pkg.icon | trustUrl" /> [queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
<span tuiTitle> >
{{ pkg.title }} <tui-avatar [src]="pkg.icon | trustUrl" />
<span tuiSubtitle>{{ pkg.version }}</span> <span tuiTitle>
</span> {{ pkg.title }}
</a> <span tuiSubtitle>{{ pkg.version }}</span>
</span>
</a>
}
</div>
</div>
`,
styles: `
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.75rem;
}
[tuiCell] {
border-radius: 0.5rem;
margin: 0 -1rem;
} }
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterLink, TuiCell, TuiTitle, SharedPipesModule], imports: [RouterLink, TuiCell, TuiTitle, SharedPipesModule, TuiAvatar],
}) })
export class FlavorsComponent { export class FlavorsComponent {
@Input() @Input()

View File

@@ -30,7 +30,7 @@
size="s" size="s"
appearance="tertiary-solid" appearance="tertiary-solid"
iconEnd="@tui.download" iconEnd="@tui.download"
href="/eos/local.crt" href="/static/local-root-ca.crt"
> >
Download Download
</a> </a>

View File

@@ -87,22 +87,18 @@ import { ABOUT } from './about.component'
`, `,
styles: [ styles: [
` `
tui-icon { :host {
font-size: 1rem; margin: 0 -0.5rem;
} }
tui-hosted-dropdown { [tuiIconButton] {
margin: 0 -0.5rem; height: calc(var(--tui-height-m) + 0.25rem);
width: calc(var(--tui-height-m) + 0.625rem);
[tuiIconButton] {
height: calc(var(--tui-height-m) + 0.25rem);
width: calc(var(--tui-height-m) + 0.625rem);
}
} }
.item { .item {
justify-content: flex-start; justify-content: flex-start;
gap: 0.75rem; gap: 0.5rem;
} }
`, `,
], ],

View File

@@ -1,8 +1,7 @@
import { TuiButton, TuiNotification } from '@taiga-ui/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { Component, inject, OnInit, signal } from '@angular/core' import { Component, inject, OnInit, signal } from '@angular/core'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService, LoadingService } from '@start9labs/shared'
import { CT } from '@start9labs/start-sdk' import { TuiButton, TuiNotification } from '@taiga-ui/core'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { FormComponent } from 'src/app/routes/portal/components/form.component' import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { import {

View File

@@ -88,10 +88,10 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
<button <button
tuiButton tuiButton
type="button" type="button"
appearance="primary" [appearance]="localFlavor ? 'warning' : 'primary'"
(click)="tryInstall()" (click)="tryInstall()"
> >
Install {{ localFlavor ? 'Switch' : 'Install' }}
</button> </button>
} }
`, `,
@@ -119,6 +119,9 @@ export class MarketplaceControlsComponent {
@Input() @Input()
localPkg!: PackageDataEntry | null localPkg!: PackageDataEntry | null
@Input()
localFlavor!: boolean
readonly showDevTools$ = inject(ClientStorageService).showDevTools$ readonly showDevTools$ = inject(ClientStorageService).showDevTools$
async tryInstall() { async tryInstall() {

View File

@@ -1,32 +1,43 @@
import { TuiDropdownService, TuiButton } from '@taiga-ui/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
computed,
inject, inject,
Input, input,
} from '@angular/core' } from '@angular/core'
import { toObservable, toSignal } from '@angular/core/rxjs-interop'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { ItemModule, MarketplacePkg } from '@start9labs/marketplace' import { ItemModule, MarketplacePkg } from '@start9labs/marketplace'
import { Exver } from '@start9labs/shared'
import { TuiSidebar } from '@taiga-ui/addon-mobile' import { TuiSidebar } from '@taiga-ui/addon-mobile'
import { TuiAutoFocus, TuiClickOutside } from '@taiga-ui/cdk' import { TuiAutoFocus, TuiClickOutside } from '@taiga-ui/cdk'
import { debounceTime, map } from 'rxjs' import { TuiButton, TuiDropdownService } from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client'
import {
debounceTime,
filter,
map,
Observable,
shareReplay,
switchMap,
} from 'rxjs'
import {
DataModel,
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { getManifest } from 'src/app/utils/get-package-data'
import { MarketplacePreviewComponent } from '../modals/preview.component' import { MarketplacePreviewComponent } from '../modals/preview.component'
import { ToLocalPipe } from '../pipes/to-local.pipe'
import { MarketplaceSidebarService } from '../services/sidebar.service' import { MarketplaceSidebarService } from '../services/sidebar.service'
import { MarketplaceControlsComponent } from './controls.component' import { MarketplaceControlsComponent } from './controls.component'
@Component({ @Component({
selector: 'marketplace-tile', selector: 'marketplace-tile',
template: ` template: `
<marketplace-item [pkg]="pkg" (click)="toggle(true)"> <marketplace-item [pkg]="pkg()" (click)="toggle(true)">
<marketplace-preview <marketplace-preview
*tuiSidebar=" *tuiSidebar="!!open(); direction: 'right'; autoWidth: true"
(id$ | async) === pkg.id; [pkgId]="pkg().id"
direction: 'right';
autoWidth: true
"
[pkgId]="pkg.id"
class="preview-wrapper" class="preview-wrapper"
(tuiClickOutside)="toggle(false)" (tuiClickOutside)="toggle(false)"
> >
@@ -44,8 +55,9 @@ import { MarketplaceControlsComponent } from './controls.component'
<marketplace-controls <marketplace-controls
slot="controls" slot="controls"
class="controls-wrapper" class="controls-wrapper"
[pkg]="pkg" [pkg]="pkg()"
[localPkg]="pkg.id | toLocal | async" [localPkg]="local$ | async"
[localFlavor]="!!(flavor$ | async)"
/> />
</marketplace-preview> </marketplace-preview>
</marketplace-item> </marketplace-item>
@@ -104,7 +116,6 @@ import { MarketplaceControlsComponent } from './controls.component'
imports: [ imports: [
CommonModule, CommonModule,
ItemModule, ItemModule,
ToLocalPipe,
TuiAutoFocus, TuiAutoFocus,
TuiClickOutside, TuiClickOutside,
TuiSidebar, TuiSidebar,
@@ -114,18 +125,44 @@ import { MarketplaceControlsComponent } from './controls.component'
], ],
}) })
export class MarketplaceTileComponent { export class MarketplaceTileComponent {
private readonly exver = inject(Exver)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly router = inject(Router) private readonly router = inject(Router)
readonly id$ = inject(ActivatedRoute).queryParamMap.pipe( private readonly params = toSignal(
map(map => map.get('id') || ''), inject(ActivatedRoute).queryParamMap.pipe(debounceTime(100)),
debounceTime(100),
) )
@Input({ required: true }) readonly pkg = input.required<MarketplacePkg>()
pkg!: MarketplacePkg readonly open = computed(
() =>
this.params()?.get('id') === this.pkg()?.id &&
this.params()?.get('flavor') === this.pkg()?.flavor,
)
readonly local$: Observable<PackageDataEntry | null> = toObservable(
this.pkg,
).pipe(
switchMap(({ id, flavor }) =>
this.patch.watch$('packageData', id).pipe(
filter(Boolean),
map(pkg =>
this.exver.getFlavor(getManifest(pkg).version) === flavor
? pkg
: null,
),
),
),
shareReplay({ bufferSize: 1, refCount: true }),
)
readonly flavor$ = this.local$.pipe(map(pkg => !pkg))
toggle(open: boolean) { toggle(open: boolean) {
this.router.navigate([], { this.router.navigate([], {
queryParams: { id: open ? this.pkg.id : null }, queryParams: {
id: open ? this.pkg().id : null,
flavor: open ? this.pkg().flavor : null,
},
}) })
} }
} }

View File

@@ -52,7 +52,6 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
styles: [ styles: [
` `
:host { :host {
height: calc(100vh - 3.5rem);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
@@ -131,7 +130,7 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
} }
:host-context(tui-root._mobile) { :host-context(tui-root._mobile) {
height: calc(100vh - 3.5rem - var(--tui-height-l)); padding: 0;
} }
`, `,
], ],

View File

@@ -12,6 +12,7 @@ import {
AboutModule, AboutModule,
AbstractMarketplaceService, AbstractMarketplaceService,
AdditionalModule, AdditionalModule,
FlavorsComponent,
MarketplaceAdditionalItemComponent, MarketplaceAdditionalItemComponent,
MarketplaceDependenciesComponent, MarketplaceDependenciesComponent,
MarketplacePackageHeroComponent, MarketplacePackageHeroComponent,
@@ -26,7 +27,16 @@ import {
TuiLoader, TuiLoader,
} from '@taiga-ui/core' } from '@taiga-ui/core'
import { TuiRadioList, TuiStringifyContentPipe } from '@taiga-ui/kit' import { TuiRadioList, TuiStringifyContentPipe } from '@taiga-ui/kit'
import { BehaviorSubject, filter, switchMap, tap } from 'rxjs' import {
BehaviorSubject,
combineLatest,
filter,
firstValueFrom,
map,
startWith,
switchMap,
tap,
} from 'rxjs'
@Component({ @Component({
selector: 'marketplace-preview', selector: 'marketplace-preview',
@@ -34,34 +44,36 @@ import { BehaviorSubject, filter, switchMap, tap } from 'rxjs'
<div class="outer-container"> <div class="outer-container">
<ng-content select="[slot=close]" /> <ng-content select="[slot=close]" />
@if (pkg$ | async; as pkg) { @if (pkg$ | async; as pkg) {
@if (loading$ | async) { <marketplace-package-hero [pkg]="pkg">
<tui-loader class="loading" textContent="Loading" /> <ng-content select="[slot=controls]" />
} @else { </marketplace-package-hero>
<marketplace-package-hero [pkg]="pkg"> <div class="inner-container">
<ng-content select="[slot=controls]" /> @if (flavors$ | async; as flavors) {
</marketplace-package-hero> <marketplace-flavors [pkgs]="flavors" />
<div class="inner-container"> }
<marketplace-about [pkg]="pkg" /> <marketplace-about [pkg]="pkg" />
@if (!(pkg.dependencyMetadata | empty)) { @if (!(pkg.dependencyMetadata | empty)) {
<marketplace-dependencies <marketplace-dependencies [pkg]="pkg" (open)="open($event)" />
[pkg]="pkg" }
(open)="open($event)" <marketplace-additional [pkg]="pkg">
></marketplace-dependencies> @if (versions$ | async; as versions) {
}
<marketplace-additional [pkg]="pkg">
<marketplace-additional-item <marketplace-additional-item
(click)="presentAlertVersions(pkg, version)" (click)="versions.length ? selectVersion(pkg, version) : 0"
data="Click to view all versions" [data]="
versions.length
? 'Click to view all versions'
: 'No other versions'
"
label="All versions" label="All versions"
icon="@tui.chevron-right" icon="@tui.chevron-right"
class="versions" class="versions"
></marketplace-additional-item> />
<ng-template <ng-template
#version #version
let-data="data" let-data="data"
let-completeWith="completeWith" let-completeWith="completeWith"
> >
<tui-radio-list [items]="data.items" [(ngModel)]="data.value" /> <tui-radio-list [items]="versions" [(ngModel)]="data.value" />
<footer class="buttons"> <footer class="buttons">
<button <button
tuiButton tuiButton
@@ -73,15 +85,17 @@ import { BehaviorSubject, filter, switchMap, tap } from 'rxjs'
<button <button
tuiButton tuiButton
appearance="secondary" appearance="secondary"
(click)="loading$.next(true); completeWith(data.value)" (click)="completeWith(data.value)"
> >
Ok Ok
</button> </button>
</footer> </footer>
</ng-template> </ng-template>
</marketplace-additional> }
</div> </marketplace-additional>
} </div>
} @else {
<tui-loader class="loading" textContent="Loading" />
} }
</div> </div>
`, `,
@@ -164,54 +178,72 @@ import { BehaviorSubject, filter, switchMap, tap } from 'rxjs'
TuiRadioList, TuiRadioList,
TuiLoader, TuiLoader,
TuiIcon, TuiIcon,
FlavorsComponent,
], ],
}) })
export class MarketplacePreviewComponent { export class MarketplacePreviewComponent {
@Input({ required: true }) @Input({ required: true })
pkgId!: string pkgId!: string
readonly loading$ = new BehaviorSubject(true) private readonly dialogs = inject(TuiDialogService)
private readonly exver = inject(Exver)
private readonly router = inject(Router) private readonly router = inject(Router)
private readonly marketplaceService = inject(AbstractMarketplaceService) private readonly marketplaceService = inject(AbstractMarketplaceService)
readonly url = this.router.routerState.snapshot.root.queryParamMap.get('url') private readonly version$ = new BehaviorSubject<string>('*')
private readonly flavor$ = this.router.routerState.root.queryParamMap.pipe(
readonly loadVersion$ = new BehaviorSubject<string>('*') map(paramMap => paramMap.get('flavor')),
readonly pkg$ = this.loadVersion$.pipe(
switchMap(version =>
this.marketplaceService.getPackage$(this.pkgId, version, this.url),
),
tap(data => {
this.loading$.next(false)
return data
}),
) )
constructor( readonly pkg$ = combineLatest([this.version$, this.flavor$]).pipe(
private readonly dialogs: TuiDialogService, switchMap(([version, flavor]) =>
private readonly exver: Exver, this.marketplaceService
) {} .getPackage$(this.pkgId, version, flavor)
.pipe(startWith(null)),
),
)
readonly flavors$ = this.flavor$.pipe(
switchMap(current =>
this.marketplaceService
.getSelectedStore$()
.pipe(
map(({ packages }) =>
packages.filter(
({ id, flavor }) => id === this.pkgId && flavor !== current,
),
),
),
),
)
readonly versions$ = combineLatest([
this.pkg$.pipe(filter(Boolean)),
this.flavor$,
]).pipe(
map(([{ otherVersions }, flavor]) =>
Object.keys(otherVersions)
.filter(v => this.exver.getFlavor(v) === flavor)
.sort((a, b) => -1 * (this.exver.compareExver(a, b) || 0)),
),
)
open(id: string) { open(id: string) {
this.router.navigate([], { queryParams: { id } }) this.router.navigate([], { queryParams: { id } })
} }
presentAlertVersions( selectVersion(
pkg: MarketplacePkg, { version }: MarketplacePkg,
version: TemplateRef<TuiDialogContext>, template: TemplateRef<TuiDialogContext>,
) { ) {
this.dialogs this.dialogs
.open<string>(version, { .open<string>(template, {
label: 'Versions', label: 'Versions',
size: 's', size: 's',
data: { data: {
value: pkg.version, value: version,
items: [...new Set(Object.keys(pkg.otherVersions))].sort(
(a, b) => -1 * (this.exver.compareExver(a, b) || 0),
),
}, },
}) })
.pipe(filter(Boolean)) .pipe(filter(Boolean))
.subscribe(version => this.loadVersion$.next(version)) .subscribe(version => this.version$.next(version))
} }
} }

View File

@@ -1,17 +0,0 @@
import { inject, Pipe, PipeTransform } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import { filter, Observable } from 'rxjs'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Pipe({
name: 'toLocal',
standalone: true,
})
export class ToLocalPipe implements PipeTransform {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
transform(id: string): Observable<PackageDataEntry> {
return this.patch.watch$('packageData', id).pipe(filter(Boolean))
}
}

View File

@@ -16,16 +16,25 @@ export class FilterUpdatesPipe implements PipeTransform {
transform( transform(
pkgs?: MarketplacePkg[], pkgs?: MarketplacePkg[],
local?: Record<string, PackageDataEntry<InstalledState | UpdatingState>>, local: Record<
string,
PackageDataEntry<InstalledState | UpdatingState>
> = {},
): MarketplacePkg[] | null { ): MarketplacePkg[] | null {
return ( return (
pkgs?.filter( pkgs?.filter(
({ version, id }) => ({ id, version, flavor }) =>
this.exver.compareExver( local[id] &&
version, this.exver.getFlavor(getVersion(local, id)) === flavor &&
local?.[id]?.stateInfo.manifest.version || '', this.exver.compareExver(version, getVersion(local, id)) === 1,
) === 1,
) || null ) || null
) )
} }
} }
function getVersion(
local: Record<string, PackageDataEntry<InstalledState | UpdatingState>>,
id: string,
): string {
return local[id].stateInfo.manifest.version
}

View File

@@ -521,7 +521,7 @@ export class MockApiService extends ApiService {
versionRange: string, versionRange: string,
): Promise<GetPackageRes> { ): Promise<GetPackageRes> {
await pauseFor(2000) await pauseFor(2000)
if (!versionRange) { if (!versionRange || versionRange === '=*') {
return Mock.RegistryPackages[id] return Mock.RegistryPackages[id]
} else { } else {
return Mock.OtherPackageVersions[id][versionRange] return Mock.OtherPackageVersions[id][versionRange]

View File

@@ -189,7 +189,6 @@ export class MarketplaceService implements AbstractMarketplaceService {
this.marketplace$.pipe( this.marketplace$.pipe(
switchMap(m => { switchMap(m => {
const url = registryUrl || selected.url const url = registryUrl || selected.url
const pkg = m[url]?.packages.find( const pkg = m[url]?.packages.find(
p => p =>
p.id === id && p.id === id &&
@@ -197,9 +196,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
(!version || this.exver.compareExver(p.version, version) === 0), (!version || this.exver.compareExver(p.version, version) === 0),
) )
return !!pkg return pkg ? of(pkg) : this.fetchPackage$(url, id, version, flavor)
? of(pkg)
: this.fetchPackage$(url, id, version, flavor)
}), }),
), ),
), ),
@@ -229,7 +226,18 @@ export class MarketplaceService implements AbstractMarketplaceService {
} }
fetchInfo$(url: string): Observable<T.RegistryInfo> { fetchInfo$(url: string): Observable<T.RegistryInfo> {
return from(this.api.getRegistryInfo(url)) return from(this.api.getRegistryInfo(url)).pipe(
map(info => ({
...info,
categories: {
all: {
name: 'All',
description: { short: 'All services', long: 'All services' },
},
...info.categories,
},
})),
)
} }
fetchStatic$( fetchStatic$(
@@ -269,7 +277,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
convertToMarketplacePkg( convertToMarketplacePkg(
id: string, id: string,
version: string | null, version: string | null | undefined,
flavor: string | null, flavor: string | null,
pkgInfo: GetPackageRes, pkgInfo: GetPackageRes,
): MarketplacePkg { ): MarketplacePkg {
@@ -299,7 +307,12 @@ export class MarketplaceService implements AbstractMarketplaceService {
this.api.getRegistryPackage(url, id, version ? `=${version}` : null), this.api.getRegistryPackage(url, id, version ? `=${version}` : null),
).pipe( ).pipe(
map(pkgInfo => map(pkgInfo =>
this.convertToMarketplacePkg(id, version, flavor, pkgInfo), this.convertToMarketplacePkg(
id,
version === '*' ? null : version,
flavor,
pkgInfo,
),
), ),
) )
} }