update/alpha.9 (#2988)

* import marketplac preview for sideload

* fix: improve state service (#2977)

* fix: fix sideload DI

* fix: update Angular

* fix: cleanup

* fix: fix version selection

* Bump node version to fix build for Angular

* misc fixes
- update node to v22
- fix chroot-and-upgrade access to prune-images
- don't self-migrate legacy packages
- #2985
- move dataVersion to volume folder
- remove "instructions.md" from s9pk
- add "docsUrl" to manifest

* version bump

* include flavor when clicking view listing from updates tab

* closes #2980

* fix: fix select button

* bring back ssh keys

* fix: drop 'portal' from all routes

* fix: implement longtap action to select table rows

* fix description for ssh page

* replace instructions with docsLink and refactor marketplace preview

* delete unused translations

* fix patchdb diffing algorithm

* continue refactor of marketplace lib show components

* Booting StartOS instead of Setting up your server on init

* misc fixes
- closes #2990
- closes #2987

* fix build

* docsUrl and clickable service headers

* don't cleanup after update until new service install succeeds

* update types

* misc fixes

* beta.35

* sdkversion, githash for sideload, correct logs for init, startos pubkey display

* bring back reboot button on install

* misc fixes

* beta.36

* better handling of setup and init for websocket errors

* reopen init and setup logs even on graceful closure

* better logging, misc fixes

* fix build

* dont let package stats hang

* dont show docsurl in marketplace if no docsurl

* re-add needs-config

* show error if init fails, shorten hover state on header icons

* fix operator precedemce

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Mariusz Kogen <k0gen@pm.me>
This commit is contained in:
Aiden McClelland
2025-07-18 18:31:12 +00:00
committed by GitHub
parent ba2906a42e
commit 377b7b12ce
237 changed files with 5953 additions and 4777 deletions

View File

@@ -4,20 +4,20 @@ import { knownRegistries, sameUrl } from '@start9labs/shared'
@Component({
selector: 'store-icon',
template: `
<img
*ngIf="icon; else noIcon"
[style.border-radius.%]="100"
[style.max-width]="size || '100%'"
[src]="icon"
alt="Registry Icon"
/>
<ng-template #noIcon>
@if (icon) {
<img
[style.border-radius.%]="100"
[style.max-width]="size || '100%'"
[src]="icon"
alt="Registry Icon"
/>
} @else {
<img
[style.max-width]="size || '100%'"
src="assets/img/storefront-outline.png"
alt="Registry Icon"
/>
</ng-template>
}
`,
styles: ':host { overflow: hidden; }',
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -1,52 +0,0 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { Exver, MarkdownPipe } from '@start9labs/shared'
import { TuiDialogContext } from '@taiga-ui/core'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { TuiAccordion } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { MarketplacePkg } from '../../src/types'
@Component({
template: `
<tui-accordion>
@for (note of notes | keyvalue: asIsOrder; track $index) {
<tui-accordion-item>
{{ note.key }}
<ng-template tuiAccordionItemContent>
<div [innerHTML]="note.value | markdown | dompurify"></div>
</ng-template>
</tui-accordion-item>
}
</tui-accordion>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, TuiAccordion, MarkdownPipe, NgDompurifyPipe],
})
export class ReleaseNotesComponent {
private readonly exver = inject(Exver)
private readonly pkg =
injectContext<TuiDialogContext<void, MarketplacePkg>>().data
readonly notes = 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,
},
)
asIsOrder(a: any, b: any) {
return 0
}
}
export const RELEASE_NOTES = new PolymorpheusComponent(ReleaseNotesComponent)

View File

@@ -16,8 +16,8 @@
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
top: 0;
left: 0;
z-index: -50;
border-radius: 1.5rem;
background-color: rgb(39 39 42);

View File

@@ -0,0 +1,139 @@
import {
ChangeDetectionStrategy,
Component,
inject,
input,
output,
} from '@angular/core'
import { MarketplacePkgBase } from '../../types'
import { CopyService } from '@start9labs/shared'
import { DatePipe } from '@angular/common'
import { MarketplaceItemComponent } from './item.component'
@Component({
selector: 'marketplace-about',
template: `
<div class="background-border box-shadow-lg shadow-color-light">
<div class="box-container">
<div class="detail-container">
<!-- version -->
<marketplace-item
[style.pointer-events]="'none'"
[data]="pkg().version"
label="Version"
icon=""
/>
<!-- release date -->
@if (pkg().s9pk?.publishedAt; as published) {
<marketplace-item
[style.pointer-events]="'none'"
[data]="(published | date: 'medium')!"
label="Released"
icon=""
/>
}
<!-- SDK version -->
<marketplace-item
[style.pointer-events]="'none'"
[data]="pkg().sdkVersion || 'Unknown'"
label="SDK Version"
icon=""
/>
<!-- git hash -->
@if (pkg().gitHash; as gitHash) {
<marketplace-item
(click)="copyService.copy(gitHash)"
[data]="gitHash"
label="Git Hash"
icon="@tui.copy"
class="item-copy"
/>
} @else {
<div class="item-padding">
<label tuiTitle>
<span tuiSubtitle>Git Hash</span>
Unknown
</label>
</div>
}
<!-- license -->
<marketplace-item
(click)="static.emit('license')"
[data]="pkg().license"
label="License"
icon="@tui.chevron-right"
class="item-pointer"
/>
</div>
</div>
</div>
<div class="background-border box-shadow-lg shadow-color-light">
<div class="box-container">
<p [innerHTML]="pkg().description.long"></p>
</div>
</div>
`,
styles: `
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.25rem 1.75rem;
p {
font-size: 1rem;
line-height: 1.5rem;
margin-bottom: 0.75rem;
pointer-events: none;
}
}
.detail-container {
display: grid;
grid-auto-flow: row;
grid-auto-columns: minmax(0, 1fr);
& > * + * {
border-top-width: 1px;
border-bottom-width: 0;
border-color: rgb(113 113 122);
}
}
.item-pointer:hover {
cursor: pointer;
::ng-deep label {
cursor: pointer;
}
}
.item-copy:hover {
cursor: copy;
::ng-deep label {
cursor: copy;
}
}
.item-padding {
padding: 0.75rem 0.25rem;
}
* {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: rgb(var(--tw-color-gray-200) / 1);
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarketplaceItemComponent, DatePipe],
})
export class MarketplaceAboutComponent {
readonly copyService = inject(CopyService)
readonly pkg = input.required<MarketplacePkgBase>()
readonly static = output<'license'>()
}

View File

@@ -1,29 +0,0 @@
<div class="background-border box-shadow-lg shadow-color-light">
<div class="box-container">
<h2 class="additional-detail-title">New in {{ pkg.version }}</h2>
<p [innerHTML]="pkg.releaseNotes | markdown | dompurify"></p>
<button
tuiButton
size="s"
appearance="whiteblock"
iconEnd="@tui.chevron-right"
(click)="onPast()"
>
Past Release Notes
</button>
<h2 class="additional-detail-title" [style.margin-top.rem]="2">About</h2>
<p [innerHTML]="pkg.description.long"></p>
<a
*ngIf="pkg.marketingSite as url"
tuiButton
iconEnd="@tui.external-link"
size="s"
appearance="secondary-grayscale"
target="_blank"
rel="noreferrer"
[href]="url"
>
View website
</a>
</div>
</div>

View File

@@ -1,12 +0,0 @@
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.75rem;
p {
font-size: 1rem;
line-height: 1.5rem;
margin-bottom: 0.75rem;
pointer-events: none;
}
}

View File

@@ -1,29 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
} from '@angular/core'
import { TuiDialogService } from '@taiga-ui/core'
import { RELEASE_NOTES } from '../../../modals/release-notes.component'
import { MarketplacePkgBase } from '../../../types'
@Component({
selector: 'marketplace-about',
templateUrl: 'about.component.html',
styleUrls: ['about.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class AboutComponent {
private readonly dialogs = inject(TuiDialogService)
@Input({ required: true })
pkg!: MarketplacePkgBase
async onPast() {
this.dialogs
.open(RELEASE_NOTES, { label: 'Past Release Notes', data: this.pkg })
.subscribe()
}
}

View File

@@ -1,23 +0,0 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { MarkdownPipe, SafeLinksDirective } from '@start9labs/shared'
import { TuiButton } from '@taiga-ui/core'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { TuiTagModule } from '@taiga-ui/legacy'
import { AboutComponent } from './about.component'
@NgModule({
imports: [
CommonModule,
RouterModule,
TuiTagModule,
NgDompurifyPipe,
SafeLinksDirective,
MarkdownPipe,
TuiButton,
],
declarations: [AboutComponent],
exports: [AboutComponent],
})
export class AboutModule {}

View File

@@ -1,28 +0,0 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplaceAdditionalItemComponent } from './additional-item.component'
@Component({
selector: 'marketplace-additional-link',
template: `
<a [href]="url" target="_blank" rel="noreferrer">
<marketplace-additional-item
[label]="label"
[icon]="icon"
[data]="url"
></marketplace-additional-item>
</a>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, MarketplaceAdditionalItemComponent],
})
export class MarketplaceAdditionalLinkComponent {
@Input({ required: true })
label!: string
@Input({ required: true })
icon!: string
@Input({ required: true })
url!: string
}

View File

@@ -1,78 +0,0 @@
<div class="background-border shadow-color-light box-shadow-lg">
<div class="box-container">
<h2 class="additional-detail-title">Information</h2>
<div class="detail-container">
<!-- release date -->
<marketplace-additional-item
*ngIf="pkg.s9pk?.publishedAt as published"
[data]="(published | date: 'medium')!"
label="Released"
icon=""
/>
<!-- git hash -->
<marketplace-additional-item
*ngIf="pkg.gitHash as gitHash; else noHash"
(click)="copyService.copy(gitHash)"
[data]="gitHash"
label="Git Hash"
icon="@tui.copy"
class="item-copy"
/>
<ng-template #noHash>
<div class="item-padding">
<label tuiTitle>
<span tuiSubtitle>Git Hash</span>
Unknown
</label>
</div>
</ng-template>
<!-- license -->
<marketplace-additional-item
(click)="static.emit('license')"
[data]="pkg.license"
label="License"
icon="@tui.chevron-right"
class="item-pointer"
/>
<!-- instructions -->
<marketplace-additional-item
(click)="static.emit('instructions')"
data="Click to view instructions"
label="Instructions"
icon="@tui.chevron-right"
class="item-pointer"
/>
<!-- versions -->
<ng-content />
<!-- links -->
<marketplace-additional-link
*ngIf="pkg.marketingSite"
[url]="pkg.marketingSite"
label="Marketing Site"
icon="@tui.external-link"
class="item-pointer"
/>
<marketplace-additional-link
*ngIf="pkg.upstreamRepo"
[url]="pkg.upstreamRepo"
label="Source Repository"
icon="@tui.external-link"
class="item-pointer"
/>
<marketplace-additional-link
*ngIf="pkg.wrapperRepo"
[url]="pkg.wrapperRepo"
label="Wrapper Repository"
icon="@tui.external-link"
class="item-pointer"
/>
<marketplace-additional-link
*ngIf="pkg.supportSite"
[url]="pkg.supportSite"
label="Support Site"
icon="@tui.external-link"
class="item-pointer"
/>
</div>
</div>
</div>

View File

@@ -1,46 +0,0 @@
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.75rem;
}
.detail-container {
display: grid;
grid-auto-flow: row;
grid-auto-columns: minmax(0, 1fr);
& > * + * {
border-top-width: 1px;
border-bottom-width: 0;
border-color: rgb(113 113 122);
}
}
.item-pointer:hover {
cursor: pointer;
::ng-deep label {
cursor: pointer;
}
}
.item-copy:hover {
cursor: copy;
::ng-deep label {
cursor: copy;
}
}
.item-padding {
padding: 0.75rem 0.25rem;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: rgb(var(--tw-color-gray-200) / 1);
}

View File

@@ -1,32 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
} from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { CopyService } from '@start9labs/shared'
import { MarketplacePkgBase } from '../../../types'
@Component({
selector: 'marketplace-additional',
templateUrl: 'additional.component.html',
styleUrls: ['additional.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class AdditionalComponent {
@Input({ required: true })
pkg!: MarketplacePkgBase
@Output()
readonly static = new EventEmitter<'license' | 'instructions'>()
constructor(
readonly copyService: CopyService,
private readonly route: ActivatedRoute,
) {}
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
}

View File

@@ -1,20 +0,0 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { AdditionalComponent } from './additional.component'
import { TuiButton, TuiLabel, TuiTitle } from '@taiga-ui/core'
import { MarketplaceAdditionalItemComponent } from './additional-item.component'
import { MarketplaceAdditionalLinkComponent } from './additional-link.component'
@NgModule({
imports: [
CommonModule,
TuiButton,
TuiLabel,
MarketplaceAdditionalItemComponent,
MarketplaceAdditionalLinkComponent,
TuiTitle,
],
declarations: [AdditionalComponent],
exports: [AdditionalComponent],
})
export class AdditionalModule {}

View File

@@ -1,4 +1,4 @@
import { CommonModule, KeyValue } from '@angular/common'
import { KeyValue } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { RouterModule } from '@angular/router'
import { ExverPipesModule } from '@start9labs/shared'
@@ -19,10 +19,11 @@ import { MarketplacePkgBase } from '../../../types'
{{ getTitle(dep.key) }}
</span>
<p>
<ng-container [ngSwitch]="dep.value.optional">
<span *ngSwitchCase="true">(optional)</span>
<span *ngSwitchCase="false">(required)</span>
</ng-container>
@if (dep.value.optional) {
<span>(optional)</span>
} @else {
<span>(required)</span>
}
</p>
</div>
</ng-template>
@@ -87,13 +88,7 @@ import { MarketplacePkgBase } from '../../../types'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
RouterModule,
TuiAvatar,
ExverPipesModule,
TuiLineClamp,
],
imports: [RouterModule, TuiAvatar, ExverPipesModule, TuiLineClamp],
})
export class MarketplaceDepItemComponent {
@Input({ required: true })

View File

@@ -4,7 +4,7 @@ import { SharedPipesModule } from '@start9labs/shared'
import { TuiTitle } from '@taiga-ui/core'
import { TuiAvatar } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { MarketplacePkg } from '../../../types'
import { MarketplacePkg } from '../../types'
@Component({
selector: 'marketplace-flavors',
@@ -33,7 +33,7 @@ import { MarketplacePkg } from '../../../types'
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.75rem;
padding: 1.25rem 1.75rem;
}
[tuiCell] {
@@ -44,7 +44,7 @@ import { MarketplacePkg } from '../../../types'
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterLink, TuiCell, TuiTitle, SharedPipesModule, TuiAvatar],
})
export class FlavorsComponent {
export class MarketplaceFlavorsComponent {
@Input()
pkgs!: MarketplacePkg[]
}

View File

@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
@@ -17,8 +16,6 @@ import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
<div class="dark-overlay"></div>
<div class="inner-container-title">
<h2 ticker>{{ pkg.title }}</h2>
<h3>{{ pkg.version }}</h3>
<p>{{ pkg.description.short }}</p>
</div>
<!-- control buttons -->
<ng-content />
@@ -43,7 +40,7 @@ import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
min-height: 32vh;
position: relative;
border-radius: 1.5rem;
padding: 4rem 2rem 0 2rem;
padding: 3rem 2rem 0 2rem;
@media (min-width: 376px) {
min-height: 20vh;
@@ -66,7 +63,7 @@ import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
}
.inner-container-title {
margin: 1rem 0;
margin: 0.5rem 0 1rem 0;
color: rgb(250 250 250);
mix-blend-mode: plus-lighter;
z-index: 1;
@@ -77,29 +74,11 @@ import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
}
h2 {
font-size: 2rem;
font-size: 2.5rem;
line-height: 3rem;
font-weight: normal;
display: inline-block;
margin-left: -1px;
}
h3 {
font-size: 1.1rem;
font-weight: normal;
margin-bottom: 1rem;
pointer-events: none;
}
p {
font-size: 1rem;
line-height: 1.5rem;
font-weight: 300;
pointer-events: none;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
margin: 1rem 0 1rem 0;
}
}
@@ -138,7 +117,7 @@ import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, SharedPipesModule, TickerComponent],
imports: [SharedPipesModule, TickerComponent],
})
export class MarketplacePackageHeroComponent {
@Input({ required: true })

View File

@@ -1,10 +1,9 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiFade } from '@taiga-ui/kit'
@Component({
selector: 'marketplace-additional-item',
selector: 'marketplace-item',
template: `
<label tuiTitle>
<span tuiSubtitle>{{ label }}</span>
@@ -35,9 +34,9 @@ import { TuiFade } from '@taiga-ui/kit'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, TuiIcon, TuiTitle, TuiFade],
imports: [TuiIcon, TuiTitle, TuiFade],
})
export class MarketplaceAdditionalItemComponent {
export class MarketplaceItemComponent {
@Input({ required: true })
label!: string

View File

@@ -0,0 +1,23 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplaceItemComponent } from './item.component'
@Component({
selector: 'marketplace-link',
template: `
<a [href]="url" target="_blank" rel="noreferrer">
<marketplace-item [label]="label" [icon]="icon" [data]="url" />
</a>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarketplaceItemComponent],
})
export class MarketplaceLinkComponent {
@Input({ required: true })
label!: string
@Input({ required: true })
icon!: string
@Input({ required: true })
url!: string
}

View File

@@ -0,0 +1,128 @@
import {
ChangeDetectionStrategy,
Component,
inject,
input,
} from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { CopyService } from '@start9labs/shared'
import { MarketplacePkgBase } from '../../types'
import { MarketplaceLinkComponent } from './link.component'
@Component({
selector: 'marketplace-links',
template: `
<div class="background-border shadow-color-light box-shadow-lg">
<div class="box-container">
<h2 class="additional-detail-title">Source Code</h2>
<div class="detail-container">
<marketplace-link
[url]="pkg().upstreamRepo"
label="Upstream Service"
icon="@tui.external-link"
class="item-pointer"
/>
<marketplace-link
[url]="pkg().wrapperRepo"
label="StartOS Package"
icon="@tui.external-link"
class="item-pointer"
/>
</div>
</div>
</div>
<div class="background-border shadow-color-light box-shadow-lg">
<div class="box-container">
<h2 class="additional-detail-title">Links</h2>
<div class="detail-container">
<marketplace-link
[url]="pkg().marketingSite"
label="Marketing"
icon="@tui.external-link"
class="item-pointer"
/>
@if (pkg().docsUrl; as docsUrl) {
<marketplace-link
[url]="docsUrl"
label="Documentation"
icon="@tui.external-link"
class="item-pointer"
/>
}
<marketplace-link
[url]="pkg().supportSite"
label="Support"
icon="@tui.external-link"
class="item-pointer"
/>
@if (pkg().donationUrl; as donationUrl) {
<marketplace-link
[url]="donationUrl"
label="Donations"
icon="@tui.external-link"
class="item-pointer"
/>
}
</div>
</div>
</div>
`,
styles: `
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.25rem 1.75rem;
}
.detail-container {
display: grid;
grid-auto-flow: row;
grid-auto-columns: minmax(0, 1fr);
& > * + * {
border-top-width: 1px;
border-bottom-width: 0;
border-color: rgb(113 113 122);
}
}
.item-pointer:hover {
cursor: pointer;
::ng-deep label {
cursor: pointer;
}
}
.item-copy:hover {
cursor: copy;
::ng-deep label {
cursor: copy;
}
}
.item-padding {
padding: 0.75rem 0.25rem;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: rgb(var(--tw-color-gray-200) / 1);
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarketplaceLinkComponent],
})
export class MarketplaceLinksComponent {
readonly copyService = inject(CopyService)
readonly url =
inject(ActivatedRoute).snapshot.queryParamMap.get('url') || undefined
readonly pkg = input.required<MarketplacePkgBase>()
}

View File

@@ -0,0 +1,28 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
import { MarkdownPipe } from '@start9labs/shared'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { MarketplacePkgBase } from '../../types'
@Component({
selector: 'marketplace-release-notes',
template: `
<div class="background-border box-shadow-lg shadow-color-light">
<div class="box-container">
<h2 class="additional-detail-title">New in {{ pkg().version }}</h2>
<p [innerHTML]="pkg().releaseNotes | markdown | dompurify"></p>
</div>
</div>
`,
styles: `
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.25rem 1.75rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgDompurifyPipe, MarkdownPipe],
})
export class MarketplaceReleaseNotesComponent {
readonly pkg = input.required<MarketplacePkgBase>()
}

View File

@@ -1,5 +1,3 @@
import { TuiCarousel } from '@taiga-ui/kit'
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
@@ -7,73 +5,72 @@ import {
Input,
} from '@angular/core'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import { TuiDialogContext, TuiDialogService, TuiButton } from '@taiga-ui/core'
import { MarketplacePkg } from '../../../types'
import { TuiButton, TuiDialogContext, TuiDialogService } from '@taiga-ui/core'
import { TuiCarousel } from '@taiga-ui/kit'
import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
import { MarketplacePkg } from '../../types'
@Component({
selector: 'marketplace-package-screenshots',
template: `
<!--@TODO future release-->
<div
*ngIf="$any(pkg).screenshots as screenshots"
tuiCarouselButtons
class="outer-container"
>
<button
tuiIconButton
appearance="flat-grayscale"
iconStart="@tui.chevron-left"
title="Previous"
type="button"
(click)="carousel.prev()"
></button>
<tui-carousel
#carousel
[itemsCount]="isMobile ? 1 : 2"
[(index)]="index"
class="carousel"
>
<ng-container *ngFor="let item of screenshots; let i = index">
<div
*tuiItem
draggable="false"
[class.item_active]="i === index + 1"
class="screenshot-item"
>
<img
#template
alt="Service screenshot"
src="assets/img/temp/{{ item }}"
class="screenshot-item-img"
(click)="presentModalImg(dialogTemplate)"
/>
<ng-template #dialogTemplate let-observer>
@if ($any(pkg).screenshots; as screenshots) {
<div tuiCarouselButtons class="outer-container">
<button
tuiIconButton
appearance="flat-grayscale"
iconStart="@tui.chevron-left"
title="Previous"
type="button"
(click)="carousel.prev()"
></button>
<tui-carousel
#carousel
[itemsCount]="isMobile ? 1 : 2"
[(index)]="index"
class="carousel"
>
@for (item of screenshots; track item; let i = $index) {
<div
*tuiItem
draggable="false"
[class.item_active]="i === index + 1"
class="screenshot-item"
>
<img
#template
alt="Service screenshot"
src="assets/img/temp/{{ item }}"
class="screenshot-item-img-enlarged"
class="screenshot-item-img"
(click)="presentModalImg(dialogTemplate)"
/>
</ng-template>
</div>
</ng-container>
</tui-carousel>
<button
tuiIconButton
appearance="flat-grayscale"
type="button"
iconStart="@tui.chevron-right"
title="Next"
(click)="carousel.next()"
></button>
</div>
<ng-template #dialogTemplate let-observer>
<img
alt="Service screenshot"
src="assets/img/temp/{{ item }}"
class="screenshot-item-img-enlarged"
/>
</ng-template>
</div>
}
</tui-carousel>
<button
tuiIconButton
appearance="flat-grayscale"
type="button"
iconStart="@tui.chevron-right"
title="Next"
(click)="carousel.next()"
></button>
</div>
}
`,
styles: `
.outer-container {
display: flex;
align-items: center;
align-content: center;
margin: 0px;
margin: 0;
@media (min-width: 1024px) {
margin-left: -3.5rem;
@@ -123,7 +120,7 @@ import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, TuiCarousel, TuiButton],
imports: [TuiCarousel, TuiButton],
})
export class MarketplacePackageScreenshotComponent {
private readonly dialogs = inject(TuiDialogService)

View File

@@ -0,0 +1,102 @@
import {
ChangeDetectionStrategy,
Component,
inject,
input,
output,
TemplateRef,
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import { DialogService, i18nPipe, SharedPipesModule } from '@start9labs/shared'
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
import { TuiRadioList } from '@taiga-ui/kit'
import { filter } from 'rxjs'
import { MarketplaceItemComponent } from './item.component'
@Component({
selector: 'marketplace-versions',
template: `
<div class="background-border shadow-color-light box-shadow-lg">
<div class="box-container">
<h2 class="additional-detail-title">Versions</h2>
<marketplace-item
(click)="promptSelectVersion(versionSelect)"
data="Select another version"
icon="@tui.chevron-right"
label=""
class="select"
/>
<ng-template
#versionSelect
let-data="data"
let-completeWith="completeWith"
>
<tui-radio-list [items]="versions()" [(ngModel)]="data.version" />
<footer class="buttons">
<button
tuiButton
appearance="secondary"
(click)="completeWith(null)"
>
{{ 'Cancel' | i18n }}
</button>
<button
tuiButton
appearance="secondary"
(click)="completeWith(data.version)"
>
{{ 'Ok' | i18n }}
</button>
</footer>
</ng-template>
</div>
</div>
`,
styles: `
.box-container {
background-color: rgb(39 39 42);
border-radius: 0.75rem;
padding: 1.25rem 1.75rem;
}
.select {
border: 0;
// border-top-width: 1px;
border-bottom-width: 1px;
border-color: rgb(113 113 122);
border-style: solid;
cursor: pointer;
::ng-deep label {
cursor: pointer;
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
MarketplaceItemComponent,
TuiButton,
SharedPipesModule,
FormsModule,
TuiRadioList,
i18nPipe,
],
})
export class MarketplaceVersionsComponent {
private readonly dialog = inject(DialogService)
readonly version = input.required<string | null>()
readonly versions = input.required<string[]>()
onVersion = output<string>()
promptSelectVersion(template: TemplateRef<TuiDialogContext>) {
this.dialog
.openComponent<string>(template, {
label: 'All versions',
size: 's',
data: { version: this.version() },
})
.pipe(filter(Boolean))
.subscribe(selected => this.onVersion.emit(selected))
}
}

View File

@@ -8,17 +8,17 @@ 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/show/about/about.component'
export * from './pages/show/about/about.module'
export * from './pages/show/additional/additional-link.component'
export * from './pages/show/additional/additional-item.component'
export * from './pages/show/additional/additional.component'
export * from './pages/show/additional/additional.module'
export * from './pages/show/link.component'
export * from './pages/show/item.component'
export * from './pages/show/links.component'
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/about.component'
export * from './pages/show/screenshots.component'
export * from './pages/show/hero.component'
export * from './pages/show/flavors.component'
export * from './pages/show/versions.component'
export * from './pages/show/release-notes.component'
export * from './pipes/filter-packages.pipe'