Merge branch 'next/minor' of github.com:Start9Labs/start-os into next/major

This commit is contained in:
Matt Hill
2024-08-08 10:52:49 -06:00
765 changed files with 43858 additions and 19423 deletions

View File

@@ -0,0 +1,70 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
} from '@angular/core'
import { AbstractMarketplaceService } from '../../services/marketplace.service'
import { MarketplacePkg } from '../../types'
import { map } from 'rxjs'
import { Exver } from '@start9labs/shared'
// @TODO Alex use Taiga modal
import { ModalController } from '@ionic/angular'
@Component({
selector: 'release-notes',
templateUrl: './release-notes.component.html',
styleUrls: ['./release-notes.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReleaseNotesComponent {
@Input() pkg!: MarketplacePkg
private selected: string | null = null
readonly notes$ = this.marketplaceService.getSelectedStore$().pipe(
map(s => {
return Object.entries(this.pkg.otherVersions)
.filter(
([v, _]) =>
this.exver.getFlavor(v) === this.pkg.flavor &&
this.exver.compareExver(this.pkg.version, v) === 1,
)
.reduce(
(obj, [version, info]) => ({
...obj,
[version]: info.releaseNotes,
}),
{
[`${this.pkg.version} (current)`]: this.pkg.releaseNotes,
},
)
}),
)
constructor(
private readonly marketplaceService: AbstractMarketplaceService,
private readonly exver: Exver,
private readonly modalCtrl: ModalController,
) {}
async dismiss() {
return this.modalCtrl.dismiss()
}
isSelected(key: string): boolean {
return this.selected === key
}
setSelected(selected: string) {
this.selected = this.isSelected(selected) ? null : selected
}
getDocSize(key: string, { nativeElement }: ElementRef<HTMLElement>) {
return this.isSelected(key) ? nativeElement.scrollHeight : 0
}
asIsOrder(a: any, b: any) {
return 0
}
}

View File

@@ -2,7 +2,7 @@ import { TuiAccordion } from '@taiga-ui/kit'
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import {
EmverPipesModule,
ExverPipesModule,
MarkdownPipeModule,
SafeLinksDirective,
} from '@start9labs/shared'
@@ -12,11 +12,12 @@ import {
FilterVersionsPipe,
ReleaseNotesComponent,
} from './release-notes.component'
import { ReleaseNotesComponent } from './release-notes.component'
@NgModule({
imports: [
CommonModule,
EmverPipesModule,
ExverPipesModule,
MarkdownPipeModule,
NgDompurifyModule,
SafeLinksDirective,
@@ -28,4 +29,4 @@ import {
declarations: [ReleaseNotesComponent],
exports: [ReleaseNotesComponent],
})
export class ReleaseNotesModule {}
export class ReleaseNotesComponentModule {}

View File

@@ -5,6 +5,7 @@ import {
Input,
Output,
} from '@angular/core'
import { T } from '@start9labs/start-sdk'
@Component({
selector: 'marketplace-categories',
@@ -14,7 +15,7 @@ import {
})
export class CategoriesComponent {
@Input()
categories?: string[]
categories!: Map<string, T.Category>
@Input()
category = ''

View File

@@ -6,7 +6,7 @@
<div class="background">
<img
[src]="determineIcon(marketplace)"
alt="{{ pkg.manifest.title }} Icon"
alt="{{ pkg.title }} Icon"
/>
</div>
<!-- darkening overlay -->
@@ -15,14 +15,32 @@
<img
[src]="determineIcon(marketplace)"
class="icon"
alt="{{ pkg.manifest.title }} Icon"
alt="{{ pkg.title }} Icon"
/>
<div class="detail">
<span class="detail-title" ticker>
{{ pkg.manifest.title }}
{{ pkg.title }}
</span>
<span class="detail-description">
{{ pkg.manifest.description.short }}
{{ pkg.description.short }}
</span>
</div>
</div>
<!-- @TODO Alex -->
<!-- <ion-item
class="service-card"
[routerLink]="['/marketplace', pkg.id]"
[queryParams]="{ flavor: pkg.flavor, version: pkg.version }"
>
<ion-thumbnail slot="start">
<img alt="" [src]="pkg.icon | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2 class="montserrat">
<strong>{{ pkg.title }}</strong>
</h2>
<h3>{{ pkg.description.short }}</h3>
<ng-content></ng-content>
</ion-label>
</ion-item> -->

View File

@@ -1,80 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
Inject,
Input,
Pipe,
PipeTransform,
} from '@angular/core'
import { AbstractMarketplaceService } from '../../services/marketplace.service'
import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
import { TuiDialogContext, TuiDialogService } from '@taiga-ui/core'
import { MarketplacePkg } from '../../types'
import { Observable } from 'rxjs'
import { Emver } from '@start9labs/shared'
import { KeyValue } from '@angular/common'
@Component({
selector: 'release-notes',
templateUrl: './release-notes.component.html',
styleUrls: ['./release-notes.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReleaseNotesComponent {
constructor(
private readonly emver: Emver,
private readonly marketplaceService: AbstractMarketplaceService,
@Inject(TuiDialogService) private readonly dialogs: TuiDialogService,
) {}
@Input({ required: true })
pkg!: MarketplacePkg
notes$!: Observable<Record<string, string>>
ngOnChanges() {
this.notes$ = this.marketplaceService.fetchReleaseNotes$(
this.pkg.manifest.id,
)
}
asIsOrder(a: KeyValue<string, string>, b: KeyValue<string, string>) {
const a1 = a.key.split('.')
const b1 = b.key.split('.')
// contingency in case there's a 4th or 5th version
const len = Math.min(a1.length, b1.length)
// look through each version number and compare.
for (let i = 0; i < len; i++) {
const a2 = +a1[i] || 0
const b2 = +b1[i] || 0
if (a2 !== b2) {
// sort descending
return a2 > b2 ? -1 : 1
}
}
return a1.length - b1.length
}
async showReleaseNotes(content: PolymorpheusContent<TuiDialogContext>) {
this.dialogs
.open(content, {
label: 'Previous Release Notes',
})
.subscribe()
}
}
@Pipe({
name: 'filterVersions',
standalone: true,
})
export class FilterVersionsPipe implements PipeTransform {
transform(
notes: Record<string, string>,
pkgVersion: string,
): Record<string, string> {
delete notes[pkgVersion]
return notes
}
}

View File

@@ -1,5 +1,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types'
import { ModalController } from '@ionic/angular'
import { ReleaseNotesComponent } from '../../../modals/release-notes/release-notes.component'
@Component({
selector: 'marketplace-about',
@@ -10,4 +12,15 @@ import { MarketplacePkg } from '../../../types'
export class AboutComponent {
@Input({ required: true })
pkg!: MarketplacePkg
constructor(private readonly modalCtrl: ModalController) {}
async presentModalNotes() {
const modal = await this.modalCtrl.create({
componentProps: { pkg: this.pkg },
component: ReleaseNotesComponent,
})
await modal.present()
}
}

View File

@@ -11,7 +11,7 @@
></marketplace-additional-item>
<!-- git hash -->
<marketplace-additional-item
*ngIf="pkg.manifest.gitHash as gitHash; else noHash"
*ngIf="pkg.gitHash as gitHash; else noHash"
(click)="copyService.copy(gitHash)"
[data]="gitHash"
label="Git Hash"
@@ -29,7 +29,7 @@
<!-- license -->
<marketplace-additional-item
(click)="presentModalMd('License')"
[data]="pkg.manifest.license"
[data]="pkg.license"
label="License"
icon="@tui.chevron-right"
class="item-pointer"
@@ -46,29 +46,29 @@
<ng-content />
<!-- links -->
<marketplace-additional-link
*ngIf="pkg.manifest.marketingSite"
[url]="pkg.manifest.marketingSite"
*ngIf="pkg.marketingSite"
[url]="pkg.marketingSite"
label="Marketing Site"
icon="@tui.external-link"
class="item-pointer"
></marketplace-additional-link>
<marketplace-additional-link
*ngIf="pkg.manifest.upstreamRepo"
[url]="pkg.manifest.upstreamRepo"
*ngIf="pkg.upstreamRepo"
[url]="pkg.upstreamRepo"
label="Source Repository"
icon="@tui.external-link"
class="item-pointer"
></marketplace-additional-link>
<marketplace-additional-link
*ngIf="pkg.manifest.wrapperRepo"
[url]="pkg.manifest.wrapperRepo"
*ngIf="pkg.wrapperRepo"
[url]="pkg.wrapperRepo"
label="Wrapper Repository"
icon="@tui.external-link"
class="item-pointer"
></marketplace-additional-link>
<marketplace-additional-link
*ngIf="pkg.manifest.supportSite"
[url]="pkg.manifest.supportSite"
*ngIf="pkg.supportSite"
[url]="pkg.supportSite"
label="Support Site"
icon="@tui.external-link"
class="item-pointer"
@@ -76,3 +76,98 @@
</div>
</div>
</div>
<!-- <ion-item-divider>Additional Info</ion-item-divider>
<ion-grid *ngIf="pkg">
<ion-row>
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
*ngIf="pkg.gitHash as gitHash; else noHash"
button
detail="false"
(click)="copy(gitHash)"
>
<ion-label>
<h2>Git Hash</h2>
<p>{{ gitHash }}</p>
</ion-label>
<ion-icon slot="end" name="copy-outline"></ion-icon>
</ion-item>
<ng-template #noHash>
<ion-item>
<ion-label>
<h2>Git Hash</h2>
<p>Unknown</p>
</ion-label>
</ion-item>
</ng-template>
<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"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('license')">
<ion-label>
<h2>License</h2>
<p>{{ pkg.license }}</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward"></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"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
[href]="pkg.upstreamRepo"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Source Repository</h2>
<p>{{ pkg.upstreamRepo }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item
[href]="pkg.wrapperRepo"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Wrapper Repository</h2>
<p>{{ pkg.wrapperRepo }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item
[href]="pkg.supportSite"
[disabled]="!pkg.supportSite"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Support Site</h2>
<p>{{ pkg.supportSite || 'Not provided' }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
</ion-row>
</ion-grid> -->

View File

@@ -7,7 +7,7 @@ import {
import { ActivatedRoute } from '@angular/router'
import { TuiDialogService } from '@taiga-ui/core'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { CopyService, MarkdownComponent } from '@start9labs/shared'
import { CopyService, Exver, MarkdownComponent } from '@start9labs/shared'
import { MarketplacePkg } from '../../../types'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@@ -38,7 +38,7 @@ export class AdditionalComponent {
size: 'l',
data: {
content: this.marketplaceService.fetchStatic$(
this.pkg.manifest.id,
this.pkg.id,
label.toLowerCase(),
this.url,
),

View File

@@ -0,0 +1,21 @@
<ion-item-divider>Alternative Implementations</ion-item-divider>
<ion-grid>
<ion-row>
<ion-col *ngFor="let pkg of pkgs" responsiveCol sizeSm="12" sizeMd="6">
<ion-item
[routerLink]="['/marketplace', pkg.id]"
[queryParams]="{ flavor: pkg.flavor }"
>
<ion-thumbnail slot="start">
<img alt="" style="border-radius: 100%" [src]="pkg.icon | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2>
{{ pkg.title }}
</h2>
<p>{{ pkg.version }}</p>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types'
@Component({
selector: 'marketplace-flavors',
templateUrl: 'flavors.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FlavorsComponent {
@Input()
pkgs!: MarketplacePkg[]
}

View File

@@ -0,0 +1,19 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { ResponsiveColModule, SharedPipesModule } from '@start9labs/shared'
import { FlavorsComponent } from './flavors.component'
@NgModule({
imports: [
CommonModule,
RouterModule,
IonicModule,
SharedPipesModule,
ResponsiveColModule,
],
declarations: [FlavorsComponent],
exports: [FlavorsComponent],
})
export class FlavorsModule {}

View File

@@ -26,11 +26,11 @@ export class FilterPackagesPipe implements PipeTransform {
distance: 16,
keys: [
{
name: 'manifest.title',
name: 'title',
weight: 1,
},
{
name: 'manifest.id',
name: 'id',
weight: 0.5,
},
],
@@ -42,19 +42,19 @@ export class FilterPackagesPipe implements PipeTransform {
useExtendedSearch: true,
keys: [
{
name: 'manifest.title',
name: 'title',
weight: 1,
},
{
name: 'manifest.id',
name: 'id',
weight: 0.5,
},
{
name: 'manifest.description.short',
name: 'description.short',
weight: 0.4,
},
{
name: 'manifest.description.long',
name: 'description.long',
weight: 0.1,
},
],
@@ -71,7 +71,8 @@ export class FilterPackagesPipe implements PipeTransform {
.filter(p => category === 'all' || p.categories.includes(category!))
.sort((a, b) => {
return (
new Date(b.publishedAt).valueOf() - new Date(a.publishedAt).valueOf()
new Date(b.s9pk.publishedAt).valueOf() -
new Date(a.s9pk.publishedAt).valueOf()
)
})
.map(a => ({ ...a }))

View File

@@ -8,8 +8,6 @@ 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/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-link.component'
@@ -20,6 +18,8 @@ export * from './pages/show/dependencies/dependencies.component'
export * from './pages/show/dependencies/dependency-item.component'
export * from './pages/show/screenshots/screenshots.component'
export * from './pages/show/hero/hero.component'
export * from './pages/show/flavors/flavors.component'
export * from './pages/show/flavors/flavors.module'
export * from './pipes/filter-packages.pipe'

View File

@@ -20,18 +20,13 @@ export abstract class AbstractMarketplaceService {
abstract getPackage$(
id: string,
version: string,
version: string | null,
flavor: string | null,
url?: string,
): Observable<MarketplacePkg> // could be {} so need to check in show page
abstract fetchReleaseNotes$(
id: string,
url?: string,
): Observable<Record<string, string>>
): Observable<MarketplacePkg>
abstract fetchStatic$(
id: string,
type: string,
url?: string,
pkg: MarketplacePkg,
type: 'LICENSE.md' | 'instructions.md',
): Observable<string>
}

View File

@@ -1,49 +1,39 @@
import { Url } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
export type StoreURL = string
export type StoreName = string
export interface StoreIdentity {
url: StoreURL
name?: StoreName
export type GetPackageReq = {
id: string
version: string | null
otherVersions: 'short'
}
export type GetPackageRes = T.GetPackageResponse & {
otherVersions: { [version: string]: T.PackageInfoShort }
}
export type Marketplace = Record<StoreURL, StoreData | null>
export interface StoreData {
info: StoreInfo
export type GetPackagesReq = {
id: null
version: null
otherVersions: 'short'
}
export type GetPackagesRes = {
[id: T.PackageId]: GetPackageRes
}
export type StoreIdentity = {
url: string
name?: string
}
export type Marketplace = Record<string, StoreData | null>
export type StoreData = {
info: T.RegistryInfo
packages: MarketplacePkg[]
}
export interface StoreInfo {
name: StoreName
categories: string[]
}
export type StoreIdentityWithData = StoreData & StoreIdentity
export interface MarketplacePkg {
icon: Url
license: Url
screenshots?: string[]
instructions: Url
manifest: T.Manifest
categories: string[]
versions: string[]
dependencyMetadata: {
[id: string]: DependencyMetadata
export type MarketplacePkg = T.PackageVersionInfo &
Omit<GetPackageRes, 'best'> & {
id: T.PackageId
version: string
flavor: string | null
}
publishedAt: string
}
export interface DependencyMetadata {
title: string
icon: Url
optional: boolean
hidden: boolean
}
export interface Dependency {
description: string | null
optional: boolean
}