Skip to content

Commit

Permalink
markdown widget (#135)
Browse files Browse the repository at this point in the history
* markdown widget

* webpack in travis

* remove description from Layout

* support adding visuals to sidebar, in particular markdown

* unused imports

* refactor instatiation code

* fix style problems

* bump a couple packages

* document markdown try to improve progress doc wording

* improve add_sidebar docstring

* add caret to webpack version

* remove unnecessary space

* _jsbool to _component.jsbool
  • Loading branch information
jwkvam committed Jun 11, 2017
1 parent dc62fbd commit 74a9172
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 242 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ install:
- curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
- source ~/.nvm/nvm.sh
- nvm install node
- npm install -g webpack@2.5.1 yarn
- npm install -g webpack@2.6.1 yarn
- npm install -g eslint babel-eslint eslint-plugin-react
- make eslint
- sudo apt-get update
Expand Down
2 changes: 1 addition & 1 deletion bowtie/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Interactive dashboard toolkit."""

__version__ = '0.3.4-dev'
__version__ = '0.4.0-dev'

from bowtie._layout import Layout
from bowtie._command import command
Expand Down
30 changes: 30 additions & 0 deletions bowtie/_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# pylint: disable=redefined-builtin
from builtins import bytes

import string
import inspect
from functools import wraps
import json
Expand All @@ -34,6 +35,11 @@ def varname(variable):
return name


def jsbool(x):
"""Convert Python bool to Javascript bool."""
return repr(x).lower()


def json_conversion(obj):
"""Encode additional objects to JSON."""
try:
Expand Down Expand Up @@ -195,6 +201,14 @@ def __new__(mcs, name, parents, dct):
return super(_Maker, mcs).__new__(mcs, name, parents, dct)


class FormatDict(dict):
"""Dict to replace missing keys."""

def __missing__(self, key):
"""Replace missing key with "{key"}"."""
return "{" + key + "}"


