mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Feat/marketplace show links (#2105)
* closes #2084, rearranges marketplace show, app show, and donation link for Start9 * use url query param if present when fetching license and instructions * remove log * chore: Add some checking * chore: Update something about validation * chore: Update to use correct default Co-authored-by: BluJ <mogulslayer@gmail.com>
This commit is contained in:
committed by
Aiden McClelland
parent
212e94756b
commit
46222e9352
@@ -76,6 +76,9 @@ pub struct Manifest {
|
|||||||
pub dependencies: Dependencies,
|
pub dependencies: Dependencies,
|
||||||
#[model]
|
#[model]
|
||||||
pub containers: Option<DockerContainers>,
|
pub containers: Option<DockerContainers>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub replaces: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manifest {
|
impl Manifest {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ use crate::s9pk::docker::DockerReader;
|
|||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::{Error, ResultExt};
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
|
const MAX_REPLACES: usize = 10;
|
||||||
|
const MAX_TITLE_LEN: usize = 30;
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ReadHandle<'a, R = File> {
|
pub struct ReadHandle<'a, R = File> {
|
||||||
@@ -231,6 +234,35 @@ impl<R: AsyncRead + AsyncSeek + Unpin + Send + Sync> S9pkReader<R> {
|
|||||||
&validated_image_ids,
|
&validated_image_ids,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "js_engine")]
|
||||||
|
if man.containers.is_some()
|
||||||
|
|| matches!(man.main, crate::procedure::PackageProcedure::Script(_))
|
||||||
|
{
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Right now we don't support the containers and the long running main"),
|
||||||
|
crate::ErrorKind::ValidateS9pk,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if man.replaces.len() >= MAX_REPLACES {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Cannot have more than {MAX_REPLACES} replaces"),
|
||||||
|
crate::ErrorKind::ValidateS9pk,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(too_big) = man.replaces.iter().find(|x| x.len() >= MAX_REPLACES) {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("We have found a replaces of ({too_big}) that exceeds the max length of {MAX_TITLE_LEN} "),
|
||||||
|
crate::ErrorKind::ValidateS9pk,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if man.title.len() >= MAX_TITLE_LEN {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Cannot have more than a length of {MAX_TITLE_LEN} for title"),
|
||||||
|
crate::ErrorKind::ValidateS9pk,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if man.containers.is_some()
|
if man.containers.is_some()
|
||||||
&& matches!(man.main, crate::procedure::PackageProcedure::Docker(_))
|
&& matches!(man.main, crate::procedure::PackageProcedure::Docker(_))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/marketplace",
|
"name": "@start9labs/marketplace",
|
||||||
"version": "0.3.8",
|
"version": "0.3.9",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=13.2.0",
|
"@angular/common": ">=13.2.0",
|
||||||
"@angular/core": ">=13.2.0",
|
"@angular/core": ">=13.2.0",
|
||||||
|
|||||||
@@ -21,6 +21,6 @@
|
|||||||
<ion-item-divider>Description</ion-item-divider>
|
<ion-item-divider>Description</ion-item-divider>
|
||||||
<ion-item lines="none" color="transparent">
|
<ion-item lines="none" color="transparent">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
{{ pkg.manifest.description.long }}
|
<h2>{{ pkg.manifest.description.long }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
<div
|
||||||
|
*ngIf="pkg.manifest['marketing-site'] as url"
|
||||||
|
style="padding: 4px 0 10px 14px"
|
||||||
|
>
|
||||||
|
<ion-button [href]="url" target="_blank" rel="noreferrer" color="tertiary">
|
||||||
|
View marketing website
|
||||||
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="pkg.manifest.replaces as replaces">
|
||||||
|
<div *ngIf="replaces.length" class="ion-padding-bottom">
|
||||||
|
<ion-item-divider>Intended to replace</ion-item-divider>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let app of replaces">
|
||||||
|
{{ app }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ion-item-divider>Additional Info</ion-item-divider>
|
<ion-item-divider>Additional Info</ion-item-divider>
|
||||||
<ion-grid *ngIf="pkg.manifest as manifest">
|
<ion-grid *ngIf="pkg.manifest as manifest">
|
||||||
<ion-row>
|
<ion-row>
|
||||||
@@ -89,19 +110,6 @@
|
|||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
|
||||||
[href]="manifest['marketing-site']"
|
|
||||||
[disabled]="!manifest['marketing-site']"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
detail="false"
|
|
||||||
>
|
|
||||||
<ion-label>
|
|
||||||
<h2>Marketing Site</h2>
|
|
||||||
<p>{{ manifest['marketing-site'] || 'Not provided' }}</p>
|
|
||||||
</ion-label>
|
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
|
||||||
</ion-item>
|
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { MarketplacePkg } from '../../../types'
|
import { MarketplacePkg } from '../../../types'
|
||||||
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
|
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
|
||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-additional',
|
selector: 'marketplace-additional',
|
||||||
@@ -31,12 +32,15 @@ export class AdditionalComponent {
|
|||||||
@Output()
|
@Output()
|
||||||
version = new EventEmitter<string>()
|
version = new EventEmitter<string>()
|
||||||
|
|
||||||
|
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly emver: Emver,
|
private readonly emver: Emver,
|
||||||
private readonly marketplaceService: AbstractMarketplaceService,
|
private readonly marketplaceService: AbstractMarketplaceService,
|
||||||
private readonly toastCtrl: ToastController,
|
private readonly toastCtrl: ToastController,
|
||||||
|
private readonly route: ActivatedRoute,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async copy(address: string): Promise<void> {
|
async copy(address: string): Promise<void> {
|
||||||
@@ -84,6 +88,7 @@ export class AdditionalComponent {
|
|||||||
const content = this.marketplaceService.fetchStatic$(
|
const content = this.marketplaceService.fetchStatic$(
|
||||||
this.pkg.manifest.id,
|
this.pkg.manifest.id,
|
||||||
title,
|
title,
|
||||||
|
this.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
const modal = await this.modalCtrl.create({
|
const modal = await this.modalCtrl.create({
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface MarketplaceManifest<T = unknown> {
|
|||||||
short: string
|
short: string
|
||||||
long: string
|
long: string
|
||||||
}
|
}
|
||||||
|
replaces?: string[]
|
||||||
'release-notes': string
|
'release-notes': string
|
||||||
license: string // type of license
|
license: string // type of license
|
||||||
'wrapper-repo': Url
|
'wrapper-repo': Url
|
||||||
|
|||||||
@@ -36,6 +36,19 @@
|
|||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="chevron-forward"></ion-icon>
|
<ion-icon slot="end" name="chevron-forward"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item
|
||||||
|
[href]="manifest['marketing-site']"
|
||||||
|
[disabled]="!manifest['marketing-site']"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
detail="false"
|
||||||
|
>
|
||||||
|
<ion-label>
|
||||||
|
<h2>Marketing Site</h2>
|
||||||
|
<p>{{ manifest['marketing-site'] || 'Not provided' }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col sizeXs="12" sizeMd="6">
|
<ion-col sizeXs="12" sizeMd="6">
|
||||||
@@ -77,6 +90,19 @@
|
|||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item
|
||||||
|
[href]="manifest['donation-url']"
|
||||||
|
[disabled]="!manifest['donation-url']"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
detail="false"
|
||||||
|
>
|
||||||
|
<ion-label>
|
||||||
|
<h2>Donation Link</h2>
|
||||||
|
<p>{{ manifest['donation-url'] || 'Not provided' }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject, Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { DOCUMENT } from '@angular/common'
|
import { ModalController, NavController } from '@ionic/angular'
|
||||||
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
|
||||||
import { MarkdownComponent } from '@start9labs/shared'
|
import { MarkdownComponent } from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
@@ -26,8 +25,6 @@ export interface Button {
|
|||||||
})
|
})
|
||||||
export class ToButtonsPipe implements PipeTransform {
|
export class ToButtonsPipe implements PipeTransform {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DOCUMENT) private readonly document: Document,
|
|
||||||
private readonly alertCtrl: AlertController,
|
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly navCtrl: NavController,
|
private readonly navCtrl: NavController,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
@@ -97,13 +94,6 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
},
|
},
|
||||||
// view in marketplace
|
// view in marketplace
|
||||||
this.viewInMarketplaceButton(pkg),
|
this.viewInMarketplaceButton(pkg),
|
||||||
// donate
|
|
||||||
{
|
|
||||||
action: () => this.donate(pkg),
|
|
||||||
title: 'Donate',
|
|
||||||
description: `Support ${pkgTitle}`,
|
|
||||||
icon: 'logo-bitcoin',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,17 +138,4 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
|
|
||||||
return button
|
return button
|
||||||
}
|
}
|
||||||
|
|
||||||
private async donate({ manifest }: PackageDataEntry): Promise<void> {
|
|
||||||
const url = manifest['donation-url']
|
|
||||||
if (url) {
|
|
||||||
this.document.defaultView?.open(url, '_blank', 'noreferrer')
|
|
||||||
} else {
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
header: 'Not Accepting Donations',
|
|
||||||
message: `The developers of ${manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`,
|
|
||||||
})
|
|
||||||
await alert.present()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -519,6 +519,19 @@ export class ServerShowPage {
|
|||||||
detail: true,
|
detail: true,
|
||||||
disabled$: of(false),
|
disabled$: of(false),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Donate to Start9',
|
||||||
|
description: `Support embassyOS development`,
|
||||||
|
icon: 'logo-bitcoin',
|
||||||
|
action: () =>
|
||||||
|
this.document.defaultView?.open(
|
||||||
|
'https://donate.start9.com',
|
||||||
|
'_blank',
|
||||||
|
'noreferrer',
|
||||||
|
),
|
||||||
|
detail: true,
|
||||||
|
disabled$: of(false),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
Power: [
|
Power: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export module Mock {
|
|||||||
short: 'A Bitcoin full node by Bitcoin Core.',
|
short: 'A Bitcoin full node by Bitcoin Core.',
|
||||||
long: 'Bitcoin is a decentralized consensus protocol and settlement network.',
|
long: 'Bitcoin is a decentralized consensus protocol and settlement network.',
|
||||||
},
|
},
|
||||||
|
replaces: ['banks', 'governments'],
|
||||||
'release-notes': 'Taproot, Schnorr, and more.',
|
'release-notes': 'Taproot, Schnorr, and more.',
|
||||||
assets: {
|
assets: {
|
||||||
icon: 'icon.png',
|
icon: 'icon.png',
|
||||||
|
|||||||
Reference in New Issue
Block a user