mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
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:
@@ -8,9 +8,8 @@ import { HttpClientModule } from '@angular/common/http'
|
|||||||
import { ApiService } from './services/api/api.service'
|
import { ApiService } from './services/api/api.service'
|
||||||
import { MockApiService } from './services/api/mock-api.service'
|
import { MockApiService } from './services/api/mock-api.service'
|
||||||
import { LiveApiService } from './services/api/live-api.service'
|
import { LiveApiService } from './services/api/live-api.service'
|
||||||
import { HttpService } from './services/http.service'
|
|
||||||
import { GlobalErrorHandler } from './services/global-error-handler.service'
|
import { GlobalErrorHandler } from './services/global-error-handler.service'
|
||||||
import { WorkspaceConfig } from '@start9labs/shared'
|
import { AbstractApiService, WorkspaceConfig } from '@start9labs/shared'
|
||||||
|
|
||||||
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||||
|
|
||||||
@@ -29,14 +28,11 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
|||||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||||
{
|
{
|
||||||
provide: ApiService,
|
provide: ApiService,
|
||||||
useFactory: (http: HttpService) => {
|
useClass: useMocks ? MockApiService : LiveApiService,
|
||||||
if (useMocks) {
|
},
|
||||||
return new MockApiService()
|
{
|
||||||
} else {
|
provide: AbstractApiService,
|
||||||
return new LiveApiService(http)
|
useExisting: ApiService,
|
||||||
}
|
|
||||||
},
|
|
||||||
deps: [HttpService],
|
|
||||||
},
|
},
|
||||||
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
|
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.pkg-title {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'marketplace-skeleton',
|
||||||
|
templateUrl: 'skeleton.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class SkeletonComponent {}
|
||||||
@@ -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 {}
|
||||||
@@ -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>
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
:host {
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { MarkdownPage } from './markdown.page'
|
|
||||||
import {
|
import {
|
||||||
|
EmverPipesModule,
|
||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
|
ElementModule,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
|
|
||||||
|
import { ReleaseNotesComponent } from './release-notes.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [MarkdownPage],
|
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
IonicModule,
|
IonicModule,
|
||||||
MarkdownPipeModule,
|
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
|
EmverPipesModule,
|
||||||
|
MarkdownPipeModule,
|
||||||
|
ElementModule,
|
||||||
],
|
],
|
||||||
exports: [MarkdownPage],
|
declarations: [ReleaseNotesComponent],
|
||||||
|
exports: [ReleaseNotesComponent],
|
||||||
})
|
})
|
||||||
export class MarkdownPageModule {}
|
export class ReleaseNotesModule {}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -6,17 +6,17 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { AlertController, ModalController } from '@ionic/angular'
|
import { AlertController, ModalController } from '@ionic/angular'
|
||||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
import { displayEmver, Emver, MarkdownComponent } from '@start9labs/shared'
|
||||||
import { displayEmver, Emver } from '@start9labs/shared'
|
|
||||||
|
|
||||||
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
|
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
|
||||||
|
import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-show-additional',
|
selector: 'marketplace-additional',
|
||||||
templateUrl: 'marketplace-show-additional.component.html',
|
templateUrl: 'additional.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class MarketplaceShowAdditionalComponent {
|
export class AdditionalComponent {
|
||||||
@Input()
|
@Input()
|
||||||
pkg: MarketplacePkg
|
pkg: MarketplacePkg
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ export class MarketplaceShowAdditionalComponent {
|
|||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly emver: Emver,
|
private readonly emver: Emver,
|
||||||
|
private readonly marketplaceService: AbstractMarketplaceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async presentAlertVersions() {
|
async presentAlertVersions() {
|
||||||
@@ -58,12 +59,14 @@ export class MarketplaceShowAdditionalComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async presentModalMd(title: string) {
|
async presentModalMd(title: string) {
|
||||||
|
const content = this.marketplaceService.getPackageMarkdown(
|
||||||
|
title,
|
||||||
|
this.pkg.manifest.id,
|
||||||
|
)
|
||||||
|
|
||||||
const modal = await this.modalCtrl.create({
|
const modal = await this.modalCtrl.create({
|
||||||
componentProps: {
|
componentProps: { title, content },
|
||||||
title,
|
component: MarkdownComponent,
|
||||||
contentUrl: `/marketplace${this.pkg[title]}`,
|
|
||||||
},
|
|
||||||
component: MarkdownPage,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await modal.present()
|
await modal.present()
|
||||||
@@ -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 {}
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -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 Fuse from 'fuse.js/dist/fuse.min.js'
|
||||||
|
|
||||||
import { LocalPkg } from '../types/local-pkg'
|
|
||||||
import { MarketplacePkg } from '../types/marketplace-pkg'
|
import { MarketplacePkg } from '../types/marketplace-pkg'
|
||||||
|
import { MarketplaceManifest } from '../types/marketplace-manifest'
|
||||||
|
|
||||||
const defaultOps = {
|
const defaultOps = {
|
||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
@@ -31,9 +31,9 @@ const defaultOps = {
|
|||||||
export class FilterPackagesPipe implements PipeTransform {
|
export class FilterPackagesPipe implements PipeTransform {
|
||||||
transform(
|
transform(
|
||||||
packages: MarketplacePkg[] | null,
|
packages: MarketplacePkg[] | null,
|
||||||
local: Record<string, LocalPkg>,
|
|
||||||
query: string,
|
query: string,
|
||||||
category: string,
|
category: string,
|
||||||
|
local: Record<string, { manifest: MarketplaceManifest }> = {},
|
||||||
): MarketplacePkg[] | null {
|
): MarketplacePkg[] | null {
|
||||||
if (!packages) {
|
if (!packages) {
|
||||||
return null
|
return null
|
||||||
@@ -63,3 +63,9 @@ export class FilterPackagesPipe implements PipeTransform {
|
|||||||
.map(p => p.item)
|
.map(p => p.item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [FilterPackagesPipe],
|
||||||
|
exports: [FilterPackagesPipe],
|
||||||
|
})
|
||||||
|
export class FilterPackagesPipeModule {}
|
||||||
|
|||||||
@@ -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 {}
|
|
||||||
@@ -2,16 +2,31 @@
|
|||||||
* Public API Surface of @start9labs/marketplace
|
* Public API Surface of @start9labs/marketplace
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './pipes/install-progress.pipe'
|
export * from './pages/list/categories/categories.component'
|
||||||
export * from './pipes/trust.pipe'
|
export * from './pages/list/categories/categories.module'
|
||||||
export * from './pipes/marketplace-pipes.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 './services/marketplace.service'
|
||||||
|
|
||||||
export * from './types/local-pkg'
|
export * from './types/dependency'
|
||||||
export * from './types/marketplace'
|
export * from './types/marketplace'
|
||||||
export * from './types/marketplace-data'
|
export * from './types/marketplace-data'
|
||||||
export * from './types/marketplace-manifest'
|
export * from './types/marketplace-manifest'
|
||||||
export * from './types/marketplace-pkg'
|
export * from './types/marketplace-pkg'
|
||||||
|
|
||||||
export * from './utils/spread-progress'
|
|
||||||
|
|||||||
@@ -13,5 +13,7 @@ export abstract class AbstractMarketplaceService {
|
|||||||
|
|
||||||
abstract getPackages(): Observable<MarketplacePkg[]>
|
abstract getPackages(): Observable<MarketplacePkg[]>
|
||||||
|
|
||||||
|
abstract getPackageMarkdown(type: string, pkgId: string): Observable<string>
|
||||||
|
|
||||||
abstract getPackage(id: string, version: string): Observable<MarketplacePkg>
|
abstract getPackage(id: string, version: string): Observable<MarketplacePkg>
|
||||||
}
|
}
|
||||||
|
|||||||
17
frontend/projects/marketplace/src/types/dependency.ts
Normal file
17
frontend/projects/marketplace/src/types/dependency.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { PackageState } from '@start9labs/shared'
|
|
||||||
|
|
||||||
import { MarketplaceManifest } from './marketplace-manifest'
|
|
||||||
|
|
||||||
export interface LocalPkg {
|
|
||||||
state: PackageState
|
|
||||||
manifest: MarketplaceManifest
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Url } from '@start9labs/shared'
|
import { Url } from '@start9labs/shared'
|
||||||
|
|
||||||
export interface MarketplaceManifest {
|
import { Dependency } from './dependency'
|
||||||
|
|
||||||
|
export interface MarketplaceManifest<T = unknown> {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
version: string
|
version: string
|
||||||
@@ -22,4 +24,5 @@ export interface MarketplaceManifest {
|
|||||||
start: string | null
|
start: string | null
|
||||||
stop: string | null
|
stop: string | null
|
||||||
}
|
}
|
||||||
|
dependencies: Record<string, Dependency<T>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { LocalPkg } from '../types/local-pkg'
|
|
||||||
|
|
||||||
export function spreadProgress(pkg: LocalPkg) {
|
|
||||||
pkg['install-progress'] = { ...pkg['install-progress'] }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>{{ title | titlecase }}</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="dismiss()">
|
||||||
|
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-item *ngIf="error$ | async as error">
|
||||||
|
<ion-label>
|
||||||
|
<ion-text safeLinks color="danger">{{ error }}</ion-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<div
|
||||||
|
*ngIf="content$ | async as result; else loading"
|
||||||
|
safeLinks
|
||||||
|
class="content-padding"
|
||||||
|
[innerHTML]="result | markdown"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<ng-template #loading>
|
||||||
|
<text-spinner [text]="'Loading ' + title | titlecase"></text-spinner>
|
||||||
|
</ng-template>
|
||||||
|
</ion-content>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Component, Input, Optional } from '@angular/core'
|
||||||
|
import { ModalController } from '@ionic/angular'
|
||||||
|
|
||||||
|
import { getErrorMessage } from '../../services/error-toast.service'
|
||||||
|
import { AbstractApiService } from '../../services/api.service'
|
||||||
|
import { defer, from, isObservable, Observable, of } from 'rxjs'
|
||||||
|
import { catchError, ignoreElements, share } from 'rxjs/operators'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'markdown',
|
||||||
|
templateUrl: './markdown.component.html',
|
||||||
|
styleUrls: ['./markdown.component.scss'],
|
||||||
|
})
|
||||||
|
export class MarkdownComponent {
|
||||||
|
@Input() contentUrl?: string
|
||||||
|
@Input() content?: string | Observable<string>
|
||||||
|
@Input() title = ''
|
||||||
|
|
||||||
|
private readonly data$ = defer(() => this.contentObservable).pipe(share())
|
||||||
|
|
||||||
|
readonly error$ = this.data$.pipe(
|
||||||
|
ignoreElements(),
|
||||||
|
catchError(e => of(getErrorMessage(e))),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly content$ = this.data$.pipe(catchError(() => of([])))
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly modalCtrl: ModalController,
|
||||||
|
@Optional()
|
||||||
|
private readonly embassyApi: AbstractApiService | null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async dismiss() {
|
||||||
|
return this.modalCtrl.dismiss(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get contentObservable() {
|
||||||
|
if (isObservable(this.content)) {
|
||||||
|
return this.content
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.contentUrl
|
||||||
|
? from(this.embassyApi?.getStatic(this.contentUrl))
|
||||||
|
: of(this.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
|
||||||
|
import { MarkdownPipeModule } from '../../pipes/markdown/markdown.module'
|
||||||
|
import { SafeLinksModule } from '../../directives/safe-links/safe-links.module'
|
||||||
|
import { TextSpinnerComponentModule } from '../text-spinner/text-spinner.component.module'
|
||||||
|
import { MarkdownComponent } from './markdown.component'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MarkdownComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
MarkdownPipeModule,
|
||||||
|
TextSpinnerComponentModule,
|
||||||
|
SafeLinksModule,
|
||||||
|
],
|
||||||
|
exports: [MarkdownComponent],
|
||||||
|
})
|
||||||
|
export class MarkdownModule {}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { AfterViewInit, Directive, ElementRef, Inject } from '@angular/core'
|
||||||
|
import { DOCUMENT } from '@angular/common'
|
||||||
|
|
||||||
|
// TODO: Refactor to use `MutationObserver` so it works with dynamic content
|
||||||
|
@Directive({
|
||||||
|
selector: '[safeLinks]',
|
||||||
|
})
|
||||||
|
export class SafeLinksDirective implements AfterViewInit {
|
||||||
|
constructor(
|
||||||
|
@Inject(DOCUMENT) private readonly document: Document,
|
||||||
|
private readonly elementRef: ElementRef<HTMLElement>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
Array.from(this.document.links)
|
||||||
|
.filter(
|
||||||
|
link =>
|
||||||
|
link.hostname !== this.document.location.hostname &&
|
||||||
|
this.elementRef.nativeElement.contains(link),
|
||||||
|
)
|
||||||
|
.forEach(link => {
|
||||||
|
link.target = '_blank'
|
||||||
|
link.setAttribute('rel', 'noreferrer')
|
||||||
|
link.classList.add('externalLink')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { SafeLinksDirective } from './safe-links.directive'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [SafeLinksDirective],
|
||||||
|
exports: [SafeLinksDirective],
|
||||||
|
})
|
||||||
|
export class SafeLinksModule {}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { IncludesPipe } from './includes.pipe'
|
import { IncludesPipe } from './includes.pipe'
|
||||||
import { EmptyPipe } from './empty.pipe'
|
import { EmptyPipe } from './empty.pipe'
|
||||||
|
import { TrustUrlPipe } from './trust.pipe'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [IncludesPipe, EmptyPipe],
|
declarations: [IncludesPipe, EmptyPipe, TrustUrlPipe],
|
||||||
exports: [IncludesPipe, EmptyPipe],
|
exports: [IncludesPipe, EmptyPipe, TrustUrlPipe],
|
||||||
})
|
})
|
||||||
export class SharedPipesModule {}
|
export class SharedPipesModule {}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Pipe, PipeTransform } from '@angular/core'
|
|||||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'trust',
|
name: 'trustUrl',
|
||||||
})
|
})
|
||||||
export class TrustPipe implements PipeTransform {
|
export class TrustUrlPipe implements PipeTransform {
|
||||||
constructor(public readonly sanitizer: DomSanitizer) {}
|
constructor(public readonly sanitizer: DomSanitizer) {}
|
||||||
|
|
||||||
transform(base64Icon: string): SafeResourceUrl {
|
transform(base64Icon: string): SafeResourceUrl {
|
||||||
@@ -2,11 +2,15 @@
|
|||||||
* Public API Surface of @start9labs/shared
|
* Public API Surface of @start9labs/shared
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './components/markdown/markdown.component'
|
||||||
|
export * from './components/markdown/markdown.module'
|
||||||
export * from './components/text-spinner/text-spinner.component.module'
|
export * from './components/text-spinner/text-spinner.component.module'
|
||||||
export * from './components/text-spinner/text-spinner.component'
|
export * from './components/text-spinner/text-spinner.component'
|
||||||
|
|
||||||
export * from './directives/element/element.directive'
|
export * from './directives/element/element.directive'
|
||||||
export * from './directives/element/element.module'
|
export * from './directives/element/element.module'
|
||||||
|
export * from './directives/safe-links/safe-links.directive'
|
||||||
|
export * from './directives/safe-links/safe-links.module'
|
||||||
|
|
||||||
export * from './pipes/emver/emver.module'
|
export * from './pipes/emver/emver.module'
|
||||||
export * from './pipes/emver/emver.pipe'
|
export * from './pipes/emver/emver.pipe'
|
||||||
@@ -15,19 +19,17 @@ export * from './pipes/markdown/markdown.pipe'
|
|||||||
export * from './pipes/shared/shared.module'
|
export * from './pipes/shared/shared.module'
|
||||||
export * from './pipes/shared/empty.pipe'
|
export * from './pipes/shared/empty.pipe'
|
||||||
export * from './pipes/shared/includes.pipe'
|
export * from './pipes/shared/includes.pipe'
|
||||||
|
export * from './pipes/shared/trust.pipe'
|
||||||
export * from './pipes/unit-conversion/unit-conversion.module'
|
export * from './pipes/unit-conversion/unit-conversion.module'
|
||||||
export * from './pipes/unit-conversion/unit-conversion.pipe'
|
export * from './pipes/unit-conversion/unit-conversion.pipe'
|
||||||
|
|
||||||
|
export * from './services/api.service'
|
||||||
export * from './services/destroy.service'
|
export * from './services/destroy.service'
|
||||||
export * from './services/emver.service'
|
export * from './services/emver.service'
|
||||||
export * from './services/error-toast.service'
|
export * from './services/error-toast.service'
|
||||||
|
|
||||||
export * from './types/dependent-info'
|
|
||||||
export * from './types/install-progress'
|
|
||||||
export * from './types/package-state'
|
|
||||||
export * from './types/progress-data'
|
|
||||||
export * from './types/url'
|
export * from './types/url'
|
||||||
export * from './types/workspace-config'
|
export * from './types/workspace-config'
|
||||||
|
|
||||||
export * from './util/misc.util'
|
export * from './util/misc.util'
|
||||||
export * from './util/package-loading-progress'
|
export * from './util/unused'
|
||||||
|
|||||||
4
frontend/projects/shared/src/services/api.service.ts
Normal file
4
frontend/projects/shared/src/services/api.service.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export abstract class AbstractApiService {
|
||||||
|
// for getting static files: ex icons, instructions, licenses
|
||||||
|
abstract getStatic(url: string): Promise<string>
|
||||||
|
}
|
||||||
@@ -1,56 +1,3 @@
|
|||||||
import { OperatorFunction } from 'rxjs'
|
|
||||||
import { map } from 'rxjs/operators'
|
|
||||||
|
|
||||||
export function trace<T>(t: T): T {
|
|
||||||
console.log(`TRACE`, t)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// curried description. This allows e.g somePromise.thentraceDesc('my result'))
|
|
||||||
export function traceDesc<T>(description: string): (t: T) => T {
|
|
||||||
return t => {
|
|
||||||
console.log(`TRACE`, description, t)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for use in observables. This allows e.g. someObservable.pipe(traceM('my result'))
|
|
||||||
// the practical equivalent of `tap(t => console.log(t, description))`
|
|
||||||
export function traceWheel<T>(description?: string): OperatorFunction<T, T> {
|
|
||||||
return description ? map(traceDesc(description)) : map(trace)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function traceThrowDesc<T>(description: string, t: T | undefined): T {
|
|
||||||
if (!t) throw new Error(description)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inMs(
|
|
||||||
count: number,
|
|
||||||
unit: 'days' | 'hours' | 'minutes' | 'seconds',
|
|
||||||
) {
|
|
||||||
switch (unit) {
|
|
||||||
case 'seconds':
|
|
||||||
return count * 1000
|
|
||||||
case 'minutes':
|
|
||||||
return inMs(count * 60, 'seconds')
|
|
||||||
case 'hours':
|
|
||||||
return inMs(count * 60, 'minutes')
|
|
||||||
case 'days':
|
|
||||||
return inMs(count * 24, 'hours')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// arr1 - arr2
|
|
||||||
export function diff<T>(arr1: T[], arr2: T[]): T[] {
|
|
||||||
return arr1.filter(x => !arr2.includes(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
// arr1 & arr2
|
|
||||||
export function both<T>(arr1: T[], arr2: T[]): T[] {
|
|
||||||
return arr1.filter(x => arr2.includes(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isObject(val: any): boolean {
|
export function isObject(val: any): boolean {
|
||||||
return val && typeof val === 'object' && !Array.isArray(val)
|
return val && typeof val === 'object' && !Array.isArray(val)
|
||||||
}
|
}
|
||||||
@@ -63,79 +10,6 @@ export function pauseFor(ms: number): Promise<void> {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toObject<T>(t: T[], map: (t0: T) => string): Record<string, T> {
|
|
||||||
return t.reduce((acc, next) => {
|
|
||||||
acc[map(next)] = next
|
|
||||||
return acc
|
|
||||||
}, {} as Record<string, T>)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update<T>(
|
|
||||||
t: Record<string, T>,
|
|
||||||
u: Record<string, T>,
|
|
||||||
): Record<string, T> {
|
|
||||||
return { ...t, ...u }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deepCloneUnknown<T>(value: T): T {
|
|
||||||
if (typeof value !== 'object' || value === null) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return deepCloneArray(value)
|
|
||||||
}
|
|
||||||
return deepCloneObject(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deepCloneObject<T>(source: T) {
|
|
||||||
const result = {}
|
|
||||||
Object.keys(source).forEach(key => {
|
|
||||||
const value = source[key]
|
|
||||||
result[key] = deepCloneUnknown(value)
|
|
||||||
}, {})
|
|
||||||
return result as T
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deepCloneArray(collection: any) {
|
|
||||||
return collection.map(value => {
|
|
||||||
return deepCloneUnknown(value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function partitionArray<T>(
|
|
||||||
ts: T[],
|
|
||||||
condition: (t: T) => boolean,
|
|
||||||
): [T[], T[]] {
|
|
||||||
const yes = [] as T[]
|
|
||||||
const no = [] as T[]
|
|
||||||
ts.forEach(t => {
|
|
||||||
if (condition(t)) {
|
|
||||||
yes.push(t)
|
|
||||||
} else {
|
|
||||||
no.push(t)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return [yes, no]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uniqueBy<T>(
|
|
||||||
ts: T[],
|
|
||||||
uniqueBy: (t: T) => string,
|
|
||||||
prioritize: (t1: T, t2: T) => T,
|
|
||||||
) {
|
|
||||||
return Object.values(
|
|
||||||
ts.reduce((acc, next) => {
|
|
||||||
const previousValue = acc[uniqueBy(next)]
|
|
||||||
if (previousValue) {
|
|
||||||
acc[uniqueBy(next)] = prioritize(acc[uniqueBy(next)], previousValue)
|
|
||||||
} else {
|
|
||||||
acc[uniqueBy(next)] = previousValue
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, {}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function capitalizeFirstLetter(string: string): string {
|
export function capitalizeFirstLetter(string: string): string {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||||
}
|
}
|
||||||
|
|||||||
130
frontend/projects/shared/src/util/unused.ts
Normal file
130
frontend/projects/shared/src/util/unused.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { OperatorFunction } from 'rxjs'
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These utils are not used anywhere
|
||||||
|
* They are candidates for removal
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function trace<T>(t: T): T {
|
||||||
|
console.log(`TRACE`, t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// curried description. This allows e.g somePromise.thentraceDesc('my result'))
|
||||||
|
export function traceDesc<T>(description: string): (t: T) => T {
|
||||||
|
return t => {
|
||||||
|
console.log(`TRACE`, description, t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for use in observables. This allows e.g. someObservable.pipe(traceM('my result'))
|
||||||
|
// the practical equivalent of `tap(t => console.log(t, description))`
|
||||||
|
export function traceWheel<T>(description?: string): OperatorFunction<T, T> {
|
||||||
|
return description ? map(traceDesc(description)) : map(trace)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function traceThrowDesc<T>(description: string, t: T | undefined): T {
|
||||||
|
if (!t) throw new Error(description)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inMs(
|
||||||
|
count: number,
|
||||||
|
unit: 'days' | 'hours' | 'minutes' | 'seconds',
|
||||||
|
) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'seconds':
|
||||||
|
return count * 1000
|
||||||
|
case 'minutes':
|
||||||
|
return inMs(count * 60, 'seconds')
|
||||||
|
case 'hours':
|
||||||
|
return inMs(count * 60, 'minutes')
|
||||||
|
case 'days':
|
||||||
|
return inMs(count * 24, 'hours')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arr1 - arr2
|
||||||
|
export function diff<T>(arr1: T[], arr2: T[]): T[] {
|
||||||
|
return arr1.filter(x => !arr2.includes(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
// arr1 & arr2
|
||||||
|
export function both<T>(arr1: T[], arr2: T[]): T[] {
|
||||||
|
return arr1.filter(x => arr2.includes(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toObject<T>(t: T[], map: (t0: T) => string): Record<string, T> {
|
||||||
|
return t.reduce((acc, next) => {
|
||||||
|
acc[map(next)] = next
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepCloneUnknown<T>(value: T): T {
|
||||||
|
if (typeof value !== 'object' || value === null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return deepCloneArray(value)
|
||||||
|
}
|
||||||
|
return deepCloneObject(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepCloneObject<T>(source: T) {
|
||||||
|
const result = {}
|
||||||
|
Object.keys(source).forEach(key => {
|
||||||
|
const value = source[key]
|
||||||
|
result[key] = deepCloneUnknown(value)
|
||||||
|
}, {})
|
||||||
|
return result as T
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepCloneArray(collection: any) {
|
||||||
|
return collection.map(value => {
|
||||||
|
return deepCloneUnknown(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function partitionArray<T>(
|
||||||
|
ts: T[],
|
||||||
|
condition: (t: T) => boolean,
|
||||||
|
): [T[], T[]] {
|
||||||
|
const yes = [] as T[]
|
||||||
|
const no = [] as T[]
|
||||||
|
ts.forEach(t => {
|
||||||
|
if (condition(t)) {
|
||||||
|
yes.push(t)
|
||||||
|
} else {
|
||||||
|
no.push(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return [yes, no]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update<T>(
|
||||||
|
t: Record<string, T>,
|
||||||
|
u: Record<string, T>,
|
||||||
|
): Record<string, T> {
|
||||||
|
return { ...t, ...u }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uniqueBy<T>(
|
||||||
|
ts: T[],
|
||||||
|
uniqueBy: (t: T) => string,
|
||||||
|
prioritize: (t1: T, t2: T) => T,
|
||||||
|
) {
|
||||||
|
return Object.values(
|
||||||
|
ts.reduce((acc, next) => {
|
||||||
|
const previousValue = acc[uniqueBy(next)]
|
||||||
|
if (previousValue) {
|
||||||
|
acc[uniqueBy(next)] = prioritize(acc[uniqueBy(next)], previousValue)
|
||||||
|
} else {
|
||||||
|
acc[uniqueBy(next)] = previousValue
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory'
|
|||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
import { QrCodeModule } from 'ng-qrcode'
|
import { QrCodeModule } from 'ng-qrcode'
|
||||||
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
||||||
import { MarkdownPageModule } from './modals/markdown/markdown.module'
|
|
||||||
import { PatchDbService } from './services/patch-db/patch-db.service'
|
import { PatchDbService } from './services/patch-db/patch-db.service'
|
||||||
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
||||||
import { FormBuilder } from '@angular/forms'
|
import { FormBuilder } from '@angular/forms'
|
||||||
@@ -22,7 +21,11 @@ import { GlobalErrorHandler } from './services/global-error-handler.service'
|
|||||||
import { MockApiService } from './services/api/embassy-mock-api.service'
|
import { MockApiService } from './services/api/embassy-mock-api.service'
|
||||||
import { LiveApiService } from './services/api/embassy-live-api.service'
|
import { LiveApiService } from './services/api/embassy-live-api.service'
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
||||||
import { SharedPipesModule, WorkspaceConfig } from '@start9labs/shared'
|
import {
|
||||||
|
MarkdownModule,
|
||||||
|
SharedPipesModule,
|
||||||
|
WorkspaceConfig,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { MarketplaceModule } from './marketplace.module'
|
import { MarketplaceModule } from './marketplace.module'
|
||||||
|
|
||||||
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||||
@@ -45,7 +48,7 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
|||||||
}),
|
}),
|
||||||
QrCodeModule,
|
QrCodeModule,
|
||||||
OSWelcomePageModule,
|
OSWelcomePageModule,
|
||||||
MarkdownPageModule,
|
MarkdownModule,
|
||||||
GenericInputComponentModule,
|
GenericInputComponentModule,
|
||||||
MonacoEditorModule,
|
MonacoEditorModule,
|
||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
import {
|
import {
|
||||||
ErrorToastService,
|
ErrorToastService,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
DependentInfo,
|
|
||||||
isEmptyObject,
|
isEmptyObject,
|
||||||
isObject,
|
isObject,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
|
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>{{ title | titlecase }}</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button (click)="dismiss()">
|
|
||||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<text-spinner *ngIf="loading; else loaded" [text]="'Loading ' + title | titlecase"></text-spinner>
|
|
||||||
|
|
||||||
<ng-template #loaded>
|
|
||||||
|
|
||||||
<ion-item *ngIf="loadingError; else noError">
|
|
||||||
<ion-label>
|
|
||||||
<ion-text color="danger">
|
|
||||||
{{ loadingError }}
|
|
||||||
</ion-text>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ng-template #noError>
|
|
||||||
<div class="content-padding" [innerHTML]="content | markdown"></div>
|
|
||||||
</ng-template>
|
|
||||||
</ng-template>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
|
||||||
import { ModalController, IonicSafeString } from '@ionic/angular'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { getErrorMessage, pauseFor } from '@start9labs/shared'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'markdown',
|
|
||||||
templateUrl: './markdown.page.html',
|
|
||||||
styleUrls: ['./markdown.page.scss'],
|
|
||||||
})
|
|
||||||
export class MarkdownPage {
|
|
||||||
@Input() contentUrl?: string
|
|
||||||
@Input() content?: string
|
|
||||||
@Input() title: string
|
|
||||||
loading = true
|
|
||||||
loadingError: string | IonicSafeString
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly embassyApi: ApiService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
try {
|
|
||||||
if (!this.content) {
|
|
||||||
this.content = await this.embassyApi.getStatic(this.contentUrl)
|
|
||||||
}
|
|
||||||
this.loading = false
|
|
||||||
await pauseFor(50)
|
|
||||||
const links = document.links
|
|
||||||
for (let i = 0, linksLength = links.length; i < linksLength; i++) {
|
|
||||||
if (links[i].hostname != window.location.hostname) {
|
|
||||||
links[i].target = '_blank'
|
|
||||||
links[i].setAttribute('rel', 'noreferrer')
|
|
||||||
links[i].className += ' externalLink'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.loadingError = getErrorMessage(e)
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dismiss() {
|
|
||||||
return this.modalCtrl.dismiss(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|||||||
import { NavController } from '@ionic/angular'
|
import { NavController } from '@ionic/angular'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { PackageState } from '@start9labs/shared'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
import {
|
import {
|
||||||
PackageStatus,
|
PackageStatus,
|
||||||
PrimaryStatus,
|
PrimaryStatus,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { InstallProgress, ProgressData } from '@start9labs/shared'
|
import { InstallProgress } from 'src/app/types/install-progress'
|
||||||
|
import { ProgressData } from 'src/app/types/progress-data'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show-progress',
|
selector: 'app-show-progress',
|
||||||
|
|||||||
@@ -10,11 +10,8 @@ import {
|
|||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
Status,
|
Status,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import {
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
isEmptyObject,
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
ErrorToastService,
|
|
||||||
PackageState,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
import {
|
import {
|
||||||
AlertController,
|
AlertController,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { ProgressData, packageLoadingProgress } from '@start9labs/shared'
|
import { ProgressData } from 'src/app/types/progress-data'
|
||||||
|
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'installState',
|
name: 'installState',
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { Inject, Pipe, PipeTransform } from '@angular/core'
|
|||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { DOCUMENT } from '@angular/common'
|
import { DOCUMENT } from '@angular/common'
|
||||||
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
||||||
|
import { MarkdownComponent } from '@start9labs/shared'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
|
|
||||||
import { ModalService } from 'src/app/services/modal.service'
|
import { ModalService } from 'src/app/services/modal.service'
|
||||||
|
|
||||||
export interface Button {
|
export interface Button {
|
||||||
@@ -105,7 +105,7 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
title: 'Instructions',
|
title: 'Instructions',
|
||||||
contentUrl: pkg['static-files']['instructions'],
|
contentUrl: pkg['static-files']['instructions'],
|
||||||
},
|
},
|
||||||
component: MarkdownPage,
|
component: MarkdownComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
await modal.present()
|
await modal.present()
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
DependencyErrorType,
|
DependencyErrorType,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { DependentInfo, exists } from '@start9labs/shared'
|
import { exists } from '@start9labs/shared'
|
||||||
|
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { ModalService } from 'src/app/services/modal.service'
|
import { ModalService } from 'src/app/services/modal.service'
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import * as yaml from 'js-yaml'
|
|||||||
import { take } from 'rxjs/operators'
|
import { take } from 'rxjs/operators'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { debounce } from '../../../../../../shared/src/util/misc.util'
|
|
||||||
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
|
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { debounce, ErrorToastService } from '@start9labs/shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-config',
|
selector: 'dev-config',
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { ActivatedRoute } from '@angular/router'
|
|||||||
import { ModalController } from '@ionic/angular'
|
import { ModalController } from '@ionic/angular'
|
||||||
import { take } from 'rxjs/operators'
|
import { take } from 'rxjs/operators'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import {
|
||||||
|
debounce,
|
||||||
|
ErrorToastService,
|
||||||
|
MarkdownComponent,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { debounce } from '../../../../../../shared/src/util/misc.util'
|
|
||||||
import { MarkdownPage } from '../../../modals/markdown/markdown.page'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-instructions',
|
selector: 'dev-instructions',
|
||||||
@@ -44,7 +46,7 @@ export class DevInstructionsPage {
|
|||||||
title: 'Instructions Sample',
|
title: 'Instructions Sample',
|
||||||
content: this.code,
|
content: this.code,
|
||||||
},
|
},
|
||||||
component: MarkdownPage,
|
component: MarkdownComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
await modal.present()
|
await modal.present()
|
||||||
|
|||||||
@@ -1,62 +1,34 @@
|
|||||||
<h1 class="heading ion-text-center">{{ name }}</h1>
|
<h1 class="heading ion-text-center">{{ name }}</h1>
|
||||||
|
|
||||||
<ion-grid class="grid">
|
<marketplace-search [(query)]="query"></marketplace-search>
|
||||||
<ion-row>
|
|
||||||
<ion-col sizeSm="8" offset-sm="2">
|
|
||||||
<ion-toolbar color="transparent">
|
|
||||||
<ion-searchbar
|
|
||||||
enterkeyhint="search"
|
|
||||||
color="dark"
|
|
||||||
debounce="250"
|
|
||||||
[(ngModel)]="query"
|
|
||||||
></ion-searchbar>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
|
|
||||||
<ng-container *ngIf="pkgs && categories; else loading">
|
<ng-container *ngIf="pkgs && categories; else loading">
|
||||||
<div class="hidden-scrollbar ion-text-center">
|
<marketplace-categories
|
||||||
<ion-button
|
[categories]="categories"
|
||||||
*ngFor="let cat of categories"
|
[category]="category"
|
||||||
fill="clear"
|
(categoryChange)="onCategoryChange($event)"
|
||||||
class="category"
|
></marketplace-categories>
|
||||||
[class.category_selected]="isSelected(cat)"
|
|
||||||
(click)="switchCategory(cat)"
|
|
||||||
>
|
|
||||||
{{ cat }}
|
|
||||||
</ion-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<ion-grid *ngIf="pkgs | filterPackages: localPkgs:query:category as filtered">
|
<ion-grid *ngIf="pkgs | filterPackages: query:category:localPkgs as filtered">
|
||||||
<div *ngIf="!filtered.length && category === 'updates'" class="ion-padding">
|
<div *ngIf="!filtered.length && category === 'updates'" class="ion-padding">
|
||||||
<h1>All services are up to date!</h1>
|
<h1>All services are up to date!</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col *ngFor="let pkg of filtered" sizeXs="12" sizeSm="12" sizeMd="6">
|
<ion-col *ngFor="let pkg of filtered" sizeXs="12" sizeSm="12" sizeMd="6">
|
||||||
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
|
<marketplace-item [pkg]="pkg">
|
||||||
<ion-thumbnail slot="start">
|
<marketplace-status
|
||||||
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trust" />
|
class="status"
|
||||||
</ion-thumbnail>
|
[pkg]="localPkgs[pkg.manifest.id]"
|
||||||
<ion-label>
|
></marketplace-status>
|
||||||
<h2 class="pkg-title">
|
</marketplace-item>
|
||||||
{{ pkg.manifest.title }}
|
|
||||||
</h2>
|
|
||||||
<h3>{{ pkg.manifest.description.short }}</h3>
|
|
||||||
<marketplace-status
|
|
||||||
class="status"
|
|
||||||
[pkg]="localPkgs[pkg.manifest.id]"
|
|
||||||
></marketplace-status>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
<marketplace-list-skeleton></marketplace-list-skeleton>
|
<marketplace-skeleton></marketplace-skeleton>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -12,35 +12,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
|
||||||
padding-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pkg-title {
|
|
||||||
font-family: 'Montserrat', sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eos-item {
|
|
||||||
--border-style: none;
|
|
||||||
--background: linear-gradient(
|
|
||||||
45deg,
|
|
||||||
var(--ion-color-dark) -380%,
|
|
||||||
var(--ion-color-medium) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category {
|
|
||||||
font-weight: 300;
|
|
||||||
color: var(--ion-color-dark-shade);
|
|
||||||
|
|
||||||
&_selected {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 17px;
|
|
||||||
color: var(--color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { LocalPkg, MarketplacePkg } from '@start9labs/marketplace'
|
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
|
|
||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-list-content',
|
selector: 'marketplace-list-content',
|
||||||
@@ -12,7 +14,7 @@ export class MarketplaceListContentComponent {
|
|||||||
pkgs: MarketplacePkg[] | null = null
|
pkgs: MarketplacePkg[] | null = null
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
localPkgs: Record<string, LocalPkg> = {}
|
localPkgs: Record<string, PackageDataEntry> = {}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
categories: Set<string> | null = null
|
categories: Set<string> | null = null
|
||||||
@@ -23,11 +25,7 @@ export class MarketplaceListContentComponent {
|
|||||||
category = 'featured'
|
category = 'featured'
|
||||||
query = ''
|
query = ''
|
||||||
|
|
||||||
isSelected(category: string) {
|
onCategoryChange(category: string): void {
|
||||||
return category === this.category && !this.query
|
|
||||||
}
|
|
||||||
|
|
||||||
switchCategory(category: string): void {
|
|
||||||
this.category = category
|
this.category = category
|
||||||
this.query = ''
|
this.query = ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<badge-menu-button></badge-menu-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'marketplace-list-header',
|
|
||||||
templateUrl: 'marketplace-list-header.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class MarketplaceListHeaderComponent {}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'marketplace-list-skeleton',
|
|
||||||
templateUrl: 'marketplace-list-skeleton.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class MarketplaceListSkeletonComponent {}
|
|
||||||
@@ -8,13 +8,17 @@ import {
|
|||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { MarketplacePipesModule } from '@start9labs/marketplace'
|
import {
|
||||||
|
FilterPackagesPipeModule,
|
||||||
|
CategoriesModule,
|
||||||
|
ItemModule,
|
||||||
|
SearchModule,
|
||||||
|
SkeletonModule,
|
||||||
|
} from '@start9labs/marketplace'
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||||
|
|
||||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||||
import { MarketplaceListPage } from './marketplace-list.page'
|
import { MarketplaceListPage } from './marketplace-list.page'
|
||||||
import { MarketplaceListHeaderComponent } from './marketplace-list-header/marketplace-list-header.component'
|
|
||||||
import { MarketplaceListSkeletonComponent } from './marketplace-list-skeleton/marketplace-list-skeleton.component'
|
|
||||||
import { MarketplaceListContentComponent } from './marketplace-list-content/marketplace-list-content.component'
|
import { MarketplaceListContentComponent } from './marketplace-list-content/marketplace-list-content.component'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -33,21 +37,15 @@ const routes: Routes = [
|
|||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
MarketplacePipesModule,
|
FilterPackagesPipeModule,
|
||||||
MarketplaceStatusModule,
|
MarketplaceStatusModule,
|
||||||
BadgeMenuComponentModule,
|
BadgeMenuComponentModule,
|
||||||
|
ItemModule,
|
||||||
|
CategoriesModule,
|
||||||
|
SearchModule,
|
||||||
|
SkeletonModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [MarketplaceListPage, MarketplaceListContentComponent],
|
||||||
MarketplaceListPage,
|
exports: [MarketplaceListPage, MarketplaceListContentComponent],
|
||||||
MarketplaceListHeaderComponent,
|
|
||||||
MarketplaceListContentComponent,
|
|
||||||
MarketplaceListSkeletonComponent,
|
|
||||||
],
|
|
||||||
exports: [
|
|
||||||
MarketplaceListPage,
|
|
||||||
MarketplaceListHeaderComponent,
|
|
||||||
MarketplaceListContentComponent,
|
|
||||||
MarketplaceListSkeletonComponent,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class MarketplaceListPageModule {}
|
export class MarketplaceListPageModule {}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
<marketplace-list-header></marketplace-list-header>
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<badge-menu-button></badge-menu-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<marketplace-list-content
|
<marketplace-list-content
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ import { filter, first, map, startWith, switchMapTo, tap } from 'rxjs/operators'
|
|||||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
LocalPkg,
|
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
spreadProgress,
|
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
|
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
import { spreadProgress } from '../utils/spread-progress'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-list',
|
selector: 'marketplace-list',
|
||||||
templateUrl: './marketplace-list.page.html',
|
templateUrl: './marketplace-list.page.html',
|
||||||
})
|
})
|
||||||
export class MarketplaceListPage {
|
export class MarketplaceListPage {
|
||||||
readonly localPkgs$: Observable<Record<string, LocalPkg>> = defer(() =>
|
readonly localPkgs$: Observable<Record<string, PackageDataEntry>> = defer(
|
||||||
this.patch.watch$('package-data'),
|
() => this.patch.watch$('package-data'),
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(data => exists(data) && !isEmptyObject(data)),
|
filter(data => exists(data) && !isEmptyObject(data)),
|
||||||
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
|
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
|
||||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'marketplace-show-about',
|
|
||||||
templateUrl: 'marketplace-show-about.component.html',
|
|
||||||
styleUrls: ['marketplace-show-about.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class MarketplaceShowAboutComponent {
|
|
||||||
@Input()
|
|
||||||
pkg: MarketplacePkg
|
|
||||||
}
|
|
||||||
@@ -3,11 +3,14 @@ import { AlertController, ModalController, NavController } from '@ionic/angular'
|
|||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
LocalPkg,
|
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { pauseFor, PackageState } from '@start9labs/shared'
|
import { pauseFor } from '@start9labs/shared'
|
||||||
|
|
||||||
import { Manifest } from 'src/app/services/patch-db/data-model'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
|
import {
|
||||||
|
Manifest,
|
||||||
|
PackageDataEntry,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||||
@@ -22,7 +25,7 @@ export class MarketplaceShowControlsComponent {
|
|||||||
pkg: MarketplacePkg
|
pkg: MarketplacePkg
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
localPkg: LocalPkg
|
localPkg: PackageDataEntry
|
||||||
|
|
||||||
readonly PackageState = PackageState
|
readonly PackageState = PackageState
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<ng-container *ngIf="!(dependencies | empty)">
|
|
||||||
<ion-item-divider>Dependencies</ion-item-divider>
|
|
||||||
<ion-grid>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col
|
|
||||||
*ngFor="let dep of dependencies | keyvalue"
|
|
||||||
sizeSm="12"
|
|
||||||
sizeMd="6"
|
|
||||||
>
|
|
||||||
<ion-item [routerLink]="['/marketplace', dep.key]">
|
|
||||||
<ion-thumbnail slot="start">
|
|
||||||
<img alt="" [src]="getImg(dep.key) | trust" />
|
|
||||||
</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>
|
|
||||||
</ng-container>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
|
||||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
|
||||||
|
|
||||||
import { DependencyInfo, Manifest } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'marketplace-show-dependencies',
|
|
||||||
templateUrl: 'marketplace-show-dependencies.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class MarketplaceShowDependenciesComponent {
|
|
||||||
@Input()
|
|
||||||
pkg: MarketplacePkg
|
|
||||||
|
|
||||||
get dependencies(): DependencyInfo {
|
|
||||||
// TODO: Fix type
|
|
||||||
return (this.pkg.manifest as Manifest).dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
getImg(key: string): string {
|
|
||||||
return 'data:image/png;base64,' + this.pkg['dependency-metadata'][key].icon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- auto-config -->
|
<!-- auto-config -->
|
||||||
<ion-item lines="none" *ngIf="dependentInfo" class="rec-item">
|
<ion-item *ngIf="dependentInfo" lines="none" class="rec-item">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 class="heading">
|
<h2 class="heading">
|
||||||
<ion-text class="title">
|
<ion-text class="title">
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
Inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
import { DependentInfo } from '@start9labs/shared'
|
import { DOCUMENT } from '@angular/common'
|
||||||
|
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-show-dependent',
|
selector: 'marketplace-show-dependent',
|
||||||
@@ -12,7 +18,10 @@ export class MarketplaceShowDependentComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
pkg: MarketplacePkg
|
pkg: MarketplacePkg
|
||||||
|
|
||||||
readonly dependentInfo?: DependentInfo = history.state?.dependentInfo
|
readonly dependentInfo?: DependentInfo =
|
||||||
|
this.document.defaultView?.history.state?.dependentInfo
|
||||||
|
|
||||||
|
constructor(@Inject(DOCUMENT) private readonly document: Document) {}
|
||||||
|
|
||||||
get title(): string {
|
get title(): string {
|
||||||
return this.pkg?.manifest.title || ''
|
return this.pkg?.manifest.title || ''
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ import {
|
|||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { MarketplacePipesModule } from '@start9labs/marketplace'
|
import {
|
||||||
|
PackageModule,
|
||||||
|
AboutModule,
|
||||||
|
AdditionalModule,
|
||||||
|
DependenciesModule,
|
||||||
|
} from '@start9labs/marketplace'
|
||||||
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
|
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
|
||||||
|
|
||||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||||
import { MarketplaceShowPage } from './marketplace-show.page'
|
import { MarketplaceShowPage } from './marketplace-show.page'
|
||||||
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component'
|
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component'
|
||||||
import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component'
|
import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component'
|
||||||
import { MarketplaceShowDependenciesComponent } from './marketplace-show-dependencies/marketplace-show-dependencies.component'
|
|
||||||
import { MarketplaceShowAdditionalComponent } from './marketplace-show-additional/marketplace-show-additional.component'
|
|
||||||
import { MarketplaceShowAboutComponent } from './marketplace-show-about/marketplace-show-about.component'
|
|
||||||
import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component'
|
import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -36,27 +38,24 @@ const routes: Routes = [
|
|||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
MarketplacePipesModule,
|
|
||||||
MarketplaceStatusModule,
|
MarketplaceStatusModule,
|
||||||
InstallWizardComponentModule,
|
InstallWizardComponentModule,
|
||||||
|
PackageModule,
|
||||||
|
AboutModule,
|
||||||
|
DependenciesModule,
|
||||||
|
AdditionalModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MarketplaceShowPage,
|
MarketplaceShowPage,
|
||||||
MarketplaceShowHeaderComponent,
|
MarketplaceShowHeaderComponent,
|
||||||
MarketplaceShowControlsComponent,
|
MarketplaceShowControlsComponent,
|
||||||
MarketplaceShowDependentComponent,
|
MarketplaceShowDependentComponent,
|
||||||
MarketplaceShowAboutComponent,
|
|
||||||
MarketplaceShowDependenciesComponent,
|
|
||||||
MarketplaceShowAdditionalComponent,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
MarketplaceShowPage,
|
MarketplaceShowPage,
|
||||||
MarketplaceShowHeaderComponent,
|
MarketplaceShowHeaderComponent,
|
||||||
MarketplaceShowControlsComponent,
|
MarketplaceShowControlsComponent,
|
||||||
MarketplaceShowDependentComponent,
|
MarketplaceShowDependentComponent,
|
||||||
MarketplaceShowAboutComponent,
|
|
||||||
MarketplaceShowDependenciesComponent,
|
|
||||||
MarketplaceShowAdditionalComponent,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MarketplaceShowPageModule {}
|
export class MarketplaceShowPageModule {}
|
||||||
|
|||||||
@@ -2,72 +2,53 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<ng-container *ngIf="pkg$ | async as pkg else loading">
|
<ng-container *ngIf="pkg$ | async as pkg else loading">
|
||||||
<ion-grid>
|
<ng-container *ngIf="!(pkg | empty)">
|
||||||
<ion-row>
|
<marketplace-package [pkg]="pkg">
|
||||||
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
|
<marketplace-status
|
||||||
<div class="header">
|
class="status"
|
||||||
<img alt="" [src]="getIcon(pkg.icon) | trust" />
|
[pkg]="localPkg$ | async"
|
||||||
<div class="header-text">
|
></marketplace-status>
|
||||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
<marketplace-show-controls
|
||||||
<p class="header-version">
|
slot="controls"
|
||||||
{{ pkg.manifest.version | displayEmver }}
|
[pkg]="pkg"
|
||||||
</p>
|
[localPkg]="localPkg$ | async"
|
||||||
<marketplace-status
|
></marketplace-show-controls>
|
||||||
class="header-status"
|
<ion-row *ngIf="localPkg$ | async">
|
||||||
[pkg]="localPkg$ | async"
|
<ion-col
|
||||||
></marketplace-status>
|
sizeXl="3"
|
||||||
</div>
|
sizeLg="3"
|
||||||
</div>
|
sizeMd="3"
|
||||||
</ion-col>
|
sizeSm="12"
|
||||||
<ion-col
|
sizeXs="12"
|
||||||
sizeXl="3"
|
class="ion-align-self-center"
|
||||||
sizeLg="3"
|
|
||||||
sizeMd="3"
|
|
||||||
sizeSm="12"
|
|
||||||
sizeXs="12"
|
|
||||||
class="ion-align-self-center"
|
|
||||||
>
|
|
||||||
<marketplace-show-controls
|
|
||||||
[pkg]="pkg"
|
|
||||||
[localPkg]="localPkg$ | async"
|
|
||||||
></marketplace-show-controls>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
<ion-row *ngIf="localPkg$ | async">
|
|
||||||
<ion-col
|
|
||||||
sizeXl="3"
|
|
||||||
sizeLg="3"
|
|
||||||
sizeMd="3"
|
|
||||||
sizeSm="12"
|
|
||||||
sizeXs="12"
|
|
||||||
class="ion-align-self-center"
|
|
||||||
>
|
|
||||||
<ion-button
|
|
||||||
expand="block"
|
|
||||||
fill="outline"
|
|
||||||
color="primary"
|
|
||||||
[routerLink]="['/services', pkg.manifest.id]"
|
|
||||||
>
|
>
|
||||||
View Service
|
<ion-button
|
||||||
</ion-button>
|
expand="block"
|
||||||
</ion-col>
|
fill="outline"
|
||||||
</ion-row>
|
color="primary"
|
||||||
</ion-grid>
|
[routerLink]="['/services', pkg.manifest.id]"
|
||||||
|
>
|
||||||
|
View Service
|
||||||
|
</ion-button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</marketplace-package>
|
||||||
|
|
||||||
<marketplace-show-dependent [pkg]="pkg"></marketplace-show-dependent>
|
<marketplace-show-dependent [pkg]="pkg"></marketplace-show-dependent>
|
||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<marketplace-show-about [pkg]="pkg"></marketplace-show-about>
|
<marketplace-about [pkg]="pkg"></marketplace-about>
|
||||||
|
<marketplace-dependencies
|
||||||
|
*ngIf="!(pkg.manifest.dependencies | empty)"
|
||||||
|
[pkg]="pkg"
|
||||||
|
></marketplace-dependencies>
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
<marketplace-show-dependencies
|
<marketplace-additional
|
||||||
[pkg]="pkg"
|
[pkg]="pkg"
|
||||||
></marketplace-show-dependencies>
|
(version)="loadVersion$.next($event)"
|
||||||
</ion-item-group>
|
></marketplace-additional>
|
||||||
|
</ng-container>
|
||||||
<marketplace-show-additional
|
|
||||||
[pkg]="pkg"
|
|
||||||
(version)="loadVersion$.next($event)"
|
|
||||||
></marketplace-show-additional>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
|
|||||||
@@ -1,30 +1,3 @@
|
|||||||
.header {
|
.status {
|
||||||
font-family: 'Montserrat', sans-serif;
|
font-size: calc(16px + 1vw);
|
||||||
padding: 2%;
|
|
||||||
|
|
||||||
img {
|
|
||||||
min-width: 15%;
|
|
||||||
max-width: 18%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-text {
|
|
||||||
margin-left: 5%;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
margin: 0 0 0 -2px;
|
|
||||||
font-size: calc(20px + 3vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-version {
|
|
||||||
padding: 4px 0 12px 0;
|
|
||||||
margin: 0;
|
|
||||||
font-size: calc(10px + 1vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-status {
|
|
||||||
font-size: calc(16px + 1vw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,15 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
LocalPkg,
|
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
spreadProgress,
|
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { BehaviorSubject, defer, Observable, of } from 'rxjs'
|
import { BehaviorSubject, defer, Observable, of } from 'rxjs'
|
||||||
import {
|
import { catchError, filter, shareReplay, switchMap, tap } from 'rxjs/operators'
|
||||||
catchError,
|
|
||||||
filter,
|
import { spreadProgress } from '../utils/spread-progress'
|
||||||
shareReplay,
|
|
||||||
startWith,
|
|
||||||
switchMap,
|
|
||||||
tap,
|
|
||||||
} from 'rxjs/operators'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-show',
|
selector: 'marketplace-show',
|
||||||
@@ -32,16 +26,14 @@ export class MarketplaceShowPage {
|
|||||||
readonly localPkg$ = defer(() =>
|
readonly localPkg$ = defer(() =>
|
||||||
this.patch.watch$('package-data', this.pkgId),
|
this.patch.watch$('package-data', this.pkgId),
|
||||||
).pipe(
|
).pipe(
|
||||||
filter<LocalPkg>(Boolean),
|
filter<PackageDataEntry>(Boolean),
|
||||||
tap(spreadProgress),
|
tap(spreadProgress),
|
||||||
shareReplay({ bufferSize: 1, refCount: true }),
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly pkg$: Observable<MarketplacePkg> = this.loadVersion$.pipe(
|
readonly pkg$: Observable<MarketplacePkg> = this.loadVersion$.pipe(
|
||||||
switchMap(version =>
|
switchMap(version =>
|
||||||
this.marketplaceService
|
this.marketplaceService.getPackage(this.pkgId, version),
|
||||||
.getPackage(this.pkgId, version)
|
|
||||||
.pipe(startWith(null)),
|
|
||||||
),
|
),
|
||||||
// TODO: Better fallback
|
// TODO: Better fallback
|
||||||
catchError(e => this.errToast.present(e) && of({} as MarketplacePkg)),
|
catchError(e => this.errToast.present(e) && of({} as MarketplacePkg)),
|
||||||
@@ -57,16 +49,4 @@ export class MarketplaceShowPage {
|
|||||||
getIcon(icon: string): string {
|
getIcon(icon: string): string {
|
||||||
return `data:image/png;base64,${icon}`
|
return `data:image/png;base64,${icon}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// async getPkg(version: string): Promise<void> {
|
|
||||||
// this.loading = true
|
|
||||||
// try {
|
|
||||||
// this.pkg = await this.marketplaceService.getPkg(this.pkgId, version)
|
|
||||||
// } catch (e) {
|
|
||||||
// this.errToast.present(e)
|
|
||||||
// } finally {
|
|
||||||
// await pauseFor(100)
|
|
||||||
// this.loading = false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { InstallProgress, packageLoadingProgress } from '@start9labs/shared'
|
import { InstallProgress } from 'src/app/types/install-progress'
|
||||||
|
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'installProgress',
|
name: 'installProgress',
|
||||||
@@ -24,5 +24,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #none>
|
<ng-template #none>
|
||||||
<div>Not Installed</div>
|
<ion-text style="color: var(--ion-color-step-450)">Not Installed</ion-text>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { LocalPkg } from '@start9labs/marketplace'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
import { PackageState } from '@start9labs/shared'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-status',
|
selector: 'marketplace-status',
|
||||||
@@ -8,7 +8,7 @@ import { PackageState } from '@start9labs/shared'
|
|||||||
})
|
})
|
||||||
export class MarketplaceStatusComponent {
|
export class MarketplaceStatusComponent {
|
||||||
@Input()
|
@Input()
|
||||||
pkg?: LocalPkg
|
pkg?: PackageDataEntry
|
||||||
|
|
||||||
PackageState = PackageState
|
PackageState = PackageState
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,13 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { EmverPipesModule } from '@start9labs/shared'
|
import { EmverPipesModule } from '@start9labs/shared'
|
||||||
import { MarketplacePipesModule } from '@start9labs/marketplace'
|
|
||||||
|
|
||||||
|
import { InstallProgressPipe } from './install-progress.pipe'
|
||||||
import { MarketplaceStatusComponent } from './marketplace-status.component'
|
import { MarketplaceStatusComponent } from './marketplace-status.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [CommonModule, IonicModule, EmverPipesModule],
|
||||||
CommonModule,
|
declarations: [MarketplaceStatusComponent, InstallProgressPipe],
|
||||||
IonicModule,
|
|
||||||
EmverPipesModule,
|
|
||||||
MarketplacePipesModule,
|
|
||||||
],
|
|
||||||
declarations: [MarketplaceStatusComponent],
|
|
||||||
exports: [MarketplaceStatusComponent],
|
exports: [MarketplaceStatusComponent],
|
||||||
})
|
})
|
||||||
export class MarketplaceStatusModule {}
|
export class MarketplaceStatusModule {}
|
||||||
|
|||||||
@@ -1,8 +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>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|
||||||
import { ActivatedRoute } from '@angular/router'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'release-notes-header',
|
|
||||||
templateUrl: 'release-notes-header.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class ReleaseNotesHeaderComponent {
|
|
||||||
readonly href = `/marketplace/${this.route.snapshot.paramMap.get('pkgId')}`
|
|
||||||
|
|
||||||
constructor(private readonly route: ActivatedRoute) {}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,9 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { Routes, RouterModule } from '@angular/router'
|
import { Routes, RouterModule } from '@angular/router'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import {
|
import { ReleaseNotesModule } from '@start9labs/marketplace'
|
||||||
EmverPipesModule,
|
|
||||||
MarkdownPipeModule,
|
|
||||||
TextSpinnerComponentModule,
|
|
||||||
ElementModule,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { MarketplacePipesModule } from '@start9labs/marketplace'
|
|
||||||
|
|
||||||
import { ReleaseNotesPage } from './release-notes.page'
|
import { ReleaseNotesPage } from './release-notes.page'
|
||||||
import { ReleaseNotesHeaderComponent } from './release-notes-header/release-notes-header.component'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -21,17 +13,8 @@ const routes: Routes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [IonicModule, ReleaseNotesModule, RouterModule.forChild(routes)],
|
||||||
CommonModule,
|
declarations: [ReleaseNotesPage],
|
||||||
IonicModule,
|
exports: [ReleaseNotesPage],
|
||||||
RouterModule.forChild(routes),
|
|
||||||
TextSpinnerComponentModule,
|
|
||||||
EmverPipesModule,
|
|
||||||
MarkdownPipeModule,
|
|
||||||
MarketplacePipesModule,
|
|
||||||
ElementModule,
|
|
||||||
],
|
|
||||||
declarations: [ReleaseNotesPage, ReleaseNotesHeaderComponent],
|
|
||||||
exports: [ReleaseNotesPage, ReleaseNotesHeaderComponent],
|
|
||||||
})
|
})
|
||||||
export class ReleaseNotesPageModule {}
|
export class ReleaseNotesPageModule {}
|
||||||
|
|||||||
@@ -1,34 +1,10 @@
|
|||||||
<release-notes-header></release-notes-header>
|
<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>
|
||||||
|
|
||||||
<ion-content>
|
<release-notes></release-notes>
|
||||||
<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>
|
|
||||||
|
|||||||
@@ -1,38 +1,12 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'release-notes',
|
|
||||||
templateUrl: './release-notes.page.html',
|
templateUrl: './release-notes.page.html',
|
||||||
styleUrls: ['./release-notes.page.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ReleaseNotesPage {
|
export class ReleaseNotesPage {
|
||||||
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
|
readonly href = `/marketplace/${this.route.snapshot.paramMap.get('pkgId')}`
|
||||||
|
|
||||||
private selected: string | null = null
|
constructor(private readonly route: ActivatedRoute) {}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
|
export function spreadProgress(pkg: PackageDataEntry) {
|
||||||
|
pkg['install-progress'] = { ...pkg['install-progress'] }
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { PackageState } from '@start9labs/shared'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
import {
|
import {
|
||||||
InterfaceDef,
|
InterfaceDef,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PackageState } from '@start9labs/shared'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||||
import {
|
import {
|
||||||
DependencyErrorType,
|
DependencyErrorType,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AbstractApiService } from '@start9labs/shared'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
Http,
|
Http,
|
||||||
@@ -13,7 +14,10 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
import { RequestError } from '../http.service'
|
import { RequestError } from '../http.service'
|
||||||
import { map } from 'rxjs/operators'
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
export abstract class ApiService
|
||||||
|
extends AbstractApiService
|
||||||
|
implements Source<DataModel>, Http<DataModel>
|
||||||
|
{
|
||||||
protected readonly sync$ = new Subject<Update<DataModel>>()
|
protected readonly sync$ = new Subject<Update<DataModel>>()
|
||||||
|
|
||||||
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
|
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
|
||||||
@@ -24,9 +28,6 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
|||||||
.pipe(map(result => ({ result, jsonrpc: '2.0' })))
|
.pipe(map(result => ({ result, jsonrpc: '2.0' })))
|
||||||
}
|
}
|
||||||
|
|
||||||
// for getting static files: ex icons, instructions, licenses
|
|
||||||
abstract getStatic(url: string): Promise<string>
|
|
||||||
|
|
||||||
// db
|
// db
|
||||||
|
|
||||||
abstract getRevisions(since: number): Promise<RR.GetRevisionsRes>
|
abstract getRevisions(since: number): Promise<RR.GetRevisionsRes>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { InstallProgress, pauseFor } from '@start9labs/shared'
|
import { pauseFor } from '@start9labs/shared'
|
||||||
import { ApiService } from './embassy-api.service'
|
import { ApiService } from './embassy-api.service'
|
||||||
import { PatchOp, Update, Operation, RemoveOperation } from 'patch-db-client'
|
import { PatchOp, Update, Operation, RemoveOperation } from 'patch-db-client'
|
||||||
import { PackageState } from '@start9labs/shared'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
|
import { InstallProgress } from 'src/app/types/install-progress'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
DependencyErrorType,
|
DependencyErrorType,
|
||||||
@@ -192,6 +193,8 @@ export class MockApiService extends ApiService {
|
|||||||
return Mock.MarketplacePkgsList
|
return Mock.MarketplacePkgsList
|
||||||
} else if (path.startsWith('/package/v0/release-notes')) {
|
} else if (path.startsWith('/package/v0/release-notes')) {
|
||||||
return Mock.ReleaseNotes
|
return Mock.ReleaseNotes
|
||||||
|
} else if (path.includes('instructions') || path.includes('license')) {
|
||||||
|
return markdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PackageState } from '@start9labs/shared'
|
import { PackageState } from 'src/app/types/package-state'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
DependencyErrorType,
|
DependencyErrorType,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user