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 />
## 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
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,
user: &str,
pass: &str,
pkg_id: &str,
body: Body,
) -> Result<(), Error> {
url.set_path("/admin/v0/upload");
let req = httpc
.post(url)
.header(header::ACCEPT, "text/plain")
.query(&[("id", pkg_id)])
.basic_auth(user, Some(pass))
.body(body)
.build()?;
@@ -198,6 +200,7 @@ pub async fn publish(
registry.clone(),
&user,
&pass,
&pkg.id,
Body::wrap_stream(file_stream),
)
.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
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 [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
echo "deb http://deb.debian.org/debian/ trixie main contrib" > config/archives/trixie.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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ export class CategoriesComponent {
case 'all':
return 'tuiIconGridLarge'
case 'bitcoin':
return 'tuiIconBitcoin'
return 'assets/img/icons/logo-bitcoin.svg'
case 'messaging':
case 'communications':
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 -->
<div class="background">
<img [src]="pkg.icon" alt="{{ pkg.manifest.title }} Icon" />
<img
[src]="determineIcon(marketplace)"
alt="{{ pkg.manifest.title }} Icon"
/>
</div>
<!-- darkening overlay -->
<div class="overlay"></div>
<!-- icon -->
<img
[src]="pkg.icon"
class="icon box-shadow-lg"
[src]="determineIcon(marketplace)"
class="icon"
alt="{{ pkg.manifest.title }} Icon"
/>
<div class="detail">

View File

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

View File

@@ -1,5 +1,11 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types'
import {
ChangeDetectionStrategy,
Component,
Input,
inject,
} from '@angular/core'
import { MarketplacePkg, StoreIdentity } from '../../../types'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({
selector: 'marketplace-item',
@@ -10,4 +16,16 @@ import { MarketplacePkg } from '../../../types'
export class ItemComponent {
@Input({ required: true })
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 { SharedPipesModule, TickerModule } from '@start9labs/shared'
import { ItemComponent } from './item.component'
import { TuiLetModule } from '@taiga-ui/cdk'
@NgModule({
declarations: [ItemComponent],
exports: [ItemComponent],
imports: [CommonModule, RouterModule, SharedPipesModule, TickerModule],
imports: [
CommonModule,
RouterModule,
SharedPipesModule,
TickerModule,
TuiLetModule,
],
})
export class ItemModule {}

View File

@@ -39,7 +39,21 @@ export class ReleaseNotesComponent {
}
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>) {

View File

@@ -1,19 +1,26 @@
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 { Dependency, MarketplacePkg } from '../../../types'
import { Dependency, MarketplacePkg, StoreIdentity } from '../../../types'
import { RouterModule } from '@angular/router'
import { TuiAvatarModule, TuiLineClampModule } from '@taiga-ui/kit'
import { TuiLetModule } from '@taiga-ui/cdk'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({
selector: 'marketplace-dep-item',
template: `
<div class="outer-container">
<div class="outer-container" *tuiLet="marketplace$ | async as marketplace">
<tui-avatar
class="dep-img"
[rounded]="true"
[size]="'l'"
[avatarUrl]="getImage(dep.key)"
[avatarUrl]="getImage(dep.key, marketplace)"
></tui-avatar>
<div>
<tui-line-clamp
@@ -103,6 +110,7 @@ import { TuiAvatarModule, TuiLineClampModule } from '@taiga-ui/kit'
TuiAvatarModule,
EmverPipesModule,
TuiLineClampModule,
TuiLetModule,
],
})
export class MarketplaceDepItemComponent {
@@ -112,11 +120,24 @@ export class MarketplaceDepItemComponent {
@Input({ required: true })
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
// @TODO fix when registry api is updated to include mimetype in icon url
// return icon ? `data:image/png;base64,${icon}` : key.substring(0, 2)
return icon ? icon : key.substring(0, 2)
const camelToSnakeCase = (str: string) =>
str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
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 {

View File

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

View File

@@ -59,7 +59,9 @@ export class ServiceBackupsComponent {
)
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 = {

View File

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

View File

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

View File

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

View File

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