mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Bulk editor component skeleton
This commit is contained in:
		@@ -27,6 +27,9 @@ export class FilterableDropdownComponent {
 | 
			
		||||
  @Output()
 | 
			
		||||
  toggle = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  close = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
 | 
			
		||||
  @ViewChild('dropdown') dropdown: NgbDropdown
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +50,7 @@ export class FilterableDropdownComponent {
 | 
			
		||||
      }, 0);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.filterText = ''
 | 
			
		||||
      this.close.emit(this.itemsSelected)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,89 +1,34 @@
 | 
			
		||||
<div class="card bg-light">
 | 
			
		||||
<div class="card-body small px-2 py-2 d-flex flex-column flex-xl-row justify-content-between justify-content-xl-start">
 | 
			
		||||
  <div class="d-flex flex-grow-1 flex-xl-grow-0 mb-2 mb-xl-0 mr-xl-5" role="group" aria-label="Select">
 | 
			
		||||
    <label class="d-flex align-self-center my-0 mr-auto mr-lg-2">Select:</label>
 | 
			
		||||
    <div class="btn-group d-flex">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="selectPage.next()">
 | 
			
		||||
        <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
<div class="row">
 | 
			
		||||
  <div class="col mb-2 mb-xl-0" role="group" aria-label="Select">
 | 
			
		||||
    <label class="mr-lg-2">Select:</label>
 | 
			
		||||
    <div class="btn-group">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary" (click)="selectPage.next()">
 | 
			
		||||
        <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
          <use xlink:href="assets/bootstrap-icons.svg#file-earmark-check" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        <small>Page</small>
 | 
			
		||||
        Page
 | 
			
		||||
      </button>
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="selectAll.next()">
 | 
			
		||||
        <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary" (click)="selectAll.next()">
 | 
			
		||||
        <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
          <use xlink:href="assets/bootstrap-icons.svg#check-all" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        <small>All</small>
 | 
			
		||||
        All
 | 
			
		||||
      </button>
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="selectNone.next()">
 | 
			
		||||
        <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary" (click)="selectNone.next()">
 | 
			
		||||
        <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
          <use xlink:href="assets/bootstrap-icons.svg#slash-circle" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        <small>None</small>
 | 
			
		||||
        None
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="d-flex flex-grow-1 flex-xl-grow-0 mb-2 mb-xl-0 mr-xl-5" role="group" aria-label="Tags">
 | 
			
		||||
    <label class="d-flex align-self-center my-0 mr-auto mr-lg-2">Tags:</label>
 | 
			
		||||
    <div class="btn-group d-flex">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="addTag.next()">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="add"></ng-container>
 | 
			
		||||
      </button>
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="removeTag.next()">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="remove"></ng-container>
 | 
			
		||||
      </button>
 | 
			
		||||
  <div class="w-100 d-xl-none"></div>
 | 
			
		||||
  <div class="col mb-2 mb-xl-0">
 | 
			
		||||
    <div class="d-flex">
 | 
			
		||||
      <label class="ml-auto mt-1 mr-2">Apply:</label>
 | 
			
		||||
      <app-filterable-dropdown class="mr-2 mr-md-3" title="Apply Tags" icon="tag-fill" [items]="tags" [itemsSelected]="selectedTags" (close)="applyTags($event)"></app-filterable-dropdown>
 | 
			
		||||
      <app-filterable-dropdown class="mr-2 mr-md-3" title="Set Correspondent" icon="person-fill" [items]="correspondents" [itemsSelected]="selectedCorrespondents" (close)="applyCorrespondent($event)"></app-filterable-dropdown>
 | 
			
		||||
      <app-filterable-dropdown class="mr-2 mr-md-3" title="Set Document Type" icon="file-earmark-fill" [items]="documentTypes" [itemsSelected]="selectedDocumentTypes" (close)="applyDocumentType($event)"></app-filterable-dropdown>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="d-flex flex-grow-1 flex-xl-grow-0 mb-2 mb-xl-0 mr-xl-5" role="group" aria-label="Correspondent">
 | 
			
		||||
    <label class="d-flex align-self-center my-0 mr-auto mr-lg-2">Correspondent:</label>
 | 
			
		||||
    <div class="btn-group d-flex">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="setCorrespondent.next()">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="edit"></ng-container>
 | 
			
		||||
      </button>
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="removeCorrespondent.next()">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="remove"></ng-container>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="d-flex flex-grow-1 flex-xl-grow-0 mb-2 mb-xl-0 mr-xl-5" role="group" aria-label="Document Type">
 | 
			
		||||
    <label class="d-flex align-self-center my-0 mr-auto mr-lg-2">Document Type:</label>
 | 
			
		||||
    <div class="btn-group d-flex">
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="setDocumentType.next()">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="edit"></ng-container>
 | 
			
		||||
      </button>
 | 
			
		||||
      <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="removeDocumentType.next()">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="remove"></ng-container>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="d-flex flex-grow-1 flex-xl-grow-0 mb-2 mb-lg-0 ml-auto ml-lg-0 ml-xl-auto" role="group" aria-label="Delete">
 | 
			
		||||
    <button class="btn btn-sm btn-outline-danger" (click)="delete.next()">
 | 
			
		||||
      <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
        <use xlink:href="assets/bootstrap-icons.svg#trash" />
 | 
			
		||||
      </svg>
 | 
			
		||||
      <small>Delete</small>
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #add>
 | 
			
		||||
  <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
    <use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
 | 
			
		||||
  </svg>
 | 
			
		||||
  <small>Add</small>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #edit>
 | 
			
		||||
  <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
    <use xlink:href="assets/bootstrap-icons.svg#pencil" />
 | 
			
		||||
  </svg>
 | 
			
		||||
  <small>Edit</small>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #remove>
 | 
			
		||||
  <svg viewBox="0 0 16 16" fill="currentColor">
 | 
			
		||||
    <use xlink:href="assets/bootstrap-icons.svg#x-circle" />
 | 
			
		||||
  </svg>
 | 
			
		||||
  <small>Remove</small>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
