support sort fields & some refactoring

This commit is contained in:
Michael Shamoon 2022-05-05 08:36:18 -07:00
parent 3e8bff03e7
commit 261cab8450
9 changed files with 145 additions and 117 deletions

View File

@ -94,7 +94,7 @@ export class AppFrameComponent {
search() { search() {
this.closeMenu() this.closeMenu()
this.queryParamsService.loadFilterRules([ this.queryParamsService.navigateWithFilterRules([
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: (this.searchField.value as string).trim(), value: (this.searchField.value as string).trim(),

View File

@ -67,7 +67,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
} }
clickTag(tag: PaperlessTag) { clickTag(tag: PaperlessTag) {
this.queryParamsService.loadFilterRules([ this.queryParamsService.navigateWithFilterRules([
{ rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() }, { rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
]) ])
} }

View File

@ -448,7 +448,7 @@ export class DocumentDetailComponent
} }
moreLike() { moreLike() {
this.queryParamsService.loadFilterRules([ this.queryParamsService.navigateWithFilterRules([
{ {
rule_type: FILTER_FULLTEXT_MORELIKE, rule_type: FILTER_FULLTEXT_MORELIKE,
value: this.documentId.toString(), value: this.documentId.toString(),

View File

@ -38,7 +38,7 @@
<div ngbDropdown class="btn-group ms-2 flex-fill"> <div ngbDropdown class="btn-group ms-2 flex-fill">
<button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle i18n>Sort</button> <button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle i18n>Sort</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1" class="shadow dropdown-menu-right"> <div ngbDropdownMenu aria-labelledby="dropdownBasic1" class="shadow dropdown-menu-right">
<div class="w-100 d-flex btn-group-toggle pb-2 mb-1 border-bottom" ngbRadioGroup [(ngModel)]="list.sortReverse"> <div class="w-100 d-flex btn-group-toggle pb-2 mb-1 border-bottom" ngbRadioGroup [(ngModel)]="listSort">
<label ngbButtonLabel class="btn-outline-primary btn-sm mx-2 flex-fill"> <label ngbButtonLabel class="btn-outline-primary btn-sm mx-2 flex-fill">
<input ngbButton type="radio" class="btn btn-check btn-sm" [value]="false"> <input ngbButton type="radio" class="btn btn-check btn-sm" [value]="false">
<svg class="toolbaricon" fill="currentColor"> <svg class="toolbaricon" fill="currentColor">
@ -53,7 +53,7 @@
</label> </label>
</div> </div>
<div> <div>
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="list.sortField = f.field" <button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSortField(f.field)"
[class.active]="list.sortField == f.field">{{f.name}} [class.active]="list.sortField == f.field">{{f.name}}
</button> </button>
</div> </div>

View File

@ -9,20 +9,9 @@ import {
} from '@angular/core' } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { import { filter, first, map, Subject, switchMap, takeUntil } from 'rxjs'
filter,
first,
map,
Subject,
Subscription,
switchMap,
takeUntil,
} from 'rxjs'
import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule' import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule'
import { import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
FILTER_FULLTEXT_MORELIKE,
FILTER_RULE_TYPES,
} from 'src/app/data/filter-rule-type'
import { PaperlessDocument } from 'src/app/data/paperless-document' import { PaperlessDocument } from 'src/app/data/paperless-document'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { import {
@ -31,9 +20,11 @@ import {
} from 'src/app/directives/sortable.directive' } from 'src/app/directives/sortable.directive'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { QueryParamsService } from 'src/app/services/query-params.service'
import { import {
DocumentService, filterRulesFromQueryParams,
QueryParamsService,
} from 'src/app/services/query-params.service'
import {
DOCUMENT_SORT_FIELDS, DOCUMENT_SORT_FIELDS,
DOCUMENT_SORT_FIELDS_FULLTEXT, DOCUMENT_SORT_FIELDS_FULLTEXT,
} from 'src/app/services/rest/document.service' } from 'src/app/services/rest/document.service'
@ -50,7 +41,6 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit { export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
constructor( constructor(
public list: DocumentListViewService, public list: DocumentListViewService,
private documentService: DocumentService,
public savedViewService: SavedViewService, public savedViewService: SavedViewService,
public route: ActivatedRoute, public route: ActivatedRoute,
private router: Router, private router: Router,
@ -85,8 +75,26 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
: DOCUMENT_SORT_FIELDS : DOCUMENT_SORT_FIELDS
} }
set listSort(reverse: boolean) {
this.list.sortReverse = reverse
this.queryParamsService.sortField = this.list.sortField
this.queryParamsService.sortReverse = reverse
}
get listSort(): boolean {
return this.list.sortReverse
}
setSortField(field: string) {
this.list.sortField = field
this.queryParamsService.sortField = field
this.queryParamsService.sortReverse = this.listSort
}
onSort(event: SortEvent) { onSort(event: SortEvent) {
this.list.setSort(event.column, event.reverse) this.list.setSort(event.column, event.reverse)
this.queryParamsService.sortField = event.column
this.queryParamsService.sortReverse = event.reverse
} }
get isBulkEditing(): boolean { get isBulkEditing(): boolean {
@ -139,9 +147,7 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
this.loadViewConfig(parseInt(queryParams.get('view'))) this.loadViewConfig(parseInt(queryParams.get('view')))
} else { } else {
this.list.activateSavedView(null) this.list.activateSavedView(null)
this.queryParamsService.params = queryParams this.queryParamsService.parseQueryParams(queryParams)
this.list.filterRules = this.queryParamsService.filterRules
this.list.reload()
this.unmodifiedFilterRules = [] this.unmodifiedFilterRules = []
} }
}) })
@ -152,16 +158,7 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: (filterRules) => { next: (filterRules) => {
this.queryParamsService.filterRules = filterRules this.queryParamsService.updateFilterRules(filterRules)
// if we were on a saved view we navigate 'away' to /documents
let base = []
if (this.route.snapshot.paramMap.has('id')) base = ['/documents']
this.router.navigate(base, {
relativeTo: this.route,
queryParams: this.queryParamsService.params,
})
}, },
}) })
} }
@ -272,7 +269,7 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
} }
clickMoreLike(documentID: number) { clickMoreLike(documentID: number) {
this.queryParamsService.loadFilterRules([ this.queryParamsService.navigateWithFilterRules([
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() }, { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
]) ])
} }

View File

@ -18,7 +18,6 @@ import {
SortableDirective, SortableDirective,
SortEvent, SortEvent,
} from 'src/app/directives/sortable.directive' } from 'src/app/directives/sortable.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { QueryParamsService } from 'src/app/services/query-params.service' import { QueryParamsService } from 'src/app/services/query-params.service'
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service' import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
@ -141,7 +140,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
} }
filterDocuments(object: ObjectWithId) { filterDocuments(object: ObjectWithId) {
this.queryParamsService.loadFilterRules([ this.queryParamsService.navigateWithFilterRules([
{ rule_type: this.filterRuleType, value: object.id.toString() }, { rule_type: this.filterRuleType, value: object.id.toString() },
]) ])
} }

