Only reset selection / filters if no menus open

This commit is contained in:
shamoon 2024-04-07 17:30:26 -07:00
parent ae5639cf96
commit 1f25d7aa69
13 changed files with 138 additions and 8 deletions

View File

@ -135,4 +135,10 @@ describe('DateDropdownComponent', () => {
input.dispatchEvent(event) input.dispatchEvent(event)
expect(eventSpy).toHaveBeenCalled() expect(eventSpy).toHaveBeenCalled()
}) })
it('should correctly pass open state', () => {
expect(component.isOpen()).toBeFalsy()
component.dropdown.open()
expect(component.isOpen()).toBeTruthy()
})
}) })

View File

@ -5,8 +5,9 @@ import {
Output, Output,
OnInit, OnInit,
OnDestroy, OnDestroy,
ViewChild,
} from '@angular/core' } from '@angular/core'
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap' import { NgbDateAdapter, NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
import { Subject, Subscription } from 'rxjs' import { Subject, Subscription } from 'rxjs'
import { debounceTime } from 'rxjs/operators' import { debounceTime } from 'rxjs/operators'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
@ -88,6 +89,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
@Input() @Input()
disabled: boolean = false disabled: boolean = false
@ViewChild(NgbDropdown) dropdown: NgbDropdown
get isActive(): boolean { get isActive(): boolean {
return ( return (
this.relativeDate !== null || this.relativeDate !== null ||
@ -96,6 +99,10 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
) )
} }
public isOpen(): boolean {
return this.dropdown.isOpen()
}
private datesSetDebounce$ = new Subject() private datesSetDebounce$ = new Subject()
private sub: Subscription private sub: Subscription

View File

@ -577,4 +577,13 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
expect(selectionModel.getSelectedItems()).toEqual([items[0]]) expect(selectionModel.getSelectedItems()).toEqual([items[0]])
expect(selectionModel.getExcludedItems()).toEqual([items[1]]) expect(selectionModel.getExcludedItems()).toEqual([items[1]])
}) })
it('should correctly pass open state', () => {
component.items = items
component.icon = 'tag-fill'
fixture.detectChanges()
expect(component.isOpen()).toBeFalsy()
component.dropdown.open()
expect(component.isOpen()).toBeTruthy()
})
}) })

View File

@ -425,6 +425,10 @@ export class FilterableDropdownComponent {
modelIsDirty: boolean = false modelIsDirty: boolean = false
public isOpen(): boolean {
return this.dropdown.isOpen()
}
private keyboardIndex: number private keyboardIndex: number
constructor(private filterPipe: FilterPipe) { constructor(private filterPipe: FilterPipe) {

View File

@ -165,4 +165,10 @@ describe('PermissionsFilterDropdownComponent', () => {
userID: null, userID: null,
}) })
}) })
it('should correctly pass open state', () => {
expect(component.isOpen()).toBeFalsy()
component.dropdown.open()
expect(component.isOpen()).toBeTruthy()
})
}) })

View File

@ -1,4 +1,10 @@
import { Component, EventEmitter, Input, Output } from '@angular/core' import {
Component,
EventEmitter,
Input,
Output,
ViewChild,
} from '@angular/core'
import { first } from 'rxjs' import { first } from 'rxjs'
import { User } from 'src/app/data/user' import { User } from 'src/app/data/user'
import { import {
@ -9,6 +15,7 @@ import {
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
export class PermissionsSelectionModel { export class PermissionsSelectionModel {
ownerFilter: OwnerFilterType ownerFilter: OwnerFilterType
@ -59,6 +66,8 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
hideUnowned: boolean hideUnowned: boolean
@ViewChild(NgbDropdown) dropdown: NgbDropdown
get isActive(): boolean { get isActive(): boolean {
return ( return (
this.selectionModel.ownerFilter !== OwnerFilterType.NONE || this.selectionModel.ownerFilter !== OwnerFilterType.NONE ||
@ -66,6 +75,10 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
) )
} }
public isOpen(): boolean {
return this.dropdown.isOpen()
}
constructor( constructor(
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
userService: UserService, userService: UserService,

View File

@ -1179,4 +1179,16 @@ describe('BulkEditorComponent', () => {
) )
expect(component.storagePaths).toEqual(storagePaths.results) expect(component.storagePaths).toEqual(storagePaths.results)
}) })
it('should correctly pass open state from dropdowns', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
expect(component.hasOpenMenu).toBeFalsy()
component.filterableDropdowns = [
{
isOpen: () => true,
} as any,
]
expect(component.hasOpenMenu).toBeTruthy()
})
}) })

View File

