mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-11-11 08:41:33 +00:00
Chorehancement: update to Angular v22, 'zoneless' / 'reactive' (#13114)
This commit is contained in:
1 parent
f244442c65
commit
106b41a15c
213 files changed
+5363
-5842
No files matched your search
+59
-24
@@ -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)"
|
||||
|
||||
+8
-10
@@ -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', () => {
|
||||
|
||||
+38
-30
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user