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": [
|
||||
"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": []
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -12,16 +12,16 @@ 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 {
|
||||
@if (!error()) {
|
||||
<tui-loader textContent="Loading" [style.height.%]="100" />
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'g-subpage' },
|
||||
@@ -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))),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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$,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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]) =>
|
||||
|
||||
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) {
|
||||
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])
|
||||
|
||||
if (code === 1) {
|
||||
// Backup Report
|
||||
this.dialogs
|
||||
.openComponent(full ? message : component, {
|
||||
label,
|
||||
data: {
|
||||
content,
|
||||
timestamp: createdAt,
|
||||
},
|
||||
size: code === 1 ? 'm' : 'l',
|
||||
.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) {
|
||||
|
||||
@@ -25,7 +25,6 @@ hr {
|
||||
|
||||
:root {
|
||||
--bumper: 0.375rem;
|
||||
--tui-font-text: 'Proxima Nova', system-ui;
|
||||
}
|
||||
|
||||
.g-page {
|
||||
|
||||
Reference in New Issue
Block a user