@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit, ViewChildren } from '@angular/core'
import { Tag } from 'src/app/data/tag' import { Tag } from 'src/app/data/tag'
import { Correspondent } from 'src/app/data/correspondent' import { Correspondent } from 'src/app/data/correspondent'
import { DocumentType } from 'src/app/data/document-type' import { DocumentType } from 'src/app/data/document-type'
@ -6,7 +6,7 @@ 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 { DocumentTypeService } from 'src/app/services/rest/document-type.service' import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { import {
DocumentService, DocumentService,
SelectionDataItem, SelectionDataItem,
@ -15,6 +15,7 @@ import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'
import { import {
ChangedItems, ChangedItems,
FilterableDropdownComponent,
FilterableDropdownSelectionModel, FilterableDropdownSelectionModel,
} from '../../common/filterable-dropdown/filterable-dropdown.component' } from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
@ -74,6 +75,10 @@ export class BulkEditorComponent
downloadUseFormatting: new FormControl(false), downloadUseFormatting: new FormControl(false),
}) })
@ViewChildren(NgbDropdown) dropdowns: NgbDropdown[]
@ViewChildren(FilterableDropdownComponent)
filterableDropdowns: FilterableDropdownComponent[]
constructor( constructor(
private documentTypeService: DocumentTypeService, private documentTypeService: DocumentTypeService,
private tagService: TagService, private tagService: TagService,
@ -121,6 +126,13 @@ export class BulkEditorComponent
return ownsAll return ownsAll
} }
get hasOpenMenu(): boolean {
return (
this.filterableDropdowns.some((d) => d.isOpen()) ||
this.dropdowns.some((d) => d.isOpen())
)
}
ngOnInit() { ngOnInit() {
if ( if (
this.permissionService.currentUserCan( this.permissionService.currentUserCan(

View File

@ -81,7 +81,7 @@
<div class="row sticky-top py-3 mt-n2 mt-md-n3 bg-body"> <div class="row sticky-top py-3 mt-n2 mt-md-n3 bg-body">
<pngx-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></pngx-filter-editor> <pngx-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></pngx-filter-editor>
<pngx-bulk-editor [hidden]="!isBulkEditing"></pngx-bulk-editor> <pngx-bulk-editor [hidden]="!isBulkEditing" #bulkEditor></pngx-bulk-editor>
</div> </div>

View File

@ -630,4 +630,15 @@ describe('DocumentListComponent', () => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'o' })) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'o' }))
expect(detailSpy).toHaveBeenCalledWith(docs[0]) expect(detailSpy).toHaveBeenCalledWith(docs[0])
}) })
it('should ignore escape hotkey if there is a filter or bulk editor menu to close', () => {
fixture.detectChanges()
jest
.spyOn(component['filterEditor'], 'hasOpenMenu', 'get')
.mockReturnValue(true)
const resetSpy = jest.spyOn(component['filterEditor'], 'resetSelected')
component.clickTag(1)
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'escape' }))
expect(resetSpy).not.toHaveBeenCalled()
})
}) })

View File

@ -7,7 +7,7 @@ import {
ViewChildren, ViewChildren,
} from '@angular/core' } from '@angular/core'
import { ActivatedRoute, convertToParamMap, Router } from '@angular/router' import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { filter, first, map, Subject, switchMap, takeUntil } from 'rxjs' import { filter, first, map, Subject, switchMap, takeUntil } from 'rxjs'
import { FilterRule } from 'src/app/data/filter-rule' import { FilterRule } from 'src/app/data/filter-rule'
import { import {
@ -37,6 +37,7 @@ import { ComponentWithPermissions } from '../with-permissions/with-permissions.c
import { FilterEditorComponent } from './filter-editor/filter-editor.component' import { FilterEditorComponent } from './filter-editor/filter-editor.component'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component' import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
import { HotKeyService } from 'src/app/services/hot-key.service' import { HotKeyService } from 'src/app/services/hot-key.service'
import { BulkEditorComponent } from './bulk-editor/bulk-editor.component'
@Component({ @Component({
selector: 'pngx-document-list', selector: 'pngx-document-list',
@ -66,8 +67,13 @@ export class DocumentListComponent
@ViewChild('filterEditor') @ViewChild('filterEditor')
private filterEditor: FilterEditorComponent private filterEditor: FilterEditorComponent
@ViewChild('bulkEditor')
private bulkEditor: BulkEditorComponent
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective> @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
@ViewChildren(NgbDropdown) dropdowns: QueryList<NgbDropdown>
displayMode = 'smallCards' // largeCards, smallCards, details displayMode = 'smallCards' // largeCards, smallCards, details
unmodifiedFilterRules: FilterRule[] = [] unmodifiedFilterRules: FilterRule[] = []
@ -129,6 +135,14 @@ export class DocumentListComponent
return this.list.selected.size > 0 return this.list.selected.size > 0
} }
get hasOpenMenu(): boolean {
return (
this.dropdowns.some((d) => d.isOpen()) ||
this.filterEditor.hasOpenMenu ||
this.bulkEditor.hasOpenMenu
)
}
saveDisplayMode() { saveDisplayMode() {
localStorage.setItem('document-list:displayMode', this.displayMode) localStorage.setItem('document-list:displayMode', this.displayMode)
} }
@ -192,7 +206,10 @@ export class DocumentListComponent
keys: 'escape', keys: 'escape',
description: $localize`Reset filters / selection`, description: $localize`Reset filters / selection`,
}) })
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(
takeUntil(this.unsubscribeNotifier),
filter(() => !this.hasOpenMenu)
)
.subscribe(() => { .subscribe(() => {
if (this.list.selected.size > 0) { if (this.list.selected.size > 0) {
this.list.selectNone() this.list.selectNone()

View File

@ -1870,4 +1870,14 @@ describe('FilterEditorComponent', () => {
component.itemSelected({ item: 'world', preventDefault: () => true }) component.itemSelected({ item: 'world', preventDefault: () => true })
expect(component.textFilter).toEqual('hello world ') expect(component.textFilter).toEqual('hello world ')
}) })
it('should correctly pass open state from dropdowns', () => {
expect(component.hasOpenMenu).toBeFalsy()
component.filterableDropdowns = [
{
isOpen: () => true,
} as any,
]
expect(component.hasOpenMenu).toBeTruthy()
})
}) })

