From e3c4c0ef7eff6454e91656ddf863f735e96103ed Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 8 Jul 2026 07:52:28 -0700 Subject: [PATCH] Sheesh more dialog hangers on --- .../merge-confirm-dialog.component.ts | 54 +++++++++-- .../hotkey-dialog/hotkey-dialog.component.ts | 21 ++++- .../share-link-bundle-dialog.component.ts | 35 +++++-- .../system-status-dialog.component.ts | 94 ++++++++++++++----- .../processed-mail-dialog.component.ts | 62 +++++++++--- 5 files changed, 211 insertions(+), 55 deletions(-) diff --git a/src-ui/src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.ts b/src-ui/src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.ts index dbf269a23..8006600cd 100644 --- a/src-ui/src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.ts +++ b/src-ui/src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.ts @@ -4,7 +4,7 @@ import { moveItemInArray, } from '@angular/cdk/drag-drop' import { AsyncPipe } from '@angular/common' -import { Component, OnInit, inject } from '@angular/core' +import { Component, OnInit, inject, signal } from '@angular/core' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { takeUntil } from 'rxjs' @@ -36,15 +36,47 @@ export class MergeConfirmDialogComponent private documentService = inject(DocumentService) private permissionService = inject(PermissionsService) - public documentIDs: number[] = [] - public archiveFallback: boolean = false - public deleteOriginals: boolean = false - private _documents: Document[] = [] - get documents(): Document[] { - return this._documents + private documentIDsSignal = signal([]) + private archiveFallbackSignal = signal(false) + private deleteOriginalsSignal = signal(false) + private documentsSignal = signal([]) + private metadataDocumentIDSignal = signal(-1) + + public get documentIDs(): number[] { + return this.documentIDsSignal() } - public metadataDocumentID: number = -1 + public set documentIDs(documentIDs: number[]) { + this.documentIDsSignal.set(documentIDs) + } + + public get archiveFallback(): boolean { + return this.archiveFallbackSignal() + } + + public set archiveFallback(archiveFallback: boolean) { + this.archiveFallbackSignal.set(archiveFallback) + } + + public get deleteOriginals(): boolean { + return this.deleteOriginalsSignal() + } + + public set deleteOriginals(deleteOriginals: boolean) { + this.deleteOriginalsSignal.set(deleteOriginals) + } + + get documents(): Document[] { + return this.documentsSignal() + } + + public get metadataDocumentID(): number { + return this.metadataDocumentIDSignal() + } + + public set metadataDocumentID(metadataDocumentID: number) { + this.metadataDocumentIDSignal.set(metadataDocumentID) + } constructor() { super() @@ -55,12 +87,14 @@ export class MergeConfirmDialogComponent .getFew(this.documentIDs) .pipe(takeUntil(this.unsubscribeNotifier)) .subscribe((r) => { - this._documents = r.results + this.documentsSignal.set(r.results) }) } onDrop(event: CdkDragDrop) { - moveItemInArray(this.documentIDs, event.previousIndex, event.currentIndex) + const documentIDs = this.documentIDs.concat() + moveItemInArray(documentIDs, event.previousIndex, event.currentIndex) + this.documentIDs = documentIDs } getDocument(documentID: number): Document { diff --git a/src-ui/src/app/components/common/hotkey-dialog/hotkey-dialog.component.ts b/src-ui/src/app/components/common/hotkey-dialog/hotkey-dialog.component.ts index 180fc1005..8666522b2 100644 --- a/src-ui/src/app/components/common/hotkey-dialog/hotkey-dialog.component.ts +++ b/src-ui/src/app/components/common/hotkey-dialog/hotkey-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, inject } from '@angular/core' +import { Component, inject, signal } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' const SYMBOLS = { @@ -20,9 +20,24 @@ const SYMBOLS = { }) export class HotkeyDialogComponent { activeModal = inject(NgbActiveModal) + private titleSignal = signal($localize`Keyboard shortcuts`) + private hotkeysSignal = signal>(new Map()) - public title: string = $localize`Keyboard shortcuts` - public hotkeys: Map = new Map() + public get title(): string { + return this.titleSignal() + } + + public set title(title: string) { + this.titleSignal.set(title) + } + + public get hotkeys(): Map { + return this.hotkeysSignal() + } + + public set hotkeys(hotkeys: Map) { + this.hotkeysSignal.set(hotkeys) + } public close(): void { this.activeModal.close() diff --git a/src-ui/src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.ts b/src-ui/src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.ts index 37aa70950..d92aad81d 100644 --- a/src-ui/src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.ts +++ b/src-ui/src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.ts @@ -1,6 +1,6 @@ import { Clipboard } from '@angular/cdk/clipboard' import { CommonModule } from '@angular/common' -import { Component, Input, inject } from '@angular/core' +import { Component, Input, inject, signal } from '@angular/core' import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { Document } from 'src/app/data/document' @@ -38,10 +38,27 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent { private readonly clipboard = inject(Clipboard) private readonly toastService = inject(ToastService) - private _documents: Document[] = [] + private documentsSignal = signal([]) + private selectionCountSignal = signal(0) + private documentPreviewSignal = signal([]) + private copiedSignal = signal(false) + + get selectionCount(): number { + return this.selectionCountSignal() + } + + get documentPreview(): Document[] { + return this.documentPreviewSignal() + } + + get copied(): boolean { + return this.copiedSignal() + } + + set copied(copied: boolean) { + this.copiedSignal.set(copied) + } - selectionCount = 0 - documentPreview: Document[] = [] form: FormGroup = this.formBuilder.group({ shareArchiveVersion: true, expirationDays: [7], @@ -51,7 +68,6 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent { readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS createdBundle: ShareLinkBundleSummary | null = null - copied = false onOpenManage?: () => void readonly statuses = ShareLinkBundleStatus @@ -64,15 +80,16 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent { @Input() set documents(docs: Document[]) { - this._documents = docs.concat() - this.selectionCount = this._documents.length - this.documentPreview = this._documents.slice(0, 10) + const documents = docs.concat() + this.documentsSignal.set(documents) + this.selectionCountSignal.set(documents.length) + this.documentPreviewSignal.set(documents.slice(0, 10)) } submit() { if (this.createdBundle) return this.payload = { - document_ids: this._documents.map((doc) => doc.id), + document_ids: this.documentsSignal().map((doc) => doc.id), file_version: this.form.value.shareArchiveVersion ? FileVersion.Archive : FileVersion.Original, diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts index 88426186c..bc43c3873 100644 --- a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts @@ -1,5 +1,12 @@ import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard' -import { Component, OnDestroy, OnInit, inject } from '@angular/core' +import { + Component, + Input, + OnDestroy, + OnInit, + inject, + signal, +} from '@angular/core' import { NgbActiveModal, NgbModalModule, @@ -50,15 +57,40 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy { public SystemStatusItemStatus = SystemStatusItemStatus public PaperlessTaskType = PaperlessTaskType - public status: SystemStatus + private statusSignal = signal(undefined) public frontendVersion: string = environment.version - public versionMismatch: boolean = false + private versionMismatchSignal = signal(false) - public copied: boolean = false + private copiedSignal = signal(false) - private runningTasks: Set = new Set() + private runningTasksSignal = signal>(new Set()) private unsubscribeNotifier: Subject = new Subject() + @Input() + get status(): SystemStatus { + return this.statusSignal() + } + + set status(status: SystemStatus) { + this.statusSignal.set(status) + } + + get versionMismatch(): boolean { + return this.versionMismatchSignal() + } + + set versionMismatch(versionMismatch: boolean) { + this.versionMismatchSignal.set(versionMismatch) + } + + get copied(): boolean { + return this.copiedSignal() + } + + set copied(copied: boolean) { + this.copiedSignal.set(copied) + } + get currentUserIsSuperUser(): boolean { return this.permissionsService.isSuperUser() } @@ -68,25 +100,23 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy { } public ngOnInit() { + const status = this.status this.versionMismatch = environment.production && - this.status.pngx_version && + status.pngx_version && this.frontendVersion && - this.status.pngx_version !== this.frontendVersion + status.pngx_version !== this.frontendVersion if (this.versionMismatch) { - this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})` + this.status = { + ...status, + pngx_version: `${status.pngx_version} (frontend: ${this.frontendVersion})`, + } } - this.status.websocket_connected = this.websocketStatusService.isConnected() - ? SystemStatusItemStatus.OK - : SystemStatusItemStatus.ERROR + this.updateWebsocketStatus(this.websocketStatusService.isConnected()) this.websocketStatusService .onConnectionStatus() .pipe(takeUntil(this.unsubscribeNotifier)) - .subscribe((connected) => { - this.status.websocket_connected = connected - ? SystemStatusItemStatus.OK - : SystemStatusItemStatus.ERROR - }) + .subscribe((connected) => this.updateWebsocketStatus(connected)) } public close() { @@ -108,23 +138,26 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy { } public isRunning(taskName: PaperlessTaskType): boolean { - return this.runningTasks.has(taskName) + return this.runningTasksSignal().has(taskName) } public runTask(taskName: PaperlessTaskType) { - this.runningTasks.add(taskName) + this.setTaskRunning(taskName, true) this.toastService.showInfo(`Task ${taskName} started`) this.tasksService.run(taskName).subscribe({ next: () => { - this.runningTasks.delete(taskName) + this.setTaskRunning(taskName, false) this.systemStatusService.get().subscribe({ next: (status) => { - Object.assign(this.status, status) + this.status = { + ...this.status, + ...status, + } }, }) }, error: (err) => { - this.runningTasks.delete(taskName) + this.setTaskRunning(taskName, false) this.toastService.showError( `Failed to start task ${taskName}, see the logs for more details`, err @@ -133,6 +166,25 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy { }) } + private updateWebsocketStatus(connected: boolean): void { + this.status = { + ...this.status, + websocket_connected: connected + ? SystemStatusItemStatus.OK + : SystemStatusItemStatus.ERROR, + } + } + + private setTaskRunning(taskName: PaperlessTaskType, running: boolean): void { + const runningTasks = new Set(this.runningTasksSignal()) + if (running) { + runningTasks.add(taskName) + } else { + runningTasks.delete(taskName) + } + this.runningTasksSignal.set(runningTasks) + } + ngOnDestroy(): void { this.unsubscribeNotifier.next(this) this.unsubscribeNotifier.complete() diff --git a/src-ui/src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.ts b/src-ui/src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.ts index ed51ad0ed..f1bda332d 100644 --- a/src-ui/src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.ts +++ b/src-ui/src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.ts @@ -1,5 +1,5 @@ import { SlicePipe } from '@angular/common' -import { Component, inject, Input, OnInit } from '@angular/core' +import { Component, inject, Input, OnInit, signal } from '@angular/core' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { NgbActiveModal, @@ -36,15 +36,50 @@ export class ProcessedMailDialogComponent implements OnInit { private readonly processedMailService = inject(ProcessedMailService) private readonly toastService = inject(ToastService) - public processedMails: ProcessedMail[] = [] + private ruleSignal = signal(undefined) + private processedMailsSignal = signal([]) + private loadingSignal = signal(true) + private toggleAllEnabledSignal = signal(false) + private selectedMailIdsSignal = signal>(new Set()) - public loading: boolean = true - public toggleAllEnabled: boolean = false - public readonly selectedMailIds: Set = new Set() + public get processedMails(): ProcessedMail[] { + return this.processedMailsSignal() + } + + public set processedMails(processedMails: ProcessedMail[]) { + this.processedMailsSignal.set(processedMails) + } + + public get loading(): boolean { + return this.loadingSignal() + } + + public set loading(loading: boolean) { + this.loadingSignal.set(loading) + } + + public get toggleAllEnabled(): boolean { + return this.toggleAllEnabledSignal() + } + + public set toggleAllEnabled(toggleAllEnabled: boolean) { + this.toggleAllEnabledSignal.set(toggleAllEnabled) + } + + public get selectedMailIds(): Set { + return this.selectedMailIdsSignal() + } public page: number = 1 - @Input() rule: MailRule + @Input() + get rule(): MailRule { + return this.ruleSignal() + } + + set rule(rule: MailRule) { + this.ruleSignal.set(rule) + } ngOnInit(): void { this.loadProcessedMails() @@ -75,9 +110,10 @@ export class ProcessedMailDialogComponent implements OnInit { } public toggleAll(event: PointerEvent) { + const selectedMailIds = new Set() if ((event.target as HTMLInputElement).checked) { - this.selectedMailIds.clear() - this.processedMails.forEach((mail) => this.selectedMailIds.add(mail.id)) + this.processedMails.forEach((mail) => selectedMailIds.add(mail.id)) + this.selectedMailIdsSignal.set(selectedMailIds) } else { this.clearSelection() } @@ -85,12 +121,14 @@ export class ProcessedMailDialogComponent implements OnInit { public clearSelection() { this.toggleAllEnabled = false - this.selectedMailIds.clear() + this.selectedMailIdsSignal.set(new Set()) } public toggleSelected(mail: ProcessedMail) { - this.selectedMailIds.has(mail.id) - ? this.selectedMailIds.delete(mail.id) - : this.selectedMailIds.add(mail.id) + const selectedMailIds = new Set(this.selectedMailIds) + selectedMailIds.has(mail.id) + ? selectedMailIds.delete(mail.id) + : selectedMailIds.add(mail.id) + this.selectedMailIdsSignal.set(selectedMailIds) } }