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:
@@ -12,7 +12,6 @@ import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory'
|
||||
import { ConfigService } from './services/config.service'
|
||||
import { QrCodeModule } from 'ng-qrcode'
|
||||
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 { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
||||
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 { LiveApiService } from './services/api/embassy-live-api.service'
|
||||
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'
|
||||
|
||||
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||
@@ -45,7 +48,7 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||
}),
|
||||
QrCodeModule,
|
||||
OSWelcomePageModule,
|
||||
MarkdownPageModule,
|
||||
MarkdownModule,
|
||||
GenericInputComponentModule,
|
||||
MonacoEditorModule,
|
||||
SharedPipesModule,
|
||||
|
||||
@@ -10,10 +10,10 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import {
|
||||
ErrorToastService,
|
||||
getErrorMessage,
|
||||
DependentInfo,
|
||||
isEmptyObject,
|
||||
isObject,
|
||||
} from '@start9labs/shared'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { MarkdownPage } from './markdown.page'
|
||||
import {
|
||||
MarkdownPipeModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [MarkdownPage],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
MarkdownPipeModule,
|
||||
TextSpinnerComponentModule,
|
||||
],
|
||||
exports: [MarkdownPage],
|
||||
})
|
||||
export class MarkdownPageModule {}
|
||||
@@ -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,3 +0,0 @@
|
||||
.content-padding {
|
||||
padding: 0 16px 16px 16px
|
||||
}
|
||||
@@ -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 { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PackageState } from '@start9labs/shared'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import {
|
||||
PackageStatus,
|
||||
PrimaryStatus,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
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({
|
||||
selector: 'app-show-progress',
|
||||
|
||||
@@ -10,11 +10,8 @@ import {
|
||||
PackageDataEntry,
|
||||
Status,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
isEmptyObject,
|
||||
ErrorToastService,
|
||||
PackageState,
|
||||
} from '@start9labs/shared'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import {
|
||||
AlertController,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
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({
|
||||
name: 'installState',
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
||||
import { MarkdownComponent } from '@start9labs/shared'
|
||||
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'
|
||||
|
||||
export interface Button {
|
||||
@@ -105,7 +105,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
title: 'Instructions',
|
||||
contentUrl: pkg['static-files']['instructions'],
|
||||
},
|
||||
component: MarkdownPage,
|
||||
component: MarkdownComponent,
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
DependencyErrorType,
|
||||
PackageDataEntry,
|
||||
} 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 { ModalService } from 'src/app/services/modal.service'
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import * as yaml from 'js-yaml'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.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 { ErrorToastService } from '@start9labs/shared'
|
||||
import { debounce, ErrorToastService } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-config',
|
||||
|
||||
@@ -3,10 +3,12 @@ import { ActivatedRoute } from '@angular/router'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { take } from 'rxjs/operators'
|
||||
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 { debounce } from '../../../../../../shared/src/util/misc.util'
|
||||
import { MarkdownPage } from '../../../modals/markdown/markdown.page'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-instructions',
|
||||
@@ -44,7 +46,7 @@ export class DevInstructionsPage {
|
||||
title: 'Instructions Sample',
|
||||
content: this.code,
|
||||
},
|
||||
component: MarkdownPage,
|
||||
component: MarkdownComponent,
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
|
||||
@@ -1,62 +1,34 @@
|
||||
<h1 class="heading ion-text-center">{{ name }}</h1>
|
||||
|
||||
<ion-grid class="grid">
|
||||
<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>
|
||||
<marketplace-search [(query)]="query"></marketplace-search>
|
||||
|
||||
<ng-container *ngIf="pkgs && categories; else loading">
|
||||
<div class="hidden-scrollbar ion-text-center">
|
||||
<ion-button
|
||||
*ngFor="let cat of categories"
|
||||
fill="clear"
|
||||
class="category"
|
||||
[class.category_selected]="isSelected(cat)"
|
||||
(click)="switchCategory(cat)"
|
||||
>
|
||||
{{ cat }}
|
||||
</ion-button>
|
||||
</div>
|
||||
<marketplace-categories
|
||||
[categories]="categories"
|
||||
[category]="category"
|
||||
(categoryChange)="onCategoryChange($event)"
|
||||
></marketplace-categories>
|
||||
|
||||
<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">
|
||||
<h1>All services are up to date!</h1>
|
||||
</div>
|
||||
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let pkg of filtered" sizeXs="12" sizeSm="12" sizeMd="6">
|
||||
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trust" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2 class="pkg-title">
|
||||
{{ 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>
|
||||
<marketplace-item [pkg]="pkg">
|
||||
<marketplace-status
|
||||
class="status"
|
||||
[pkg]="localPkgs[pkg.manifest.id]"
|
||||
></marketplace-status>
|
||||
</marketplace-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #loading>
|
||||
<marketplace-list-skeleton></marketplace-list-skeleton>
|
||||
<marketplace-skeleton></marketplace-skeleton>
|
||||
</ng-template>
|
||||
|
||||
@@ -12,35 +12,6 @@
|
||||
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 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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({
|
||||
selector: 'marketplace-list-content',
|
||||
@@ -12,7 +14,7 @@ export class MarketplaceListContentComponent {
|
||||
pkgs: MarketplacePkg[] | null = null
|
||||
|
||||
@Input()
|
||||
localPkgs: Record<string, LocalPkg> = {}
|
||||
localPkgs: Record<string, PackageDataEntry> = {}
|
||||
|
||||
@Input()
|
||||
categories: Set<string> | null = null
|
||||
@@ -23,11 +25,7 @@ export class MarketplaceListContentComponent {
|
||||
category = 'featured'
|
||||
query = ''
|
||||
|
||||
isSelected(category: string) {
|
||||
return category === this.category && !this.query
|
||||
}
|
||||
|
||||
switchCategory(category: string): void {
|
||||
onCategoryChange(category: string): void {
|
||||
this.category = category
|
||||
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,38 +0,0 @@
|
||||
<div class="hidden-scrollbar ion-text-center">
|
||||
<ion-button *ngFor="let cat of ['', '', '', '', '', '', '']" fill="clear">
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 80px; border-radius: 0"
|
||||
></ion-skeleton-text>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<div class="divider" style="margin: 24px 0"></div>
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col
|
||||
*ngFor="let pkg of ['', '', '', '']"
|
||||
sizeXs="12"
|
||||
sizeSm="12"
|
||||
sizeMd="6"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-thumbnail slot="start">
|
||||
<ion-skeleton-text
|
||||
style="border-radius: 100%"
|
||||
animated
|
||||
></ion-skeleton-text>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 150px; height: 18px; margin-bottom: 8px"
|
||||
></ion-skeleton-text>
|
||||
<ion-skeleton-text animated style="width: 400px"></ion-skeleton-text>
|
||||
<ion-skeleton-text animated style="width: 100px"></ion-skeleton-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
@@ -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,
|
||||
TextSpinnerComponentModule,
|
||||
} 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 { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||
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'
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -33,21 +37,15 @@ const routes: Routes = [
|
||||
TextSpinnerComponentModule,
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarketplacePipesModule,
|
||||
FilterPackagesPipeModule,
|
||||
MarketplaceStatusModule,
|
||||
BadgeMenuComponentModule,
|
||||
ItemModule,
|
||||
CategoriesModule,
|
||||
SearchModule,
|
||||
SkeletonModule,
|
||||
],
|
||||
declarations: [
|
||||
MarketplaceListPage,
|
||||
MarketplaceListHeaderComponent,
|
||||
MarketplaceListContentComponent,
|
||||
MarketplaceListSkeletonComponent,
|
||||
],
|
||||
exports: [
|
||||
MarketplaceListPage,
|
||||
MarketplaceListHeaderComponent,
|
||||
MarketplaceListContentComponent,
|
||||
MarketplaceListSkeletonComponent,
|
||||
],
|
||||
declarations: [MarketplaceListPage, MarketplaceListContentComponent],
|
||||
exports: [MarketplaceListPage, MarketplaceListContentComponent],
|
||||
})
|
||||
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">
|
||||
<marketplace-list-content
|
||||
|
||||
@@ -4,20 +4,20 @@ import { filter, first, map, startWith, switchMapTo, tap } from 'rxjs/operators'
|
||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||
import {
|
||||
AbstractMarketplaceService,
|
||||
LocalPkg,
|
||||
MarketplacePkg,
|
||||
spreadProgress,
|
||||
} from '@start9labs/marketplace'
|
||||
|
||||
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({
|
||||
selector: 'marketplace-list',
|
||||
templateUrl: './marketplace-list.page.html',
|
||||
})
|
||||
export class MarketplaceListPage {
|
||||
readonly localPkgs$: Observable<Record<string, LocalPkg>> = defer(() =>
|
||||
this.patch.watch$('package-data'),
|
||||
readonly localPkgs$: Observable<Record<string, PackageDataEntry>> = defer(
|
||||
() => this.patch.watch$('package-data'),
|
||||
).pipe(
|
||||
filter(data => exists(data) && !isEmptyObject(data)),
|
||||
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<!-- release notes -->
|
||||
<ion-item-divider>
|
||||
New in {{ pkg.manifest.version | displayEmver }}
|
||||
<ion-button routerLink="notes" class="all-notes" fill="clear" color="dark">
|
||||
All Release Notes
|
||||
<ion-icon slot="end" name="arrow-forward-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item-divider>
|
||||
<ion-item lines="none" color="transparent">
|
||||
<ion-label>
|
||||
<div
|
||||
class="release-notes"
|
||||
[innerHTML]="pkg.manifest['release-notes'] | markdown"
|
||||
></div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<!-- description -->
|
||||
<ion-item-divider>Description</ion-item-divider>
|
||||
<ion-item lines="none" color="transparent">
|
||||
<ion-label>
|
||||
<div class="release-notes">{{ pkg.manifest.description.long }}</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
@@ -1,9 +0,0 @@
|
||||
.all-notes {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.release-notes {
|
||||
overflow: auto;
|
||||
max-height: 120px;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<ion-item-divider>Additional Info</ion-item-divider>
|
||||
<ion-card>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col sizeSm="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
<ion-item button detail="false" (click)="presentAlertVersions()">
|
||||
<ion-label>
|
||||
<h2>Other Versions</h2>
|
||||
<p>Click to view other versions</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item button detail="false" (click)="presentModalMd('license')">
|
||||
<ion-label>
|
||||
<h2>License</h2>
|
||||
<p>{{ pkg.manifest.license }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
button
|
||||
detail="false"
|
||||
(click)="presentModalMd('instructions')"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Instructions</h2>
|
||||
<p>Click to view instructions</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-col>
|
||||
<ion-col sizeSm="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
<ion-item
|
||||
[href]="pkg.manifest['upstream-repo']"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Source Repository</h2>
|
||||
<p>{{ pkg.manifest['upstream-repo'] }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="pkg.manifest['wrapper-repo']"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Wrapper Repository</h2>
|
||||
<p>{{ pkg.manifest['wrapper-repo'] }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="pkg.manifest['support-site']"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Support Site</h2>
|
||||
<p>{{ pkg.manifest['support-site'] }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-card>
|
||||
@@ -1,71 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core'
|
||||
import { AlertController, ModalController } from '@ionic/angular'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { displayEmver, Emver } from '@start9labs/shared'
|
||||
|
||||
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-additional',
|
||||
templateUrl: 'marketplace-show-additional.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceShowAdditionalComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
|
||||
@Output()
|
||||
version = new EventEmitter<string>()
|
||||
|
||||
constructor(
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly emver: Emver,
|
||||
) {}
|
||||
|
||||
async presentAlertVersions() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Versions',
|
||||
inputs: this.pkg.versions
|
||||
.sort((a, b) => -1 * this.emver.compare(a, b))
|
||||
.map(v => ({
|
||||
name: v, // for CSS
|
||||
type: 'radio',
|
||||
label: displayEmver(v), // appearance on screen
|
||||
value: v, // literal SEM version value
|
||||
checked: this.pkg.manifest.version === v,
|
||||
})),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: (version: string) => this.version.emit(version),
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async presentModalMd(title: string) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
title,
|
||||
contentUrl: `/marketplace${this.pkg[title]}`,
|
||||
},
|
||||
component: MarkdownPage,
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,14 @@ import { AlertController, ModalController, NavController } from '@ionic/angular'
|
||||
import {
|
||||
AbstractMarketplaceService,
|
||||
MarketplacePkg,
|
||||
LocalPkg,
|
||||
} 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 { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||
@@ -22,7 +25,7 @@ export class MarketplaceShowControlsComponent {
|
||||
pkg: MarketplacePkg
|
||||
|
||||
@Input()
|
||||
localPkg: LocalPkg
|
||||
localPkg: PackageDataEntry
|
||||
|
||||
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 -->
|
||||
<ion-item lines="none" *ngIf="dependentInfo" class="rec-item">
|
||||
<ion-item *ngIf="dependentInfo" lines="none" class="rec-item">
|
||||
<ion-label>
|
||||
<h2 class="heading">
|
||||
<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 { DependentInfo } from '@start9labs/shared'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-dependent',
|
||||
@@ -12,7 +18,10 @@ export class MarketplaceShowDependentComponent {
|
||||
@Input()
|
||||
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 {
|
||||
return this.pkg?.manifest.title || ''
|
||||
|
||||
@@ -8,16 +8,18 @@ import {
|
||||
MarkdownPipeModule,
|
||||
TextSpinnerComponentModule,
|
||||
} 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 { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||
import { MarketplaceShowPage } from './marketplace-show.page'
|
||||
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.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'
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -36,27 +38,24 @@ const routes: Routes = [
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
MarketplacePipesModule,
|
||||
MarketplaceStatusModule,
|
||||
InstallWizardComponentModule,
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
DependenciesModule,
|
||||
AdditionalModule,
|
||||
],
|
||||
declarations: [
|
||||
MarketplaceShowPage,
|
||||
MarketplaceShowHeaderComponent,
|
||||
MarketplaceShowControlsComponent,
|
||||
MarketplaceShowDependentComponent,
|
||||
MarketplaceShowAboutComponent,
|
||||
MarketplaceShowDependenciesComponent,
|
||||
MarketplaceShowAdditionalComponent,
|
||||
],
|
||||
exports: [
|
||||
MarketplaceShowPage,
|
||||
MarketplaceShowHeaderComponent,
|
||||
MarketplaceShowControlsComponent,
|
||||
MarketplaceShowDependentComponent,
|
||||
MarketplaceShowAboutComponent,
|
||||
MarketplaceShowDependenciesComponent,
|
||||
MarketplaceShowAdditionalComponent,
|
||||
],
|
||||
})
|
||||
export class MarketplaceShowPageModule {}
|
||||
|
||||
@@ -2,72 +2,53 @@
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ng-container *ngIf="pkg$ | async as pkg else loading">
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
|
||||
<div class="header">
|
||||
<img alt="" [src]="getIcon(pkg.icon) | trust" />
|
||||
<div class="header-text">
|
||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="header-version">
|
||||
{{ pkg.manifest.version | displayEmver }}
|
||||
</p>
|
||||
<marketplace-status
|
||||
class="header-status"
|
||||
[pkg]="localPkg$ | async"
|
||||
></marketplace-status>
|
||||
</div>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ion-col
|
||||
sizeXl="3"
|
||||
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]"
|
||||
<ng-container *ngIf="!(pkg | empty)">
|
||||
<marketplace-package [pkg]="pkg">
|
||||
<marketplace-status
|
||||
class="status"
|
||||
[pkg]="localPkg$ | async"
|
||||
></marketplace-status>
|
||||
<marketplace-show-controls
|
||||
slot="controls"
|
||||
[pkg]="pkg"
|
||||
[localPkg]="localPkg$ | async"
|
||||
></marketplace-show-controls>
|
||||
<ion-row *ngIf="localPkg$ | async">
|
||||
<ion-col
|
||||
sizeXl="3"
|
||||
sizeLg="3"
|
||||
sizeMd="3"
|
||||
sizeSm="12"
|
||||
sizeXs="12"
|
||||
class="ion-align-self-center"
|
||||
>
|
||||
View Service
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<ion-button
|
||||
expand="block"
|
||||
fill="outline"
|
||||
color="primary"
|
||||
[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>
|
||||
<marketplace-show-about [pkg]="pkg"></marketplace-show-about>
|
||||
<ion-item-group>
|
||||
<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"
|
||||
></marketplace-show-dependencies>
|
||||
</ion-item-group>
|
||||
|
||||
<marketplace-show-additional
|
||||
[pkg]="pkg"
|
||||
(version)="loadVersion$.next($event)"
|
||||
></marketplace-show-additional>
|
||||
(version)="loadVersion$.next($event)"
|
||||
></marketplace-additional>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #loading>
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
.header {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
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);
|
||||
}
|
||||
}
|
||||
.status {
|
||||
font-size: calc(16px + 1vw);
|
||||
}
|
||||
|
||||
@@ -2,21 +2,15 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import {
|
||||
LocalPkg,
|
||||
MarketplacePkg,
|
||||
AbstractMarketplaceService,
|
||||
spreadProgress,
|
||||
} from '@start9labs/marketplace'
|
||||
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 {
|
||||
catchError,
|
||||
filter,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap,
|
||||
} from 'rxjs/operators'
|
||||
import { catchError, filter, shareReplay, switchMap, tap } from 'rxjs/operators'
|
||||
|
||||
import { spreadProgress } from '../utils/spread-progress'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show',
|
||||
@@ -32,16 +26,14 @@ export class MarketplaceShowPage {
|
||||
readonly localPkg$ = defer(() =>
|
||||
this.patch.watch$('package-data', this.pkgId),
|
||||
).pipe(
|
||||
filter<LocalPkg>(Boolean),
|
||||
filter<PackageDataEntry>(Boolean),
|
||||
tap(spreadProgress),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
)
|
||||
|
||||
readonly pkg$: Observable<MarketplacePkg> = this.loadVersion$.pipe(
|
||||
switchMap(version =>
|
||||
this.marketplaceService
|
||||
.getPackage(this.pkgId, version)
|
||||
.pipe(startWith(null)),
|
||||
this.marketplaceService.getPackage(this.pkgId, version),
|
||||
),
|
||||
// TODO: Better fallback
|
||||
catchError(e => this.errToast.present(e) && of({} as MarketplacePkg)),
|
||||
@@ -57,16 +49,4 @@ export class MarketplaceShowPage {
|
||||
getIcon(icon: string): string {
|
||||
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
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { InstallProgress } from 'src/app/types/install-progress'
|
||||
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||
|
||||
@Pipe({
|
||||
name: 'installProgress',
|
||||
})
|
||||
export class InstallProgressPipe implements PipeTransform {
|
||||
transform(loadData: InstallProgress): string {
|
||||
const { totalProgress } = packageLoadingProgress(loadData)
|
||||
|
||||
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,5 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #none>
|
||||
<div>Not Installed</div>
|
||||
<ion-text style="color: var(--ion-color-step-450)">Not Installed</ion-text>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { LocalPkg } from '@start9labs/marketplace'
|
||||
import { PackageState } from '@start9labs/shared'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-status',
|
||||
@@ -8,7 +8,7 @@ import { PackageState } from '@start9labs/shared'
|
||||
})
|
||||
export class MarketplaceStatusComponent {
|
||||
@Input()
|
||||
pkg?: LocalPkg
|
||||
pkg?: PackageDataEntry
|
||||
|
||||
PackageState = PackageState
|
||||
|
||||
|
||||
@@ -2,18 +2,13 @@ import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
import { MarketplacePipesModule } from '@start9labs/marketplace'
|
||||
|
||||
import { InstallProgressPipe } from './install-progress.pipe'
|
||||
import { MarketplaceStatusComponent } from './marketplace-status.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
EmverPipesModule,
|
||||
MarketplacePipesModule,
|
||||
],
|
||||
declarations: [MarketplaceStatusComponent],
|
||||
imports: [CommonModule, IonicModule, EmverPipesModule],
|
||||
declarations: [MarketplaceStatusComponent, InstallProgressPipe],
|
||||
exports: [MarketplaceStatusComponent],
|
||||
})
|
||||
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 { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import {
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
TextSpinnerComponentModule,
|
||||
ElementModule,
|
||||
} from '@start9labs/shared'
|
||||
import { MarketplacePipesModule } from '@start9labs/marketplace'
|
||||
import { ReleaseNotesModule } from '@start9labs/marketplace'
|
||||
|
||||
import { ReleaseNotesPage } from './release-notes.page'
|
||||
import { ReleaseNotesHeaderComponent } from './release-notes-header/release-notes-header.component'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -21,17 +13,8 @@ const routes: Routes = [
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TextSpinnerComponentModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
MarketplacePipesModule,
|
||||
ElementModule,
|
||||
],
|
||||
declarations: [ReleaseNotesPage, ReleaseNotesHeaderComponent],
|
||||
exports: [ReleaseNotesPage, ReleaseNotesHeaderComponent],
|
||||
imports: [IonicModule, ReleaseNotesModule, RouterModule.forChild(routes)],
|
||||
declarations: [ReleaseNotesPage],
|
||||
exports: [ReleaseNotesPage],
|
||||
})
|
||||
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>
|
||||
<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>
|
||||
<release-notes></release-notes>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
.panel {
|
||||
margin: 0;
|
||||
padding: 0 24px;
|
||||
transition: max-height 0.2s ease-out;
|
||||
}
|
||||
|
||||
.active {
|
||||
border: 5px solid #4d4d4d;
|
||||
}
|
||||
|
||||
.version-button {
|
||||
height: 50px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.version {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
}
|
||||
@@ -1,38 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
|
||||
@Component({
|
||||
selector: 'release-notes',
|
||||
templateUrl: './release-notes.page.html',
|
||||
styleUrls: ['./release-notes.page.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
constructor(private readonly route: ActivatedRoute) {}
|
||||
}
|
||||
|
||||
@@ -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 { PackageState } from '@start9labs/shared'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import {
|
||||
InterfaceDef,
|
||||
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 {
|
||||
DependencyErrorType,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AbstractApiService } from '@start9labs/shared'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import {
|
||||
Http,
|
||||
@@ -13,7 +14,10 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { RequestError } from '../http.service'
|
||||
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>>()
|
||||
|
||||
/** 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' })))
|
||||
}
|
||||
|
||||
// for getting static files: ex icons, instructions, licenses
|
||||
abstract getStatic(url: string): Promise<string>
|
||||
|
||||
// db
|
||||
|
||||
abstract getRevisions(since: number): Promise<RR.GetRevisionsRes>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { InstallProgress, pauseFor } from '@start9labs/shared'
|
||||
import { pauseFor } from '@start9labs/shared'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
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 {
|
||||
DataModel,
|
||||
DependencyErrorType,
|
||||
@@ -192,6 +193,8 @@ export class MockApiService extends ApiService {
|
||||
return Mock.MarketplacePkgsList
|
||||
} else if (path.startsWith('/package/v0/release-notes')) {
|
||||
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 {
|
||||
DataModel,
|
||||
DependencyErrorType,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { WorkspaceConfig, PackageState } from '@start9labs/shared'
|
||||
import { WorkspaceConfig } from '@start9labs/shared'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import {
|
||||
InterfaceDef,
|
||||
PackageDataEntry,
|
||||
|
||||
@@ -15,9 +15,9 @@ import { ServerInfo } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import {
|
||||
catchError,
|
||||
finalize,
|
||||
map,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap,
|
||||
} from 'rxjs/operators'
|
||||
@@ -56,11 +56,11 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly emver: Emver,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly emver: Emver,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -79,12 +79,17 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
||||
|
||||
getPackage(id: string, version: string): Observable<MarketplacePkg> {
|
||||
const params = { ids: [{ id, version }] }
|
||||
|
||||
return this.init$.pipe(
|
||||
const fallback$ = this.init$.pipe(
|
||||
switchMap(({ url }) => from(this.getMarketplacePkgs(params, url))),
|
||||
map(pkgs => pkgs.find(pkg => pkg.manifest.id == id)),
|
||||
map(pkgs => this.findPackage(pkgs, id, version)),
|
||||
startWith(null),
|
||||
)
|
||||
|
||||
return this.getPackages().pipe(
|
||||
map(pkgs => this.findPackage(pkgs, id, version)),
|
||||
switchMap(pkg => (pkg ? of(pkg) : fallback$)),
|
||||
tap(pkg => {
|
||||
if (!pkg) {
|
||||
if (pkg === undefined) {
|
||||
throw new Error(`No results for ${id}${version ? ' ' + version : ''}`)
|
||||
}
|
||||
}),
|
||||
@@ -103,26 +108,6 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
||||
)
|
||||
}
|
||||
|
||||
// async install(id: string, version?: string): Promise<void> {
|
||||
// const loader = await this.loadingCtrl.create({
|
||||
// spinner: 'lines',
|
||||
// message: 'Beginning Installation',
|
||||
// cssClass: 'loader',
|
||||
// })
|
||||
// loader.present()
|
||||
//
|
||||
// try {
|
||||
// await this.installPackage({
|
||||
// id,
|
||||
// 'version-spec': version ? `=${version}` : undefined,
|
||||
// })
|
||||
// } catch (e) {
|
||||
// this.errToast.present(e)
|
||||
// } finally {
|
||||
// loader.dismiss()
|
||||
// }
|
||||
// }
|
||||
|
||||
install(id: string, version?: string): Observable<unknown> {
|
||||
return defer(() =>
|
||||
from(
|
||||
@@ -161,6 +146,20 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
||||
)
|
||||
}
|
||||
|
||||
getPackageMarkdown(type: string, pkgId: string): Observable<string> {
|
||||
return this.getMarketplace().pipe(
|
||||
switchMap(({ url }) =>
|
||||
from(
|
||||
this.api.marketplaceProxy<string>(
|
||||
`/package/v0/${type}/${pkgId}`,
|
||||
{},
|
||||
url,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async getMarketplaceData(
|
||||
params: RR.GetMarketplaceDataReq,
|
||||
url: string,
|
||||
@@ -217,4 +216,18 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private findPackage(
|
||||
pkgs: readonly MarketplacePkg[],
|
||||
id: string,
|
||||
version: string,
|
||||
): MarketplacePkg | undefined {
|
||||
return pkgs.find(pkg => {
|
||||
const versionIsSame =
|
||||
version === '*' ||
|
||||
this.emver.compare(pkg.manifest.version, version) === 0
|
||||
|
||||
return pkg.manifest.id === id && versionIsSame
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { DependentInfo } from '@start9labs/shared'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import { AppConfigPage } from 'src/app/modals/app-config/app-config.page'
|
||||
|
||||
@Injectable({
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import { InstallProgress, PackageState, Url } from '@start9labs/shared'
|
||||
import { Url } from '@start9labs/shared'
|
||||
import { MarketplaceManifest } from '@start9labs/marketplace'
|
||||
import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import { InstallProgress } from 'src/app/types/install-progress'
|
||||
|
||||
export interface DataModel {
|
||||
'server-info': ServerInfo
|
||||
@@ -111,7 +113,7 @@ export interface CurrentDependencyInfo {
|
||||
'health-checks': string[] // array of health check IDs
|
||||
}
|
||||
|
||||
export interface Manifest extends MarketplaceManifest {
|
||||
export interface Manifest extends MarketplaceManifest<DependencyConfig> {
|
||||
main: ActionImpl
|
||||
'health-checks': Record<
|
||||
string,
|
||||
@@ -125,7 +127,11 @@ export interface Manifest extends MarketplaceManifest {
|
||||
migrations: Migrations
|
||||
actions: Record<string, Action>
|
||||
permissions: any // @TODO 0.3.1
|
||||
dependencies: DependencyInfo
|
||||
}
|
||||
|
||||
export interface DependencyConfig {
|
||||
check: ActionImpl
|
||||
'auto-configure': ActionImpl
|
||||
}
|
||||
|
||||
export interface ActionImpl {
|
||||
@@ -352,28 +358,3 @@ export interface DependencyErrorHealthChecksFailed {
|
||||
export interface DependencyErrorTransitive {
|
||||
type: DependencyErrorType.Transitive
|
||||
}
|
||||
|
||||
export interface DependencyInfo {
|
||||
[id: string]: DependencyEntry
|
||||
}
|
||||
|
||||
export interface DependencyEntry {
|
||||
version: string
|
||||
requirement:
|
||||
| {
|
||||
type: 'opt-in'
|
||||
how: string
|
||||
}
|
||||
| {
|
||||
type: 'opt-out'
|
||||
how: string
|
||||
}
|
||||
| {
|
||||
type: 'required'
|
||||
}
|
||||
description: string | null
|
||||
config: {
|
||||
check: ActionImpl
|
||||
'auto-configure': ActionImpl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isEmptyObject, PackageState } from '@start9labs/shared'
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
import { PackageState } from 'src/app/types/package-state'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
|
||||
5
frontend/projects/ui/src/app/types/dependent-info.ts
Normal file
5
frontend/projects/ui/src/app/types/dependent-info.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface DependentInfo {
|
||||
id: string
|
||||
title: string
|
||||
version?: string
|
||||
}
|
||||
9
frontend/projects/ui/src/app/types/install-progress.ts
Normal file
9
frontend/projects/ui/src/app/types/install-progress.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface InstallProgress {
|
||||
size: number | null
|
||||
downloaded: number
|
||||
'download-complete': boolean
|
||||
validated: number
|
||||
'validation-complete': boolean
|
||||
unpacked: number
|
||||
'unpack-complete': boolean
|
||||
}
|
||||
7
frontend/projects/ui/src/app/types/package-state.ts
Normal file
7
frontend/projects/ui/src/app/types/package-state.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum PackageState {
|
||||
Installing = 'installing',
|
||||
Installed = 'installed',
|
||||
Updating = 'updating',
|
||||
Removing = 'removing',
|
||||
Restoring = 'restoring',
|
||||
}
|
||||
7
frontend/projects/ui/src/app/types/progress-data.ts
Normal file
7
frontend/projects/ui/src/app/types/progress-data.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface ProgressData {
|
||||
totalProgress: number
|
||||
downloadProgress: number
|
||||
validateProgress: number
|
||||
unpackProgress: number
|
||||
isComplete: boolean
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
renderPkgStatus,
|
||||
StatusRendering,
|
||||
} from '../services/pkg-status-rendering.service'
|
||||
import { packageLoadingProgress, ProgressData } from '@start9labs/shared'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { packageLoadingProgress } from './package-loading-progress'
|
||||
|
||||
export function getPackageInfo(entry: PackageDataEntry): PkgInfo {
|
||||
const statuses = renderPkgStatus(entry)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
import { InstallProgress } from 'src/app/types/install-progress'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
|
||||
export function packageLoadingProgress(
|
||||
loadData: InstallProgress,
|
||||
): ProgressData | null {
|
||||
if (isEmptyObject(loadData)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let {
|
||||
downloaded,
|
||||
validated,
|
||||
unpacked,
|
||||
size,
|
||||
'download-complete': downloadComplete,
|
||||
'validation-complete': validationComplete,
|
||||
'unpack-complete': unpackComplete,
|
||||
} = loadData
|
||||
|
||||
// only permit 100% when "complete" == true
|
||||
downloaded = downloadComplete ? size : Math.max(downloaded - 1, 0)
|
||||
validated = validationComplete ? size : Math.max(validated - 1, 0)
|
||||
unpacked = unpackComplete ? size : Math.max(unpacked - 1, 0)
|
||||
|
||||
const downloadWeight = 1
|
||||
const validateWeight = 0.2
|
||||
const unpackWeight = 0.7
|
||||
|
||||
const numerator = Math.floor(
|
||||
downloadWeight * downloaded +
|
||||
validateWeight * validated +
|
||||
unpackWeight * unpacked,
|
||||
)
|
||||
|
||||
const denominator = Math.floor(
|
||||
size * (downloadWeight + validateWeight + unpackWeight),
|
||||
)
|
||||
const totalProgress = Math.floor((100 * numerator) / denominator)
|
||||
|
||||
return {
|
||||
totalProgress,
|
||||
downloadProgress: Math.floor((100 * downloaded) / size),
|
||||
validateProgress: Math.floor((100 * validated) / size),
|
||||
unpackProgress: Math.floor((100 * unpacked) / size),
|
||||
isComplete: downloadComplete && validationComplete && unpackComplete,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user