<div class="btn-group w-100" ngbDropdown role="group">
- <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'">
+ <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
{{title}}
<app-clearable-badge [selected]="isActive" (cleared)="reset()"></app-clearable-badge><span class="visually-hidden">selected</span>
</button>
@Output()
datesSet = new EventEmitter<DateSelection>()
+ @Input()
+ disabled: boolean = false
+
get isActive(): boolean {
return (
this.relativeDate !== null ||
<div class="btn-group w-100" ngbDropdown role="group">
- <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'">
+ <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
</svg>
OnDestroy,
OnInit,
QueryList,
- ViewChild,
ViewChildren,
} from '@angular/core'
import { Router } from '@angular/router'
-import { Subscription } from 'rxjs'
+import { Subject, takeUntil } from 'rxjs'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
documents: PaperlessDocument[] = []
- subscription: Subscription
+ unsubscribeNotifier: Subject<any> = new Subject()
@ViewChildren('popover') popovers: QueryList<NgbPopover>
popover: NgbPopover
ngOnInit(): void {
this.reload()
- this.subscription = this.consumerStatusService
+ this.consumerStatusService
.onDocumentConsumptionFinished()
- .subscribe((status) => {
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
this.reload()
})
}
ngOnDestroy(): void {
- this.subscription.unsubscribe()
+ this.unsubscribeNotifier.next(true)
+ this.unsubscribeNotifier.complete()
}
reload() {
this.savedView.filter_rules,
{ truncate_content: true }
)
+ .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((result) => {
this.loading = false
this.documents = result.results
}
ngOnDestroy() {
- // unsubscribes all
+ this.list.cancelPending()
this.unsubscribeNotifier.next(this)
this.unsubscribeNotifier.complete()
}
</app-page-header>
-
<ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
<li *ngFor="let logFile of logFiles" [ngbNavItem]="logFile">
<a ngbNavLink>{{logFile}}.log</a>
</li>
+ <div *ngIf="isLoading && !logFiles.length" class="pb-2">
+ <div class="spinner-border spinner-border-sm me-2" role="status"></div>
+ <ng-container i18n>Loading...</ng-container>
+ </div>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="bg-dark p-3 text-light font-monospace log-container" #logContainer>
+ <div *ngIf="isLoading && logFiles.length">
+ <div class="spinner-border spinner-border-sm me-2" role="status"></div>
+ <ng-container i18n>Loading...</ng-container>
+ </div>
<p
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
*ngFor="let log of logs">{{log}}</p>
OnInit,
AfterViewChecked,
ViewChild,
+ OnDestroy,
} from '@angular/core'
+import { Subject, takeUntil } from 'rxjs'
import { LogService } from 'src/app/services/rest/log.service'
@Component({
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'],
})
-export class LogsComponent implements OnInit, AfterViewChecked {
+export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
constructor(private logService: LogService) {}
- logs: string[] = []
+ public logs: string[] = []
- logFiles: string[] = []
+ public logFiles: string[] = []
- activeLog: string
+ public activeLog: string
+
+ private unsubscribeNotifier: Subject<any> = new Subject()
+
+ public isLoading: boolean = false
@ViewChild('logContainer') logContainer: ElementRef
ngOnInit(): void {
- this.logService.list().subscribe((result) => {
- this.logFiles = result
- if (this.logFiles.length > 0) {
- this.activeLog = this.logFiles[0]
- this.reloadLogs()
- }
- })
+ this.isLoading = true
+ this.logService
+ .list()
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe((result) => {
+ this.logFiles = result
+ this.isLoading = false
+ if (this.logFiles.length > 0) {
+ this.activeLog = this.logFiles[0]
+ this.reloadLogs()
+ }
+ })
}
ngAfterViewChecked() {
this.scrollToBottom()
}
+ ngOnDestroy(): void {
+ this.unsubscribeNotifier.next(true)
+ this.unsubscribeNotifier.complete()
+ }
+
reloadLogs() {
- this.logService.get(this.activeLog).subscribe({
- next: (result) => {
- this.logs = result
- },
- error: () => {
- this.logs = []
- },
- })
+ this.isLoading = true
+ this.logService
+ .get(this.activeLog)
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe({
+ next: (result) => {
+ this.logs = result
+ this.isLoading = false
+ },
+ error: () => {
+ this.logs = []
+ this.isLoading = false
+ },
+ })
}
getLogLevel(log: string) {
</tr>
</thead>
<tbody>
+ <tr *ngIf="isLoading">
+ <td colspan="5">
+ <div class="spinner-border spinner-border-sm me-2" role="status"></div>
+ <ng-container i18n>Loading...</ng-container>
+ </td>
+ </tr>
<tr *ngFor="let object of data">
<td scope="row">{{ object.name }}</td>
<td scope="row" class="d-none d-sm-table-cell">{{ getMatching(object) }}</td>
</tbody>
</table>
-<div class="d-flex">
+<div class="d-flex" *ngIf="!isLoading">
<div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</div>
<ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
</div>
} from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject, Subscription } from 'rxjs'
-import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
+import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'
import {
MatchingModel,
MATCHING_ALGORITHMS,
public sortField: string
public sortReverse: boolean
+ public isLoading: boolean = false
+
private nameFilterDebounce: Subject<string>
- private subscription: Subscription
+ private unsubscribeNotifier: Subject<any> = new Subject()
private _nameFilter: string
ngOnInit(): void {
this.nameFilterDebounce = new Subject<string>()
- this.subscription = this.nameFilterDebounce
- .pipe(debounceTime(400), distinctUntilChanged())
+ this.nameFilterDebounce
+ .pipe(
+ takeUntil(this.unsubscribeNotifier),
+ debounceTime(400),
+ distinctUntilChanged()
+ )
.subscribe((title) => {
this._nameFilter = title
this.page = 1
}
ngOnDestroy() {
- this.subscription.unsubscribe()
+ this.unsubscribeNotifier.next(true)
+ this.unsubscribeNotifier.complete()
}
getMatching(o: MatchingModel) {
}
reloadData() {
+ this.isLoading = true
this.service
.listFiltered(
this.page,
this._nameFilter,
true
)
+ .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((c) => {
this.data = c.results
this.collectionSize = c.count
+ this.isLoading = false
})
}
activeModal.componentInstance.btnCaption = $localize`Delete`
activeModal.componentInstance.confirmClicked.subscribe(() => {
activeModal.componentInstance.buttonsEnabled = false
- this.service.delete(object).subscribe({
- next: () => {
- activeModal.close()
- this.reloadData()
- },
- error: (error) => {
- activeModal.componentInstance.buttonsEnabled = true
- this.toastService.showError(
- $localize`Error while deleting element`,
- error
- )
- },
- })
+ this.service
+ .delete(object)
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe({
+ next: () => {
+ activeModal.close()
+ this.reloadData()
+ },
+ error: (error) => {
+ activeModal.componentInstance.buttonsEnabled = true
+ this.toastService.showError(
+ $localize`Error while deleting element`,
+ error
+ )
+ },
+ })
})
}
import { Component, OnInit, OnDestroy } from '@angular/core'
import { Router } from '@angular/router'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { Subject, first } from 'rxjs'
+import { first } from 'rxjs'
import { PaperlessTask } from 'src/app/data/paperless-task'
import { TasksService } from 'src/app/services/tasks.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
{
public activeTab: string
public selectedTasks: Set<number> = new Set()
- private unsubscribeNotifer = new Subject()
public expandedTask: number
public pageSize: number = 25
}
ngOnDestroy() {
- this.unsubscribeNotifer.next(true)
+ this.tasksService.cancelPending()
}
dismissTask(task: PaperlessTask) {
})
afterEach(() => {
+ documentListViewService.cancelPending()
httpTestingController.verify()
sessionStorage.clear()
})
})
expect(documentListViewService.selected.size).toEqual(3)
})
+
+ it('should cancel on reload the list', () => {
+ const cancelSpy = jest.spyOn(documentListViewService, 'cancelPending')
+ documentListViewService.reload()
+ httpTestingController.expectOne(
+ `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9`
+ )
+ expect(cancelSpy).toHaveBeenCalled()
+ })
})
import { Injectable } from '@angular/core'
import { ParamMap, Router } from '@angular/router'
-import { Observable, first } from 'rxjs'
+import { Observable, Subject, first, takeUntil } from 'rxjs'
import { FilterRule } from '../data/filter-rule'
import {
filterRulesDiffer,
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
+ private unsubscribeNotifier: Subject<any> = new Subject()
+
private listViewStates: Map<number, ListViewState> = new Map()
private _activeSavedViewId: number = null
return this.listViewStates.get(this._activeSavedViewId)
}
+ public cancelPending(): void {
+ this.unsubscribeNotifier.next(true)
+ }
+
activateSavedView(view: PaperlessSavedView) {
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
if (view) {
}
reload(onFinish?, updateQueryParams: boolean = true) {
+ this.cancelPending()
this.isReloading = true
this.error = null
let activeListViewState = this.activeListViewState
activeListViewState.filterRules,
{ truncate_content: true }
)
+ .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (result) => {
this.initialized = true
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
-import { first } from 'rxjs/operators'
+import { Subject } from 'rxjs'
+import { first, takeUntil } from 'rxjs/operators'
import {
PaperlessTask,
PaperlessTaskStatus,
export class TasksService {
private baseUrl: string = environment.apiBaseUrl
- loading: boolean
+ public loading: boolean
private fileTasks: PaperlessTask[] = []
+ private unsubscribeNotifer: Subject<any> = new Subject()
+
public get total(): number {
return this.fileTasks.length
}
this.http
.get<PaperlessTask[]>(`${this.baseUrl}tasks/`)
- .pipe(first())
+ .pipe(takeUntil(this.unsubscribeNotifer), first())
.subscribe((r) => {
this.fileTasks = r.filter((t) => t.type == PaperlessTaskType.File) // they're all File tasks, for now
this.loading = false
.post(`${this.baseUrl}acknowledge_tasks/`, {
tasks: [...task_ids],
})
- .pipe(first())
+ .pipe(takeUntil(this.unsubscribeNotifer), first())
.subscribe((r) => {
this.reload()
})
}
+
+ public cancelPending(): void {
+ this.unsubscribeNotifer.next(true)
+ }
}