View File

@ -9,7 +9,6 @@ import {
import { PaperlessDocument } from '../data/paperless-document' import { PaperlessDocument } from '../data/paperless-document'
import { PaperlessSavedView } from '../data/paperless-saved-view' import { PaperlessSavedView } from '../data/paperless-saved-view'
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys' import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
import { QueryParamsService } from './query-params.service'
import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service' import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service'
import { SettingsService, SETTINGS_KEYS } from './settings.service' import { SettingsService, SETTINGS_KEYS } from './settings.service'
@ -434,8 +433,7 @@ export class DocumentListViewService {
constructor( constructor(
private documentService: DocumentService, private documentService: DocumentService,
private settings: SettingsService, private settings: SettingsService
private queryParamsService: QueryParamsService
) { ) {
let documentListViewConfigJson = localStorage.getItem( let documentListViewConfigJson = localStorage.getItem(
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG

View File

@ -1,101 +1,138 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { import { ParamMap, Params, Router } from '@angular/router'
ActivatedRoute,
convertToParamMap,
ParamMap,
Params,
Router,
} from '@angular/router'
import { FilterRule } from '../data/filter-rule' import { FilterRule } from '../data/filter-rule'
import { FILTER_RULE_TYPES } from '../data/filter-rule-type' import { FILTER_RULE_TYPES } from '../data/filter-rule-type'
import { DocumentListViewService } from './document-list-view.service'
const SORT_FIELD_PARAMETER = 'sort'
const SORT_REVERSE_PARAMETER = 'reverse'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class QueryParamsService { export class QueryParamsService {
constructor(private router: Router, private route: ActivatedRoute) {} constructor(private router: Router, private list: DocumentListViewService) {}
private filterParams: Params private filterParams: Params = {}
private _filterRules: FilterRule[] private sortParams: Params = {}
set filterRules(filterRules: FilterRule[]) { updateFilterRules(
this._filterRules = filterRules filterRules: FilterRule[],
this.filterParams = this.filterRulesToQueryParams(filterRules) updateQueryParams: boolean = true
) {
this.filterParams = filterRulesToQueryParams(filterRules)
if (updateQueryParams) this.updateQueryParams()
} }
get filterRules(): FilterRule[] { set sortField(field: string) {
return this._filterRules this.sortParams[SORT_FIELD_PARAMETER] = field
this.updateQueryParams()
} }
set params(params: any) { set sortReverse(reverse: boolean) {
this.filterParams = params if (!reverse) this.sortParams[SORT_REVERSE_PARAMETER] = undefined
this._filterRules = this.filterRulesFromQueryParams( else this.sortParams[SORT_REVERSE_PARAMETER] = reverse
params.keys ? params : convertToParamMap(params) // ParamMap this.updateQueryParams()
)
} }
get params(): Params { get params(): Params {
return { return {
...this.sortParams,
...this.filterParams, ...this.filterParams,
} }
} }
private filterRulesToQueryParams(filterRules: FilterRule[]): Object { private updateQueryParams() {
if (filterRules) { // if we were on a saved view we navigate 'away' to /documents
let params = {} let base = []
for (let rule of filterRules) { if (this.router.routerState.snapshot.url.includes('/view/'))
let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type) base = ['/documents']
if (ruleType.multi) {
params[ruleType.filtervar] = params[ruleType.filtervar] this.router.navigate(base, {
? params[ruleType.filtervar] + ',' + rule.value queryParams: this.params,
: rule.value })
} else if (ruleType.isnull_filtervar && rule.value == null) { }
params[ruleType.isnull_filtervar] = true
} else { public parseQueryParams(queryParams: ParamMap) {
params[ruleType.filtervar] = rule.value let filterRules = filterRulesFromQueryParams(queryParams)
} if (
} filterRules.length ||
return params queryParams.has(SORT_FIELD_PARAMETER) ||
} else { queryParams.has(SORT_REVERSE_PARAMETER)
return null ) {
this.list.filterRules = filterRules
this.list.sortField = queryParams.get(SORT_FIELD_PARAMETER)
this.list.sortReverse =
queryParams.has(SORT_REVERSE_PARAMETER) ||
(!queryParams.has(SORT_FIELD_PARAMETER) &&
!queryParams.has(SORT_REVERSE_PARAMETER))
this.list.reload()
} else if (
filterRules.length == 0 &&
!queryParams.has(SORT_FIELD_PARAMETER)
) {
// this is navigating to /documents so we need to update the params from the list
this.updateFilterRules(this.list.filterRules, false)
this.sortField = this.list.sortField
this.sortReverse = this.list.sortReverse
} }
} }
private filterRulesFromQueryParams(queryParams: ParamMap) { navigateWithFilterRules(filterRules: FilterRule[]) {
const allFilterRuleQueryParams: string[] = FILTER_RULE_TYPES.map( this.updateFilterRules(filterRules)
(rt) => rt.filtervar
)
// transform query params to filter rules
let filterRulesFromQueryParams: FilterRule[] = []
allFilterRuleQueryParams
.filter((frqp) => queryParams.has(frqp))
.forEach((filterQueryParamName) => {
const filterQueryParamValues: string[] = queryParams
.get(filterQueryParamName)
.split(',')
filterRulesFromQueryParams = filterRulesFromQueryParams.concat(
// map all values to filter rules
filterQueryParamValues.map((val) => {
return {
rule_type: FILTER_RULE_TYPES.find(
(rt) => rt.filtervar == filterQueryParamName
).id,
value: val,
}
})
)
})
return filterRulesFromQueryParams
}
loadFilterRules(filterRules: FilterRule[]) {
this.filterRules = filterRules
this.router.navigate(['/documents'], { this.router.navigate(['/documents'], {
relativeTo: this.route,
queryParams: this.params, queryParams: this.params,
}) })
} }
} }
export function filterRulesToQueryParams(filterRules: FilterRule[]): Object {
if (filterRules) {
let params = {}
for (let rule of filterRules) {
let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type)
if (ruleType.multi) {
params[ruleType.filtervar] = params[ruleType.filtervar]
? params[ruleType.filtervar] + ',' + rule.value
: rule.value
} else if (ruleType.isnull_filtervar && rule.value == null) {
params[ruleType.isnull_filtervar] = true
} else {
params[ruleType.filtervar] = rule.value
}
}
return params
} else {
return null
}
}
export function filterRulesFromQueryParams(queryParams: ParamMap) {
const allFilterRuleQueryParams: string[] = FILTER_RULE_TYPES.map(
(rt) => rt.filtervar
)
// transform query params to filter rules
let filterRulesFromQueryParams: FilterRule[] = []
allFilterRuleQueryParams
.filter((frqp) => queryParams.has(frqp))
.forEach((filterQueryParamName) => {
const filterQueryParamValues: string[] = queryParams
.get(filterQueryParamName)
.split(',')
filterRulesFromQueryParams = filterRulesFromQueryParams.concat(
// map all values to filter rules
filterQueryParamValues.map((val) => {
return {
rule_type: FILTER_RULE_TYPES.find(
(rt) => rt.filtervar == filterQueryParamName
).id,
value: val,
}
})
)
})
return filterRulesFromQueryParams
}

View File

@ -10,9 +10,8 @@ import { map } from 'rxjs/operators'
import { CorrespondentService } from './correspondent.service' import { CorrespondentService } from './correspondent.service'
import { DocumentTypeService } from './document-type.service' import { DocumentTypeService } from './document-type.service'
import { TagService } from './tag.service' import { TagService } from './tag.service'
import { FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions' import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
import { QueryParamsService } from '../query-params.service' import { filterRulesToQueryParams } from '../query-params.service'
export const DOCUMENT_SORT_FIELDS = [ export const DOCUMENT_SORT_FIELDS = [
{ field: 'archive_serial_number', name: $localize`ASN` }, { field: 'archive_serial_number', name: $localize`ASN` },
@ -53,8 +52,7 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
http: HttpClient, http: HttpClient,
private correspondentService: CorrespondentService, private correspondentService: CorrespondentService,
private documentTypeService: DocumentTypeService, private documentTypeService: DocumentTypeService,
private tagService: TagService, private tagService: TagService
private queryParamsService: QueryParamsService
) { ) {
super(http, 'documents') super(http, 'documents')
} }
@ -82,13 +80,12 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
filterRules?: FilterRule[], filterRules?: FilterRule[],
extraParams = {} extraParams = {}
): Observable<Results<PaperlessDocument>> { ): Observable<Results<PaperlessDocument>> {
this.queryParamsService.filterRules = filterRules
return this.list( return this.list(
page, page,
pageSize, pageSize,
sortField, sortField,
sortReverse, sortReverse,
Object.assign(extraParams, this.queryParamsService.params) Object.assign(extraParams, filterRulesToQueryParams(filterRules))
).pipe( ).pipe(
map((results) => { map((results) => {
results.results.forEach((doc) => this.addObservablesToDocument(doc)) results.results.forEach((doc) => this.addObservablesToDocument(doc))