mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Feature/marketplace redesign (#2395)
* wip * update marketplace categories styling * update logo icons * add sort pipe * update search component styling * clean up categories component * cleanup and remove unnecessary sort pipe * query packages in selected category * fix search styling * add reg icon and font, adjust category styles * fix build from rebasing integration/refactors * adjust marketplace types for icon with store data, plus formatting * formatting * update categories and search * hover styling for categories * category styling * refactor for category as a behavior subject * more category styling * base functionality with new marketplace components * styling cleanup * misc style fixes and fix category selection from package page * fixes from review feedback * add and style additional details * implement release notes modal * fix menu when on service show page mobile to display change marketplace * style and responsiveness fixes * rename header to sidebar * input icon config to sidebar * add mime type pipe and type fn * review feedback fixes * skeleton text, more abstraction * reorder categories, clean up a little * audit sidebar, categories, store-icon, marketplace-sidebar, search * finish code cleanup and fix few bugs * misc fixes and cleanup * fix broken styles and markdown * bump shared marketplace version * more cleanup * sync package lock * rename sidebar component to menu * wip preview sidebar * sync package lock * breakout package show elements into components * link to brochure in preview; custom taiga button styles * move marketplace preview component into ui; open preview when viewing service in marketplace * sync changes post file struture rename * further cleanup * create service for sidebar toggle and cleanup marketplace components * bump shared marketplace version * bump shared for new images needed for brochure marketplace * cleanup --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -18,6 +18,7 @@ tui-root {
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
transition: filter 0.3s;
|
||||
|
||||
&_offline {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ThemeSwitcherService } from './services/theme-switcher.service'
|
||||
import { THEME } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from './services/patch-db/data-model'
|
||||
import { slideInAnimation } from './route-animation'
|
||||
|
||||
function hasNavigation(url: string): boolean {
|
||||
return (
|
||||
@@ -28,13 +29,14 @@ function hasNavigation(url: string): boolean {
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.scss'],
|
||||
animations: [slideInAnimation],
|
||||
})
|
||||
export class AppComponent implements OnDestroy {
|
||||
readonly subscription = merge(this.patchData, this.patchMonitor).subscribe()
|
||||
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
||||
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
|
||||
readonly theme$ = inject(THEME)
|
||||
|
||||
// @TODO theres a bug here disabling the side menu from appearing on first login; refresh fixes
|
||||
readonly navigation$ = combineLatest([
|
||||
this.authService.isVerified$,
|
||||
this.router.events.pipe(map(() => hasNavigation(this.router.url))),
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
TUI_DATE_VALUE_TRANSFORMER,
|
||||
} from '@taiga-ui/kit'
|
||||
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import {
|
||||
AbstractCategoryService,
|
||||
AbstractMarketplaceService,
|
||||
} from '@start9labs/marketplace'
|
||||
import { ApiService } from './services/api/embassy-api.service'
|
||||
import { MockApiService } from './services/api/embassy-mock-api.service'
|
||||
import { LiveApiService } from './services/api/embassy-live-api.service'
|
||||
@@ -25,6 +28,7 @@ import { DateTransformerService } from './services/date-transformer.service'
|
||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
||||
import { MarketplaceService } from './services/marketplace.service'
|
||||
import { RoutingStrategyService } from './apps/portal/services/routing-strategy.service'
|
||||
import { CategoryService } from './services/category.service'
|
||||
|
||||
const {
|
||||
useMocks,
|
||||
@@ -80,6 +84,10 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
provide: RouteReuseStrategy,
|
||||
useExisting: RoutingStrategyService,
|
||||
},
|
||||
{
|
||||
provide: AbstractCategoryService,
|
||||
useClass: CategoryService,
|
||||
},
|
||||
]
|
||||
|
||||
export function appInitializer(
|
||||
|
||||
@@ -6,8 +6,8 @@ import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||
import { from } from 'rxjs'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
InstalledPackageInfo,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
@@ -92,8 +92,9 @@ export class ToMenuPipe implements PipeTransform {
|
||||
name: 'Marketplace Listing',
|
||||
description: `View ${manifest.title} on the Marketplace`,
|
||||
action: () =>
|
||||
this.router.navigate(['marketplace', manifest.id], {
|
||||
queryParams: { url },
|
||||
this.router.navigate(['marketplace'], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { url, id: manifest.id },
|
||||
}),
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
import {
|
||||
TuiAvatarModule,
|
||||
TuiCellModule,
|
||||
TuiTitleModule,
|
||||
} from '@taiga-ui/experimental'
|
||||
|
||||
@Component({
|
||||
selector: 'sideload-dependencies',
|
||||
template: `
|
||||
<h3 class="g-title" [style.text-indent.rem]="1">Dependencies</h3>
|
||||
<div *ngFor="let dep of package.manifest.dependencies | keyvalue" tuiCell>
|
||||
<tui-avatar [src]="getImage(dep.key)"></tui-avatar>
|
||||
<div tuiTitle>
|
||||
<div>
|
||||
<strong>{{ getTitle(dep.key) }} </strong>
|
||||
<ng-container [ngSwitch]="dep.value.requirement.type">
|
||||
<span *ngSwitchCase="'required'">(required)</span>
|
||||
<span *ngSwitchCase="'opt-out'">(required by default)</span>
|
||||
<span *ngSwitchCase="'opt-in'">(optional)</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div tuiSubtitle [style.color]="'var(--tui-text-03)'">
|
||||
{{ dep.value.version | displayEmver }}
|
||||
</div>
|
||||
<div tuiSubtitle>
|
||||
{{ dep.value.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiTitleModule,
|
||||
EmverPipesModule,
|
||||
TuiAvatarModule,
|
||||
TuiCellModule,
|
||||
],
|
||||
})
|
||||
export class SideloadDependenciesComponent {
|
||||
@Input({ required: true })
|
||||
package!: MarketplacePkg
|
||||
|
||||
getTitle(key: string): string {
|
||||
return this.package['dependency-metadata'][key]?.title || key
|
||||
}
|
||||
|
||||
getImage(key: string): string {
|
||||
const icon = this.package['dependency-metadata'][key]?.icon
|
||||
|
||||
return icon ? `data:image/png;base64,${icon}` : key.substring(0, 2)
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ import { Router, RouterLink } from '@angular/router'
|
||||
import {
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
MarketplacePackageHeroComponent,
|
||||
MarketplacePkg,
|
||||
PackageModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import {
|
||||
Emver,
|
||||
@@ -23,31 +24,49 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ClientStorageService } from 'src/app/services/client-storage.service'
|
||||
|
||||
import { NavigationService } from '../../../services/navigation.service'
|
||||
import { SideloadDependenciesComponent } from './dependencies.component'
|
||||
|
||||
@Component({
|
||||
selector: 'sideload-package',
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
<marketplace-package *tuiLet="button$ | async as button" [pkg]="package">
|
||||
<a
|
||||
*ngIf="button !== null && button !== 'Install'"
|
||||
tuiButton
|
||||
appearance="secondary"
|
||||
[routerLink]="'/portal/service/' + package.manifest.id"
|
||||
<div class="grid gap-8 mb-16 p-4 lg:px-16 lg:pb-8 pt-14 justify-center">
|
||||
<ng-content></ng-content>
|
||||
<marketplace-package-hero
|
||||
*tuiLet="button$ | async as button"
|
||||
[pkg]="package"
|
||||
>
|
||||
View installed
|
||||
</a>
|
||||
<button *ngIf="button" tuiButton (click)="upload()">
|
||||
{{ button }}
|
||||
</button>
|
||||
</marketplace-package>
|
||||
<marketplace-about [pkg]="package"></marketplace-about>
|
||||
<sideload-dependencies
|
||||
*ngIf="!(package.manifest.dependencies | empty)"
|
||||
[package]="package"
|
||||
></sideload-dependencies>
|
||||
<marketplace-additional [pkg]="package"></marketplace-additional>
|
||||
<div class="flex justify-start">
|
||||
<a
|
||||
*ngIf="button !== null && button !== 'Install'"
|
||||
tuiButton
|
||||
appearance="secondary"
|
||||
[routerLink]="'/portal/service/' + package.manifest.id"
|
||||
>
|
||||
View installed
|
||||
</a>
|
||||
<button *ngIf="button" tuiButton (click)="upload()">
|
||||
{{ button }}
|
||||
</button>
|
||||
</div>
|
||||
</marketplace-package-hero>
|
||||
<marketplace-about [pkg]="package"></marketplace-about>
|
||||
<div
|
||||
*ngIf="!(package.manifest.dependencies | empty)"
|
||||
class="rounded-xl bg-gradient-to-bl from-zinc-400/75 to-zinc-600 p-px shadow-lg shadow-zinc-400/10"
|
||||
>
|
||||
<div class="lg:col-span-5 xl:col-span-4 bg-zinc-800 rounded-xl p-7">
|
||||
<h2 class="text-lg font-bold small-caps my-2 pb-3">Dependencies</h2>
|
||||
<div class="grid grid-row-auto gap-3">
|
||||
<div *ngFor="let dep of package.manifest.dependencies | keyvalue">
|
||||
<marketplace-dependencies
|
||||
[dep]="dep"
|
||||
[pkg]="package"
|
||||
></marketplace-dependencies>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<marketplace-additional [pkg]="package"></marketplace-additional>
|
||||
</div>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [
|
||||
@@ -56,10 +75,10 @@ import { SideloadDependenciesComponent } from './dependencies.component'
|
||||
SharedPipesModule,
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
PackageModule,
|
||||
TuiButtonModule,
|
||||
TuiLetModule,
|
||||
SideloadDependenciesComponent,
|
||||
MarketplacePackageHeroComponent,
|
||||
DependenciesModule,
|
||||
],
|
||||
})
|
||||
export class SideloadPackageComponent {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { SideloadPackageComponent } from './package.component'
|
||||
[style.border-radius.%]="100"
|
||||
[style.float]="'right'"
|
||||
(click)="clear()"
|
||||
class="justify-self-end"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
@@ -83,8 +83,8 @@ import { InstallProgressPipe } from '../pipes/install-progress.pipe'
|
||||
tuiLink
|
||||
iconAlign="right"
|
||||
icon="tuiIconExternalLink"
|
||||
[routerLink]="'/marketplace/' + marketplacePkg.manifest.id"
|
||||
[queryParams]="{ url: url }"
|
||||
routerLink="/marketplace"
|
||||
[queryParams]="{ url: url, id: marketplacePkg.manifest.id }"
|
||||
>
|
||||
View listing
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { CommonModule, DOCUMENT } from '@angular/common'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
HostListener,
|
||||
Inject,
|
||||
Input,
|
||||
inject,
|
||||
} from '@angular/core'
|
||||
import { TuiButtonModule } from '@taiga-ui/core'
|
||||
import { TuiActiveZoneModule } from '@taiga-ui/cdk'
|
||||
import { TuiSidebarModule } from '@taiga-ui/addon-mobile'
|
||||
import { ItemModule, MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { MarketplaceShowControlsComponent } from '../marketplace-show-preview/components/marketplace-show-controls.component'
|
||||
import { MarketplaceShowPreviewModule } from '../marketplace-show-preview/marketplace-show-preview.module'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { BehaviorSubject, filter, Observable, shareReplay } from 'rxjs'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { SidebarService } from 'src/app/services/sidebar.service'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-item-toggle',
|
||||
template: `
|
||||
<div
|
||||
[id]="pkg.manifest.id"
|
||||
class="block h-full animate"
|
||||
style="--animation-order: {{ index }}"
|
||||
(click)="toggle(true)"
|
||||
(tuiActiveZoneChange)="toggle($event)"
|
||||
>
|
||||
<marketplace-item [pkg]="pkg"></marketplace-item>
|
||||
<marketplace-show-preview
|
||||
[pkg]="pkg"
|
||||
*tuiSidebar="
|
||||
!!(sidebarService.getToggleState(pkg.manifest.id) | async);
|
||||
direction: 'right';
|
||||
autoWidth: true
|
||||
"
|
||||
class="overflow-y-auto max-w-full md:max-w-[30rem]"
|
||||
>
|
||||
<button
|
||||
slot="close"
|
||||
[style.--tui-padding]="0"
|
||||
size="xs"
|
||||
class="place-self-end"
|
||||
tuiIconButton
|
||||
type="button"
|
||||
appearance="icon"
|
||||
icon="tuiIconClose"
|
||||
(tuiActiveZoneChange)="toggle($event)"
|
||||
(click)="toggle(false)"
|
||||
></button>
|
||||
<marketplace-show-controls
|
||||
slot="controls"
|
||||
[pkg]="pkg"
|
||||
[localPkg]="localPkg$ | async"
|
||||
></marketplace-show-controls>
|
||||
</marketplace-show-preview>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.animate {
|
||||
animation-name: animateIn;
|
||||
animation-duration: 400ms;
|
||||
animation-delay: calc(var(--animation-order) * 200ms);
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes animateIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.6) translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiActiveZoneModule,
|
||||
TuiButtonModule,
|
||||
TuiSidebarModule,
|
||||
MarketplaceShowPreviewModule,
|
||||
ItemModule,
|
||||
MarketplaceShowControlsComponent,
|
||||
],
|
||||
})
|
||||
export class MarketplaceItemToggleComponent {
|
||||
@Input({ required: true })
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
@Input({ required: true })
|
||||
index!: number
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly activatedRoute: ActivatedRoute,
|
||||
@Inject(DOCUMENT) private readonly document: Document,
|
||||
) {}
|
||||
readonly sidebarService = inject(SidebarService)
|
||||
localPkg$!: Observable<PackageDataEntry>
|
||||
pkgIdQueryParam = new BehaviorSubject<string>('')
|
||||
readonly pkgId = this.activatedRoute.queryParamMap.subscribe(params => {
|
||||
this.pkgIdQueryParam.next(params.get('id')!)
|
||||
})
|
||||
|
||||
ngOnChanges() {
|
||||
this.localPkg$ = this.patch
|
||||
.watch$('package-data', this.pkg.manifest.id)
|
||||
.pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true }))
|
||||
}
|
||||
|
||||
@HostListener('animationend', ['$event.target'])
|
||||
async onAnimationEnd(_target: EventTarget | null) {
|
||||
if (this.pkgIdQueryParam.value === this.pkg.manifest.id) {
|
||||
this.toggle(true)
|
||||
}
|
||||
}
|
||||
|
||||
toggle(open: boolean) {
|
||||
this.sidebarService.toggleState(this.pkg.manifest.id, open)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<menu [iconConfig]="marketplace">
|
||||
<button
|
||||
slot="desktop"
|
||||
tuiIconButton
|
||||
type="button"
|
||||
appearance="icon"
|
||||
icon="tuiIconRepeatLarge"
|
||||
class="hover:opacity-70 bg-zinc-600 rounded-lg -mt-3"
|
||||
(click)="presentModalMarketplaceSettings()"
|
||||
></button>
|
||||
<a
|
||||
slot="mobile"
|
||||
class="flex gap-2 relative hover:no-underline p-5"
|
||||
(click)="presentModalMarketplaceSettings()"
|
||||
>
|
||||
<img
|
||||
alt="Change Registry Icon"
|
||||
width="24"
|
||||
height="24"
|
||||
class="opacity-70 invert"
|
||||
src="svg/repeat-outline.svg"
|
||||
/>
|
||||
<span
|
||||
class="text-base text-zinc-50 text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
>
|
||||
Change Registry
|
||||
</span>
|
||||
</a>
|
||||
</menu>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||
import { MarketplaceSettingsPage } from '../../marketplace-list/marketplace-settings/marketplace-settings.page'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-menu',
|
||||
templateUrl: 'marketplace-menu.component.html',
|
||||
styleUrls: ['./marketplace-menu.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceMenuComponent {
|
||||
constructor(
|
||||
@Inject(TuiDialogService) private readonly dialogs: TuiDialogService,
|
||||
readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
readonly marketplace = this.config.marketplace
|
||||
|
||||
async presentModalMarketplaceSettings() {
|
||||
this.dialogs
|
||||
.open<MarketplaceSettingsPage>(
|
||||
new PolymorpheusComponent(MarketplaceSettingsPage),
|
||||
{
|
||||
label: 'Change Registry',
|
||||
},
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { MarketplaceMenuComponent } from './marketplace-menu.component'
|
||||
import { MenuModule } from '@start9labs/marketplace'
|
||||
import { TuiButtonModule } from '@taiga-ui/core'
|
||||
|
||||
@NgModule({
|
||||
imports: [MenuModule, TuiButtonModule],
|
||||
exports: [MarketplaceMenuComponent],
|
||||
declarations: [MarketplaceMenuComponent],
|
||||
})
|
||||
export class MarketplaceMenuModule {}
|
||||
@@ -1,26 +1,15 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import {
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
ResponsiveColDirective,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
FilterPackagesPipeModule,
|
||||
CategoriesModule,
|
||||
ItemModule,
|
||||
SearchModule,
|
||||
SkeletonModule,
|
||||
StoreIconComponentModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module'
|
||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||
import { ResponsiveColDirective, SharedPipesModule } from '@start9labs/shared'
|
||||
import { FilterPackagesPipeModule } from '@start9labs/marketplace'
|
||||
import { MarketplaceMenuModule } from '../components/marketplace-menu/marketplace-menu.module'
|
||||
import { MarketplaceListPage } from './marketplace-list.page'
|
||||
import { MarketplaceSettingsPageModule } from './marketplace-settings/marketplace-settings.module'
|
||||
|
||||
import { TuiNotificationModule } from '@taiga-ui/core'
|
||||
import { TuiLetModule } from '@taiga-ui/cdk'
|
||||
import { MarketplaceItemToggleComponent } from '../components/marketplace-item-toggle.component'
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
@@ -32,20 +21,15 @@ const routes: Routes = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
FormsModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
FilterPackagesPipeModule,
|
||||
MarketplaceStatusModule,
|
||||
BadgeMenuComponentModule,
|
||||
ItemModule,
|
||||
CategoriesModule,
|
||||
SearchModule,
|
||||
SkeletonModule,
|
||||
MarketplaceMenuModule,
|
||||
MarketplaceSettingsPageModule,
|
||||
StoreIconComponentModule,
|
||||
TuiNotificationModule,
|
||||
TuiLetModule,
|
||||
ResponsiveColDirective,
|
||||
MarketplaceItemToggleComponent,
|
||||
],
|
||||
declarations: [MarketplaceListPage],
|
||||
exports: [MarketplaceListPage],
|
||||
|
||||
@@ -1,90 +1,112 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start" *ngIf="back">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Marketplace</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding with-widgets">
|
||||
<ng-container *ngIf="details$ | async as details">
|
||||
<ion-item [color]="details.color">
|
||||
<ion-icon slot="start" name="information-circle-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2 style="font-weight: 600" [innerHTML]="details.description"></h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="heading">
|
||||
<store-icon
|
||||
class="icon"
|
||||
size="80px"
|
||||
[url]="details.url"
|
||||
[marketplace]="config.marketplace"
|
||||
></store-icon>
|
||||
<h1 class="montserrat">{{ details.name }}</h1>
|
||||
</div>
|
||||
<ion-button fill="clear" (click)="presentModalMarketplaceSettings()">
|
||||
<ion-icon slot="start" name="repeat-outline"></ion-icon>
|
||||
Change
|
||||
</ion-button>
|
||||
<marketplace-search [(query)]="query"></marketplace-search>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col>
|
||||
<ng-container *ngIf="store$ | async as store; else loading">
|
||||
<marketplace-categories
|
||||
[categories]="store.categories"
|
||||
[category]="query ? '' : category"
|
||||
(categoryChange)="onCategoryChange($event)"
|
||||
></marketplace-categories>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<ion-grid
|
||||
*ngIf="store.packages | filterPackages: query:category as filtered"
|
||||
>
|
||||
<ng-container *ngIf="filtered.length; else empty">
|
||||
<ion-row *ngIf="localPkgs$ | async as localPkgs">
|
||||
<ion-col
|
||||
*ngFor="let pkg of filtered"
|
||||
responsiveCol
|
||||
sizeXs="12"
|
||||
sizeSm="12"
|
||||
sizeMd="6"
|
||||
>
|
||||
<marketplace-item [pkg]="pkg">
|
||||
<marketplace-status
|
||||
class="status"
|
||||
[version]="pkg.manifest.version"
|
||||
[localPkg]="localPkgs[pkg.manifest.id]"
|
||||
></marketplace-status>
|
||||
</marketplace-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #empty>
|
||||
<div class="ion-padding">
|
||||
<h2>No results</h2>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ion-grid>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #loading>
|
||||
<marketplace-skeleton></marketplace-skeleton>
|
||||
</ng-template>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
<marketplace-menu></marketplace-menu>
|
||||
<div
|
||||
class="background sm:pl-[34vw] md:pl-[28vw] lg:pl-[22vw] 2xl:pl-[280px] min-h-screen flex justify-between overflow-auto scroll-smooth"
|
||||
>
|
||||
<main class="pt-24 sm:pt-3 md:pb-10 md:px-8">
|
||||
<ng-container *ngIf="details$ | async as details">
|
||||
<!-- icon as empty string displays no icon -->
|
||||
<tui-notification
|
||||
*ngIf="details.url === marketplace.start9"
|
||||
status="success"
|
||||
icon=""
|
||||
class="m-4"
|
||||
>
|
||||
<p>
|
||||
Services from this registry are packaged and maintained by the Start9
|
||||
team. If you experience an issue or have questions related to a
|
||||
service from this registry, one of our dedicated support staff will be
|
||||
happy to assist you.
|
||||
</p>
|
||||
</tui-notification>
|
||||
<tui-notification
|
||||
*ngIf="details.url === marketplace.community"
|
||||
status="info"
|
||||
icon=""
|
||||
class="m-4"
|
||||
>
|
||||
<p>
|
||||
Services from this registry are packaged and maintained by members of
|
||||
the Start9 community.
|
||||
<strong>Install at your own risk</strong>
|
||||
. If you experience an issue or have a question related to a service
|
||||
in this marketplace, please reach out to the package developer for
|
||||
assistance.
|
||||
</p>
|
||||
</tui-notification>
|
||||
<tui-notification
|
||||
*ngIf="details.url.includes('beta')"
|
||||
status="warning"
|
||||
icon=""
|
||||
class="m-4"
|
||||
>
|
||||
<p>
|
||||
Services from this registry are undergoing
|
||||
<strong>beta</strong>
|
||||
testing and may contain bugs.
|
||||
<strong>Install at your own risk</strong>
|
||||
.
|
||||
</p>
|
||||
</tui-notification>
|
||||
<tui-notification
|
||||
*ngIf="details.url.includes('alpha')"
|
||||
status="error"
|
||||
icon=""
|
||||
class="m-4"
|
||||
>
|
||||
<p>
|
||||
Services from this registry are undergoing
|
||||
<strong>alpha</strong>
|
||||
testing. They are expected to contain bugs and could damage your
|
||||
system.
|
||||
<strong>Install at your own risk</strong>
|
||||
.
|
||||
</p>
|
||||
</tui-notification>
|
||||
<tui-notification
|
||||
*ngIf="details.url !== marketplace.community && details.url !== marketplace.start9 && !details.url.includes('beta') && !details.url.includes('alpha')"
|
||||
status="warning"
|
||||
icon=""
|
||||
class="m-4"
|
||||
>
|
||||
<p>
|
||||
This is a Custom Registry. Start9 cannot verify the integrity or
|
||||
functionality of services from this registry, and they could damage
|
||||
your system.
|
||||
<strong>Install at your own risk</strong>
|
||||
.
|
||||
</p>
|
||||
</tui-notification>
|
||||
</ng-container>
|
||||
<div class="mt-8 px-6 mb-10">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold text-zinc-50/80 pb-6">
|
||||
{{ category$ | async | titlecase }}
|
||||
</h1>
|
||||
</div>
|
||||
<ng-container *ngIf="packages$ | async as packages; else loading">
|
||||
<section
|
||||
class="mt-10"
|
||||
*ngIf="
|
||||
packages | filterPackages : (query$ | async) : (category$ | async) as filtered
|
||||
"
|
||||
>
|
||||
<ul
|
||||
class="px-6 md:px-8 grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-16 list-none"
|
||||
>
|
||||
<li *ngFor="let pkg of filtered; index as i">
|
||||
<marketplace-item-toggle
|
||||
[pkg]="pkg"
|
||||
[index]="i"
|
||||
class="block h-full"
|
||||
></marketplace-item-toggle>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</ng-container>
|
||||
<ng-template #loading>
|
||||
<h1 class="text-xl pl-6">
|
||||
Loading
|
||||
<span class="loading-dots"></span>
|
||||
</h1>
|
||||
</ng-template>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,40 +1,4 @@
|
||||
.heading {
|
||||
margin-top: 32px;
|
||||
h1 {
|
||||
font-size: 42px;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
.ion-padding {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.description {
|
||||
|
||||
ion-icon {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
ion-label {
|
||||
::ng-deep p {
|
||||
font-size: 1.1rem;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.background {
|
||||
background: url('/assets/img/background.png') no-repeat center center fixed;
|
||||
z-index: -100;
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import {
|
||||
AbstractCategoryService,
|
||||
AbstractMarketplaceService,
|
||||
} from '@start9labs/marketplace'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs'
|
||||
import { MarketplaceSettingsPage } from './marketplace-settings/marketplace-settings.page'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { CategoryService } from 'src/app/services/category.service'
|
||||
import { SidebarService } from 'src/app/services/sidebar.service'
|
||||
import { MarketplaceSettingsPage } from './marketplace-settings/marketplace-settings.page'
|
||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-list',
|
||||
@@ -17,81 +21,37 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceListPage {
|
||||
readonly back = !!this.route.snapshot.queryParamMap.get('back')
|
||||
|
||||
readonly store$ = this.marketplaceService.getSelectedStore$().pipe(
|
||||
map(({ info, packages }) => {
|
||||
const categories = new Set<string>()
|
||||
if (info.categories.includes('featured')) categories.add('featured')
|
||||
info.categories.forEach(c => categories.add(c))
|
||||
categories.add('all')
|
||||
|
||||
return { categories: Array.from(categories), packages }
|
||||
}),
|
||||
)
|
||||
|
||||
readonly localPkgs$ = this.patch.watch$('package-data')
|
||||
|
||||
readonly details$ = this.marketplaceService.getSelectedHost$().pipe(
|
||||
map(({ url, name }) => {
|
||||
const { start9, community } = this.config.marketplace
|
||||
let color: string
|
||||
let description: string
|
||||
|
||||
if (url === start9) {
|
||||
color = 'success'
|
||||
description =
|
||||
'Services from this registry are packaged and maintained by the Start9 team. If you experience an issue or have questions related to a service from this registry, one of our dedicated support staff will be happy to assist you.'
|
||||
} else if (url === community) {
|
||||
color = 'tertiary'
|
||||
description =
|
||||
'Services from this registry are packaged and maintained by members of the Start9 community. <b>Install at your own risk</b>. If you experience an issue or have a question related to a service in this marketplace, please reach out to the package developer for assistance.'
|
||||
} else if (url.includes('beta')) {
|
||||
color = 'warning'
|
||||
description =
|
||||
'Services from this registry are undergoing <b>beta</b> testing and may contain bugs. <b>Install at your own risk</b>.'
|
||||
} else if (url.includes('alpha')) {
|
||||
color = 'danger'
|
||||
description =
|
||||
'Services from this registry are undergoing <b>alpha</b> testing. They are expected to contain bugs and could damage your system. <b>Install at your own risk</b>.'
|
||||
} else {
|
||||
// alt marketplace
|
||||
color = 'warning'
|
||||
description =
|
||||
'This is a Custom Registry. Start9 cannot verify the integrity or functionality of services from this registry, and they could damage your system. <b>Install at your own risk</b>.'
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
url,
|
||||
color,
|
||||
description,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly dialogs: TuiDialogService,
|
||||
@Inject(AbstractCategoryService)
|
||||
private readonly categoryService: CategoryService,
|
||||
@Inject(TuiDialogService) private readonly dialogs: TuiDialogService,
|
||||
readonly config: ConfigService,
|
||||
private readonly route: ActivatedRoute,
|
||||
readonly sidebarService: SidebarService,
|
||||
) {}
|
||||
|
||||
category = 'featured'
|
||||
query = ''
|
||||
readonly packages$ = this.marketplaceService.getSelectedStore$().pipe(
|
||||
map(({ packages }) => {
|
||||
this.sidebarService.setMap(packages.map(p => p.manifest.id))
|
||||
return packages
|
||||
}),
|
||||
)
|
||||
readonly localPkgs$ = this.patch.watch$('package-data')
|
||||
readonly category$ = this.categoryService.getCategory$()
|
||||
readonly query$ = this.categoryService.getQuery$()
|
||||
readonly details$ = this.marketplaceService.getSelectedHost$()
|
||||
readonly marketplace = this.config.marketplace
|
||||
|
||||
presentModalMarketplaceSettings() {
|
||||
async presentModalMarketplaceSettings() {
|
||||
this.dialogs
|
||||
.open(new PolymorpheusComponent(MarketplaceSettingsPage), {
|
||||
label: 'Change Registry',
|
||||
})
|
||||
.open<MarketplaceSettingsPage>(
|
||||
new PolymorpheusComponent(MarketplaceSettingsPage),
|
||||
{
|
||||
label: 'Change Registry',
|
||||
},
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
onCategoryChange(category: string): void {
|
||||
this.category = category
|
||||
this.query = ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,18 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceSettingsPage {
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly loader: LoadingService,
|
||||
private readonly formDialog: FormDialogService,
|
||||
private readonly errorService: ErrorService,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly dialogs: TuiDialogService,
|
||||
readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
stores$ = combineLatest([
|
||||
this.marketplaceService.getKnownHosts$(),
|
||||
this.marketplaceService.getSelectedHost$(),
|
||||
@@ -43,18 +55,6 @@ export class MarketplaceSettingsPage {
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly loader: LoadingService,
|
||||
private readonly formDialog: FormDialogService,
|
||||
private readonly errorService: ErrorService,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly dialogs: TuiDialogService,
|
||||
readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
async presentModalAdd() {
|
||||
const { name, spec } = getMarketplaceValueSpec()
|
||||
|
||||
|
||||
@@ -2,18 +2,27 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
inject,
|
||||
Input,
|
||||
} from '@angular/core'
|
||||
import {
|
||||
AbstractMarketplaceService,
|
||||
MarketplacePkg,
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import {
|
||||
Emver,
|
||||
ErrorService,
|
||||
isEmptyObject,
|
||||
LoadingService,
|
||||
pauseFor,
|
||||
sameUrl,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
SharedPipesModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { filter, firstValueFrom, of, Subscription, switchMap } from 'rxjs'
|
||||
@@ -29,12 +38,99 @@ import { PatchDB } from 'patch-db-client'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
import { TUI_PROMPT } from '@taiga-ui/kit'
|
||||
import { dryUpdate } from 'src/app/util/dry-update'
|
||||
import { Router } from '@angular/router'
|
||||
import { SidebarService } from 'src/app/services/sidebar.service'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
|
||||
import { TuiButtonModule } from '@taiga-ui/core'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-controls',
|
||||
templateUrl: 'marketplace-show-controls.component.html',
|
||||
styleUrls: ['./marketplace-show-controls.page.scss'],
|
||||
template: `
|
||||
<div class="flex justify-start">
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
class="mr-2"
|
||||
appearance="primary"
|
||||
*ngIf="localPkg"
|
||||
(click)="showService()"
|
||||
>
|
||||
View Installed
|
||||
</button>
|
||||
<ng-container *ngIf="localPkg; else install">
|
||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
class="mr-2"
|
||||
appearance="warning-solid"
|
||||
*ngIf="(localVersion | compareEmver : pkg.manifest.version) === -1"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
class="mr-2"
|
||||
appearance="secondary-solid"
|
||||
*ngIf="(localVersion | compareEmver : pkg.manifest.version) === 1"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Downgrade
|
||||
</button>
|
||||
<ng-container *ngIf="showDevTools$ | async">
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
class="mr-2"
|
||||
appearance="tertiary-solid"
|
||||
*ngIf="(localVersion | compareEmver : pkg.manifest.version) === 0"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Reinstall
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #install>
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
appearance="primary-solid"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
button {
|
||||
--tui-padding: 1.5rem;
|
||||
}
|
||||
`,
|
||||
],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule,
|
||||
TextSpinnerComponentModule,
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
AboutModule,
|
||||
DependenciesModule,
|
||||
AdditionalModule,
|
||||
TuiButtonModule,
|
||||
],
|
||||
})
|
||||
export class MarketplaceShowControlsComponent {
|
||||
@Input()
|
||||
@@ -47,8 +143,9 @@ export class MarketplaceShowControlsComponent {
|
||||
localPkg!: PackageDataEntry | null
|
||||
|
||||
readonly showDevTools$ = this.ClientStorageService.showDevTools$
|
||||
|
||||
readonly PackageState = PackageState
|
||||
private readonly router = inject(Router)
|
||||
readonly sidebarService = inject(SidebarService)
|
||||
|
||||
constructor(
|
||||
private readonly dialogs: TuiDialogService,
|
||||
@@ -66,6 +163,7 @@ export class MarketplaceShowControlsComponent {
|
||||
}
|
||||
|
||||
async tryInstall() {
|
||||
this.sidebarService.toggleState(this.pkg.manifest.id, false)
|
||||
const currentMarketplace = await firstValueFrom(
|
||||
this.marketplaceService.getSelectedHost$(),
|
||||
)
|
||||
@@ -96,6 +194,13 @@ export class MarketplaceShowControlsComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async showService() {
|
||||
this.sidebarService.toggleState(this.pkg.manifest.id, false)
|
||||
// @TODO code smell - needed to close preview - likely due to sidebar animation
|
||||
await pauseFor(300)
|
||||
this.router.navigate(['/services', this.pkg.manifest.id])
|
||||
}
|
||||
|
||||
private async presentAlertDifferentMarketplace(
|
||||
url: string,
|
||||
originalUrl: string | null | undefined,
|
||||
@@ -150,7 +255,7 @@ export class MarketplaceShowControlsComponent {
|
||||
of(this.pkg.manifest.alerts.install)
|
||||
.pipe(
|
||||
switchMap(content =>
|
||||
content
|
||||
!content
|
||||
? of(true)
|
||||
: this.dialogs.open<boolean>(TUI_PROMPT, {
|
||||
label: 'Alert',
|
||||
@@ -0,0 +1,47 @@
|
||||
<div class="grid gap-8 p-7 justify-center">
|
||||
<!-- close button -->
|
||||
<ng-content select="[slot=close]"></ng-content>
|
||||
<marketplace-package-hero [pkg]="pkg">
|
||||
<!-- control buttons -->
|
||||
<ng-content select="[slot=controls]"></ng-content>
|
||||
</marketplace-package-hero>
|
||||
<a
|
||||
*ngIf="url$ | async as url"
|
||||
href="{{ url + '/marketplace/' + pkg.manifest.id }}"
|
||||
tuiButton
|
||||
appearance="tertiary-solid"
|
||||
type="button"
|
||||
class="tui-space_right-3 tui-space_bottom-3"
|
||||
iconRight="tuiIconExternalLink"
|
||||
target="_blank"
|
||||
style="margin: 0"
|
||||
>
|
||||
View more details
|
||||
</a>
|
||||
<div class="grid grid-cols-1 gap-x-8">
|
||||
<marketplace-about [pkg]="pkg"></marketplace-about>
|
||||
<div
|
||||
*ngIf="!(pkg.manifest.dependencies | empty)"
|
||||
class="rounded-xl bg-gradient-to-bl from-zinc-400/75 to-zinc-600 p-px shadow-lg shadow-zinc-400/10 mt-6"
|
||||
>
|
||||
<div class="lg:col-span-5 xl:col-span-4 bg-zinc-800 rounded-xl p-7">
|
||||
<h2 class="text-lg font-bold small-caps my-2 pb-3">Dependencies</h2>
|
||||
<div class="grid grid-row-auto gap-3">
|
||||
<div *ngFor="let dep of pkg.manifest.dependencies | keyvalue">
|
||||
<marketplace-dependencies
|
||||
[dep]="dep"
|
||||
[pkg]="pkg"
|
||||
(click)="sidebarService.toggleState(dep.key, true)"
|
||||
></marketplace-dependencies>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<release-notes [pkg]="pkg"></release-notes>
|
||||
<marketplace-additional
|
||||
class="mt-6"
|
||||
[pkg]="pkg"
|
||||
(version)="version$.next($event)"
|
||||
></marketplace-additional>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
inject,
|
||||
Input,
|
||||
} from '@angular/core'
|
||||
import { BehaviorSubject, map } from 'rxjs'
|
||||
import {
|
||||
TuiDialogContext,
|
||||
TuiDialogService,
|
||||
TuiDurationOptions,
|
||||
tuiFadeIn,
|
||||
} from '@taiga-ui/core'
|
||||
import { tuiPure } from '@taiga-ui/cdk'
|
||||
import { PolymorpheusContent } from '@tinkoff/ng-polymorpheus'
|
||||
import { isPlatform } from '@ionic/angular'
|
||||
import {
|
||||
AbstractMarketplaceService,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { SidebarService } from 'src/app/services/sidebar.service'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-preview',
|
||||
templateUrl: './marketplace-show-preview.component.html',
|
||||
styleUrls: ['./marketplace-show-preview.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [tuiFadeIn],
|
||||
})
|
||||
export class MarketplaceShowPreviewComponent {
|
||||
@Input({ required: true })
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
constructor(
|
||||
@Inject(TuiDialogService) private readonly dialogs: TuiDialogService,
|
||||
) {}
|
||||
|
||||
readonly sidebarService = inject(SidebarService)
|
||||
private readonly marketplaceService = inject(AbstractMarketplaceService)
|
||||
readonly version$ = new BehaviorSubject('*')
|
||||
index = 0
|
||||
speed = 1000
|
||||
isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android')
|
||||
url$ = this.marketplaceService.getSelectedHost$().pipe(map(({ url }) => url))
|
||||
|
||||
@tuiPure
|
||||
getAnimation(duration: number): TuiDurationOptions {
|
||||
return { value: '', params: { duration } }
|
||||
}
|
||||
|
||||
presentModalImg(content: PolymorpheusContent<TuiDialogContext>) {
|
||||
this.dialogs
|
||||
.open(content, {
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import {
|
||||
SharedPipesModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
MarketplacePackageHeroComponent,
|
||||
ReleaseNotesModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { MarketplaceShowPreviewComponent } from './marketplace-show-preview.component'
|
||||
|
||||
import { TuiButtonModule } from '@taiga-ui/core'
|
||||
import { RouterModule } from '@angular/router'
|
||||
|
||||
@NgModule({
|
||||
declarations: [MarketplaceShowPreviewComponent],
|
||||
exports: [MarketplaceShowPreviewComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedPipesModule,
|
||||
TextSpinnerComponentModule,
|
||||
RouterModule,
|
||||
DependenciesModule,
|
||||
AdditionalModule,
|
||||
ReleaseNotesModule,
|
||||
TuiButtonModule,
|
||||
AboutModule,
|
||||
MarketplacePackageHeroComponent,
|
||||
],
|
||||
})
|
||||
export class MarketplaceShowPreviewModule {}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import {
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component'
|
||||
import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component'
|
||||
import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
MarketplaceShowHeaderComponent,
|
||||
MarketplaceShowControlsComponent,
|
||||
MarketplaceShowDependentComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule,
|
||||
TextSpinnerComponentModule,
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
DependenciesModule,
|
||||
AdditionalModule,
|
||||
],
|
||||
exports: [
|
||||
MarketplaceShowHeaderComponent,
|
||||
MarketplaceShowControlsComponent,
|
||||
MarketplaceShowDependentComponent,
|
||||
],
|
||||
})
|
||||
export class MarketplaceShowComponentsModule {}
|
||||
@@ -1,46 +0,0 @@
|
||||
<div class="action-buttons">
|
||||
<ion-button
|
||||
*ngIf="localPkg"
|
||||
expand="block"
|
||||
color="primary"
|
||||
[routerLink]="['/services', pkg.manifest.id]"
|
||||
>
|
||||
View Installed
|
||||
</ion-button>
|
||||
<ng-container *ngIf="localPkg; else install">
|
||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
||||
<ion-button
|
||||
*ngIf="(localVersion | compareEmver : pkg.manifest.version) === -1"
|
||||
expand="block"
|
||||
color="success"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Update
|
||||
</ion-button>
|
||||
<ion-button
|
||||
*ngIf="(localVersion | compareEmver : pkg.manifest.version) === 1"
|
||||
expand="block"
|
||||
color="warning"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Downgrade
|
||||
</ion-button>
|
||||
<ng-container *ngIf="showDevTools$ | async">
|
||||
<ion-button
|
||||
*ngIf="(localVersion | compareEmver : pkg.manifest.version) === 0"
|
||||
expand="block"
|
||||
color="success"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Reinstall
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #install>
|
||||
<ion-button expand="block" color="success" (click)="tryInstall()">
|
||||
Install
|
||||
</ion-button>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -1,19 +0,0 @@
|
||||
ion-button::part(native) {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
ion-button {
|
||||
height: 44px;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
ion-button {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<!-- auto-config -->
|
||||
<ion-item *ngIf="dependentInfo" lines="none" class="rec-item">
|
||||
<ion-label>
|
||||
<h2 class="heading">
|
||||
<ion-text class="montserrat title">
|
||||
{{ title }}
|
||||
</ion-text>
|
||||
</h2>
|
||||
<p>
|
||||
<ion-text color="dark">
|
||||
{{ dependentInfo.title }} requires an install of {{ title }} satisfying
|
||||
{{ dependentInfo.version }}.
|
||||
<br />
|
||||
<br />
|
||||
<span
|
||||
*ngIf="version | satisfiesEmver : dependentInfo.version"
|
||||
class="text"
|
||||
>
|
||||
{{ title }} version {{ version | displayEmver }} is compatible.
|
||||
</span>
|
||||
<span
|
||||
*ngIf="!(version | satisfiesEmver : dependentInfo.version)"
|
||||
class="text text_error"
|
||||
>
|
||||
{{ title }} version {{ version | displayEmver }} is NOT compatible.
|
||||
</span>
|
||||
</ion-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
@@ -1,17 +0,0 @@
|
||||
.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 5px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-style: italic;
|
||||
|
||||
&_error {
|
||||
color: var(--ion-color-danger);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
Input,
|
||||
} from '@angular/core'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-dependent',
|
||||
templateUrl: 'marketplace-show-dependent.component.html',
|
||||
styleUrls: ['marketplace-show-dependent.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceShowDependentComponent {
|
||||
@Input({ required: true })
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
readonly dependentInfo?: DependentInfo =
|
||||
this.document.defaultView?.history.state?.dependentInfo
|
||||
|
||||
constructor(@Inject(DOCUMENT) private readonly document: Document) {}
|
||||
|
||||
get title(): string {
|
||||
return this.pkg.manifest.title
|
||||
}
|
||||
|
||||
get version(): string {
|
||||
return this.pkg.manifest.version
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button defaultHref="marketplace"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Marketplace Listing</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
@@ -1,8 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-header',
|
||||
templateUrl: 'marketplace-show-header.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceShowHeaderComponent {}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import {
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
SharedPipesModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
PackageModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||
import { MarketplaceShowPage } from './marketplace-show.page'
|
||||
import { MarketplaceShowComponentsModule } from './components/marketplace-show-components.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MarketplaceShowPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TextSpinnerComponentModule,
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
MarketplaceStatusModule,
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
DependenciesModule,
|
||||
AdditionalModule,
|
||||
MarketplaceShowComponentsModule,
|
||||
],
|
||||
declarations: [MarketplaceShowPage],
|
||||
})
|
||||
export class MarketplaceShowPageModule {}
|
||||
@@ -1,50 +0,0 @@
|
||||
<marketplace-show-header></marketplace-show-header>
|
||||
|
||||
<ion-content class="ion-padding with-widgets">
|
||||
<ng-container *ngIf="pkg$ | async as pkg else loading">
|
||||
<ng-container *ngIf="pkg | empty; else show">
|
||||
<div
|
||||
*ngIf="loadVersion$ | async as version"
|
||||
class="ion-text-center"
|
||||
style="padding-top: 64px"
|
||||
>
|
||||
<ion-icon
|
||||
name="close-circle-outline"
|
||||
style="font-size: 48px"
|
||||
></ion-icon>
|
||||
<h2>
|
||||
{{ pkgId }} @{{ version === '*' ? 'latest' : version }} not found in
|
||||
this registry
|
||||
</h2>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #show>
|
||||
<marketplace-package [pkg]="pkg"></marketplace-package>
|
||||
<marketplace-show-controls
|
||||
[url]="url"
|
||||
[pkg]="pkg"
|
||||
[localPkg]="localPkg$ | async"
|
||||
></marketplace-show-controls>
|
||||
|
||||
<marketplace-show-dependent [pkg]="pkg"></marketplace-show-dependent>
|
||||
|
||||
<ion-item-group>
|
||||
<marketplace-about [pkg]="pkg"></marketplace-about>
|
||||
<marketplace-dependencies
|
||||
*ngIf="!(pkg.manifest.dependencies | empty)"
|
||||
[pkg]="pkg"
|
||||
></marketplace-dependencies>
|
||||
</ion-item-group>
|
||||
|
||||
<marketplace-additional
|
||||
[pkg]="pkg"
|
||||
(version)="loadVersion$.next($event)"
|
||||
></marketplace-additional>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #loading>
|
||||
<text-spinner text="Loading Package"></text-spinner>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
@@ -1,3 +0,0 @@
|
||||
.status {
|
||||
font-size: calc(16px + 1vw);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter, shareReplay, switchMap, BehaviorSubject } from 'rxjs'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show',
|
||||
templateUrl: './marketplace-show.page.html',
|
||||
styleUrls: ['./marketplace-show.page.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceShowPage {
|
||||
readonly pkgId = getPkgId(this.route)
|
||||
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
|
||||
|
||||
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
||||
|
||||
readonly localPkg$ = this.patch
|
||||
.watch$('package-data', this.pkgId)
|
||||
.pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true }))
|
||||
|
||||
readonly pkg$ = this.loadVersion$.pipe(
|
||||
switchMap(version =>
|
||||
this.marketplaceService.getPackage$(this.pkgId, version, this.url),
|
||||
),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly marketplaceService: AbstractMarketplaceService,
|
||||
) {}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -10,20 +10,6 @@ const routes: Routes = [
|
||||
m => m.MarketplaceListPageModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':pkgId',
|
||||
loadChildren: () =>
|
||||
import('./marketplace-show/marketplace-show.module').then(
|
||||
m => m.MarketplaceShowPageModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':pkgId/notes',
|
||||
loadChildren: () =>
|
||||
import('./release-notes/release-notes.module').then(
|
||||
m => m.ReleaseNotesPageModule,
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { ReleaseNotesModule } from '@start9labs/marketplace'
|
||||
|
||||
import { ReleaseNotesPage } from './release-notes.page'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ReleaseNotesPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [IonicModule, ReleaseNotesModule, RouterModule.forChild(routes)],
|
||||
declarations: [ReleaseNotesPage],
|
||||
exports: [ReleaseNotesPage],
|
||||
})
|
||||
export class ReleaseNotesPageModule {}
|
||||
@@ -1,10 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [defaultHref]="href"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Release Notes</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<release-notes></release-notes>
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
templateUrl: './release-notes.page.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ReleaseNotesPage {
|
||||
readonly href = `/marketplace/${getPkgId(this.route)}`
|
||||
|
||||
constructor(private readonly route: ActivatedRoute) {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
img {
|
||||
border-radius: 100%;
|
||||
}
|
||||
@@ -6,4 +6,4 @@
|
||||
font-weight: bold;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
.icon {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 100%;
|
||||
}
|
||||
@@ -11,3 +11,7 @@
|
||||
width: 54px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 100%;
|
||||
}
|
||||
@@ -77,7 +77,7 @@
|
||||
*ngIf="pkg.installed?.['marketplace-url'] as url; else sideloaded"
|
||||
button
|
||||
detail
|
||||
(click)="navigate('/marketplace/' + pkg.manifest.id, { url })"
|
||||
(click)="navigate('/marketplace', { url, id: pkg.manifest.id })"
|
||||
>
|
||||
<ion-icon slot="start" name="storefront-outline"></ion-icon>
|
||||
<ion-label>
|
||||
|
||||
@@ -2,17 +2,17 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { SideloadPage } from './sideload.page'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared'
|
||||
import { DragNDropDirective } from './dnd.directive'
|
||||
import {
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
MarketplacePackageHeroComponent,
|
||||
} from '@start9labs/marketplace'
|
||||
// TODO: Find a way to not tie two routes together
|
||||
import { MarketplaceShowComponentsModule } from '../../marketplace/marketplace-show/components/marketplace-show-components.module'
|
||||
import { MarketplaceShowControlsComponent } from '../../marketplace/marketplace-show-preview/components/marketplace-show-controls.component'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -28,11 +28,11 @@ const routes: Routes = [
|
||||
RouterModule.forChild(routes),
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
AdditionalModule,
|
||||
MarketplaceShowControlsComponent,
|
||||
DependenciesModule,
|
||||
MarketplaceShowComponentsModule,
|
||||
MarketplacePackageHeroComponent,
|
||||
],
|
||||
declarations: [SideloadPage, DragNDropDirective],
|
||||
})
|
||||
|
||||
@@ -30,17 +30,29 @@
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<marketplace-package [pkg]="pkg"></marketplace-package>
|
||||
<marketplace-show-controls [pkg]="pkg"></marketplace-show-controls>
|
||||
<marketplace-show-dependent [pkg]="pkg"></marketplace-show-dependent>
|
||||
<ion-item-group>
|
||||
<div class="grid gap-8 mb-16 p-4 lg:px-16 lg:pb-8 pt-14 justify-center">
|
||||
<marketplace-package-hero [pkg]="pkg">
|
||||
<marketplace-show-controls [pkg]="pkg"></marketplace-show-controls>
|
||||
</marketplace-package-hero>
|
||||
<marketplace-about [pkg]="pkg"></marketplace-about>
|
||||
<marketplace-dependencies
|
||||
<div
|
||||
*ngIf="!(pkg.manifest.dependencies | empty)"
|
||||
[pkg]="pkg"
|
||||
></marketplace-dependencies>
|
||||
</ion-item-group>
|
||||
<marketplace-additional [pkg]="pkg"></marketplace-additional>
|
||||
class="rounded-xl bg-gradient-to-bl from-zinc-400/75 to-zinc-600 p-px shadow-lg shadow-zinc-400/10 mt-6"
|
||||
>
|
||||
<div class="lg:col-span-5 xl:col-span-4 bg-zinc-800 rounded-xl p-7">
|
||||
<h2 class="text-lg font-bold small-caps my-2 pb-3">Dependencies</h2>
|
||||
<div class="grid grid-row-auto gap-3">
|
||||
<div *ngFor="let dep of pkg.manifest.dependencies | keyvalue">
|
||||
<marketplace-dependencies
|
||||
[dep]="dep"
|
||||
[pkg]="pkg"
|
||||
></marketplace-dependencies>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<marketplace-additional [pkg]="pkg"></marketplace-additional>
|
||||
</div>
|
||||
</div>
|
||||
<!-- empty -->
|
||||
<ng-template #empty>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ion-content class="with-widgets">
|
||||
<ion-item-group *ngIf="data$ | async as data">
|
||||
<div *ngFor="let host of data.hosts">
|
||||
<ion-item-divider class="inline">
|
||||
<ion-item-divider>
|
||||
<store-icon
|
||||
[url]="host.url"
|
||||
[marketplace]="config.marketplace"
|
||||
|
||||
33
web/projects/ui/src/app/route-animation.ts
Normal file
33
web/projects/ui/src/app/route-animation.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
animate,
|
||||
group,
|
||||
query,
|
||||
style,
|
||||
transition,
|
||||
trigger,
|
||||
} from '@angular/animations'
|
||||
export const slideInAnimation = trigger('routeAnimations', [
|
||||
transition('* => *', [
|
||||
query(':enter, :leave', style({ position: 'fixed', width: '100%' }), {
|
||||
optional: true,
|
||||
}),
|
||||
group([
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate('1s ease-in-out', style({ transform: 'translateX(0%)' })),
|
||||
],
|
||||
{ optional: true },
|
||||
),
|
||||
query(
|
||||
':leave',
|
||||
[
|
||||
style({ transform: 'translateX(0%)' }),
|
||||
animate('1s ease-in-out', style({ transform: 'translateX(100%)' })),
|
||||
],
|
||||
{ optional: true },
|
||||
),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
@@ -6,17 +6,17 @@ import {
|
||||
ServerStatusInfo,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
RR,
|
||||
NotificationLevel,
|
||||
ServerNotifications,
|
||||
Metrics,
|
||||
NotificationLevel,
|
||||
RR,
|
||||
ServerNotifications,
|
||||
} from './api.types'
|
||||
|
||||
import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons'
|
||||
import {
|
||||
DependencyMetadata,
|
||||
MarketplacePkg,
|
||||
Manifest,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { Log } from '@start9labs/shared'
|
||||
import { unionSelectKey } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||
@@ -102,7 +102,8 @@ export module Mock {
|
||||
assets: {
|
||||
icon: 'icon.png',
|
||||
},
|
||||
'release-notes': 'Dual funded channels!',
|
||||
'release-notes':
|
||||
'Dual funded channels! And lots more amazing new features. Also includes several bugfixes and performance enhancements.',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper',
|
||||
'upstream-repo': 'https://github.com/lightningnetwork/lnd',
|
||||
@@ -203,7 +204,7 @@ export module Mock {
|
||||
...Mock.MockManifestBitcoind,
|
||||
version: '0.19.0',
|
||||
},
|
||||
categories: ['bitcoin', 'cryptocurrency'],
|
||||
categories: ['bitcoin', 'cryptocurrency', 'featured'],
|
||||
versions: ['0.19.0', '0.20.0', '0.21.0'],
|
||||
'dependency-metadata': {},
|
||||
'published-at': new Date().toISOString(),
|
||||
@@ -240,6 +241,7 @@ export module Mock {
|
||||
icon: BTC_ICON,
|
||||
license: 'licenseUrl',
|
||||
instructions: 'instructionsUrl',
|
||||
screenshots: ['one.png', 'two.png', 'three.png'],
|
||||
manifest: {
|
||||
...Mock.MockManifestBitcoind,
|
||||
'release-notes':
|
||||
|
||||
26
web/projects/ui/src/app/services/category.service.ts
Normal file
26
web/projects/ui/src/app/services/category.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { AbstractCategoryService } from '@start9labs/marketplace'
|
||||
|
||||
@Injectable()
|
||||
export class CategoryService extends AbstractCategoryService {
|
||||
getCategory$(): Observable<string> {
|
||||
return this.category$
|
||||
}
|
||||
|
||||
changeCategory(category: string) {
|
||||
this.category$.next(category)
|
||||
}
|
||||
|
||||
setQuery(query: string) {
|
||||
this.query$.next(query)
|
||||
}
|
||||
|
||||
getQuery$(): Observable<string> {
|
||||
return this.query$
|
||||
}
|
||||
|
||||
resetQuery() {
|
||||
this.query$.next('')
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,28 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { sameUrl } from '@start9labs/shared'
|
||||
import {
|
||||
MarketplacePkg,
|
||||
AbstractMarketplaceService,
|
||||
StoreData,
|
||||
Marketplace,
|
||||
StoreInfo,
|
||||
MarketplacePkg,
|
||||
StoreData,
|
||||
StoreIdentity,
|
||||
StoreIdentityWithData,
|
||||
StoreInfo,
|
||||
} from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
catchError,
|
||||
combineLatest,
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
from,
|
||||
map,
|
||||
mergeMap,
|
||||
Observable,
|
||||
of,
|
||||
scan,
|
||||
catchError,
|
||||
filter,
|
||||
map,
|
||||
pairwise,
|
||||
scan,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
@@ -162,6 +163,32 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
||||
return this.selectedStore$
|
||||
}
|
||||
|
||||
getSelectedStoreWithCategories$() {
|
||||
return this.selectedHost$.pipe(
|
||||
switchMap(({ url }) =>
|
||||
this.marketplace$.pipe(
|
||||
map(m => m[url]),
|
||||
filter(Boolean),
|
||||
map(({ info, packages }) => {
|
||||
const categories = new Set<string>()
|
||||
if (info.categories.includes('featured')) categories.add('featured')
|
||||
categories.add('all')
|
||||
info.categories.forEach(c => categories.add(c))
|
||||
|
||||
return {
|
||||
url,
|
||||
info: {
|
||||
...info,
|
||||
categories: Array.from(categories),
|
||||
},
|
||||
packages,
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
getPackage$(
|
||||
id: string,
|
||||
version: string,
|
||||
|
||||
21
web/projects/ui/src/app/services/sidebar.service.ts
Normal file
21
web/projects/ui/src/app/services/sidebar.service.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { BehaviorSubject, Observable } from 'rxjs'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SidebarService {
|
||||
openMap: Record<string, BehaviorSubject<boolean>> = {}
|
||||
|
||||
setMap(ids: string[]) {
|
||||
ids.map(i => (this.openMap[i] = new BehaviorSubject(false)))
|
||||
}
|
||||
|
||||
getToggleState(pkgId: string): Observable<boolean> {
|
||||
return this.openMap[pkgId]
|
||||
}
|
||||
|
||||
toggleState(pkgId: string, open: boolean) {
|
||||
this.openMap[pkgId].next(open)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user