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
import forest.db.control
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 window.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 window.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))