feat(marketplace): extract common components (#1338)

* feat(marketplace): extract common components

* chore: fix service provide

* feat(markdown): allow Observable content

* chore: remove unnecessary module import

* minor styling for marketplacee list

* only show loading for marketplace show if version change

* chore: get rid of unnecessary server request

* chore: fix version switching

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
Alex Inkin
2022-03-21 22:50:06 +03:00
committed by GitHub
parent 8b286431e6
commit 7ea3aefdd5
111 changed files with 1064 additions and 803 deletions

View File

@@ -0,0 +1,9 @@
<ion-button
*ngFor="let cat of categories"
fill="clear"
class="category"
[class.category_selected]="cat === category"
(click)="switchCategory(cat)"
>
{{ cat }}
</ion-button>

View File

@@ -0,0 +1,14 @@
:host {
display: block;
}
.category {
font-weight: 300;
color: var(--ion-color-dark-shade);
&_selected {
font-weight: bold;
font-size: 17px;
color: var(--color);
}
}

View File

@@ -0,0 +1,32 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
} from '@angular/core'
@Component({
selector: 'marketplace-categories',
templateUrl: 'categories.component.html',
styleUrls: ['categories.component.scss'],
host: {
class: 'hidden-scrollbar ion-text-center',
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CategoriesComponent {
@Input()
categories = new Set<string>()
@Input()
category = ''
@Output()
readonly categoryChange = new EventEmitter<string>()
switchCategory(category: string): void {
this.category = category
this.categoryChange.emit(category)
}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { CategoriesComponent } from './categories.component'
@NgModule({
imports: [CommonModule, IonicModule],
declarations: [CategoriesComponent],
exports: [CategoriesComponent],
})
export class CategoriesModule {}

View File

@@ -0,0 +1,10 @@
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
<ion-thumbnail slot="start">
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2 class="pkg-title">{{ pkg.manifest.title }}</h2>
<h3>{{ pkg.manifest.description.short }}</h3>
<ng-content></ng-content>
</ion-label>
</ion-item>

View File

@@ -0,0 +1,4 @@
.pkg-title {
font-family: 'Montserrat', sans-serif;
font-weight: bold;
}

View File

@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types/marketplace-pkg'
@Component({
selector: 'marketplace-item',
templateUrl: 'item.component.html',
styleUrls: ['item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ItemComponent {
@Input()
pkg: MarketplacePkg
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharedPipesModule } from '@start9labs/shared'
import { ItemComponent } from './item.component'
@NgModule({
imports: [IonicModule, RouterModule, SharedPipesModule],
declarations: [ItemComponent],
exports: [ItemComponent],
})
export class ItemModule {}

View File

@@ -0,0 +1,15 @@
<ion-grid>
<ion-row>
<ion-col sizeSm="8" offset-sm="2">
<ion-toolbar color="transparent">
<ion-searchbar
enterkeyhint="search"
color="dark"
debounce="250"
[ngModel]="query"
(ngModelChange)="onModelChange($event)"
></ion-searchbar>
</ion-toolbar>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,4 @@
:host {
display: block;
padding-bottom: 32px;
}

View File

@@ -0,0 +1,26 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
} from '@angular/core'
@Component({
selector: 'marketplace-search',
templateUrl: 'search.component.html',
styleUrls: ['search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchComponent {
@Input()
query = ''
@Output()
readonly queryChange = new EventEmitter<string>()
onModelChange(query: string) {
this.query = query
this.queryChange.emit(query)
}
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
import { SearchComponent } from './search.component'
@NgModule({
imports: [IonicModule, FormsModule],
declarations: [SearchComponent],
exports: [SearchComponent],
})
export class SearchModule {}

View File

@@ -0,0 +1,38 @@
<div class="hidden-scrollbar ion-text-center">
<ion-button *ngFor="let cat of ['', '', '', '', '', '', '']" fill="clear">
<ion-skeleton-text
animated
style="width: 80px; border-radius: 0"
></ion-skeleton-text>
</ion-button>
</div>
<div class="divider" style="margin: 24px 0"></div>
<ion-grid>
<ion-row>
<ion-col
*ngFor="let pkg of ['', '', '', '']"
sizeXs="12"
sizeSm="12"
sizeMd="6"
>
<ion-item>
<ion-thumbnail slot="start">
<ion-skeleton-text
style="border-radius: 100%"
animated
></ion-skeleton-text>
</ion-thumbnail>
<ion-label>
<ion-skeleton-text
animated
style="width: 150px; height: 18px; margin-bottom: 8px"
></ion-skeleton-text>
<ion-skeleton-text animated style="width: 400px"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 100px"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
@Component({
selector: 'marketplace-skeleton',
templateUrl: 'skeleton.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SkeletonComponent {}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { SkeletonComponent } from './skeleton.component'
@NgModule({
imports: [CommonModule, IonicModule],
declarations: [SkeletonComponent],
exports: [SkeletonComponent],
})
export class SkeletonModule {}

View File

@@ -0,0 +1,32 @@
<ion-content>
<ng-container *ngIf="notes$ | async as notes; else loading">
<div *ngFor="let note of notes | keyvalue: asIsOrder">
<ion-button
expand="full"
color="light"
class="version-button"
[class.ion-activated]="isSelected(note.key)"
(click)="setSelected(note.key)"
>
<p class="version">{{ note.key | displayEmver }}</p>
</ion-button>
<ion-card
elementRef
#element="elementRef"
class="panel"
color="light"
[id]="note.key"
[style.maxHeight.px]="getDocSize(note.key, element)"
>
<ion-text
id="release-notes"
[innerHTML]="note.value | markdown"
></ion-text>
</ion-card>
</div>
</ng-container>
<ng-template #loading>
<text-spinner text="Loading Release Notes"></text-spinner>
</ng-template>
</ion-content>

View File

@@ -0,0 +1,23 @@
:host {
flex: 1 1 0;
}
.panel {
margin: 0;
padding: 0 24px;
transition: max-height 0.2s ease-out;
}
.active {
border: 5px solid #4d4d4d;
}
.version-button {
height: 50px;
margin: 1px;
}
.version {
position: absolute;
left: 10px;
}

View File

@@ -0,0 +1,38 @@
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { AbstractMarketplaceService } from '../../services/marketplace.service'
@Component({
selector: 'release-notes',
templateUrl: './release-notes.component.html',
styleUrls: ['./release-notes.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReleaseNotesComponent {
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
private selected: string | null = null
readonly notes$ = this.marketplaceService.getReleaseNotes(this.pkgId)
constructor(
private readonly route: ActivatedRoute,
private readonly marketplaceService: AbstractMarketplaceService,
) {}
isSelected(key: string): boolean {
return this.selected === key
}
setSelected(selected: string) {
this.selected = this.isSelected(selected) ? null : selected
}
getDocSize(key: string, { nativeElement }: ElementRef<HTMLElement>) {
return this.isSelected(key) ? nativeElement.scrollHeight : 0
}
asIsOrder(a: any, b: any) {
return 0
}
}

View File

@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import {
EmverPipesModule,
MarkdownPipeModule,
TextSpinnerComponentModule,
ElementModule,
} from '@start9labs/shared'
import { ReleaseNotesComponent } from './release-notes.component'
@NgModule({
imports: [
CommonModule,
IonicModule,
TextSpinnerComponentModule,
EmverPipesModule,
MarkdownPipeModule,
ElementModule,
],
declarations: [ReleaseNotesComponent],
exports: [ReleaseNotesComponent],
})
export class ReleaseNotesModule {}

View File

@@ -0,0 +1,23 @@
<!-- release notes -->
<ion-item-divider>
New in {{ pkg.manifest.version | displayEmver }}
<ion-button routerLink="notes" class="all-notes" fill="clear" color="dark">
All Release Notes
<ion-icon slot="end" name="arrow-forward-outline"></ion-icon>
</ion-button>
</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div
class="release-notes"
[innerHTML]="pkg.manifest['release-notes'] | markdown"
></div>
</ion-label>
</ion-item>
<!-- description -->
<ion-item-divider>Description</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div class="release-notes">{{ pkg.manifest.description.long }}</div>
</ion-label>
</ion-item>

View File

@@ -0,0 +1,9 @@
.all-notes {
position: absolute;
right: 10px;
}
.release-notes {
overflow: auto;
max-height: 120px;
}

View File

@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types/marketplace-pkg'
@Component({
selector: 'marketplace-about',
templateUrl: 'about.component.html',
styleUrls: ['about.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AboutComponent {
@Input()
pkg: MarketplacePkg
}

View File

@@ -0,0 +1,20 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { EmverPipesModule, MarkdownPipeModule } from '@start9labs/shared'
import { AboutComponent } from './about.component'
@NgModule({
imports: [
CommonModule,
RouterModule,
IonicModule,
MarkdownPipeModule,
EmverPipesModule,
],
declarations: [AboutComponent],
exports: [AboutComponent],
})
export class AboutModule {}

View File

@@ -0,0 +1,76 @@
<ion-item-divider>Additional Info</ion-item-divider>
<ion-card>
<ion-grid>
<ion-row>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item button detail="false" (click)="presentAlertVersions()">
<ion-label>
<h2>Other Versions</h2>
<p>Click to view other versions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('license')">
<ion-label>
<h2>License</h2>
<p>{{ pkg.manifest.license }}</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item
button
detail="false"
(click)="presentModalMd('instructions')"
>
<ion-label>
<h2>Instructions</h2>
<p>Click to view instructions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item
[href]="pkg.manifest['upstream-repo']"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Source Repository</h2>
<p>{{ pkg.manifest['upstream-repo'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item
[href]="pkg.manifest['wrapper-repo']"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Wrapper Repository</h2>
<p>{{ pkg.manifest['wrapper-repo'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item
[href]="pkg.manifest['support-site']"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Support Site</h2>
<p>{{ pkg.manifest['support-site'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
</ion-row>
</ion-grid>
</ion-card>

View File

@@ -0,0 +1,74 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
} from '@angular/core'
import { AlertController, ModalController } from '@ionic/angular'
import { displayEmver, Emver, MarkdownComponent } from '@start9labs/shared'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
import { MarketplacePkg } from '../../../types/marketplace-pkg'
@Component({
selector: 'marketplace-additional',
templateUrl: 'additional.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdditionalComponent {
@Input()
pkg: MarketplacePkg
@Output()
version = new EventEmitter<string>()
constructor(
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
private readonly emver: Emver,
private readonly marketplaceService: AbstractMarketplaceService,
) {}
async presentAlertVersions() {
const alert = await this.alertCtrl.create({
header: 'Versions',
inputs: this.pkg.versions
.sort((a, b) => -1 * this.emver.compare(a, b))
.map(v => ({
name: v, // for CSS
type: 'radio',
label: displayEmver(v), // appearance on screen
value: v, // literal SEM version value
checked: this.pkg.manifest.version === v,
})),
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Ok',
handler: (version: string) => this.version.emit(version),
cssClass: 'enter-click',
},
],
})
await alert.present()
}
async presentModalMd(title: string) {
const content = this.marketplaceService.getPackageMarkdown(
title,
this.pkg.manifest.id,
)
const modal = await this.modalCtrl.create({
componentProps: { title, content },
component: MarkdownComponent,
})
await modal.present()
}
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { MarkdownModule } from '@start9labs/shared'
import { AdditionalComponent } from './additional.component'
@NgModule({
imports: [IonicModule, MarkdownModule],
declarations: [AdditionalComponent],
exports: [AdditionalComponent],
})
export class AdditionalModule {}

View File

@@ -0,0 +1,30 @@
<ion-item-divider>Dependencies</ion-item-divider>
<ion-grid>
<ion-row>
<ion-col
*ngFor="let dep of pkg.manifest.dependencies | keyvalue"
sizeSm="12"
sizeMd="6"
>
<ion-item [routerLink]="['/marketplace', dep.key]">
<ion-thumbnail slot="start">
<img alt="" [src]="getImg(dep.key) | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2>
{{ pkg['dependency-metadata'][dep.key].title }}
<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>
</h2>
<p>
<small>{{ dep.value.version | displayEmver }}</small>
</p>
<p>{{ dep.value.description }}</p>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,17 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types/marketplace-pkg'
@Component({
selector: 'marketplace-dependencies',
templateUrl: 'dependencies.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DependenciesComponent {
@Input()
pkg: MarketplacePkg
getImg(key: string): string {
return 'data:image/png;base64,' + this.pkg['dependency-metadata'][key].icon
}
}

View File

@@ -0,0 +1,20 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared'
import { DependenciesComponent } from './dependencies.component'
@NgModule({
imports: [
CommonModule,
RouterModule,
IonicModule,
SharedPipesModule,
EmverPipesModule,
],
declarations: [DependenciesComponent],
exports: [DependenciesComponent],
})
export class DependenciesModule {}

View File

@@ -0,0 +1,29 @@
<ion-grid>
<ion-row>
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
<div class="header">
<img
class="logo"
alt=""
[src]="'data:image/png;base64,' + pkg.icon | trustUrl"
/>
<div class="text">
<h1 class="title">{{ pkg.manifest.title }}</h1>
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
<ng-content></ng-content>
</div>
</div>
</ion-col>
<ion-col
sizeXl="3"
sizeLg="3"
sizeMd="3"
sizeSm="12"
sizeXs="12"
class="ion-align-self-center"
>
<ng-content select="[slot='controls']"></ng-content>
</ion-col>
</ion-row>
<ng-content select="ion-row"></ng-content>
</ion-grid>

View File

@@ -0,0 +1,26 @@
.header {
font-family: 'Montserrat', sans-serif;
padding: 2%;
}
.logo {
min-width: 15%;
max-width: 18%;
}
.text {
margin-left: 5%;
display: inline-block;
vertical-align: top;
}
.title {
margin: 0 0 0 -2px;
font-size: calc(20px + 3vw);
}
.version {
padding: 4px 0 12px 0;
margin: 0;
font-size: calc(10px + 1vw);
}

View File

@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types/marketplace-pkg'
@Component({
selector: 'marketplace-package',
templateUrl: 'package.component.html',
styleUrls: ['package.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PackageComponent {
@Input()
pkg: MarketplacePkg
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared'
import { PackageComponent } from './package.component'
@NgModule({
imports: [IonicModule, SharedPipesModule, EmverPipesModule],
declarations: [PackageComponent],
exports: [PackageComponent],
})
export class PackageModule {}

View File

@@ -1,8 +1,8 @@
import { Pipe, PipeTransform } from '@angular/core'
import { NgModule, Pipe, PipeTransform } from '@angular/core'
import Fuse from 'fuse.js/dist/fuse.min.js'
import { LocalPkg } from '../types/local-pkg'
import { MarketplacePkg } from '../types/marketplace-pkg'
import { MarketplaceManifest } from '../types/marketplace-manifest'
const defaultOps = {
isCaseSensitive: false,
@@ -31,9 +31,9 @@ const defaultOps = {
export class FilterPackagesPipe implements PipeTransform {
transform(
packages: MarketplacePkg[] | null,
local: Record<string, LocalPkg>,
query: string,
category: string,
local: Record<string, { manifest: MarketplaceManifest }> = {},
): MarketplacePkg[] | null {
if (!packages) {
return null
@@ -63,3 +63,9 @@ export class FilterPackagesPipe implements PipeTransform {
.map(p => p.item)
}
}
@NgModule({
declarations: [FilterPackagesPipe],
exports: [FilterPackagesPipe],
})
export class FilterPackagesPipeModule {}

View File

@@ -1,13 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core'
import { InstallProgress, packageLoadingProgress } from '@start9labs/shared'
@Pipe({
name: 'installProgress',
})
export class InstallProgressPipe implements PipeTransform {
transform(loadData: InstallProgress): string {
const { totalProgress } = packageLoadingProgress(loadData)
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
}
}

View File

@@ -1,10 +0,0 @@
import { NgModule } from '@angular/core'
import { InstallProgressPipe } from './install-progress.pipe'
import { TrustPipe } from './trust.pipe'
import { FilterPackagesPipe } from './filter-packages.pipe'
@NgModule({
declarations: [InstallProgressPipe, TrustPipe, FilterPackagesPipe],
exports: [InstallProgressPipe, TrustPipe, FilterPackagesPipe],
})
export class MarketplacePipesModule {}

View File

@@ -1,13 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
@Pipe({
name: 'trust',
})
export class TrustPipe implements PipeTransform {
constructor(public readonly sanitizer: DomSanitizer) {}
transform(base64Icon: string): SafeResourceUrl {
return this.sanitizer.bypassSecurityTrustResourceUrl(base64Icon)
}
}

View File

@@ -2,16 +2,31 @@
* Public API Surface of @start9labs/marketplace
*/
export * from './pipes/install-progress.pipe'
export * from './pipes/trust.pipe'
export * from './pipes/marketplace-pipes.module'
export * from './pages/list/categories/categories.component'
export * from './pages/list/categories/categories.module'
export * from './pages/list/item/item.component'
export * from './pages/list/item/item.module'
export * from './pages/list/search/search.component'
export * from './pages/list/search/search.module'
export * from './pages/list/skeleton/skeleton.component'
export * from './pages/list/skeleton/skeleton.module'
export * from './pages/release-notes/release-notes.component'
export * from './pages/release-notes/release-notes.module'
export * from './pages/show/about/about.component'
export * from './pages/show/about/about.module'
export * from './pages/show/additional/additional.component'
export * from './pages/show/additional/additional.module'
export * from './pages/show/dependencies/dependencies.component'
export * from './pages/show/dependencies/dependencies.module'
export * from './pages/show/package/package.component'
export * from './pages/show/package/package.module'
export * from './pipes/filter-packages.pipe'
export * from './services/marketplace.service'
export * from './types/local-pkg'
export * from './types/dependency'
export * from './types/marketplace'
export * from './types/marketplace-data'
export * from './types/marketplace-manifest'
export * from './types/marketplace-pkg'
export * from './utils/spread-progress'

View File

@@ -13,5 +13,7 @@ export abstract class AbstractMarketplaceService {
abstract getPackages(): Observable<MarketplacePkg[]>
abstract getPackageMarkdown(type: string, pkgId: string): Observable<string>
abstract getPackage(id: string, version: string): Observable<MarketplacePkg>
}

View File

@@ -0,0 +1,17 @@
export interface Dependency<T> {
version: string
requirement:
| {
type: 'opt-in'
how: string
}
| {
type: 'opt-out'
how: string
}
| {
type: 'required'
}
description: string | null
config: T
}

View File

@@ -1,8 +0,0 @@
import { PackageState } from '@start9labs/shared'
import { MarketplaceManifest } from './marketplace-manifest'
export interface LocalPkg {
state: PackageState
manifest: MarketplaceManifest
}

View File

@@ -1,6 +1,8 @@
import { Url } from '@start9labs/shared'
export interface MarketplaceManifest {
import { Dependency } from './dependency'
export interface MarketplaceManifest<T = unknown> {
id: string
title: string
version: string
@@ -22,4 +24,5 @@ export interface MarketplaceManifest {
start: string | null
stop: string | null
}
dependencies: Record<string, Dependency<T>>
}

View File

@@ -1,5 +0,0 @@
import { LocalPkg } from '../types/local-pkg'
export function spreadProgress(pkg: LocalPkg) {
pkg['install-progress'] = { ...pkg['install-progress'] }
}