Merge pull request #2638 from Start9Labs/update/marketplace-for-brochure

misc fixes and backwards compatibility with new registry types for brochure
This commit is contained in:
Matt Hill
2024-06-28 11:52:06 -06:00
committed by GitHub
23 changed files with 155 additions and 46 deletions

View File

@@ -48,7 +48,8 @@
<br /> <br />
## Running StartOS ## Running StartOS
There are multiple ways to get started with StartOS: > [!WARNING]
> StartOS is in beta. It lacks features. It doesn't always work perfectly. Start9 servers are not plug and play. Using them properly requires some effort and patience. Please do not use StartOS or purchase a server if you are unable or unwilling to follow instructions and learn new concepts.
### 💰 Buy a Start9 server ### 💰 Buy a Start9 server
This is the most convenient option. Simply [buy a server](https://store.start9.com) from Start9 and plug it in. This is the most convenient option. Simply [buy a server](https://store.start9.com) from Start9 and plug it in.

View File

@@ -74,12 +74,14 @@ async fn do_upload(
mut url: Url, mut url: Url,
user: &str, user: &str,
pass: &str, pass: &str,
pkg_id: &str,
body: Body, body: Body,
) -> Result<(), Error> { ) -> Result<(), Error> {
url.set_path("/admin/v0/upload"); url.set_path("/admin/v0/upload");
let req = httpc let req = httpc
.post(url) .post(url)
.header(header::ACCEPT, "text/plain") .header(header::ACCEPT, "text/plain")
.query(&[("id", pkg_id)])
.basic_auth(user, Some(pass)) .basic_auth(user, Some(pass))
.body(body) .body(body)
.build()?; .build()?;
@@ -198,6 +200,7 @@ pub async fn publish(
registry.clone(), registry.clone(),
&user, &user,
&pass, &pass,
&pkg.id,
Body::wrap_stream(file_stream), Body::wrap_stream(file_stream),
) )
.await?; .await?;

View File

@@ -169,8 +169,16 @@ echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/tor.key.gpg]
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o config/archives/docker.key curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o config/archives/docker.key
echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/docker.key.gpg] https://download.docker.com/linux/debian ${IB_SUITE} stable" > config/archives/docker.list echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/docker.key.gpg] https://download.docker.com/linux/debian ${IB_SUITE} stable" > config/archives/docker.list
curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/Debian_Testing/Release.key | gpg --dearmor -o config/archives/podman.key echo "deb http://deb.debian.org/debian/ trixie main contrib" > config/archives/trixie.list
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/podman.key.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/Debian_Testing/ /" > config/archives/podman.list cat > config/archives/trixie.pref <<- EOF
Package: *
Pin: release n=trixie
Pin-Priority: 100
Package: podman
Pin: release n=trixie
Pin-Priority: 600
EOF
# Dependencies # Dependencies

View File