.btn svg {
 | 
			
		||||
  width: 0.9em;
 | 
			
		||||
  height: 0.9em;
 | 
			
		||||
  margin-right: 3px;
 | 
			
		||||
  margin-top: -1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-sm {
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,12 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
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 { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bulk-editor',
 | 
			
		||||
@@ -9,7 +16,10 @@ import { DocumentListViewService } from 'src/app/services/document-list-view.ser
 | 
			
		||||
export class BulkEditorComponent {
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  list: DocumentListViewService
 | 
			
		||||
  documentsSelected: Set<number>
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  allDocuments: PaperlessDocument[]
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  selectPage = new EventEmitter()
 | 
			
		||||
@@ -41,6 +51,65 @@ export class BulkEditorComponent {
 | 
			
		||||
  @Output()
 | 
			
		||||
  delete = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  constructor( ) { }
 | 
			
		||||
  tags: PaperlessTag[]
 | 
			
		||||
  correspondents: PaperlessCorrespondent[]
 | 
			
		||||
  documentTypes: PaperlessDocumentType[]
 | 
			
		||||
 | 
			
		||||
  get selectedTags(): PaperlessTag[] {
 | 
			
		||||
    let selectedTags = []
 | 
			
		||||
    this.allDocuments.forEach(d => {
 | 
			
		||||
      if (this.documentsSelected.has(d.id)) {
 | 
			
		||||
        if (d.tags && !d.tags.every(t => selectedTags.find(st => st.id == t) !== undefined)) d.tags$.subscribe(t => selectedTags = selectedTags.concat(t))
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    return selectedTags
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get selectedCorrespondents(): PaperlessCorrespondent[]  {
 | 
			
		||||
    let selectedCorrespondents = []
 | 
			
		||||
    this.allDocuments.forEach(d => {
 | 
			
		||||
      if (this.documentsSelected.has(d.id)) {
 | 
			
		||||
        if (d.correspondent && selectedCorrespondents.find(sc => sc.id == d.correspondent) == undefined) d.correspondent$.subscribe(c => selectedCorrespondents.push(c))
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    return selectedCorrespondents
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get selectedDocumentTypes(): PaperlessDocumentType[] {
 | 
			
		||||
    let selectedDocumentTypes = []
 | 
			
		||||
    this.allDocuments.forEach(d => {
 | 
			
		||||
      if (this.documentsSelected.has(d.id)) {
 | 
			
		||||
        if (d.document_type && selectedDocumentTypes.find(sdt => sdt.id == d.document_type) == undefined) d.document_type$.subscribe(dt => selectedDocumentTypes.push(dt))
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    return selectedDocumentTypes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private documentTypeService: DocumentTypeService,
 | 
			
		||||
    private tagService: TagService,
 | 
			
		||||
    private correspondentService: CorrespondentService,
 | 
			
		||||
    private documentService: DocumentService
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.tagService.listAll().subscribe(result => this.tags = result.results)
 | 
			
		||||
    this.correspondentService.listAll().subscribe(result => this.correspondents = result.results)
 | 
			
		||||
    this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyTags(tags) {
 | 
			
		||||
    console.log(tags);
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyCorrespondent(correspondent) {
 | 
			
		||||
    console.log(correspondent);
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyDocumentType(documentType) {
 | 
			
		||||
    console.log(documentType);
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,17 +78,11 @@
 | 
			
		||||
</app-page-header>
 | 
			
		||||
 | 
			
		||||
<div class="w-100 mb-2 mb-sm-4">
 | 
			
		||||
  <app-filter-editor [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor>
 | 
			
		||||
</div>
 | 
			
		||||
  <app-filter-editor *ngIf="!isBulkEditing" [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor>
 | 
			
		||||
 | 
			
		||||
<div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
  <p><span *ngIf="list.selected.size > 0">Selected {{list.selected.size}} of </span>{{list.collectionSize || 0}} document(s) <span *ngIf="isFiltered">(filtered)</span></p>
 | 
			
		||||
  <ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
 | 
			
		||||
  [rotate]="true" (pageChange)="list.reload()" aria-label="Default pagination"></ngb-pagination>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="w-100 mb-3" [ngbCollapse]="!isBulkEditing">
 | 
			
		||||
  <app-bulk-editor
 | 
			
		||||
  <app-bulk-editor *ngIf="isBulkEditing"
 | 
			
		||||
    [allDocuments]="list.documents"
 | 
			
		||||
    [(documentsSelected)]="list.selected"
 | 
			
		||||
    (selectPage)="list.selectPage()"
 | 
			
		||||
    (selectAll)="list.selectAll()"
 | 
			
		||||
    (selectNone)="list.selectNone()"
 | 
			
		||||
@@ -99,7 +93,13 @@
 | 
			
		||||
    (addTag)="bulkAddTag()"
 | 
			
		||||
    (removeTag)="bulkRemoveTag()"
 | 
			
		||||
    (delete)="bulkDelete()">
 | 
			
		||||
  </app-bulk-editor>
 | 
			
		||||
</app-bulk-editor>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
  <p><span *ngIf="list.selected.size > 0">Selected {{list.selected.size}} of </span>{{list.collectionSize || 0}} document(s) <span *ngIf="isFiltered">(filtered)</span></p>
 | 
			
		||||
  <ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
 | 
			
		||||
  [rotate]="true" (pageChange)="list.reload()" aria-label="Default pagination"></ngb-pagination>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div *ngIf="displayMode == 'largeCards'">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user