Skip to content

Commit

Permalink
refactor(templates): server and notebook templates in the same direct…
Browse files Browse the repository at this point in the history
…ory, and add subdirectory to search path
  • Loading branch information
maartenbreddels committed Nov 28, 2019
1 parent c83e0c4 commit 7fe3aa9
Show file tree
Hide file tree
Showing 35 changed files with 435 additions and 225 deletions.
7 changes: 2 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ config.rst

package-lock.json

share/jupyter/voila/templates/nbconvert/static/index.css
share/jupyter/voila/templates/nbconvert/static/theme-light.css
share/jupyter/voila/templates/nbconvert/static/theme-dark.css
share/jupyter/voila/templates/default/static/*voila.js
share/jupyter/voila/templates/default/static/*[woff|woff2|eot|svg]
share/jupyter/voila/templates/classic/static/*voila.js
share/jupyter/voila/templates/classic/static/*[woff|woff2|eot|svg]

js/lib/

Expand Down
2 changes: 1 addition & 1 deletion js/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var rules = [
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, use: 'url-loader?limit=10000&mimetype=image/svg+xml&publicPath=/voila/static/' }
]

var distRoot = path.resolve(__dirname, '..', 'share', 'jupyter', 'voila', 'templates', 'default', 'static')
var distRoot = path.resolve(__dirname, '..', 'share', 'jupyter', 'templates', 'voila', 'default', 'resources')

module.exports = [
{
Expand Down
95 changes: 7 additions & 88 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class NPM(Command):

node_modules = os.path.join(node_root, 'node_modules')

template_root = os.path.join(here, 'share', 'jupyter', 'voila', 'templates', 'default', 'static')
template_root = os.path.join(here, 'share', 'jupyter', 'voila', 'templates', 'classic', 'static')
targets = [
os.path.join(template_root, 'voila.js')
]
Expand Down Expand Up @@ -234,86 +234,6 @@ def run(self):
update_package_data(self.distribution)


jupyterlab_css_version = '0.1.0'
css_url = "https://unpkg.com/@jupyterlab/nbconvert-css@%s/style/index.css" % jupyterlab_css_version

theme_light_version = '0.19.1'
theme_light_url = "https://unpkg.com/@jupyterlab/theme-light-extension@%s/static/embed.css" % theme_light_version

theme_dark_version = '0.19.1'
theme_dark_url = "https://unpkg.com/@jupyterlab/theme-dark-extension@%s/static/embed.css" % theme_dark_version


class FetchCSS(Command):
description = "Fetch Notebook CSS from CDN"
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def _download(self, url):
try:
return urlopen(url).read()
except Exception as e:
if 'ssl' in str(e).lower():
try:
import pycurl # noqa
except ImportError:
print("Failed, try again after installing PycURL with `pip install pycurl` to avoid outdated SSL.", file=sys.stderr)
raise e
else:
print("Failed, trying again with PycURL to avoid outdated SSL.", file=sys.stderr)
return self._download_pycurl(url)
raise e

def _download_pycurl(self, url):
"""Download CSS with pycurl, in case of old SSL (e.g. Python < 2.7.9)."""
import pycurl
c = pycurl.Curl()
c.setopt(c.URL, url)
buf = BytesIO()
c.setopt(c.WRITEDATA, buf)
c.perform()
return buf.getvalue()

def run(self):
css_dest = os.path.join('share', 'jupyter', 'voila', 'templates', 'default', 'static', 'index.css')
theme_light_dest = os.path.join('share', 'jupyter', 'voila', 'templates', 'default', 'static', 'theme-light.css')
theme_dark_dest = os.path.join('share', 'jupyter', 'voila', 'templates', 'default', 'static', 'theme-dark.css')

try:
css = self._download(css_url)
theme_light = self._download(theme_light_url)
theme_dark = self._download(theme_dark_url)
except Exception:
if os.path.exists(css_dest) and os.path.exists(theme_light_dest) and os.path.exists(theme_dark_dest):
print("Already have CSS, moving on.")
else:
raise OSError("Need Notebook CSS to proceed.")
return

try:
os.mkdir(os.path.join('share', 'jupyter', 'voila', 'templates', 'default', 'static'))
except OSError: # Use FileExistsError from python 3.3 onward.
pass
with open(css_dest, 'wb+') as f:
f.write(css)
with open(theme_light_dest, 'wb+') as f:
f.write(theme_light)
with open(theme_dark_dest, 'wb+') as f:
f.write(theme_dark)


def css_first(command):
class CSSFirst(command):
def run(self):
self.distribution.run_command('css')
return command.run(self)
return CSSFirst


class BdistEggDisabled(bdist_egg):
"""Disabled version of bdist_egg
Expand All @@ -326,12 +246,11 @@ def run(self):


cmdclass = {
'css': FetchCSS,
'jsdeps': NPM,
'build_py': css_first(js_first(build_py)),
'egg_info': css_first(js_first(egg_info)),
'sdist': css_first(js_first(sdist, strict=True)),
'develop': css_first(develop),
'build_py': js_first(build_py),
'egg_info': js_first(egg_info),
'sdist': js_first(sdist, strict=True),
'develop': develop,
'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else BdistEggDisabled
}

Expand All @@ -350,7 +269,7 @@ def get_data_files():
('share/jupyter/nbextensions/voila', ['voila/static/extension.js'])
]
# Add all the templates
for (dirpath, dirnames, filenames) in os.walk('share/jupyter/voila/templates/'):
for (dirpath, dirnames, filenames) in os.walk('share/jupyter/templates/voila/'):
if filenames:
data_files.append((dirpath, [os.path.join(dirpath, filename) for filename in filenames]))
return data_files
Expand All @@ -377,7 +296,7 @@ def get_data_files():
'install_requires': [
'async_generator',
'jupyter_server>=0.1.0,<0.2.0',
'nbconvert>=5.5.0,<6',
'nbconvert>=6.0.0-a,<7',
'jupyterlab_pygments>=0.1.0,<0.2',
'pygments>=2.4.1,<3' # Explicitly requiring pygments which is a second-order dependency.
# An older versions is generally installed already and is otherwise not updated by pip.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
{%- extends 'lab.tpl' -%}
{%- extends 'nbconvert/templates/classic/index.html.j2' -%}

{%- block header -%}
<!DOCTYPE html>
<html>
<head>
{%- block html_head -%}
<meta charset="utf-8" />
{% set nb_title = nb.metadata.get('title', '') or resources['metadata']['name'] %}
<title>Voila: {{nb_title}}</title>

<link rel="stylesheet" href="https://unpkg.com/font-awesome@4.5.0/css/font-awesome.min.css" type="text/css" />

{%- block html_head_js -%}
<script
src="{{resources.base_url}}voila/static/require.min.js"
integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA="
crossorigin="anonymous">
</script>
{%- block html_head_js_jquery -%}
{%- endblock html_head_js_jquery -%}
{%- endblock html_head_js -%}


{%- block html_head_css -%}
<style>
/*Hide empty cells*/
.jp-mod-noOutputs.jp-mod-noInput {
display: none;
}
</style>
<link rel="stylesheet" href="https://unpkg.com/font-awesome@4.5.0/css/font-awesome.min.css" type="text/css" />
{{ super() }}
{%- endblock html_head_css -%}



