diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html index 58c831456..f9bf78b6c 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html @@ -19,6 +19,12 @@ +
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts index 05c77cd10..3f0b3c3ad 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts @@ -69,12 +69,18 @@ export class FilterableDropdownComponent { @Input() singular: boolean = false + @Input() + showRemoveAll: boolean = false + @Output() toggle = new EventEmitter() @Output() open = new EventEmitter() + @Output() + removeAll = new EventEmitter() + @Output() editingComplete = new EventEmitter() @@ -110,7 +116,7 @@ export class FilterableDropdownComponent { this.open.next() } else { this.filterText = '' - if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.itemsSelected) + if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems) } } diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html index 35b6d5197..589739d78 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -29,9 +29,33 @@
- - - + + + + + +
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts index 053af8157..31f7b5566 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -11,6 +11,11 @@ import { DocumentService } from 'src/app/services/rest/document.service'; import { FilterableDropdownType } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component'; import { ToggleableItem, ToggleableItemState } from 'src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; +interface ChangedItems { + itemsToAdd: any[], + itemsToRemove: any[] +} + @Component({ selector: 'app-bulk-editor', templateUrl: './bulk-editor.component.html', @@ -33,6 +38,9 @@ export class BulkEditorComponent { @Output() selectNone = new EventEmitter() + @Output() + setTags = new EventEmitter() + @Output() setCorrespondent = new EventEmitter() @@ -40,18 +48,24 @@ export class BulkEditorComponent { setDocumentType = new EventEmitter() @Output() - setTags = new EventEmitter() + delete = new EventEmitter() @Output() - delete = new EventEmitter() + removeTags = new EventEmitter() + + @Output() + removeCorrespondents = new EventEmitter() + + @Output() + removeDocumentTypes = new EventEmitter() tags: PaperlessTag[] correspondents: PaperlessCorrespondent[] documentTypes: PaperlessDocumentType[] - private initiallySelectedTagsToggleableItems: ToggleableItem[] - private initiallySelectedCorrespondentsToggleableItems: ToggleableItem[] - private initiallySelectedDocumentTypesToggleableItems: ToggleableItem[] + private initialTagsToggleableItems: ToggleableItem[] + private initialCorrespondentsToggleableItems: ToggleableItem[] + private initialDocumentTypesToggleableItems: ToggleableItem[] dropdownTypes = FilterableDropdownType @@ -59,6 +73,7 @@ export class BulkEditorComponent { return this.selectedDocuments.size > this.viewDocuments.length || !Array.from(this.selectedDocuments).every(sd => this.viewDocuments.find(d => d.id == sd)) } + private _tagsToggleableItems: ToggleableItem[] get tagsToggleableItems(): ToggleableItem[] { let tagsToggleableItems = [] let selectedDocuments: PaperlessDocument[] = this.viewDocuments.filter(d => this.selectedDocuments.has(d.id)) @@ -71,9 +86,11 @@ export class BulkEditorComponent { else if (selectedDocumentsWithTag.length > 0 && selectedDocumentsWithTag.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected tagsToggleableItems.push({item: t, state: state, count: selectedDocumentsWithTag.length}) }) + this._tagsToggleableItems = tagsToggleableItems return tagsToggleableItems } + private _correspondentsToggleableItems: ToggleableItem[] get correspondentsToggleableItems(): ToggleableItem[] { let correspondentsToggleableItems = [] let selectedDocuments: PaperlessDocument[] = this.viewDocuments.filter(d => this.selectedDocuments.has(d.id)) @@ -86,9 +103,11 @@ export class BulkEditorComponent { else if (selectedDocumentsWithCorrespondent.length > 0 && selectedDocumentsWithCorrespondent.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected correspondentsToggleableItems.push({item: c, state: state, count: selectedDocumentsWithCorrespondent.length}) }) + this._correspondentsToggleableItems = correspondentsToggleableItems return correspondentsToggleableItems } + private _documentTypesToggleableItems: ToggleableItem[] get documentTypesToggleableItems(): ToggleableItem[] { let documentTypesToggleableItems = [] let selectedDocuments: PaperlessDocument[] = this.viewDocuments.filter(d => this.selectedDocuments.has(d.id)) @@ -101,6 +120,7 @@ export class BulkEditorComponent { else if (selectedDocumentsWithDocumentType.length > 0 && selectedDocumentsWithDocumentType.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected documentTypesToggleableItems.push({item: dt, state: state, count: selectedDocumentsWithDocumentType.length}) }) + this._documentTypesToggleableItems = documentTypesToggleableItems return documentTypesToggleableItems } @@ -118,39 +138,45 @@ export class BulkEditorComponent { } tagsDropdownOpen() { - this.initiallySelectedTagsToggleableItems = this.tagsToggleableItems.filter(tti => tti.state == ToggleableItemState.Selected) + this.initialTagsToggleableItems = this._tagsToggleableItems } correspondentsDropdownOpen() { - this.initiallySelectedCorrespondentsToggleableItems = this.correspondentsToggleableItems.filter(cti => cti.state == ToggleableItemState.Selected) + this.initialCorrespondentsToggleableItems = this._correspondentsToggleableItems } documentTypesDropdownOpen() { - this.initiallySelectedDocumentTypesToggleableItems = this.documentTypesToggleableItems.filter(dtti => dtti.state == ToggleableItemState.Selected) + this.initialDocumentTypesToggleableItems = this._documentTypesToggleableItems } - applyTags(selectedTags: PaperlessTag[]) { - let unchanged = this.equateItemsToToggleableItems(selectedTags, this.initiallySelectedTagsToggleableItems) - if (!unchanged) this.setTags.emit(selectedTags) - this.initiallySelectedTagsToggleableItems = [] + applyTags(newTagsToggleableItems: ToggleableItem[], forceApply:boolean = false) { + let changedTags = this.checkForChangedItems(this.initialTagsToggleableItems, newTagsToggleableItems) + if (changedTags.itemsToAdd.length > 0) this.setTags.emit(changedTags.itemsToAdd) + if (changedTags.itemsToRemove.length > 0) this.removeTags.emit(changedTags.itemsToRemove) } - applyCorrespondent(selectedCorrespondents: PaperlessCorrespondent[]) { - let unchanged = this.equateItemsToToggleableItems(selectedCorrespondents, this.initiallySelectedCorrespondentsToggleableItems) - if (!unchanged) this.setCorrespondent.emit(selectedCorrespondents?.length > 0 ? selectedCorrespondents.shift() : null) - this.initiallySelectedCorrespondentsToggleableItems = [] + applyCorrespondent(newCorrespondentsToggleableItems: ToggleableItem[], forceApply:boolean = false) { + let changedCorrespondents = this.checkForChangedItems(this.initialCorrespondentsToggleableItems, newCorrespondentsToggleableItems) + if (changedCorrespondents.itemsToAdd.length > 0) this.setCorrespondent.emit(changedCorrespondents.itemsToAdd) + else if (changedCorrespondents.itemsToRemove.length > 0) this.removeCorrespondents.emit(changedCorrespondents.itemsToRemove) } - applyDocumentType(selectedDocumentTypes: PaperlessDocumentType[]) { - let unchanged = this.equateItemsToToggleableItems(selectedDocumentTypes, this.initiallySelectedDocumentTypesToggleableItems) - if (!unchanged) this.setDocumentType.emit(selectedDocumentTypes.length > 0 ? selectedDocumentTypes.shift() : null) - this.initiallySelectedDocumentTypesToggleableItems = [] + applyDocumentType(newDocumentTypesToggleableItems: ToggleableItem[], forceApply:boolean = false) { + let changedDocumentTypes = this.checkForChangedItems(this.initialDocumentTypesToggleableItems, newDocumentTypesToggleableItems) + if (changedDocumentTypes.itemsToAdd.length > 0) this.setDocumentType.emit(changedDocumentTypes.itemsToAdd) + else if (changedDocumentTypes.itemsToRemove.length > 0) this.removeDocumentTypes.emit(changedDocumentTypes.itemsToRemove) } - equateItemsToToggleableItems(items: ObjectWithId[], toggleableItems: ToggleableItem[]): boolean { - // either both empty or all items must in toggleableItems and vice-versa - return (toggleableItems.length == 0 && items.length == 0) || - (items.every(i => toggleableItems.find(ti => ti.item.id == i.id) !== undefined) && toggleableItems.every(ti => items.find(i => i.id == ti.item.id) !== undefined)) + checkForChangedItems(toggleableItemsA: ToggleableItem[], toggleableItemsB: ToggleableItem[]): ChangedItems { + let itemsToAdd: any[] = [] + let itemsToRemove: any[] = [] + toggleableItemsA.forEach(oldItem => { + let newItem = toggleableItemsB.find(nTTI => nTTI.item.id == oldItem.item.id) + + if (newItem.state == ToggleableItemState.Selected && (oldItem.state == ToggleableItemState.PartiallySelected || oldItem.state == ToggleableItemState.NotSelected)) itemsToAdd.push(newItem.item) + else if (newItem.state == ToggleableItemState.NotSelected && (oldItem.state == ToggleableItemState.Selected || oldItem.state == ToggleableItemState.PartiallySelected)) itemsToRemove.push(newItem.item) + }) + return { itemsToAdd: itemsToAdd, itemsToRemove: itemsToRemove } } applyDelete() { diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 36c6daa5b..d685e6770 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -87,8 +87,11 @@ (selectAll)="list.selectAll()" (selectNone)="list.selectNone()" (setTags)="bulkSetTags($event)" + (removeTags)="bulkRemoveTags($event)" (setCorrespondent)="bulkSetCorrespondent($event)" + (removeCorrespondents)="bulkRemoveCorrespondents($event)" (setDocumentType)="bulkSetDocumentType($event)" + (removeDocumentTypes)="bulkRemoveDocumentTypes($event)" (delete)="bulkDelete()">
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 2575e4b49..60d763f13 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -158,7 +158,7 @@ export class DocumentListComponent implements OnInit { bulkSetCorrespondent(correspondent: PaperlessCorrespondent) { let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) - modal.componentInstance.title = "Confirm correspondent assignment" + modal.componentInstance.title = "Confirm Correspondent assignment" let messageFragment = correspondent ? `assign the correspondent ${correspondent.name} to` : `remove all correspondents from` modal.componentInstance.message = `This operation will ${messageFragment} all ${this.list.selected.size} selected document(s).` modal.componentInstance.btnClass = "btn-warning" @@ -172,6 +172,22 @@ export class DocumentListComponent implements OnInit { }) } + bulkRemoveCorrespondents(correspondents: PaperlessCorrespondent[]) { + let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Confirm Correspondent Removal" + modal.componentInstance.message = `This operation will remove the correspondent(s) ${correspondents.map(t => t.name).join(', ')} from all ${this.list.selected.size} selected document(s).` + modal.componentInstance.btnClass = "btn-warning" + modal.componentInstance.btnCaption = "Confirm" + modal.componentInstance.confirmClicked.subscribe(() => { + // TODO: API endpoint for remove multiple correspondents + this.executeBulkOperation('remove_correspondents', {"correspondents": correspondents.map(t => t.id)}).subscribe( + response => { + modal.close() + } + ) + }) + } + bulkSetDocumentType(documentType: PaperlessDocumentType) { let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) modal.componentInstance.title = "Confirm Document Type assignment" @@ -188,6 +204,22 @@ export class DocumentListComponent implements OnInit { }) } + bulkRemoveDocumentTypes(documentTypes: PaperlessDocumentType[]) { + let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Confirm Document Type Removal" + modal.componentInstance.message = `This operation will remove the document type(s) ${documentTypes.map(t => t.name).join(', ')} all ${this.list.selected.size} selected document(s).` + modal.componentInstance.btnClass = "btn-warning" + modal.componentInstance.btnCaption = "Confirm" + modal.componentInstance.confirmClicked.subscribe(() => { + // TODO: API endpoint for remove multiple document types + this.executeBulkOperation('remove_document_types', {"document_types": documentTypes.map(t => t.id)}).subscribe( + response => { + modal.close() + } + ) + }) + } + bulkSetTags(tags: PaperlessTag[]) { let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) modal.componentInstance.title = "Confirm Tags assignment" @@ -197,7 +229,23 @@ export class DocumentListComponent implements OnInit { modal.componentInstance.btnCaption = "Confirm" modal.componentInstance.confirmClicked.subscribe(() => { // TODO: API endpoint for set multiple tags - this.executeBulkOperation('set_tags', {"document_type": tags ? tags.map(t => t.id) : null}).subscribe( + this.executeBulkOperation('set_tags', {"tags": tags ? tags.map(t => t.id) : null}).subscribe( + response => { + modal.close() + } + ) + }) + } + + bulkRemoveTags(tags: PaperlessTag[]) { + let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Confirm Tags Removal" + modal.componentInstance.message = `This operation will remove the tags ${tags.map(t => t.name).join(', ')} from all ${this.list.selected.size} selected document(s).` + modal.componentInstance.btnClass = "btn-warning" + modal.componentInstance.btnCaption = "Confirm" + modal.componentInstance.confirmClicked.subscribe(() => { + // TODO: API endpoint for remove multiple tags + this.executeBulkOperation('remove_tags', {"tags": tags.map(t => t.id)}).subscribe( response => { modal.close() }