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

This commit is contained in:
shamoon
2026-07-10 00:42:16 -07:00
committed by GitHub
parent f244442c65
commit 106b41a15c
213 changed files with 5363 additions and 5842 deletions
@@ -8,16 +8,16 @@
</div>
</li>
@if (loading) {
@if (loading()) {
<li class="list-group-item">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</li>
}
@for (field of fields; track field) {
@for (field of fields(); track field) {
<li class="list-group-item">
<div class="row fade" [class.show]="show">
<div class="row fade" [class.show]="show()">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editField(field)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.CustomField)">{{field.name}}</button></div>
<div class="col d-flex align-items-center">{{getDataType(field)}}</div>
<div class="col">
@@ -64,7 +64,7 @@
</div>
</li>
}
@if (!loading && fields.length === 0) {
@if (!loading() && fields().length === 0) {
<li class="list-group-item" i18n>No fields defined.</li>
}
</ul>
@@ -94,7 +94,7 @@ describe('CustomFieldsComponent', () => {
toastService = TestBed.inject(ToastService)
listViewService = TestBed.inject(DocumentListViewService)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 0, username: 'test' }
settingsService.currentUser.set({ id: 0, username: 'test' })
fixture = TestBed.createComponent(CustomFieldsComponent)
component = fixture.componentInstance
@@ -1,4 +1,4 @@
import { Component, OnInit, inject } from '@angular/core'
import { Component, OnInit, inject, signal } from '@angular/core'
import { RouterModule } from '@angular/router'
import {
NgbDropdownModule,
@@ -6,7 +6,7 @@ import {
NgbPaginationModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { delay, takeUntil, tap } from 'rxjs'
import { takeUntil, tap } from 'rxjs'
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'
import { CustomFieldEditDialogComponent } from 'src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { EditDialogMode } from 'src/app/components/common/edit-dialog/edit-dialog.component'
@@ -51,33 +51,33 @@ export class CustomFieldsComponent
private readonly documentService = inject(DocumentService)
private readonly savedViewService = inject(SavedViewService)
public fields: CustomField[] = []
readonly fields = signal<CustomField[]>([])
ngOnInit() {
this.reload()
}
reload() {
this.loading.set(true)
this.customFieldsService
.listAll()
.pipe(
takeUntil(this.unsubscribeNotifier),
tap((r) => {
this.fields = r.results
}),
delay(100)
this.fields.set(r.results)
})
)
.subscribe(() => {
this.show = true
this.loading = false
this.show.set(true)
this.loading.set(false)
})
}
editField(field: CustomField) {
const modal = this.modalService.open(CustomFieldEditDialogComponent)
modal.componentInstance.dialogMode = field
? EditDialogMode.EDIT
: EditDialogMode.CREATE
modal.componentInstance.dialogMode.set(
field ? EditDialogMode.EDIT : EditDialogMode.CREATE
)
modal.componentInstance.object = field
modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier))
@@ -59,7 +59,7 @@
}
</pngx-page-header>
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [(activeId)]="activeNavID" class="nav-underline">
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [activeId]="activeNavID()" (activeIdChange)="activeNavID.set($event)" class="nav-underline">
@for (section of visibleSections; track section.id) {
<li [ngbNavItem]="section.id">
<a ngbNavLink >
@@ -100,7 +100,7 @@ describe('DocumentAttributesComponent', () => {
expect(router.navigate).toHaveBeenCalledWith(['attributes', 'tags'], {
replaceUrl: true,
})
expect(component.activeNavID).toBe(1)
expect(component.activeNavID()).toBe(1)
})
it('should set active section from route param when valid', () => {
@@ -116,7 +116,7 @@ describe('DocumentAttributesComponent', () => {
fixture.detectChanges()
paramMapSubject.next(convertToParamMap({ section: 'customfields' }))
expect(component.activeNavID).toBe(2)
expect(component.activeNavID()).toBe(2)
expect(router.navigate).not.toHaveBeenCalled()
})
@@ -124,10 +124,10 @@ describe('DocumentAttributesComponent', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
component.activeNavID = 1
component.activeNavID.set(1)
paramMapSubject.next(convertToParamMap({ section: 'customfields' }))
expect(component.activeNavID).toBe(2)
expect(component.activeNavID()).toBe(2)
})
it('should redirect to dashboard when no sections are visible', () => {
@@ -169,7 +169,7 @@ describe('DocumentAttributesComponent', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.activeManagementList).toBeNull()
component.activeNavID = 1
component.activeNavID.set(1)
expect(component.activeSection.kind).toBe(
DocumentAttributesSectionKind.ManagementList
)
@@ -180,7 +180,7 @@ describe('DocumentAttributesComponent', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.activeCustomFields).toBeNull()
component.activeNavID = 2
component.activeNavID.set(2)
expect(component.activeSection.kind).toBe(
DocumentAttributesSectionKind.CustomFields
)
@@ -1,11 +1,10 @@
import { NgComponentOutlet } from '@angular/common'
import {
AfterViewChecked,
ChangeDetectorRef,
Component,
inject,
OnDestroy,
OnInit,
signal,
Type,
ViewChild,
} from '@angular/core'
@@ -70,13 +69,10 @@ interface DocumentAttributesSection {
ClearableBadgeComponent,
],
})
export class DocumentAttributesComponent
implements OnInit, OnDestroy, AfterViewChecked
{
export class DocumentAttributesComponent implements OnInit, OnDestroy {
private readonly permissionsService = inject(PermissionsService)
private readonly activatedRoute = inject(ActivatedRoute)
private readonly router = inject(Router)
private readonly cdr = inject(ChangeDetectorRef)
private readonly unsubscribeNotifier = new Subject<void>()
protected readonly PermissionAction = PermissionAction
@@ -136,11 +132,12 @@ export class DocumentAttributesComponent
]
@ViewChild('activeOutlet', { read: NgComponentOutlet })
private readonly activeOutlet?: NgComponentOutlet
set activeOutlet(outlet: NgComponentOutlet | undefined) {
this.activeComponent.set(outlet?.componentInstance ?? null)
}
private lastHeaderLoading: boolean
activeNavID: number = null
readonly activeComponent = signal<unknown>(null)
readonly activeNavID = signal<number>(null)
get visibleSections(): DocumentAttributesSection[] {
return this.sections.filter((section) =>
@@ -153,8 +150,9 @@ export class DocumentAttributesComponent
get activeSection(): DocumentAttributesSection | null {
return (
this.visibleSections.find((section) => section.id === this.activeNavID) ??
null
this.visibleSections.find(
(section) => section.id === this.activeNavID()
) ?? null
)
}
@@ -163,14 +161,14 @@ export class DocumentAttributesComponent
this.activeSection?.kind !== DocumentAttributesSectionKind.ManagementList
)
return null
const instance = this.activeOutlet?.componentInstance
const instance = this.activeComponent()
return instance instanceof ManagementListComponent ? instance : null
}
get activeCustomFields(): CustomFieldsComponent | null {
if (this.activeSection?.kind !== DocumentAttributesSectionKind.CustomFields)
return null
const instance = this.activeOutlet?.componentInstance
const instance = this.activeComponent()
return instance instanceof CustomFieldsComponent ? instance : null
}
@@ -184,8 +182,8 @@ export class DocumentAttributesComponent
get activeHeaderLoading(): boolean {
return (
this.activeManagementList?.loading ??
this.activeCustomFields?.loading ??
this.activeManagementList?.loading() ??
this.activeCustomFields?.loading() ??
false
)
}
@@ -203,13 +201,13 @@ export class DocumentAttributesComponent
return
}
if (this.activeNavID !== navIDFromSection) {
this.activeNavID = navIDFromSection
if (this.activeNavID() !== navIDFromSection) {
this.activeNavID.set(navIDFromSection)
}
if (!section || this.getNavIDForSection(section) == null) {
this.router.navigate(
['attributes', this.getSectionForNavID(this.activeNavID)],
['attributes', this.getSectionForNavID(this.activeNavID())],
{ replaceUrl: true }
)
}
@@ -221,14 +219,6 @@ export class DocumentAttributesComponent
this.unsubscribeNotifier.complete()
}
ngAfterViewChecked(): void {
const current = this.activeHeaderLoading
if (this.lastHeaderLoading !== current) {
this.lastHeaderLoading = current
this.cdr.detectChanges()
}
}
onNavChange(navChangeEvent: NgbNavChangeEvent): void {
const nextSection = this.getSectionForNavID(navChangeEvent.nextId)
if (!nextSection) {
@@ -19,7 +19,7 @@
</select>
<span class="input-group-text text-muted d-none d-md-flex" i18n>per page</span>
</div>
<ngb-pagination [pageSize]="pageSize" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
<ngb-pagination [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
</div>
</div>
@@ -31,21 +31,21 @@
<tr>
<th>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="all-objects" [(ngModel)]="togggleAll" [disabled]="data.length === 0" (change)="$event.target.checked ? selectPage() : clearSelection(); $event.stopPropagation();">
<input type="checkbox" class="form-check-input" id="all-objects" [ngModel]="togggleAll()" [disabled]="data().length === 0" (change)="$event.target.checked ? selectPage() : clearSelection(); $event.stopPropagation();">
<label class="form-check-label" for="all-objects"></label>
</div>
</th>
<th class="fw-normal" pngxSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th class="fw-normal d-none d-sm-table-cell" pngxSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th class="fw-normal" pngxSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
<th class="fw-normal" pngxSortable="name" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Name</th>
<th class="fw-normal d-none d-sm-table-cell" pngxSortable="matching_algorithm" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Matching</th>
<th class="fw-normal" pngxSortable="document_count" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Document count</th>
@for (column of extraColumns; track column) {
<th class="fw-normal" [ngClass]="{ 'd-none d-sm-table-cell' : column.hideOnMobile }" pngxSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
<th class="fw-normal" [ngClass]="{ 'd-none d-sm-table-cell' : column.hideOnMobile }" pngxSortable="{{column.key}}" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)">{{column.name}}</th>
}
<th class="fw-normal" i18n>Actions</th>
</tr>
</thead>
<tbody>
@if (loading && data.length === 0) {
@if (loading() && data().length === 0) {
<tr>
<td colspan="5">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
@@ -53,31 +53,31 @@
</td>
</tr>
}
@for (object of data; track object) {
@for (object of data(); track object) {
<ng-container [ngTemplateOutlet]="objectRow" [ngTemplateOutletContext]="{ object: object, depth: 0 }"></ng-container>
}
</tbody>
</table>
</div>
@if (!loading || data.length > 0) {
@if (!loading() || data().length > 0) {
<div class="d-flex mb-2">
@if (displayCollectionSize > 0) {
@if (displayCollectionSize() > 0) {
<div>
<ng-container i18n>{displayCollectionSize, plural, =1 {One {{typeName}}} other {{{displayCollectionSize || 0}} total {{typeNamePlural}}}}</ng-container>
<ng-container i18n>{displayCollectionSize(), plural, =1 {One {{typeName}}} other {{{displayCollectionSize() || 0}} total {{typeNamePlural}}}}</ng-container>
@if (hasSelection) {
&nbsp;({{selectedCount}} selected)
}
</div>
}
@if (collectionSize > 20) {
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
@if (collectionSize() > 20) {
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
}
</div>
}
<ng-template #objectRow let-object="object" let-depth="depth">
<tr (click)="toggleSelected(object); $event.stopPropagation();" class="data-row fade" [class.show]="show">
<tr (click)="toggleSelected(object); $event.stopPropagation();" class="data-row fade" [class.show]="show()">
<td>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="{{typeName}}{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">
@@ -5,12 +5,7 @@ import {
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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import { RouterLinkWithHref } from '@angular/router'
@@ -140,24 +135,26 @@ describe('ManagementListComponent', () => {
// These tests are shared among all management list components
it('should support filtering, clear on Esc key', fakeAsync(() => {
it('should support filtering, clear on Esc key', () => {
jest.useFakeTimers()
const nameFilterInput = fixture.debugElement.query(By.css('input'))
nameFilterInput.nativeElement.value = 'foo'
// nameFilterInput.nativeElement.dispatchEvent(new Event('input'))
component.nameFilter = 'foo' // subject normally triggered by ngModel
tick(400) // debounce
jest.advanceTimersByTime(400) // debounce
fixture.detectChanges()
expect(component.data).toEqual([tags[0]])
expect(component.data()).toEqual([tags[0]])
nameFilterInput.nativeElement.dispatchEvent(
new KeyboardEvent('keyup', { code: 'Escape' })
)
tick(400) // debounce
jest.advanceTimersByTime(400) // debounce
fixture.detectChanges()
expect(component.nameFilter).toBeNull()
expect(component.data).toEqual(tags)
tick(100) // load
}))
expect(component.data()).toEqual(tags)
jest.advanceTimersByTime(100) // load
jest.useRealTimers()
})
it('should support create, show notification on error / success', () => {
let modal: NgbModalRef
@@ -230,7 +227,8 @@ describe('ManagementListComponent', () => {
expect(reloadSpy).toHaveBeenCalled()
})
it('should use API count for pagination and nested ids for displayed total', fakeAsync(() => {
it('should use API count for pagination and nested ids for displayed total', () => {
jest.useFakeTimers()
jest.spyOn(tagService, 'listFiltered').mockReturnValueOnce(
of({
count: 1,
@@ -240,11 +238,12 @@ describe('ManagementListComponent', () => {
)
component.reloadData()
tick(100)
jest.advanceTimersByTime(100)
expect(component.collectionSize).toBe(1)
expect(component.displayCollectionSize).toBe(3)
}))
expect(component.collectionSize()).toBe(1)
expect(component.displayCollectionSize()).toBe(3)
jest.useRealTimers()
})
it('should support quick filter for objects', () => {
const expectedUrl = documentListViewService.getQuickFilterUrl([
@@ -275,9 +274,9 @@ describe('ManagementListComponent', () => {
})
)
)
component.page = 2
component.page.set(2)
component.reloadData()
expect(component.page).toEqual(1)
expect(component.page()).toEqual(1)
})
it('should support toggle select page in vew', () => {
@@ -295,28 +294,28 @@ describe('ManagementListComponent', () => {
it('selectNone should clear selection and reset toggle flag', () => {
component.selectedObjects = new Set([tags[0].id, tags[1].id])
component.togggleAll = true
component.togggleAll.set(true)
component.selectNone()
expect(component.selectedObjects.size).toBe(0)
expect(component.togggleAll).toBe(false)
expect(component.togggleAll()).toBe(false)
})
it('selectPage should select current page items or clear selection', () => {
component.selectPage()
expect(component.selectedObjects).toEqual(new Set(tags.map((t) => t.id)))
expect(component.togggleAll).toBe(true)
expect(component.togggleAll()).toBe(true)
component.togggleAll = true
component.togggleAll.set(true)
component.clearSelection()
expect(component.selectedObjects.size).toBe(0)
expect(component.togggleAll).toBe(false)
expect(component.togggleAll()).toBe(false)
})
it('selectAll should activate all-selection mode', () => {
;(tagService.listFiltered as jest.Mock).mockClear()
component.collectionSize = tags.length
component.collectionSize.set(tags.length)
component.selectAll()
@@ -325,41 +324,41 @@ describe('ManagementListComponent', () => {
expect((component as any).allSelectionActive).toBe(true)
expect(component.hasSelection).toBe(true)
expect(component.selectedCount).toBe(tags.length)
expect(component.togggleAll).toBe(true)
expect(component.togggleAll()).toBe(true)
})
it('selectAll should clear selection when collection size is zero', () => {
component.selectedObjects = new Set([1])
component.collectionSize = 0
component.togggleAll = true
component.collectionSize.set(0)
component.togggleAll.set(true)
component.selectAll()
expect(component.selectedObjects.size).toBe(0)
expect(component.togggleAll).toBe(false)
expect(component.togggleAll()).toBe(false)
})
it('toggleSelected should toggle object selection and update toggle state', () => {
component.toggleSelected(tags[0])
expect(component.selectedObjects.has(tags[0].id)).toBe(true)
expect(component.togggleAll).toBe(false)
expect(component.togggleAll()).toBe(false)
component.toggleSelected(tags[1])
component.toggleSelected(tags[2])
expect(component.togggleAll).toBe(true)
expect(component.togggleAll()).toBe(true)
component.toggleSelected(tags[1])
expect(component.selectedObjects.has(tags[1].id)).toBe(false)
expect(component.togggleAll).toBe(false)
expect(component.togggleAll()).toBe(false)
})
it('areAllPageItemsSelected should return false when page has no selectable items', () => {
component.data = []
component.data.set([])
component.selectedObjects.clear()
expect((component as any).areAllPageItemsSelected()).toBe(false)
component.data = tags
component.data.set(tags)
})
it('should support bulk edit permissions', () => {
@@ -498,7 +497,7 @@ describe('ManagementListComponent', () => {
document_count: 10,
parent: 1,
}
component['unfilteredData'].push(childTag)
component['unfilteredData'].update((data) => [...data, childTag])
const original = component.getOriginalObject({ id: 4 } as Tag)
expect(original).toEqual(childTag)
})
@@ -525,7 +524,7 @@ describe('ManagementListComponent', () => {
expect(component.pageSize).toBe(25)
})
it('pageSize setter should update settings, reset page and reload data on success', fakeAsync(() => {
it('pageSize setter should update settings, reset page and reload data on success', () => {
const reloadSpy = jest.spyOn(component, 'reloadData')
const toastErrorSpy = jest.spyOn(toastService, 'showError')
@@ -536,21 +535,19 @@ describe('ManagementListComponent', () => {
.mockReturnValue(of({ success: true }))
component.typeNamePlural = 'tags'
component.page = 2
component.page.set(2)
component.pageSize = 100
tick()
expect(settingsService.set).toHaveBeenCalledWith(
SETTINGS_KEYS.OBJECT_LIST_SIZES,
{ tags: 100 }
)
expect(component.page).toBe(1)
expect(component.page()).toBe(1)
expect(reloadSpy).toHaveBeenCalled()
expect(toastErrorSpy).not.toHaveBeenCalled()
}))
})
it('pageSize setter should show error toast on settings store failure', fakeAsync(() => {
it('pageSize setter should show error toast on settings store failure', () => {
const reloadSpy = jest.spyOn(component, 'reloadData')
const toastErrorSpy = jest.spyOn(toastService, 'showError')
@@ -563,12 +560,10 @@ describe('ManagementListComponent', () => {
component.typeNamePlural = 'tags'
component.pageSize = 50
tick()
expect(toastErrorSpy).toHaveBeenCalledWith(
'Error saving settings',
expect.any(Error)
)
expect(reloadSpy).not.toHaveBeenCalled()
}))
})
})
@@ -5,13 +5,13 @@ import {
OnDestroy,
OnInit,
QueryList,
signal,
ViewChildren,
} from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject } from 'rxjs'
import {
debounceTime,
delay,
distinctUntilChanged,
takeUntil,
tap,
@@ -88,25 +88,25 @@ export abstract class ManagementListComponent<T extends MatchingModel>
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
public data: T[] = []
private unfilteredData: T[] = []
readonly data = signal<T[]>([])
private readonly unfilteredData = signal<T[]>([])
private currentExtraParams: { [key: string]: any } = null
private allSelectionActive = false
public page = 1
readonly page = signal(1)
public collectionSize = 0
public displayCollectionSize = 0
readonly collectionSize = signal(0)
readonly displayCollectionSize = signal(0)
public sortField: string
public sortReverse: boolean
readonly sortField = signal<string>(undefined)
readonly sortReverse = signal<boolean>(undefined)
private nameFilterDebounce: Subject<string>
protected unsubscribeNotifier: Subject<any> = new Subject()
protected _nameFilter: string
public selectedObjects: Set<number> = new Set()
public togggleAll: boolean = false
readonly togggleAll = signal(false)
public get hasSelection(): boolean {
return this.selectedObjects.size > 0 || this.allSelectionActive
@@ -114,7 +114,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
public get selectedCount(): number {
return this.allSelectionActive
? this.displayCollectionSize
? this.displayCollectionSize()
: this.selectedObjects.size
}
@@ -131,7 +131,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
)
.subscribe((title) => {
this._nameFilter = title
this.page = 1
this.page.set(1)
this.reloadData()
})
}
@@ -151,8 +151,8 @@ export abstract class ManagementListComponent<T extends MatchingModel>
}
onSort(event: SortEvent) {
this.sortField = event.column
this.sortReverse = event.reverse
this.sortField.set(event.column)
this.sortReverse.set(event.reverse)
this.reloadData()
}
@@ -171,25 +171,25 @@ export abstract class ManagementListComponent<T extends MatchingModel>
getDocumentCount(object: MatchingModel): number {
return (
object.document_count ??
this.unfilteredData.find((d) => d.id == object.id)?.document_count ??
this.unfilteredData().find((d) => d.id == object.id)?.document_count ??
0
)
}
public getOriginalObject(object: T): T {
return this.unfilteredData.find((d) => d?.id == object?.id) || object
return this.unfilteredData().find((d) => d?.id == object?.id) || object
}
reloadData(extraParams: { [key: string]: any } = null) {
this.loading = true
this.loading.set(true)
this.currentExtraParams = extraParams
this.clearSelection()
this.service
.listFiltered(
this.page,
this.page(),
this.pageSize,
this.sortField,
this.sortReverse,
this.sortField(),
this.sortReverse(),
this._nameFilter,
true,
extraParams
@@ -197,23 +197,22 @@ export abstract class ManagementListComponent<T extends MatchingModel>
.pipe(
takeUntil(this.unsubscribeNotifier),
tap((c) => {
this.unfilteredData = c.results
this.data = this.filterData(c.results)
this.collectionSize = this.getCollectionSize(c)
this.displayCollectionSize = this.getDisplayCollectionSize(c)
}),
delay(100)
this.unfilteredData.set(c.results)
this.data.set(this.filterData(c.results))
this.collectionSize.set(this.getCollectionSize(c))
this.displayCollectionSize.set(this.getDisplayCollectionSize(c))
})
)
.subscribe({
error: (error: HttpErrorResponse) => {
if (error.error?.detail?.includes('Invalid page')) {
this.page = 1
this.page.set(1)
this.reloadData()
}
},
next: () => {
this.show = true
this.loading = false
this.show.set(true)
this.loading.set(false)
},
})
}
@@ -222,7 +221,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
const activeModal = this.modalService.open(this.editDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.dialogMode = EditDialogMode.CREATE
activeModal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
activeModal.componentInstance.succeeded.subscribe(() => {
this.reloadData()
this.toastService.showInfo(
@@ -242,7 +241,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
backdrop: 'static',
})
activeModal.componentInstance.object = object
activeModal.componentInstance.dialogMode = EditDialogMode.EDIT
activeModal.componentInstance.dialogMode.set(EditDialogMode.EDIT)
activeModal.componentInstance.succeeded.subscribe(() => {
this.reloadData()
this.toastService.showInfo(
@@ -322,7 +321,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
})
this.settingsService.storeSettings().subscribe({
next: () => {
this.page = 1
this.page.set(1)
this.reloadData()
},
error: (error) => {
@@ -346,7 +345,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
if (!this.permissionsService.currentUserCan(action, this.permissionType))
return false
let ownsAll: boolean = true
const objects = this.data.filter((o) => this.selectedObjects.has(o.id))
const objects = this.data().filter((o) => this.selectedObjects.has(o.id))
ownsAll = objects.every((o) =>
this.permissionsService.currentUserOwnsObject(o)
)
@@ -367,7 +366,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
clearSelection() {
this.allSelectionActive = false
this.togggleAll = false
this.togggleAll.set(false)
this.selectedObjects.clear()
}
@@ -377,19 +376,19 @@ export abstract class ManagementListComponent<T extends MatchingModel>
selectPage() {
this.allSelectionActive = false
this.selectedObjects = new Set(this.getSelectableIDs(this.data))
this.togggleAll = this.areAllPageItemsSelected()
this.selectedObjects = new Set(this.getSelectableIDs(this.data()))
this.togggleAll.set(this.areAllPageItemsSelected())
}
selectAll() {
if (!this.collectionSize) {
if (!this.collectionSize()) {
this.clearSelection()
return
}
this.allSelectionActive = true
this.selectedObjects = new Set(this.getSelectableIDs(this.data))
this.togggleAll = this.areAllPageItemsSelected()
this.selectedObjects = new Set(this.getSelectableIDs(this.data()))
this.togggleAll.set(this.areAllPageItemsSelected())
}
toggleSelected(object) {
@@ -399,14 +398,14 @@ export abstract class ManagementListComponent<T extends MatchingModel>
this.selectedObjects.has(object.id)
? this.selectedObjects.delete(object.id)
: this.selectedObjects.add(object.id)
this.togggleAll = this.areAllPageItemsSelected()
this.togggleAll.set(this.areAllPageItemsSelected())
}
protected areAllPageItemsSelected(): boolean {
if (this.allSelectionActive) {
return this.data.length > 0
return this.data().length > 0
}
const ids = this.getSelectableIDs(this.data)
const ids = this.getSelectableIDs(this.data())
return ids.length > 0 && ids.every((id) => this.selectedObjects.has(id))
}
@@ -416,7 +415,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
})
modal.componentInstance.confirmClicked.subscribe(
({ permissions, merge }) => {
modal.componentInstance.buttonsEnabled = false
modal.componentInstance.buttonsEnabled.set(false)
this.service
.bulk_edit_objects(
this.allSelectionActive ? [] : Array.from(this.selectedObjects),
@@ -435,7 +434,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
this.reloadData()
},
error: (error) => {
modal.componentInstance.buttonsEnabled = true
modal.componentInstance.buttonsEnabled.set(true)
this.toastService.showError(
$localize`Error updating permissions`,
error
@@ -136,7 +136,7 @@ describe('TagListComponent', () => {
],
}
component.data = [parent as any]
component.data.set([parent as any])
component.selectPage()
expect(component.selectedObjects.has(10)).toBe(true)