diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts index 36feeea1e..20bc26cdc 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts @@ -48,7 +48,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy { if (this.savedView.show_in_sidebar) { this.router.navigate(['view', this.savedView.id]) } else { - this.list.load(this.savedView) + this.list.loadSavedView(this.savedView, true) this.router.navigate(["documents"]) } } diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 071c8e249..af98a6f7f 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -191,8 +191,8 @@ export class DocumentDetailComponent implements OnInit { close() { this.openDocumentService.closeDocument(this.document) - if (this.documentListViewService.savedViewId) { - this.router.navigate(['view', this.documentListViewService.savedViewId]) + if (this.documentListViewService.activeSavedViewId) { + this.router.navigate(['view', this.documentListViewService.activeSavedViewId]) } else { this.router.navigate(['documents']) } diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts index 04fc2a978..ebc7a8c11 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -137,7 +137,7 @@ export class BulkEditorComponent { } else { modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on ${this.list.selected.size} selected document(s).` } - + modal.componentInstance.btnClass = "btn-warning" modal.componentInstance.btnCaption = $localize`Confirm` modal.componentInstance.confirmClicked.subscribe(() => { diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index cfc2e655d..75a9804a2 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -63,12 +63,12 @@
@@ -86,7 +86,7 @@ {list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}} (filtered)

+ [rotate]="true" aria-label="Default pagination">
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 0d1562c24..cf7afb845 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Subscription } from 'rxjs'; @@ -9,7 +9,7 @@ import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service'; import { SavedViewService } from 'src/app/services/rest/saved-view.service'; -import { Toast, ToastService } from 'src/app/services/toast.service'; +import { ToastService } from 'src/app/services/toast.service'; import { FilterEditorComponent } from './filter-editor/filter-editor.component'; import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; @@ -46,7 +46,7 @@ export class DocumentListComponent implements OnInit, OnDestroy { } getTitle() { - return this.list.savedViewTitle || $localize`Documents` + return this.list.activeSavedViewTitle || $localize`Documents` } getSortFields() { @@ -73,19 +73,18 @@ export class DocumentListComponent implements OnInit, OnDestroy { this.list.reload() }) this.route.paramMap.subscribe(params => { - this.list.clear() if (params.has('id')) { this.savedViewService.getCached(+params.get('id')).subscribe(view => { if (!view) { this.router.navigate(["404"]) return } - this.list.savedView = view + this.list.activateSavedView(view) this.list.reload() this.rulesChanged() }) } else { - this.list.savedView = null + this.list.activateSavedView(null) this.list.reload() this.rulesChanged() } @@ -99,16 +98,23 @@ export class DocumentListComponent implements OnInit, OnDestroy { } loadViewConfig(view: PaperlessSavedView) { - this.list.load(view) + this.list.loadSavedView(view) this.list.reload() this.rulesChanged() } saveViewConfig() { - this.savedViewService.update(this.list.savedView).subscribe(result => { - this.toastService.showInfo($localize`View "${this.list.savedView.name}" saved successfully.`) - }) - + if (this.list.activeSavedViewId != null) { + let savedView: PaperlessSavedView = { + id: this.list.activeSavedViewId, + filter_rules: this.list.filterRules, + sort_field: this.list.sortField, + sort_reverse: this.list.sortReverse + } + this.savedViewService.patch(savedView).subscribe(result => { + this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`) + }) + } } saveViewConfigAs() { @@ -116,7 +122,7 @@ export class DocumentListComponent implements OnInit, OnDestroy { modal.componentInstance.defaultName = this.filterEditor.generateFilterName() modal.componentInstance.saveClicked.subscribe(formValue => { modal.componentInstance.buttonsEnabled = false - let savedView = { + let savedView: PaperlessSavedView = { name: formValue.name, show_on_dashboard: formValue.showOnDashboard, show_in_sidebar: formValue.showInSideBar, @@ -137,8 +143,8 @@ export class DocumentListComponent implements OnInit, OnDestroy { resetFilters(): void { this.filterRulesModified = false - if (this.list.savedViewId) { - this.savedViewService.getCached(this.list.savedViewId).subscribe(viewUntouched => { + if (this.list.activeSavedViewId) { + this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(viewUntouched => { this.list.filterRules = viewUntouched.filter_rules this.list.reload() }) @@ -150,11 +156,11 @@ export class DocumentListComponent implements OnInit, OnDestroy { rulesChanged() { let modified = false - if (this.list.savedView == null) { + if (this.list.activeSavedViewId == null) { modified = this.list.filterRules.length > 0 // documents list is modified if it has any filters } else { // compare savedView current filters vs original - this.savedViewService.getCached(this.list.savedViewId).subscribe(view => { + this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(view => { let filterRulesInitial = view.filter_rules if (this.list.filterRules.length !== filterRulesInitial.length) modified = true diff --git a/src-ui/src/app/services/document-list-view.service.ts b/src-ui/src/app/services/document-list-view.service.ts index 4eb0c276c..334706a3c 100644 --- a/src-ui/src/app/services/document-list-view.service.ts +++ b/src-ui/src/app/services/document-list-view.service.ts @@ -8,6 +8,23 @@ import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'; import { DocumentService } from './rest/document.service'; import { SettingsService, SETTINGS_KEYS } from './settings.service'; +interface ListViewState { + + title?: string + + documents?: PaperlessDocument[] + + currentPage: number + collectionSize: number + + sortField: string + sortReverse: boolean + + filterRules: FilterRule[] + + selected?: Set + +} /** * This service manages the document list which is displayed using the document list view. @@ -20,156 +37,174 @@ import { SettingsService, SETTINGS_KEYS } from './settings.service'; }) export class DocumentListViewService { - static DEFAULT_SORT_FIELD = 'created' - isReloading: boolean = false - documents: PaperlessDocument[] = [] - currentPage = 1 - currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE) - collectionSize: number + rangeSelectionAnchorIndex: number lastRangeSelectionToIndex: number - /** - * This is the current config for the document list. The service will always remember the last settings used for the document list. - */ - private _documentListViewConfig: PaperlessSavedView - /** - * Optionally, this is the currently selected saved view, which might be null. - */ - private _savedViewConfig: PaperlessSavedView + currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE) - get savedView(): PaperlessSavedView { - return this._savedViewConfig + private listViewStates: Map = new Map() + + private _activeSavedViewId: number = null + + get activeSavedViewId() { + return this._activeSavedViewId } - set savedView(value: PaperlessSavedView) { - if (value && !this._savedViewConfig || value && value.id != this._savedViewConfig.id) { - //saved view inactive and should be active now, or saved view active, but a different view is requested - //this is here so that we don't modify value, which might be the actual instance of the saved view. - this.selectNone() - this._savedViewConfig = Object.assign({}, value) - } else if (this._savedViewConfig && !value) { - //saved view active, but document list requested - this.selectNone() - this._savedViewConfig = null + get activeSavedViewTitle() { + return this.activeListViewState.title + } + + private defaultListViewState(): ListViewState { + return { + title: null, + documents: [], + currentPage: 1, + collectionSize: null, + sortField: "created", + sortReverse: true, + filterRules: [], + selected: new Set() } } - get savedViewId() { - return this.savedView?.id + private get activeListViewState() { + if (!this.listViewStates.has(this._activeSavedViewId)) { + this.listViewStates.set(this._activeSavedViewId, this.defaultListViewState()) + } + return this.listViewStates.get(this._activeSavedViewId) } - get savedViewTitle() { - return this.savedView?.name - } - - get documentListView() { - return this._documentListViewConfig - } - - set documentListView(value) { - if (value) { - this._documentListViewConfig = Object.assign({}, value) - this.saveDocumentListView() + activateSavedView(view: PaperlessSavedView) { + this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null + if (view) { + this._activeSavedViewId = view.id + this.loadSavedView(view) + } else { + this._activeSavedViewId = null } } - /** - * This is what switches between the saved views and the document list view. Everything on the document list uses - * this property to determine the settings for the currently displayed document list. - */ - get view() { - return this.savedView || this.documentListView - } - - load(view: PaperlessSavedView) { - this.documentListView.filter_rules = cloneFilterRules(view.filter_rules) - this.documentListView.sort_reverse = view.sort_reverse - this.documentListView.sort_field = view.sort_field - this.saveDocumentListView() - } - - clear() { - this.collectionSize = null - this.documents = [] - this.currentPage = 1 + loadSavedView(view: PaperlessSavedView, closeCurrentView: boolean = false) { + if (closeCurrentView) { + this._activeSavedViewId = null + } + this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules) + this.activeListViewState.sortField = view.sort_field + this.activeListViewState.sortReverse = view.sort_reverse + if (this._activeSavedViewId) { + this.activeListViewState.title = view.name + } + this.reduceSelectionToFilter() } reload(onFinish?) { this.isReloading = true + let activeListViewState = this.activeListViewState + this.documentService.listFiltered( - this.currentPage, + activeListViewState.currentPage, this.currentPageSize, - this.view.sort_field, - this.view.sort_reverse, - this.view.filter_rules).subscribe( + activeListViewState.sortField, + activeListViewState.sortReverse, + activeListViewState.filterRules).subscribe( result => { - this.collectionSize = result.count - this.documents = result.results + this.isReloading = false + activeListViewState.collectionSize = result.count + activeListViewState.documents = result.results if (onFinish) { onFinish() } this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null - this.isReloading = false }, error => { - if (this.currentPage != 1 && error.status == 404) { + this.isReloading = false + if (activeListViewState.currentPage != 1 && error.status == 404) { // this happens when applying a filter: the current page might not be available anymore due to the reduced result set. - this.currentPage = 1 + activeListViewState.currentPage = 1 this.reload() } - this.isReloading = false }) } set filterRules(filterRules: FilterRule[]) { - //we're going to clone the filterRules object, since we don't - //want changes in the filter editor to propagate into here right away. - this.view.filter_rules = filterRules + this.activeListViewState.filterRules = filterRules this.reload() this.reduceSelectionToFilter() this.saveDocumentListView() } get filterRules(): FilterRule[] { - return this.view.filter_rules + return this.activeListViewState.filterRules } set sortField(field: string) { - this.view.sort_field = field - this.saveDocumentListView() + this.activeListViewState.sortField = field this.reload() + this.saveDocumentListView() } get sortField(): string { - return this.view.sort_field + return this.activeListViewState.sortField } set sortReverse(reverse: boolean) { - this.view.sort_reverse = reverse - this.saveDocumentListView() + this.activeListViewState.sortReverse = reverse this.reload() + this.saveDocumentListView() } get sortReverse(): boolean { - return this.view.sort_reverse + return this.activeListViewState.sortReverse + } + + get collectionSize(): number { + return this.activeListViewState.collectionSize + } + + get currentPage(): number { + return this.activeListViewState.currentPage + } + + set currentPage(page: number) { + this.activeListViewState.currentPage = page + this.reload() + this.saveDocumentListView() + } + + get documents(): PaperlessDocument[] { + return this.activeListViewState.documents + } + + get selected(): Set { + return this.activeListViewState.selected } setSort(field: string, reverse: boolean) { - this.view.sort_field = field - this.view.sort_reverse = reverse - this.saveDocumentListView() + this.activeListViewState.sortField = field + this.activeListViewState.sortReverse = reverse this.reload() + this.saveDocumentListView() } private saveDocumentListView() { - sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(this.documentListView)) + if (this._activeSavedViewId == null) { + let savedState: ListViewState = { + collectionSize: this.activeListViewState.collectionSize, + currentPage: this.activeListViewState.currentPage, + filterRules: this.activeListViewState.filterRules, + sortField: this.activeListViewState.sortField, + sortReverse: this.activeListViewState.sortReverse + } + sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(savedState)) + } } quickFilter(filterRules: FilterRule[]) { - this.savedView = null - this.view.filter_rules = filterRules + this._activeSavedViewId = null + this.activeListViewState.filterRules = filterRules + this.activeListViewState.currentPage = 1 this.reduceSelectionToFilter() this.saveDocumentListView() this.router.navigate(["documents"]) @@ -217,8 +252,6 @@ export class DocumentListViewService { } } - selected = new Set() - selectNone() { this.selected.clear() this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null @@ -227,13 +260,11 @@ export class DocumentListViewService { reduceSelectionToFilter() { if (this.selected.size > 0) { this.documentService.listAllFilteredIds(this.filterRules).subscribe(ids => { - let subset = new Set() - for (let id of ids) { - if (this.selected.has(id)) { - subset.add(id) + for (let id of this.selected) { + if (!ids.includes(id)) { + this.selected.delete(id) } } - this.selected = subset }) } } @@ -287,20 +318,21 @@ export class DocumentListViewService { } constructor(private documentService: DocumentService, private settings: SettingsService, private router: Router) { - let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG) + let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG) if (documentListViewConfigJson) { try { - this.documentListView = JSON.parse(documentListViewConfigJson) + let savedState: ListViewState = JSON.parse(documentListViewConfigJson) + // Remove null elements from the restored state + Object.keys(savedState).forEach(k => { + if (savedState[k] == null) { + delete savedState[k] + } + }) + //only use restored state attributes instead of defaults if they are not null + let newState = Object.assign(this.defaultListViewState(), savedState) + this.listViewStates.set(null, newState) } catch (e) { sessionStorage.removeItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG) - this.documentListView = null - } - } - if (!this.documentListView || this.documentListView.filter_rules == null || this.documentListView.sort_reverse == null || this.documentListView.sort_field == null) { - this.documentListView = { - filter_rules: [], - sort_reverse: true, - sort_field: 'created' } } }