mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	partial selection model implementation
This commit is contained in:
		@@ -30,9 +30,15 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
  @Input()
 | 
			
		||||
  dateBefore: string
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  dateBeforeChange = new EventEmitter<string>()
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  dateAfter: string
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  dateAfterChange = new EventEmitter<string>()
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
@@ -83,6 +89,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChange() {
 | 
			
		||||
    this.dateAfterChange.emit(this.dateAfter)
 | 
			
		||||
    this.dateBeforeChange.emit(this.dateBefore)
 | 
			
		||||
    this.datesSet.emit({after: this.dateAfter, before: this.dateBefore})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -91,12 +99,12 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearBefore() {
 | 
			
		||||
    this.dateBefore = null;
 | 
			
		||||
    this.dateBefore = null
 | 
			
		||||
    this.onChange()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearAfter() {
 | 
			
		||||
    this.dateAfter = null;
 | 
			
		||||
    this.dateAfter = null
 | 
			
		||||
    this.onChange()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
<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-inline-block d-md-none">
 | 
			
		||||
      <svg class="toolbaricon" fill="currentColor">
 | 
			
		||||
        <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
 | 
			
		||||
      </svg>
 | 
			
		||||
    </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">
 | 
			
		||||
        {{itemsSelected?.length}}
 | 
			
		||||
        {{selectionModel.selectionSize()}}
 | 
			
		||||
      </div>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </button>
 | 
			
		||||
@@ -19,9 +19,9 @@
 | 
			
		||||
          <input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div *ngIf="toggleableItems" class="items">
 | 
			
		||||
        <ng-container *ngFor="let toggleableItem of toggleableItems | filter: filterText">
 | 
			
		||||
          <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="toggleItem($event)"></app-toggleable-dropdown-button>
 | 
			
		||||
      <div *ngIf="selectionModel.items" class="items">
 | 
			
		||||
        <ng-container *ngFor="let toggleableItem of selectionModel.items | filter: filterText">
 | 
			
		||||
          <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="selectionModel.toggle(toggleableItem.item)"></app-toggleable-dropdown-button>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
      </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">
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,55 @@ import { FilterPipe } from  'src/app/pipes/filter.pipe';
 | 
			
		||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { ToggleableItem, ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model';
 | 
			
		||||
import { Subject } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
export enum FilterableDropdownType {
 | 
			
		||||
  Filtering = 'filtering',
 | 
			
		||||
  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({
 | 
			
		||||
  selector: 'app-filterable-dropdown',
 | 
			
		||||
  templateUrl: './filterable-dropdown.component.html',
 | 
			
		||||
@@ -24,33 +67,45 @@ export class FilterableDropdownComponent {
 | 
			
		||||
  @Input()
 | 
			
		||||
  set items(items: MatchingModel[]) {
 | 
			
		||||
    if (items) {
 | 
			
		||||
      this._toggleableItems = items.map(i => {
 | 
			
		||||
      this._selectionModel.items = items.map(i => {
 | 
			
		||||
        return {item: i, state: ToggleableItemState.NotSelected, count: i.document_count}
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _toggleableItems: ToggleableItem[] = []
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  set toggleableItems (toggleableItems: ToggleableItem[]) {
 | 
			
		||||
    if (this.type == FilterableDropdownType.Editing && this.dropdown?.isOpen()) return
 | 
			
		||||
    else this._toggleableItems = toggleableItems
 | 
			
		||||
  get items(): MatchingModel[] {
 | 
			
		||||
    return this._selectionModel.items.map(i => i.item)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get toggleableItems(): ToggleableItem[] {
 | 
			
		||||
    return this._toggleableItems
 | 
			
		||||
  }
 | 
			
		||||
  _selectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  set itemsSelected(itemsSelected: MatchingModel[]) {
 | 
			
		||||
    this.toggleableItems.forEach(i => {
 | 
			
		||||
      i.state = (itemsSelected.find(is => is.id == i.item.id)) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected
 | 
			
		||||
  set selectionModel(model: FilterableDropdownSelectionModel) {
 | 
			
		||||
    if (this.selectionModel) {
 | 
			
		||||
      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[] {
 | 
			
		||||
    return this.toggleableItems.filter(ti => ti.state == ToggleableItemState.Selected).map(ti => ti.item)
 | 
			
		||||
  get selectionModel(): FilterableDropdownSelectionModel {
 | 
			
		||||
    return this._selectionModel
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>()
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  set multiple(value: boolean) {
 | 
			
		||||
    this.selectionModel.multiple = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get multiple() {
 | 
			
		||||
    return this.selectionModel.multiple
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
@@ -64,50 +119,40 @@ export class FilterableDropdownComponent {
 | 
			
		||||
 | 
			
		||||
  types = FilterableDropdownType
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  singular: boolean = false
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  toggle = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  open = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  editingComplete = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  hasBeenToggled:boolean = false
 | 
			
		||||
 | 
			
		||||
  constructor(private filterPipe: FilterPipe) { }
 | 
			
		||||
  constructor(private filterPipe: FilterPipe) {
 | 
			
		||||
    this.selectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleItem(toggleableItem: ToggleableItem): void {
 | 
			
		||||
    if (this.singular && toggleableItem.state == ToggleableItemState.Selected) {
 | 
			
		||||
      this._toggleableItems.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected)
 | 
			
		||||
    }
 | 
			
		||||
    this.hasBeenToggled = true
 | 
			
		||||
    this.toggle.emit(toggleableItem.item)
 | 
			
		||||
    // if (this.singular && toggleableItem.state == ToggleableItemState.Selected) {
 | 
			
		||||
    //   this.selectionModel.items.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected)
 | 
			
		||||
    // }
 | 
			
		||||
    // this.hasBeenToggled = true
 | 
			
		||||
    // this.toggle.emit(toggleableItem.item)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dropdownOpenChange(open: boolean): void {
 | 
			
		||||
    if (open) {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        this.listFilterTextInput.nativeElement.focus();
 | 
			
		||||
      }, 0)
 | 
			
		||||
      this.hasBeenToggled = false
 | 
			
		||||
      this.open.next()
 | 
			
		||||
    } else {
 | 
			
		||||
      this.filterText = ''
 | 
			
		||||
      if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems)
 | 
			
		||||
    }
 | 
			
		||||
    // if (open) {
 | 
			
		||||
    //   setTimeout(() => {
 | 
			
		||||
    //     this.listFilterTextInput.nativeElement.focus();
 | 
			
		||||
    //   }, 0)
 | 
			
		||||
    //   this.hasBeenToggled = false
 | 
			
		||||
    //   this.open.next()
 | 
			
		||||
    // } else {
 | 
			
		||||
    //   this.filterText = ''
 | 
			
		||||
    //   if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems)
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  listFilterEnter(): void {
 | 
			
		||||
    let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText)
 | 
			
		||||
    if (filtered.length == 1) {
 | 
			
		||||
      let toggleableItem = this.toggleableItems.find(ti => ti.item.id == filtered[0].item.id)
 | 
			
		||||
      if (toggleableItem) toggleableItem.state = ToggleableItemState.Selected
 | 
			
		||||
      this.toggleItem(filtered[0])
 | 
			
		||||
      this.dropdown.close()
 | 
			
		||||
    }
 | 
			
		||||
    // let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText)
 | 
			
		||||
    // if (filtered.length == 1) {
 | 
			
		||||
    //   let toggleableItem = this.toggleableItems.find(ti => ti.item.id == filtered[0].item.id)
 | 
			
		||||
    //   if (toggleableItem) toggleableItem.state = ToggleableItemState.Selected
 | 
			
		||||
    //   this.toggleItem(filtered[0])
 | 
			
		||||
    //   this.dropdown.close()
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,8 +31,7 @@ export class ToggleableDropdownButtonComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleItem(): void {
 | 
			
		||||
    this.toggleableItem.state = (this.toggleableItem.state == ToggleableItemState.NotSelected || this.toggleableItem.state == ToggleableItemState.PartiallySelected) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected
 | 
			
		||||
    this.toggle.emit(this.toggleableItem)
 | 
			
		||||
    this.toggle.emit()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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">
 | 
			
		||||
    <button class="btn btn-sm btn-outline-danger" (click)="documentList.selectNone()">
 | 
			
		||||
      <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
@@ -58,4 +58,4 @@
 | 
			
		||||
      Delete
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
</div> -->
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@
 | 
			
		||||
  <div class="w-100 d-xl-none"></div>
 | 
			
		||||
   <div class="col col-xl-auto mb-2 mb-xl-0">
 | 
			
		||||
     <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]="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]="documentTypes" [itemsSelected]="selectedDocumentTypes" title="Document types" icon="file-earmark-fill" (toggle)="toggleDocumentType($event.id)"></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 [dateBefore]="dateAddedBefore" [dateAfter]="dateAddedAfter" title="Added"  (datesSet)="onDatesAddedSet($event)"></app-date-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" [(selectionModel)]="correspondentSelectionModel" (selectionModelChange)="updateRules()" title="Correspondents" icon="person-fill"></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)="updateRules()"></app-date-dropdown>
 | 
			
		||||
       <app-date-dropdown [(dateBefore)]="dateAddedBefore" [(dateAfter)]="dateAddedAfter" title="Added"  (datesSet)="updateRules()"></app-date-dropdown>
 | 
			
		||||
     </div>
 | 
			
		||||
   </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 { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
 | 
			
		||||
import { Subject, Subscription } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
 | 
			
		||||
import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, filter, flatMap, mergeMap } from 'rxjs/operators';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
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 { DateSelection } from 'src/app/components/common/date-dropdown/date-dropdown.component';
 | 
			
		||||
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-filter-editor',
 | 
			
		||||
@@ -46,37 +45,91 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  tags: PaperlessTag[] = []
 | 
			
		||||
  correspondents: PaperlessCorrespondent[]
 | 
			
		||||
  correspondents: PaperlessCorrespondent[] = []
 | 
			
		||||
  documentTypes: PaperlessDocumentType[] = []
 | 
			
		||||
 | 
			
		||||
  _titleFilter = ""
 | 
			
		||||
 | 
			
		||||
  tagSelectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
  correspondentSelectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
  documentTypeSelectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
 | 
			
		||||
  dateCreatedBefore: string
 | 
			
		||||
  dateCreatedAfter: string
 | 
			
		||||
  dateAddedBefore: string
 | 
			
		||||
  dateAddedAfter: string
 | 
			
		||||
 | 
			
		||||
  @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()
 | 
			
		||||
  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() {
 | 
			
		||||
    return this.filterRules.length > 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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))
 | 
			
		||||
    return this._titleFilter || 
 | 
			
		||||
      this.dateCreatedAfter || this.dateAddedBefore || this.dateCreatedAfter || this.dateCreatedBefore ||
 | 
			
		||||
      this.tagSelectionModel.selectionSize() || this.correspondentSelectionModel.selectionSize() || this.documentTypeSelectionModel.selectionSize()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get titleFilter() {
 | 
			
		||||
    let existingRule = this.filterRules.find(rule => rule.rule_type == FILTER_TITLE)
 | 
			
		||||
    return existingRule ? existingRule.value : ''
 | 
			
		||||
    return this._titleFilter
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set titleFilter(value) {
 | 
			
		||||
@@ -97,142 +150,27 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
      debounceTime(400),
 | 
			
		||||
      distinctUntilChanged()
 | 
			
		||||
    ).subscribe(title => {
 | 
			
		||||
      this.setTitleRule(title)
 | 
			
		||||
      this._titleFilter = title
 | 
			
		||||
      this.updateRules()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    this.titleFilterDebounce.complete()
 | 
			
		||||
    // TODO: not sure if both is necessary
 | 
			
		||||
    this.subscription.unsubscribe()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyFilters() {
 | 
			
		||||
    this.filterRulesChange.next(this.filterRules)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearSelected() {
 | 
			
		||||
    this.filterRules = []
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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()
 | 
			
		||||
    this._titleFilter = ""
 | 
			
		||||
    this.updateRules()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleTag(tagId: number) {
 | 
			
		||||
    this.toggleFilterRule(FILTER_HAS_TAG, tagId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleCorrespondent(correspondentId: number) {
 | 
			
		||||
    this.toggleFilterRule(FILTER_CORRESPONDENT, correspondentId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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