rework installing page and add cancel install button (#2915)

* rework installing page and add cancel install button

* actually call cancel endpoint

* fix two bugs

* include translations in progress component

* cancellable installs

* fix: comments (#2916)

* fix: comments

* delete comments

* ensure trailing slash and no qp for new registry url

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* fix raspi

* bump sdk

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Alex Inkin <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2025-04-30 13:50:08 -06:00
committed by GitHub
parent 5c473eb9cc
commit e6f0067728
37 changed files with 431 additions and 269 deletions

View File

@@ -319,6 +319,9 @@ export namespace RR {
export type InstallPackageReq = T.InstallParams
export type InstallPackageRes = null
export type CancelInstallPackageReq = { id: string }
export type CancelInstallPackageRes = null
export type GetActionInputReq = { packageId: string; actionId: string } // package.action.get-input
export type GetActionInputRes = {
spec: IST.InputSpec

View File

@@ -325,6 +325,10 @@ export abstract class ApiService {
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes>
abstract cancelInstallPackage(
params: RR.CancelInstallPackageReq,
): Promise<RR.CancelInstallPackageRes>
abstract getActionInput(
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes>

View File

@@ -560,6 +560,12 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'package.install', params })
}
async cancelInstallPackage(
params: RR.CancelInstallPackageReq,
): Promise<RR.CancelInstallPackageRes> {
return this.rpcRequest({ method: 'package.cancel-install', params })
}
async getActionInput(
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes> {

View File

@@ -50,10 +50,7 @@ const PROGRESS: T.FullProgress = {
},
{
name: 'Installing',
progress: {
done: 0,
total: 40,
},
progress: null,
},
],
}
@@ -1077,6 +1074,22 @@ export class MockApiService extends ApiService {
return null
}
async cancelInstallPackage(
params: RR.CancelInstallPackageReq,
): Promise<RR.CancelInstallPackageRes> {
await pauseFor(500)
const patch: RemoveOperation[] = [
{
op: PatchOp.REMOVE,
path: `/packageData/${params.id}`,
},
]
this.mockRevision(patch)
return null
}
async getActionInput(
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes> {

View File

@@ -1,13 +1,12 @@
import { Injectable } from '@angular/core'
import { inject, Injectable } from '@angular/core'
import {
GetPackageRes,
Marketplace,
MarketplacePkg,
StoreData,
StoreDataWithUrl,
StoreIdentity,
} from '@start9labs/marketplace'
import { Exver, defaultRegistries, sameUrl } from '@start9labs/shared'
import { defaultRegistries, Exver, sameUrl } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { PatchDB } from 'patch-db-client'
import {
@@ -40,29 +39,11 @@ const { start9, community } = defaultRegistries
providedIn: 'root',
})
export class MarketplaceService {
private readonly currentRegistryUrlSubject$ = new ReplaySubject<string>(1)
private readonly currentRegistryUrl$ = this.currentRegistryUrlSubject$.pipe(
distinctUntilChanged(),
)
private readonly api = inject(ApiService)
private readonly patch: PatchDB<DataModel> = inject(PatchDB)
private readonly exver = inject(Exver)
private readonly currentRegistry$: Observable<StoreDataWithUrl> =
this.currentRegistryUrl$.pipe(
switchMap(url => this.fetchRegistry$(url)),
filter(Boolean),
map(registry => {
registry.info.categories = {
all: {
name: 'All',
},
...registry.info.categories,
}
return registry
}),
shareReplay(1),
)
private readonly registries$: Observable<StoreIdentity[]> = this.patch
readonly registries$: Observable<StoreIdentity[]> = this.patch
.watch$('ui', 'registries')
.pipe(
map(registries => [
@@ -74,21 +55,23 @@ export class MarketplaceService {
]),
)
private readonly filteredRegistries$: Observable<StoreIdentity[]> =
combineLatest([
this.clientStorageService.showDevTools$,
this.registries$,
]).pipe(
map(([devMode, registries]) =>
devMode
? registries
: registries.filter(
({ url }) => !url.includes('alpha') && !url.includes('beta'),
),
),
)
// option to filter out hosts containing 'alpha' or 'beta' substrings in registryURL
readonly filteredRegistries$: Observable<StoreIdentity[]> = combineLatest([
inject(ClientStorageService).showDevTools$,
this.registries$,
]).pipe(
map(([devMode, registries]) =>
devMode
? registries
: registries.filter(
({ url }) => !url.includes('alpha') && !url.includes('beta'),
),
),
)
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
readonly currentRegistryUrl$ = new ReplaySubject<string>(1)
readonly requestErrors$ = new BehaviorSubject<string[]>([])
readonly marketplace$: Observable<Marketplace> = this.registries$.pipe(
startWith<StoreIdentity[]>([]),
@@ -102,11 +85,11 @@ export class MarketplaceService {
if (data?.info.name)
this.updateRegistryName(url, name, data.info.name)
}),
map<StoreData | null, [string, StoreData | null]>(data => [url, data]),
startWith<[string, StoreData | null]>([url, null]),
map(data => [url, data] satisfies [string, StoreDataWithUrl | null]),
startWith<[string, StoreDataWithUrl | null]>([url, null]),
),
),
scan<[string, StoreData | null], Record<string, StoreData | null>>(
scan<[string, StoreDataWithUrl | null], Marketplace>(
(requests, [url, store]) => {
requests[url] = store
@@ -114,32 +97,21 @@ export class MarketplaceService {
},
{},
),
shareReplay({ bufferSize: 1, refCount: true }),
shareReplay(1),
)
constructor(
private readonly api: ApiService,
private readonly patch: PatchDB<DataModel>,
private readonly clientStorageService: ClientStorageService,
private readonly exver: Exver,
) {}
getRegistries$(filtered = false): Observable<StoreIdentity[]> {
// option to filter out hosts containing 'alpha' or 'beta' substrings in registryURL
return filtered ? this.filteredRegistries$ : this.registries$
}
getCurrentRegistryUrl$() {
return this.currentRegistryUrl$
}
setRegistryUrl(url: string) {
this.currentRegistryUrlSubject$.next(url)
}
getCurrentRegistry$(): Observable<StoreDataWithUrl> {
return this.currentRegistry$
}
readonly currentRegistry$: Observable<StoreDataWithUrl> = combineLatest([
this.marketplace$,
this.currentRegistryUrl$,
this.currentRegistryUrl$.pipe(
distinctUntilChanged(),
switchMap(url => this.fetchRegistry$(url).pipe(startWith(null))),
),
]).pipe(
map(([all, url, current]) => current || all[url]),
filter(Boolean),
shareReplay(1),
)
getPackage$(
id: string,
@@ -161,14 +133,12 @@ export class MarketplaceService {
)
}
fetchInfo$(url: string): Observable<T.RegistryInfo> {
return from(this.api.getRegistryInfo({ registry: url })).pipe(
fetchInfo$(registry: string): Observable<T.RegistryInfo> {
return from(this.api.getRegistryInfo({ registry })).pipe(
map(info => ({
...info,
categories: {
all: {
name: 'All',
},
all: { name: 'All' },
...info.categories,
},
})),
@@ -263,10 +233,6 @@ export class MarketplaceService {
}
}
getRequestErrors$(): Observable<string[]> {
return this.requestErrors$
}
async installPackage(
id: string,
version: string,