{% block custom_css %}
{% endblock %}



{% block ipywidgets %}
{% block notebook_execute %}
{%- set kernel_id = kernel_start() -%}
<script id="jupyter-config-data" type="application/json">
Expand All @@ -30,22 +39,25 @@
{% do notebook_execute(nb, kernel_id) %}
{%- endblock notebook_execute -%}

{%- endblock html_head_js -%}
{% endblock ipywidgets %}



{% block body_header %}
<body data-base-url="{{resources.base_url}}voila/">
<div tabindex="-1" id="notebook" class="border-box-sizing">
<div class="container" id="notebook-container">
{% endblock body_header %}



{%- block html_head_css -%}
<style>
/*Hide empty cells*/
.jp-mod-noOutputs.jp-mod-noInput {
display: none;
}
</style>
{%- endblock html_head_css -%}
{%- endblock html_head -%}
</head>
{%- endblock header -%}

{% block footer %}
{% block footer_js %}
<script
src="{{resources.base_url}}voila/static/require.min.js"
integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA="
crossorigin="anonymous">
</script>
<script>
requirejs.config({ baseUrl: '{{resources.base_url}}voila/', waitSeconds: 30})
requirejs(
Expand All @@ -57,8 +69,5 @@ requirejs(
]
)
</script>

{% endblock footer_js %}
{{ super() }}
</html>
{% endblock footer %}

110 changes: 110 additions & 0 deletions share/jupyter/voila/templates/lab/index.html.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{%- extends 'nbconvert/templates/lab/index.html.j2' -%}

{%- block html_head_js -%}
{%- endblock html_head_js -%}

{%- block html_head_css -%}
<link rel="stylesheet" href="https://unpkg.com/font-awesome@4.5.0/css/font-awesome.min.css" type="text/css" />
{{ super() }}

<style>
#loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 75vh;
color: var(--jp-content-font-color1);
font-family: sans-serif;
}
.spinner {
animation: rotation 2s infinite linear;
transform-origin: 50% 50%;
}
.spinner-container {
width: 10%;
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(359deg);}
}
.voila-spinner-color1{
fill:#268380;
}
.voila-spinner-color2{
fill:#f8e14b;
}
</style>
{%- endblock html_head_css -%}



