mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
fix: address comments (#3044)
* fix: address comments * fix unread notification mocks * fix row click for notification --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -368,6 +368,7 @@
|
|||||||
"styles": [
|
"styles": [
|
||||||
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
|
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
|
||||||
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
|
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
|
||||||
|
"projects/shared/styles/shared.scss",
|
||||||
"projects/start-tunnel/src/styles.scss"
|
"projects/start-tunnel/src/styles.scss"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { MarketplaceItemComponent } from './item.component'
|
|||||||
let-completeWith="completeWith"
|
let-completeWith="completeWith"
|
||||||
>
|
>
|
||||||
<tui-radio-list [items]="versions()" [(ngModel)]="data.version" />
|
<tui-radio-list [items]="versions()" [(ngModel)]="data.version" />
|
||||||
<footer class="buttons">
|
<footer class="g-buttons">
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
appearance="secondary"
|
appearance="secondary"
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ import { getErrorMessage } from '../services/error.service'
|
|||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<tui-notification appearance="negative" safeLinks>
|
<tui-notification appearance="negative" safeLinks [innerHTML]="error()" />
|
||||||
{{ error() }}
|
|
||||||
</tui-notification>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (content(); as result) {
|
@if (content(); as result) {
|
||||||
<div safeLinks [innerHTML]="result | markdown | dompurify"></div>
|
<div safeLinks [innerHTML]="result | markdown | dompurify"></div>
|
||||||
} @else {
|
} @else {
|
||||||
<tui-loader textContent="Loading" [style.height.%]="100" />
|
@if (!error()) {
|
||||||
|
<tui-loader textContent="Loading" [style.height.%]="100" />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -34,14 +34,10 @@ import { getErrorMessage } from '../services/error.service'
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MarkdownComponent {
|
export class MarkdownComponent {
|
||||||
private readonly data =
|
protected readonly data = injectContext<{ data: Observable<string> }>().data
|
||||||
injectContext<TuiDialogContext<void, { content: Observable<string> }>>({
|
protected readonly content = toSignal<string>(this.data)
|
||||||
optional: true,
|
protected readonly error = toSignal(
|
||||||
})?.data || inject(ActivatedRoute).snapshot.data
|
this.data.pipe(
|
||||||
|
|
||||||
readonly content = toSignal<string>(this.data['content'])
|
|
||||||
readonly error = toSignal(
|
|
||||||
this.data['content'].pipe(
|
|
||||||
ignoreElements(),
|
ignoreElements(),
|
||||||
catchError(e => of(getErrorMessage(e))),
|
catchError(e => of(getErrorMessage(e))),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ $wide-modal: 900px;
|
|||||||
--tw-color-zinc-800: 39 39 42;
|
--tw-color-zinc-800: 39 39 42;
|
||||||
--tw-color-zinc-900: 24 24 27;
|
--tw-color-zinc-900: 24 24 27;
|
||||||
--tw-color-zinc-950: 9 9 11;
|
--tw-color-zinc-950: 9 9 11;
|
||||||
|
|
||||||
|
--tui-font-text: 'Proxima Nova', system-ui;
|
||||||
|
--tui-font-heading: 'Proxima Nova', system-ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -172,14 +175,6 @@ a {
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.06rem;
|
letter-spacing: 0.06rem;
|
||||||
margin: 0rem 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
:first-child {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -182,16 +182,14 @@ export class MarketplacePreviewComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onStatic() {
|
onStatic() {
|
||||||
const content = this.pkg$.pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
switchMap(pkg => this.marketplaceService.fetchStatic$(pkg)),
|
|
||||||
)
|
|
||||||
|
|
||||||
this.dialog
|
this.dialog
|
||||||
.openComponent(MARKDOWN, {
|
.openComponent(MARKDOWN, {
|
||||||
label: 'License',
|
label: 'License',
|
||||||
size: 'l',
|
size: 'l',
|
||||||
data: { content },
|
data: this.pkg$.pipe(
|
||||||
|
filter(Boolean),
|
||||||
|
switchMap(pkg => this.marketplaceService.fetchStatic$(pkg)),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ import { i18nPipe } from '@start9labs/shared'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
host: {
|
host: {
|
||||||
'[class._new]': '!notificationItem.seen',
|
'[class._new]': '!notificationItem.seen',
|
||||||
'(click)': 'onClick()',
|
|
||||||
},
|
},
|
||||||
styles: `
|
styles: `
|
||||||
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
import { TuiCheckbox, TuiSkeleton } from '@taiga-ui/kit'
|
import { TuiCheckbox, TuiSkeleton } from '@taiga-ui/kit'
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
|
inject,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
signal,
|
signal,
|
||||||
@@ -43,7 +45,9 @@ import { i18nPipe } from '@start9labs/shared'
|
|||||||
[notificationItem]="notification"
|
[notificationItem]="notification"
|
||||||
(longtap)="!selected().length && onToggle(notification)"
|
(longtap)="!selected().length && onToggle(notification)"
|
||||||
(click.capture)="
|
(click.capture)="
|
||||||
selected().length && onToggle(notification, $event)
|
selected().length &&
|
||||||
|
$any($event.target).closest('tui-root._mobile') &&
|
||||||
|
onToggle(notification, $event)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -81,6 +85,7 @@ import { i18nPipe } from '@start9labs/shared'
|
|||||||
top: 0.875rem;
|
top: 0.875rem;
|
||||||
left: 1rem;
|
left: 1rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host:not(:has(:checked)) input {
|
:host:not(:has(:checked)) input {
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
inject,
|
|
||||||
INJECTOR,
|
|
||||||
} from '@angular/core'
|
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import {
|
import {
|
||||||
CopyService,
|
CopyService,
|
||||||
@@ -11,12 +6,12 @@ import {
|
|||||||
getPkgId,
|
getPkgId,
|
||||||
i18nKey,
|
i18nKey,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
MarkdownComponent,
|
MARKDOWN,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { TuiCell } from '@taiga-ui/layout'
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { map } from 'rxjs'
|
import { from, map } from 'rxjs'
|
||||||
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
import {
|
import {
|
||||||
@@ -58,15 +53,17 @@ import {
|
|||||||
imports: [ServiceAdditionalItemComponent, TuiCell, i18nPipe],
|
imports: [ServiceAdditionalItemComponent, TuiCell, i18nPipe],
|
||||||
})
|
})
|
||||||
export default class ServiceAboutRoute {
|
export default class ServiceAboutRoute {
|
||||||
|
private readonly pkgId = getPkgId()
|
||||||
private readonly copyService = inject(CopyService)
|
private readonly copyService = inject(CopyService)
|
||||||
private readonly markdown = inject(DialogService).openComponent(
|
private readonly markdown = inject(DialogService).openComponent(MARKDOWN, {
|
||||||
new PolymorpheusComponent(MarkdownComponent, inject(INJECTOR)),
|
label: 'License',
|
||||||
{ label: 'License', size: 'l' },
|
size: 'l',
|
||||||
)
|
data: from(inject(ApiService).getStaticInstalled(this.pkgId, 'LICENSE.md')),
|
||||||
|
})
|
||||||
|
|
||||||
readonly groups = toSignal<{ header: i18nKey; items: AdditionalItem[] }[]>(
|
readonly groups = toSignal<{ header: i18nKey; items: AdditionalItem[] }[]>(
|
||||||
inject<PatchDB<DataModel>>(PatchDB)
|
inject<PatchDB<DataModel>>(PatchDB)
|
||||||
.watch$('packageData', getPkgId())
|
.watch$('packageData', this.pkgId)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(pkg => {
|
map(pkg => {
|
||||||
const manifest = getManifest(pkg)
|
const manifest = getManifest(pkg)
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import { inject } from '@angular/core'
|
import { Routes } from '@angular/router'
|
||||||
import { ActivatedRouteSnapshot, ResolveFn, Routes } from '@angular/router'
|
|
||||||
import { defer, map, Observable, of } from 'rxjs'
|
|
||||||
import { share } from 'rxjs/operators'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { titleResolver } from 'src/app/utils/title-resolver'
|
import { titleResolver } from 'src/app/utils/title-resolver'
|
||||||
|
|
||||||
import { ServiceOutletComponent } from './routes/outlet.component'
|
import { ServiceOutletComponent } from './routes/outlet.component'
|
||||||
@@ -33,7 +29,6 @@ export const ROUTES: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
loadComponent: () => import('./routes/about.component'),
|
loadComponent: () => import('./routes/about.component'),
|
||||||
resolve: { content: getStatic() },
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -44,15 +39,4 @@ export const ROUTES: Routes = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function getStatic(): ResolveFn<Observable<string>> {
|
|
||||||
return ({ paramMap }: ActivatedRouteSnapshot) =>
|
|
||||||
of(inject(ApiService)).pipe(
|
|
||||||
map(api =>
|
|
||||||
defer(() =>
|
|
||||||
api.getStaticInstalled(paramMap.get('pkgId')!, 'LICENSE.md'),
|
|
||||||
).pipe(share()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ROUTES
|
export default ROUTES
|
||||||
|
|||||||
@@ -87,13 +87,11 @@ export class SideloadPackageComponent {
|
|||||||
readonly file = input.required<File>()
|
readonly file = input.required<File>()
|
||||||
|
|
||||||
onStatic() {
|
onStatic() {
|
||||||
const content = of(this.pkg()['license'])
|
|
||||||
|
|
||||||
this.dialog
|
this.dialog
|
||||||
.openComponent(MARKDOWN, {
|
.openComponent(MARKDOWN, {
|
||||||
label: 'License',
|
label: 'License',
|
||||||
size: 'l',
|
size: 'l',
|
||||||
data: { content },
|
data: of(this.pkg()['license']),
|
||||||
})
|
})
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,11 @@ import { i18nPipe } from '@start9labs/shared'
|
|||||||
@for (session of sessions(); track $index) {
|
@for (session of sessions(); track $index) {
|
||||||
<tr
|
<tr
|
||||||
(longtap)="!selected().length && onToggle(session)"
|
(longtap)="!selected().length && onToggle(session)"
|
||||||
(click)="selected().length && onToggle(session)"
|
(click)="
|
||||||
|
selected().length &&
|
||||||
|
$any($event.target).closest('tui-root._mobile') &&
|
||||||
|
onToggle(session)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<td [style.padding-left.rem]="single() ? null : 2.5">
|
<td [style.padding-left.rem]="single() ? null : 2.5">
|
||||||
@if (!single()) {
|
@if (!single()) {
|
||||||
@@ -123,6 +127,7 @@ import { i18nPipe } from '@start9labs/shared'
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
left: 0.25rem;
|
left: 0.25rem;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ import { SSHKey } from 'src/app/services/api/api.types'
|
|||||||
@for (key of keys(); track $index) {
|
@for (key of keys(); track $index) {
|
||||||
<tr
|
<tr
|
||||||
(longtap)="!selected().length && onToggle(key)"
|
(longtap)="!selected().length && onToggle(key)"
|
||||||
(click)="selected().length && onToggle(key)"
|
(click)="
|
||||||
|
selected().length &&
|
||||||
|
$any($event.target).closest('tui-root._mobile') &&
|
||||||
|
onToggle(key)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<td [style.padding-left.rem]="2.5">
|
<td [style.padding-left.rem]="2.5">
|
||||||
<input
|
<input
|
||||||
@@ -104,6 +108,7 @@ import { SSHKey } from 'src/app/services/api/api.types'
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
left: 0.25rem;
|
left: 0.25rem;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
|
|||||||
@@ -149,6 +149,11 @@ import UpdatesComponent from './updates.component'
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tui-progress-circle {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -172,12 +177,7 @@ import UpdatesComponent from './updates.component'
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
div {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&[colspan]:only-child {
|
&[colspan]:only-child {
|
||||||
|
|||||||
@@ -19,19 +19,17 @@ import {
|
|||||||
TuiSkeleton,
|
TuiSkeleton,
|
||||||
} from '@taiga-ui/kit'
|
} from '@taiga-ui/kit'
|
||||||
import { TuiCell } from '@taiga-ui/layout'
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { combineLatest, tap } from 'rxjs'
|
||||||
import { combineLatest, map, tap } from 'rxjs'
|
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
|
import { LocalPackagesService } from 'src/app/services/local-packages.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
|
||||||
InstalledState,
|
InstalledState,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
UpdatingState,
|
UpdatingState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { TitleDirective } from 'src/app/services/title.service'
|
import { TitleDirective } from 'src/app/services/title.service'
|
||||||
import { isInstalled, isUpdating } from 'src/app/utils/get-package-data'
|
|
||||||
import { FilterUpdatesPipe } from './filter-updates.pipe'
|
import { FilterUpdatesPipe } from './filter-updates.pipe'
|
||||||
import { UpdatesItemComponent } from './item.component'
|
import { UpdatesItemComponent } from './item.component'
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
@@ -256,21 +254,7 @@ export default class UpdatesComponent {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
marketplace: this.marketplaceService.marketplace$,
|
marketplace: this.marketplaceService.marketplace$,
|
||||||
localPkgs: inject<PatchDB<DataModel>>(PatchDB)
|
localPkgs: inject(LocalPackagesService),
|
||||||
.watch$('packageData')
|
|
||||||
.pipe(
|
|
||||||
map(pkgs =>
|
|
||||||
Object.entries(pkgs).reduce<
|
|
||||||
Record<string, PackageDataEntry<InstalledState | UpdatingState>>
|
|
||||||
>(
|
|
||||||
(acc, [id, val]) =>
|
|
||||||
isInstalled(val) || isUpdating(val)
|
|
||||||
? { ...acc, [id]: val }
|
|
||||||
: acc,
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
errors: this.marketplaceService.requestErrors$,
|
errors: this.marketplaceService.requestErrors$,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export const mockPatchData: DataModel = {
|
|||||||
staticServers: null,
|
staticServers: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
unreadNotificationCount: 4,
|
unreadNotificationCount: 5,
|
||||||
// password is asdfasdf
|
// password is asdfasdf
|
||||||
passwordHash:
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import { combineLatest, EMPTY, map, Observable, shareReplay } from 'rxjs'
|
||||||
combineLatest,
|
import { LocalPackagesService } from 'src/app/services/local-packages.service'
|
||||||
EMPTY,
|
|
||||||
filter,
|
|
||||||
first,
|
|
||||||
map,
|
|
||||||
Observable,
|
|
||||||
pairwise,
|
|
||||||
shareReplay,
|
|
||||||
startWith,
|
|
||||||
switchMap,
|
|
||||||
} from 'rxjs'
|
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
|
||||||
import { OSService } from 'src/app/services/os.service'
|
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { NotificationService } from 'src/app/services/notification.service'
|
import { NotificationService } from 'src/app/services/notification.service'
|
||||||
|
import { OSService } from 'src/app/services/os.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
|
||||||
import { FilterUpdatesPipe } from '../routes/portal/routes/updates/filter-updates.pipe'
|
import { FilterUpdatesPipe } from '../routes/portal/routes/updates/filter-updates.pipe'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -35,32 +23,9 @@ export class BadgeService {
|
|||||||
private readonly marketplaceService = inject(MarketplaceService)
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
private readonly filterUpdatesPipe = inject(FilterUpdatesPipe)
|
private readonly filterUpdatesPipe = inject(FilterUpdatesPipe)
|
||||||
|
|
||||||
private readonly local$ = inject(ConnectionService).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
switchMap(() => this.patch.watch$('packageData').pipe(first())),
|
|
||||||
switchMap(outer =>
|
|
||||||
this.patch.watch$('packageData').pipe(
|
|
||||||
pairwise(),
|
|
||||||
filter(([prev, curr]) =>
|
|
||||||
Object.values(prev).some(p => {
|
|
||||||
const { id } = getManifest(p)
|
|
||||||
|
|
||||||
return (
|
|
||||||
!curr[id] ||
|
|
||||||
(p.stateInfo.installingInfo &&
|
|
||||||
!curr[id]?.stateInfo.installingInfo)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
map(([_, curr]) => curr),
|
|
||||||
startWith(outer),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly updates$ = combineLatest([
|
private readonly updates$ = combineLatest([
|
||||||
this.marketplaceService.marketplace$,
|
this.marketplaceService.marketplace$,
|
||||||
this.local$,
|
inject(LocalPackagesService),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(
|
map(
|
||||||
([marketplace, local]) =>
|
([marketplace, local]) =>
|
||||||
|
|||||||
34
web/projects/ui/src/app/services/local-packages.service.ts
Normal file
34
web/projects/ui/src/app/services/local-packages.service.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { map, Observable, shareReplay } from 'rxjs'
|
||||||
|
import {
|
||||||
|
DataModel,
|
||||||
|
InstalledState,
|
||||||
|
PackageDataEntry,
|
||||||
|
UpdatingState,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
import { isInstalled, isUpdating } from 'src/app/utils/get-package-data'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LocalPackagesService extends Observable<
|
||||||
|
Record<string, PackageDataEntry<InstalledState | UpdatingState>>
|
||||||
|
> {
|
||||||
|
private readonly stream$ = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
|
.watch$('packageData')
|
||||||
|
.pipe(
|
||||||
|
map(pkgs =>
|
||||||
|
Object.entries(pkgs).reduce(
|
||||||
|
(acc, [id, val]) =>
|
||||||
|
isInstalled(val) || isUpdating(val) ? { ...acc, [id]: val } : acc,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(subscriber => this.stream$.subscribe(subscriber))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,21 +95,26 @@ export class NotificationService {
|
|||||||
|
|
||||||
viewModal(notification: ServerNotification<number>, full = false) {
|
viewModal(notification: ServerNotification<number>, full = false) {
|
||||||
const { data, createdAt, code, title, message } = notification
|
const { data, createdAt, code, title, message } = notification
|
||||||
const label = code === 1 ? 'Backup Report' : (title as i18nKey)
|
|
||||||
const component = code === 1 ? REPORT : MARKDOWN
|
|
||||||
const content = code === 1 ? data : of(data)
|
|
||||||
|
|
||||||
this.markSeen([notification])
|
this.markSeen([notification])
|
||||||
this.dialogs
|
|
||||||
.openComponent(full ? message : component, {
|
if (code === 1) {
|
||||||
label,
|
// Backup Report
|
||||||
data: {
|
this.dialogs
|
||||||
content,
|
.openComponent(full ? message : REPORT, {
|
||||||
timestamp: createdAt,
|
label: 'Backup Report',
|
||||||
},
|
data: { content: data, createdAt },
|
||||||
size: code === 1 ? 'm' : 'l',
|
})
|
||||||
})
|
.subscribe()
|
||||||
.subscribe()
|
} else {
|
||||||
|
// Markdown viewer
|
||||||
|
this.dialogs
|
||||||
|
.openComponent(full ? message : MARKDOWN, {
|
||||||
|
label: title as i18nKey,
|
||||||
|
data: of(data),
|
||||||
|
size: 'l',
|
||||||
|
})
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateCount(toAdjust: number) {
|
private async updateCount(toAdjust: number) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ hr {
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bumper: 0.375rem;
|
--bumper: 0.375rem;
|
||||||
--tui-font-text: 'Proxima Nova', system-ui;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.g-page {
|
.g-page {
|
||||||
|
|||||||
Reference in New Issue
Block a user