@@ -1,6 +1,6 @@
{ {
"name": "@start9labs/marketplace", "name": "@start9labs/marketplace",
"version": "0.3.25", "version": "0.3.28",
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=13.2.0", "@angular/common": ">=13.2.0",
"@angular/core": ">=13.2.0", "@angular/core": ">=13.2.0",

View File

@@ -33,6 +33,7 @@ header {
1; 1;
border-width: 0; border-width: 0;
border-right: 0.125rem solid; border-right: 0.125rem solid;
overflow: visible;
} }
@media screen and (min-width: 1536px) { @media screen and (min-width: 1536px) {

View File

@@ -66,6 +66,7 @@ export class MenuComponent implements OnDestroy {
this.query = '' this.query = ''
this.categoryService.resetQuery() this.categoryService.resetQuery()
this.categoryService.changeCategory(category) this.categoryService.changeCategory(category)
this.categoryService.handleNavigation()
} }
onQueryChange(query: string): void { onQueryChange(query: string): void {

View File

@@ -32,7 +32,7 @@ button {
} }
@media (min-width: 640px) { @media (min-width: 640px) {
width: 120%; width: 108%;
padding: 0.5rem; padding: 0.5rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;

View File

@@ -32,7 +32,7 @@ export class CategoriesComponent {
case 'all': case 'all':
return 'tuiIconGridLarge' return 'tuiIconGridLarge'
case 'bitcoin': case 'bitcoin':
return 'tuiIconBitcoin' return 'assets/img/icons/logo-bitcoin.svg'
case 'messaging': case 'messaging':
case 'communications': case 'communications':
return 'tuiIconMessageCircleLarge' return 'tuiIconMessageCircleLarge'

View File

@@ -1,14 +1,20 @@
<div class="item-container box-shadow-lg"> <div
class="item-container box-shadow-lg"
*tuiLet="marketplace$ | async as marketplace"
>
<!-- color background --> <!-- color background -->
<div class="background"> <div class="background">
<img [src]="pkg.icon" alt="{{ pkg.manifest.title }} Icon" /> <img
[src]="determineIcon(marketplace)"
alt="{{ pkg.manifest.title }} Icon"
/>
</div> </div>
<!-- darkening overlay --> <!-- darkening overlay -->
<div class="overlay"></div> <div class="overlay"></div>
<!-- icon --> <!-- icon -->
<img <img
[src]="pkg.icon" [src]="determineIcon(marketplace)"
class="icon box-shadow-lg" class="icon"
alt="{{ pkg.manifest.title }} Icon" alt="{{ pkg.manifest.title }} Icon"
/> />
<div class="detail"> <div class="detail">

View File

@@ -1,10 +1,7 @@
.item-container { .item-container {
height: 100%;
position: relative; position: relative;
min-width: 300px;
border-radius: 1.5rem; border-radius: 1.5rem;
padding: 5rem 2rem 2rem 2.5rem; padding: 5rem 2rem 2rem 2.5rem;
gap: 1rem;
transition-property: transform; transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 500ms; transition-duration: 500ms;
@@ -56,21 +53,20 @@
top: -2.5rem; top: -2.5rem;
border-radius: 9999px; border-radius: 9999px;
object-fit: cover; object-fit: cover;
backdrop-filter: blur(24px);
background-color: rgb(0 0 0 / 0.5);
transform: none; transform: none;
} }
.detail { .detail {
margin-top: 0.75rem; margin-top: 0.75rem;
mix-blend-mode: plus-lighter; mix-blend-mode: plus-lighter;
&-title { &-title {
display: inline-block; display: inline-block;
font-size: 1.5rem; font-size: 1.5rem;
line-height: 2rem; line-height: 2rem;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
font-weight: 400; font-weight: 400;
will-change: transform, text-indent;
} }
&-description { &-description {

View File

@@ -1,5 +1,11 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import {
import { MarketplacePkg } from '../../../types' ChangeDetectionStrategy,
Component,
Input,
inject,
} from '@angular/core'
import { MarketplacePkg, StoreIdentity } from '../../../types'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({ @Component({
selector: 'marketplace-item', selector: 'marketplace-item',
@@ -10,4 +16,16 @@ import { MarketplacePkg } from '../../../types'
export class ItemComponent { export class ItemComponent {
@Input({ required: true }) @Input({ required: true })
pkg!: MarketplacePkg pkg!: MarketplacePkg
private readonly marketplaceService = inject(AbstractMarketplaceService)
readonly marketplace$ = this.marketplaceService.getSelectedHost$()
determineIcon(marketplace: StoreIdentity | null): string {
try {
const iconUrl = new URL(this.pkg.icon)
return iconUrl.href
} catch (e) {
return `${marketplace?.url}package/v0/icon/${this.pkg.manifest.id}`
}
}
} }

View File

@@ -3,10 +3,17 @@ import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharedPipesModule, TickerModule } from '@start9labs/shared' import { SharedPipesModule, TickerModule } from '@start9labs/shared'
import { ItemComponent } from './item.component' import { ItemComponent } from './item.component'
import { TuiLetModule } from '@taiga-ui/cdk'
@NgModule({ @NgModule({
declarations: [ItemComponent], declarations: [ItemComponent],
exports: [ItemComponent], exports: [ItemComponent],
imports: [CommonModule, RouterModule, SharedPipesModule, TickerModule], imports: [
CommonModule,
RouterModule,
SharedPipesModule,
TickerModule,
TuiLetModule,
],
}) })
export class ItemModule {} export class ItemModule {}

View File

@@ -39,7 +39,21 @@ export class ReleaseNotesComponent {
} }
asIsOrder(a: KeyValue<string, string>, b: KeyValue<string, string>) { asIsOrder(a: KeyValue<string, string>, b: KeyValue<string, string>) {
return a.key > b.key ? -1 : b.key > a.key ? 1 : 0 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>) { async showReleaseNotes(content: PolymorpheusContent<TuiDialogContext>) {

View File

@@ -1,19 +1,26 @@
import { CommonModule, KeyValue } from '@angular/common' import { CommonModule, KeyValue } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import {
ChangeDetectionStrategy,
Component,
Input,
inject,
} from '@angular/core'
import { EmverPipesModule } from '@start9labs/shared' import { EmverPipesModule } from '@start9labs/shared'
import { Dependency, MarketplacePkg } from '../../../types' import { Dependency, MarketplacePkg, StoreIdentity } from '../../../types'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { TuiAvatarModule, TuiLineClampModule } from '@taiga-ui/kit' import { TuiAvatarModule, TuiLineClampModule } from '@taiga-ui/kit'
import { TuiLetModule } from '@taiga-ui/cdk'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({ @Component({
selector: 'marketplace-dep-item', selector: 'marketplace-dep-item',
template: ` template: `
<div class="outer-container"> <div class="outer-container" *tuiLet="marketplace$ | async as marketplace">
<tui-avatar <tui-avatar
class="dep-img" class="dep-img"
[rounded]="true" [rounded]="true"
[size]="'l'" [size]="'l'"
[avatarUrl]="getImage(dep.key)" [avatarUrl]="getImage(dep.key, marketplace)"
></tui-avatar> ></tui-avatar>
<div> <div>
<tui-line-clamp <tui-line-clamp
@@ -103,6 +110,7 @@ import { TuiAvatarModule, TuiLineClampModule } from '@taiga-ui/kit'
TuiAvatarModule, TuiAvatarModule,
EmverPipesModule, EmverPipesModule,
TuiLineClampModule, TuiLineClampModule,
TuiLetModule,
], ],
}) })
export class MarketplaceDepItemComponent { export class MarketplaceDepItemComponent {
@@ -112,11 +120,24 @@ export class MarketplaceDepItemComponent {
@Input({ required: true }) @Input({ required: true })
dep!: KeyValue<string, Dependency> dep!: KeyValue<string, Dependency>
getImage(key: string): string { private readonly marketplaceService = inject(AbstractMarketplaceService)
readonly marketplace$ = this.marketplaceService.getSelectedHost$()
getImage(key: string, marketplace: StoreIdentity | null) {
const icon = this.pkg.dependencyMetadata[key]?.icon const icon = this.pkg.dependencyMetadata[key]?.icon
// @TODO fix when registry api is updated to include mimetype in icon url const camelToSnakeCase = (str: string) =>
// return icon ? `data:image/png;base64,${icon}` : key.substring(0, 2) str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
return icon ? icon : key.substring(0, 2)
if (icon) {
try {
const iconUrl = new URL(icon)
return iconUrl.href
} catch (e) {
return `${marketplace?.url}package/v0/icon/${camelToSnakeCase(key)}`
}
} else {
return key.substring(0, 2)
}
} }
getTitle(key: string): string { getTitle(key: string): string {

View File

@@ -1,23 +1,29 @@
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import {
ChangeDetectionStrategy,
Component,
Input,
inject,
} from '@angular/core'
import { SharedPipesModule, TickerModule } from '@start9labs/shared' import { SharedPipesModule, TickerModule } from '@start9labs/shared'
import { MarketplacePkg } from '../../../types' import { MarketplacePkg, StoreIdentity } from '../../../types'
import { TuiLetModule } from '@taiga-ui/cdk'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({ @Component({
selector: 'marketplace-package-hero', selector: 'marketplace-package-hero',
template: ` template: `
<div class="outer-container"> <div class="outer-container" *tuiLet="marketplace$ | async as marketplace">
<div class="inner-container box-shadow-lg"> <div class="inner-container box-shadow-lg">
<!-- icon --> <!-- icon -->
<img <img
[src]="pkg.icon | trustUrl" [src]="determineIcon(marketplace) | trustUrl"
class="box-shadow-lg"
alt="{{ pkg.manifest.title }} Icon" alt="{{ pkg.manifest.title }} Icon"
/> />
<!-- color background --> <!-- color background -->
<div class="color-background"> <div class="color-background">
<img <img
[src]="pkg.icon | trustUrl" [src]="determineIcon(marketplace) | trustUrl"
alt="{{ pkg.manifest.title }} background image" alt="{{ pkg.manifest.title }} background image"
/> />
</div> </div>
@@ -61,11 +67,11 @@ import { MarketplacePkg } from '../../../types'
padding: 4rem 2rem 0 2rem; padding: 4rem 2rem 0 2rem;
@media (min-width: 376px) { @media (min-width: 376px) {
min-height: 26vh; min-height: 20vh;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
min-height: 14rem; min-height: 11rem;
} }
img { img {
@@ -75,8 +81,6 @@ import { MarketplacePkg } from '../../../types'
border-radius: 9999px; border-radius: 9999px;
object-fit: cover; object-fit: cover;
position: absolute; position: absolute;
backdrop-filter: blur(24px);
background-color: rgb(0 0 0 / 0.5);
top: -2.25rem; top: -2.25rem;
left: 1.75rem; left: 1.75rem;
z-index: 1; z-index: 1;
@@ -157,9 +161,21 @@ import { MarketplacePkg } from '../../../types'
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [CommonModule, SharedPipesModule, TickerModule], imports: [CommonModule, SharedPipesModule, TickerModule, TuiLetModule],
}) })
export class MarketplacePackageHeroComponent { export class MarketplacePackageHeroComponent {
@Input({ required: true }) @Input({ required: true })
pkg!: MarketplacePkg pkg!: MarketplacePkg
private readonly marketplaceService = inject(AbstractMarketplaceService)
readonly marketplace$ = this.marketplaceService.getSelectedHost$()
determineIcon(marketplace: StoreIdentity | null) {
try {
const iconUrl = new URL(this.pkg.icon)
return iconUrl.href
} catch (e) {
return `${marketplace?.url}package/v0/icon/${this.pkg.manifest.id}`
}
}
} }

View File

@@ -13,4 +13,6 @@ export abstract class AbstractCategoryService {
abstract getQuery$(): Observable<string> abstract getQuery$(): Observable<string>
abstract resetQuery(): void abstract resetQuery(): void
abstract handleNavigation(): void
} }

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M410.47 279.2c-5-11.5-12.7-21.6-28.1-30.1a98.15 98.15 0 00-25.4-10 62.22 62.22 0 0016.3-11 56.37 56.37 0 0015.6-23.3 77.11 77.11 0 003.5-28.2c-1.1-16.8-4.4-33.1-13.2-44.8s-21.2-20.7-37.6-27c-12.6-4.8-25.5-7.8-45.5-8.9V32h-40v64h-32V32h-41v64H96v48h27.87c8.7 0 14.6.8 17.6 2.3a13.22 13.22 0 016.5 6c1.3 2.5 1.9 8.4 1.9 17.5V343c0 9-.6 14.8-1.9 17.4s-2 4.9-5.1 6.3-3.2 1.3-11.8 1.3h-26.4L96 416h87v64h41v-64h32v64h40v-64.4c26-1.3 44.5-4.7 59.4-10.3 19.3-7.2 34.1-17.7 44.7-31.5s14-34.9 14.93-51.2c.67-14.5-.03-33.2-4.56-43.4zM224 150h32v74h-32zm0 212v-90h32v90zm72-208.1c6 2.5 9.9 7.5 13.8 12.7 4.3 5.7 6.5 13.3 6.5 21.4 0 7.8-2.9 14.5-7.5 20.5-3.8 4.9-6.8 8.3-12.8 11.1zm28.8 186.7c-7.8 6.9-12.3 10.1-22.1 13.8a56.06 56.06 0 01-6.7 1.9v-82.8a40.74 40.74 0 0111.3 3.4c7.8 3.3 15.2 6.9 19.8 13.2a43.82 43.82 0 018 24.7c-.03 10.9-2.83 19.2-10.33 25.8z"/></svg>

After

Width:  |  Height:  |  Size: 943 B

View File

@@ -1,6 +1,6 @@
{ {
"name": "@start9labs/shared", "name": "@start9labs/shared",
"version": "0.3.12", "version": "0.3.13",
"peerDependencies": { "peerDependencies": {
"@angular/common": "^17.0.6", "@angular/common": "^17.0.6",
"@angular/core": "^17.0.6", "@angular/core": "^17.0.6",

View File

@@ -59,7 +59,9 @@ export class ServiceBackupsComponent {
) )
readonly next = computed(() => readonly next = computed(() =>
daysBetween(new Date(), new Date(this.pkg().nextBackup || new Date())), // TODO @lucy add this back in when types fixed for PackageDataEntry ie. when next/minor merge resolved
// daysBetween(new Date(), new Date(this.pkg().nextBackup || new Date())),
daysBetween(new Date(), new Date(new Date())),
) )
readonly ago = { readonly ago = {

View File

@@ -101,8 +101,7 @@ import { TuiScrollbarModule } from '@taiga-ui/core'
&-list { &-list {
display: grid; display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 4rem; gap: 4rem 3rem;
list-style-type: none;
padding: 1.5rem; padding: 1.5rem;
@media (min-width: 768px) { @media (min-width: 768px) {
@@ -111,9 +110,12 @@ import { TuiScrollbarModule } from '@taiga-ui/core'
@media (min-width: 1024px) { @media (min-width: 1024px) {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
@media (min-width: 1536px) { @media (min-width: 1280px) {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
@media (min-width: 1536px) {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.tile-wrapper { .tile-wrapper {
display: block; display: block;

View File

@@ -152,6 +152,10 @@ import { Router } from '@angular/router'
height: 100%; height: 100%;
place-self: center; place-self: center;
} }
marketplace-additional {
padding-bottom: 2rem;
}
`, `,
], ],
standalone: true, standalone: true,

View File

@@ -1,9 +1,12 @@
import { Injectable } from '@angular/core' import { Injectable, inject } from '@angular/core'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { AbstractCategoryService } from '@start9labs/marketplace' import { AbstractCategoryService } from '@start9labs/marketplace'
import { Router } from '@angular/router'
@Injectable() @Injectable()
export class CategoryService extends AbstractCategoryService { export class CategoryService extends AbstractCategoryService {
private readonly router = inject(Router)
getCategory$(): Observable<string> { getCategory$(): Observable<string> {
return this.category$ return this.category$
} }
@@ -23,4 +26,8 @@ export class CategoryService extends AbstractCategoryService {
resetQuery() { resetQuery() {
this.query$.next('') this.query$.next('')
} }
handleNavigation() {
this.router.navigate([])
}
} }

View File

@@ -1,6 +1,5 @@
{ {
"rules": { "rules": {
"no-unused-variable": true,
"no-unused-expression": true, "no-unused-expression": true,
"semicolon": [true, "never"], "semicolon": [true, "never"],
"no-trailing-whitespace": true, "no-trailing-whitespace": true,