{# this overrides the default behaviour of directly starting the kernel and executing the notebook #}
{% block notebook_execute %}
{% endblock notebook_execute %}


{%- block body_header -%}
{% if resources.theme == 'dark' %}
<body class="jp-Notebook theme-dark" data-base-url="{{resources.base_url}}voila/">
{% else %}
<body class="jp-Notebook theme-light" data-base-url="{{resources.base_url}}voila/">
{% endif %}
<div id="loading">
<div class="spinner-container">
<svg class="spinner" data-name="c1" version="1.1" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title>voila</dc:title></cc:Work></rdf:RDF></metadata><title>spin</title><path class="voila-spinner-color1" d="m250 405c-85.47 0-155-69.53-155-155s69.53-155 155-155 155 69.53 155 155-69.53 155-155 155zm0-275.5a120.5 120.5 0 1 0 120.5 120.5 120.6 120.6 0 0 0-120.5-120.5z"/><path class="voila-spinner-color2" d="m250 405c-85.47 0-155-69.53-155-155a17.26 17.26 0 1 1 34.51 0 120.6 120.6 0 0 0 120.5 120.5 17.26 17.26 0 1 1 0 34.51z"/></svg>
</div>
<h2 id="loading_text">Running {{nb_title}}...</h2>
</div>
<script>
var voila_process = function(cell_index, cell_count) {
var el = document.getElementById("loading_text")
el.innerHTML = `Executing ${cell_index} of ${cell_count}`
}
</script>
<div id="rendered_cells" style="display: none">
{%- endblock body_header -%}

{%- block body_loop -%}
{# from this point on, the kernel is started #}
{%- with kernel_id = kernel_start() -%}
<script id="jupyter-config-data" type="application/json">
{
"baseUrl": "{{resources.base_url}}",
"kernelId": "{{kernel_id}}"
}
</script>
{% set cell_count = nb.cells|length %}
{#
Voila is using Jinja's Template.generate method to not render the whole template in one go.
The current implementation of Jinja will however not yield template snippets if we call a blocks' super()
Therefore it is important to have the cell loop in the template.
The issue for Jinja is: https://github.com/pallets/jinja/issues/1044
#}
{%- for cell in cell_generator(nb, kernel_id) -%}
{% set cellloop = loop %}
{%- block any_cell scoped -%}
<script>
voila_process({{ cellloop.index }}, {{ cell_count }})
</script>
{{ super() }}
{%- endblock any_cell -%}
{%- endfor -%}
{% endwith %}
{%- endblock body_loop -%}

{%- block body_footer -%}
{{ super() }}
</div>
<script type="text/javascript">
(function() {
// remove the loading element
var el = document.getElementById("loading")
el.parentNode.removeChild(el)
// show the cell output
el = document.getElementById("rendered_cells")
el.style.display = 'unset'
})();
</script>
</body>
{%- endblock body_footer -%}
2 changes: 1 addition & 1 deletion tests/app/execute_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_hello_world(http_client, base_url):
assert response.code == 200
html_text = response.body.decode('utf-8')
assert 'Hi Voila' in html_text
assert 'print' not in html_text, 'by default the source code should be stripped'
assert 'print(' not in html_text, 'by default the source code should be stripped'
assert 'test_template.css' not in html_text, "test_template should not be the default"


Expand Down
6 changes: 3 additions & 3 deletions tests/app/template_cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

@pytest.fixture
def voila_args_extra():
path_test_template = os.path.abspath(os.path.join(BASE_DIR, '../test_template/share/jupyter/voila/templates/test_template/nbconvert_templates'))
path_default = os.path.abspath(os.path.join(BASE_DIR, '../../share/jupyter/voila/templates/default/nbconvert_templates'))
return ['--template=None', '--Voila.nbconvert_template_paths=[%r, %r]' % (path_test_template, path_default)]
path_test_template = os.path.abspath(os.path.join(BASE_DIR, '../test_template/share/jupyter/voila/templates/test_template/'))
path_default = os.path.abspath(os.path.join(BASE_DIR, '../../share/jupyter/voila/templates/default'))
return ['--template=None', '--VoilaTest.template_paths=[%r, %r]' % (path_test_template, path_default)]


@pytest.mark.gen_test
Expand Down
8 changes: 3 additions & 5 deletions tests/app/template_custom_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ def voila_args_extra():
@pytest.fixture
def voila_config():
def config(app):
path_test_template = os.path.abspath(os.path.join(BASE_DIR, '../test_template/share/jupyter/voila/templates/test_template/nbconvert_templates'))
path_default = os.path.abspath(os.path.join(BASE_DIR, '../../share/jupyter/voila/templates/default/nbconvert_templates'))
app.nbconvert_template_paths = [path_test_template, path_default]
path = os.path.abspath(os.path.join(BASE_DIR, '../../share/jupyter/voila/templates/default/templates'))
app.template_paths = [path]
path_test_template = os.path.abspath(os.path.join(BASE_DIR, '../test_template/share/jupyter/voila/templates/test_template'))
path_default = os.path.abspath(os.path.join(BASE_DIR, '../../share/jupyter/voila/templates/default'))
app.template_paths = [path_test_template, path_default]

return config

Expand Down
Loading

0 comments on commit 7fe3aa9

Please sign in to comment.