CORD-582, CORD-734 Registering events listeners for keyboard shortcuts
and displaying them in the side panel
Change-Id: Ifbb227b3a425be5c33d1fe211abd473209414896
diff --git a/src/app/core/services/helpers/component-injector.helpers.ts b/src/app/core/services/helpers/component-injector.helpers.ts
index e70e257..908e4c7 100644
--- a/src/app/core/services/helpers/component-injector.helpers.ts
+++ b/src/app/core/services/helpers/component-injector.helpers.ts
@@ -16,7 +16,7 @@
}
export class XosComponentInjector implements IXosComponentInjectorService {
- static $inject = ['$rootScope', '$compile', '$transitions', '$log'];
+ static $inject = ['$rootScope', '$compile', '$transitions', '$log', '$timeout'];
public injectedComponents: IXosInjectedComponent[] = [];
@@ -24,7 +24,8 @@
private $rootScope: ng.IRootScopeService,
private $compile: ng.ICompileService,
private $transitions: any,
- private $log: ng.ILogService
+ private $log: ng.ILogService,
+ private $timeout: ng.ITimeoutService
) {
$transitions.onFinish({ to: '**' }, (transtion) => {
// wait for route transition to complete
@@ -79,6 +80,10 @@
const componentTag = `<${componentTagName} ${attr}>${transclude || ''}</${componentTagName}>`;
const element = this.$compile(componentTag)(scope);
+ this.$timeout(function() {
+ scope.$apply();
+ });
+
targetEl.append(element);
// store a reference for the element
diff --git a/src/app/core/services/keyboard-shortcut.ts b/src/app/core/services/keyboard-shortcut.ts
new file mode 100644
index 0000000..e649abf
--- /dev/null
+++ b/src/app/core/services/keyboard-shortcut.ts
@@ -0,0 +1,164 @@
+import * as $ from 'jquery';
+import * as _ from 'lodash';
+import {IXosSidePanelService} from '../side-panel/side-panel.service';
+
+export interface IXosKeyboardShortcutService {
+ keyMapping: IXosKeyboardShortcutMap;
+ registerKeyBinding(binding: IXosKeyboardShortcutBinding, target?: string);
+ setup(): void;
+}
+
+export interface IXosKeyboardShortcutMap {
+ global: IXosKeyboardShortcutBinding[];
+ view: IXosKeyboardShortcutBinding[];
+}
+
+export interface IXosKeyboardShortcutBinding {
+ key: string;
+ cb: any;
+ modifiers?: string[];
+ description?: string;
+ onInput?: boolean;
+}
+
+export class XosKeyboardShortcut implements IXosKeyboardShortcutService {
+ static $inject = ['$log', '$transitions', 'XosSidePanel'];
+ public keyMapping: IXosKeyboardShortcutMap = {
+ global: [],
+ view: []
+ };
+ public allowedModifiers: string[] = ['Meta', 'Alt', 'Shift', 'Control'];
+ public activeModifiers: string[] = [];
+
+ private toggleKeyBindingPanel = (): void => {
+ if (!this.isPanelOpen) {
+ this.XosSidePanel.injectComponent('xosKeyBindingPanel');
+ this.isPanelOpen = true;
+ }
+ else {
+ this.XosSidePanel.removeInjectedComponents();
+ this.isPanelOpen = false;
+ }
+ };
+
+ /* tslint:disable */
+ public baseBindings: IXosKeyboardShortcutBinding[] = [
+ {
+ key: '?',
+ description: 'Toggle Shortcut Panel',
+ cb: this.toggleKeyBindingPanel,
+ },
+ {
+ key: '/',
+ description: 'Toggle Shortcut Panel',
+ cb: this.toggleKeyBindingPanel,
+ },
+ {
+ key: 'Escape',
+ cb: (event) => {
+ // NOTE removing focus from input elements on Esc
+ event.target.blur();
+ },
+ onInput: true
+ }
+ ];
+ /* tslint:enable */
+
+ private isPanelOpen: boolean;
+
+ constructor(
+ private $log: ng.ILogService,
+ $transitions: any,
+ private XosSidePanel: IXosSidePanelService
+ ) {
+ this.keyMapping.global = this.keyMapping.global.concat(this.baseBindings);
+
+ $transitions.onStart({ to: '**' }, (transtion) => {
+ // delete view keys before that a new view is loaded
+ this.$log.info(`[XosKeyboardShortcut] Deleting view keys`);
+ this.keyMapping.view = [];
+ });
+ }
+
+
+ public setup(): void {
+ this.$log.info(`[XosKeyboardShortcut] Setup`);
+ $('body').on('keydown', (e) => {
+
+ let pressedKey = null;
+
+ if (e.key.length === 1 && e.key.match(/[a-z]/i) || String.fromCharCode(e.keyCode).toLowerCase().match(/[a-z]/i)) {
+ // alphabet letters found
+ pressedKey = String.fromCharCode(e.keyCode).toLowerCase();
+ }
+ else {
+ pressedKey = e.key;
+ }
+
+ if (this.allowedModifiers.indexOf(e.key) > -1) {
+ this.addActiveModifierKey(e.key);
+ return;
+ }
+
+ // NOTE e.key change if we are using some modifiers (eg: Alt) while getting the value from the keyCode works
+ const binding = this.findBindedShortcut(pressedKey);
+ if (angular.isDefined(binding) && angular.isFunction(binding.cb)) {
+ // NOTE disable binding if they come from an input or textarea
+ // if not different specified
+ const t = e.target.tagName.toLowerCase();
+ if ((t === 'input' || t === 'textarea') && !binding.onInput) {
+ return;
+ }
+ binding.cb(e);
+ e.preventDefault();
+ }
+ });
+
+ $('body').on('keyup', (e) => {
+ if (this.allowedModifiers.indexOf(e.key) > -1) {
+ this.removeActiveModifierKey(e.key);
+ return;
+ }
+ });
+ }
+
+ public registerKeyBinding(binding: IXosKeyboardShortcutBinding, target: string = 'view'): void {
+ // NOTE check for already taken binding (by key)
+ // NOTE check target is either 'view' or 'global'
+ this.$log.info(`[XosKeyboardShortcut] Registering binding for key: ${binding.key}`);
+ if (!_.find(this.keyMapping[target], {key: binding.key})) {
+ this.keyMapping[target].push(binding);
+ }
+ }
+
+ private addActiveModifierKey(key: string) {
+ if (this.activeModifiers.indexOf(key) === -1) {
+ this.activeModifiers.push(key);
+ }
+ }
+
+ private removeActiveModifierKey(key: string) {
+ _.remove(this.activeModifiers, k => k === key);
+ }
+
+ private findBindedShortcut(key: string): IXosKeyboardShortcutBinding {
+ // NOTE search for binding in the global map
+ let target = _.find(this.keyMapping.global, {key: key});
+
+ // NOTE if it is not there look in the view map
+ if (!angular.isDefined(target)) {
+ target = _.find(this.keyMapping.view, {key: key});
+ }
+
+
+ if (target && target.modifiers) {
+ // if not all the modifier keys for that binding are pressed
+ if (_.difference(target.modifiers, this.activeModifiers).length > 0) {
+ // do not match
+ return;
+ };
+ }
+ return target;
+ }
+
+}