mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	selection model
This commit is contained in:
		@@ -20,8 +20,8 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div *ngIf="selectionModel.items" class="items">
 | 
					      <div *ngIf="selectionModel.items" class="items">
 | 
				
			||||||
        <ng-container *ngFor="let toggleableItem of selectionModel.items | filter: filterText">
 | 
					        <ng-container *ngFor="let item of selectionModel.items | filter: filterText">
 | 
				
			||||||
          <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="selectionModel.toggle(toggleableItem.item)"></app-toggleable-dropdown-button>
 | 
					          <app-toggleable-dropdown-button [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)"></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">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ 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';
 | 
					import { Subject } from 'rxjs';
 | 
				
			||||||
 | 
					import { ThrowStmt } from '@angular/compiler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum FilterableDropdownType {
 | 
					export enum FilterableDropdownType {
 | 
				
			||||||
  Filtering = 'filtering',
 | 
					  Filtering = 'filtering',
 | 
				
			||||||
@@ -16,39 +17,56 @@ export class FilterableDropdownSelectionModel {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  multiple = false
 | 
					  multiple = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  items: ToggleableItem[] = []
 | 
					  items: MatchingModel[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getSelected() {
 | 
					  selection = new Map<number, ToggleableItemState>()
 | 
				
			||||||
    return this.items.filter(i => i.state == ToggleableItemState.Selected).map(i => i.item)
 | 
					
 | 
				
			||||||
 | 
					  getSelectedItems() {
 | 
				
			||||||
 | 
					    return this.items.filter(i => this.selection.get(i.id) == ToggleableItemState.Selected)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set(id: number, state: ToggleableItemState, fireEvent = true) {
 | 
				
			||||||
 | 
					    this.selection.set(id, state)
 | 
				
			||||||
  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) {
 | 
					    if (fireEvent) {
 | 
				
			||||||
      this.changed.next(this)
 | 
					      this.changed.next(this)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toggle(id: number, fireEvent = true) {
 | 
				
			||||||
 | 
					    let state = this.selection.get(id)
 | 
				
			||||||
 | 
					    if (state == null || state != ToggleableItemState.Selected) {
 | 
				
			||||||
 | 
					      this.selection.set(id, ToggleableItemState.Selected)
 | 
				
			||||||
 | 
					    } else if (state == ToggleableItemState.Selected) {
 | 
				
			||||||
 | 
					      this.selection.set(id, ToggleableItemState.NotSelected)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this.multiple) {
 | 
				
			||||||
 | 
					      for (let key of this.selection.keys()) {
 | 
				
			||||||
 | 
					        if (key != id) {
 | 
				
			||||||
 | 
					          this.selection.set(key, ToggleableItemState.NotSelected)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (fireEvent) {
 | 
				
			||||||
 | 
					      this.changed.next(this)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get(id: number) {
 | 
				
			||||||
 | 
					    return this.selection.get(id) || ToggleableItemState.NotSelected
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  selectionSize() {
 | 
					  selectionSize() {
 | 
				
			||||||
    return this.getSelected().length
 | 
					    return this.getSelectedItems().length
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  clear(fireEvent = true) {
 | 
				
			||||||
 | 
					    this.selection.clear()
 | 
				
			||||||
 | 
					    if (fireEvent) {
 | 
				
			||||||
 | 
					      this.changed.next(this)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,14 +85,12 @@ export class FilterableDropdownComponent {
 | 
				
			|||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  set items(items: MatchingModel[]) {
 | 
					  set items(items: MatchingModel[]) {
 | 
				
			||||||
    if (items) {
 | 
					    if (items) {
 | 
				
			||||||
      this._selectionModel.items = items.map(i => {
 | 
					      this._selectionModel.items = items
 | 
				
			||||||
        return {item: i, state: ToggleableItemState.NotSelected, count: i.document_count}
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get items(): MatchingModel[] {
 | 
					  get items(): MatchingModel[] {
 | 
				
			||||||
    return this._selectionModel.items.map(i => i.item)
 | 
					    return this._selectionModel.items
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _selectionModel = new FilterableDropdownSelectionModel()
 | 
					  _selectionModel = new FilterableDropdownSelectionModel()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,8 @@
 | 
				
			|||||||
    </svg>
 | 
					    </svg>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="mr-1">
 | 
					  <div class="mr-1">
 | 
				
			||||||
    <app-tag *ngIf="isTag; else displayName" [tag]="toggleableItem?.item" [clickable]="true" linkTitle="Filter by tag"></app-tag>
 | 
					    <app-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag>
 | 
				
			||||||
    <ng-template #displayName><small>{{toggleableItem?.item.name}}</small></ng-template>
 | 
					    <ng-template #displayName><small>{{item.name}}</small></ng-template>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="badge badge-light rounded-pill ml-auto mr-1">{{toggleableItem?.count}}</div>
 | 
					  <div class="badge badge-light rounded-pill ml-auto mr-1">{{item.document_count}}</div>
 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,13 +21,19 @@ export enum ToggleableItemState {
 | 
				
			|||||||
export class ToggleableDropdownButtonComponent {
 | 
					export class ToggleableDropdownButtonComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  toggleableItem: ToggleableItem
 | 
					  item: MatchingModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input()
 | 
				
			||||||
 | 
					  state: ToggleableItemState
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input()
 | 
				
			||||||
 | 
					  count: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Output()
 | 
					  @Output()
 | 
				
			||||||
  toggle = new EventEmitter()
 | 
					  toggle = new EventEmitter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get isTag(): boolean {
 | 
					  get isTag(): boolean {
 | 
				
			||||||
    return 'is_inbox_tag' in this.toggleableItem?.item // ~ this.item instanceof PaperlessTag
 | 
					    return 'is_inbox_tag' in this.item
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleItem(): void {
 | 
					  toggleItem(): void {
 | 
				
			||||||
@@ -35,9 +41,9 @@ export class ToggleableDropdownButtonComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getSelectedIconName() {
 | 
					  getSelectedIconName() {
 | 
				
			||||||
    if (this.toggleableItem?.state == ToggleableItemState.Selected) {
 | 
					    if (this.state == ToggleableItemState.Selected) {
 | 
				
			||||||
      return "check"
 | 
					      return "check"
 | 
				
			||||||
    } else if (this.toggleableItem?.state == ToggleableItemState.PartiallySelected) {
 | 
					    } else if (this.state == ToggleableItemState.PartiallySelected) {
 | 
				
			||||||
      return "dash"
 | 
					      return "dash"
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return ""
 | 
					      return ""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,13 +3,14 @@ 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, filter, flatMap, mergeMap } from 'rxjs/operators';
 | 
					import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
 | 
				
			||||||
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_TITLE } from 'src/app/data/filter-rule-type';
 | 
				
			||||||
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
 | 
					import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
 | 
				
			||||||
 | 
					import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-filter-editor',
 | 
					  selector: 'app-filter-editor',
 | 
				
			||||||
@@ -61,7 +62,6 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  set filterRules (value: FilterRule[]) {
 | 
					  set filterRules (value: FilterRule[]) {
 | 
				
			||||||
    console.log("SET FILTER RULES")
 | 
					 | 
				
			||||||
    value.forEach(rule => {
 | 
					    value.forEach(rule => {
 | 
				
			||||||
      switch (rule.rule_type) {
 | 
					      switch (rule.rule_type) {
 | 
				
			||||||
        case FILTER_TITLE:
 | 
					        case FILTER_TITLE:
 | 
				
			||||||
@@ -79,31 +79,34 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        case FILTER_ADDED_BEFORE:
 | 
					        case FILTER_ADDED_BEFORE:
 | 
				
			||||||
          this.dateAddedBefore = rule.value
 | 
					          this.dateAddedBefore = rule.value
 | 
				
			||||||
          break
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_HAS_TAG:
 | 
				
			||||||
 | 
					          this.tagSelectionModel.set(+rule.value, ToggleableItemState.Selected, false)
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_CORRESPONDENT:
 | 
				
			||||||
 | 
					          this.correspondentSelectionModel.set(+rule.value, ToggleableItemState.Selected, false)
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_DOCUMENT_TYPE:
 | 
				
			||||||
 | 
					          this.documentTypeSelectionModel.set(+rule.value, ToggleableItemState.Selected, false)
 | 
				
			||||||
 | 
					          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() {
 | 
					  updateRules() {
 | 
				
			||||||
    console.log("UPDATE RULES!!!")
 | 
					 | 
				
			||||||
    let filterRules: FilterRule[] = []
 | 
					    let filterRules: FilterRule[] = []
 | 
				
			||||||
    if (this._titleFilter) {
 | 
					    if (this._titleFilter) {
 | 
				
			||||||
      filterRules.push({rule_type: FILTER_TITLE, value: this._titleFilter})
 | 
					      filterRules.push({rule_type: FILTER_TITLE, value: this._titleFilter})
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.tagSelectionModel.getSelected().forEach(tag => {
 | 
					    this.tagSelectionModel.getSelectedItems().forEach(tag => {
 | 
				
			||||||
      filterRules.push({rule_type: FILTER_HAS_TAG, value: tag.id.toString()})
 | 
					      filterRules.push({rule_type: FILTER_HAS_TAG, value: tag.id.toString()})
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    this.correspondentSelectionModel.getSelected().forEach(correspondent => {
 | 
					    this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => {
 | 
				
			||||||
      filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id.toString()})
 | 
					      filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id.toString()})
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    this.documentTypeSelectionModel.getSelected().forEach(documentType => {
 | 
					    this.documentTypeSelectionModel.getSelectedItems().forEach(documentType => {
 | 
				
			||||||
      filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id.toString()})
 | 
					      filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id.toString()})
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    if (this.dateCreatedBefore) {
 | 
					    if (this.dateCreatedBefore) {
 | 
				
			||||||
@@ -118,13 +121,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    if (this.dateAddedAfter) {
 | 
					    if (this.dateAddedAfter) {
 | 
				
			||||||
      filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter})
 | 
					      filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter})
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    console.log(filterRules)
 | 
					 | 
				
			||||||
    this.filterRulesChange.next(filterRules)
 | 
					    this.filterRulesChange.next(filterRules)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  hasFilters() {
 | 
					  hasFilters() {
 | 
				
			||||||
    return this._titleFilter || 
 | 
					    return this._titleFilter || 
 | 
				
			||||||
      this.dateCreatedAfter || this.dateAddedBefore || this.dateCreatedAfter || this.dateCreatedBefore ||
 | 
					      this.dateAddedAfter || this.dateAddedBefore || this.dateCreatedAfter || this.dateCreatedBefore ||
 | 
				
			||||||
      this.tagSelectionModel.selectionSize() || this.correspondentSelectionModel.selectionSize() || this.documentTypeSelectionModel.selectionSize()
 | 
					      this.tagSelectionModel.selectionSize() || this.correspondentSelectionModel.selectionSize() || this.documentTypeSelectionModel.selectionSize()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -161,16 +163,26 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  clearSelected() {
 | 
					  clearSelected() {
 | 
				
			||||||
    this._titleFilter = ""
 | 
					    this._titleFilter = ""
 | 
				
			||||||
 | 
					    this.tagSelectionModel.clear(false)
 | 
				
			||||||
 | 
					    this.documentTypeSelectionModel.clear(false)
 | 
				
			||||||
 | 
					    this.correspondentSelectionModel.clear(false)
 | 
				
			||||||
 | 
					    this.dateAddedBefore = null
 | 
				
			||||||
 | 
					    this.dateAddedAfter = null
 | 
				
			||||||
 | 
					    this.dateCreatedBefore = null
 | 
				
			||||||
 | 
					    this.dateCreatedAfter = null
 | 
				
			||||||
    this.updateRules()
 | 
					    this.updateRules()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleTag(tagId: number) {
 | 
					  toggleTag(tagId: number) {
 | 
				
			||||||
 | 
					    this.tagSelectionModel.toggle(tagId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleCorrespondent(correspondentId: number) {
 | 
					  toggleCorrespondent(correspondentId: number) {
 | 
				
			||||||
 | 
					    this.correspondentSelectionModel.toggle(correspondentId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleDocumentType(documentTypeId: number) {
 | 
					  toggleDocumentType(documentTypeId: number) {
 | 
				
			||||||
 | 
					    this.documentTypeSelectionModel.toggle(documentTypeId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user