View File

@ -8,6 +8,7 @@ import {
ViewChild, ViewChild,
ElementRef, ElementRef,
AfterViewInit, AfterViewInit,
ViewChildren,
} from '@angular/core' } from '@angular/core'
import { Tag } from 'src/app/data/tag' import { Tag } from 'src/app/data/tag'
import { Correspondent } from 'src/app/data/correspondent' import { Correspondent } from 'src/app/data/correspondent'
@ -61,6 +62,7 @@ import {
FILTER_SHARED_BY_USER, FILTER_SHARED_BY_USER,
} from 'src/app/data/filter-rule-type' } from 'src/app/data/filter-rule-type'
import { import {
FilterableDropdownComponent,
FilterableDropdownSelectionModel, FilterableDropdownSelectionModel,
Intersection, Intersection,
LogicalOperator, LogicalOperator,
@ -74,9 +76,13 @@ import {
import { Document } from 'src/app/data/document' import { Document } from 'src/app/data/document'
import { StoragePath } from 'src/app/data/storage-path' import { StoragePath } from 'src/app/data/storage-path'
import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { RelativeDate } from '../../common/date-dropdown/date-dropdown.component' import {
DateDropdownComponent,
RelativeDate,
} from '../../common/date-dropdown/date-dropdown.component'
import { import {
OwnerFilterType, OwnerFilterType,
PermissionsFilterDropdownComponent,
PermissionsSelectionModel, PermissionsSelectionModel,
} from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component' } from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
import { import {
@ -86,6 +92,7 @@ import {
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { SearchService } from 'src/app/services/rest/search.service' import { SearchService } from 'src/app/services/rest/search.service'
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
const TEXT_FILTER_TARGET_TITLE = 'title' const TEXT_FILTER_TARGET_TITLE = 'title'
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content' const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
@ -269,6 +276,13 @@ export class FilterEditorComponent
unsubscribeNotifier: Subject<any> = new Subject() unsubscribeNotifier: Subject<any> = new Subject()
@ViewChild(NgbDropdown) textFilterDropdown: NgbDropdown
@ViewChildren(FilterableDropdownComponent)
filterableDropdowns: FilterableDropdownComponent[]
@ViewChildren(DateDropdownComponent) dateDropdowns: DateDropdownComponent[]
@ViewChild(PermissionsFilterDropdownComponent)
permissionsDropdown: PermissionsFilterDropdownComponent
get textFilterTargets() { get textFilterTargets() {
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
return DEFAULT_TEXT_FILTER_TARGET_OPTIONS.concat([ return DEFAULT_TEXT_FILTER_TARGET_OPTIONS.concat([
@ -876,6 +890,15 @@ export class FilterEditorComponent
textFilterDebounce: Subject<string> textFilterDebounce: Subject<string>
get hasOpenMenu(): boolean {
return (
this.textFilterDropdown.isOpen() ||
this.filterableDropdowns.some((d) => d.isOpen()) ||
this.dateDropdowns.some((d) => d.isOpen()) ||
this.permissionsDropdown.isOpen()
)
}
ngOnInit() { ngOnInit() {
if ( if (
this.permissionsService.currentUserCan( this.permissionsService.currentUserCan(