Hotkey service

This commit is contained in:
shamoon 2024-04-04 19:30:04 -07:00
parent 4ee76f4edf
commit 5bb577ab20
4 changed files with 102 additions and 11 deletions

View File

@ -150,12 +150,12 @@ describe('GlobalSearchComponent', () => {
it('should handle keyboard nav', () => {
const focusSpy = jest.spyOn(component.searchInput.nativeElement, 'focus')
component.handleKeyboardEvent(
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'k', ctrlKey: true })
)
expect(focusSpy).toHaveBeenCalled()
// coverage
component.handleKeyboardEvent(
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'k', metaKey: true })
)

View File

@ -5,6 +5,7 @@ import {
ViewChildren,
QueryList,
HostListener,
OnInit,
} from '@angular/core'
import { Router } from '@angular/router'
import { NgbDropdown, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
@ -39,13 +40,14 @@ import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
import { HotKeyService } from 'src/app/services/hot-key.service'
@Component({
selector: 'pngx-global-search',
templateUrl: './global-search.component.html',
styleUrl: './global-search.component.scss',
})
export class GlobalSearchComponent {
export class GlobalSearchComponent implements OnInit {
public DataType = DataType
public query: string
public queryDebounce: Subject<string>
@ -60,13 +62,6 @@ export class GlobalSearchComponent {
@ViewChildren('primaryButton') primaryButtons: QueryList<ElementRef>
@ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef>
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (event.key === 'k' && (event.ctrlKey || event.metaKey)) {
this.searchInput.nativeElement.focus()
}
}
constructor(
private searchService: SearchService,
private router: Router,
@ -74,7 +69,8 @@ export class GlobalSearchComponent {
private documentService: DocumentService,
private documentListViewService: DocumentListViewService,
private permissionsService: PermissionsService,
private toastService: ToastService
private toastService: ToastService,
private hotkeyService: HotKeyService
) {
this.queryDebounce = new Subject<string>()
@ -90,6 +86,15 @@ export class GlobalSearchComponent {
})
}
ngOnInit() {
this.hotkeyService.addShortcut({ keys: 'meta.k' }).subscribe(() => {
this.searchInput.nativeElement.focus()
})
this.hotkeyService.addShortcut({ keys: 'control.k' }).subscribe(() => {
this.searchInput.nativeElement.focus()
})
}
private search(query: string) {
this.loading = true
this.searchService.globalSearch(query).subscribe((results) => {

View File

@ -0,0 +1,41 @@
import { TestBed } from '@angular/core/testing'
import { EventManager } from '@angular/platform-browser'
import { DOCUMENT } from '@angular/common'
import { HotKeyService } from './hot-key.service'
describe('HotKeyService', () => {
let service: HotKeyService
let eventManager: EventManager
let document: Document
beforeEach(() => {
TestBed.configureTestingModule({
providers: [HotKeyService, EventManager],
})
service = TestBed.inject(HotKeyService)
eventManager = TestBed.inject(EventManager)
document = TestBed.inject(DOCUMENT)
})
it('should support adding a shortcut', () => {
const callback = jest.fn()
const addEventListenerSpy = jest.spyOn(eventManager, 'addEventListener')
const observable = service
.addShortcut({ keys: 'control.a' })
.subscribe(() => {
callback()
})
expect(addEventListenerSpy).toHaveBeenCalled()
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'a', ctrlKey: true })
)
expect(callback).toHaveBeenCalled()
//coverage
observable.unsubscribe()
})
})

View File

@ -0,0 +1,45 @@
import { DOCUMENT } from '@angular/common'
import { Inject, Injectable } from '@angular/core'
import { EventManager } from '@angular/platform-browser'
import { Observable } from 'rxjs'
export interface ShortcutOptions {
element?: any
keys: string
}
@Injectable({
providedIn: 'root',
})
export class HotKeyService {
defaults: Partial<ShortcutOptions> = {
element: this.document,
}
constructor(
private eventManager: EventManager,
@Inject(DOCUMENT) private document: Document
) {}
addShortcut(options: ShortcutOptions) {
const merged = { ...this.defaults, ...options }
const event = `keydown.${merged.keys}`
return new Observable((observer) => {
const handler = (e) => {
e.preventDefault()
observer.next(e)
}
const dispose = this.eventManager.addEventListener(
merged.element,
event,
handler
)
return () => {
dispose()
}
})
}
}