mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Bulk editor component skeleton
This commit is contained in:
		| @@ -27,6 +27,9 @@ export class FilterableDropdownComponent { | |||||||
|   @Output() |   @Output() | ||||||
|   toggle = new EventEmitter() |   toggle = new EventEmitter() | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   close = new EventEmitter() | ||||||
|  |  | ||||||
|   @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef |   @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef | ||||||
|   @ViewChild('dropdown') dropdown: NgbDropdown |   @ViewChild('dropdown') dropdown: NgbDropdown | ||||||
|  |  | ||||||
| @@ -47,6 +50,7 @@ export class FilterableDropdownComponent { | |||||||
|       }, 0); |       }, 0); | ||||||
|     } else { |     } else { | ||||||
|       this.filterText = '' |       this.filterText = '' | ||||||
|  |       this.close.emit(this.itemsSelected) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,89 +1,34 @@ | |||||||
| <div class="card bg-light"> | <div class="row"> | ||||||
| <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="col mb-2 mb-xl-0" role="group" aria-label="Select"> | ||||||
|   <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="mr-lg-2">Select:</label> | ||||||
|     <label class="d-flex align-self-center my-0 mr-auto mr-lg-2">Select:</label> |     <div class="btn-group"> | ||||||
|     <div class="btn-group d-flex"> |       <button class="btn btn-sm btn-outline-primary" (click)="selectPage.next()"> | ||||||
|       <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="selectPage.next()"> |         <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||||
|         <svg viewBox="0 0 16 16" fill="currentColor"> |  | ||||||
|           <use xlink:href="assets/bootstrap-icons.svg#file-earmark-check" /> |           <use xlink:href="assets/bootstrap-icons.svg#file-earmark-check" /> | ||||||
|         </svg> |         </svg> | ||||||
|         <small>Page</small> |         Page | ||||||
|       </button> |       </button> | ||||||
|       <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="selectAll.next()"> |       <button class="btn btn-sm btn-outline-primary" (click)="selectAll.next()"> | ||||||
|         <svg viewBox="0 0 16 16" fill="currentColor"> |         <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||||
|           <use xlink:href="assets/bootstrap-icons.svg#check-all" /> |           <use xlink:href="assets/bootstrap-icons.svg#check-all" /> | ||||||
|         </svg> |         </svg> | ||||||
|         <small>All</small> |         All | ||||||
|       </button> |       </button> | ||||||
|       <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="selectNone.next()"> |       <button class="btn btn-sm btn-outline-primary" (click)="selectNone.next()"> | ||||||
|         <svg viewBox="0 0 16 16" fill="currentColor"> |         <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||||
|           <use xlink:href="assets/bootstrap-icons.svg#slash-circle" /> |           <use xlink:href="assets/bootstrap-icons.svg#slash-circle" /> | ||||||
|         </svg> |         </svg> | ||||||
|         <small>None</small> |         None | ||||||
|       </button> |       </button> | ||||||
|     </div> |     </div> | ||||||
|   </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"> |   <div class="w-100 d-xl-none"></div> | ||||||
|     <label class="d-flex align-self-center my-0 mr-auto mr-lg-2">Tags:</label> |   <div class="col mb-2 mb-xl-0"> | ||||||
|     <div class="btn-group d-flex"> |     <div class="d-flex"> | ||||||
|       <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="addTag.next()"> |       <label class="ml-auto mt-1 mr-2">Apply:</label> | ||||||
|         <ng-container *ngTemplateOutlet="add"></ng-container> |       <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> | ||||||
|       </button> |       <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> | ||||||
|       <button class="btn btn-sm btn-outline-primary py-1 px-2" (click)="removeTag.next()"> |       <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> | ||||||
|         <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="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> |   </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 { 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({ | @Component({ | ||||||
|   selector: 'app-bulk-editor', |   selector: 'app-bulk-editor', | ||||||
| @@ -9,7 +16,10 @@ import { DocumentListViewService } from 'src/app/services/document-list-view.ser | |||||||
| export class BulkEditorComponent { | export class BulkEditorComponent { | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   list: DocumentListViewService |   documentsSelected: Set<number> | ||||||
|  |  | ||||||
|  |   @Input() | ||||||
|  |   allDocuments: PaperlessDocument[] | ||||||
|  |  | ||||||
|   @Output() |   @Output() | ||||||
|   selectPage = new EventEmitter() |   selectPage = new EventEmitter() | ||||||
| @@ -41,6 +51,65 @@ export class BulkEditorComponent { | |||||||
|   @Output() |   @Output() | ||||||
|   delete = new EventEmitter() |   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> | </app-page-header> | ||||||
|  |  | ||||||
| <div class="w-100 mb-2 mb-sm-4"> | <div class="w-100 mb-2 mb-sm-4"> | ||||||
|   <app-filter-editor [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor> |   <app-filter-editor *ngIf="!isBulkEditing" [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor> | ||||||
| </div> |  | ||||||
|  |  | ||||||
| <div class="d-flex justify-content-between align-items-center"> |   <app-bulk-editor *ngIf="isBulkEditing" | ||||||
|   <p><span *ngIf="list.selected.size > 0">Selected {{list.selected.size}} of </span>{{list.collectionSize || 0}} document(s) <span *ngIf="isFiltered">(filtered)</span></p> |     [allDocuments]="list.documents" | ||||||
|   <ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5" |     [(documentsSelected)]="list.selected" | ||||||
|   [rotate]="true" (pageChange)="list.reload()" aria-label="Default pagination"></ngb-pagination> |  | ||||||
| </div> |  | ||||||
|  |  | ||||||
| <div class="w-100 mb-3" [ngbCollapse]="!isBulkEditing"> |  | ||||||
|   <app-bulk-editor |  | ||||||
|     (selectPage)="list.selectPage()" |     (selectPage)="list.selectPage()" | ||||||
|     (selectAll)="list.selectAll()" |     (selectAll)="list.selectAll()" | ||||||
|     (selectNone)="list.selectNone()" |     (selectNone)="list.selectNone()" | ||||||
| @@ -102,6 +96,12 @@ | |||||||
| </app-bulk-editor> | </app-bulk-editor> | ||||||
| </div> | </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'"> | <div *ngIf="displayMode == 'largeCards'"> | ||||||
|   <app-document-card-large *ngFor="let d of list.documents" [document]="d" [details]="d.content" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"> |   <app-document-card-large *ngFor="let d of list.documents" [document]="d" [details]="d.content" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"> | ||||||
|   </app-document-card-large> |   </app-document-card-large> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon