|
|
@ -39,21 +39,20 @@ const accessKeys = { |
|
|
|
projectSearch: 5, |
|
|
|
projectSearch: 5, |
|
|
|
help: 6, |
|
|
|
help: 6, |
|
|
|
moreMenu: 7, |
|
|
|
moreMenu: 7, |
|
|
|
details: 8 |
|
|
|
details: 8, |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// this could be extracted into a separate component if it grows
|
|
|
|
// this could be extracted into a separate component if it grows
|
|
|
|
const accessibleListSelector = 'table.keyboard-accessible-list'; |
|
|
|
const accessibleListSelector = 'table.keyboard-accessible-list'; |
|
|
|
const accessibleRowSelector = 'table.keyboard-accessible-list tbody tr'; |
|
|
|
const accessibleRowSelector = 'table.keyboard-accessible-list tbody tr'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Injectable({ |
|
|
|
@Injectable({ |
|
|
|
providedIn: 'root' |
|
|
|
providedIn: 'root', |
|
|
|
}) |
|
|
|
}) |
|
|
|
export class KeyboardShortcutService { |
|
|
|
export class KeyboardShortcutService { |
|
|
|
|
|
|
|
|
|
|
|
// maybe move it to a .constant
|
|
|
|
// maybe move it to a .constant
|
|
|
|
private shortcuts:any = { |
|
|
|
private shortcuts:{ [name:string]:() => void } = { |
|
|
|
|
|
|
|
/* eslint-disable quote-props */ |
|
|
|
'?': () => this.showHelpModal(), |
|
|
|
'?': () => this.showHelpModal(), |
|
|
|
'g m': this.globalAction('myPagePath'), |
|
|
|
'g m': this.globalAction('myPagePath'), |
|
|
|
'g o': this.projectScoped('projectPath'), |
|
|
|
'g o': this.projectScoped('projectPath'), |
|
|
@ -71,34 +70,34 @@ export class KeyboardShortcutService { |
|
|
|
'p': this.accessKey('projectSearch'), |
|
|
|
'p': this.accessKey('projectSearch'), |
|
|
|
's': this.accessKey('quickSearch'), |
|
|
|
's': this.accessKey('quickSearch'), |
|
|
|
'k': () => this.focusPrevItem(), |
|
|
|
'k': () => this.focusPrevItem(), |
|
|
|
'j': () => this.focusNextItem() |
|
|
|
'j': () => this.focusNextItem(), |
|
|
|
|
|
|
|
/* eslint-enable quote-props */ |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(private readonly PathHelper:PathHelperService, |
|
|
|
constructor(private readonly PathHelper:PathHelperService, |
|
|
|
private readonly FocusHelper:FocusHelperService, |
|
|
|
private readonly FocusHelper:FocusHelperService, |
|
|
|
private readonly currentProject:CurrentProjectService) { |
|
|
|
private readonly currentProject:CurrentProjectService) { |
|
|
|
this.register(); |
|
|
|
this.register(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Register the keyboard shortcuts. |
|
|
|
* Register the keyboard shortcuts. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public register() { |
|
|
|
public register():void { |
|
|
|
_.each(this.shortcuts, (action:() => void, key:string) => Mousetrap.bind(key, action)); |
|
|
|
_.each(this.shortcuts, (action:() => void, key:string) => Mousetrap.bind(key, action)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public accessKey(keyName:'preview'|'newWorkPackage'|'edit'|'quickSearch'|'projectSearch'|'help'|'moreMenu'|'details') { |
|
|
|
public accessKey(keyName:'preview'|'newWorkPackage'|'edit'|'quickSearch'|'projectSearch'|'help'|'moreMenu'|'details'):() => void { |
|
|
|
var key = accessKeys[keyName]; |
|
|
|
const key = accessKeys[keyName]; |
|
|
|
|
|
|
|
|
|
|
|
return () => { |
|
|
|
return () => { |
|
|
|
var elem = jQuery('[accesskey=' + key + ']:first'); |
|
|
|
const elem = jQuery(`[accesskey=${key}]:first`); |
|
|
|
if (elem.is('input') || elem.attr('id') === 'global-search-input') { |
|
|
|
if (elem.is('input') || elem.attr('id') === 'global-search-input') { |
|
|
|
// timeout with delay so that the key is not
|
|
|
|
// timeout with delay so that the key is not
|
|
|
|
// triggered on the input
|
|
|
|
// triggered on the input
|
|
|
|
setTimeout(() => this.FocusHelper.focus(elem), 200); |
|
|
|
setTimeout(() => this.FocusHelper.focus(elem), 200); |
|
|
|
} else if (elem.is('[href]')) { |
|
|
|
} else if (elem.is('[href]')) { |
|
|
|
this.clickLink(elem[0]); |
|
|
|
this.clickLink(elem[0] as HTMLLinkElement); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
elem[0].click(); |
|
|
|
elem[0].click(); |
|
|
|
} |
|
|
|
} |
|
|
@ -106,27 +105,26 @@ export class KeyboardShortcutService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public globalAction(action:keyof PathHelperService) { |
|
|
|
public globalAction(action:keyof PathHelperService) { |
|
|
|
return () => { |
|
|
|
return ():void => { |
|
|
|
var url = (this.PathHelper[action] as any)(); |
|
|
|
window.location.href = (this.PathHelper[action] as () => string)(); |
|
|
|
window.location.href = url; |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public projectScoped(action:keyof PathHelperService) { |
|
|
|
public projectScoped(action:keyof PathHelperService) { |
|
|
|
return () => { |
|
|
|
return ():void => { |
|
|
|
var projectIdentifier = this.currentProject.identifier; |
|
|
|
const projectIdentifier = this.currentProject.identifier; |
|
|
|
if (projectIdentifier) { |
|
|
|
if (projectIdentifier) { |
|
|
|
var url = (this.PathHelper[action] as any)(projectIdentifier); |
|
|
|
window.location.href = (this.PathHelper[action] as (identifier:string|null) => string)(projectIdentifier); |
|
|
|
window.location.href = url; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
clickLink(link:any) { |
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
|
|
|
|
|
clickLink(link:HTMLLinkElement):void { |
|
|
|
const event = new MouseEvent('click', { |
|
|
|
const event = new MouseEvent('click', { |
|
|
|
view: window, |
|
|
|
view: window, |
|
|
|
bubbles: true, |
|
|
|
bubbles: true, |
|
|
|
cancelable: true |
|
|
|
cancelable: true, |
|
|
|
}); |
|
|
|
}); |
|
|
|
const cancelled = !link.dispatchEvent(event); |
|
|
|
const cancelled = !link.dispatchEvent(event); |
|
|
|
|
|
|
|
|
|
|
@ -135,15 +133,16 @@ export class KeyboardShortcutService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
showHelpModal() { |
|
|
|
showHelpModal():void { |
|
|
|
window.open(this.PathHelper.keyboardShortcutsHelpPath()); |
|
|
|
window.open(this.PathHelper.keyboardShortcutsHelpPath()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
findListInPage() { |
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
|
|
|
|
|
findListInPage():HTMLElement[] { |
|
|
|
const domLists = jQuery(accessibleListSelector); |
|
|
|
const domLists = jQuery(accessibleListSelector); |
|
|
|
const focusElements:any = []; |
|
|
|
const focusElements:HTMLElement[] = []; |
|
|
|
domLists.find('tbody tr').each(function (index, tr) { |
|
|
|
domLists.find('tbody tr').each((index, tr) => { |
|
|
|
var firstLink = jQuery(tr).find(':visible:tabbable')[0]; |
|
|
|
const firstLink = jQuery(tr).find(':visible:tabbable')[0]; |
|
|
|
if (firstLink !== undefined) { |
|
|
|
if (firstLink !== undefined) { |
|
|
|
focusElements.push(firstLink); |
|
|
|
focusElements.push(firstLink); |
|
|
|
} |
|
|
|
} |
|
|
@ -151,30 +150,29 @@ export class KeyboardShortcutService { |
|
|
|
return focusElements; |
|
|
|
return focusElements; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
focusItemOffset(offset:number) { |
|
|
|
focusItemOffset(offset:number):void { |
|
|
|
const list = this.findListInPage(); |
|
|
|
const list = this.findListInPage(); |
|
|
|
let index; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (list === null) { |
|
|
|
if (list === null) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
index = list.indexOf( |
|
|
|
const index = list.indexOf( |
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
|
jQuery(document.activeElement!) |
|
|
|
jQuery(document.activeElement!) |
|
|
|
.closest(accessibleRowSelector) |
|
|
|
.closest(accessibleRowSelector) |
|
|
|
.find(':visible:tabbable')[0] |
|
|
|
.find(':visible:tabbable')[0] as HTMLElement, |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const target = jQuery(list[(index + offset + list.length) % list.length]); |
|
|
|
const target = jQuery(list[(index + offset + list.length) % list.length]); |
|
|
|
target.focus(); |
|
|
|
target.focus(); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
focusNextItem() { |
|
|
|
focusNextItem():void { |
|
|
|
this.focusItemOffset(1); |
|
|
|
this.focusItemOffset(1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
focusPrevItem() { |
|
|
|
focusPrevItem():void { |
|
|
|
this.focusItemOffset(-1); |
|
|
|
this.focusItemOffset(-1); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|