Chorehancement: update to Angular v22, 'zoneless' / 'reactive' (#13114)

This commit is contained in:
shamoon authored and GitHub committed 2026-07-10 00:42:16 -07:00
1 parent f244442c65
commit 106b41a15c
213 files changed
+5363 -5842

No files matched your search

@@ -1,22 +1,25 @@
<pngx-widget-frame
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
[title]="savedView.name"
[badge]="count"
[loading]="loading"
[loading]="false"
[draggable]="savedView"
>
@if (documents.length) {
@if (count() !== null) {
<span title-badge class="badge bg-info text-dark ms-2">{{count()}}</span>
}
@if (documents().length) {
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
}
<div content class="wrapper fade" [class.show]="show">
@if (displayMode === DisplayMode.TABLE) {
<div content class="wrapper fade" [class.show]="show()">
@if (displayMode() === DisplayMode.TABLE) {
<table class="table table-hover mb-0 mt-n2 align-middle">
<thead>
<tr>
@for (field of displayFields; track field; let i = $index) {
@if (displayFields.includes(field)) {
@for (field of displayFields(); track field; let i = $index) {
@if (displayFields().includes(field)) {
<th
scope="col"
[ngClass]="{
@@ -30,13 +33,26 @@
</tr>
</thead>
<tbody>
@for (doc of documents; track doc.id; let i = $index) {
<tr>
@for (field of displayFields; track field; let j = $index) {
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
@if (loading && show) {
@if (loading() && !documents().length) {
@for (row of placeholderRows; track row) {
<tr>
@for (field of displayFields(); track field; let j = $index) {
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
<div class="placeholder-glow text-start">
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents.length) }"></span>
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (row * 1/placeholderRows.length) }"></span>
</div>
</td>
}
</tr>
}
}
@for (doc of documents(); track doc.id; let i = $index) {
<tr>
@for (field of displayFields(); track field; let j = $index) {
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
@if (loading() && !documents().length) {
<div class="placeholder-glow text-start">
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents().length) }"></span>
</div>
} @else {
@switch (field) {
@@ -96,7 +112,7 @@
@if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="field"></pngx-custom-field-display>
}
@if (j === displayFields.length - 1) {
@if (j === displayFields().length - 1) {
<div class="btn-group position-absolute top-50 end-0 translate-middle-y" (mouseleave)="popupPreview.close()">
<pngx-preview-popup [document]="doc" linkClasses="btn px-4 btn-dark border-dark-subtle" #popupPreview>
<i-bs width="0.8em" height="0.8em" name="eye"></i-bs>
@@ -113,15 +129,25 @@
}
</tbody>
</table>
} @else if (displayMode === DisplayMode.SMALL_CARDS) {
} @else if (displayMode() === DisplayMode.SMALL_CARDS) {
<div class="row row-cols-paperless-cards my-n2">
@for (d of documents; track d.id; let i = $index) {
@if (loading() && !documents().length) {
@for (row of placeholderRows; track row) {
<pngx-document-card-small
class="p-0"
[ngStyle]="{ opacity: 1 - (row * 1/placeholderRows.length) }"
[document]="null"
[displayFields]="displayFields()">
</pngx-document-card-small>
}
}
@for (d of documents(); track d.id; let i = $index) {
<pngx-document-card-small
class="p-0"
[ngStyle]="{ opacity: !loading && show ? 1 : 1 - (i * 1/documents.length) }"
[ngStyle]="{ opacity: show() ? 1 : 1 - (i * 1/documents().length) }"
(dblClickDocument)="openDocumentDetail(d)"
[document]="!loading && show ? d : null"
[displayFields]="displayFields"
[document]="d"
[displayFields]="displayFields()"
(clickTag)="clickTag($event)"
(clickCorrespondent)="clickCorrespondent($event)"
(clickStoragePath)="clickStoragePath($event)"
@@ -129,14 +155,23 @@
</pngx-document-card-small>
}
</div>
} @else if (displayMode === DisplayMode.LARGE_CARDS) {
} @else if (displayMode() === DisplayMode.LARGE_CARDS) {
<div class="row my-n2">
@for (d of documents; track d.id; let i = $index) {
@if (loading() && !documents().length) {
@for (row of placeholderRows; track row) {
<pngx-document-card-large
[document]="null"
[ngStyle]="{ opacity: 1 - (row * 1/placeholderRows.length) }"
[displayFields]="displayFields()">
</pngx-document-card-large>
}
}
@for (d of documents(); track d.id; let i = $index) {
<pngx-document-card-large
(dblClickDocument)="openDocumentDetail(d)"
[document]="!loading && show ? d : null"
[ngStyle]="{ opacity: !loading && show ? 1 : 1 - (i * 1/documents.length) }"
[displayFields]="displayFields"
[document]="d"
[ngStyle]="{ opacity: show() ? 1 : 1 - (i * 1/documents().length) }"
[displayFields]="displayFields()"
(clickTag)="clickTag($event)"
(clickCorrespondent)="clickCorrespondent($event)"
(clickStoragePath)="clickStoragePath($event)"
@@ -2,12 +2,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop'
import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { Router } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
@@ -186,7 +181,8 @@ describe('SavedViewWidgetComponent', () => {
fixture.detectChanges()
})
it('should show a list of documents', fakeAsync(() => {
it('should show a list of documents', async () => {
jest.useFakeTimers()
jest.spyOn(documentService, 'listFiltered').mockReturnValue(
of({
all: [2, 3],
@@ -195,7 +191,8 @@ describe('SavedViewWidgetComponent', () => {
})
)
component.ngOnInit()
tick(500)
jest.advanceTimersByTime(500)
await fixture.whenStable()
fixture.detectChanges()
expect(fixture.debugElement.nativeElement.textContent).toContain('doc2')
expect(fixture.debugElement.nativeElement.textContent).toContain('doc3')
@@ -206,7 +203,8 @@ describe('SavedViewWidgetComponent', () => {
expect(
fixture.debugElement.queryAll(By.css('td a.btn'))[1].attributes['href']
).toEqual(component.getDownloadUrl(documentResults[0]))
}))
jest.useRealTimers()
})
it('should call api endpoint and load results', () => {
const listAllSpy = jest.spyOn(documentService, 'listFiltered')
@@ -229,7 +227,7 @@ describe('SavedViewWidgetComponent', () => {
}
)
fixture.detectChanges()
expect(component.documents).toEqual(documentResults)
expect(component.documents()).toEqual(documentResults)
})
it('should reload on document consumption finished', () => {
@@ -6,12 +6,13 @@ import {
OnDestroy,
OnInit,
QueryList,
signal,
ViewChildren,
} from '@angular/core'
import { Router, RouterModule } from '@angular/router'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { delay, Subject, takeUntil, tap } from 'rxjs'
import { Subject, takeUntil } from 'rxjs'
import { CustomFieldDisplayComponent } from 'src/app/components/common/custom-field-display/custom-field-display.component'
import { PreviewPopupComponent } from 'src/app/components/common/preview-popup/preview-popup.component'
import { TagComponent } from 'src/app/components/common/tag/tag.component'
@@ -106,7 +107,7 @@ export class SavedViewWidgetComponent
@Input()
savedView: SavedView
documents: Document[] = []
readonly documents = signal<Document[]>([])
unsubscribeNotifier: Subject<any> = new Subject()
@@ -116,21 +117,24 @@ export class SavedViewWidgetComponent
mouseOnPreview = false
popoverHidden = true
displayMode: DisplayMode
readonly displayMode = signal<DisplayMode>(null)
displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS
readonly displayFields = signal<DisplayField[]>(
DEFAULT_DASHBOARD_DISPLAY_FIELDS
)
count: number
readonly count = signal<number>(null)
placeholderRows: number[] = []
ngOnInit(): void {
this.reload()
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
this.websocketStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.reload()
})
this.placeholderRows = Array.from(
{
length: this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
},
(_, index) => index
)
this.displayMode.set(this.savedView.display_mode ?? DisplayMode.TABLE)
if (
this.permissionsService.currentUserCan(
@@ -150,15 +154,23 @@ export class SavedViewWidgetComponent
this.savedView.display_fields &&
this.savedView.display_fields.length > 0
) {
this.displayFields = this.savedView.display_fields
this.displayFields.set(this.savedView.display_fields)
}
// filter by perms etc
this.displayFields = this.displayFields.filter(
(field) =>
this.settingsService.allDisplayFields.find((f) => f.id === field) !==
undefined
this.displayFields.set(
this.displayFields().filter((field) =>
this.settingsService.allDisplayFields().some((f) => f.id === field)
)
)
this.reload()
this.websocketStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.reload()
})
}
ngOnDestroy(): void {
@@ -167,7 +179,8 @@ export class SavedViewWidgetComponent
}
reload() {
this.loading = this.documents.length == 0
this.loading.set(this.documents().length == 0)
this.show.set(true)
this.documentService
.listFiltered(
1,
@@ -177,18 +190,13 @@ export class SavedViewWidgetComponent
this.savedView.filter_rules,
{ truncate_content: true }
)
.pipe(
takeUntil(this.unsubscribeNotifier),
tap((result) => {
this.show = true
this.documents = result.results
this.count = result.count
this.savedViewService.setDocumentCount(this.savedView, result.count)
}),
delay(500)
)
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((result) => {
this.loading = false
this.documents.set(result.results)
this.count.set(result.count)
this.savedViewService.setDocumentCount(this.savedView, result.count)
this.loading.set(false)
this.show.set(true)
})
}