# pylint: disable=too-few-public-methods
class Component(with_metaclass(_Maker, object)):
"""Abstract class for all components.
Expand All @@ -216,3 +230,19 @@ def __init__(self):
# was surprised that didn't work
self._uuid = Component._next_uuid()
super(Component, self).__init__()
self._tagbase = " socket={{socket}} uuid={{'{uuid}'}} />".format(uuid=self._uuid)
self._tag = '<' + self._COMPONENT
if self._ATTRS:
self._tag += ' ' + self._ATTRS

@staticmethod
def _insert(wrap, tag):
"""Insert the component tag into the wrapper html.
This ignores other tags already created like ``{socket}``.
https://stackoverflow.com/a/11284026/744520
"""
formatter = string.Formatter()
mapping = FormatDict(component=tag)
return formatter.vformat(wrap, (), mapping)
50 changes: 19 additions & 31 deletions bowtie/_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,15 @@
from collections import namedtuple, defaultdict, OrderedDict
from subprocess import Popen

from flask import Markup
from jinja2 import Environment, FileSystemLoader
from markdown import markdown

from bowtie._compat import makedirs
from bowtie.control import _Controller
from bowtie.visual import _Visual
from bowtie._component import Component


_Import = namedtuple('_Import', ['module', 'component'])
_Control = namedtuple('_Control', ['instantiate', 'caption'])
_Schedule = namedtuple('_Control', ['seconds', 'function'])
_Schedule = namedtuple('_Schedule', ['seconds', 'function'])


class YarnError(Exception):
Expand Down Expand Up @@ -179,8 +176,8 @@ class Layout(object):
"""Core class to layout, connect, build a Bowtie app."""

def __init__(self, rows=1, columns=1, sidebar=True,
title='Bowtie App', description='Bowtie App\n---',
basic_auth=False, username='username', password='password',
title='Bowtie App', basic_auth=False,
username='username', password='password',
background_color='White', directory='build',
host='0.0.0.0', port=9991, socketio='', debug=False):
"""Create a Bowtie App.
Expand All @@ -195,8 +192,6 @@ def __init__(self, rows=1, columns=1, sidebar=True,
Enable a sidebar for control widgets.
title : str, optional
Title of the HTML.
description : str, optional
Describe the app in Markdown, inserted in control pane.
basic_auth : bool, optional
Enable basic authentication.
username : str, optional
Expand All @@ -221,7 +216,6 @@ def __init__(self, rows=1, columns=1, sidebar=True,
self.basic_auth = basic_auth
self.controllers = []
self.debug = debug
self.description = Markup(markdown(description))
self.directory = directory
self.functions = []
self.host = host
Expand Down Expand Up @@ -264,6 +258,8 @@ def add(self, widget, row_start=None, column_start=None,
Ending column for the widget.
"""
assert isinstance(widget, Component)

for index in [row_start, row_end]:
if index is not None and not 0 <= index < len(self.rows):
raise GridIndexError('Invalid Row Index')
Expand Down Expand Up @@ -316,27 +312,27 @@ def add(self, widget, row_start=None, column_start=None,
self.widgets.append(widget)
self.spans.append(span)

def add_sidebar(self, control):
"""Add a controller to the sidebar.
def add_sidebar(self, widget):
"""Add a widget to the sidebar.
Parameters
----------
control : bowtie._Controller
A Bowtie controller instance.
widget : bowtie._Component
Add this widget to the sidebar, it will be appended to the end.
"""
if not self.sidebar:
raise NoSidebarError('Set sidebar=True if you want to use the sidebar.')

assert isinstance(control, _Controller)
assert isinstance(widget, Component)

# pylint: disable=protected-access
self.packages.add(control._PACKAGE)
self.templates.add(control._TEMPLATE)
self.imports.add(_Import(component=control._COMPONENT,
module=control._TEMPLATE[:control._TEMPLATE.find('.')]))
self.controllers.append(_Control(instantiate=control._instantiate,
caption=control.caption))
self.packages.add(widget._PACKAGE)
self.templates.add(widget._TEMPLATE)
self.imports.add(_Import(component=widget._COMPONENT,
module=widget._TEMPLATE[:widget._TEMPLATE.find('.')]))
self.controllers.append(_Control(instantiate=widget._instantiate,
caption=getattr(widget, 'caption', None)))

def respond(self, pager, func):
"""Call a function in response to a page.
Expand Down Expand Up @@ -467,15 +463,8 @@ def build(self):
template_src = path.join(file_dir, 'src', template)
shutil.copy(template_src, app)

for i, widget in enumerate(self.widgets):
# pylint: disable=protected-access
winst = widget._instantiate
if isinstance(widget, _Visual):
progress = widget.progress._instantiate()
close_progress = '</AntProgress>'
self.widgets[i] = ''.join((progress, winst, close_progress))
else:
self.widgets[i] = winst
# pylint: disable=protected-access
self.widgets = [w._instantiate for w in self.widgets]

columns = []
if self.sidebar:
Expand All @@ -485,7 +474,6 @@ def build(self):
with open(path.join(app, react.name[:-3]), 'w') as f:
f.write(
react.render(
description=self.description,
socketio=self.socketio,
sidebar=self.sidebar,
columns=columns,
Expand Down
66 changes: 32 additions & 34 deletions bowtie/_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,45 @@


class Progress(Component):
"""A progress indicator.
"""Circle progress indicator."""

This component is used by all visual components and
is not meant to be used alone.
_TEMPLATE = 'progress.jsx'
_COMPONENT = 'AntProgress'
_PACKAGE = None
_ATTRS = None

By default, it is not visible.
It is an opt-in feature and you can happily use Bowtie
without using the progress indicators at all.
def __init__(self):
"""Create a progress indicator.
It is useful for indicating progress to the user for long-running processes.
It can be accessed through the ``.progress`` accessor.
This component is used by all visual components.
It is not meant to be used alone.
Examples
--------
>>> plotly = Plotly()
>>> def callback(x):
>>> plotly.progress.do_visible(True)
>>> plotly.progress.do_percent(0)
>>> compute1()
>>> plotly.progress.do_inc(50)
>>> compute2()
>>> plotly.progress.do_visible(False)
By default, it is not visible.
It is an opt-in feature and you can happily use Bowtie
without using the progress indicators at all.
References
----------
https://ant.design/components/progress/
It is useful for indicating progress to the user for long-running processes.
It can be accessed through the ``.progress`` accessor.
"""
Examples
--------
>>> plotly = Plotly()
>>> def callback(x):
>>> plotly.progress.do_visible(True)
>>> plotly.progress.do_percent(0)
>>> compute1()
>>> plotly.progress.do_inc(50)
>>> compute2()
>>> plotly.progress.do_visible(False)
_TEMPLATE = 'progress.jsx'
_COMPONENT = 'AntProgress'
_PACKAGE = None
_TAG = ('<AntProgress '
'socket={{socket}} '
'uuid={{{uuid}}} '
'>')

def _instantiate(self):
return self._TAG.format(
uuid="'{}'".format(self._uuid)
)
References
----------
https://ant.design/components/progress/
"""
super(Progress, self).__init__()
self._tagbase = self._tagbase[:-3] + '>'
self._tags = '<' + self._COMPONENT + self._tagbase, '</AntProgress>'

# pylint: disable=no-self-use
def do_percent(self, percent):
Expand Down

0 comments on commit 74a9172

Please sign in to comment.