mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	partial selection model implementation
This commit is contained in:
		| @@ -30,9 +30,15 @@ export class DateDropdownComponent implements OnInit, OnDestroy { | |||||||
|   @Input() |   @Input() | ||||||
|   dateBefore: string |   dateBefore: string | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   dateBeforeChange = new EventEmitter<string>() | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   dateAfter: string |   dateAfter: string | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   dateAfterChange = new EventEmitter<string>() | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   title: string |   title: string | ||||||
|  |  | ||||||
| @@ -83,6 +89,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   onChange() { |   onChange() { | ||||||
|  |     this.dateAfterChange.emit(this.dateAfter) | ||||||
|  |     this.dateBeforeChange.emit(this.dateBefore) | ||||||
|     this.datesSet.emit({after: this.dateAfter, before: this.dateBefore}) |     this.datesSet.emit({after: this.dateAfter, before: this.dateBefore}) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -91,12 +99,12 @@ export class DateDropdownComponent implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   clearBefore() { |   clearBefore() { | ||||||
|     this.dateBefore = null; |     this.dateBefore = null | ||||||
|     this.onChange() |     this.onChange() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   clearAfter() { |   clearAfter() { | ||||||
|     this.dateAfter = null; |     this.dateAfter = null | ||||||
|     this.onChange() |     this.onChange() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +1,14 @@ | |||||||
| <div class="btn-group" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown"> | <div class="btn-group" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown"> | ||||||
|   <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="type !== types.Editing && itemsSelected?.length > 0 ? 'btn-primary' : 'btn-outline-primary'"> |   <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="type !== types.Editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'"> | ||||||
|     <div class="d-none d-md-inline">{{title}}</div> |     <div class="d-none d-md-inline">{{title}}</div> | ||||||
|     <div class="d-inline-block d-md-none"> |     <div class="d-inline-block d-md-none"> | ||||||
|       <svg class="toolbaricon" fill="currentColor"> |       <svg class="toolbaricon" fill="currentColor"> | ||||||
|         <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" /> |         <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" /> | ||||||
|       </svg> |       </svg> | ||||||
|     </div> |     </div> | ||||||
|     <ng-container *ngIf="type !== types.Editing && itemsSelected?.length > 0"> |     <ng-container *ngIf="type !== types.Editing && selectionModel.selectionSize() > 0"> | ||||||
|       <div class="badge bg-secondary text-light rounded-pill badge-corner"> |       <div class="badge bg-secondary text-light rounded-pill badge-corner"> | ||||||
|         {{itemsSelected?.length}} |         {{selectionModel.selectionSize()}} | ||||||
|       </div> |       </div> | ||||||
|     </ng-container> |     </ng-container> | ||||||
|   </button> |   </button> | ||||||
| @@ -19,9 +19,9 @@ | |||||||
|           <input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput> |           <input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div *ngIf="toggleableItems" class="items"> |       <div *ngIf="selectionModel.items" class="items"> | ||||||
|         <ng-container *ngFor="let toggleableItem of toggleableItems | filter: filterText"> |         <ng-container *ngFor="let toggleableItem of selectionModel.items | filter: filterText"> | ||||||
|           <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="toggleItem($event)"></app-toggleable-dropdown-button> |           <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="selectionModel.toggle(toggleableItem.item)"></app-toggleable-dropdown-button> | ||||||
|         </ng-container> |         </ng-container> | ||||||
|       </div> |       </div> | ||||||
|       <button *ngIf="type == types.Editing" class="list-group-item list-group-item-action bg-light" (click)="dropdown.close()" [disabled]="!hasBeenToggled || (toggleableItems | filter: filterText).length == 0"> |       <button *ngIf="type == types.Editing" class="list-group-item list-group-item-action bg-light" (click)="dropdown.close()" [disabled]="!hasBeenToggled || (toggleableItems | filter: filterText).length == 0"> | ||||||
|   | |||||||
| @@ -3,12 +3,55 @@ import { FilterPipe } from  'src/app/pipes/filter.pipe'; | |||||||
| import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | ||||||
| import { ToggleableItem, ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component'; | import { ToggleableItem, ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component'; | ||||||
| import { MatchingModel } from 'src/app/data/matching-model'; | import { MatchingModel } from 'src/app/data/matching-model'; | ||||||
|  | import { Subject } from 'rxjs'; | ||||||
|  |  | ||||||
| export enum FilterableDropdownType { | export enum FilterableDropdownType { | ||||||
|   Filtering = 'filtering', |   Filtering = 'filtering', | ||||||
|   Editing = 'editing' |   Editing = 'editing' | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export class FilterableDropdownSelectionModel { | ||||||
|  |  | ||||||
|  |   changed = new Subject<FilterableDropdownSelectionModel>() | ||||||
|  |  | ||||||
|  |   multiple = false | ||||||
|  |  | ||||||
|  |   items: ToggleableItem[] = [] | ||||||
|  |  | ||||||
|  |   getSelected() { | ||||||
|  |     return this.items.filter(i => i.state == ToggleableItemState.Selected).map(i => i.item) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   toggle(item: MatchingModel, fireEvent = true) { | ||||||
|  |     console.log("TOGGLE TAG") | ||||||
|  |     let toggleableItem = this.items.find(i => i.item == item) | ||||||
|  |     console.log(toggleableItem) | ||||||
|  |  | ||||||
|  |     if (toggleableItem) { | ||||||
|  |       if (toggleableItem.state == ToggleableItemState.Selected) { | ||||||
|  |         toggleableItem.state = ToggleableItemState.NotSelected | ||||||
|  |       } else { | ||||||
|  |         this.items.forEach(i => { | ||||||
|  |           if (i.item == item) { | ||||||
|  |             i.state = ToggleableItemState.Selected | ||||||
|  |           } else if (!this.multiple) { | ||||||
|  |             i.state = ToggleableItemState.NotSelected | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |       if (fireEvent) { | ||||||
|  |         this.changed.next(this) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   selectionSize() { | ||||||
|  |     return this.getSelected().length | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-filterable-dropdown', |   selector: 'app-filterable-dropdown', | ||||||
|   templateUrl: './filterable-dropdown.component.html', |   templateUrl: './filterable-dropdown.component.html', | ||||||
| @@ -24,33 +67,45 @@ export class FilterableDropdownComponent { | |||||||
|   @Input() |   @Input() | ||||||
|   set items(items: MatchingModel[]) { |   set items(items: MatchingModel[]) { | ||||||
|     if (items) { |     if (items) { | ||||||
|       this._toggleableItems = items.map(i => { |       this._selectionModel.items = items.map(i => { | ||||||
|         return {item: i, state: ToggleableItemState.NotSelected, count: i.document_count} |         return {item: i, state: ToggleableItemState.NotSelected, count: i.document_count} | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _toggleableItems: ToggleableItem[] = [] |   get items(): MatchingModel[] { | ||||||
|  |     return this._selectionModel.items.map(i => i.item) | ||||||
|   @Input() |  | ||||||
|   set toggleableItems (toggleableItems: ToggleableItem[]) { |  | ||||||
|     if (this.type == FilterableDropdownType.Editing && this.dropdown?.isOpen()) return |  | ||||||
|     else this._toggleableItems = toggleableItems |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get toggleableItems(): ToggleableItem[] { |   _selectionModel = new FilterableDropdownSelectionModel() | ||||||
|     return this._toggleableItems |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   set itemsSelected(itemsSelected: MatchingModel[]) { |   set selectionModel(model: FilterableDropdownSelectionModel) { | ||||||
|     this.toggleableItems.forEach(i => { |     if (this.selectionModel) { | ||||||
|       i.state = (itemsSelected.find(is => is.id == i.item.id)) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected |       this.selectionModel.changed.complete() | ||||||
|  |       model.items = this.selectionModel.items | ||||||
|  |       model.multiple = this.selectionModel.multiple | ||||||
|  |     } | ||||||
|  |     model.changed.subscribe(updatedModel => { | ||||||
|  |       this.selectionModelChange.next(updatedModel) | ||||||
|     }) |     }) | ||||||
|  |     this._selectionModel = model | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get itemsSelected(): MatchingModel[] { |   get selectionModel(): FilterableDropdownSelectionModel { | ||||||
|     return this.toggleableItems.filter(ti => ti.state == ToggleableItemState.Selected).map(ti => ti.item) |     return this._selectionModel | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>() | ||||||
|  |  | ||||||
|  |   @Input() | ||||||
|  |   set multiple(value: boolean) { | ||||||
|  |     this.selectionModel.multiple = value | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get multiple() { | ||||||
|  |     return this.selectionModel.multiple | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
| @@ -64,50 +119,40 @@ export class FilterableDropdownComponent { | |||||||
|  |  | ||||||
|   types = FilterableDropdownType |   types = FilterableDropdownType | ||||||
|  |  | ||||||
|   @Input() |  | ||||||
|   singular: boolean = false |  | ||||||
|  |  | ||||||
|   @Output() |  | ||||||
|   toggle = new EventEmitter() |  | ||||||
|  |  | ||||||
|   @Output() |  | ||||||
|   open = new EventEmitter() |  | ||||||
|  |  | ||||||
|   @Output() |  | ||||||
|   editingComplete = new EventEmitter() |  | ||||||
|  |  | ||||||
|   hasBeenToggled:boolean = false |   hasBeenToggled:boolean = false | ||||||
|  |  | ||||||
|   constructor(private filterPipe: FilterPipe) { } |   constructor(private filterPipe: FilterPipe) { | ||||||
|  |     this.selectionModel = new FilterableDropdownSelectionModel() | ||||||
|  |   } | ||||||
|  |  | ||||||
|   toggleItem(toggleableItem: ToggleableItem): void { |   toggleItem(toggleableItem: ToggleableItem): void { | ||||||
|     if (this.singular && toggleableItem.state == ToggleableItemState.Selected) { |     // if (this.singular && toggleableItem.state == ToggleableItemState.Selected) { | ||||||
|       this._toggleableItems.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected) |     //   this.selectionModel.items.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected) | ||||||
|     } |     // } | ||||||
|     this.hasBeenToggled = true |     // this.hasBeenToggled = true | ||||||
|     this.toggle.emit(toggleableItem.item) |     // this.toggle.emit(toggleableItem.item) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dropdownOpenChange(open: boolean): void { |   dropdownOpenChange(open: boolean): void { | ||||||
|     if (open) { |     // if (open) { | ||||||
|       setTimeout(() => { |     //   setTimeout(() => { | ||||||
|         this.listFilterTextInput.nativeElement.focus(); |     //     this.listFilterTextInput.nativeElement.focus(); | ||||||
|       }, 0) |     //   }, 0) | ||||||
|       this.hasBeenToggled = false |     //   this.hasBeenToggled = false | ||||||
|       this.open.next() |     //   this.open.next() | ||||||
|     } else { |     // } else { | ||||||
|       this.filterText = '' |     //   this.filterText = '' | ||||||
|       if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems) |     //   if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems) | ||||||
|     } |     // } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   listFilterEnter(): void { |   listFilterEnter(): void { | ||||||
|     let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText) |     // let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText) | ||||||
|     if (filtered.length == 1) { |     // if (filtered.length == 1) { | ||||||
|       let toggleableItem = this.toggleableItems.find(ti => ti.item.id == filtered[0].item.id) |     //   let toggleableItem = this.toggleableItems.find(ti => ti.item.id == filtered[0].item.id) | ||||||
|       if (toggleableItem) toggleableItem.state = ToggleableItemState.Selected |     //   if (toggleableItem) toggleableItem.state = ToggleableItemState.Selected | ||||||
|       this.toggleItem(filtered[0]) |     //   this.toggleItem(filtered[0]) | ||||||
|       this.dropdown.close() |     //   this.dropdown.close() | ||||||
|     } |     // } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,8 +31,7 @@ export class ToggleableDropdownButtonComponent { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   toggleItem(): void { |   toggleItem(): void { | ||||||
|     this.toggleableItem.state = (this.toggleableItem.state == ToggleableItemState.NotSelected || this.toggleableItem.state == ToggleableItemState.PartiallySelected) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected |     this.toggle.emit() | ||||||
|     this.toggle.emit(this.toggleableItem) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getSelectedIconName() { |   getSelectedIconName() { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| <div class="row"> | <!-- <div class="row"> | ||||||
|   <div class="col-auto mb-2 mb-xl-0" role="group" aria-label="Select"> |   <div class="col-auto mb-2 mb-xl-0" role="group" aria-label="Select"> | ||||||
|     <button class="btn btn-sm btn-outline-danger" (click)="documentList.selectNone()"> |     <button class="btn btn-sm btn-outline-danger" (click)="documentList.selectNone()"> | ||||||
|       <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> |       <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||||
| @@ -58,4 +58,4 @@ | |||||||
|       Delete |       Delete | ||||||
|     </button> |     </button> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> --> | ||||||
|   | |||||||
| @@ -8,11 +8,11 @@ | |||||||
|   <div class="w-100 d-xl-none"></div> |   <div class="w-100 d-xl-none"></div> | ||||||
|    <div class="col col-xl-auto mb-2 mb-xl-0"> |    <div class="col col-xl-auto mb-2 mb-xl-0"> | ||||||
|      <div class="d-flex"> |      <div class="d-flex"> | ||||||
|        <app-filterable-dropdown class="mr-2 mr-md-3" [items]="tags" [itemsSelected]="selectedTags" title="Tags" icon="tag-fill" (toggle)="toggleTag($event.id)"></app-filterable-dropdown> |        <app-filterable-dropdown class="mr-2 mr-md-3" [items]="tags" [(selectionModel)]="tagSelectionModel" (selectionModelChange)="updateRules()" [multiple]="true" title="Tags" icon="tag-fill"></app-filterable-dropdown> | ||||||
|        <app-filterable-dropdown class="mr-2 mr-md-3" [items]="correspondents" [itemsSelected]="selectedCorrespondents" title="Correspondents" icon="person-fill" (toggle)="toggleCorrespondent($event.id)"></app-filterable-dropdown> |        <app-filterable-dropdown class="mr-2 mr-md-3" [items]="correspondents" [(selectionModel)]="correspondentSelectionModel" (selectionModelChange)="updateRules()" title="Correspondents" icon="person-fill"></app-filterable-dropdown> | ||||||
|        <app-filterable-dropdown class="mr-2 mr-md-3" [items]="documentTypes" [itemsSelected]="selectedDocumentTypes" title="Document types" icon="file-earmark-fill" (toggle)="toggleDocumentType($event.id)"></app-filterable-dropdown> |        <app-filterable-dropdown class="mr-2 mr-md-3" [items]="documentTypes" [(selectionModel)]="documentTypeSelectionModel" (selectionModelChange)="updateRules()" title="Document types" icon="file-earmark-fill"></app-filterable-dropdown> | ||||||
|        <app-date-dropdown class="mr-2 mr-md-3" [dateBefore]="dateCreatedBefore" [dateAfter]="dateCreatedAfter" title="Created" (datesSet)="onDatesCreatedSet($event)"></app-date-dropdown> |        <app-date-dropdown class="mr-2 mr-md-3" [(dateBefore)]="dateCreatedBefore" [(dateAfter)]="dateCreatedAfter" title="Created" (datesSet)="updateRules()"></app-date-dropdown> | ||||||
|        <app-date-dropdown [dateBefore]="dateAddedBefore" [dateAfter]="dateAddedAfter" title="Added"  (datesSet)="onDatesAddedSet($event)"></app-date-dropdown> |        <app-date-dropdown [(dateBefore)]="dateAddedBefore" [(dateAfter)]="dateAddedAfter" title="Added"  (datesSet)="updateRules()"></app-date-dropdown> | ||||||
|      </div> |      </div> | ||||||
|    </div> |    </div> | ||||||
|    <div class="w-100 d-xl-none"></div> |    <div class="w-100 d-xl-none"></div> | ||||||
|   | |||||||
| @@ -3,14 +3,13 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'; | |||||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||||
| import { Subject, Subscription } from 'rxjs'; | import { Subject, Subscription } from 'rxjs'; | ||||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; | import { debounceTime, distinctUntilChanged, filter, flatMap, mergeMap } from 'rxjs/operators'; | ||||||
| import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap'; |  | ||||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||||
| import { TagService } from 'src/app/services/rest/tag.service'; | import { TagService } from 'src/app/services/rest/tag.service'; | ||||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||||
| import { FilterRule } from 'src/app/data/filter-rule'; | import { FilterRule } from 'src/app/data/filter-rule'; | ||||||
| import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES, FILTER_TITLE } from 'src/app/data/filter-rule-type'; | import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES, FILTER_TITLE } from 'src/app/data/filter-rule-type'; | ||||||
| import { DateSelection } from 'src/app/components/common/date-dropdown/date-dropdown.component'; | import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-filter-editor', |   selector: 'app-filter-editor', | ||||||
| @@ -46,37 +45,91 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|   ) { } |   ) { } | ||||||
|  |  | ||||||
|   tags: PaperlessTag[] = [] |   tags: PaperlessTag[] = [] | ||||||
|   correspondents: PaperlessCorrespondent[] |   correspondents: PaperlessCorrespondent[] = [] | ||||||
|   documentTypes: PaperlessDocumentType[] = [] |   documentTypes: PaperlessDocumentType[] = [] | ||||||
|  |  | ||||||
|  |   _titleFilter = "" | ||||||
|  |  | ||||||
|  |   tagSelectionModel = new FilterableDropdownSelectionModel() | ||||||
|  |   correspondentSelectionModel = new FilterableDropdownSelectionModel() | ||||||
|  |   documentTypeSelectionModel = new FilterableDropdownSelectionModel() | ||||||
|  |  | ||||||
|  |   dateCreatedBefore: string | ||||||
|  |   dateCreatedAfter: string | ||||||
|  |   dateAddedBefore: string | ||||||
|  |   dateAddedAfter: string | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   filterRules: FilterRule[] |   set filterRules (value: FilterRule[]) { | ||||||
|  |     console.log("SET FILTER RULES") | ||||||
|  |     value.forEach(rule => { | ||||||
|  |       switch (rule.rule_type) { | ||||||
|  |         case FILTER_TITLE: | ||||||
|  |           this._titleFilter = rule.value | ||||||
|  |           break | ||||||
|  |         case FILTER_CREATED_AFTER: | ||||||
|  |           this.dateCreatedAfter = rule.value | ||||||
|  |           break | ||||||
|  |         case FILTER_CREATED_BEFORE: | ||||||
|  |           this.dateCreatedBefore = rule.value | ||||||
|  |           break | ||||||
|  |         case FILTER_ADDED_AFTER: | ||||||
|  |           this.dateAddedAfter = rule.value | ||||||
|  |           break | ||||||
|  |         case FILTER_ADDED_BEFORE: | ||||||
|  |           this.dateAddedBefore = rule.value | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     this.tagService.getCachedMany(value.filter(v => v.rule_type == FILTER_HAS_TAG).map(rule => +rule.value)).subscribe(tags => { | ||||||
|  |       console.log(tags) | ||||||
|  |       tags.forEach(tag => this.tagSelectionModel.toggle(tag, false)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @Output() |   @Output() | ||||||
|   filterRulesChange = new EventEmitter<FilterRule[]>() |   filterRulesChange = new EventEmitter<FilterRule[]>() | ||||||
|  |  | ||||||
|  |   updateRules() { | ||||||
|  |     console.log("UPDATE RULES!!!") | ||||||
|  |     let filterRules: FilterRule[] = [] | ||||||
|  |     if (this._titleFilter) { | ||||||
|  |       filterRules.push({rule_type: FILTER_TITLE, value: this._titleFilter}) | ||||||
|  |     } | ||||||
|  |     this.tagSelectionModel.getSelected().forEach(tag => { | ||||||
|  |       filterRules.push({rule_type: FILTER_HAS_TAG, value: tag.id.toString()}) | ||||||
|  |     }) | ||||||
|  |     this.correspondentSelectionModel.getSelected().forEach(correspondent => { | ||||||
|  |       filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id.toString()}) | ||||||
|  |     }) | ||||||
|  |     this.documentTypeSelectionModel.getSelected().forEach(documentType => { | ||||||
|  |       filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id.toString()}) | ||||||
|  |     }) | ||||||
|  |     if (this.dateCreatedBefore) { | ||||||
|  |       filterRules.push({rule_type: FILTER_CREATED_BEFORE, value: this.dateCreatedBefore}) | ||||||
|  |     } | ||||||
|  |     if (this.dateCreatedAfter) { | ||||||
|  |       filterRules.push({rule_type: FILTER_CREATED_AFTER, value: this.dateCreatedAfter}) | ||||||
|  |     } | ||||||
|  |     if (this.dateAddedBefore) { | ||||||
|  |       filterRules.push({rule_type: FILTER_ADDED_BEFORE, value: this.dateAddedBefore}) | ||||||
|  |     } | ||||||
|  |     if (this.dateAddedAfter) { | ||||||
|  |       filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter}) | ||||||
|  |     } | ||||||
|  |     console.log(filterRules) | ||||||
|  |     this.filterRulesChange.next(filterRules) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   hasFilters() { |   hasFilters() { | ||||||
|     return this.filterRules.length > 0 |     return this._titleFilter ||  | ||||||
|   } |       this.dateCreatedAfter || this.dateAddedBefore || this.dateCreatedAfter || this.dateCreatedBefore || | ||||||
|  |       this.tagSelectionModel.selectionSize() || this.correspondentSelectionModel.selectionSize() || this.documentTypeSelectionModel.selectionSize() | ||||||
|   get selectedTags(): PaperlessTag[] { |  | ||||||
|     let tagRules: FilterRule[] = this.filterRules.filter(fr => fr.rule_type == FILTER_HAS_TAG) |  | ||||||
|     return this.tags?.filter(t => tagRules.find(tr => +tr.value == t.id)) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get selectedCorrespondents(): PaperlessCorrespondent[] { |  | ||||||
|     let correspondentRules: FilterRule[] = this.filterRules.filter(fr => fr.rule_type == FILTER_CORRESPONDENT) |  | ||||||
|     return this.correspondents?.filter(c => correspondentRules.find(cr => +cr.value == c.id)) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get selectedDocumentTypes(): PaperlessDocumentType[] { |  | ||||||
|     let documentTypeRules: FilterRule[] = this.filterRules.filter(fr => fr.rule_type == FILTER_DOCUMENT_TYPE) |  | ||||||
|     return this.documentTypes?.filter(dt => documentTypeRules.find(dtr => +dtr.value == dt.id)) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get titleFilter() { |   get titleFilter() { | ||||||
|     let existingRule = this.filterRules.find(rule => rule.rule_type == FILTER_TITLE) |     return this._titleFilter | ||||||
|     return existingRule ? existingRule.value : '' |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set titleFilter(value) { |   set titleFilter(value) { | ||||||
| @@ -97,142 +150,27 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|       debounceTime(400), |       debounceTime(400), | ||||||
|       distinctUntilChanged() |       distinctUntilChanged() | ||||||
|     ).subscribe(title => { |     ).subscribe(title => { | ||||||
|       this.setTitleRule(title) |       this._titleFilter = title | ||||||
|  |       this.updateRules() | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ngOnDestroy() { |   ngOnDestroy() { | ||||||
|     this.titleFilterDebounce.complete() |     this.titleFilterDebounce.complete() | ||||||
|     // TODO: not sure if both is necessary |  | ||||||
|     this.subscription.unsubscribe() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   applyFilters() { |  | ||||||
|     this.filterRulesChange.next(this.filterRules) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   clearSelected() { |   clearSelected() { | ||||||
|     this.filterRules = [] |     this._titleFilter = "" | ||||||
|     this.applyFilters() |     this.updateRules() | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private toggleFilterRule(filterRuleTypeID: number, value: number) { |  | ||||||
|  |  | ||||||
|     let filterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID) |  | ||||||
|  |  | ||||||
|     let existingRule = this.filterRules.find(rule => rule.rule_type == filterRuleTypeID && rule.value == value?.toString()) |  | ||||||
|     let existingRuleOfSameType = this.filterRules.find(rule => rule.rule_type == filterRuleTypeID) |  | ||||||
|  |  | ||||||
|     if (existingRule) { |  | ||||||
|       // if this exact rule already exists, remove it in all cases. |  | ||||||
|       this.filterRules.splice(this.filterRules.indexOf(existingRule), 1) |  | ||||||
|     } else if (filterRuleType.multi || !existingRuleOfSameType) { |  | ||||||
|       // if we allow multiple rules per type, or no rule of this type already exists, push a new rule. |  | ||||||
|       this.filterRules.push({rule_type: filterRuleTypeID, value: value?.toString()}) |  | ||||||
|     } else { |  | ||||||
|       // otherwise (i.e., no multi support AND there's already a rule of this type), update the rule. |  | ||||||
|       existingRuleOfSameType.value = value?.toString() |  | ||||||
|     } |  | ||||||
|     this.applyFilters() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private setTitleRule(title: string) { |  | ||||||
|     let existingRule = this.filterRules.find(rule => rule.rule_type == FILTER_TITLE) |  | ||||||
|  |  | ||||||
|     if (!existingRule && title) { |  | ||||||
|       this.filterRules.push({rule_type: FILTER_TITLE, value: title}) |  | ||||||
|     } else if (existingRule && !title) { |  | ||||||
|       this.filterRules.splice(this.filterRules.findIndex(rule => rule.rule_type == FILTER_TITLE), 1) |  | ||||||
|     } else if (existingRule && title) { |  | ||||||
|       existingRule.value = title |  | ||||||
|     } |  | ||||||
|     this.applyFilters() |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   toggleTag(tagId: number) { |   toggleTag(tagId: number) { | ||||||
|     this.toggleFilterRule(FILTER_HAS_TAG, tagId) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   toggleCorrespondent(correspondentId: number) { |   toggleCorrespondent(correspondentId: number) { | ||||||
|     this.toggleFilterRule(FILTER_CORRESPONDENT, correspondentId) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   toggleDocumentType(documentTypeId: number) { |   toggleDocumentType(documentTypeId: number) { | ||||||
|     this.toggleFilterRule(FILTER_DOCUMENT_TYPE, documentTypeId) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   // Date handling |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   onDatesCreatedSet(dates: DateSelection) { |  | ||||||
|     this.setDateCreatedBefore(dates.before) |  | ||||||
|     this.setDateCreatedAfter(dates.after) |  | ||||||
|     this.applyFilters() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   onDatesAddedSet(dates: DateSelection) { |  | ||||||
|     this.setDateAddedBefore(dates.before) |  | ||||||
|     this.setDateAddedAfter(dates.after) |  | ||||||
|     this.applyFilters() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateCreatedBefore(): string { |  | ||||||
|     let createdBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_BEFORE) |  | ||||||
|     return createdBeforeRule ? createdBeforeRule.value : null |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateCreatedAfter(): string { |  | ||||||
|     let createdAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_AFTER) |  | ||||||
|     return createdAfterRule ? createdAfterRule.value : null |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateAddedBefore(): string { |  | ||||||
|     let addedBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_BEFORE) |  | ||||||
|     return addedBeforeRule ? addedBeforeRule.value : null |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateAddedAfter(): string { |  | ||||||
|     let addedAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_AFTER) |  | ||||||
|     return addedAfterRule ? addedAfterRule.value : null |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateCreatedBefore(date?: string) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_CREATED_BEFORE) |  | ||||||
|     else this.clearDateFilter(FILTER_CREATED_BEFORE) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateCreatedAfter(date?: string) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_CREATED_AFTER) |  | ||||||
|     else this.clearDateFilter(FILTER_CREATED_AFTER) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateAddedBefore(date?: string) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_ADDED_BEFORE) |  | ||||||
|     else this.clearDateFilter(FILTER_ADDED_BEFORE) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateAddedAfter(date?: string) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_ADDED_AFTER) |  | ||||||
|     else this.clearDateFilter(FILTER_ADDED_AFTER) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateFilter(date: string, dateRuleTypeID: number) { |  | ||||||
|     let existingRule = this.filterRules.find(rule => rule.rule_type == dateRuleTypeID) |  | ||||||
|  |  | ||||||
|     if (existingRule) { |  | ||||||
|       existingRule.value = date |  | ||||||
|     } else { |  | ||||||
|       this.filterRules.push({rule_type: dateRuleTypeID, value: date}) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   clearDateFilter(dateRuleTypeID: number) { |  | ||||||
|     let ruleIndex = this.filterRules.findIndex(rule => rule.rule_type == dateRuleTypeID) |  | ||||||
|     if (ruleIndex != -1) { |  | ||||||
|       this.filterRules.splice(ruleIndex, 1) |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 jonaswinkler
					jonaswinkler