Created
January 8, 2017 14:28
-
-
Save andrewplummer/52c7a38316303456d13db47e2b732197 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Requires bufferWithMap custom operator (below). | |
function KeyEventManager (delay) { | |
this.shortcutsBySeqId = {}; | |
this.partialSequenceCounts = {}; | |
this.delay = delay; | |
} | |
KeyEventManager.prototype.connectObservable = function(observable) { | |
this.setupObservable(observable); | |
}; | |
KeyEventManager.prototype.fireKeyEvent = function(evt) { | |
if (!this.fakeKeyEvents) { | |
this.fakeKeyEvents = new Rx.Subject(); | |
this.setupObservable(this.fakeKeyEvents); | |
} | |
this.fakeKeyEvents.onNext(evt); | |
}; | |
KeyEventManager.prototype.addKeyShortcut = function(keyDescriptors, handler) { | |
let shortcut = new KeyShortcut(keyDescriptors, handler); | |
// Only map key commands if the shortcut does not already | |
// exist, otherwise, allow to overwrite and finish. | |
if (!this.shortcutsBySeqId[shortcut.id]) { | |
shortcut.keys.slice(0, -1).reduce((keys, key) => { | |
keys.push(key); | |
let sequence = new KeySequence(keys) | |
this.addPartialSequenceCount(sequence); | |
return keys; | |
}, []); | |
} | |
this.shortcutsBySeqId[shortcut.sequence.id] = shortcut; | |
}; | |
KeyEventManager.prototype.getPartialSequenceCount = function(sequence) { | |
return this.partialSequenceCounts[sequence.id] || 0; | |
}; | |
KeyEventManager.prototype.addPartialSequenceCount = function(sequence) { | |
this.partialSequenceCounts[sequence.id] = this.getPartialSequenceCount(sequence) + 1; | |
}; | |
KeyEventManager.prototype.setupObservable = function(observable) { | |
observable | |
.map(evt => Key.fromEvent(evt, true)) | |
.bufferWithMap(sequence => { | |
if (this.getPartialSequenceCount(sequence) > 0) { | |
return Rx.Observable.timer(this.delay); | |
} | |
return true; | |
}, (keys) => { | |
return new KeySequence(keys); | |
}) | |
.map(sequence => this.shortcutsBySeqId[sequence.id]) | |
.filter(shortcut => shortcut) | |
.subscribe(shortcut => { | |
shortcut.handler(); | |
}); | |
}; | |
function KeyShortcut (keyDescriptors, handler) { | |
this.keys = keyDescriptors.map(d => Key.fromDescriptor(d)); | |
this.handler = handler; | |
this.sequence = new KeySequence(this.keys); | |
} | |
function KeySequence(keys) { | |
this.id = keys.map(key => key.id).join('|'); | |
} | |
function Key (id) { | |
this.id = id; | |
} | |
Key.META_KEYS = [ | |
{ name: 'ctrl', eventName: 'ctrlKey', mask: 0x1 }, | |
{ name: 'shift', eventName: 'shiftKey', mask: 0x2 }, | |
{ name: 'option', eventName: 'metaKey', mask: 0x4 }, | |
{ name: 'command', eventName: 'altKey', mask: 0x8 } | |
]; | |
Key.fromDescriptor = function(descriptor) { | |
return new Key(Key.getId(descriptor)); | |
}; | |
Key.fromEvent = function(evt) { | |
return new Key(Key.getId(evt, true)); | |
}; | |
Key.getId = function(obj, isEvent) { | |
let code = obj[isEvent ? 'keyCode' : 'code'], mask = 0; | |
for (let i = 0, key; key = Key.META_KEYS[i]; i++) { | |
let type = key[isEvent ? 'eventName' : 'name'] | |
mask = mask | obj[type] * key.mask; | |
} | |
return code.toString() + mask.toString(); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function KeyEventManager (delay) { | |
this.shortcutsBySeqId = {}; | |
this.partialSequenceCounts = {}; | |
this.delay = delay; | |
} | |
KeyEventManager.prototype.connectObservable = function(observable) { | |
this.setupObservable(observable); | |
}; | |
KeyEventManager.prototype.fireKeyEvent = function(evt) { | |
if (!this.fakeKeyEvents) { | |
this.fakeKeyEvents = new Rx.Subject(); | |
this.setupObservable(this.fakeKeyEvents); | |
} | |
this.fakeKeyEvents.onNext(evt); | |
}; | |
KeyEventManager.prototype.addKeyShortcut = function(keyDescriptors, handler) { | |
let shortcut = new KeyShortcut(keyDescriptors, handler); | |
// Only map key commands if the shortcut does not already | |
// exist, otherwise, allow to overwrite and finish. | |
if (!this.shortcutsBySeqId[shortcut.id]) { | |
shortcut.keys.slice(0, -1).reduce((keys, key) => { | |
keys.push(key); | |
let sequence = new KeySequence(keys); | |
this.addPartialSequenceCount(sequence); | |
return keys; | |
}, []); | |
} | |
this.shortcutsBySeqId[shortcut.sequence.id] = shortcut; | |
}; | |
KeyEventManager.prototype.getPartialSequenceCount = function(sequence) { | |
return this.partialSequenceCounts[sequence.id] || 0; | |
}; | |
KeyEventManager.prototype.addPartialSequenceCount = function(sequence) { | |
this.partialSequenceCounts[sequence.id] = this.getPartialSequenceCount(sequence) + 1; | |
}; | |
KeyEventManager.prototype.setupObservable = function(observable) { | |
let buffer = []; | |
observable | |
.map(evt => { | |
let key = Key.fromEvent(evt, true); | |
buffer.push(key); | |
return new KeySequence(buffer); | |
}) | |
.switchMap(sequence => { | |
if (this.getPartialSequenceCount(sequence) > 0) { | |
return Rx.Observable.timer(this.delay).map(() => { | |
buffer = []; | |
return sequence; | |
}); | |
} else { | |
buffer = []; | |
return Rx.Observable.just(sequence); | |
} | |
}) | |
.map(sequence => { | |
return this.shortcutsBySeqId[sequence.id]; | |
}) | |
.filter(shortcut => shortcut) | |
.subscribe(shortcut => { | |
shortcut.handler(); | |
}); | |
}; | |
function KeyShortcut (keyDescriptors, handler) { | |
this.keys = keyDescriptors.map(d => Key.fromDescriptor(d)); | |
this.handler = handler; | |
this.sequence = new KeySequence(this.keys); | |
} | |
function KeySequence(keys) { | |
this.id = keys.map(key => key.id).join('|'); | |
} | |
function Key (id) { | |
this.id = id; | |
} | |
Key.META_KEYS = [ | |
{ name: 'ctrl', eventName: 'ctrlKey', mask: 0x1 }, | |
{ name: 'shift', eventName: 'shiftKey', mask: 0x2 }, | |
{ name: 'option', eventName: 'metaKey', mask: 0x4 }, | |
{ name: 'command', eventName: 'altKey', mask: 0x8 } | |
]; | |
Key.fromDescriptor = function(descriptor) { | |
return new Key(Key.getId(descriptor)); | |
}; | |
Key.fromEvent = function(evt) { | |
return new Key(Key.getId(evt, true)); | |
}; | |
Key.getId = function(obj, isEvent) { | |
let code = obj[isEvent ? 'keyCode' : 'code'], mask = 0; | |
for (let i = 0, key; key = Key.META_KEYS[i]; i++) { | |
let type = key[isEvent ? 'eventName' : 'name']; | |
mask = mask | obj[type] * key.mask; | |
} | |
return code.toString() + mask.toString(); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function KeyEventManager (delay) { | |
this.shortcuts = new KeyNode(); | |
this.currentNode = this.shortcuts; | |
this.delay = delay || KeyEventManager.DELAY; | |
this.timerId = null; | |
} | |
KeyEventManager.DELAY = 500; | |
KeyEventManager.prototype.addKeyShortcut = function(keyDescriptors, handler) { | |
var shortcut = new KeyShortcut(keyDescriptors, handler); | |
shortcut.keys.reduce((parentNode, key, i, arr) => { | |
var node = parentNode.createChildNode(key.id); | |
if (i === arr.length - 1) { | |
return node.addHandler(handler); | |
} | |
return node; | |
}, this.shortcuts); | |
}; | |
KeyEventManager.prototype.fireKeyEvent = function(evt) { | |
var node = this.currentNode.getChildNode(Key.getId(evt, true)); | |
if (!node) { | |
// No shortcut exists, so do nothing. | |
return; | |
} | |
if (node.childNodeCount > 0) { | |
this.currentNode = node; | |
this.waitForInput(); | |
} else if (node.handler) { | |
this.fireHandler(node); | |
} | |
}; | |
KeyEventManager.prototype.clearKeyEvents = function() { | |
clearTimeout(this.timerId); | |
this.timerId = null; | |
this.currentNode = this.shortcuts; | |
}; | |
KeyEventManager.prototype.fireHandler = function(node) { | |
if (node.handler) { | |
node.handler(); | |
} | |
this.clearKeyEvents(); | |
}; | |
KeyEventManager.prototype.waitForInput = function() { | |
if (!this.timerId) { | |
this.timerId = setTimeout(this.onTimeout.bind(this), this.delay); | |
} | |
}; | |
KeyEventManager.prototype.onTimeout = function() { | |
this.fireHandler(this.currentNode); | |
}; | |
function KeyNode() { | |
this.handler = null; | |
this.childNodes = {}; | |
this.childNodeCount = 0; | |
} | |
KeyNode.prototype.addHandler = function(handler) { | |
this.handler = handler; | |
}; | |
KeyNode.prototype.getChildNode = function(id) { | |
return this.childNodes[id]; | |
}; | |
KeyNode.prototype.createChildNode = function(id) { | |
var childNode = this.childNodes[id]; | |
if (!childNode) { | |
childNode = new KeyNode(); | |
this.childNodes[id] = childNode; | |
this.childNodeCount++; | |
} | |
return childNode; | |
}; | |
function KeyShortcut (keyDescriptors, handler) { | |
this.keys = keyDescriptors.map(d => Key.fromDescriptor(d)); | |
this.handler = handler; | |
} | |
function Key (id) { | |
this.id = id; | |
} | |
Key.META_KEYS = [ | |
{ name: 'ctrl', eventName: 'ctrlKey', mask: 0x1 }, | |
{ name: 'shift', eventName: 'shiftKey', mask: 0x2 }, | |
{ name: 'option', eventName: 'metaKey', mask: 0x4 }, | |
{ name: 'command', eventName: 'altKey', mask: 0x8 } | |
]; | |
Key.fromDescriptor = function(descriptor) { | |
return new Key(Key.getId(descriptor)); | |
}; | |
Key.fromEvent = function(evt) { | |
return new Key(Key.getId(evt, true)); | |
}; | |
Key.getId = function(obj, isEvent) { | |
let code = obj[isEvent ? 'keyCode' : 'code'], mask = 0; | |
for (let i = 0, key; key = Key.META_KEYS[i]; i++) { | |
let type = key[isEvent ? 'eventName' : 'name'] | |
mask = mask | obj[type] * key.mask; | |
} | |
return code.toString() + mask.toString(); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
// Buffer that handles returning observables. | |
function bufferWithMapAndObservable(bufferFn, mapFn) { | |
let buffer = []; | |
function flushBufferWith(mapped) { | |
buffer = []; | |
return mapped; | |
} | |
return this.switchMap(item => { | |
buffer.push(item); | |
let mapped = mapFn(buffer) | |
let result = bufferFn(mapped); | |
if (Rx.Observable.isObservable(result)) { | |
return result.map(() => flushBufferWith(mapped)) | |
} else if (result === true) { | |
// Flush buffer and return mapped. | |
return Rx.Observable.just(flushBufferWith(mapped)); | |
} else { | |
// Return empty and retain buffer. | |
return Rx.Observable.empty(); | |
} | |
}); | |
} | |
Rx.Observable.prototype.bufferWithMap = bufferWithMapAndObservable; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment