from flask import Flask
from flask_socketio import SocketIO
from olympus.utils import debug, info
_socketio = None
_socketio_ready = False
[docs]def socketio():
return _socketio
[docs]def register_event(event, handler, namespace='/'):
"""Register a socketio event to a python handler
Parameters
----------
event: str
Name of the event
handler: call
Function to call when the event is fired
"""
return socketio().on(event, namespace)(handler)
_pending_bind = []
_pending_attr = []
[docs]def set_attribute(id, attribute, value):
"""Set the attribute of an element on the webpage
Parameters
----------
id: str
id of the DOM element
attribute: str
name of the attribute to set
value: json
new value of the attribute
"""
if not _socketio_ready:
_pending_bind.append((id, attribute, value))
debug(f'set_attribute {attribute} of {id}')
socketio().emit('set_attribute', dict(
id=id,
attribute=attribute,
value=value
))
[docs]def get_element_size(id, callback):
"""Get the size of an element inside the webpage
Parameters
----------
id: str
id of the DOM element
callback: Call
Function to call with the size information `{width: w, height: h}`
"""
debug(f'get_element_size of {id}')
socketio().emit('get_size', dict(
id=id
))
register_event(f'get_size_{id}', callback)
[docs]def bind(id, event, handler, attribute=None, property=None):
"""Bind an element event to a handler and return a property of an attribute of the element
Parameters
----------
id: str
id of the DOM element
event: str
Name of the event we are listening too.
The full list of supported events can be found `here <https://www.w3schools.com/jsref/dom_obj_event.asp>`_.
handler: call
function to callback when the event is fired
attribute: str
Attribute of the element to return
property: str
Property of the element to return
"""
if not _socketio_ready:
_pending_bind.append((id, event, handler, attribute, property))
debug(f'binding `{id}` with `{event}` to `{handler}`')
# ask javascript to listen to events for a particular kind of event on our element
socketio().emit('bind', {'id': id, 'event': event, 'attribute': attribute, 'property': property})
# when the event happen js will send us back the innerHTML of that element
register_event(f'bind_{event}_{id}', handler)
return
[docs]def handshake_event():
"""Called when socketIO connects to the server"""
global _socketio_ready, _pending_attr, _pending_bind
_socketio_ready = True
info('SocketIO connected')
if _pending_attr:
for arg in _pending_attr:
set_attribute(*arg)
_pending_attr = []
if _pending_bind:
for arg in _pending_bind:
bind(*arg)
_pending_bind = []
[docs]def disconnect_event():
"""Called when socketIO disconnects from the server"""
global _socketio_ready
_socketio_ready = False
info('SocketIO disconnected')
[docs]class Dashboard:
"""Dashboard entry point"""
def __init__(self, name=__name__, secret='__secret__'):
import os
self.app = Flask(name, static_folder=os.path.join(os.path.dirname(__file__), 'static'))
self.app.debug = True
self.app.config['SECRET_KEY'] = secret
self.socket = SocketIO(self.app)
self.routes = []
global _socketio
_socketio = self.socket
self.on_event('handshake', handshake_event)
self.on_event('disconnect', disconnect_event)
[docs] def run(self):
"""Run the flask App"""
return self.socket.run(self.app)
[docs] def add_page(self, page, route=None, header=None, **kwargs):
"""Add a new page to the dashboard
Parameters
----------
page: Page
page object
route: str
Route specification to reach the page
Will default to the page route if left undefined
header: str
HTML header to insert onto the page
"""
route = route or page.routes()
if not isinstance(route, list):
route = [route]
for r in route:
self.routes.append((r, type(page).__name__))
self.app.add_url_rule(r, type(page).__name__, page, **kwargs)
if header is not None:
page.header = header
[docs] def on_event(self, event, handler, namespace='/'):
"""Register an handler for a givent event"""
self.socket.on(event, namespace)(handler)