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:
Alex Inkin
2025-11-07 01:17:57 +04:00
committed by GitHub
parent 515d37147b
commit 66cb9a93b8
19 changed files with 115 additions and 145 deletions

View File

@@ -368,6 +368,7 @@
"styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
"projects/shared/styles/shared.scss",
"projects/start-tunnel/src/styles.scss"
],
"scripts": []

View File

@@ -32,7 +32,7 @@ import { MarketplaceItemComponent } from './item.component'
let-completeWith="completeWith"
>
<tui-radio-list [items]="versions()" [(ngModel)]="data.version" />
<footer class="buttons">
<footer class="g-buttons">
<button
tuiButton
appearance="secondary"

View File

@@ -12,15 +12,15 @@ import { getErrorMessage } from '../services/error.service'
@Component({
template: `
@if (error()) {
<tui-notification appearance="negative" safeLinks>
{{ error() }}
</tui-notification>
<tui-notification appearance="negative" safeLinks [innerHTML]="error()" />
}
@if (content(); as result) {
<div safeLinks [innerHTML]="result | markdown | dompurify"></div>
} @else {
<tui-loader textContent="Loading" [style.height.%]="100" />
@if (!error()) {
<tui-loader textContent="Loading" [style.height.%]="100" />
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -34,14 +34,10 @@ import { getErrorMessage } from '../services/error.service'
],
})
export class MarkdownComponent {
private readonly data =
injectContext<TuiDialogContext<void, { content: Observable<string> }>>({
optional: true,
})?.data || inject(ActivatedRoute).snapshot.data
readonly content = toSignal<string>(this.data['content'])
readonly error = toSignal(
this.data['content'].pipe(
protected readonly data = injectContext<{ data: Observable<string> }>().data
protected readonly content = toSignal<string>(this.data)
protected readonly error = toSignal(
this.data.pipe(
ignoreElements(),
catchError(e => of(getErrorMessage(e))),
),

View File

@@ -95,6 +95,9 @@ $wide-modal: 900px;
--tw-color-zinc-800: 39 39 42;
--tw-color-zinc-900: 24 24 27;
--tw-color-zinc-950: 9 9 11;
--tui-font-text: 'Proxima Nova', system-ui;
--tui-font-heading: 'Proxima Nova', system-ui;
}
body {
@@ -172,14 +175,6 @@ a {
font-weight: 300;
text-transform: uppercase;
letter-spacing: 0.06rem;
margin: 0rem 0 1rem 0;
margin: 0 0 1rem 0;
pointer-events: none;
}
.buttons {
margin-top: 1rem;
:first-child {
margin-right: 0.5rem;
}
}

View File

@@ -182,16 +182,14 @@ export class MarketplacePreviewComponent {
}
onStatic() {
const content = this.pkg$.pipe(
filter(Boolean),
switchMap(pkg => this.marketplaceService.fetchStatic$(pkg)),
)
this.dialog
.openComponent(MARKDOWN, {
label: 'License',
size: 'l',
data: { content },
data: this.pkg$.pipe(
filter(Boolean),
switchMap(pkg => this.marketplaceService.fetchStatic$(pkg)),
),
})
.subscribe()
}

View File

@@ -67,7 +67,6 @@ import { i18nPipe } from '@start9labs/shared'
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[class._new]': '!notificationItem.seen',
'(click)': 'onClick()',
},
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;

View File

@@ -1,7 +1,9 @@
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import { TuiCheckbox, TuiSkeleton } from '@taiga-ui/kit'
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
OnChanges,
signal,
@@ -43,7 +45,9 @@ import { i18nPipe } from '@start9labs/shared'
[notificationItem]="notification"
(longtap)="!selected().length && onToggle(notification)"
(click.capture)="
selected().length && onToggle(notification, $event)
selected().length &&
$any($event.target).closest('tui-root._mobile') &&
onToggle(notification, $event)
"
>
<input
@@ -81,6 +85,7 @@ import { i18nPipe } from '@start9labs/shared'
top: 0.875rem;
left: 1rem;
z-index: 1;
pointer-events: none;
}
:host:not(:has(:checked)) input {

View File

@@ -1,9 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
inject,
INJECTOR,
} from '@angular/core'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import {
CopyService,
@@ -11,12 +6,12 @@ import {
getPkgId,
i18nKey,
i18nPipe,
MarkdownComponent,
MARKDOWN,
} from '@start9labs/shared'
import { TuiCell } from '@taiga-ui/layout'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
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 { getManifest } from 'src/app/utils/get-package-data'
import {
@@ -58,15 +53,17 @@ import {
imports: [ServiceAdditionalItemComponent, TuiCell, i18nPipe],
})
export default class ServiceAboutRoute {
private readonly pkgId = getPkgId()
private readonly copyService = inject(CopyService)
private readonly markdown = inject(DialogService).openComponent(
new PolymorpheusComponent(MarkdownComponent, inject(INJECTOR)),
{ label: 'License', size: 'l' },
)
private readonly markdown = inject(DialogService).openComponent(MARKDOWN, {
label: 'License',
size: 'l',
data: from(inject(ApiService).getStaticInstalled(this.pkgId, 'LICENSE.md')),
})
readonly groups = toSignal<{ header: i18nKey; items: AdditionalItem[] }[]>(
inject<PatchDB<DataModel>>(PatchDB)
.watch$('packageData', getPkgId())
.watch$('packageData', this.pkgId)
.pipe(
map(pkg => {
const manifest = getManifest(pkg)

View File

@@ -1,8 +1,4 @@
import { inject } from '@angular/core'
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 { Routes } from '@angular/router'
import { titleResolver } from 'src/app/utils/title-resolver'
import { ServiceOutletComponent } from './routes/outlet.component'
@@ -33,7 +29,6 @@ export const ROUTES: Routes = [
{
path: 'about',
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

View File

@@ -87,13 +87,11 @@ export class SideloadPackageComponent {
readonly file = input.required<File>()
onStatic() {
const content = of(this.pkg()['license'])
this.dialog
.openComponent(MARKDOWN, {
label: 'License',
size: 'l',
data: { content },
data: of(this.pkg()['license']),
})
.subscribe()
}

View File

@@ -41,7 +41,11 @@ import { i18nPipe } from '@start9labs/shared'
@for (session of sessions(); track $index) {
<tr
(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">
@if (!single()) {
@@ -123,6 +127,7 @@ import { i18nPipe } from '@start9labs/shared'
input {
left: 0.25rem;
pointer-events: none;
}
td {

View File

@@ -31,7 +31,11 @@ import { SSHKey } from 'src/app/services/api/api.types'
@for (key of keys(); track $index) {
<tr
(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">
<input
@@ -104,6 +108,7 @@ import { SSHKey } from 'src/app/services/api/api.types'
input {
left: 0.25rem;
pointer-events: none;
}
td {

View File

@@ -149,6 +149,11 @@ import UpdatesComponent from './updates.component'
font-size: 1rem;
}
tui-progress-circle {
display: inline-block;
vertical-align: middle;
}
div {
display: flex;
align-items: center;
@@ -172,12 +177,7 @@ import UpdatesComponent from './updates.component'
}
&:last-child {
text-align: right;
white-space: nowrap;
div {
justify-content: flex-start;
}
}
&[colspan]:only-child {

View File

@@ -19,19 +19,17 @@ import {
TuiSkeleton,
} from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { PatchDB } from 'patch-db-client'
import { combineLatest, map, tap } from 'rxjs'
import { combineLatest, tap } from 'rxjs'
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.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 {
DataModel,
InstalledState,
PackageDataEntry,
UpdatingState,
} from 'src/app/services/patch-db/data-model'
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 { UpdatesItemComponent } from './item.component'
import { i18nPipe } from '@start9labs/shared'
@@ -256,21 +254,7 @@ export default class UpdatesComponent {
),
),
marketplace: this.marketplaceService.marketplace$,
localPkgs: inject<PatchDB<DataModel>>(PatchDB)
.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,
{},
),
),
),
localPkgs: inject(LocalPackagesService),
errors: this.marketplaceService.requestErrors$,
}),
)

View File

@@ -194,7 +194,7 @@ export const mockPatchData: DataModel = {
staticServers: null,
},
},
unreadNotificationCount: 4,
unreadNotificationCount: 5,
// password is asdfasdf
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',

View File

@@ -1,23 +1,11 @@
import { inject, Injectable } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import {
combineLatest,
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 { combineLatest, EMPTY, map, Observable, shareReplay } from 'rxjs'
import { LocalPackagesService } from 'src/app/services/local-packages.service'
import { MarketplaceService } from 'src/app/services/marketplace.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 { getManifest } from 'src/app/utils/get-package-data'
import { FilterUpdatesPipe } from '../routes/portal/routes/updates/filter-updates.pipe'
@Injectable({
@@ -35,32 +23,9 @@ export class BadgeService {
private readonly marketplaceService = inject(MarketplaceService)
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([
this.marketplaceService.marketplace$,
this.local$,
inject(LocalPackagesService),
]).pipe(
map(
([marketplace, local]) =>

View 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))
}
}

View File

@@ -95,21 +95,26 @@ export class NotificationService {
viewModal(notification: ServerNotification<number>, full = false) {
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.dialogs
.openComponent(full ? message : component, {
label,
data: {
content,
timestamp: createdAt,
},
size: code === 1 ? 'm' : 'l',
})
.subscribe()
if (code === 1) {
// Backup Report
this.dialogs
.openComponent(full ? message : REPORT, {
label: 'Backup Report',
data: { content: data, createdAt },
})
.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) {

View File

@@ -25,7 +25,6 @@ hr {
:root {
--bumper: 0.375rem;
--tui-font-text: 'Proxima Nova', system-ui;
}
.g-page {