1- import React from 'react' ;
1+ import React , { KeyboardEvent } from 'react' ;
22import { connect } from 'react-redux' ;
33import { createSelector } from 'reselect' ;
4- import Mousetrap from 'mousetrap' ;
5- import 'mousetrap/plugins/global-bind/mousetrap-global-bind.min' ;
6- import 'mousetrap/plugins/pause/mousetrap-pause.min' ;
74
8- import { KEYBINDINGS } from 'app/store/preferences/keybindings' ;
5+ import { KEYBINDINGS , normalizeKey } from 'app/store/preferences/keybindings' ;
96import { keybindingsSelector } from 'app/store/preferences/selectors' ;
107import { modalSelector } from 'app/store/modal/selectors' ;
118
@@ -17,62 +14,138 @@ type Props = {
1714 action : Function ,
1815 } ,
1916 } ,
17+ bindingStrings : {
18+ [ combo : string ] : string ,
19+ } ,
2020 dispatch : Function ,
2121 modalOpen : boolean ,
2222} ;
2323
2424const mapStateToProps = createSelector (
2525 keybindingsSelector ,
26- ( ) => KEYBINDINGS ,
2726 modalSelector ,
28- ( userKeybindings , defaultBindings , modal ) => {
29- const newBindings = { ...defaultBindings } ;
27+ ( userKeybindings , modal ) => {
28+ const newBindings = { ...KEYBINDINGS } ;
3029 Object . keys ( userKeybindings ) . forEach ( key => {
3130 newBindings [ key ] . bindings = userKeybindings [ key ] ;
3231 } ) ;
3332
34- return { keybindings : newBindings , modalOpen : modal . open } ;
33+ const bindingStrings = { } ;
34+
35+ Object . keys ( newBindings ) . forEach ( key => {
36+ const binding = newBindings [ key ] ;
37+
38+ if ( binding . bindings [ 0 ] ) {
39+ const bindingString = binding . bindings [ 0 ] . join ( '' ) ;
40+
41+ if ( binding . bindings [ 1 ] && binding . bindings [ 1 ] . length ) {
42+ bindingStrings [ bindingString ] = {
43+ [ binding . bindings [ 1 ] . join ( '' ) ] : key ,
44+ } ;
45+ } else {
46+ bindingStrings [ bindingString ] = key ;
47+ }
48+ }
49+ } ) ;
50+
51+ return { keybindings : newBindings , bindingStrings, modalOpen : modal . open } ;
3552 }
3653) ;
3754const mapDispatchToProps = dispatch => ( {
3855 dispatch,
3956} ) ;
4057class KeybindingManager extends React . Component < Props > {
41- setBindings = ( ) => {
42- Mousetrap . reset ( ) ;
43- Object . keys ( this . props . keybindings ) . forEach ( k => {
44- const { bindings, action } = this . props . keybindings [ k ] ; // eslint-disable-line
45- const stroke =
46- bindings [ 0 ] . join ( '+' ) +
47- ( bindings [ 1 ] && bindings [ 1 ] . length ? ' ' + bindings [ 1 ] . join ( '+' ) : '' ) ;
48-
49- Mousetrap . bindGlobal ( stroke . toLowerCase ( ) , ( ) => {
50- this . props . dispatch ( action ( { id : this . props . sandboxId } ) ) ;
51- return false ;
52- } ) ;
53- } ) ;
58+ pressedComboKeys = [ ] ;
59+ pressedComboMetaKeys = [ ] ;
60+ checkedStrokes = this . props . bindingStrings ;
61+
62+ removeFromPressedComboKeys = ( key : string ) => {
63+ this . pressedComboKeys = this . pressedComboKeys . filter ( x => x !== key ) ;
5464 } ;
5565
56- shouldComponentUpdate ( nextProps : Props ) {
57- if ( nextProps . modalOpen ) {
58- Mousetrap . pause ( ) ;
59- } else {
60- Mousetrap . unpause ( ) ;
66+ handleKeyDown = ( e : KeyboardEvent ) => {
67+ if ( this . props . modalOpen ) {
68+ return ;
6169 }
6270
63- return nextProps . keybindings !== this . props . keybindings ;
71+ const key = normalizeKey ( e ) ;
72+
73+ if ( this . pressedComboKeys . indexOf ( key ) === - 1 ) {
74+ this . pressedComboKeys . push ( key ) ;
75+
76+ // if the meta key is pressed
77+ // register the keyCode also in seperate array
78+ if ( e . metaKey ) {
79+ this . pressedComboMetaKeys . push ( key ) ;
80+ }
81+ }
82+
83+ // check match
84+ const match = this . checkCombosForPressedKeys ( ) ;
85+
86+ if ( match != null ) {
87+ e . preventDefault ( ) ;
88+ e . stopPropagation ( ) ;
89+ }
90+
91+ if ( typeof match === 'string' ) {
92+ this . pressedComboKeys = [ ] ;
93+ this . pressedComboMetaKeys = [ ] ;
94+ this . checkedStrokes = this . props . bindingStrings ;
95+
96+ this . props . dispatch (
97+ this . props . keybindings [ match ] . action ( {
98+ id : this . props . sandboxId ,
99+ } )
100+ ) ;
101+ } else if ( typeof match === 'object' ) {
102+ this . checkedStrokes = match ;
103+
104+ if ( this . timeout ) {
105+ clearTimeout ( this . timeout ) ;
106+ }
107+
108+ this . timeout = setTimeout ( ( ) => {
109+ this . checkedStrokes = this . props . bindingStrings ;
110+ } , 300 ) ;
111+ }
112+ } ;
113+
114+ checkCombosForPressedKeys ( ) {
115+ const pressedComboKeysStr = this . pressedComboKeys . join ( '' ) ;
116+
117+ return this . checkedStrokes [ pressedComboKeysStr ] ;
64118 }
65119
120+ handleKeyUp = ( e : KeyboardEvent ) => {
121+ const key = normalizeKey ( e ) ;
122+
123+ this . removeFromPressedComboKeys ( key ) ;
124+ if ( this . pressedComboMetaKeys . length > 0 ) {
125+ // if there are keys that were pressed while
126+ // the meta key was pressed flush them
127+ // because the keyup wasn't triggered for them
128+ // @see http:// stackoverflow.com/questions/27380018/when-cmd-key-is-kept-pressed-keyup-is-not-triggered-for-any-other-key
129+
130+ this . pressedComboMetaKeys . forEach ( metaKey =>
131+ this . removeFromPressedComboKeys ( metaKey )
132+ ) ;
133+ this . pressedComboMetaKeys = [ ] ;
134+ }
135+ } ;
136+
66137 componentWillMount ( ) {
67- this . setBindings ( ) ;
138+ document . addEventListener ( 'keydown' , this . handleKeyDown ) ;
139+ document . addEventListener ( 'keyup' , this . handleKeyUp ) ;
68140 }
69141
70- componentDidUpdate ( ) {
71- this . setBindings ( ) ;
142+ componentWillUnmount ( ) {
143+ document . removeEventListener ( 'keydown' , this . handleKeyDown ) ;
144+ document . removeEventListener ( 'keyup' , this . handleKeyUp ) ;
72145 }
73146
74- componentWillUnmount ( ) {
75- Mousetrap . reset ( ) ;
147+ componentDidUpdate ( ) {
148+ this . checkedStrokes = this . props . bindingStrings ;
76149 }
77150
78151 render ( ) {
0 commit comments