-
About
+
About
{{ pkg.description.long }}
Alternative Implementations
- @for (pkg of pkgs; track $index) {
-
-
-
- {{ pkg.title }}
- {{ pkg.version }}
-
-
+
+ `,
+ 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,
- imports: [RouterLink, TuiCell, TuiTitle, SharedPipesModule],
+ imports: [RouterLink, TuiCell, TuiTitle, SharedPipesModule, TuiAvatar],
})
export class FlavorsComponent {
@Input()
diff --git a/web/projects/ui/src/app/routes/login/ca-wizard/ca-wizard.component.html b/web/projects/ui/src/app/routes/login/ca-wizard/ca-wizard.component.html
index 4cb52b8bc..950e203c1 100644
--- a/web/projects/ui/src/app/routes/login/ca-wizard/ca-wizard.component.html
+++ b/web/projects/ui/src/app/routes/login/ca-wizard/ca-wizard.component.html
@@ -30,7 +30,7 @@
size="s"
appearance="tertiary-solid"
iconEnd="@tui.download"
- href="/eos/local.crt"
+ href="/static/local-root-ca.crt"
>
Download
diff --git a/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts b/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts
index 6b2bd9406..2c6e14abe 100644
--- a/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts
+++ b/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts
@@ -87,22 +87,18 @@ import { ABOUT } from './about.component'
`,
styles: [
`
- tui-icon {
- font-size: 1rem;
+ :host {
+ margin: 0 -0.5rem;
}
- tui-hosted-dropdown {
- margin: 0 -0.5rem;
-
- [tuiIconButton] {
- 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 {
justify-content: flex-start;
- gap: 0.75rem;
+ gap: 0.5rem;
}
`,
],
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts
index 4d9fdc98a..e86016c36 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts
@@ -1,8 +1,7 @@
-import { TuiButton, TuiNotification } from '@taiga-ui/core'
import { CommonModule } from '@angular/common'
import { Component, inject, OnInit, signal } from '@angular/core'
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 { FormComponent } from 'src/app/routes/portal/components/form.component'
import {
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts
index 2587691a5..55712ce20 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts
@@ -88,10 +88,10 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
}
`,
@@ -119,6 +119,9 @@ export class MarketplaceControlsComponent {
@Input()
localPkg!: PackageDataEntry | null
+ @Input()
+ localFlavor!: boolean
+
readonly showDevTools$ = inject(ClientStorageService).showDevTools$
async tryInstall() {
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts
index 5e3fb7da9..ea8fa3eba 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts
@@ -1,32 +1,43 @@
-import { TuiDropdownService, TuiButton } from '@taiga-ui/core'
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
+ computed,
inject,
- Input,
+ input,
} from '@angular/core'
+import { toObservable, toSignal } from '@angular/core/rxjs-interop'
import { ActivatedRoute, Router } from '@angular/router'
import { ItemModule, MarketplacePkg } from '@start9labs/marketplace'
+import { Exver } from '@start9labs/shared'
import { TuiSidebar } from '@taiga-ui/addon-mobile'
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 { ToLocalPipe } from '../pipes/to-local.pipe'
import { MarketplaceSidebarService } from '../services/sidebar.service'
import { MarketplaceControlsComponent } from './controls.component'
@Component({
selector: 'marketplace-tile',
template: `
-
+
@@ -44,8 +55,9 @@ import { MarketplaceControlsComponent } from './controls.component'
@@ -104,7 +116,6 @@ import { MarketplaceControlsComponent } from './controls.component'
imports: [
CommonModule,
ItemModule,
- ToLocalPipe,
TuiAutoFocus,
TuiClickOutside,
TuiSidebar,
@@ -114,18 +125,44 @@ import { MarketplaceControlsComponent } from './controls.component'
],
})
export class MarketplaceTileComponent {
+ private readonly exver = inject(Exver)
+ private readonly patch = inject>(PatchDB)
private readonly router = inject(Router)
- readonly id$ = inject(ActivatedRoute).queryParamMap.pipe(
- map(map => map.get('id') || ''),
- debounceTime(100),
+ private readonly params = toSignal(
+ inject(ActivatedRoute).queryParamMap.pipe(debounceTime(100)),
)
- @Input({ required: true })
- pkg!: MarketplacePkg
+ readonly pkg = input.required()
+ readonly open = computed(
+ () =>
+ this.params()?.get('id') === this.pkg()?.id &&
+ this.params()?.get('flavor') === this.pkg()?.flavor,
+ )
+
+ readonly local$: Observable = 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) {
this.router.navigate([], {
- queryParams: { id: open ? this.pkg.id : null },
+ queryParams: {
+ id: open ? this.pkg().id : null,
+ flavor: open ? this.pkg().flavor : null,
+ },
})
}
}
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts
index b30e59df2..616afaced 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts
@@ -52,7 +52,6 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
styles: [
`
:host {
- height: calc(100vh - 3.5rem);
display: flex;
flex-direction: column;
overflow: hidden;
@@ -131,7 +130,7 @@ import { MarketplaceSidebarsComponent } from './components/sidebars.component'
}
:host-context(tui-root._mobile) {
- height: calc(100vh - 3.5rem - var(--tui-height-l));
+ padding: 0;
}
`,
],
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts
index 6fd9cc1c1..53a91ccd7 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts
@@ -12,6 +12,7 @@ import {
AboutModule,
AbstractMarketplaceService,
AdditionalModule,
+ FlavorsComponent,
MarketplaceAdditionalItemComponent,
MarketplaceDependenciesComponent,
MarketplacePackageHeroComponent,
@@ -26,7 +27,16 @@ import {
TuiLoader,
} from '@taiga-ui/core'
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({
selector: 'marketplace-preview',
@@ -34,34 +44,36 @@ import { BehaviorSubject, filter, switchMap, tap } from 'rxjs'
@if (pkg$ | async; as pkg) {
- @if (loading$ | async) {
-
- } @else {
-
-
-
-
-
- @if (!(pkg.dependencyMetadata | empty)) {
-
- }
-
+
+
+
+
+ @if (flavors$ | async; as flavors) {
+
+ }
+
+ @if (!(pkg.dependencyMetadata | empty)) {
+
+ }
+
+ @if (versions$ | async; as versions) {
+ />
-
+
-
-
- }
+ }
+
+
+ } @else {
+
}
`,
@@ -164,54 +178,72 @@ import { BehaviorSubject, filter, switchMap, tap } from 'rxjs'
TuiRadioList,
TuiLoader,
TuiIcon,
+ FlavorsComponent,
],
})
export class MarketplacePreviewComponent {
@Input({ required: true })
pkgId!: string
- readonly loading$ = new BehaviorSubject(true)
-
+ private readonly dialogs = inject(TuiDialogService)
+ private readonly exver = inject(Exver)
private readonly router = inject(Router)
private readonly marketplaceService = inject(AbstractMarketplaceService)
- readonly url = this.router.routerState.snapshot.root.queryParamMap.get('url')
-
- readonly loadVersion$ = new BehaviorSubject('*')
- readonly pkg$ = this.loadVersion$.pipe(
- switchMap(version =>
- this.marketplaceService.getPackage$(this.pkgId, version, this.url),
- ),
- tap(data => {
- this.loading$.next(false)
- return data
- }),
+ private readonly version$ = new BehaviorSubject('*')
+ private readonly flavor$ = this.router.routerState.root.queryParamMap.pipe(
+ map(paramMap => paramMap.get('flavor')),
)
- constructor(
- private readonly dialogs: TuiDialogService,
- private readonly exver: Exver,
- ) {}
+ readonly pkg$ = combineLatest([this.version$, this.flavor$]).pipe(
+ switchMap(([version, flavor]) =>
+ 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) {
this.router.navigate([], { queryParams: { id } })
}
- presentAlertVersions(
- pkg: MarketplacePkg,
- version: TemplateRef,
+ selectVersion(
+ { version }: MarketplacePkg,
+ template: TemplateRef,
) {
this.dialogs
- .open(version, {
+ .open(template, {
label: 'Versions',
size: 's',
data: {
- value: pkg.version,
- items: [...new Set(Object.keys(pkg.otherVersions))].sort(
- (a, b) => -1 * (this.exver.compareExver(a, b) || 0),
- ),
+ value: version,
},
})
.pipe(filter(Boolean))
- .subscribe(version => this.loadVersion$.next(version))
+ .subscribe(version => this.version$.next(version))
}
}
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/pipes/to-local.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/pipes/to-local.pipe.ts
deleted file mode 100644
index a60ea9154..000000000
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/pipes/to-local.pipe.ts
+++ /dev/null
@@ -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)
-
- transform(id: string): Observable {
- return this.patch.watch$('packageData', id).pipe(filter(Boolean))
- }
-}
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts
index 0e257a1d8..3d312bf68 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts
@@ -16,16 +16,25 @@ export class FilterUpdatesPipe implements PipeTransform {
transform(
pkgs?: MarketplacePkg[],
- local?: Record>,
+ local: Record<
+ string,
+ PackageDataEntry
+ > = {},
): MarketplacePkg[] | null {
return (
pkgs?.filter(
- ({ version, id }) =>
- this.exver.compareExver(
- version,
- local?.[id]?.stateInfo.manifest.version || '',
- ) === 1,
+ ({ id, version, flavor }) =>
+ local[id] &&
+ this.exver.getFlavor(getVersion(local, id)) === flavor &&
+ this.exver.compareExver(version, getVersion(local, id)) === 1,
) || null
)
}
}
+
+function getVersion(
+ local: Record>,
+ id: string,
+): string {
+ return local[id].stateInfo.manifest.version
+}
diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts
index 43c012b8f..83fbd9829 100644
--- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts
+++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts
@@ -521,7 +521,7 @@ export class MockApiService extends ApiService {
versionRange: string,
): Promise {
await pauseFor(2000)
- if (!versionRange) {
+ if (!versionRange || versionRange === '=*') {
return Mock.RegistryPackages[id]
} else {
return Mock.OtherPackageVersions[id][versionRange]
diff --git a/web/projects/ui/src/app/services/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts
index f4d11f475..f2cd6c878 100644
--- a/web/projects/ui/src/app/services/marketplace.service.ts
+++ b/web/projects/ui/src/app/services/marketplace.service.ts
@@ -189,7 +189,6 @@ export class MarketplaceService implements AbstractMarketplaceService {
this.marketplace$.pipe(
switchMap(m => {
const url = registryUrl || selected.url
-
const pkg = m[url]?.packages.find(
p =>
p.id === id &&
@@ -197,9 +196,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
(!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)
}),
),
),
@@ -229,7 +226,18 @@ export class MarketplaceService implements AbstractMarketplaceService {
}
fetchInfo$(url: string): Observable {
- 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$(
@@ -269,7 +277,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
convertToMarketplacePkg(
id: string,
- version: string | null,
+ version: string | null | undefined,
flavor: string | null,
pkgInfo: GetPackageRes,
): MarketplacePkg {
@@ -299,7 +307,12 @@ export class MarketplaceService implements AbstractMarketplaceService {
this.api.getRegistryPackage(url, id, version ? `=${version}` : null),
).pipe(
map(pkgInfo =>
- this.convertToMarketplacePkg(id, version, flavor, pkgInfo),
+ this.convertToMarketplacePkg(
+ id,
+ version === '*' ? null : version,
+ flavor,
+ pkgInfo,
+ ),
),
)
}