blob: e649abf7c6564addc60172106693921802818108 [file] [log] [blame]
Matteo Scandolo5053cbe2017-01-31 17:37:56 -08001import * as $ from 'jquery';
2import * as _ from 'lodash';
3import {IXosSidePanelService} from '../side-panel/side-panel.service';
4
5export interface IXosKeyboardShortcutService {
6 keyMapping: IXosKeyboardShortcutMap;
7 registerKeyBinding(binding: IXosKeyboardShortcutBinding, target?: string);
8 setup(): void;
9}
10
11export interface IXosKeyboardShortcutMap {
12 global: IXosKeyboardShortcutBinding[];
13 view: IXosKeyboardShortcutBinding[];
14}
15
16export interface IXosKeyboardShortcutBinding {
17 key: string;
18 cb: any;
19 modifiers?: string[];
20 description?: string;
21 onInput?: boolean;
22}
23
24export class XosKeyboardShortcut implements IXosKeyboardShortcutService {
25 static $inject = ['$log', '$transitions', 'XosSidePanel'];
26 public keyMapping: IXosKeyboardShortcutMap = {
27 global: [],
28 view: []
29 };
30 public allowedModifiers: string[] = ['Meta', 'Alt', 'Shift', 'Control'];
31 public activeModifiers: string[] = [];
32
33 private toggleKeyBindingPanel = (): void => {
34 if (!this.isPanelOpen) {
35 this.XosSidePanel.injectComponent('xosKeyBindingPanel');
36 this.isPanelOpen = true;
37 }
38 else {
39 this.XosSidePanel.removeInjectedComponents();
40 this.isPanelOpen = false;
41 }
42 };
43
44 /* tslint:disable */
45 public baseBindings: IXosKeyboardShortcutBinding[] = [
46 {
47 key: '?',
48 description: 'Toggle Shortcut Panel',
49 cb: this.toggleKeyBindingPanel,
50 },
51 {
52 key: '/',
53 description: 'Toggle Shortcut Panel',
54 cb: this.toggleKeyBindingPanel,
55 },
56 {
57 key: 'Escape',
58 cb: (event) => {
59 // NOTE removing focus from input elements on Esc
60 event.target.blur();
61 },
62 onInput: true
63 }
64 ];
65 /* tslint:enable */
66
67 private isPanelOpen: boolean;
68
69 constructor(
70 private $log: ng.ILogService,
71 $transitions: any,
72 private XosSidePanel: IXosSidePanelService
73 ) {
74 this.keyMapping.global = this.keyMapping.global.concat(this.baseBindings);
75
76 $transitions.onStart({ to: '**' }, (transtion) => {
77 // delete view keys before that a new view is loaded
78 this.$log.info(`[XosKeyboardShortcut] Deleting view keys`);
79 this.keyMapping.view = [];
80 });
81 }
82
83
84 public setup(): void {
85 this.$log.info(`[XosKeyboardShortcut] Setup`);
86 $('body').on('keydown', (e) => {
87
88 let pressedKey = null;
89
90 if (e.key.length === 1 && e.key.match(/[a-z]/i) || String.fromCharCode(e.keyCode).toLowerCase().match(/[a-z]/i)) {
91 // alphabet letters found
92 pressedKey = String.fromCharCode(e.keyCode).toLowerCase();
93 }
94 else {
95 pressedKey = e.key;
96 }
97
98 if (this.allowedModifiers.indexOf(e.key) > -1) {
99 this.addActiveModifierKey(e.key);
100 return;
101 }
102
103 // NOTE e.key change if we are using some modifiers (eg: Alt) while getting the value from the keyCode works
104 const binding = this.findBindedShortcut(pressedKey);
105 if (angular.isDefined(binding) && angular.isFunction(binding.cb)) {
106 // NOTE disable binding if they come from an input or textarea
107 // if not different specified
108 const t = e.target.tagName.toLowerCase();
109 if ((t === 'input' || t === 'textarea') && !binding.onInput) {
110 return;
111 }
112 binding.cb(e);
113 e.preventDefault();
114 }
115 });
116
117 $('body').on('keyup', (e) => {
118 if (this.allowedModifiers.indexOf(e.key) > -1) {
119 this.removeActiveModifierKey(e.key);
120 return;
121 }
122 });
123 }
124
125 public registerKeyBinding(binding: IXosKeyboardShortcutBinding, target: string = 'view'): void {
126 // NOTE check for already taken binding (by key)
127 // NOTE check target is either 'view' or 'global'
128 this.$log.info(`[XosKeyboardShortcut] Registering binding for key: ${binding.key}`);
129 if (!_.find(this.keyMapping[target], {key: binding.key})) {
130 this.keyMapping[target].push(binding);
131 }
132 }
133
134 private addActiveModifierKey(key: string) {
135 if (this.activeModifiers.indexOf(key) === -1) {
136 this.activeModifiers.push(key);
137 }
138 }
139
140 private removeActiveModifierKey(key: string) {
141 _.remove(this.activeModifiers, k => k === key);
142 }
143
144 private findBindedShortcut(key: string): IXosKeyboardShortcutBinding {
145 // NOTE search for binding in the global map
146 let target = _.find(this.keyMapping.global, {key: key});
147
148 // NOTE if it is not there look in the view map
149 if (!angular.isDefined(target)) {
150 target = _.find(this.keyMapping.view, {key: key});
151 }
152
153
154 if (target && target.modifiers) {
155 // if not all the modifier keys for that binding are pressed
156 if (_.difference(target.modifiers, this.activeModifiers).length > 0) {
157 // do not match
158 return;
159 };
160 }
161 return target;
162 }
163
164}