Source code for forest.keys

"""
Key press interaction
---------------------

This module provides a :class:`KeyPress` observable to
enable server side Python functions to subscribe to
key events.

In addtion there is also a middleware function, :meth:`navigate`,
that maps key actions to navigation actions, e.g.
`next_valid_time()` etc.

.. autoclass:: KeyPress
    :members:

.. autofunction:: navigate

.. autofunction:: press

"""
import bokeh.models
from forest import db
from forest.observe import Observable
from forest.export import export


__all__ = []


KEY_PRESS = "KEY_PRESS"


[docs]def press(code): """Key press action creator :param code: str representing browser event.code :returns: dict representing action """ return { "kind": KEY_PRESS, "payload": { "code": code } }
[docs]@export class KeyPress(Observable): """Key press server-side observable To add this to an existing `document`, add the hidden_button to the document and update `templates/index.html` to click the button on page load >>> key_press = KeyPress() >>> document.add_root(key_press.hidden_button) To observe the stream of actions generated by user key press events simply register a function via the `subscribe` method >>> key_press.subscribe(print) .. note:: KeyPress.hidden_button must be added to the document to allow JS hack to initialise callbacks """ def __init__(self): self.source = bokeh.models.ColumnDataSource({ 'keys': [] }) self.source.on_change('data', self._on_change) custom_js = bokeh.models.CustomJS(args=dict(source=self.source), code=""" if (typeof keyPressOn === 'undefined') { let interval = 150 // Key hold is about 30ms, double-click is about 200ms let throttle = function(callback, miliseconds) { var waiting = false return function() { if (!waiting) { callback.apply(null, arguments) waiting = true setTimeout(function() { waiting = false } , miliseconds) } } } let onkeydown = function(e) { let keys = source.data['keys'] keys.push(e.code) source.data = { 'keys': keys } source.change.emit() } document.onkeydown = throttle(onkeydown, interval) // Global to prevent multiple onkeydown callbacks keyPressOn = true } """) self.hidden_button = bokeh.models.Button( css_classes=['keypress-hidden-btn']) self.hidden_button.js_on_click(custom_js) super().__init__() def _on_change(self, attr, old, new): code = self.source.data['keys'][-1] self.notify(press(code))