mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Refactor SlectableItem to ToggleableItem
This commit is contained in:
		| @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'; | ||||
| 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 { SelectableItem, SelectableItemState } from '../filterable-dropdown.component'; | ||||
| import { ToggleableItem, ToggleableItemState } from '../filterable-dropdown.component'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-filterable-dropdown-button', | ||||
| @@ -12,10 +12,10 @@ import { SelectableItem, SelectableItemState } from '../filterable-dropdown.comp | ||||
| export class FilterableDropdownButtonComponent implements OnInit { | ||||
|  | ||||
|   @Input() | ||||
|   selectableItem: SelectableItem | ||||
|   toggleableItem: ToggleableItem | ||||
|  | ||||
|   get item(): PaperlessTag | PaperlessDocumentType | PaperlessCorrespondent { | ||||
|     return this.selectableItem?.item | ||||
|     return this.toggleableItem?.item | ||||
|   } | ||||
|  | ||||
|   @Output() | ||||
| @@ -28,14 +28,14 @@ export class FilterableDropdownButtonComponent implements OnInit { | ||||
|   } | ||||
|  | ||||
|   toggleItem(): void { | ||||
|     this.selectableItem.state = (this.selectableItem.state == SelectableItemState.NotSelected || this.selectableItem.state == SelectableItemState.PartiallySelected) ? SelectableItemState.Selected : SelectableItemState.NotSelected | ||||
|     this.toggle.emit(this.selectableItem) | ||||
|     this.toggleableItem.state = (this.toggleableItem.state == ToggleableItemState.NotSelected || this.toggleableItem.state == ToggleableItemState.PartiallySelected) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected | ||||
|     this.toggle.emit(this.toggleableItem) | ||||
|   } | ||||
|  | ||||
|   getSelectedIconName() { | ||||
|     let iconName = '' | ||||
|     if (this.selectableItem?.state == SelectableItemState.Selected) iconName = 'check' | ||||
|     else if (this.selectableItem?.state == SelectableItemState.PartiallySelected) iconName = 'dash' | ||||
|     if (this.toggleableItem?.state == ToggleableItemState.Selected) iconName = 'check' | ||||
|     else if (this.toggleableItem?.state == ToggleableItemState.PartiallySelected) iconName = 'dash' | ||||
|     return iconName | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -19,9 +19,9 @@ | ||||
|           <input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div *ngIf="selectableItems" class="items"> | ||||
|         <ng-container *ngFor="let selectableItem of selectableItems | filter: filterText"> | ||||
|           <app-filterable-dropdown-button [selectableItem]="selectableItem" (toggle)="toggleItem($event)"></app-filterable-dropdown-button> | ||||
|       <div *ngIf="toggleableItems" class="items"> | ||||
|         <ng-container *ngFor="let toggleableItem of toggleableItems | filter: filterText"> | ||||
|           <app-filterable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="toggleItem($event)"></app-filterable-dropdown-button> | ||||
|         </ng-container> | ||||
|       </div> | ||||
|     </div> | ||||
|   | ||||
| @@ -6,12 +6,12 @@ import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { FilterPipe } from  'src/app/pipes/filter.pipe'; | ||||
| import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | ||||
|  | ||||
| export interface SelectableItem { | ||||
| export interface ToggleableItem { | ||||
|   item: PaperlessTag | PaperlessDocumentType | PaperlessCorrespondent, | ||||
|   state: SelectableItemState | ||||
|   state: ToggleableItemState | ||||
| } | ||||
|  | ||||
| export enum SelectableItemState { | ||||
| export enum ToggleableItemState { | ||||
|   NotSelected = 0, | ||||
|   Selected = 1, | ||||
|   PartiallySelected = 2 | ||||
| @@ -37,33 +37,33 @@ export class FilterableDropdownComponent { | ||||
|   @Input() | ||||
|   set items(items: ObjectWithId[]) { | ||||
|     if (items) { | ||||
|       this._selectableItems = items.map(i => { | ||||
|         return {item: i, state: SelectableItemState.NotSelected} | ||||
|       this._toggleableItems = items.map(i => { | ||||
|         return {item: i, state: ToggleableItemState.NotSelected} | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _selectableItems: SelectableItem[] = [] | ||||
|   _toggleableItems: ToggleableItem[] = [] | ||||
|  | ||||
|   @Input() | ||||
|   set selectableItems (selectableItems: SelectableItem[]) { | ||||
|   set toggleableItems (toggleableItems: ToggleableItem[]) { | ||||
|     if (this.type == FilterableDropdownType.Editing && this.dropdown?.isOpen()) return | ||||
|     else this._selectableItems = selectableItems | ||||
|     else this._toggleableItems = toggleableItems | ||||
|   } | ||||
|  | ||||
|   get selectableItems(): SelectableItem[] { | ||||
|     return this._selectableItems | ||||
|   get toggleableItems(): ToggleableItem[] { | ||||
|     return this._toggleableItems | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   set itemsSelected(itemsSelected: ObjectWithId[]) { | ||||
|     this.selectableItems.forEach(i => { | ||||
|       i.state = (itemsSelected.find(is => is.id == i.item.id)) ? SelectableItemState.Selected : SelectableItemState.NotSelected | ||||
|     this.toggleableItems.forEach(i => { | ||||
|       i.state = (itemsSelected.find(is => is.id == i.item.id)) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   get itemsSelected() :ObjectWithId[] { | ||||
|     return this.selectableItems.filter(si => si.state == SelectableItemState.Selected).map(si => si.item) | ||||
|     return this.toggleableItems.filter(si => si.state == ToggleableItemState.Selected).map(si => si.item) | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
| @@ -91,11 +91,11 @@ export class FilterableDropdownComponent { | ||||
|  | ||||
|   constructor(private filterPipe: FilterPipe) { } | ||||
|  | ||||
|   toggleItem(selectableItem: SelectableItem): void { | ||||
|     if (this.singular && selectableItem.state == SelectableItemState.Selected) { | ||||
|       this._selectableItems.filter(si => si.item.id !== selectableItem.item.id).forEach(si => si.state = SelectableItemState.NotSelected) | ||||
|   toggleItem(toggleableItem: ToggleableItem): void { | ||||
|     if (this.singular && toggleableItem.state == ToggleableItemState.Selected) { | ||||
|       this._toggleableItems.filter(si => si.item.id !== toggleableItem.item.id).forEach(si => si.state = ToggleableItemState.NotSelected) | ||||
|     } | ||||
|     this.toggle.emit(selectableItem.item) | ||||
|     this.toggle.emit(toggleableItem.item) | ||||
|   } | ||||
|  | ||||
|   dropdownOpenChange(open: boolean): void { | ||||
| @@ -111,7 +111,7 @@ export class FilterableDropdownComponent { | ||||
|   } | ||||
|  | ||||
|   listFilterEnter(): void { | ||||
|     let filtered = this.filterPipe.transform(this.selectableItems, this.filterText) | ||||
|     let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText) | ||||
|     if (filtered.length == 1) this.toggleItem(filtered.shift()) | ||||
|     this.dropdown.close() | ||||
|   } | ||||
|   | ||||
| @@ -26,9 +26,9 @@ | ||||
|   <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="Tags" icon="tag-fill" [selectableItems]="tagsSelectableItems" [type]="dropdownTypes.Editing" (open)="tagsDropdownOpen()" (editingComplete)="applyTags($event)"></app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="mr-2 mr-md-3" title="Correspondent" icon="person-fill" [selectableItems]="correspondentsSelectableItems" [type]="dropdownTypes.Editing" singular="true" (open)="correspondentsDropdownOpen()" (editingComplete)="applyCorrespondent($event)"></app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="mr-2 mr-md-3" title="Document Type" icon="file-earmark-fill" [selectableItems]="documentTypesSelectableItems" [type]="dropdownTypes.Editing" singular="true" (open)="documentTypesDropdownOpen()" (editingComplete)="applyDocumentType($event)"></app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="mr-2 mr-md-3" title="Tags" icon="tag-fill" [toggleableItems]="tagsToggleableItems" [type]="dropdownTypes.Editing" (open)="tagsDropdownOpen()" (editingComplete)="applyTags($event)"></app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="mr-2 mr-md-3" title="Correspondent" icon="person-fill" [toggleableItems]="correspondentsToggleableItems" [type]="dropdownTypes.Editing" singular="true" (open)="correspondentsDropdownOpen()" (editingComplete)="applyCorrespondent($event)"></app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="mr-2 mr-md-3" title="Document Type" icon="file-earmark-fill" [toggleableItems]="documentTypesToggleableItems" [type]="dropdownTypes.Editing" singular="true" (open)="documentTypesDropdownOpen()" (editingComplete)="applyDocumentType($event)"></app-filterable-dropdown> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="w-100 d-xl-none"></div> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ 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'; | ||||
| import { SelectableItem, SelectableItemState, FilterableDropdownType } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component'; | ||||
| import { ToggleableItem, ToggleableItemState, FilterableDropdownType } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-bulk-editor', | ||||
| @@ -48,51 +48,51 @@ export class BulkEditorComponent { | ||||
|   correspondents: PaperlessCorrespondent[] | ||||
|   documentTypes: PaperlessDocumentType[] | ||||
|  | ||||
|   private initiallySelectedTagsSelectableItems: SelectableItem[] | ||||
|   private initiallySelectedCorrespondentsSelectableItems: SelectableItem[] | ||||
|   private initiallySelectedDocumentTypesSelectableItems: SelectableItem[] | ||||
|   private initiallySelectedTagsToggleableItems: ToggleableItem[] | ||||
|   private initiallySelectedCorrespondentsToggleableItems: ToggleableItem[] | ||||
|   private initiallySelectedDocumentTypesToggleableItems: ToggleableItem[] | ||||
|  | ||||
|   dropdownTypes = FilterableDropdownType | ||||
|  | ||||
|   get tagsSelectableItems(): SelectableItem[] { | ||||
|     let tagsSelectableItems = [] | ||||
|   get tagsToggleableItems(): ToggleableItem[] { | ||||
|     let tagsToggleableItems = [] | ||||
|     let selectedDocuments: PaperlessDocument[] = this.allDocuments.filter(d => this.selectedDocuments.has(d.id)) | ||||
|     this.tags.forEach(t => { | ||||
|       let selectedDocumentsWithTag: PaperlessDocument[] = selectedDocuments.filter(d => d.tags.includes(t.id)) | ||||
|       let state = SelectableItemState.NotSelected | ||||
|       if (selectedDocumentsWithTag.length == selectedDocuments.length) state = SelectableItemState.Selected | ||||
|       else if (selectedDocumentsWithTag.length > 0 && selectedDocumentsWithTag.length < selectedDocuments.length) state = SelectableItemState.PartiallySelected | ||||
|       tagsSelectableItems.push( { item: t, state: state } ) | ||||
|       let state = ToggleableItemState.NotSelected | ||||
|       if (selectedDocumentsWithTag.length == selectedDocuments.length) state = ToggleableItemState.Selected | ||||
|       else if (selectedDocumentsWithTag.length > 0 && selectedDocumentsWithTag.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected | ||||
|       tagsToggleableItems.push( { item: t, state: state } ) | ||||
|     }) | ||||
|     return tagsSelectableItems | ||||
|     return tagsToggleableItems | ||||
|   } | ||||
|  | ||||
|   get correspondentsSelectableItems(): SelectableItem[] { | ||||
|     let correspondentsSelectableItems = [] | ||||
|   get correspondentsToggleableItems(): ToggleableItem[] { | ||||
|     let correspondentsToggleableItems = [] | ||||
|     let selectedDocuments: PaperlessDocument[] = this.allDocuments.filter(d => this.selectedDocuments.has(d.id)) | ||||
|  | ||||
|     this.correspondents.forEach(c => { | ||||
|       let selectedDocumentsWithCorrespondent: PaperlessDocument[] = selectedDocuments.filter(d => d.correspondent == c.id) | ||||
|       let state = SelectableItemState.NotSelected | ||||
|       if (selectedDocumentsWithCorrespondent.length == selectedDocuments.length) state = SelectableItemState.Selected | ||||
|       else if (selectedDocumentsWithCorrespondent.length > 0 && selectedDocumentsWithCorrespondent.length < selectedDocuments.length) state = SelectableItemState.PartiallySelected | ||||
|       correspondentsSelectableItems.push( { item: c, state: state } ) | ||||
|       let state = ToggleableItemState.NotSelected | ||||
|       if (selectedDocumentsWithCorrespondent.length == selectedDocuments.length) state = ToggleableItemState.Selected | ||||
|       else if (selectedDocumentsWithCorrespondent.length > 0 && selectedDocumentsWithCorrespondent.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected | ||||
|       correspondentsToggleableItems.push( { item: c, state: state } ) | ||||
|     }) | ||||
|     return correspondentsSelectableItems | ||||
|     return correspondentsToggleableItems | ||||
|   } | ||||
|  | ||||
|   get documentTypesSelectableItems(): SelectableItem[] { | ||||
|     let documentTypesSelectableItems = [] | ||||
|   get documentTypesToggleableItems(): ToggleableItem[] { | ||||
|     let documentTypesToggleableItems = [] | ||||
|     let selectedDocuments: PaperlessDocument[] = this.allDocuments.filter(d => this.selectedDocuments.has(d.id)) | ||||
|  | ||||
|     this.documentTypes.forEach(dt => { | ||||
|       let selectedDocumentsWithDocumentType: PaperlessDocument[] = selectedDocuments.filter(d => d.document_type == dt.id) | ||||
|       let state = SelectableItemState.NotSelected | ||||
|       if (selectedDocumentsWithDocumentType.length == selectedDocuments.length) state = SelectableItemState.Selected | ||||
|       else if (selectedDocumentsWithDocumentType.length > 0 && selectedDocumentsWithDocumentType.length < selectedDocuments.length) state = SelectableItemState.PartiallySelected | ||||
|       documentTypesSelectableItems.push( { item: dt, state: state } ) | ||||
|       let state = ToggleableItemState.NotSelected | ||||
|       if (selectedDocumentsWithDocumentType.length == selectedDocuments.length) state = ToggleableItemState.Selected | ||||
|       else if (selectedDocumentsWithDocumentType.length > 0 && selectedDocumentsWithDocumentType.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected | ||||
|       documentTypesToggleableItems.push( { item: dt, state: state } ) | ||||
|     }) | ||||
|     return documentTypesSelectableItems | ||||
|     return documentTypesToggleableItems | ||||
|   } | ||||
|  | ||||
|   constructor( | ||||
| @@ -109,39 +109,39 @@ export class BulkEditorComponent { | ||||
|   } | ||||
|  | ||||
|   tagsDropdownOpen() { | ||||
|     this.initiallySelectedTagsSelectableItems = this.tagsSelectableItems.filter(tsi => tsi.state == SelectableItemState.Selected) | ||||
|     this.initiallySelectedTagsToggleableItems = this.tagsToggleableItems.filter(tsi => tsi.state == ToggleableItemState.Selected) | ||||
|   } | ||||
|  | ||||
|   correspondentsDropdownOpen() { | ||||
|     this.initiallySelectedCorrespondentsSelectableItems = this.correspondentsSelectableItems.filter(csi => csi.state == SelectableItemState.Selected) | ||||
|     this.initiallySelectedCorrespondentsToggleableItems = this.correspondentsToggleableItems.filter(csi => csi.state == ToggleableItemState.Selected) | ||||
|   } | ||||
|  | ||||
|   documentTypesDropdownOpen() { | ||||
|     this.initiallySelectedDocumentTypesSelectableItems = this.documentTypesSelectableItems.filter(dtsi => dtsi.state == SelectableItemState.Selected) | ||||
|     this.initiallySelectedDocumentTypesToggleableItems = this.documentTypesToggleableItems.filter(dtsi => dtsi.state == ToggleableItemState.Selected) | ||||
|   } | ||||
|  | ||||
|   applyTags(selectedTags: PaperlessTag[]) { | ||||
|     let unchanged = this.equateItemsToSelectableItems(selectedTags, this.initiallySelectedTagsSelectableItems) | ||||
|     let unchanged = this.equateItemsToToggleableItems(selectedTags, this.initiallySelectedTagsToggleableItems) | ||||
|     if (!unchanged) this.setTags.emit(selectedTags) | ||||
|     this.initiallySelectedTagsSelectableItems = null | ||||
|     this.initiallySelectedTagsToggleableItems = null | ||||
|   } | ||||
|  | ||||
|   applyCorrespondent(selectedCorrespondents: PaperlessCorrespondent[]) { | ||||
|     let unchanged = this.equateItemsToSelectableItems(selectedCorrespondents, this.initiallySelectedCorrespondentsSelectableItems) | ||||
|     let unchanged = this.equateItemsToToggleableItems(selectedCorrespondents, this.initiallySelectedCorrespondentsToggleableItems) | ||||
|     if (!unchanged) this.setCorrespondent.emit(selectedCorrespondents?.length > 0 ? selectedCorrespondents.shift() : null) | ||||
|     this.initiallySelectedCorrespondentsSelectableItems = null | ||||
|     this.initiallySelectedCorrespondentsToggleableItems = null | ||||
|   } | ||||
|  | ||||
|   applyDocumentType(selectedDocumentTypes: PaperlessDocumentType[]) { | ||||
|     let unchanged = this.equateItemsToSelectableItems(selectedDocumentTypes, this.initiallySelectedDocumentTypesSelectableItems) | ||||
|     let unchanged = this.equateItemsToToggleableItems(selectedDocumentTypes, this.initiallySelectedDocumentTypesToggleableItems) | ||||
|     if (!unchanged) this.setDocumentType.emit(selectedDocumentTypes.length > 0 ? selectedDocumentTypes.shift() : null) | ||||
|     this.initiallySelectedDocumentTypesSelectableItems = null | ||||
|     this.initiallySelectedDocumentTypesToggleableItems = null | ||||
|   } | ||||
|  | ||||
|   equateItemsToSelectableItems(items: ObjectWithId[], selectableItems: SelectableItem[]): boolean { | ||||
|     // either both empty or all items must in selectableItems and vice-versa | ||||
|     return (selectableItems.length == 0 && items.length == 0) || | ||||
|            (items.every(i => selectableItems.find(si => si.item.id == i.id) !== undefined) && selectableItems.every(si => items.find(i => i.id == si.item.id) !== undefined)) | ||||
|   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(si => si.item.id == i.id) !== undefined) && toggleableItems.every(si => items.find(i => i.id == si.item.id) !== undefined)) | ||||
|   } | ||||
|  | ||||
|   applyDelete() { | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| import { Pipe, PipeTransform } from '@angular/core'; | ||||
| import { SelectableItem } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component'; | ||||
| import { ToggleableItem } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component'; | ||||
|  | ||||
| @Pipe({ | ||||
|   name: 'filter' | ||||
| }) | ||||
| export class FilterPipe implements PipeTransform { | ||||
|   transform(selectableItems: SelectableItem[], searchText: string): any[] { | ||||
|     if (!selectableItems) return []; | ||||
|     if (!searchText) return selectableItems; | ||||
|   transform(toggleableItems: ToggleableItem[], searchText: string): any[] { | ||||
|     if (!toggleableItems) return []; | ||||
|     if (!searchText) return toggleableItems; | ||||
|  | ||||
|     return selectableItems.filter(selectableItem => { | ||||
|       return Object.keys(selectableItem.item).some(key => { | ||||
|         return String(selectableItem.item[key]).toLowerCase().includes(searchText.toLowerCase()); | ||||
|     return toggleableItems.filter(toggleableItem => { | ||||
|       return Object.keys(toggleableItem.item).some(key => { | ||||
|         return String(toggleableItem.item[key]).toLowerCase().includes(searchText.toLowerCase()); | ||||
|       }); | ||||
|     }); | ||||
|    } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon