├── .babelrc
├── .coveragerc
├── .dockerignore
├── .esdoc
├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── README.md
├── README.rst
├── appveyor.yml
├── design
└── screens.svg
├── environment.yml
├── nbpresent
├── __init__.py
├── _version.py
├── export.py
├── exporters
│ ├── __init__.py
│ ├── base.py
│ ├── html.py
│ ├── pdf.py
│ ├── pdf_capture.py
│ └── pdf_ghost.py
├── tasks
│ ├── __init__.py
│ ├── _env.py
│ ├── build.py
│ ├── clean.py
│ ├── deps.py
│ ├── index.py
│ ├── less.py
│ ├── notebook.py
│ ├── requirejs.py
│ └── standalone.py
├── templates
│ └── nbpresent.tpl
└── tests
│ ├── __init__.py
│ ├── js
│ ├── _utils.js
│ ├── test_export_html.js
│ ├── test_notebook_basic.js
│ ├── test_notebook_create.js
│ └── test_notebook_no_regions.js
│ ├── notebooks
│ └── Basics.ipynb
│ ├── test_export.py
│ └── test_notebook.py
├── notebooks
├── Bokeh Mic Check.ipynb
├── Examples.ipynb
├── Extending nbpresent.ipynb
├── Importing revealjs themes.ipynb
├── README.ipynb
├── index.ipynb
└── proposal.ipynb
├── package.json
├── screenshot.png
├── setup.cfg
├── setup.py
├── sphinx
├── Makefile
├── make.bat
└── source
│ ├── conf.py
│ └── index.rst
└── src
├── es6
├── actions
│ ├── base.es6
│ ├── notebook.es6
│ └── standalone.es6
├── cells
│ ├── base.es6
│ ├── notebook.es6
│ ├── overlay.es6
│ └── standalone.es6
├── d3.bbox.js
├── editor.es6
├── help
│ ├── helper.es6
│ └── tours
│ │ ├── base.es6
│ │ ├── intro.es6
│ │ ├── slide-editor.es6
│ │ └── sorter.es6
├── icons.es6
├── layout
│ ├── grid.es6
│ ├── manual.es6
│ └── treemap.es6
├── less.es6
├── logger.es6
├── mini.es6
├── mode
│ ├── base.es6
│ ├── notebook.es6
│ └── standalone.es6
├── package.es6
├── parts.es6
├── presenter
│ ├── base.es6
│ ├── notebook.es6
│ └── standalone.es6
├── regiontree.es6
├── sorter.es6
├── speaker
│ ├── base.es6
│ └── notebook.es6
├── templates
│ ├── _util.es6
│ ├── library.es6
│ └── manual.es6
├── theme
│ ├── base.es6
│ ├── card.es6
│ ├── fonts.es6
│ ├── manager.es6
│ └── theme
│ │ ├── plain.es6
│ │ └── reveal.es6
├── tome
│ ├── arise.es6
│ ├── base.es6
│ └── basic.es6
├── toolbar.es6
├── tree.es6
└── vendor.es6
├── js
├── build.js
├── index.js
└── main.js
└── less
├── app.less
├── editor.less
├── help.less
├── index.less
├── layouts.less
├── link-overlay.less
├── mini.less
├── mixins.less
├── presenter.less
├── sorter.less
├── speaker.less
├── theme-card.less
├── theme-ui.less
├── toolbar.less
├── tour.less
└── variables.less
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
4 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [report]
2 | omit =
3 | */templates/*
4 | */static/*
5 | */tasks/*
6 | */pdf*
7 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | .git/
3 | *.egg-info
4 | *.pyc
5 | \.ipynb_checkpoints/
6 | build/
7 | dist/
8 | screenshots/
9 | nbpresent/static/nbpresent/
10 | node_modules/
11 | *.xunit.xml
12 | .coverage
13 | *.log
14 |
--------------------------------------------------------------------------------
/.esdoc:
--------------------------------------------------------------------------------
1 | {
2 | "source": "./src/es6",
3 | "destination": "./nbpresent/static/docs/esdoc"
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .coverage
3 | .DS_Store
4 | .eggs/
5 | *.egg-info
6 | *.pyc
7 | *.xunit.xml
8 | .coverage
9 | screenshots/
10 | sphinx/source/auto
11 | \.ipynb_checkpoints
12 | build/
13 | dist/
14 | nbpresent/static/nbpresent/
15 | node_modules
16 | npm-debug.log*
17 | screenshots/
18 | sphinx/source/auto
19 | *.log
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | sudo: true
4 |
5 | python:
6 | # We don't actually use the Travis Python, but this keeps it organized.
7 | - "2.7"
8 | - "3.4"
9 | - "3.5"
10 |
11 | install:
12 | - sudo apt-get update
13 | - sudo apt-get install -y libfreetype6-dev libfontconfig1-dev
14 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
15 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh;
16 | else
17 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
18 | fi
19 | - bash miniconda.sh -b -p $HOME/miniconda
20 | - export PATH="$HOME/miniconda/bin:$PATH"
21 | - hash -r
22 | - conda config --set always_yes yes --set changeps1 no
23 | - conda config --add channels conda-forge
24 | - conda update -q conda
25 | - conda info -a
26 | - conda create -n nbpresent python=$TRAVIS_PYTHON_VERSION
27 | - conda env update
28 | - source activate nbpresent
29 | - conda install python-coveralls
30 | - npm install
31 |
32 | script:
33 | - npm run lint
34 | - python setup.py develop
35 | - npm run build
36 | - jupyter nbextension install nbpresent --py --sys-prefix --symlink
37 | - jupyter nbextension enable nbpresent --py --sys-prefix
38 | - jupyter serverextension enable nbpresent --py --sys-prefix
39 | - npm run test
40 |
41 | after_success:
42 | - coveralls
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Continuum Analytics, Inc. and contributors
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of Continuum Analytics nor the names of any contributors
15 | may be used to endorse or promote products derived from this software
16 | without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
28 | THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include *.rst
3 | include *.md
4 | include package.json
5 |
6 | recursive-include nbpresent *
7 |
8 | exclude nbpresent/static/.git
9 | exclude sphinx
10 | exclude design
11 | exclude Dockerfile
12 |
13 | recursive-exclude * __pycache__
14 | recursive-exclude * *.py[co]
15 | recursive-exclude node_modules *.*
16 | recursive-exclude * node_modules
17 | recursive-exclude * .git
18 |
19 | prune nbpresent/tests/notebooks/.ipynb_checkpoints
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # nbpresent
3 |
4 | [](https://anaconda.org/anaconda-nb-extensions/nbpresent) [](https://pypi.python.org/pypi/nbpresent)
6 | [](https://travis-ci.org/Anaconda-Platform/nbpresent) [](https://ci.appveyor.com/project/bollwyvl/nbpresent)
7 | [](https://coveralls.io/github/Anaconda-Platform/nbpresent?branch=master)
8 |
9 | > remix your [Jupyter Notebooks](http://jupyter.org) as interactive slideshows
10 |
11 | 
12 |
13 | ## Using
14 | After [installing](#install) (and potentially enabling) as appropriate for your environment, relaunch the Jupyter Notebook: in the main toolbar, you will get two new buttons that toggle the _Authoring_ and _Presenting_ modes.
15 |
16 | ## User Documentation and Community
17 | When _Authoring_, you can click on the `(?)` icon to see a number of in-Notebook guided tours that show all the features, as well as see links to community pages:
18 |
19 | - [mailing list](https://groups.google.com/forum/#!forum/nbpresent) for general or long-term discussion and announcements
20 | - [issues](https://github.com/Anaconda-Platform/nbpresent/issues) for technical issues, as well as feature requests
21 | - [chat](https://gitter.im/Anaconda-Platform/nbpresent) for quickly connecting with other users
22 |
23 | ## Related Projects
24 | - [live_reveal/RISE](https://github.com/damianavila/RISE), the original inspiration for this work, based on [reveal.js](https://github.com/hakimel/reveal.js/).
25 | - [RMarkdown](http://rmarkdown.rstudio.com/ioslides_presentation_format.html) presentations
26 |
27 | ## Publishing
28 | When you are happy with your presentation, you can download the standalone HTML file from the _File -> Download as -> Presentation (.html)_ menu item.
29 |
30 | ## Install
31 |
32 | > Note: installing directly off this repo won't work, as we don't ship the built JavaScript and CSS assets. See more about [developing](#develop) below.
33 |
34 | ### `pip`
35 | ```shell
36 | pip install nbpresent
37 | jupyter nbextension install nbpresent --py --overwrite
38 | jupyter nbextension enable nbpresent --py
39 | jupyter serverextension enable nbpresent --py
40 | ```
41 |
42 | ### `conda`
43 | ```shell
44 | conda install -c conda-forge nbpresent
45 | ```
46 |
47 | This will enable the `nbpresent` `nbextension` and `serverextension` automatically!
48 |
49 | ## Export
50 | ### HTML
51 | Stock `nbconvert` doesn't store quite enough information, so you'll need to do something like this:
52 | ```shell
53 | nbpresent -i notebooks/README.ipynb -o README.html
54 | ```
55 | The resulting file can be hosted and viewed (but not edited!) on any site.
56 |
57 | You can also pass in and get back streams:
58 | ```shell
59 | cmd_that_generates_ipynb | nbpresent > README.html
60 | ```
61 |
62 | ### PDF (Experimental)
63 | If you have installed [nbbrowserpdf](https://github.com/Anaconda-Platform/nbbrowserpdf), you can also export to pdf:
64 | ```shell
65 | nbpresent -i notebooks/README.ipynb -f pdf -o README.pdf
66 | ```
67 |
68 | Here's the whole doc:
69 |
70 |
71 | ```python
72 | !nbpresent --help
73 | ```
74 |
75 | usage: nbpresent [-h] [-i IPYNB] [-o OUTFILE] [-f {html,pdf}]
76 |
77 | Generate a static nbpresent presentation from a Jupyter Notebook
78 |
79 | optional arguments:
80 | -h, --help show this help message and exit
81 | -i IPYNB, --ipynb IPYNB
82 | Input file (otherwise read from stdin)
83 | -o OUTFILE, --outfile OUTFILE
84 | Output file (otherwise write to stdout)
85 | -f {html,pdf}, --out-format {html,pdf}
86 | Output format
87 |
88 |
89 | ## Develop
90 | This assumes you have cloned this repository locally:
91 | ```
92 | git clone https://github.com/Anaconda-Platform/nbpresent.git
93 | cd nbpresent
94 | ```
95 |
96 | ### Repo Architecture
97 |
98 | The `nbpresent` nbextension is built from `./src` into `./nbpresent/static/nbresent` with:
99 | - `less` for style
100 | - `es6` (via `babel`) for javascript
101 | - `browserify` for packaging
102 |
103 | The `nbpresent` python module (server component) is stored in the `/nbpresent` folder
104 |
105 | ### Getting Started
106 | You'll need conda installed, either from [Anaconda](https://www.continuum.io/downloads) or [miniconda](http://conda.pydata.org/miniconda.html). You can create a Python development environment named `nbpresent` from `./environment.yml`.
107 |
108 | ```shell
109 | conda create -n nbpresent python=YOUR_FAVORITE_PYTHON
110 | conda update env
111 | source activate nbpresent
112 | ```
113 |
114 | We _still_ use `npm` for a lot of dependencies, so then run:
115 | ```shell
116 | npm install
117 | ```
118 |
119 | Finally, you are ready to build the assets!
120 | ```shell
121 | npm run build
122 | ```
123 |
124 | ### Ensure development asset loading
125 | To ensure that you always get the right assets, install the nbextension with the `symlink` options:
126 | ```shell
127 | jupyter nbextension install nbpresent --overwrite --symlink --sys-prefix
128 | jupyter nbextension enable nbpresent --sys-prefix
129 | jupyter serverextension enable nbpresent --sys-prefix
130 | ```
131 |
132 | See [chore automation](#chore-automation) below for more good times.
133 |
134 | ### Chore Automation
135 | | Task | Command |
136 | |------|---------|
137 | | Build all of the front end assets with sourcemaps for development | `npm run build` |
138 | | Rebuild on every save | `npm run watch` |
139 | | Rebuild all of the front end assets, and optimize it | `npm run dist` |
140 | | Run the CasperJS and `nose` tests | `npm run test` |
141 | | Check code style | `npm run lint` |
142 | | Build **and upload** the pypi **test** package | `npm run pkg:pypi` |
143 | | Build **and upload** the pypi **release** package | `npm run pkg:pypi:release` |
144 | | Build the ESDoc and Sphinx documentation | `npm run docs` |
145 |
146 | ## Changelog
147 |
148 | ### 3.0.2
149 | - use [Travis-CI](https://travis-ci.org/Anaconda-Platform/nbpresent) for continuous integration
150 | - use [Coveralls](https://coveralls.io/github/Anaconda-Platform/nbpresent) for code coverage
151 | - use a [conda-forge](https://github.com/conda-forge/nbpresent-feedstock) for cross-platform `conda` package building
152 |
153 | ### 3.0.1
154 | - minor build changes
155 |
156 | ### 3.0.0
157 | - Update to notebook 4.2
158 |
159 | ### 2.0.0
160 | - Theme editor removed. Significant work required to stabilize to public release quality.
161 | - Adding some themes extracted from reveal.js
162 |
163 | ### 1.1.1
164 | - fixing enabling on windows with `nb_config_manager` 0.1.3
165 | - trimming down conda packages
166 | - more reproducible builds
167 |
168 | ### 1.1.0 (Unreleased)
169 | - fixing issue with slides without regions and some layouts crashing editor [#58](https://github.com/Anaconda-Platform/nbpresent/issues/58)
170 | - adding JS extensibility of themes (partial [#44](https://github.com/Anaconda-Platform/nbpresent/issues/44))
171 | - see [Extending nbpresent](https://github.com/Anaconda-Platform/nbpresent/blob/master/notebooks/Extending%20nbpresent.ipynb)
172 |
173 | ### 1.0.0
174 | - [Theme editor](https://github.com/Anaconda-Platform/nbpresent/pull/41)
175 | - Much more consistent UI
176 | - Mnay bug fixes and more testing
177 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 |
3 | # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
4 | # /E:ON and /V:ON options are not enabled in the batch script interpreter
5 | # See: http://stackoverflow.com/a/13751649/163740
6 | CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd"
7 |
8 | # Workaround for https://github.com/conda/conda-build/issues/636
9 | PYTHONIOENCODING: "UTF-8"
10 |
11 | matrix:
12 | # Note: Because we have to separate the py2 and py3 components due to compiler version, we have a race condition for non-python packages.
13 | # Not sure how to resolve this, but maybe we should be tracking the VS version in the build string anyway?
14 | - TARGET_ARCH: "x64"
15 | CONDA_PY: "27"
16 | PY_CONDITION: "python >=2.7,<3"
17 | - TARGET_ARCH: "x64"
18 | CONDA_PY: "34"
19 | PY_CONDITION: "python >=3.4,<3.5"
20 | - TARGET_ARCH: "x64"
21 | CONDA_PY: "35"
22 | PY_CONDITION: "python >=3.5"
23 |
24 |
25 | # We always use a 64-bit machine, but can build x86 distributions
26 | # with the TARGET_ARCH variable (which is used by CMD_IN_ENV).
27 | platform:
28 | - x64
29 |
30 | install:
31 | # If there is a newer build queued for the same PR, cancel this one.
32 | # The AppVeyor 'rollout builds' option is supposed to serve the same
33 | # purpose but it is problematic because it tends to cancel builds pushed
34 | # directly to master instead of just PR builds (or the converse).
35 | # credits: JuliaLang developers.
36 | - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
37 | https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
38 | Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
39 | throw "There are newer queued builds for this pull request, failing early." }
40 |
41 | # Use the pre-installed Miniconda for the desired arch
42 | - ps: if($env:TARGET_ARCH -eq 'x86')
43 | {$root = "C:\Miniconda"}
44 | else
45 | {$root = "C:\Miniconda-x64"}
46 | $env:path="$root;$root\Scripts;$root\Library\bin;$($env:path)"
47 | - cmd: conda config --set show_channel_urls true
48 | - cmd: conda config --set always_yes yes --set changeps1 no
49 | - cmd: conda update --yes --quiet conda
50 | - cmd: set PYTHONUNBUFFERED=1
51 | - cmd: conda config --add channels conda-forge
52 | - cmd: conda info
53 | - cmd: conda create -n nbpresent "%PY_CONDITION%"
54 | - cmd: conda env update
55 | - cmd: activate nbpresent
56 | - cmd: npm install
57 |
58 | # Skip .NET project specific build phase.
59 | build: off
60 |
61 | test_script:
62 | - cmd: python setup.py develop
63 | - cmd: npm run build
64 | - cmd: jupyter nbextension install nbpresent --py --sys-prefix
65 | - cmd: jupyter nbextension enable nbpresent --py --sys-prefix
66 | - cmd: jupyter serverextension enable nbpresent --py --sys-prefix
67 | - cmd: npm run test
68 |
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: nbpresent
2 | channels:
3 | - conda-forge
4 | - defaults
5 | dependencies:
6 | - anaconda-client
7 | - coverage
8 | - flake8
9 | - ipywidgets >=5.1.5
10 | - mock
11 | - nodejs
12 | - nose
13 | - notebook >=4.2.0
14 | - python
15 | - requests
16 |
--------------------------------------------------------------------------------
/nbpresent/__init__.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | # flake8: noqa
3 | from os.path import join
4 |
5 | from nbconvert.exporters.export import exporter_map
6 |
7 | from .exporters.html import PresentExporter
8 | from ._version import __version__, __version_info__
9 |
10 |
11 | PDFPresentExporter = None
12 | pdf_import_error = None
13 |
14 | try:
15 | from .exporters.pdf import PDFPresentExporter
16 | except Exception as err:
17 | pdf_import_error = err
18 |
19 |
20 | # Jupyter Extension points
21 | def _jupyter_nbextension_paths():
22 | return [dict(
23 | section="notebook",
24 | src=join("static", "nbpresent"),
25 | dest="nbpresent",
26 | require="nbpresent/js/nbpresent.min")]
27 |
28 |
29 | def _jupyter_server_extension_paths():
30 | return [dict(module="nbpresent")]
31 |
32 |
33 | # serverextension
34 | def load_jupyter_server_extension(nbapp):
35 | nbapp.log.info("✓ nbpresent HTML export ENABLED")
36 | exporter_map.update(
37 | nbpresent=PresentExporter,
38 | )
39 |
40 | if pdf_import_error:
41 | nbapp.log.warn(
42 | "✗ nbpresent PDF export DISABLED: {}"
43 | .format(pdf_import_error)
44 | )
45 | else:
46 | nbapp.log.info("✓ nbpresent PDF export ENABLED")
47 | exporter_map.update(
48 | nbpresent_pdf=PDFPresentExporter
49 | )
50 |
--------------------------------------------------------------------------------
/nbpresent/_version.py:
--------------------------------------------------------------------------------
1 | __version_info__ = (3, 0, 2)
2 | __version__ = '.'.join(map(str, __version_info__))
3 |
--------------------------------------------------------------------------------
/nbpresent/export.py:
--------------------------------------------------------------------------------
1 | from argparse import ArgumentParser
2 | import codecs
3 | import os
4 | import sys
5 |
6 | from .exporters import (
7 | APP_ROOT
8 | )
9 |
10 | BINARY_FORMATS = ["pdf"]
11 |
12 |
13 | def export(ipynb=None, outfile=None, out_format=None, verbose=None):
14 | if out_format in ["pdf"]:
15 | from .exporters.pdf import PDFPresentExporter as Exporter
16 | elif out_format in ["html"]:
17 | from .exporters.html import PresentExporter as Exporter
18 |
19 | exp = Exporter(
20 | template_file="nbpresent",
21 | template_path=[os.path.join(APP_ROOT, "templates")]
22 | )
23 | if ipynb is not None:
24 | output, resources = exp.from_filename(ipynb)
25 | else:
26 | output, resources = exp.from_file(sys.stdin)
27 |
28 | mode, stream = (["wb+", sys.stdout.buffer]
29 | if out_format in BINARY_FORMATS
30 | else ["w+", sys.stdout])
31 |
32 | if outfile is not None:
33 | if out_format in BINARY_FORMATS:
34 | with open(outfile, mode) as fp:
35 | fp.write(output)
36 | else:
37 | with codecs.open(outfile, mode, encoding="utf-8") as fp:
38 | fp.write(output)
39 | else:
40 | stream.write(output)
41 |
42 |
43 | def main():
44 | parser = ArgumentParser(
45 | description="Generate a static nbpresent presentation from a Jupyter"
46 | " Notebook")
47 | parser.add_argument(
48 | "-i", "--ipynb",
49 | help="Input file (otherwise read from stdin)")
50 | parser.add_argument(
51 | "-o", "--outfile",
52 | help="Output file (otherwise write to stdout)")
53 | parser.add_argument(
54 | "-f", "--out-format",
55 | default="html",
56 | choices=["html", "pdf"],
57 | help="Output format"
58 | )
59 |
60 | export(**parser.parse_args().__dict__)
61 |
62 |
63 | if __name__ == "__main__":
64 | main()
65 |
--------------------------------------------------------------------------------
/nbpresent/exporters/__init__.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa
2 | from .base import (
3 | ASSETS,
4 | APP_ROOT
5 | )
6 |
--------------------------------------------------------------------------------
/nbpresent/exporters/base.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from notebook import DEFAULT_STATIC_FILES_PATH
4 |
5 | APP_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
6 | ASSETS = os.path.join(APP_ROOT, "static", "nbpresent")
7 | NB_ASSETS = [os.path.join(DEFAULT_STATIC_FILES_PATH, *bits) for bits in [
8 | ["components", "requirejs", "require.js"]
9 | ]]
10 | STANDALONE_ASSETS = [
11 | os.path.join(ASSETS, "js", "nbpresent.static.min.js"),
12 | os.path.join(ASSETS, "css", "nbpresent.min.css"),
13 | ]
14 |
--------------------------------------------------------------------------------
/nbpresent/exporters/html.py:
--------------------------------------------------------------------------------
1 | import codecs
2 | import os
3 | import json
4 |
5 | from collections import (
6 | OrderedDict,
7 | defaultdict
8 | )
9 |
10 | from nbconvert.exporters.html import HTMLExporter
11 |
12 | from .base import (
13 | APP_ROOT,
14 | STANDALONE_ASSETS,
15 | NB_ASSETS
16 | )
17 |
18 |
19 | class PresentExporter(HTMLExporter):
20 | def __init__(self, *args, **kwargs):
21 | # TODO: this probably isn't the right way
22 | kwargs.update(
23 | template_file="nbpresent",
24 | template_path=[os.path.join(APP_ROOT, "templates")]
25 | )
26 | super(PresentExporter, self).__init__(*args, **kwargs)
27 |
28 | def from_notebook_node(self, nb, resources=None, **kw):
29 | bin_ext = ["woff", "ttf"]
30 |
31 | resources = self._init_resources(resources)
32 |
33 | assets = defaultdict(OrderedDict)
34 |
35 | to_inline = NB_ASSETS + STANDALONE_ASSETS
36 |
37 | for filename in to_inline:
38 | ext = filename.split(".")[-1]
39 | mode = "rb" if ext in bin_ext else "r"
40 | with codecs.open(filename, mode, encoding="utf-8") as fp:
41 | assets[ext].update({
42 | os.path.basename(filename): fp.read()
43 | })
44 |
45 | resources.update(
46 | nbpresent={
47 | "metadata": json.dumps(nb.metadata.get("nbpresent", {}),
48 | indent=2,
49 | sort_keys=True)
50 | },
51 | nbpresent_assets=assets
52 | )
53 |
54 | return super(PresentExporter, self).from_notebook_node(
55 | nb,
56 | resources=resources,
57 | **kw
58 | )
59 |
--------------------------------------------------------------------------------
/nbpresent/exporters/pdf.py:
--------------------------------------------------------------------------------
1 | from .html import PresentExporter
2 |
3 | from nbbrowserpdf.exporters.pdf import BrowserPDFExporter
4 |
5 |
6 | class PDFPresentExporter(PresentExporter, BrowserPDFExporter):
7 | def pdf_capture_args(self):
8 | return [
9 | "--capture-server-class",
10 | "nbpresent.exporters.pdf_capture:SlideCaptureServer"
11 | ]
12 |
--------------------------------------------------------------------------------
/nbpresent/exporters/pdf_capture.py:
--------------------------------------------------------------------------------
1 | from nbbrowserpdf.exporters.pdf_capture import CaptureServer
2 |
3 | VIEWPORT = (1920, 1080)
4 |
5 |
6 | class SlideCaptureServer(CaptureServer):
7 | """ CaptureServer to generate multi-page PDF based on nbpresent metadata
8 | """
9 | ghost_cmd = "nbpresent.exporters.pdf_ghost"
10 |
--------------------------------------------------------------------------------
/nbpresent/exporters/pdf_ghost.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from ghost.bindings import (
4 | QPainter,
5 | QPrinter,
6 | QtCore,
7 | )
8 |
9 | from PyPDF2 import (
10 | PdfFileReader,
11 | PdfFileMerger,
12 | )
13 |
14 | from nbbrowserpdf.exporters.pdf_ghost import NotebookPDFGhost, parser
15 |
16 |
17 | # a notional default viewport...
18 | VIEWPORT = (1920, 1080)
19 |
20 |
21 | class PresentationPDFGhost(NotebookPDFGhost):
22 | embed_ipynb = True
23 |
24 | def print_to_pdf(self, path):
25 | """ Custom print based on metadata: generate one per slide
26 | """
27 | merger = PdfFileMerger()
28 |
29 | for i, slide in enumerate(self.notebook.metadata.nbpresent.slides):
30 | print("\n\n\nprinting slide", i, slide)
31 | # science help you if you have 9999 slides
32 | filename = self.in_static("notebook-{0:04d}.pdf".format(i))
33 | # this is weird, but it seems to always need it
34 | self.session.show()
35 | self.screenshot(filename)
36 | merger.append(PdfFileReader(filename, "rb"))
37 |
38 | # advance the slides
39 | result, resources = self.session.evaluate(
40 | """
41 | console.log(window.nbpresent);
42 | console.log(window.nbpresent.mode.presenter.speaker.advance());
43 | """)
44 |
45 | # always seem to get some weirdness... perhaps could inttegrate
46 | # ioloop...
47 | time.sleep(1)
48 |
49 | # all done!
50 | merger.write(path)
51 |
52 | def screenshot(self, filename):
53 | """ do an individual slide screenshot
54 | big thanks to https://gist.github.com/jmaupetit/4217925
55 | """
56 |
57 | printer = QPrinter(mode=QPrinter.ScreenResolution)
58 | printer.setOutputFormat(QPrinter.PdfFormat)
59 | printer.setPaperSize(QtCore.QSizeF(*reversed(VIEWPORT)),
60 | QPrinter.DevicePixel)
61 | printer.setOrientation(QPrinter.Landscape)
62 | printer.setOutputFileName(filename)
63 | printer.setPageMargins(0, 0, 0, 0, QPrinter.DevicePixel)
64 |
65 | painter = QPainter(printer)
66 | painter.scale(1.45, 1.45)
67 | self.session.main_frame.render(painter)
68 | painter.end()
69 |
70 |
71 | def main(url, static_path):
72 | pdf_ghost = PresentationPDFGhost(url, static_path)
73 |
74 | pdf_ghost.render()
75 |
76 |
77 | if __name__ == "__main__":
78 | main(**parser.parse_args().__dict__)
79 |
--------------------------------------------------------------------------------
/nbpresent/tasks/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anaconda/nbpresent/6bf2f50b7abd27273dc9e7d469bf1866bafbf5bb/nbpresent/tasks/__init__.py
--------------------------------------------------------------------------------
/nbpresent/tasks/_env.py:
--------------------------------------------------------------------------------
1 | from os.path import (
2 | dirname,
3 | abspath,
4 | join,
5 | )
6 | import platform
7 |
8 |
9 | PKG = "nbpresent"
10 |
11 | REPO_ROOT = abspath(join(dirname(__file__), "..", ".."))
12 | PKG_ROOT = join(REPO_ROOT, PKG)
13 | SRC = join(REPO_ROOT, "src")
14 | DIST = join(PKG_ROOT, "static", PKG)
15 | JS = join(DIST, "js")
16 | CSS = join(DIST, "css")
17 |
18 | IS_WIN = "Windows" in platform.system()
19 |
20 |
21 | def node_bin(*it):
22 | return join(REPO_ROOT, "node_modules", ".bin", *it)
23 |
24 |
25 | def external(*modules):
26 | # browserify --external
27 | return sum([["--external", m] for m in modules], [])
28 |
29 | transform = [
30 | "--transform", "[", "babelify", "--sourceMapRelative", ".", "]",
31 | ]
32 |
33 | extension = ["--extension", "es6"]
34 |
--------------------------------------------------------------------------------
/nbpresent/tasks/build.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from multiprocessing import Pool, cpu_count
3 | from importlib import import_module
4 |
5 | from nbpresent.tasks import (
6 | requirejs,
7 | clean,
8 | )
9 |
10 |
11 | try:
12 | CPU_COUNT = cpu_count()
13 | except NotImplementedError:
14 | CPU_COUNT = 1
15 |
16 |
17 | def _run(mod_opt, *args, **opts):
18 | print("started {0} with {1}".format(*mod_opt))
19 | task = import_module("nbpresent.tasks.{}".format(mod_opt[0]))
20 | task.main(**mod_opt[1])
21 | print("...completed {}".format(mod_opt[0]))
22 | return 0
23 |
24 |
25 | def main(**opts):
26 | clean.main()
27 |
28 | pool = Pool(processes=CPU_COUNT)
29 |
30 | tasks = [
31 | "less",
32 | "deps",
33 | "index",
34 | "notebook",
35 | "standalone",
36 | ]
37 |
38 | pool.map(_run, zip(tasks, [opts] * len(tasks)))
39 |
40 | requirejs.main(**opts)
41 |
42 | return 0
43 |
44 | if __name__ == "__main__":
45 | opts = {}
46 | args = sys.argv[1:]
47 | if "release" in args:
48 | opts.update(
49 | browserify=["-g", "uglifyify"]
50 | )
51 | if "dev" in args:
52 | opts.update(
53 | browserify=["--debug"],
54 | less=["--source-map-map-inline"]
55 | )
56 |
57 | sys.exit(main(**opts))
58 |
--------------------------------------------------------------------------------
/nbpresent/tasks/clean.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import shutil
4 |
5 | from ._env import (
6 | CSS,
7 | JS
8 | )
9 |
10 |
11 | def main(**opts):
12 | for clean_dir in [JS, CSS]:
13 | os.path.exists(clean_dir) and shutil.rmtree(clean_dir)
14 | os.makedirs(clean_dir)
15 |
16 |
17 | if __name__ == "__main__":
18 | sys.exit(main())
19 |
--------------------------------------------------------------------------------
/nbpresent/tasks/deps.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen
2 | import sys
3 |
4 | from ._env import (
5 | JS,
6 | SRC,
7 | join,
8 | node_bin,
9 | external,
10 | extension,
11 | transform,
12 | IS_WIN,
13 | )
14 |
15 |
16 | def main(**opts):
17 | args = [
18 | node_bin("browserify"),
19 | "--standalone", "nbpresent-deps",
20 | ] + extension + external(
21 | "jquery", "base/js/namespace", "notebook/js/celltoolbar"
22 | ) + transform + [
23 | "--outfile", join(JS, "nbpresent.deps.min.js"),
24 | join(SRC, "es6", "vendor.es6")
25 | ] + opts.get("browserify", [])
26 | return Popen(args, shell=IS_WIN).wait()
27 |
28 |
29 | if __name__ == "__main__":
30 | sys.exit(main())
31 |
--------------------------------------------------------------------------------
/nbpresent/tasks/index.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import shutil
3 |
4 | from ._env import (
5 | JS,
6 | SRC,
7 | join,
8 | )
9 |
10 |
11 | def main(**opts):
12 | shutil.copyfile(
13 | join(SRC, "js", "index.js"),
14 | join(JS, "nbpresent.min.js")
15 | )
16 | return 0
17 |
18 | if __name__ == "__main__":
19 | sys.exit(main())
20 |
--------------------------------------------------------------------------------
/nbpresent/tasks/less.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen
2 | import sys
3 |
4 | from ._env import (
5 | CSS,
6 | SRC,
7 | IS_WIN,
8 | join,
9 | node_bin,
10 | )
11 |
12 |
13 | def main(**opts):
14 | args = [
15 | node_bin("lessc"),
16 | "--autoprefix",
17 | "--clean-css",
18 | "--include-path={}".format(node_bin("..")),
19 | join(SRC, "less", "index.less"),
20 | join(CSS, "nbpresent.min.css")
21 | ] + opts.get("less", [])
22 | return Popen(args, shell=IS_WIN).wait()
23 |
24 |
25 | if __name__ == "__main__":
26 | sys.exit(main())
27 |
--------------------------------------------------------------------------------
/nbpresent/tasks/notebook.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen
2 | import sys
3 |
4 | from ._env import (
5 | JS,
6 | SRC,
7 | join,
8 | node_bin,
9 | external,
10 | extension,
11 | transform,
12 | IS_WIN,
13 | )
14 |
15 |
16 | def main(**opts):
17 | args = [
18 | node_bin("browserify"),
19 | "--standalone", "nbpresent-notebook",
20 | ] + extension + external(
21 | "bootstraptour",
22 | "base/js/namespace",
23 | "notebook/js/celltoolbar",
24 | "jquery",
25 | "d3",
26 | "html2canvas",
27 | "baobab",
28 | "uuid",
29 | "nbpresent-deps"
30 | ) + transform + [
31 | "--outfile", join(JS, "nbpresent.notebook.min.js"),
32 | join(SRC, "es6", "mode", "notebook.es6")
33 | ] + opts.get("browserify", [])
34 | return Popen(args, shell=IS_WIN).wait()
35 |
36 |
37 | if __name__ == "__main__":
38 | sys.exit(main())
39 |
--------------------------------------------------------------------------------
/nbpresent/tasks/requirejs.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen
2 | import sys
3 |
4 | from ._env import (
5 | SRC,
6 | join,
7 | node_bin,
8 | IS_WIN,
9 | )
10 |
11 |
12 | def main(**opts):
13 | args = [
14 | node_bin("r.js{}".format(".cmd" if IS_WIN else "")),
15 | "-o", join(SRC, "js", "build.js"),
16 | ] + opts.get("requirejs", [])
17 | return Popen(args, shell=IS_WIN).wait()
18 |
19 |
20 | if __name__ == "__main__":
21 | sys.exit(main())
22 |
--------------------------------------------------------------------------------
/nbpresent/tasks/standalone.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen
2 | import sys
3 |
4 | from ._env import (
5 | JS,
6 | SRC,
7 | join,
8 | node_bin,
9 | external,
10 | extension,
11 | transform,
12 | IS_WIN
13 | )
14 |
15 |
16 | def main(**opts):
17 | args = [
18 | node_bin("browserify"),
19 | "--standalone", "nbpresent-standalone",
20 | ] + extension + external(
21 | "jquery",
22 | "nbpresent-deps"
23 | ) + transform + [
24 | "--outfile", join(JS, "nbpresent.standalone.min.js"),
25 | join(SRC, "es6", "mode", "standalone.es6")
26 | ] + opts.get("browserify", [])
27 | return Popen(args, shell=IS_WIN).wait()
28 |
29 |
30 | if __name__ == "__main__":
31 | sys.exit(main())
32 |
--------------------------------------------------------------------------------
/nbpresent/templates/nbpresent.tpl:
--------------------------------------------------------------------------------
1 | {%- extends 'full.tpl' -%}
2 |
3 |
4 | {% macro nbpresent_id(cell) -%}
5 | {% if cell.metadata.nbpresent %}
6 | data-nbp-id="{{ cell.metadata.nbpresent.id }}"
7 | {% endif %}
8 | {%- endmacro %}
9 |
10 |
11 | {% block codecell %}
12 |
13 | {{ super() }}
14 |
15 | {%- endblock codecell %}
16 |
17 |
18 | {% block markdowncell scoped %}
19 |
20 | {{ self.empty_in_prompt() }}
21 |
22 |
23 | {{ cell.source | markdown2html | strip_files_prefix }}
24 |
25 |
26 |
27 | {%- endblock markdowncell %}
28 |
29 |
30 | {%- block html_head -%}
31 |
32 | {{resources['metadata']['name']}}
33 |
34 | {% for css in resources.inlining.css -%}
35 |
38 | {% endfor %}
39 |
40 |
52 |
53 |
54 | {% for fname, css in resources.nbpresent_assets.css.items() -%}
55 |
58 | {% endfor %}
59 |
60 |
61 |
62 |
63 | {{ mathjax() }}
64 |
65 | {% for fname, js in resources.nbpresent_assets.js.items() -%}
66 |
69 | {% endfor %}
70 |
71 |
72 |
75 |
76 | {%- endblock html_head -%}
77 |
--------------------------------------------------------------------------------
/nbpresent/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anaconda/nbpresent/6bf2f50b7abd27273dc9e7d469bf1866bafbf5bb/nbpresent/tests/__init__.py
--------------------------------------------------------------------------------
/nbpresent/tests/js/_utils.js:
--------------------------------------------------------------------------------
1 | /* globals require casper Jupyter */
2 | var system = require('system');
3 |
4 | var root = casper,
5 | _img = 0,
6 | node_modules = system.env.NBPRESENT_TEST_MODULES || "../../../node_modules",
7 | vendor = root.vendor = root.vendor ? root.vendor : (root.vendor = {}),
8 | _ = vendor._ = require(node_modules + "/lodash"),
9 | _shotDir = "_unnamed";
10 |
11 | function nextId(){
12 | return ("000" + (_img++)).slice(-4);
13 | }
14 |
15 | function slug(text){
16 | return text.replace(/[^a-z0-9]/g, "_");
17 | }
18 |
19 |
20 | root.screenshot = function(message){
21 | return this.captureSelector([
22 | "screenshots/",
23 | _shotDir,
24 | "/",
25 | nextId(),
26 | "_",
27 | slug(message),
28 | ".png"
29 | ].join(""),
30 | "body"
31 | );
32 | };
33 |
34 |
35 | root.screenshot.init = function(ns){
36 | _shotDir = ns;
37 | _img = 0;
38 | return root;
39 | };
40 |
41 |
42 | root.canSeeAndClick = function(message, visible, click){
43 | var things = message;
44 |
45 | if(arguments.length !== 1){
46 | things = [[message, visible, click]];
47 | }
48 |
49 | var that = this;
50 |
51 | things.map(function(thing){
52 | var message = thing[0],
53 | visible = thing[1],
54 | click = thing[2];
55 |
56 | that = that
57 | .waitUntilVisible(visible)
58 | .then(function(){
59 | this.test.assertExists(click || visible, "I can see and click " + message);
60 | this.screenshot(message);
61 | this.click(click || visible);
62 | });
63 | });
64 |
65 | return that;
66 | }
67 |
68 | root.hasMeta = function(path, tests){
69 | var meta;
70 |
71 | this.thenEvaluate(function () {
72 | require(['base/js/events'], function(events) {
73 | Jupyter._save_success = Jupyter._save_failed = false;
74 | events.on('notebook_saved.Notebook', function () {
75 | Jupyter._save_success = true;
76 | });
77 | events.on('notebook_save_failed.Notebook',
78 | function (event, error) {
79 | Jupyter._save_failed = "save failed with " + error;
80 | });
81 | Jupyter.notebook.save_notebook();
82 | });
83 | });
84 |
85 | this.waitFor(function () {
86 | return this.evaluate(function(){
87 | return Jupyter._save_failed || Jupyter._save_success;
88 | });
89 | });
90 |
91 | return this
92 | .then(function(){
93 | meta = this.evaluate(function(){
94 | return Jupyter.notebook.metadata;
95 | });
96 | })
97 | .then(function(){
98 | _.map(tests, function(test, message){
99 | var results = test(_.get(meta, path));
100 | this.test.assertEquals(results[0], results[1],
101 | "I remember " + message + " in " + path);
102 | }, this);
103 | });
104 | };
105 |
106 | root.dragRelease = function(message, selector, opts){
107 | var it, x, y, x1, y1;
108 | return this.then(function(){
109 | it = this.getElementBounds(selector);
110 | x = it.left + it.width / 2;
111 | y = it.top + it.height / 2;
112 | x1 = x + (opts.right || -opts.left || 0);
113 | y1 = y + (opts.down || -opts.up || 0);
114 | })
115 | .then(function(){
116 | this.mouse.down(x, y);
117 | })
118 | .then(function(){
119 | this.screenshot("click " + message);
120 | this.mouse.move(x1, y1);
121 | })
122 | .then(function(){
123 | this.screenshot("drag " + message);
124 | this.mouse.up(x1, y1);
125 | })
126 | .then(function(){
127 | this.screenshot("release " + message);
128 | });
129 | };
130 |
131 | root.baseline_notebook = function(){
132 | // the actual test
133 |
134 | this.append_cell();
135 | this.set_cell_text(1, [
136 | 'from IPython.display import Markdown',
137 | 'Markdown("# Hello World!")'
138 | ].join("\n"));
139 | this.execute_cell_then(1);
140 |
141 | this.append_cell();
142 | this.set_cell_text(2, [
143 | 'from ipywidgets import FloatSlider',
144 | 'x = FloatSlider()',
145 | 'x'
146 | ].join("\n"));
147 | this.execute_cell_then(2);
148 | }
149 |
150 | root.saveNbpresent = function (){
151 | return this.thenEvaluate(function(){
152 | window.nbpresent.mode.metadata(true);
153 | });
154 | }
155 |
--------------------------------------------------------------------------------
/nbpresent/tests/js/test_export_html.js:
--------------------------------------------------------------------------------
1 | /* global casper require */
2 |
3 | var system = require('system'),
4 | host = system.env.NBPRESENT_TEST_HTTP_HOST || "localhost",
5 | port = system.env.NBPRESENT_TEST_HTTP_PORT || 8000,
6 | root = "http://" + host + ":" + port + "/";
7 |
8 | casper.test.begin("Does exported HTML look okay?", function(test){
9 | casper.start(root + "Basics.html", function(){
10 | casper.screenshot.init("export_html");
11 | casper.viewport(1440, 900, export_test)
12 | }).run(function(){
13 | test.done();
14 | });
15 | });
16 |
17 | function export_test(){
18 | return this.canSeeAndClick("body", "body.nbp-presenting")
19 | .canSeeAndClick("markdown", ".text_cell h1")
20 | .canSeeAndClick("code source", ".code_cell .input_area")
21 | .canSeeAndClick("code output", ".code_cell .output_text")
22 | .canSeeAndClick("svg container", ".code_cell .output_svg")
23 | .then(function(){ return this.mouse.move(1430, 890); })
24 | .wait(300)
25 | .canSeeAndClick("next slide button", ".fa-step-forward")
26 | .canSeeAndClick("embedded image", ".output_png")
27 | .then(function(){ return this.mouse.move(1430, 890); })
28 | .canSeeAndClick("previous slide button", ".fa-step-backward")
29 | .wait(300)
30 | .canSeeAndClick("fin", "body.nbp-presenting");
31 | }
32 |
--------------------------------------------------------------------------------
/nbpresent/tests/js/test_notebook_basic.js:
--------------------------------------------------------------------------------
1 | /* global casper */
2 | casper.notebook_test(function(){
3 | casper.screenshot.init("basic");
4 | casper.viewport(1440, 900)
5 | .then(basic_test);
6 | });
7 |
8 | function basic_test(){
9 | var t = casper.test;
10 |
11 | this.baseline_notebook();
12 |
13 | this.then(function(){
14 | this.canSeeAndClick("the body", "body");
15 | })
16 | .then(function(){
17 | ["#nbp-app-btn", "#nbp-present-btn", ".download_nbpresent"]
18 | .map(function(selector){ t.assertExists(selector); });
19 |
20 | loaded("nbpresent.min.css");
21 | });
22 |
23 | // TODO: move to utils?
24 | function loaded(pattern){
25 | t.assertResourceExists(function(resource) {
26 | return resource.url.match(pattern);
27 | });
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/nbpresent/tests/js/test_notebook_create.js:
--------------------------------------------------------------------------------
1 | /* global casper */
2 |
3 | casper.notebook_test(function(){
4 | casper.screenshot.init("create");
5 | casper.viewport(1440, 900)
6 | .then(create_test);
7 | });
8 |
9 | function create_test(){
10 | var _ = this.vendor._,
11 | freeRegion = ".nbp-slides-wrap .slide .nbp-region.nbp-content-null";
12 |
13 | this.baseline_notebook();
14 |
15 | this.canSeeAndClick([
16 | ["the sorter button", "#nbp-app-btn"],
17 | ["the slides button", ".nbp-app-bar .fa-film"],
18 | ["the add slide button", ".nbp-deck-toolbar .fa-plus-square-o"],
19 | ["a slide template in the library",
20 | ".nbp-template-library .slide:last-of-type"]
21 | ])
22 | .waitWhileVisible(".nbp-template-library")
23 | .saveNbpresent()
24 | .hasMeta("nbpresent.slides", {
25 | "one slide": function(slides){
26 | return [_(slides).toArray().value().length, 1];
27 | },
28 | "four regions": function(slides){
29 | var regions = _(_(slides).toArray().first().regions)
30 | .toArray()
31 | .value();
32 | return [regions.length, 4];
33 | }
34 | })
35 | .canSeeAndClick([
36 | ["a region in the sorter", ".nbp-slides-wrap .slide .nbp-region"],
37 | ["the link mode button", ".nbp-region-toolbar .fa-link"],
38 | ["a linkable source", ".nbp-part-overlay-source"],
39 | ["a region in the sorter", freeRegion],
40 | ["the link mode button", ".nbp-region-toolbar .fa-link"],
41 | ["a linkable output", ".nbp-part-overlay-outputs"],
42 | ["a region in the sorter", freeRegion],
43 | ["the link mode button", ".nbp-region-toolbar .fa-link"],
44 | ["a linkable widget", ".nbp-part-overlay-widgets"],
45 | ["a region in the sorter", freeRegion],
46 | ["the link mode button", ".nbp-region-toolbar .fa-link"],
47 | ["a linkable whole", ".nbp-part-overlay-whole"],
48 | ["the presenter button", "#nbp-present-btn"],
49 | ["the presenter", ".nbp-presenter"]
50 | ])
51 | .then(function(){
52 | return this.mouse.move(1430, 890);
53 | })
54 | .wait(1)
55 | .canSeeAndClick([
56 | ["the return to notebook button", ".fa-book"],
57 | ["the edit button", ".nbp-deck-toolbar .fa-edit"],
58 | ["a region in the region tree", ".nbp-regiontree .nbp-region"],
59 | ["the treemap layout button", ".nbp-regiontree .fa-tree"],
60 | ["the weight attribute", ".attr_name"]
61 | ])
62 | .dragRelease("the weight attribute", ".attr_name", {right: 50})
63 | .canSeeAndClick("the manual layout button", ".nbp-regiontree .fa-arrows")
64 | .dragRelease("a draggable region",
65 | ".nbp-editor .nbp-region.active .nbp-region-bg", {right: 50})
66 | .canSeeAndClick([
67 | ["the exit edit mode button", ".fa-chevron-circle-down"],
68 | ["the sorter button", "#nbp-app-btn"]
69 | ])
70 | .waitWhileVisible(".nbp-sorter")
71 | .waitWhileVisible(".nbp-regiontree")
72 | .canSeeAndClick([
73 | ["the presenter button", "#nbp-present-btn"],
74 | ["the presenter", ".nbp-presenter"]
75 | ]);
76 | }
77 |
--------------------------------------------------------------------------------
/nbpresent/tests/js/test_notebook_no_regions.js:
--------------------------------------------------------------------------------
1 | /* global casper */
2 |
3 | casper.notebook_test(function(){
4 | casper.screenshot.init("empty");
5 | casper.viewport(1440, 900)
6 | .then(empty_test);
7 | });
8 |
9 | var freeRegion = ".nbp-slides-wrap .slide .nbp-region.nbp-content-null";
10 |
11 |
12 | function empty_test(){
13 | var _ = this.vendor._;
14 |
15 | this.baseline_notebook();
16 |
17 | this.canSeeAndClick([
18 | ["the sorter button", "#nbp-app-btn"],
19 | ["the slides button", ".nbp-app-bar .fa-film"],
20 | ["the add slide button", ".nbp-deck-toolbar .fa-plus-square-o"],
21 | ["a slide template in the library",
22 | ".nbp-template-library .slide:first-of-type"]
23 | ])
24 | .waitWhileVisible(".nbp-template-library")
25 | .canSeeAndClick([
26 | ["the edit button", ".nbp-deck-toolbar .fa-edit"],
27 | ["a region in the sorter", freeRegion],
28 | ["the unlink button", ".nbp-region-toolbar .fa-trash"]
29 | ])
30 | .saveNbpresent()
31 | .waitWhileSelector([
32 | ".nbp-slides-wrap .slide .nbp-region",
33 | ".nbp-regiontree .nbp-region",
34 | ".nbp-editor .nbp-region"
35 | ].join(", "))
36 | .hasMeta("nbpresent.slides", {
37 | "one slide": function(slides){
38 | return [_(slides).toArray().value().length, 1];
39 | },
40 | "no regions": function(slides){
41 | var regions = _(_(slides).toArray().first().regions)
42 | .toArray()
43 | .value();
44 | return [regions.length, 0];
45 | }
46 | })
47 | .canSeeAndClick([
48 | ["fin", "body"]
49 | ])
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/nbpresent/tests/test_export.py:
--------------------------------------------------------------------------------
1 | from glob import glob
2 | import os
3 | import socket
4 | import subprocess
5 | import tempfile
6 | import sys
7 | import platform
8 |
9 | from ..export import export
10 |
11 |
12 | IS_WIN = "Windows" in platform.system()
13 |
14 |
15 | # utility function
16 | def join(*bits):
17 | return os.path.abspath(os.path.join(*bits))
18 |
19 |
20 | here = os.path.dirname(__file__)
21 |
22 | http_module = None
23 |
24 |
25 | def node_bin(*bits):
26 | return os.path.abspath(
27 | os.path.join(here, "..", "..", "node_modules", ".bin", *bits))
28 |
29 | try:
30 | import http.server as httpd
31 | http_module = "http.server"
32 | except ImportError:
33 | import SimpleHTTPServer as httpd
34 | http_module = "SimpleHTTPServer"
35 |
36 | print("nbpresent export test using", httpd)
37 |
38 |
39 | def unused_port():
40 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
41 | sock.bind(('localhost', 0))
42 | addr, port = sock.getsockname()
43 | sock.close()
44 | return port
45 |
46 |
47 | def test_export():
48 | tmpdir = tempfile.mkdtemp()
49 | env = dict(os.environ)
50 | env.update(
51 | NBPRESENT_TEST_HTTP_PORT=str(unused_port()),
52 | PATH=os.pathsep.join([env["PATH"], node_bin()])
53 | )
54 |
55 | httpd = subprocess.Popen([
56 | sys.executable, "-m", http_module,
57 | env["NBPRESENT_TEST_HTTP_PORT"],
58 | "--bind=127.0.0.1"
59 | ],
60 | cwd=tmpdir)
61 |
62 | try:
63 | export(
64 | join(here, "notebooks", "Basics.ipynb"),
65 | join(tmpdir, "Basics.html"),
66 | "html")
67 | args = [
68 | "casperjs{}".format(".cmd" if IS_WIN else ""),
69 | "test",
70 | "--fail-fast",
71 | "--includes={}".format(
72 | ",".join(glob(join(here, 'js', '_*.js')))),
73 | ] + glob(join(here, 'js', 'test_export_*.js'))
74 |
75 | proc = subprocess.Popen(
76 | args,
77 | stderr=subprocess.PIPE,
78 | env=env
79 | )
80 | proc.communicate()
81 |
82 | assert proc.returncode == 0
83 | finally:
84 | httpd.kill()
85 |
86 |
87 | if __name__ == '__main__':
88 | test_export()
89 |
--------------------------------------------------------------------------------
/nbpresent/tests/test_notebook.py:
--------------------------------------------------------------------------------
1 | import codecs
2 | import glob
3 | import os
4 | import subprocess
5 |
6 | try:
7 | from unittest.mock import patch
8 | except ImportError:
9 | # py2
10 | from mock import patch
11 |
12 | from notebook import jstest
13 |
14 | import platform
15 |
16 | IS_WIN = "Windows" in platform.system()
17 |
18 | here = os.path.dirname(__file__)
19 |
20 | # global npm installs are bad, add the local node_modules to the path
21 | os.environ["PATH"] = os.pathsep.join([
22 | os.environ["PATH"],
23 | os.path.abspath(os.path.join(here, "node_modules", ".bin"))
24 | ])
25 |
26 | TEST_LOG = ".jupyter.jstest.log"
27 |
28 |
29 | class NBPresentTestController(jstest.JSController):
30 | """ Javascript test subclass that installs widget nbextension in test
31 | environment
32 | """
33 | def __init__(self, section, *args, **kwargs):
34 | extra_args = kwargs.pop('extra_args', None)
35 | super(NBPresentTestController, self).__init__(section, *args, **kwargs)
36 | self.xunit = True
37 |
38 | test_cases = glob.glob(os.path.join(here, 'js', 'test_notebook_*.js'))
39 | js_test_dir = jstest.get_js_test_dir()
40 |
41 | includes = [
42 | os.path.join(js_test_dir, 'util.js')
43 | ] + glob.glob(os.path.join(here, 'js', '_*.js'))
44 |
45 | self.cmd = [
46 | 'casperjs', 'test',
47 | '--includes={}'.format(",".join(includes)),
48 | '--engine={}'.format(self.engine)
49 | ] + test_cases
50 |
51 | if extra_args is not None:
52 | self.cmd = self.cmd + extra_args
53 |
54 | if IS_WIN:
55 | self.cmd[0] = "{}.cmd".format(self.cmd[0])
56 |
57 | def launch(self, buffer_output=False, capture_output=False):
58 | # print('*** ENV:', self.env) # dbg
59 | # print('*** CMD:', self.cmd) # dbg
60 | env = os.environ.copy()
61 | env.update(self.env)
62 | if buffer_output:
63 | capture_output = True
64 | self.stdout_capturer = c = jstest.StreamCapturer(
65 | echo=not buffer_output)
66 | c.start()
67 | stdout = c.writefd if capture_output else None
68 | # stderr = subprocess.STDOUT if capture_output else None
69 | self.process = subprocess.Popen(
70 | self.cmd,
71 | stderr=subprocess.PIPE,
72 | stdout=stdout,
73 | env=env)
74 |
75 | def wait(self):
76 | self.process.communicate()
77 | self.stdout_capturer.halt()
78 | self.stdout = self.stdout_capturer.get_buffer()
79 | return self.process.returncode
80 |
81 | # copy pasta from...
82 | # https://github.com/jupyter/notebook/blob/master/notebook/jstest.py#L234
83 | def setup(self):
84 | self.ipydir = jstest.TemporaryDirectory()
85 | self.config_dir = jstest.TemporaryDirectory()
86 | self.nbdir = jstest.TemporaryDirectory()
87 | self.home = jstest.TemporaryDirectory()
88 | self.env = {
89 | 'HOME': self.home.name,
90 | 'JUPYTER_CONFIG_DIR': self.config_dir.name,
91 | 'IPYTHONDIR': self.ipydir.name,
92 | }
93 | self.dirs.append(self.ipydir)
94 | self.dirs.append(self.home)
95 | self.dirs.append(self.config_dir)
96 | self.dirs.append(self.nbdir)
97 | os.makedirs(os.path.join(self.nbdir.name,
98 | os.path.join(u'sub dir1', u'sub dir 1a')))
99 | os.makedirs(os.path.join(self.nbdir.name,
100 | os.path.join(u'sub dir2', u'sub dir 1b')))
101 |
102 | if self.xunit:
103 | self.add_xunit()
104 |
105 | # If a url was specified, use that for the testing.
106 | if self.url:
107 | try:
108 | alive = jstest.requests.get(self.url).status_code == 200
109 | except:
110 | alive = False
111 |
112 | if alive:
113 | self.cmd.append("--url=%s" % self.url)
114 | else:
115 | raise Exception('Could not reach "%s".' % self.url)
116 | else:
117 | # start the ipython notebook, so we get the port number
118 | self.server_port = 0
119 | self._init_server()
120 | if self.server_port:
121 | self.cmd.append('--url=http://localhost:%i%s' % (
122 | self.server_port, self.base_url))
123 | else:
124 | # don't launch tests if the server didn't start
125 | self.cmd = [
126 | jstest.sys.executable, '-c', 'raise SystemExit(1)']
127 |
128 | def _init_server(self):
129 | "Start the notebook server in a separate process"
130 | self.server_command = command = [
131 | jstest.sys.executable,
132 | '-m', 'notebook',
133 | '--no-browser',
134 | '--notebook-dir', self.nbdir.name,
135 | '--NotebookApp.base_url=%s' % self.base_url,
136 | ]
137 | # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
138 | # which run afoul of ipc's maximum path length.
139 | # if jstest.sys.platform.startswith('linux'):
140 | # command.append('--KernelManager.transport=ipc')
141 | self.stream_capturer = c = jstest.StreamCapturer()
142 | c.start()
143 | env = os.environ.copy()
144 | env.update(self.env)
145 | # if self.engine == 'phantomjs':
146 | # env['IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS'] = '1'
147 | self.server = subprocess.Popen(
148 | command,
149 | stdout=c.writefd,
150 | stderr=jstest.subprocess.STDOUT,
151 | cwd=self.nbdir.name,
152 | env=env,
153 | )
154 | with patch.dict('os.environ', {'HOME': self.home.name}):
155 | runtime_dir = jstest.jupyter_runtime_dir()
156 | self.server_info_file = os.path.join(
157 | runtime_dir,
158 | 'nbserver-%i.json' % self.server.pid
159 | )
160 | self._wait_for_server()
161 |
162 | def cleanup(self):
163 | if hasattr(self, "stream_capturer"):
164 | captured = self.stream_capturer.get_buffer().decode(
165 | 'utf-8', 'replace')
166 | with codecs.open(TEST_LOG, "a+", encoding="utf-8") as fp:
167 | fp.write("-----------------------\n{} results:\n{}\n".format(
168 | self.section,
169 | self.server_command))
170 | fp.write(captured)
171 | print("-----\nJUPYTER JSTESTLOG")
172 | print(captured)
173 | print("-----\nEND JUPYTER JSTESTLOG")
174 |
175 | super(NBPresentTestController, self).cleanup()
176 |
177 |
178 | def prepare_controllers(options):
179 | """Monkeypatched prepare_controllers for running widget js tests
180 |
181 | instead of notebook js tests
182 | """
183 | if options.testgroups:
184 | groups = options.testgroups
185 | else:
186 | groups = ['']
187 | return [NBPresentTestController(g, extra_args=options.extra_args)
188 | for g in groups], []
189 |
190 |
191 | def test_notebook():
192 | with patch.object(jstest, 'prepare_controllers', prepare_controllers):
193 | jstest.main()
194 |
195 |
196 | if __name__ == '__main__':
197 | test_notebook()
198 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": {
3 | "name": "Nicholas Bollweg",
4 | "url": "https://github.com/bollwyvl"
5 | },
6 | "bugs": {
7 | "url": "https://github.com/Anaconda-Platform/nbpresent/issues"
8 | },
9 | "chat": "https://gitter.im/Anaconda-Platform/nbpresent",
10 | "dependencies": {
11 | "baobab": "^2.2.1",
12 | "d3": "~3.5.12",
13 | "html2canvas": "^0.5.0-beta4",
14 | "phantomjs-polyfill": "0.0.1",
15 | "underscore": "^1.8.3",
16 | "uuid": "^2.0.1",
17 | "webfontloader": "^1.6.21"
18 | },
19 | "description": "This will be the home of the next generation of slide authoring presentation for [Jupyter Notebooks](https://github.com/jupyter/notebook).",
20 | "devDependencies": {
21 | "babel": "^6.1.18",
22 | "babel-eslint": "^4.1.8",
23 | "babel-preset-es2015": "^6.1.18",
24 | "babelify": "^7.2.0",
25 | "browserify": "~11.2.0",
26 | "casperjs": "^1.1.1",
27 | "esdoc": "^0.4.0",
28 | "eslint": "^1.10.3",
29 | "jquery": "^2.2.0",
30 | "less-plugin-autoprefix": "~1.5.1",
31 | "less-plugin-clean-css": "~1.5.1",
32 | "less": "~2.5.3",
33 | "lodash": "^3.10.1",
34 | "phantomjs-prebuilt": "^2.1.7",
35 | "requirejs": "^2.1.22",
36 | "uglifyify": "~3.0.1",
37 | "watch": "~0.16.0"
38 | },
39 | "discussion": "https://groups.google.com/forum/#!forum/nbpresent",
40 | "eslintConfig": {
41 | "env": {
42 | "browser": true,
43 | "es6": true
44 | },
45 | "extends": "eslint:recommended",
46 | "parser": "babel-eslint",
47 | "rules": {
48 | "strict": 0
49 | }
50 | },
51 | "homepage": "https://github.com/Anaconda-Platform/nbpresent",
52 | "license": "BSD-3-Clause",
53 | "main": "index.js",
54 | "name": "nbpresent",
55 | "repository": {
56 | "type": "git",
57 | "url": "git://github.com/Anaconda-Platform/nbpresent.git"
58 | },
59 | "scripts": {
60 | "all": "npm run dist && npm run lint && npm run test && npm run docs",
61 | "build": "python -m nbpresent.tasks.build",
62 | "build:deps": "python -m nbpresent.tasks.deps",
63 | "build:dev": "python -m nbpresent.tasks.build dev",
64 | "build:index": "python -m nbpresent.tasks.index",
65 | "build:less": "python -m nbpresent.tasks.less",
66 | "build:notebook": "python -m nbpresent.tasks.notebook",
67 | "build:release": "python -m nbpresent.tasks.build release",
68 | "build:requirejs": "python -m nbpresent.tasks.requirejs",
69 | "build:standalone": "python -m nbpresent.tasks.standalone",
70 | "clean": "python -m nbpresent.tasks.clean",
71 | "docs": "npm run docs:clean && npm run docs:esdoc && npm run docs:sphinx && npm run docs:notebooks",
72 | "docs:clean": "rm -rf nbpresent/static/docs",
73 | "docs:esdoc": "esdoc -c .esdoc",
74 | "docs:notebooks": "nbpresent -i notebooks/index.ipynb -o nbpresent/static/index.html",
75 | "docs:sphinx": "sphinx-apidoc -f -o ./sphinx/source/auto nbpresent && cd sphinx && make html && mv -f build/html ../nbpresent/static/docs/sphinx",
76 | "less": "python -m nbpresent.tasks.less",
77 | "lint": "npm run lint:eslint && npm run lint:flake8",
78 | "lint:eslint": "eslint --ext es6 ./src",
79 | "lint:flake8": "flake8 nbpresent setup.py sphinx",
80 | "pkg:conda": "conda build conda.recipe -c javascript -c mutirri -c cpcloud -c anaconda-nb-extensions -c bokeh -c wakari",
81 | "pkg:pypi": "python setup.py register -r pypitest && python setup.py sdist && python setup.py bdist_wheel && python setup.py sdist upload -r pypitest && python setup.py bdist_wheel upload -r pypitest",
82 | "pkg:pypi:release": "python setup.py register -r pypi && python setup.py sdist && python setup.py bdist_wheel && python setup.py sdist bdist_wheel upload -r pypi",
83 | "readme": "jupyter nbconvert notebooks/README.ipynb --to=rst --stdout > README.rst && jupyter nbconvert notebooks/README.ipynb --to=markdown --stdout > README.md",
84 | "test": "python -m nose nbpresent.tests",
85 | "watch": "watch 'npm run build:dev' src",
86 | "watch:test": "watch 'npm run test' ./nbpresent/static/nbpresent/js"
87 | },
88 | "version": "3.0.2"
89 | }
90 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anaconda/nbpresent/6bf2f50b7abd27273dc9e7d469bf1866bafbf5bb/screenshot.png
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
3 |
4 | [nosetests]
5 | verbosity=1
6 | detailed-errors=1
7 | with-coverage=1
8 | cover-package=nbpresent
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from setuptools import setup
4 |
5 |
6 | # should be loaded below
7 | __version__ = None
8 |
9 | with open('nbpresent/_version.py') as version:
10 | exec(version.read())
11 |
12 | with open('./README.rst') as readme:
13 | README = readme.read()
14 |
15 | setup(
16 | name="nbpresent",
17 | version=__version__,
18 | description="Next generation slides from Jupyter Notebooks",
19 | long_description=README,
20 | author="Nicholas Bollweg",
21 | author_email="nbollweg@continuum.io",
22 | license="BSD-3-Clause",
23 | url="https://github.com/Anaconda-Platform/nbpresent",
24 | keywords="ipython jupyter markdown presentation slides revealjs d3",
25 | classifiers=[
26 | "Development Status :: 4 - Beta",
27 | "Framework :: IPython",
28 | "Programming Language :: Python",
29 | "License :: OSI Approved :: BSD License"
30 | ],
31 | packages=["nbpresent"],
32 | setup_requires=["notebook"],
33 | tests_require=["nose", "requests", "coverage"],
34 | include_package_data=True,
35 | entry_points={
36 | 'console_scripts': ['nbpresent=nbpresent.export:main'],
37 | }
38 | )
39 |
--------------------------------------------------------------------------------
/sphinx/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " applehelp to make an Apple Help Book"
34 | @echo " devhelp to make HTML files and a Devhelp project"
35 | @echo " epub to make an epub"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 |
51 | clean:
52 | rm -rf $(BUILDDIR)/*
53 |
54 | html:
55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
56 | @echo
57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
58 |
59 | dirhtml:
60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
61 | @echo
62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
63 |
64 | singlehtml:
65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
66 | @echo
67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
68 |
69 | pickle:
70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
71 | @echo
72 | @echo "Build finished; now you can process the pickle files."
73 |
74 | json:
75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
76 | @echo
77 | @echo "Build finished; now you can process the JSON files."
78 |
79 | htmlhelp:
80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
81 | @echo
82 | @echo "Build finished; now you can run HTML Help Workshop with the" \
83 | ".hhp project file in $(BUILDDIR)/htmlhelp."
84 |
85 | qthelp:
86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
87 | @echo
88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nbpresent.qhcp"
91 | @echo "To view the help file:"
92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nbpresent.qhc"
93 |
94 | applehelp:
95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
96 | @echo
97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
98 | @echo "N.B. You won't be able to view it unless you put it in" \
99 | "~/Library/Documentation/Help or install it in your application" \
100 | "bundle."
101 |
102 | devhelp:
103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
104 | @echo
105 | @echo "Build finished."
106 | @echo "To view the help file:"
107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/nbpresent"
108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/nbpresent"
109 | @echo "# devhelp"
110 |
111 | epub:
112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
113 | @echo
114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
115 |
116 | latex:
117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
118 | @echo
119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
121 | "(use \`make latexpdf' here to do that automatically)."
122 |
123 | latexpdf:
124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
125 | @echo "Running LaTeX files through pdflatex..."
126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
128 |
129 | latexpdfja:
130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
131 | @echo "Running LaTeX files through platex and dvipdfmx..."
132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
134 |
135 | text:
136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
137 | @echo
138 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
139 |
140 | man:
141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
142 | @echo
143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
144 |
145 | texinfo:
146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
147 | @echo
148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
149 | @echo "Run \`make' in that directory to run these through makeinfo" \
150 | "(use \`make info' here to do that automatically)."
151 |
152 | info:
153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
154 | @echo "Running Texinfo files through makeinfo..."
155 | make -C $(BUILDDIR)/texinfo info
156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
157 |
158 | gettext:
159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
160 | @echo
161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
162 |
163 | changes:
164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
165 | @echo
166 | @echo "The overview file is in $(BUILDDIR)/changes."
167 |
168 | linkcheck:
169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
170 | @echo
171 | @echo "Link check complete; look for any errors in the above output " \
172 | "or in $(BUILDDIR)/linkcheck/output.txt."
173 |
174 | doctest:
175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
176 | @echo "Testing of doctests in the sources finished, look at the " \
177 | "results in $(BUILDDIR)/doctest/output.txt."
178 |
179 | coverage:
180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
181 | @echo "Testing of coverage in the sources finished, look at the " \
182 | "results in $(BUILDDIR)/coverage/python.txt."
183 |
184 | xml:
185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
186 | @echo
187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
188 |
189 | pseudoxml:
190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
191 | @echo
192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
193 |
--------------------------------------------------------------------------------
/sphinx/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | echo. coverage to run coverage check of the documentation if enabled
41 | goto end
42 | )
43 |
44 | if "%1" == "clean" (
45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
46 | del /q /s %BUILDDIR%\*
47 | goto end
48 | )
49 |
50 |
51 | REM Check if sphinx-build is available and fallback to Python version if any
52 | %SPHINXBUILD% 2> nul
53 | if errorlevel 9009 goto sphinx_python
54 | goto sphinx_ok
55 |
56 | :sphinx_python
57 |
58 | set SPHINXBUILD=python -m sphinx.__init__
59 | %SPHINXBUILD% 2> nul
60 | if errorlevel 9009 (
61 | echo.
62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
63 | echo.installed, then set the SPHINXBUILD environment variable to point
64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
65 | echo.may add the Sphinx directory to PATH.
66 | echo.
67 | echo.If you don't have Sphinx installed, grab it from
68 | echo.http://sphinx-doc.org/
69 | exit /b 1
70 | )
71 |
72 | :sphinx_ok
73 |
74 |
75 | if "%1" == "html" (
76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
80 | goto end
81 | )
82 |
83 | if "%1" == "dirhtml" (
84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
88 | goto end
89 | )
90 |
91 | if "%1" == "singlehtml" (
92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
93 | if errorlevel 1 exit /b 1
94 | echo.
95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
96 | goto end
97 | )
98 |
99 | if "%1" == "pickle" (
100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
101 | if errorlevel 1 exit /b 1
102 | echo.
103 | echo.Build finished; now you can process the pickle files.
104 | goto end
105 | )
106 |
107 | if "%1" == "json" (
108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
109 | if errorlevel 1 exit /b 1
110 | echo.
111 | echo.Build finished; now you can process the JSON files.
112 | goto end
113 | )
114 |
115 | if "%1" == "htmlhelp" (
116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
117 | if errorlevel 1 exit /b 1
118 | echo.
119 | echo.Build finished; now you can run HTML Help Workshop with the ^
120 | .hhp project file in %BUILDDIR%/htmlhelp.
121 | goto end
122 | )
123 |
124 | if "%1" == "qthelp" (
125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
129 | .qhcp project file in %BUILDDIR%/qthelp, like this:
130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\nbpresent.qhcp
131 | echo.To view the help file:
132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\nbpresent.ghc
133 | goto end
134 | )
135 |
136 | if "%1" == "devhelp" (
137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished.
141 | goto end
142 | )
143 |
144 | if "%1" == "epub" (
145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
149 | goto end
150 | )
151 |
152 | if "%1" == "latex" (
153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
157 | goto end
158 | )
159 |
160 | if "%1" == "latexpdf" (
161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
162 | cd %BUILDDIR%/latex
163 | make all-pdf
164 | cd %~dp0
165 | echo.
166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdfja" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf-ja
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "text" (
181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
182 | if errorlevel 1 exit /b 1
183 | echo.
184 | echo.Build finished. The text files are in %BUILDDIR%/text.
185 | goto end
186 | )
187 |
188 | if "%1" == "man" (
189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
190 | if errorlevel 1 exit /b 1
191 | echo.
192 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
193 | goto end
194 | )
195 |
196 | if "%1" == "texinfo" (
197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
198 | if errorlevel 1 exit /b 1
199 | echo.
200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
201 | goto end
202 | )
203 |
204 | if "%1" == "gettext" (
205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
206 | if errorlevel 1 exit /b 1
207 | echo.
208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
209 | goto end
210 | )
211 |
212 | if "%1" == "changes" (
213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
214 | if errorlevel 1 exit /b 1
215 | echo.
216 | echo.The overview file is in %BUILDDIR%/changes.
217 | goto end
218 | )
219 |
220 | if "%1" == "linkcheck" (
221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
222 | if errorlevel 1 exit /b 1
223 | echo.
224 | echo.Link check complete; look for any errors in the above output ^
225 | or in %BUILDDIR%/linkcheck/output.txt.
226 | goto end
227 | )
228 |
229 | if "%1" == "doctest" (
230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
231 | if errorlevel 1 exit /b 1
232 | echo.
233 | echo.Testing of doctests in the sources finished, look at the ^
234 | results in %BUILDDIR%/doctest/output.txt.
235 | goto end
236 | )
237 |
238 | if "%1" == "coverage" (
239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
240 | if errorlevel 1 exit /b 1
241 | echo.
242 | echo.Testing of coverage in the sources finished, look at the ^
243 | results in %BUILDDIR%/coverage/python.txt.
244 | goto end
245 | )
246 |
247 | if "%1" == "xml" (
248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
249 | if errorlevel 1 exit /b 1
250 | echo.
251 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
252 | goto end
253 | )
254 |
255 | if "%1" == "pseudoxml" (
256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
257 | if errorlevel 1 exit /b 1
258 | echo.
259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
260 | goto end
261 | )
262 |
263 | :end
264 |
--------------------------------------------------------------------------------
/sphinx/source/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from nbpresent._version import __version__
3 |
4 |
5 | extensions = [
6 | 'sphinx.ext.autodoc',
7 | 'sphinx.ext.intersphinx',
8 | 'sphinx.ext.todo',
9 | 'sphinx.ext.coverage',
10 | 'sphinx.ext.viewcode',
11 | ]
12 |
13 | templates_path = ['_templates']
14 | source_suffix = '.rst'
15 | master_doc = 'index'
16 | project = 'nbpresent'
17 | copyright = '2015, Continuum Analytics'
18 | author = 'Nicholas Bollweg'
19 | version = __version__
20 | release = __version__
21 | language = None
22 | exclude_patterns = []
23 | pygments_style = 'sphinx'
24 | todo_include_todos = True
25 | html_theme = 'alabaster'
26 | html_static_path = ['_static']
27 | htmlhelp_basename = 'nbpresentdoc'
28 | latex_elements = {}
29 | latex_documents = [
30 | (master_doc, 'nbpresent.tex', 'nbpresent Documentation',
31 | 'Nicholas Bollweg', 'manual'),
32 | ]
33 | man_pages = [(master_doc, 'nbpresent', 'nbpresent Documentation', [author], 1)]
34 | texinfo_documents = [
35 | (master_doc, 'nbpresent', 'nbpresent Documentation', author, 'nbpresent',
36 | 'One line description of project.', 'Miscellaneous'),
37 | ]
38 | intersphinx_mapping = {'https://docs.python.org/': None}
39 |
--------------------------------------------------------------------------------
/sphinx/source/index.rst:
--------------------------------------------------------------------------------
1 | .. nbpresent documentation master file, created by
2 | sphinx-quickstart on Tue Dec 15 07:43:19 2015.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to nbpresent's documentation!
7 | =====================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 |
15 | .. automodule:: nbpresent
16 | :members:
17 |
18 |
19 | Indices and tables
20 | ==================
21 |
22 | * :ref:`genindex`
23 | * :ref:`modindex`
24 | * :ref:`search`
25 |
--------------------------------------------------------------------------------
/src/es6/actions/base.es6:
--------------------------------------------------------------------------------
1 | export const NS = "nbpresent";
2 |
3 | export class BaseActions {
4 | constructor(keyActions=[]){
5 | this.keyActions = keyActions;
6 | this.register();
7 | }
8 |
9 | register(){
10 |
11 | }
12 |
13 | push(){
14 | }
15 |
16 | pop(){
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/es6/actions/notebook.es6:
--------------------------------------------------------------------------------
1 | import Jupyter from "base/js/namespace";
2 | import {BaseActions, NS} from "./base";
3 |
4 | export class NotebookActions extends BaseActions{
5 | register(){
6 | this._kbm = Jupyter.notebook.keyboard_manager;
7 |
8 | this.keyActions.map(({name, value}) => {
9 | this._kbm.actions.register(value, name, NS);
10 | });
11 | }
12 |
13 | push(){
14 | this.keyActions.map(({name, keys=[]}) => {
15 | keys.map((key) => {
16 | this._kbm.command_shortcuts.add_shortcut(key, `${NS}:${name}`);
17 | })
18 | });
19 | }
20 |
21 | pop(){
22 | this.keyActions.map(({keys=[], name}) => {
23 | keys.map((key) => {
24 | if(this._kbm.command_shortcuts.get_shortcut(key) === `${NS}:${name}`){
25 | this._kbm.command_shortcuts.remove_shortcut(key);
26 | }
27 | });
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/es6/actions/standalone.es6:
--------------------------------------------------------------------------------
1 | import {BaseActions} from "./base";
2 |
3 | export class StandaloneActions extends BaseActions{
4 | }
5 |
--------------------------------------------------------------------------------
/src/es6/cells/base.es6:
--------------------------------------------------------------------------------
1 | /** @external {Cell} https://github.com/jupyter/notebook/blob/master/notebook/static/notebook/js/cell.js */
2 |
3 | import {d3, html2canvas} from "nbpresent-deps";
4 |
5 | import {log} from "../logger";
6 | import {PART, PART_SELECT} from "../parts";
7 |
8 | let _THUMBS = {};
9 |
10 |
11 | export class BaseCellManager {
12 | /** return an id-indexed set of {@link Cell}-like objects: each has the
13 | * a fake jQuery `element` member), as embedded in HTML
14 | * @abstract
15 | * @return {Object} */
16 | getCells(){
17 | throw Error("Not Implemented");
18 | }
19 |
20 | getPart(content, element){
21 | let cell = this.getCells()[content.cell];
22 |
23 | if(!cell && !element){
24 | return null;
25 | }
26 |
27 | let $el = d3.select(element || cell.element[0]),
28 | part = content.part === PART.whole ?
29 | $el : $el.select(PART_SELECT[content.part]);
30 |
31 | return part;
32 | }
33 |
34 | clearThumbnails(contents=[]){
35 | let keys = Object.keys(_THUMBS);
36 | if(contents.length){
37 | keys = contents.map((content) => this.contentKey(content));
38 | }
39 | keys.map((key)=>{ delete _THUMBS[key]; });
40 | }
41 |
42 | contentKey(content){
43 | return `${content.cell}-${content.part}`;
44 | }
45 |
46 | thumbnail(content){
47 | let el = this.getPart(content);
48 | el = el ? el.node() : null;
49 | if(!el){
50 | return Promise.reject(content);
51 | }
52 | return html2canvas(el)
53 | .then((canvas) => {
54 | return {
55 | canvas,
56 | uri: canvas.toDataURL("image/png"),
57 | width: canvas.width,
58 | height: canvas.height
59 | };
60 | }, (err) => log.error("thumbnail error", content, err));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/es6/cells/notebook.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 | import Jupyter from "base/js/namespace";
3 |
4 | import {PART_SELECT} from "../parts";
5 |
6 | import {BaseCellManager} from "./base";
7 |
8 |
9 | /** Wraps the Jupyter Notebook for working with {@link Cell}s
10 | * @extends {BaseCellManager} */
11 | export class NotebookCellManager extends BaseCellManager {
12 | /** returns an id-indexed set of {@link Cells} from the main namespace i.e.
13 | * `window.Jupyter`
14 | * @return {Object} */
15 | getCells(){
16 | return Jupyter.notebook.get_cells().reduce((memo, cell)=> {
17 | if(cell.metadata.nbpresent){
18 | memo[cell.metadata.nbpresent.id] = cell;
19 | }
20 | return memo;
21 | }, {});
22 | }
23 |
24 | cellId(cell){
25 | try {
26 | return cell.metadata.nbpresent.id;
27 | }catch(err){
28 | return null;
29 | }
30 | }
31 |
32 | /**
33 | Generate a sparse matrix of:
34 | cell * part * {cell, part, bb}
35 | ]
36 | */
37 | cellPartGeometry(){
38 | let mgr = this;
39 | return Jupyter.notebook.get_cells().map((cell)=>{
40 | let key = this.cellId(cell);
41 |
42 | let parts = d3.entries(PART_SELECT).map((part)=>{
43 | return mgr.getPart({cell: key, part: part.key}, cell.element[0])
44 | .reduce(function(memo, data){
45 | let el = data[0],
46 | bb = el && el.getBoundingClientRect();
47 | if(bb && bb.height){
48 | memo.push({cell: {key, value: cell}, part: part.key, bb});
49 | }
50 | return memo;
51 | }, []);
52 | });
53 | return parts;
54 | });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/es6/cells/overlay.es6:
--------------------------------------------------------------------------------
1 | import {d3, _} from "nbpresent-deps";
2 |
3 | import {PART} from "../parts";
4 |
5 | export class LinkOverlay{
6 | constructor(manager, done){
7 | this.manager = manager;
8 | this.done = done;
9 | this.init();
10 | }
11 |
12 | init(){
13 | this.$body = d3.select("body");
14 | this.$header = this.$body.select("#header");
15 | this.$site = this.$body.select("#site");
16 | this.$ui = this.$body.select("#notebook-container")
17 | .append("div")
18 | .classed({"nbp-link-overlay": 1});
19 | this.update();
20 |
21 | _.defer(() => this.$body.classed({"nbp-linking": 1}));
22 | }
23 |
24 | destroy(){
25 | this.$body.classed({"nbp-linking": 0});
26 | _.delay(() => this.$ui.remove(), 300);
27 | }
28 |
29 | update(){
30 | let heightOffset = this.$header.property("clientHeight") -
31 | this.$site.property("scrollTop"),
32 | parts = _.chain(this.manager.cellPartGeometry())
33 | .flatten()
34 | .filter(({bb}) => bb)
35 | .value();
36 |
37 | let $part = this.$ui.selectAll(".nbp-part-overlay")
38 | .data(parts);
39 |
40 | $part.enter()
41 | .append("button").classed({"btn nbp-part-overlay": 1})
42 | .on("click", ({part, cell}) => this.done({part, cell: cell.value}))
43 | .text(({part}) => part);
44 |
45 | $part.classed({
46 | "nbp-part-overlay-source": ({part}) => part === PART.source,
47 | "nbp-part-overlay-outputs": ({part}) => part === PART.outputs,
48 | "nbp-part-overlay-widgets": ({part}) => part === PART.widgets,
49 | "nbp-part-overlay-whole": ({part}) => part === PART.whole
50 | });
51 |
52 | $part.style({
53 | top: ({bb}) => `${bb.top - heightOffset}px`,
54 | height: ({bb}) => `${bb.height - 1}px`
55 | });
56 |
57 | $part.exit().remove();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/es6/cells/standalone.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 | import {BaseCellManager} from "./base";
3 |
4 |
5 | /** A Cell Manager for static HTML
6 | * @extends {BaseCellManager} */
7 | export class StandaloneCellManager extends BaseCellManager {
8 | /** return an id-indexed set of {@link Cell}-like objects: each has the
9 | * a fake jQuery `element` member), as embedded in HTML
10 | * @return {Object} */
11 | getCells(){
12 | let cells = {};
13 |
14 | d3.selectAll(".cell").each(function(){
15 | let el = d3.select(this),
16 | id = el.attr("data-nbp-id");
17 |
18 | if(id){
19 | cells[id] = {
20 | element: [el.node()]
21 | }
22 | }
23 | });
24 | return cells;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/es6/d3.bbox.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015 Lucas Beyer
2 | // Licensed under the MIT License (MIT)
3 | // Version 1.0
4 | // https://github.com/lucasb-eyer/d3-boundingbox
5 |
6 | // ES6 "translation" by (c) 2015 Nick Bollweg
7 | /* eslint-disable */
8 |
9 | import {d3} from "nbpresent-deps";
10 |
11 | export function bbox() {
12 | // All those are initialized to default further down using the setters.
13 | var xextent = null
14 | var yextent = null
15 | var handlesize = null
16 | var dirs = null
17 | var curs = null
18 | var cbs = {
19 | dragstart: null,
20 | dragmove: null,
21 | dragend: null,
22 | resizestart: null,
23 | resizemove: null,
24 | resizeend: null
25 | }
26 |
27 | function my(selection) {
28 | var drag = d3.behavior.drag()
29 | .origin(function(d, i) { return {x: this.getAttribute("x"), y: this.getAttribute("y")}; })
30 | .on("drag.lbbbox", dragmove)
31 | .on("dragstart.lbbbox", dragstart)
32 | .on("dragend.lbbbox", dragend)
33 | selection.call(drag)
34 | selection.on("mousemove.lbbbox", move)
35 | selection.on("mouseleave.lbbbox", leave)
36 |
37 | return selection
38 | }
39 |
40 | function clamp(x, extent) { return Math.max(extent[0], Math.min(x, extent[1])); }
41 | function inside(x, extent) { return extent[0] < x && x < extent[1]; }
42 |
43 | // Will return w, nw, n, ne, e, se, s, sw for the eight borders,
44 | // M for inside, or "" when current location not in `dirs`.
45 | function whichborder(xy, elem) {
46 | var border = ""
47 | var x = +elem.getAttribute("x")
48 | var y = +elem.getAttribute("y")
49 | var w = +elem.getAttribute("width")
50 | var h = +elem.getAttribute("height")
51 |
52 | if(xy[1] < y + handlesize.n) border += 'n'
53 | else if(xy[1] > y + h - handlesize.s) border += 's'
54 |
55 | if(xy[0] < x + handlesize.w) border += 'w'
56 | else if(xy[0] > x + w - handlesize.e) border += 'e'
57 |
58 |
59 | if(border == "" && (dirs.indexOf("x") > -1 || dirs.indexOf("y") > -1))
60 | border = "M"
61 | else if(dirs.indexOf(border) == -1)
62 | border = ""
63 |
64 | return border
65 | }
66 |
67 | function move(d, i) {
68 | // Don't do anything if we're currently dragging.
69 | // Otherwise, the cursor might jump horribly!
70 | // Also don't do anything if no cursors.
71 | if(this.__resize_action__ !== undefined || !curs)
72 | return
73 |
74 | var b = whichborder(d3.mouse(this), this)
75 |
76 | var x = dirs.indexOf("x")
77 | var y = dirs.indexOf("y")
78 | // Bwahahahaha this works even when one is at index 0.
79 | if(b == "M" && 1/(x*y) < 0)
80 | document.body.style.cursor = x >= 0 ? curs.x : curs.y
81 | else
82 | document.body.style.cursor = curs[b] || null
83 | }
84 |
85 | function leave(d, i) {
86 | // Only unset cursor if we're not dragging,
87 | // otherwise we get horrible cursor-flipping action.
88 | // Also only unset it if we actually did set it!
89 | if(this.__resize_action__ === undefined && curs)
90 | document.body.style.cursor = null
91 | }
92 |
93 | function dragstart(d, i) {
94 | this.__resize_action__ = whichborder(d3.mouse(this), this)
95 | this.__ow__ = +this.getAttribute("width")
96 | this.__oh__ = +this.getAttribute("height")
97 |
98 | if(this.__resize_action__ == "M") {
99 | if(cbs.dragstart) cbs.dragstart.call(this, d, i)
100 | } else if(this.__resize_action__.length) {
101 | if(cbs.resizestart) cbs.resizestart.call(this, d, i)
102 | }
103 | }
104 |
105 | function dragend(d, i) {
106 | if(this.__resize_action__ == "M") {
107 | if(cbs.dragend) cbs.dragend.call(this, d, i)
108 | } else if(this.__resize_action__.length) {
109 | if(cbs.resizeend) cbs.resizeend.call(this, d, i)
110 | }
111 |
112 | delete this.__resize_action__
113 | delete this.__ow__
114 | delete this.__oh__
115 |
116 | // Still need to unset here, in case the user stop dragging
117 | // while the mouse isn't on the element anymore (e.g. off-limits).
118 | if(curs)
119 | document.body.style.cursor = null
120 | }
121 |
122 | function dragmove(d, i) {
123 | if(this.__resize_action__ == "M") {
124 | if(cbs.dragmove)
125 | if(false === cbs.dragmove.call(this, d, i))
126 | return
127 | } else if(this.__resize_action__.length) {
128 | if(cbs.resizemove)
129 | if(false === cbs.resizemove.call(this, d, i))
130 | return
131 | }
132 |
133 | // Potentially dynamically determine the allowed space.
134 | var xext = typeof xextent === "function" ? xextent.call(this, d, i) : xextent
135 | var yext = typeof yextent === "function" ? yextent.call(this, d, i) : yextent
136 |
137 | // Handle moving around first, more easily.
138 | if(this.__resize_action__ == "M") {
139 | if(dirs.indexOf("x") > -1 && d3.event.dx != 0)
140 | // This is so that even moving the mouse super-fast, this still "sticks" to the extent.
141 | this.setAttribute("x", clamp(clamp(d3.event.x, xext) + this.__ow__, xext) - this.__ow__)
142 | if(dirs.indexOf("y") > -1 && d3.event.dy != 0)
143 | this.setAttribute("y", clamp(clamp(d3.event.y, yext) + this.__oh__, yext) - this.__oh__)
144 | // Now check for all possible resizes.
145 | } else {
146 | var x = +this.getAttribute("x")
147 | var y = +this.getAttribute("y")
148 |
149 | // First, check for vertical resizes,
150 | if(/^n/.test(this.__resize_action__)) {
151 | var b = y + +this.getAttribute("height")
152 | var newy = clamp(clamp(d3.event.y, yext), [-Infinity, b-1])
153 | this.setAttribute("y", newy)
154 | this.setAttribute("height", b - newy)
155 | } else if(/^s/.test(this.__resize_action__)) {
156 | var b = clamp(d3.event.y + this.__oh__, yext)
157 | this.setAttribute("height", clamp(b - y, [1, Infinity]))
158 | }
159 |
160 | // and then for horizontal ones. Note both may happen.
161 | if(/w$/.test(this.__resize_action__)) {
162 | var r = x + +this.getAttribute("width")
163 | var newx = clamp(clamp(d3.event.x, xext), [-Infinity, r-1])
164 | this.setAttribute("x", newx)
165 | this.setAttribute("width", r - newx)
166 | } else if(/e$/.test(this.__resize_action__)) {
167 | var r = clamp(d3.event.x + this.__ow__, xext)
168 | this.setAttribute("width", clamp(r - x, [1, Infinity]))
169 | }
170 | }
171 | }
172 |
173 | my.xextent = function(_) {
174 | if(!arguments.length) return xextent
175 | xextent = _ !== false ? _ : [-Infinity, +Infinity]
176 | return my
177 | }
178 | my.xextent(false)
179 |
180 | my.yextent = function(_) {
181 | if(!arguments.length) return yextent
182 | yextent = _ !== false ? _ : [-Infinity, +Infinity]
183 | return my
184 | }
185 | my.yextent(false)
186 |
187 | my.handlesize = function(_) {
188 | if(!arguments.length) return handlesize
189 | handlesize = !+_ ? _ : {'w': _,'n': _,'e': _,'s': _} // coolface
190 | return my
191 | }
192 | my.handlesize(3)
193 |
194 | my.cursors = function(_) {
195 | if(!arguments.length) return curs
196 | curs = _ !== true ? _ : {
197 | M: "move",
198 | x: "col-resize",
199 | y: "row-resize",
200 | n: "n-resize",
201 | e: "e-resize",
202 | s: "s-resize",
203 | w: "w-resize",
204 | nw: "nw-resize",
205 | ne: "ne-resize",
206 | se: "se-resize",
207 | sw: "sw-resize"
208 | }
209 | return my
210 | }
211 | my.cursors(true)
212 |
213 | my.directions = function(_) {
214 | if(!arguments.length) return dirs
215 | dirs = _ !== true ? _ : ["n", "e", "s", "w", "nw", "ne", "se", "sw", "x", "y"]
216 | return my
217 | }
218 | my.directions(true)
219 |
220 | my.on = function(name, cb) {
221 | if(cb === undefined) return cbs[name]
222 | cbs[name] = cb
223 | return my
224 | }
225 |
226 | my.infect = function(selection) {
227 | selection.call(my)
228 | return my
229 | }
230 |
231 | my.disinfect = function(selection) {
232 | selection.on(".drag", null)
233 | selection.on(".lbbbox", null)
234 | return my
235 | }
236 |
237 | return my
238 | }
239 |
--------------------------------------------------------------------------------
/src/es6/editor.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {PART} from "./parts";
4 |
5 | import {RegionTree} from "./regiontree";
6 | import {NotebookCellManager} from "./cells/notebook";
7 | import {NotebookActions} from "./actions/notebook";
8 |
9 | import {bbox} from "./d3.bbox";
10 |
11 | const DIRS = ["left", "right", "up", "down"],
12 | DIR_ATTR = {
13 | left: "x",
14 | right: "x",
15 | up: "y",
16 | down: "y"
17 | },
18 | DIR_DELTA = {
19 | left: -1,
20 | right: 1,
21 | up: -1,
22 | down: 1
23 | };
24 |
25 | /** The visual editor overlay for drag-and-drop positioning of slide regions.
26 | */
27 | export class Editor {
28 | /** Make a new Editor.
29 | * @param {baobab.Cursor} slide - the slide to edit
30 | * @param {object} selectedRegion - the slide id and region id
31 | * @listens {/slides/{slide}} */
32 | constructor(slide, selectedRegion) {
33 | /** whether this Editor has been killed.
34 | * @type {bool} */
35 | this.destroyed = false;
36 |
37 | /** cursor pointed at a specific slide.
38 | * @type {baobab.Cursor} */
39 | this.slide = slide;
40 |
41 | /** the currently selected region
42 | * @type {object}
43 | * @param {baobab.Cursor} the region/slide to edit */
44 | this.selectedRegion = selectedRegion;
45 |
46 | /** a sub-cursor for just region changes
47 | * @type {baobab.Cursor} */
48 | this.regions = this.slide.select("regions");
49 |
50 | /** handles bookkeeping with the browser
51 | * @type {NotebookCellManager} */
52 | this.cellManager = new NotebookCellManager();
53 |
54 | // TODO: make these discrete to base unit
55 | /** the relative size to screen width transfer function
56 | * @type {d3.scale} */
57 | this.x = d3.scale.linear();
58 |
59 | /** the relative size to screen height transfer function
60 | * @type {d3.scale} */
61 | this.y = d3.scale.linear();
62 |
63 | // ready to init
64 | this.initUI();
65 |
66 | // TODO: move this?
67 | /** sub-ui for editing regions
68 | * @type {RegionTree} */
69 | this.sidebar = new RegionTree(this.slide, this.selectedRegion);
70 |
71 | // ready to behave
72 | this.initBehavior()
73 | .initActions();
74 |
75 | // fake data event
76 | this.update();
77 |
78 | this.watcher = this.slide.tree.watch({
79 | slide: this.slide,
80 | selectedRegion: this.selectedRegion,
81 | regions: this.regions
82 | });
83 |
84 | // subscribe to event when cursor changes
85 | this.watcher.on("update", () => this.destroyed || this.update());
86 | }
87 |
88 | /** Destroy the editor and its children utterly. */
89 | destroy() {
90 | this.deinitActions();
91 | this.sidebar.destroy();
92 | this.$ui.remove();
93 | this.watcher.release();
94 |
95 | this.destroyed = true;
96 | }
97 |
98 |
99 | /** Create d3 behaviors.
100 | * @return {Editor} */
101 | initBehavior(){
102 | /** @type {d3.bbox} */
103 | this.bbox = bbox();
104 |
105 | return this;
106 | }
107 |
108 | /** Create all UI chrome.
109 | * @return {Editor} */
110 | initUI(){
111 | this.$body = d3.select("body");
112 |
113 | /** @type {d3.selection} */
114 | this.$ui = d3.select("body")
115 | .append("div")
116 | .classed({"nbp-editor": 1});
117 |
118 | /** @type {d3.selection} */
119 | this.$bg = this.$ui.append("div")
120 | .classed({slide_bg: 1});
121 |
122 | /** @type {d3.selection} */
123 | this.$svg = this.$bg.append("svg");
124 |
125 | /** @type {d3.selection} */
126 | this.$defs = this.$svg.append("defs");
127 |
128 | return this;
129 | }
130 |
131 |
132 | /**
133 | * A stand-in for themed padding
134 | * @return {Number}
135 | */
136 | padding(){
137 | return 20;
138 | }
139 |
140 |
141 | /**
142 | * A stand-in for themed aspect ratio
143 | * @return {Number}
144 | */
145 | aspectRatio(){
146 | // TODO: put in data
147 | return 16 / 9;
148 | }
149 |
150 | /**
151 | * The "end" event of the d3.bbox control.
152 | * @param {Element} el - the DOM element (usually `this` in d3 callbacks)
153 | * @param {Object} d - the datum for the element
154 | */
155 | bbEnd(el){
156 | let $el = d3.select(el),
157 | {region} = this.selectedRegion.get() || {},
158 | scales = {
159 | x: this.x,
160 | y: this.y,
161 | width: this.x,
162 | height: this.y
163 | };
164 |
165 | if(!region){
166 | return;
167 | }
168 |
169 | this.slide.merge(["regions", region, "attrs"],
170 | d3.entries(scales)
171 | .reduce((memo, {key, value}) => {
172 | memo[key] = value.invert($el.attr(key));
173 | return memo;
174 | }, {}));
175 | }
176 |
177 | /**
178 | * A d3 filter factory that determines whether a region contains a part of a
179 | * part type.
180 | * @param {PART_SELECT} part - the type of part to seek
181 | * @return {bool}
182 | */
183 | hasContent(part){
184 | return (d) => (d.value.content || {}).part === part;
185 | }
186 |
187 | /**
188 | * The main tree callback.
189 | */
190 | update(){
191 | // TODO: this really needs to be refactored.
192 | let that = this,
193 | uibb = this.$ui.node().getBoundingClientRect(),
194 | width = uibb.width - ((2 * this.padding())),
195 | height = width / this.aspectRatio(),
196 | regions = d3.entries(this.regions.get() || {}),
197 | {region} = this.selectedRegion.get() || {},
198 | {x, y} = this;
199 |
200 | if(height > uibb.height + 2 * this.padding()){
201 | height = uibb.height - (2 * this.padding());
202 | width = height * this.aspectRatio();
203 | }
204 |
205 | this.x.range([0, width]);
206 | this.y.range([0, height]);
207 |
208 | this.$bg.style({
209 | left: `${(((uibb.width) - width) / 2)}px`,
210 | top: `${(uibb.height - height) / 2}px`,
211 | width: `${width}px`,
212 | height: `${height}px`
213 | });
214 |
215 | this.$svg.attr({width, height});
216 |
217 | regions.sort((a, b) =>
218 | (region === a.key) ? 1 :
219 | (region === b.key) ? -1 :
220 | (a.value.attrs.z || 0) - (b.value.attrs.z || 0));
221 |
222 | let $region = this.$svg.selectAll(".nbp-region")
223 | .data(regions, ({key}) => key)
224 | .order();
225 |
226 | $region.enter()
227 | .append("g")
228 | .classed({"nbp-region": 1})
229 | .call(($region) => {
230 | $region.append("rect")
231 | .classed({"nbp-region-bg": 1})
232 | .each(function(){
233 | that.bbox.infect(d3.select(this))
234 | .on("dragend", function(d){ that.bbEnd(this, d) })
235 | .on("resizeend", function(d){ that.bbEnd(this, d) });
236 | });
237 | })
238 | .on("mousedown", (d) => {
239 | this.selectedRegion.set({slide: this.slide.get("id"), region: d.key});
240 | });
241 |
242 | $region
243 | .classed({
244 | active: ({key}) => key === region,
245 | "nbp-content-source": this.hasContent(PART.source),
246 | "nbp-content-outputs": this.hasContent(PART.outputs),
247 | "nbp-content-widgets": this.hasContent(PART.widgets),
248 | "nbp-content-whole": this.hasContent(PART.whole),
249 | "nbp-content-null": this.hasContent(null)
250 | })
251 | .select(".nbp-region-bg")
252 | .transition()
253 | .attr({
254 | width: (d) => x(d.value.attrs.width),
255 | height: (d) => y(d.value.attrs.height),
256 | x: (d) => x(d.value.attrs.x),
257 | y: (d) => y(d.value.attrs.y)
258 | });
259 |
260 | $region.filter(({value}) => !value.content)
261 | .select(".nbp-region-bg")
262 | .style({fill: null});
263 |
264 | $region.exit()
265 | .transition()
266 | .style({opacity: 0})
267 | .remove();
268 | }
269 |
270 | cycleRegion(delta=1){
271 | let {region} = this.selectedRegion.get() || {},
272 | regions = d3.entries(this.regions.get() || {}),
273 | idx = regions.map(({key, value}, i) => key === region ? i : -1)
274 | .filter((i) => i !== -1)[0];
275 |
276 | idx = idx + delta;
277 | if(idx === -1){
278 | idx = regions.length - 1;
279 | }
280 |
281 | this.selectedRegion.set(["region"], regions[idx % regions.length].key);
282 | }
283 |
284 | moveRegion(env, direction, amount=0.1){
285 | let {region} = this.selectedRegion.get() || {},
286 | attr = DIR_ATTR[direction],
287 | delta = DIR_DELTA[direction];
288 |
289 | if(region){
290 | this.regions.set(
291 | [region, "attrs", attr],
292 | this.regions.get([region, "attrs", attr]) + (delta * amount)
293 | )
294 | }
295 | return this;
296 | }
297 |
298 | initActions(){
299 | let _actions = [{
300 | keys: ["shift-tab"],
301 | name: "prev-region",
302 | value: {
303 | icon: "caret-square-o-left",
304 | help: "select previous region",
305 | handler: () => this.cycleRegion()
306 | }
307 | },
308 | {
309 | name: "next-region",
310 | keys: ["tab"],
311 | value: {
312 | icon: "caret-square-o-right",
313 | help: "select next region",
314 | handler: () => this.cycleRegion(-1)
315 | }
316 | }
317 | ];
318 |
319 | DIRS.map((dir) => {
320 | _actions.push({
321 | name: `nudge-region-${dir}`,
322 | keys: [dir],
323 | value: {
324 | icon: `arrow-circle-o-${dir}`,
325 | help: `nudge current region ${dir}`,
326 | handler: (env) => this.moveRegion(env, dir, 0.01)
327 | }
328 | });
329 |
330 | _actions.push({
331 | name: `move-region-${dir}`,
332 | keys: [`shift-${dir}`],
333 | value: {
334 | icon: `arrow-circle-o-${dir}`,
335 | help: `move current region ${dir}`,
336 | handler: (env) => this.moveRegion(env, dir, 0.1)
337 | }
338 | });
339 | });
340 |
341 | this.actions = new NotebookActions(_actions);
342 | this.actions.push();
343 |
344 | return this;
345 | }
346 |
347 | deinitActions(){
348 | this.actions && this.actions.pop();
349 | return this;
350 | }
351 |
352 | }
353 |
--------------------------------------------------------------------------------
/src/es6/help/helper.es6:
--------------------------------------------------------------------------------
1 | import {d3, _} from "nbpresent-deps";
2 |
3 | import {PKG} from "../package";
4 |
5 | import {IntroTour} from "./tours/intro";
6 | import {SlideEditorTour} from "./tours/slide-editor";
7 | import {SorterTour} from "./tours/sorter";
8 |
9 |
10 | const TOURS = {
11 | Intro: IntroTour,
12 | Slides: SorterTour,
13 | Editor: SlideEditorTour
14 | };
15 |
16 |
17 | const COMMUNITY = {
18 | Discuss: ["envelope", PKG.discussion],
19 | Chat: ["comment", PKG.chat],
20 | Contribute: ["code-fork", PKG.homepage],
21 | Bugs: ["bug", PKG.bugs.url]
22 | };
23 |
24 |
25 | export class Helper {
26 | constructor(tree, {mode}){
27 | this.tree = tree;
28 | this.mode = mode;
29 | this.initUI();
30 | _.delay(() => this.$body.classed({"nbp-helping": 1}), 10);
31 | }
32 |
33 | initUI(){
34 | this.$body = d3.select("body");
35 | this.$ui = this.$body.append("div")
36 | .classed({"nbp-helper": 1});
37 |
38 | this.$h2 = this.$ui.append("h2")
39 | .text("nbpresent ")
40 |
41 | this.$h2.append("hr");
42 |
43 | this.$h2
44 | .append("a")
45 | .classed({"nbp-version": 1})
46 | .attr({
47 | href: `${PKG.homepage}#${PKG.version}`
48 | })
49 | .text(PKG.version);
50 |
51 | this.$h1 = this.$ui.append("h1")
52 | .append("i").classed({"fa fa-gift fa-3x": 1});
53 |
54 | this.$ui.append("footer")
55 | .classed({"nbp-legal": 1})
56 | .call((legal) => {
57 | legal.append("span").text("©2016 ");
58 |
59 | legal.append("a")
60 | .attr({href: "https://continuum.io"})
61 | .text("Continuum Analytics");
62 |
63 | legal.append("p")
64 | .call((license) => {
65 | license.append("span").text("nbpresent is ");
66 | license.append("a")
67 | .attr({href: `https://spdx.org/licenses/${PKG.license}`})
68 | .text("free software");
69 | });
70 | });
71 |
72 | this.initTours()
73 | .initCommunity()
74 | .update();
75 | }
76 |
77 | initCommunity(){
78 | this.$ui.append("h2").text("Community");
79 |
80 | this.$ui.selectAll(".nbp-community")
81 | .data(d3.entries(COMMUNITY))
82 | .enter()
83 | .append("a")
84 | .classed({"btn btn-default nbp-community": 1})
85 | .attr({
86 | href: ({value}) => value[1],
87 | target: "_blank"
88 | })
89 | .call((btn) => {
90 | btn.append("i")
91 | .attr({"class": ({value}) => `fa fa-${value[0]} fa-2x`});
92 | btn.append("label").text(({key}) => key);
93 | });
94 |
95 | return this;
96 | }
97 |
98 | initTours(){
99 | this.$tours = this.$ui.append("div")
100 | .classed({"nbp-tours": 1});
101 |
102 | this.$tours.append("h2")
103 | .text("Guided Tours");
104 |
105 | let tour = this.$tours.selectAll(".nbp-tour-launcher")
106 | .data(d3.entries(TOURS));
107 |
108 | tour.enter()
109 | .append("a")
110 | .classed({"btn btn-default nbp-tour-launcher": 1})
111 | .call((btn) => ["i", "label"].map((el) => btn.append(el)))
112 | .on("click", ({value}) => this.startTour(value));
113 |
114 | tour.select("i")
115 | .attr("class", ({value}) => `fa fa-2x fa-${value.icon()}`);
116 | tour.select("label").text(({key}) => ` ${key}`);
117 |
118 | return this;
119 | }
120 |
121 | startTour(Tour){
122 | let tour = new Tour(this.mode);
123 | tour.init();
124 | tour.start(true);
125 |
126 | this.mode.mode.set(null);
127 | }
128 |
129 | update(){
130 | return this;
131 | }
132 |
133 | destroy(){
134 | this.$body.classed({"nbp-helping": 0});
135 | _.delay(() => this.$ui.remove(), 500);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/es6/help/tours/base.es6:
--------------------------------------------------------------------------------
1 | import Tour from "bootstraptour";
2 |
3 | import {d3} from "nbpresent-deps";
4 |
5 |
6 | export class TourBase {
7 | constructor(mode) {
8 | this.mode = mode;
9 | }
10 |
11 | init(){
12 | let opts = this.options();
13 | opts.steps = this.steps();
14 | this.tour = new Tour(opts);
15 | this.tour.init();
16 | return this;
17 | }
18 |
19 | static icon(){
20 | return "question-circle";
21 | }
22 |
23 | start(){
24 | this.tour.start();
25 | return this;
26 | }
27 |
28 | restart(){
29 | this.tour.restart();
30 | return this;
31 | }
32 |
33 | end(){
34 | this.tour.end();
35 | return this;
36 | }
37 |
38 | options(){
39 | return {
40 | name: "nbpresent",
41 | duration: 5000,
42 | storage: false,
43 | debug: true,
44 | orphan: true
45 | };
46 | }
47 |
48 | fakeHover(selector, value){
49 | this.mode.presenter.speaker.hint();
50 | d3.selectAll(selector).classed({"nbp-fake-hover": value});
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/es6/help/tours/intro.es6:
--------------------------------------------------------------------------------
1 | //import {$} from "nbpresent-deps";
2 |
3 | import {ICON} from "../../icons";
4 |
5 | import {TourBase} from "./base";
6 |
7 |
8 | export class IntroTour extends TourBase {
9 | static icon(){
10 | return ICON.intro;
11 | }
12 |
13 | steps(){
14 | return [{
15 | element: "#nbp-app-btn",
16 | title: "Thanks for using nbpresent!",
17 | placement: "bottom",
18 | content: "You just started Authoring a presentation with nbpresent"
19 | }, {
20 | element: "#nbp-app",
21 | title: "App Bar",
22 | placement: "left",
23 | content: "When Authoring, you control the content and style of your presentation. It also activates several special editing keyboard shortcuts"
24 | }, {
25 | element: "#nbp-app-btn",
26 | title: "Stop Authoring",
27 | placement: "bottom",
28 | content: "Clicking this again stops Authoring, and removes all keyboard shortcuts"
29 | }, {
30 | element: "#nbp-present-btn",
31 | placement: "left",
32 | title: "Simply Presenting",
33 | content: "If you just want to run your presentation, without any Authoring tools, you can start Presenting directly."
34 | }, {
35 | element: `.nbp-app-bar .fa-${ICON.presenter}`,
36 | placement: "left",
37 | title: "Presenting/Authoring",
38 | content: "Once we've made some slides, we'll start Presenting, where we can use most Notebook functions with the Theme we have defined, as well as customize slides on the fly."
39 | }, {
40 | element: `.nbp-app-bar .fa-${ICON.slides}`,
41 | placement: "left",
42 | title: "Slides",
43 | content: "Slides, made of Regions linked to Cell Parts are the bread and butter of any presentation, and can be imported, created, linked, reordered, and edited here."
44 | }, {
45 | element: `.nbp-app-bar .fa-${ICON.themer}`,
46 | placement: "left",
47 | title: "Theming",
48 | content: "Theming lets you select colors, typography, and backgrounds to make distinctive presentations."
49 | }, {
50 | element: "#save-notbook",
51 | placement: "bottom",
52 | title: "Saving",
53 | content: "Whenever you save your Notebook, all your presentation data will be stored right in the Notebook .ipynb file"
54 | }, {
55 | element: "#file_menu",
56 | placement: "bottom",
57 | title: "Downloading",
58 | content: "After you've made a presentation, you can download it as an HTML page by selecting Download As: Presentation (.html)"
59 | }, {
60 | element: `.nbp-app-bar .fa-${ICON.help}`,
61 | placement: "left",
62 | title: "Help",
63 | content: "Activate Help at any time to try other tours, connect with the nbpresent developers and community, and other information. Thanks again for using nbpresent."
64 | }
65 | ];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/es6/help/tours/slide-editor.es6:
--------------------------------------------------------------------------------
1 | import {_} from "nbpresent-deps";
2 |
3 | import {ICON} from "../../icons";
4 |
5 | import {SORTER} from "../../mode/notebook";
6 |
7 | import {TourBase} from "./base";
8 |
9 |
10 | export class SlideEditorTour extends TourBase {
11 | static icon(){
12 | return ICON.editor;
13 | }
14 |
15 | steps(){
16 | return [{
17 | element: `.nbp-app-bar .fa-${ICON.slides}`,
18 | title: "So You Made Some Slides",
19 | content: "Once you've made a few slides, you'll likely need to customize them",
20 | placement: "left",
21 | onShown: () => {
22 | this.mode.mode.set(SORTER);
23 | _.delay(() => this.mode.sorter.appendSlide(), 500);
24 | }
25 | }, {
26 | element: `.nbp-deck-toolbar .fa-${ICON.editor}`,
27 | title: "Editing Slides",
28 | content: "Once you have selected a slide, you can activate the Slide Editor",
29 | placement: "left",
30 | onHide: () => _.defer(() => {
31 | this.mode.sorter.editSlide();
32 | })
33 | }, {
34 | element: ".nbp-editor .slide_bg",
35 | placement: "top",
36 | title: "Region Editor",
37 | content: "This is the Region editor. You can click and drag Regions around and resize them."
38 | }, {
39 | element: ".nbp-regiontree",
40 | placement: "right",
41 | title: "Region Tree",
42 | content: "This is the Region tree. It lets you reorder Regions and see the details of how your Regions will show their linked Parts."
43 | }, {
44 | element: `.nbp-regiontree > .nbp-toolbar .fa-${ICON.addRegion}`,
45 | placement: "right",
46 | title: "Add Region",
47 | content: "You can add new regions",
48 | onShow: () => _.defer(() => this.mode.sorter.editor.sidebar.addRegion())
49 | }, {
50 | element: ".region_attr:first-of-type .attr_name",
51 | placement: "right",
52 | title: "Attribute Editor",
53 | content: "All of the properties of a region can be edited here"
54 | }, {
55 | element: `.nbp-regiontree .btn-toolbar .fa-${ICON.treemap}`,
56 | placement: "right",
57 | title: "Data Layouts",
58 | content: "In addition to manually moving regions around, you can use other Layouts, like this Treemap, which will fill the slide",
59 | onHidden: () => {
60 | this.mode.sorter.editor.sidebar.layout("treemap");
61 | }
62 | }, {
63 | element: `.nbp-regiontree > .nbp-toolbar .fa-${ICON.addRegion}`,
64 | placement: "right",
65 | title: "More Regions",
66 | content: "More regions will be added with a weight of 1",
67 | onShow: () => _.defer(() => this.mode.sorter.editor.sidebar.addRegion())
68 | }, {
69 | element: `.region_attr .fa-${ICON.treemap}`,
70 | placement: "right",
71 | title: "Tree Weight",
72 | content: "This new value lets you make a Region bigger or smaller based on relative Weight"
73 | }, {
74 | element: `.nbp-regiontree .btn-toolbar .fa-${ICON.grid}`,
75 | placement: "right",
76 | title: "12 Grid",
77 | content: "The Grid is a comprimise between Free layout and Treemap layout, and rounds all the values to a factor of 12",
78 | onShow: () => this.mode.sorter.editor.sidebar.layout("grid")
79 | }, {
80 | title: "FIN",
81 | content: "Thank you for using nbpresent!"
82 | }]
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/es6/help/tours/sorter.es6:
--------------------------------------------------------------------------------
1 | import {d3, $} from "nbpresent-deps";
2 |
3 | import Jupyter from "base/js/namespace";
4 |
5 | import {PART} from "../../parts";
6 | import {ICON} from "../../icons";
7 | import {SORTER} from "../../mode/notebook";
8 |
9 | import {TourBase} from "./base";
10 |
11 |
12 | export class SorterTour extends TourBase {
13 | static icon(){
14 | return ICON.slides;
15 | }
16 |
17 | steps(){
18 | return [{
19 | element: `.nbp-app-bar .fa-${ICON.slides}`,
20 | title: "Slides",
21 | content: "Slides make up a presentation",
22 | placement: "left",
23 | onHide: () => this.mode.mode.set(SORTER)
24 | }, {
25 | element: `.nbp-app-bar .fa-${ICON.slides}`,
26 | title: "Slides",
27 | content: "Clicking Slides toggles the sorter view",
28 | placement: "left"
29 | }, {
30 | element: `.nbp-deck-toolbar .fa-${ICON.addSlide}`,
31 | title: "Deck Toolbar",
32 | content: "Let's create a new slide",
33 | placement: "left",
34 | onHide: () => $(`.nbp-deck-toolbar .fa-${ICON.addSlide}`).click()
35 | }, {
36 | element: ".from_template",
37 | title: "Template Library",
38 | position: "top",
39 | content: "You can pick from some existing templates..."
40 | }, {
41 | element: ".from_slide",
42 | title: "Reuse Slide as Template",
43 | position: "top",
44 | content: "Or copy an existing slide"
45 | }, {
46 | title: "Cells",
47 | position: "top",
48 | content: "Here's are some new cells the Tour will play with",
49 | onShow: () => {
50 | let code = Jupyter.notebook.insert_cell_at_index("code", 0),
51 | markdown = Jupyter.notebook.insert_cell_at_index("markdown", 0);
52 |
53 | markdown.code_mirror.setValue('# Hello, _nbpresent_!');
54 | code.code_mirror.setValue('import nbpresent\nnbpresent.__version__');
55 | }
56 | }, {
57 | element: ".nbp-template-library .slide:nth-of-type(1)",
58 | title: "Simple Template",
59 | position: "top",
60 | content: "Let's use this one",
61 | onNext: () => this.mode.sorter.templatePicked(
62 | d3.select(".nbp-template-library .slide:nth-of-type(1)").datum()
63 | )
64 | }, {
65 | element: ".nbp-slides-wrap .slide:last-child",
66 | placement: "top",
67 | title: "Look at the slide, just look at it",
68 | content: "Here's our new slide"
69 | }, {
70 | element: ".nbp-slides-wrap .slide:last-child .nbp-region",
71 | placement: "top",
72 | title: "Region",
73 | onShow: () => {
74 | let el = d3.select(".nbp-slides-wrap .slide:last-child .nbp-region");
75 | el.on("click")(el.datum());
76 | },
77 | content: "It has one Region"
78 | }, {
79 | element: `.nbp-region-toolbar .fa-${ICON.link}`,
80 | placement: "top",
81 | title: "Linking a Region to a Cell Part",
82 | content: "Each Region can be linked to a single Cell Part",
83 | onHide: () => this.mode.sorter.linkContentOverlay()
84 | }, {
85 | element: `.nbp-region-toolbar .fa-${ICON.link}`,
86 | placement: "top",
87 | title: "Link Overlay",
88 | content: "The Link Overlay shows all of the parts available"
89 | }, {
90 | element: `.nbp-link-overlay .nbp-part-overlay-source:first-of-type`,
91 | placement: "right",
92 | title: "Cell Part: Source",
93 | content: "Source, such as code and Markdown text"
94 | }, {
95 | element: `.nbp-link-overlay .nbp-part-overlay-outputs:first-of-type`,
96 | placement: "right",
97 | title: "Cell Part: Outputs",
98 | content: "Outputs, such as rich figures and script results"
99 | }, {
100 | element: `.nbp-link-overlay .nbp-part-overlay-widgets:first-of-type`,
101 | placement: "bottom",
102 | title: "Cell Part: Widgets",
103 | content: "If you use them, all the Widgets of a cell can be shown together"
104 | }, {
105 | element: `.nbp-link-overlay .nbp-part-overlay-whole:first-of-type`,
106 | placement: "left",
107 | title: "Cell Part: Whole",
108 | content: "Finally, a Whole Cell (including its Source, Widgets, and Outputs) can be linked to a single region"
109 | }, {
110 | element: ".cell.selected",
111 | placement: "left",
112 | title: "Linking an Input Part",
113 | content: "Let's use this cell input",
114 | onHide: () => this.mode.sorter.linkContent(PART.source)
115 | }, {
116 | element: ".nbp-slides-wrap .slide:last-child .nbp-region",
117 | placement: "top",
118 | title: "Part Thumbnail",
119 | content: "A part thumbnail might look a little funny, as it can only be reliably updated when a linked Cell Part is on-screen when you mouse over it, but you should usually be able to get an idea of what you're seeing."
120 | }, {
121 | element: `.nbp-region-toolbar .fa-${ICON.unlink}`,
122 | placement: "top",
123 | title: "Cell Part: Unlinking",
124 | content: "Unlinking removes the connection between a region and a cell part, without deleting either one."
125 | }, {
126 | element: `.nbp-region-toolbar .fa-${ICON.trash}`,
127 | placement: "top",
128 | title: "Region: Trashing",
129 | content: "Trashing a Region permanently deletes it, without affecting any linked Cell Part"
130 | }, {
131 | title: "Achievement Unlocked: Presentation",
132 | content: "We're ready to look at the presentation!"
133 | }, {
134 | element: "#nbp-present-btn",
135 | title: "Great, let's have a look",
136 | placement: "bottom",
137 | content: "Clicking this button brings up the Presenter",
138 | onNext: () => {
139 | this.mode.present();
140 | this.mode.mode.set(null);
141 | }
142 | }, {
143 | title: "Looks great!",
144 | content: "Your slide is still made up of your notebook"
145 | }, {
146 | element: ".nbp-present",
147 | placement: "top",
148 | title: "It's still a Notebook",
149 | content: "This is still an editable input area"
150 | }, {
151 | element: ".nbp-present",
152 | placement: "bottom",
153 | title: "Part Execution",
154 | content: "Inputs can even be executed with keyboard shortcuts like ctrl+enter"
155 | }, {
156 | element: ".nbp-presenter-toolbar .fa-step-forward",
157 | title: "Go forward",
158 | content: "Click here to go to the next Slide",
159 | placement: "top",
160 | onShown: () => this.fakeHover(".nbp-presenter-toolbar", 1),
161 | onHidden: () => this.fakeHover(".nbp-presenter-toolbar", 0)
162 | }, {
163 | element: ".nbp-presenter-toolbar .fa-step-backward",
164 | title: "Go back",
165 | content: "Clicking here to go back to the previous slide",
166 | placement: "top",
167 | onShown: () => this.fakeHover(".nbp-presenter-toolbar", 1),
168 | onHidden: () => this.fakeHover(".nbp-presenter-toolbar", 0)
169 | }, {
170 | element: ".nbp-presenter-toolbar .fa-fast-backward",
171 | title: "Go back to the beginning",
172 | content: "Clicking here to go back to the first Slide",
173 | placement: "top",
174 | onShown: () => this.fakeHover(".nbp-presenter-toolbar", 1),
175 | onHidden: () => this.fakeHover(".nbp-presenter-toolbar", 0)
176 | }, {
177 | element: ".nbp-presenter-toolbar .fa-book",
178 | title: "My work is done here",
179 | content: "Click here to go back to the Notebook",
180 | placement: "top",
181 | onShown: () => this.fakeHover(".nbp-presenter-toolbar", 1),
182 | onHidden: () => [
183 | this.fakeHover(".nbp-presenter-toolbar", 0),
184 | this.mode.present(false)
185 | ]
186 | }]
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/es6/icons.es6:
--------------------------------------------------------------------------------
1 | export const ICON = {
2 | showRules: "adjust",
3 | addRegion: "plus-square",
4 | addSlide: "plus-square-o",
5 | addTheme: "plus-circle",
6 | defaultThemeActive: "star",
7 | defaultTheme: "star-o",
8 | editor: "edit",
9 | editRegion: "edit",
10 | grid: "th",
11 | help: "question-circle",
12 | intro: "home",
13 | link: "link",
14 | manual: "arrows",
15 | nbpresent: "gift",
16 | presenter: "youtube-play",
17 | preview: "eye-slash",
18 | slides: "film",
19 | themer: "paint-brush",
20 | trash: "trash",
21 | treemap: "tree",
22 | unlink: "unlink"
23 | };
24 |
--------------------------------------------------------------------------------
/src/es6/layout/grid.es6:
--------------------------------------------------------------------------------
1 | import {d3, _} from "nbpresent-deps";
2 |
3 | import {ManualLayout} from "./manual";
4 |
5 | let KEY = "grid",
6 | PAD = "pad",
7 | TOLERANCE = 0.001;
8 |
9 | export class GridLayout extends ManualLayout {
10 | static clsKey(){
11 | return KEY;
12 | }
13 |
14 | key(){
15 | return KEY;
16 | }
17 |
18 | attrDefaults() {
19 | var attrs = {};
20 | attrs[PAD] = 0.01;
21 | return attrs;
22 | }
23 |
24 | nice(memo, opts){
25 | let [base] = opts,
26 | d = memo._d,
27 | rounded = Math.round(d[base] * 12),
28 | normalized = rounded / 12;
29 |
30 | if(Math.abs(normalized - d[base]) > TOLERANCE){
31 | memo[base] = normalized;
32 | }
33 | return memo;
34 | }
35 |
36 | init(){
37 | super.init();
38 |
39 | let regions = d3.entries(this.slide.value.regions);
40 |
41 | regions.map((d) => {
42 | let path = ["slides", this.slide.key, "regions", d.key, "attrs"];
43 |
44 | let attrs = _.reduce([
45 | ["x", 0, 10],
46 | ["y", 0, 10],
47 | ["width", 1, 11],
48 | ["height", 1, 11]
49 | ], this.nice, {_d: d.value.attrs}, this);
50 |
51 | delete attrs._d;
52 |
53 | let needsMerge = !_.isEmpty(attrs) && !_.isEqual(
54 | attrs,
55 | _.pick(
56 | d.value.attrs,
57 | "x", "y", "width", "height", PAD));
58 |
59 | if(needsMerge){
60 | this.tree.merge(path, attrs);
61 | }
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/es6/layout/manual.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | let KEY = "manual";
4 |
5 | export class ManualLayout {
6 | static clsKey(){
7 | return KEY;
8 | }
9 |
10 | regions() { return d3.entries(this.slide.value.regions); }
11 | key(){ return KEY; }
12 |
13 | attrDefaults() {
14 | return {
15 | x: 0.1,
16 | y: 0.1,
17 | width: 0.8,
18 | height: 0.8
19 | };
20 | }
21 |
22 | constructor(tree, slide, container){
23 | this.tree = tree;
24 | this.slide = slide;
25 | this.container = container;
26 | }
27 |
28 | init(){
29 | let {clientWidth, clientHeight} = this.container;
30 |
31 | this.x = d3.scale.linear()
32 | .range([0, clientWidth]);
33 | this.y = d3.scale.linear()
34 | .range([0, clientHeight]);
35 |
36 | // initialize missing values
37 | this.initAttrs();
38 | }
39 |
40 | initAttrs(){
41 | // initialize missing values
42 | let regions = this.regions();
43 |
44 | d3.entries(this.attrDefaults())
45 | .map(({key, value})=>{
46 | regions
47 | .filter((d) => d.value.attrs[key] == null)
48 | .map((d) => {
49 | var attrs = {};
50 | attrs[key] = value;
51 | this.tree.merge(
52 | ["slides", this.slide.key, "regions", d.key, "attrs"],
53 | attrs
54 | );
55 | });
56 | });
57 | }
58 |
59 | update(region, part){
60 | let {x, y, width, height, z, pad} = region.value.attrs;
61 |
62 | z = z || 0;
63 | pad = pad || 0;
64 |
65 | part.style({
66 | height: `${parseInt(this.y(height - (2 * pad)))}px`,
67 | left: `${parseInt(this.x(x + pad))}px`,
68 | top: `${parseInt(this.y(y + pad))}px`,
69 | width: `${parseInt(this.x(width + (2 * pad)))}px`
70 | });
71 | }
72 |
73 | clean(unpresent){
74 | unpresent.style({
75 | left: null,
76 | top: null,
77 | width: null,
78 | height: null
79 | });
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/es6/layout/treemap.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {ManualLayout} from "./manual";
4 |
5 | let KEY = "treemap",
6 | WEIGHT = "treemap:weight";
7 |
8 | export class TreemapLayout extends ManualLayout {
9 | static clsKey(){
10 | return KEY;
11 | }
12 |
13 | attrDefaults() {
14 | var attrs = {};
15 | attrs[WEIGHT] = 1.0;
16 | return attrs;
17 | }
18 |
19 | key(){
20 | return KEY;
21 | }
22 |
23 | init(){
24 | super.init();
25 |
26 | let that = this;
27 |
28 | this.treemap = d3.layout.treemap()
29 | .size([100, 100])
30 | .sticky(true)
31 | .value((d) => {
32 | return (d._value || {}).attrs[WEIGHT] || 1;
33 | });
34 |
35 | let regions = d3.entries(this.slide.value.regions)
36 | .map((d)=> {
37 | d._value = d.value;
38 | return d;
39 | });
40 |
41 | this.treemap({
42 | children: regions
43 | });
44 |
45 | regions.map((d) => {
46 | let path = ["slides", that.slide.key, "regions", d.key, "attrs"];
47 | let old = that.tree.get(path),
48 | newValues = {
49 | x: d.x / 100,
50 | y: d.y / 100,
51 | width: d.dx / 100,
52 | height: d.dy / 100
53 | },
54 | toSet;
55 |
56 | for(var key in newValues){
57 | if(old[key] !== newValues[key]){
58 | (toSet ? toSet : toSet = {})[key] = newValues[key];
59 | }
60 | }
61 |
62 | if(toSet){
63 | that.tree.merge(path, toSet);
64 | }
65 | });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/es6/less.es6:
--------------------------------------------------------------------------------
1 | export const JPY_BRAND = "#f37626",
2 | UI_BG = "#222";
3 |
--------------------------------------------------------------------------------
/src/es6/logger.es6:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 |
3 | // export let FOO = 1;
4 |
5 | export const ERROR = 0,
6 | WARN = 1,
7 | INFO = 2,
8 | LOG = 3,
9 | DEBUG = 4;
10 |
11 | let _logLevel = DEBUG;
12 |
13 |
14 | export let log = {
15 | error: function(...args){ console.error(...args); },
16 | warn: function(...args){ (_logLevel >= WARN) && console.warn(...args); },
17 | info: function(...args){ (_logLevel >= INFO) && console.info(...args); },
18 | log: function(...args){ (_logLevel >= LOG) && console.log(...args); },
19 | debug: function(...args){ (_logLevel >= LOG) && console.debug(...args)}
20 | }
21 |
22 | export function setLevel(level){
23 | _logLevel = level;
24 | }
25 |
--------------------------------------------------------------------------------
/src/es6/mini.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {NotebookCellManager} from "./cells/notebook";
4 | import {PART} from "./parts";
5 |
6 | // TODO: refactor this
7 | export const SLIDE_WIDTH = 160,
8 | SLIDE_HEIGHT = 90;
9 |
10 | class MiniSlide {
11 | width() {
12 | return SLIDE_WIDTH;
13 | }
14 |
15 | height(){
16 | return SLIDE_HEIGHT;
17 | }
18 |
19 | constructor(selectedRegion) {
20 | this.selectedRegion = selectedRegion;
21 | this.cellManager = new NotebookCellManager();
22 |
23 | this._regions = ({value}) => value.regions;
24 |
25 | this.update = this.update.bind(this);
26 | this.clicked = this.clicked.bind(this);
27 | }
28 |
29 | regions(_){
30 | return arguments.length ?
31 | [this._regions = d3.functor(_), this][1] :
32 | this._regions;
33 | }
34 |
35 | hasContent(part){
36 | return (d) => (d.region.value.content || {}).part == part;
37 | }
38 |
39 | clicked(d){
40 | // if called outside the d3 context...
41 | d = d || d3.select(this).datum();
42 |
43 | if(!(this.selectedRegion)){
44 | return;
45 | }
46 |
47 | let {slide, region} = this.selectedRegion.get() || {};
48 |
49 | if(slide === d.slide.key && region === d.region.key){
50 | return this.selectedRegion.set(null);
51 | }
52 |
53 | this.selectedRegion.set({
54 | slide: d.slide.key,
55 | region: d.region.key
56 | });
57 | }
58 |
59 | update($slide) {
60 | let cellManager = this.cellManager;
61 |
62 | $slide.classed({"nbp-mini": 1});
63 |
64 | let $region = $slide
65 | .selectAll(".nbp-region")
66 | .data((d) => d3.entries(this._regions(d)).map((region) => {
67 | return {slide: d, region};
68 | }));
69 |
70 | $region.enter()
71 | .append("div")
72 | .classed({"nbp-region": 1})
73 | .on("click", this.clicked)
74 | .on("mouseover", function({region}){
75 | if(region.value.content){
76 | cellManager.thumbnail(region.value.content)
77 | .then(({uri}) => {
78 | d3.select(this).style({"background-image": `url(${uri})`})
79 | });
80 | }else{
81 | d3.select(this).style({"background-image": null});
82 | }
83 | });
84 |
85 | $region.exit()
86 | .remove();
87 |
88 | let {slide, region} = (
89 | this.selectedRegion && this.selectedRegion.get()
90 | ) || {};
91 |
92 | $region
93 | .classed({
94 | active: (d) => {
95 | return d.slide.key === slide && d.region.key === region
96 | },
97 | "nbp-content-source": this.hasContent(PART.source),
98 | "nbp-content-outputs": this.hasContent(PART.outputs),
99 | "nbp-content-widgets": this.hasContent(PART.widgets),
100 | "nbp-content-whole": this.hasContent(PART.whole),
101 | "nbp-content-null": this.hasContent(null)
102 | })
103 | // TODO: scale
104 | .style({
105 | width: (d, i) => `${d.region.value.attrs.width * this.width(d, i)}px`,
106 | height: (d, i) => `${d.region.value.attrs.height * this.height(d, i)}px`,
107 | left: (d, i) => `${d.region.value.attrs.x * this.width(d, i)}px`,
108 | top: (d, i) => `${d.region.value.attrs.y * this.height(d, i)}px`
109 | });
110 | }
111 | }
112 |
113 | export {MiniSlide};
114 |
--------------------------------------------------------------------------------
/src/es6/mode/base.es6:
--------------------------------------------------------------------------------
1 | import {Tree} from "../tree";
2 | import {log} from "../logger";
3 |
4 | /** Make a new Editor. The base app, as created in `index.html` */
5 | export class BaseMode {
6 | /** Make a new Editor.
7 | * @param {baobab.Cursor} slide - the slide to edit */
8 | constructor(root){
9 | this.root = root;
10 |
11 | this.tree = new Tree({
12 | slides: this.metadata().slides,
13 | themes: this.metadata().themes,
14 | root: this.root
15 | }).tree;
16 |
17 | this.slides = this.tree.select(["slides"]);
18 |
19 | this.themes = this.tree.select(["themes"]);
20 |
21 | this.log = log;
22 |
23 | this.init();
24 | }
25 |
26 | init(){
27 | return this;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/es6/mode/notebook.es6:
--------------------------------------------------------------------------------
1 | import Jupyter from "base/js/namespace";
2 |
3 | import {d3, _, $} from "nbpresent-deps";
4 |
5 | import {ICON} from "../icons";
6 |
7 | import {Toolbar} from "../toolbar";
8 | import {NotebookPresenter} from "../presenter/notebook";
9 |
10 | import {Sorter} from "../sorter";
11 | import {ThemeManager} from "../theme/manager";
12 | import {Helper} from "../help/helper";
13 |
14 | import {BaseMode} from "./base";
15 |
16 | import {NotebookActions} from "../actions/notebook";
17 |
18 |
19 | export const THEMER = "themer",
20 | SORTER = "sorter",
21 | HELPER = "helper",
22 | MODES = [
23 | THEMER,
24 | SORTER,
25 | HELPER
26 | ];
27 |
28 | export class NotebookMode extends BaseMode {
29 |
30 | init() {
31 | super.init();
32 |
33 | this.enabled = this.tree.select(["app", "enabled"]);
34 | this.enabled.on("update", () => this.enabledChanged());
35 |
36 | this.mode = this.tree.select(["app", "mode"]);
37 | this.mode.on("update", () => this.modeUpdated());
38 |
39 | let debouncedSave = _.debounce(() => this.metadata(true), 1e3);
40 |
41 | [this.slides, this.themes].map(({on}) => on("update", debouncedSave));
42 |
43 | this.slides.on("update", () => this.update());
44 |
45 | this.initActions();
46 | this.initEvents();
47 |
48 | this.$body = d3.select("body");
49 |
50 | return this.initUI();
51 | }
52 |
53 | initUI(){
54 | this.$ui = this.$body.append("div")
55 | .classed({"nbp-app": 1});
56 |
57 | this.appBar = new Toolbar()
58 | .btnClass("btn-default btn-lg")
59 | .btnGroupClass("btn-group-vertical")
60 | .tipOptions({container: "body", placement: "top"});
61 |
62 | this.$appBar = this.$ui.append("div")
63 | .classed({"nbp-app-bar": 1})
64 | .datum([
65 | [{
66 | icon: `${ICON.presenter} fa-2x`,
67 | label: "Present",
68 | click: () => this.present()
69 | }],
70 | [{
71 | icon: `${ICON.slides} fa-2x`,
72 | label: "Slides",
73 | click: () => this.mode.set(this.mode.get() === SORTER ? null : SORTER)
74 | }],
75 | [{
76 | icon: `${ICON.themer} fa-2x`,
77 | label: "Themes",
78 | click: () => this.mode.set(this.mode.get() === THEMER ? null : THEMER)
79 | }],
80 | [{
81 | icon: `${ICON.help} fa-2x`,
82 | label: "Help",
83 | click: () => this.mode.set(this.mode.get() === HELPER ? null : HELPER)
84 | }]
85 | ])
86 | .call(this.appBar.update);
87 |
88 | return this.update();
89 | }
90 |
91 | initEvents(){
92 | $(Jupyter.notebook.events).on(
93 | "create.Cell",
94 | (evt, {cell}) => this.cellCreated(cell));
95 | }
96 |
97 | /** Clear cell metadata (specifically on paste) of nbpresent stuff
98 | TODO: revisit when copy and paste might mean something
99 | */
100 | cellCreated(cell){
101 | _.defer(() => {
102 | delete cell.metadata.nbpresent;
103 | });
104 | }
105 |
106 | initActions(){
107 | this.actions = new NotebookActions([{
108 | name: "show-sorter",
109 | value: {
110 | icon: `fa-${ICON.nbpresent}`,
111 | help: 'enable nbpresent',
112 | handler: () => this.show()
113 | }
114 | }, {
115 | name: "show-presentation",
116 | keys: ["esc"],
117 | value: {
118 | icon: `fa-${ICON.presenter}`,
119 | help: 'show presentation',
120 | handler: ()=> {
121 | if(this.presenter.presenting.get() && this.mode.get()){
122 | this.mode.set(null);
123 | }else{
124 | this.present();
125 | }
126 | }
127 | }
128 | }
129 | ]);
130 |
131 | return this;
132 | }
133 |
134 |
135 | deinitActions(){
136 | this.actions && this.actions.pop();
137 |
138 | return this;
139 | }
140 |
141 | update(){
142 | this.$body.classed({
143 | "nbp-no-slides": !(Object.keys(this.slides.get() || {}).length)
144 | });
145 |
146 | return this;
147 | }
148 |
149 | metadata(update){
150 | let md = Jupyter.notebook.metadata;
151 | if(update){
152 | md.nbpresent = {
153 | slides: this.slides.serialize(),
154 | themes: this.themes.serialize()
155 | };
156 | }else{
157 | return md.nbpresent || {
158 | slides: {},
159 | themes: {}
160 | }
161 | }
162 | }
163 |
164 | enabledChanged(){
165 | let enabled = this.enabled.get();
166 |
167 | if(enabled){
168 | this.ensurePresenter();
169 | }
170 |
171 | this.$body.classed({
172 | "nbp-app-enabled": enabled
173 | });
174 | Jupyter.page.show_site();
175 |
176 | if(enabled){
177 | this.actions.push();
178 | }else{
179 | this.actions && this.actions.pop();
180 | }
181 | }
182 |
183 |
184 | modeClass(mode){
185 | return {
186 | themer: ThemeManager,
187 | sorter: Sorter,
188 | helper: Helper
189 | }[mode];
190 | }
191 |
192 | modeUpdated(){
193 | const current = this.mode.get();
194 |
195 | MODES.filter((mode) => (mode !== current) && this[mode])
196 | .map((mode) => {
197 | this[mode].destroy();
198 | delete this[mode];
199 | });
200 |
201 | if(current && this[current]){
202 | return;
203 | }
204 |
205 | let ModeClass = this.modeClass(current);
206 |
207 | current && (this[current] = new ModeClass(this.tree, {mode: this}));
208 | }
209 |
210 |
211 | show(){
212 | let newEnabled = !(this.enabled.get());
213 |
214 | this.enabled.set(newEnabled);
215 |
216 | if(!newEnabled){
217 | this.mode.set(null);
218 | }
219 |
220 | return this;
221 | }
222 |
223 |
224 | ensurePresenter(){
225 | if(!(this.presenter)){
226 | this.presenter = new NotebookPresenter(this.tree);
227 | }
228 | return this;
229 | }
230 |
231 | present(force){
232 | this.ensurePresenter();
233 |
234 | let presenting = arguments.length ? force :
235 | !this.presenter.presenting.get();
236 |
237 | this.presenter.presenting.set(presenting);
238 |
239 | return this;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/es6/mode/standalone.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {StandalonePresenter} from "../presenter/standalone";
4 |
5 | import {BaseMode} from "./base";
6 |
7 | export class StandaloneMode extends BaseMode {
8 | init() {
9 | super.init();
10 |
11 | this.presenter = new StandalonePresenter(this.tree);
12 |
13 | this.presenter.presenting.set(true);
14 |
15 | return this;
16 | }
17 |
18 | metadata() {
19 | let tree = JSON.parse(d3.select("#nbpresent_tree").text());
20 |
21 | // TODO: centralize serialized keys
22 | return {
23 | slides: tree.slides || {},
24 | themes: tree.themes || {},
25 | root: this.root
26 | };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/es6/package.es6:
--------------------------------------------------------------------------------
1 | import pkg from "../../package.json";
2 |
3 | export const PKG = pkg;
4 |
--------------------------------------------------------------------------------
/src/es6/parts.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | const PART = {
4 | source: "source",
5 | outputs: "outputs",
6 | widgets: "widgets",
7 | whole: "whole"
8 | };
9 |
10 | const PART_SELECT = {
11 | source: ".inner_cell",
12 | outputs: ".output_wrapper",
13 | widgets: ".widget-area .widget-subarea",
14 | whole: null
15 | };
16 |
17 | let partColor = d3.scale.ordinal()
18 | .domain(d3.values(PART))
19 | .range(["blue", "red", "purple", "orange"]);
20 |
21 | export {PART, PART_SELECT, partColor};
22 |
--------------------------------------------------------------------------------
/src/es6/presenter/base.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {SpeakerBase} from "../speaker/base";
4 |
5 | import {ThemeBase} from "../theme/base";
6 |
7 | import {ManualLayout} from "../layout/manual";
8 | import {TreemapLayout} from "../layout/treemap";
9 | import {GridLayout} from "../layout/grid";
10 |
11 | import {PART, PART_SELECT} from "../parts";
12 |
13 |
14 | export class Presenter {
15 | constructor(tree) {
16 | this.tree = tree;
17 |
18 | this.slides = tree.select(["sortedSlides"]);
19 |
20 | this.cellManager = this.makeCellManager();
21 | this.speaker = this.makeSpeaker(this.tree);
22 |
23 | this.initUI();
24 |
25 | this.themes = this.tree.select(["themes"]);
26 | this.presenting = this.tree.select(["app", "presenting"]);
27 | this.current = this.tree.select(["app", "selectedSlide"]);
28 |
29 | this.presenting.on("update", () => this.present());
30 |
31 |
32 | [this.slides, this.current, this.themes]
33 | .map(({on})=> on("update", () => this.update()));
34 | }
35 |
36 | makeCellManager() {
37 | throw new Error("Not implemented");
38 | }
39 |
40 |
41 | makeSpeaker(tree){
42 | return new SpeakerBase(tree);
43 | }
44 |
45 | initUI(){
46 | this.$ui = d3.select("body")
47 | .append("div")
48 | .classed({"nbp-presenter": 1});
49 |
50 | d3.select(window).on("mousemove", ()=> {
51 | this.presenting.get() && this.speaker.hint();
52 | });
53 |
54 | this.$style = d3.select("head")
55 | .append("style")
56 | .classed({"nbp-presenter-style": 1});
57 |
58 | this.$backgrounds = this.$ui.append("div")
59 | .classed({"nbp-presenter-backgrounds": 1});
60 | }
61 |
62 | initActions(){
63 |
64 | }
65 |
66 | deinitActions(){
67 |
68 | }
69 |
70 | /** Decode the slide object through the registry of layout classes.
71 | * @param {Object} slide - the key, value of the current slide
72 | * @return {Class} */
73 | layoutClass(slide){
74 | // TODO: refactor this into plugin mechanism
75 | return {
76 | manual: ManualLayout,
77 | treemap: TreemapLayout,
78 | grid: GridLayout
79 | }[slide.value.layout || "manual"];
80 | }
81 |
82 | /** Initialize the layout
83 | * @param {Object} slide - the immutable data of the current slide
84 | * @return {RegionTree} */
85 | updateLayout(slide){
86 | let LayoutClass = this.layoutClass(slide);
87 |
88 | if(this.layout &&
89 | this.layout.key() == LayoutClass.clsKey() &&
90 | this.layout.slide.key === slide.key
91 | ){
92 | this.layout.slide = slide;
93 | }else{
94 | this.layout = new LayoutClass(
95 | this.tree,
96 | slide,
97 | document.documentElement
98 | );
99 | }
100 | this.layout.init();
101 | return this;
102 | }
103 |
104 |
105 | themeClass(slide){ // eslint-disable-line no-unused-vars
106 | // TODO: refactor this into plugin mechanism?
107 | return ThemeBase;
108 | }
109 |
110 | updateTheme(slide){
111 | let ThemeClass = this.themeClass(slide),
112 | themes = this.themes.get(),
113 | themeId = slide.value.theme || themes.default;
114 |
115 | if(themes.theme && !themes.theme[themeId]){
116 | themeId = Object.keys(themes.theme)[0];
117 | }
118 |
119 | this.theme = new ThemeClass(
120 | this.themes.select(["theme", themeId || ""]),
121 | slide,
122 | this.$style
123 | );
124 |
125 | this.theme.init();
126 |
127 | return this;
128 | }
129 |
130 | present() {
131 | if(!(this.presenting.get())){
132 | this.deinitActions();
133 | }else{
134 | this.initActions();
135 | }
136 | this.update();
137 | }
138 |
139 | getCells() {
140 | return this.cellManager.getCells();
141 | }
142 |
143 | update() {
144 | const presenting = this.presenting.get();
145 |
146 | let that = this;
147 |
148 | d3.select("body").classed({"nbp-presenting": presenting});
149 |
150 | let current = this.current.get(),
151 | slide = this.slides.get([{key: current}]);
152 |
153 | // TODO: handle cleanup
154 | // transition = this.layout && this.layout.destroy()
155 |
156 | if(!slide){
157 | return this.current.set(this.slides.get([0, "key"]));
158 | }
159 |
160 | this.updateLayout(slide)
161 | .updateTheme(slide);
162 |
163 | if(!presenting){
164 | return this.clean(true);
165 | }
166 |
167 | let cells = this.getCells();
168 |
169 | d3.selectAll(this.allPartSelect())
170 | .classed({"nbp-unpresent": 1, "nbp-present": 0});
171 |
172 | d3.entries(slide.value.regions)
173 | .filter(({value}) => value.content)
174 | .map((region) => {
175 | let {content} = region.value,
176 | cell = cells[content.cell];
177 |
178 | if(!cell){
179 | return;
180 | }
181 |
182 | let $el = d3.select(cell.element[0]),
183 | part = content.part === PART.whole ?
184 | $el :
185 | $el.select(PART_SELECT[content.part]);
186 |
187 | part
188 | .classed({
189 | "nbp-unpresent": 0,
190 | "nbp-present": 1
191 | })
192 | .each(() => that.theme.update(region, part))
193 | .each(() => that.layout.update(region, part));
194 | });
195 |
196 | this.clean();
197 | }
198 |
199 | allPartSelect(){
200 | return d3.entries(PART_SELECT)
201 | .filter(({value}) => value)
202 | .map(({value}) => `.cell ${value}`)
203 | .concat([".cell"])
204 | .join(", ");
205 | }
206 |
207 | clean(force){
208 | let that = this,
209 | clean = (that.layout && this.layout.clean) || (() => 0);
210 |
211 | if(force){
212 | d3.selectAll(this.allPartSelect())
213 | .classed({"nbp-unpresent": 1, "nbp-present": 0});
214 | }
215 |
216 | d3.selectAll(".nbp-unpresent")
217 | .call(clean)
218 | .classed({"nbp-unpresent": 0, "nbp-present": 0});
219 |
220 | return this;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/es6/presenter/notebook.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {NotebookCellManager} from "../cells/notebook";
4 |
5 | import {NotebookSpeaker} from "../speaker/notebook";
6 |
7 | import {Presenter} from "./base";
8 |
9 | import {NotebookActions} from "../actions/notebook";
10 |
11 | export class NotebookPresenter extends Presenter {
12 | makeCellManager() {
13 | return new NotebookCellManager();
14 | }
15 | makeSpeaker(tree){
16 | return new NotebookSpeaker(tree);
17 | }
18 |
19 | initActions(){
20 | let _actions = [{
21 | name: "prev-slide",
22 | keys: ["left"],
23 | value: {
24 | icon: 'fa-step-backward',
25 | help: 'previous slide',
26 | handler: () => this.speaker.retreat()
27 | }
28 | }, {
29 | name: "next-slide",
30 | keys: ["right", "space"],
31 | value: {
32 | icon: 'fa-step-forward',
33 | help: 'next slide',
34 | handler: () => this.speaker.advance()
35 | }
36 | }
37 | ];
38 |
39 | this.actions = new NotebookActions(_actions);
40 | this.actions.push();
41 | }
42 |
43 | deinitActions(){
44 | this.actions && this.actions.pop();
45 | }
46 |
47 | clean(force){
48 | super.clean(force);
49 |
50 | d3.entries(this.getCells()).map(({value}) => {
51 | value.code_mirror.refresh();
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/es6/presenter/standalone.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 | import {StandaloneCellManager} from "../cells/standalone";
3 | import {Presenter} from "./base";
4 |
5 | const KEYS = {
6 | ESC: 27,
7 | LEFT: 37,
8 | RIGHT: 39,
9 | SPACE: 32
10 | };
11 |
12 | export class StandalonePresenter extends Presenter {
13 | makeCellManager() {
14 | return new StandaloneCellManager();
15 | }
16 |
17 | initActions(){
18 | d3.select("body")
19 | .on("keydown", ()=>{
20 | switch(d3.event.keyCode){
21 | case KEYS.RIGHT:
22 | case KEYS.SPACE:
23 | this.speaker.advance();
24 | break;
25 | case KEYS.LEFT:
26 | this.speaker.retreat();
27 | break;
28 | case KEYS.ESC:
29 | this.present();
30 | break;
31 | }
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/es6/regiontree.es6:
--------------------------------------------------------------------------------
1 | import {d3, uuid} from "nbpresent-deps";
2 |
3 | import Jupyter from "base/js/namespace";
4 |
5 | import {ICON} from "./icons";
6 | import {Toolbar} from "./toolbar";
7 | import {MiniSlide} from "./mini";
8 |
9 |
10 | class RegionTree {
11 | constructor(slide, region){
12 | this.slide = slide;
13 | this.selectedRegion = region;
14 |
15 | this.mini = (new MiniSlide(this.selectedRegion))
16 | .regions((d) => {
17 | var obj = {};
18 | obj[d.region.id] = d.region;
19 | return obj;
20 | });
21 |
22 | this.initUI();
23 | this.update();
24 |
25 | this.update = this.update.bind(this);
26 |
27 | this.slide.on("update", this.update);
28 | this.selectedRegion.on("update", this.update);
29 |
30 | this.watcher = this.slide.tree.watch({
31 | slide: this.slide,
32 | selectedRegion: this.selectedRegion
33 | });
34 | }
35 |
36 | destroy() {
37 | this.$ui.transition()
38 | .style({opacity: 0})
39 | .remove();
40 | this.killed = true;
41 | }
42 |
43 | width() {
44 | return 300;
45 | }
46 |
47 |
48 | /** Set the layout to the given key
49 | * @param {String} layout - the layout to use
50 | * @return {RegionTree} */
51 | layout(layout){
52 | this.slide.set("layout", layout);
53 | return this;
54 | }
55 |
56 | initUI(){
57 | this.$ui = d3.select("body")
58 | .append("div")
59 | .classed({"nbp-regiontree": 1});
60 |
61 | let toolbar = new Toolbar();
62 |
63 | this.$toolbar = this.$ui.append("div")
64 | .datum([
65 | [{
66 | icon: ICON.addRegion,
67 | click: () => this.addRegion(),
68 | label: "+ Region"
69 | }],
70 | // TODO: make this extensible
71 | [{
72 | icon: ICON.manual,
73 | click: () => this.layout("manual"),
74 | label: "Free"
75 | }, {
76 | icon: ICON.treemap,
77 | click: () => this.layout("treemap"),
78 | label: "Treemap"
79 | }, {
80 | icon: ICON.grid,
81 | click: () => this.layout("grid"),
82 | label: "Grid"
83 | }]
84 | ])
85 | .call(toolbar.update);
86 | }
87 |
88 | toggleStyle(style){
89 | let {region} = this.selectedRegion.get() || {},
90 | path = ["regions", region, "style", style];
91 | this.slide.set(path, !(this.slide.get(path)));
92 | }
93 |
94 | addRegion(){
95 | let id = uuid.v4();
96 | this.slide.set(["regions", id], {
97 | id,
98 | attrs: {
99 | x: 0.1,
100 | y: 0.1,
101 | width: 0.8,
102 | height: 0.8
103 | }
104 | });
105 | }
106 |
107 | update(){
108 | if(this.killed){
109 | return;
110 | }
111 |
112 | let that = this;
113 |
114 | let regions = d3.entries(this.slide.get("regions") || {}),
115 | $region = this.$ui.selectAll(".region_info")
116 | .data(regions, ({key}) => key),
117 | slide = this.slide.get();
118 |
119 | $region
120 | .enter()
121 | .append("div")
122 | .classed({region_info: 1});
123 |
124 | $region.exit()
125 | .transition()
126 | .style({opacity: 0})
127 | .remove();
128 |
129 | let $mini = $region
130 | .selectAll(".slide")
131 | .data((d) => [{
132 | value: slide,
133 | key: slide.id,
134 | region: d.value
135 | }]);
136 |
137 | $mini.enter()
138 | .append("div")
139 | .classed({slide: 1});
140 |
141 | $mini.exit().transition()
142 | .style({opacity: 0})
143 | .remove();
144 |
145 | $mini.call(this.mini.update);
146 |
147 | let layout = this.slide.get(["layout"]);
148 |
149 | let $attr = $region.selectAll(".region_attr")
150 | .data((region) =>
151 | d3.entries(region.value.attrs)
152 | .map((attr) => { return {region, attr}; })
153 | .filter((d)=>{
154 | if(layout == "treemap"){
155 | return d.attr.key.indexOf(layout) === 0;
156 | }else{
157 | return d.attr.key.indexOf("treemap") === -1;
158 | }
159 | })
160 | )
161 |
162 |
163 | $attr.exit().remove();
164 |
165 | $attr.enter()
166 | .append("div")
167 | .classed({
168 | region_attr: 1,
169 | "input-group": 1,
170 | "input-group-sm": 1
171 | })
172 | .call(function($attr){
173 | $attr.append("span").classed({"input-group-btn": 1})
174 | .append("button").classed({
175 | btn: 1,
176 | "btn-default": 1,
177 | "btn-xs": 1,
178 | attr_name: 1
179 | });
180 |
181 | $attr.append("span").classed({"input-group-addon": 1, attr_ns: 1})
182 | .append("i")
183 | .classed({fa: 1, "fa-fw": 1});
184 |
185 | $attr.append("input").classed({"form-control": 1})
186 | .attr({type: "text"})
187 | .each(function(){
188 | Jupyter.keyboard_manager.register_events(this);
189 | })
190 | .on("change", function(d){
191 | let el = d3.select(this),
192 | val = parseFloat(el.property("value"));
193 | that.slide.set(["regions", d.region.key, d.attr.key], val);
194 | });
195 | })
196 | .on("mousedown", function(d){
197 | that.makeSlider(this, d);
198 | });
199 |
200 |
201 | $attr.select(".attr_name").text((d) => {
202 | return d.attr.key.indexOf(":") === -1 ?
203 | d.attr.key :
204 | d.attr.key.split(":")[1];
205 | });
206 | $attr.select(".attr_ns").each(function(d){
207 | let hasIcon = d.attr.key.indexOf(":") !== -1;
208 | let el = d3.select(this)
209 | .style({
210 | display: hasIcon ? null : "none"
211 | });
212 |
213 | if(!hasIcon){
214 | return;
215 | }
216 |
217 | // TODO: put this in layout
218 | let icon = {
219 | treemap: "tree",
220 | grid: "calculator"
221 | }[d.attr.key.split(":")[0]];
222 |
223 | el.select(".fa")
224 | .classed(`fa-${icon}`, 1);
225 |
226 | })
227 | $attr.select(".form-control").attr({
228 | value: (d) => {
229 | if(typeof d.attr.value === "boolean"){
230 | return d.attr.value;
231 | }else if(typeof d.attr.value === "number"){
232 | return d.attr.value.toFixed(3);
233 | }
234 | }
235 | });
236 | }
237 |
238 | makeSlider(element) {
239 | let that = this,
240 | x = d3.mouse(element)[0],
241 | el = d3.select(element);
242 |
243 | el.on("mousemove", function(d){
244 | let x1 = d3.mouse(this)[0],
245 | dx = (x1 - x) / 20,
246 | path = ["regions", d.region.key, "attrs", d.attr.key];
247 |
248 | x = x1;
249 |
250 | that.slide.set(path, that.slide.get(path) + dx)
251 | })
252 | .on("mouseup", function(){
253 | el.on("mousemove", null);
254 | })
255 | .on("mouseexit", function(){
256 | el.on("mousemove", null);
257 | });
258 | }
259 |
260 | }
261 |
262 | export {RegionTree};
263 |
--------------------------------------------------------------------------------
/src/es6/speaker/base.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {Toolbar} from "../toolbar";
4 |
5 | export class SpeakerBase {
6 | constructor(tree){
7 | this.tree = tree;
8 | this.current = this.tree.select(["app", "selectedSlide"]);
9 |
10 | this.presenting = this.tree.select(["app", "presenting"]);
11 | this.presenting.on("update", () => this.update());
12 |
13 | this
14 | .init()
15 | .initUI()
16 | .update();
17 | }
18 |
19 | init(){
20 | return this;
21 | }
22 |
23 | initUI(){
24 | this.$ui = d3.select("body")
25 | .append("div")
26 | .classed({nbpresent_speaker: 1})
27 | .on("mouseover", ()=> this.focused = true )
28 | .on("mouseout", ()=> {
29 | this.focused = false;
30 | this.startDecay();
31 | });
32 |
33 | this.initToolbar();
34 | this.startDecay();
35 | return this;
36 | }
37 |
38 | update(){
39 | this.$ui.style({display: this.presenting.get() ? null : "none"});
40 | }
41 |
42 | decay(){
43 | this.energy = (this.energy || 0) * 0.8;
44 | this.$ui.style({
45 | opacity: this.focused ? 1 : this.energy
46 | });
47 |
48 | if(this.focused){
49 | clearInterval(this.decayInterval);
50 | this.decayInterval = null;
51 | }else if(this.energy <= 0.1){
52 | clearInterval(this.decayInterval);
53 | this.decayInterval = null;
54 | this.energy = 0;
55 | }
56 | }
57 |
58 | startDecay(){
59 | this.energy = 0.6;
60 | if(!(this.decayInerval)){
61 | this.decayInerval = setInterval(()=>this.decay(), 100)
62 | }
63 | }
64 |
65 | hint(){
66 | this.startDecay();
67 | }
68 |
69 | toolbarIcons(){
70 | let that = this;
71 |
72 | return [
73 | [{
74 | icon: "fast-backward",
75 | click: () => that.current.set(this.tree.get(["sortedSlides", 0, "key"])),
76 | label: "First"
77 | }],
78 | [{
79 | icon: "step-backward",
80 | click: () => this.retreat(),
81 | label: "Previous"
82 | }],
83 | [{
84 | icon: "step-forward",
85 | click: () => this.advance(),
86 | label: "Next"
87 | }]
88 | ];
89 | }
90 |
91 | retreat(){
92 | let current = this.tree.get(["slides", this.current.get()]);
93 | this.current.set(current.prev);
94 | }
95 |
96 | advance(){
97 | let slides = this.tree.get(["slides"]),
98 | current = slides[this.current.get()];
99 |
100 | d3.entries(slides).map((d)=> {
101 | if(d.value.prev === current.id){
102 | this.current.set(d.key);
103 | }
104 | });
105 |
106 | return this.current.get();
107 | }
108 |
109 | initToolbar(){
110 | let toolbar = new Toolbar();
111 |
112 | toolbar
113 | .btnGroupClass("btn-group-vertical")
114 | .btnClass("btn-link btn-lg")
115 | .tipOptions({container: "body", placement: "top"});
116 |
117 | // TODO: Make this overlay (Jupyter-branded Reveal Compass)
118 | this.$toolbar = this.$ui.append("div")
119 | .classed({"nbp-presenter-toolbar": 1})
120 | .datum(this.toolbarIcons())
121 | .call(toolbar.update);
122 |
123 | return this;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/es6/speaker/notebook.es6:
--------------------------------------------------------------------------------
1 | import {SpeakerBase} from "./base";
2 |
3 | export class NotebookSpeaker extends SpeakerBase {
4 | toolbarIcons(){
5 | return super.toolbarIcons().concat([
6 | [{
7 | icon: "book",
8 | click: () => this.presenting.set(false),
9 | label: "Notebook"
10 | }]
11 | ]);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/es6/templates/_util.es6:
--------------------------------------------------------------------------------
1 | export function regions([x, y, width, height]){
2 | return {x: x / 10, y: y / 10, width: width / 10, height: height / 10};
3 | }
4 |
5 | export function slide(dims){
6 | return {
7 | regions: dims.map(regions)
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/es6/templates/library.es6:
--------------------------------------------------------------------------------
1 | import {d3, uuid, $} from "nbpresent-deps";
2 |
3 | import {MiniSlide, SLIDE_WIDTH} from "../mini";
4 |
5 | import {MANUAL_TEMPLATES} from "./manual";
6 |
7 | const _TEMPLATES = MANUAL_TEMPLATES;
8 |
9 | class TemplateLibrary {
10 | constructor(picked) {
11 | this.picked = picked;
12 |
13 | this.initUI();
14 |
15 | this.mini = new MiniSlide();
16 |
17 | this.fakeSlide = this.fakeSlide.bind(this);
18 | this.update = this.update.bind(this);
19 |
20 | this.x = d3.scale.linear()
21 | .domain([0, 1])
22 | .range([0, SLIDE_WIDTH + 20]);
23 |
24 | this.update();
25 | }
26 |
27 | destroy() {
28 | this.$ui.transition()
29 | .style({opacity: 0})
30 | .remove();
31 | this.killed = true;
32 | }
33 |
34 | fakeSlide(template) {
35 | let id = uuid.v4();
36 | return {
37 | key: id,
38 | value: {
39 | id,
40 | regions: template.regions.reduce((memo, template) => {
41 | let id = uuid.v4();
42 | memo[id] = $.extend({}, {id}, {attrs: template});
43 | return memo;
44 | }, {})
45 | }
46 | }
47 | }
48 |
49 | initUI(){
50 | this.$ui = d3.select("body")
51 | .append("div")
52 | .classed({"nbp-template-library": 1});
53 |
54 | this.$ui.append("button")
55 | .classed({btn: 1, hide_library: 1, "btn-default": 1})
56 | .on("click", () => this.picked(null))
57 | .append("i")
58 | .classed({fa: 1, "fa-remove": 1});
59 |
60 | this.$ui.append("h3")
61 | .classed({from_template: 1})
62 | .text("Pick template");
63 |
64 | this.$ui.append("h3")
65 | .classed({from_slide: 1})
66 | .text("Reuse slide as template");
67 |
68 | this.$slides = this.$ui.append("div")
69 | .classed({"npb-template-library-slides": 1});
70 | }
71 |
72 | update(){
73 | let $slide = this.$slides.selectAll(".slide")
74 | .data(_TEMPLATES.map(this.fakeSlide));
75 |
76 | $slide.enter()
77 | .append("div")
78 | .classed({slide: 1})
79 | .call(this.mini.update);
80 |
81 | $slide.style({left: (d, i) => `${this.x(i)}px`});
82 |
83 | $slide.on("click", (d) => this.picked(d));
84 |
85 | $slide.exit().remove();
86 | }
87 | }
88 |
89 | export {TemplateLibrary};
90 |
--------------------------------------------------------------------------------
/src/es6/templates/manual.es6:
--------------------------------------------------------------------------------
1 | import {slide} from "./_util";
2 |
3 | export const MANUAL_TEMPLATES = [
4 | [[0, 0, 10, 10]],
5 | [[0.5, 1, 4.5, 8], [5, 1, 4.5, 8]],
6 | [[1, 1, 8, 6], [1, 7, 4, 2], [5, 7, 4, 2]],
7 | [[1, 1, 2, 8], [4, 1, 2, 8], [7, 1, 2, 8]],
8 | [[1, 1, 8, 8], [1, 1, 2, 8], [4, 1, 2, 8], [7, 1, 2, 8]],
9 | [[1, 1, 4, 4], [5, 1, 4, 4], [1, 5, 4, 4], [5, 5, 4, 4]]
10 | ].map(slide, (d) => d / 10);
11 |
--------------------------------------------------------------------------------
/src/es6/theme/base.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {loadFonts} from "./fonts";
4 |
5 | const PRESENT_PREFIX = ".nbp-presenting .nbp-present";
6 |
7 | export class ThemeBase{
8 | constructor(theme, slide, style){
9 | this.theme = theme;
10 | this.rules = theme.select(["rules"]);
11 | this.palette = theme.select(["palette"]);
12 |
13 | this.backgrounds = theme.select(["backgrounds"]);
14 | this.textBase = theme.select(["text-base"]);
15 | this.slide = slide;
16 | this.$style = style;
17 | }
18 |
19 | update(region, part){ // eslint-disable-line no-unused-vars
20 | // TODO: allow per-region (heh, and per-slide) theme customization
21 | }
22 |
23 | init(){
24 | let palette = this.palette.get() || {};
25 |
26 | let rules = [{
27 | key: "",
28 | value: this.textBase.get() || {}
29 | }].concat(d3.entries(this.rules.get() || {}))
30 | .map(({key, value})=>{
31 | let directives = d3.entries(value).map(({key, value})=>{
32 | switch(key){
33 | case "font-size":
34 | value = `${value}rem`;
35 | break;
36 | case "font-family":
37 | loadFonts([value]);
38 | value = `"${value}"`;
39 | break;
40 | case "color":
41 | let {rgb=[0, 0, 0], a=1.0} = palette[value] || {};
42 | value = `rgba(${rgb}, ${a})`;
43 | break;
44 | }
45 |
46 | return `\t${key}: ${value};`;
47 | });
48 |
49 | const BLOCKS = d3.range(1, 7).map((i) => `h${i}`)
50 | .concat(["blockquote"]);
51 |
52 | if(BLOCKS.indexOf(key) !== -1){
53 | directives.push('margin: 0 0 1.3em 0;')
54 | directives.push('padding: 0;');
55 | }
56 |
57 | return `${PRESENT_PREFIX} ${key}{
58 | line-height: 1.25;
59 | ${directives.join("\n")}
60 | }`;
61 | })
62 | .join("\n");
63 |
64 | this.$style.text(rules);
65 |
66 | let backgrounds = this.backgrounds.get() || {},
67 | background = d3.select(".nbp-presenter-backgrounds")
68 | .selectAll(".nbp-presenter-background")
69 | .data(d3.entries(backgrounds, ({key}) => key));
70 |
71 | background.exit().remove();
72 |
73 | background.enter().append("div").classed({
74 | "nbp-presenter-background": 1
75 | });
76 |
77 | background.style({
78 | "background-image": ({value}) => {
79 | let img = value["background-image"];
80 | return img && `url(${img})`;
81 | },
82 | "background-color": ({value}) => {
83 | let {rgb} = palette[value["background-color"]] || {};
84 | return rgb && `rgb(${rgb})`
85 | },
86 | "background-position": ({value}) => `${value.x} ${value.y}`,
87 | left: 0,
88 | right: 0,
89 | bottom: 0,
90 | top: 0
91 | });
92 | }
93 |
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/es6/theme/card.es6:
--------------------------------------------------------------------------------
1 | import {d3} from "nbpresent-deps";
2 |
3 | import {loadFonts} from "./fonts";
4 |
5 | export class ThemeCard{
6 | // accepts a d3 selector bound to {key, value} of themes
7 | update(theme){
8 | theme.classed({"nbp-theme-card": 1});
9 |
10 | theme.each(function(){
11 | let el = d3.select(this);
12 | ["palette", "backgrounds", "fonts"].map((bit) => {
13 | if(!el.select(`.nbp-theme-card-${bit}`).node()){
14 | el.append("div")
15 | .attr("class", `nbp-theme-card-${bit}`);
16 | }
17 | });
18 | });
19 |
20 | this.updateBackground(theme)
21 | .updateColor(theme)
22 | .updateFont(theme);
23 | }
24 |
25 | updateBackground(theme){
26 | let background = theme.select(".nbp-theme-card-backgrounds")
27 | .selectAll(".nbp-theme-card-background")
28 | .data((theme) => {
29 | let backgrounds = d3.entries(theme.value.backgrounds || {});
30 |
31 | return backgrounds.map(({value}) => {
32 | return {
33 | theme: theme.value,
34 | background: value,
35 | height: 100 / backgrounds.length
36 | };
37 | });
38 | });
39 |
40 | background.enter().append("div")
41 | .classed({"nbp-theme-card-background": 1});
42 |
43 | background.exit().remove();
44 |
45 | background.style({
46 | height: ({height}) => `${height}%`,
47 | "background-color": ({theme, background}) => {
48 | let color = background["background-color"];
49 | color = color && theme.palette[color];
50 | if(color){
51 | return `rgb(${color.rgb})`;
52 | }
53 | },
54 | "background-image": ({background}) => {
55 | let bg = background["background-image"];
56 | if(bg){
57 | return `url(${bg})`;
58 | }
59 | }
60 | });
61 |
62 | return this;
63 | }
64 |
65 | updateColor(theme){
66 | let color = theme.select(".nbp-theme-card-palette")
67 | .selectAll(".nbp-theme-card-color")
68 | .data(({value}) => d3.entries(value.palette));
69 |
70 | color.enter().append("div")
71 | .classed({"nbp-theme-card-color": 1});
72 |
73 | color.exit().remove();
74 |
75 | color.style({
76 | "background-color": ({value}) => {
77 | return `rgba(${value.rgb || [0,0,0]}, ${value.a || 1})`;
78 | }
79 | });
80 |
81 | return this;
82 | }
83 |
84 | updateFont(theme){
85 | let rule = theme.select(".nbp-theme-card-fonts")
86 | .selectAll(".nbp-theme-card-font")
87 | .data(({value}) => {
88 | let rules = d3.entries(value.rules),
89 | base = value["text-base"];
90 | if(base){
91 | rules = rules.concat([{key: "∅", value: base}]);
92 | }
93 | return rules.filter(({value}) => {
94 | return value["font-family"] || value["font-size"] || value["font-color"];
95 | });
96 | });
97 |
98 | rule.enter().append("div")
99 | .classed({"nbp-theme-card-font": 1});
100 |
101 | rule.exit().remove();
102 |
103 | rule.text(({key, value}) => `${key} ${value["font-size"] || ""}`)
104 | .style({
105 | "font-family": ({value}) => {
106 | let font = value["font-family"];
107 | font && loadFonts([font]);
108 | return font;
109 | },
110 | "color": (({value}) => value["color"])
111 | });
112 |
113 | return this;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/es6/theme/fonts.es6:
--------------------------------------------------------------------------------
1 | import {WebFont, _} from "nbpresent-deps";
2 |
3 |
4 | export const SYMB = `h1 h2 h3 h4 h5 h6 h7
5 | ul ol li
6 | blockquote pre code
7 | strong em a i
8 | table thead tbody tfoot th td`
9 | .split(/\s+/);
10 |
11 | export const FONTS_MONO = [
12 | "Inconsolata",
13 | "Source Code Pro",
14 | "Roboto Mono",
15 | "Droid Sans Mono",
16 | "Ubuntu Mono",
17 | "VT323",
18 | "PT Mono",
19 | "Cousine",
20 | "Oxygen Mono",
21 | "Anonymous Pro",
22 | "Fira Mono",
23 | "Cutive Mono",
24 | "Nova Mono",
25 | "Share Tech Mono"
26 | ];
27 |
28 | // http://fontpair.co
29 | export const FONTS = [
30 | "ABeeZee",
31 | "Abel",
32 | "Abril Fatface",
33 | "Alegreya",
34 | "Alfa Slab One",
35 | "Alice",
36 | "Allerta",
37 | "Amaranth",
38 | "Amatic SC",
39 | "Andika",
40 | "Arimo",
41 | "Asap",
42 | "Average",
43 | "Bevan",
44 | "Bitter",
45 | "Bree Serif",
46 | "Cabin",
47 | "Cantata One",
48 | "Cardo",
49 | "Clicker Script",
50 | "Crete Round",
51 | "Crimson Text",
52 | "Dancing Script",
53 | "Didact Gothic",
54 | "Domine",
55 | "Dosis",
56 | "Droid Sans",
57 | "EB Garamond",
58 | "Exo",
59 | "Fanwood Text",
60 | "Fauna One",
61 | "Fjalla One",
62 | "Flamenco",
63 | "Francois One",
64 | "Gentium Book Basic",
65 | "Gudea",
66 | "Hind",
67 | "Imprima",
68 | "Istok Web",
69 | "Josefin Sans",
70 | "Josefin Slab",
71 | "Judson",
72 | "Kameron",
73 | "Kreon",
74 | "Lato",
75 | "Ledger",
76 | "Libre Baskerville",
77 | "Lobster",
78 | "Lora",
79 | "Lustria",
80 | "Medula One",
81 | "Merriweather",
82 | "Montserrat",
83 | "Muli",
84 | "Neuton",
85 | "Nixie One",
86 | "Noto Sans",
87 | "Nunito",
88 | "Old Standard TT",
89 | "Open Sans",
90 | "Oswald",
91 | "Ovo",
92 | "Oxygen",
93 | "PT Sans",
94 | "PT Serif",
95 | "Pacifico",
96 | "Patua One",
97 | "Philosopher",
98 | "Playfair Display",
99 | "Playfair Display SC",
100 | "Pontano Sans",
101 | "Quando",
102 | "Quattrocento",
103 | "Quattrocento Sans",
104 | "Questrial",
105 | "Quicksand",
106 | "Raleway",
107 | "Rancho",
108 | "Roboto",
109 | "Roboto Slab",
110 | "Rokkitt",
111 | "Rufina",
112 | "Sacramento",
113 | "Sansita One",
114 | "Shadows Into Light",
115 | "Signika",
116 | "Sintony",
117 | "Slabo 13px",
118 | "Source Sans Pro",
119 | "Squada One",
120 | "Stint Ultra Expanded",
121 | "Ubuntu",
122 | "Ultra",
123 | "Unica One",
124 | "Vollkorn",
125 | "Walter Turncoat",
126 | "Yeseva One"
127 | ].concat(FONTS_MONO);
128 |
129 | let _fontLoaded = {
130 | Lato: 1,
131 | "google:Lato": 1
132 | };
133 |
134 | /* TODO: a generalized namespace thingy for fonts, a la JSON-LD:
135 | - google:Lato
136 | - nbp:Lato
137 | */
138 | export function loadFonts(fonts){
139 | fonts = _.difference(fonts, _.keys(_fontLoaded));
140 | if(!fonts.length){ return []; }
141 | WebFont.load({google: {families: fonts }});
142 | _.extend(_fontLoaded, _.object(fonts, fonts));
143 | return fonts;
144 | }
145 |
146 | loadFonts(["Lato"]);
147 |
--------------------------------------------------------------------------------
/src/es6/theme/manager.es6:
--------------------------------------------------------------------------------
1 | import {d3, uuid, _} from "nbpresent-deps";
2 |
3 | import {ICON} from "../icons";
4 | import {Toolbar} from "../toolbar";
5 |
6 | import {ThemeCard} from "./card";
7 |
8 | import {PLAIN_THEMES} from "./theme/plain";
9 | import {REVEAL_THEMES} from "./theme/reveal";
10 |
11 |
12 | // TODO: make this configurable
13 | const BASE_THEMES = _.extend({},
14 | PLAIN_THEMES,
15 | REVEAL_THEMES
16 | );
17 |
18 | export class ThemeManager {
19 | constructor(tree){
20 | this.tree = tree;
21 |
22 | this.themes = tree.select(["themes", "theme"]);
23 | this.defaultTheme = tree.select(["themes", "default"]);
24 |
25 | let mgrCursor = tree.select(["app", "theme-manager"]);
26 | this.stockThemes = mgrCursor.select(["themes"]);
27 | this.current = mgrCursor.select(["current"]);
28 |
29 | this.card = new ThemeCard();
30 |
31 | [this.defaultTheme, this.themes, this.stockThemes]
32 | .map(({on}) => on("update", ()=> this.update()));
33 | this.current.on("update", () => [
34 | this.currentUpdated(),
35 | _.defer(()=> this.update())
36 | ]);
37 |
38 | this.initUI();
39 |
40 | _.defer(() => this.$body.classed({"nbp-theming": 1}));
41 | }
42 |
43 | destroy(){
44 | this.$body.classed({"nbp-theming": 0});
45 |
46 | _.delay(() => {
47 | this.$ui.remove();
48 | }, 200);
49 | }
50 |
51 | initUI(){
52 | this.$body = d3.select("body");
53 | this.$ui = this.$body.append("div")
54 | .classed({"nbp-theme-manager": 1});
55 |
56 | this.toolbar = new Toolbar()
57 | .btnGroupClass("btn-group-vertical");
58 |
59 | this.$toolbar = this.$ui.append("div")
60 | .datum([
61 | [{
62 | icon: ICON.trash,
63 | visible: () => this.current.get(),
64 | click: () => this.destroyTheme(this.current.get()),
65 | label: "- Theme"
66 | }]
67 | ]);
68 |
69 | this.$ui.append("div")
70 | .classed({"nbp-theme-previews": 1})
71 | .append("h2").text("Your Themes");
72 |
73 | this.$ui.append("div")
74 | .classed({"nbp-theme-previews-canned": 1})
75 | .append("h2").text("Community Themes");
76 |
77 | return this.update()
78 | .currentUpdated();
79 | }
80 |
81 | update(){
82 | let themes = this.themes.get() || {},
83 | current = this.current.get(),
84 | stock = _.extend({}, this.stockThemes.get(), BASE_THEMES),
85 | theme = this.$ui.select(".nbp-theme-previews")
86 | .selectAll(".nbp-theme-preview")
87 | .data(d3.entries(themes), ({key}) => key),
88 | canned = this.$ui.select(".nbp-theme-previews-canned")
89 | .selectAll(".nbp-theme-preview-canned")
90 | .data(d3.entries(stock), ({key}) => key),
91 | defaultTheme = this.defaultTheme.get();
92 |
93 | this.$toolbar.call(this.toolbar.update);
94 |
95 | theme.enter().append("div")
96 | .classed({"nbp-theme-preview": 1})
97 | .call((theme) => {
98 | theme.append("div")
99 | .classed({"nbp-theme-preview-card": 1})
100 | .on("click", ({key}) => this.current.set(key));
101 |
102 | theme.append("a")
103 | .classed({"nbp-default-theme btn": 1})
104 | .on("click", ({key}) => this.defaultTheme.set(key))
105 | .append("i")
106 | .classed({"fa fa-2x": 1});
107 | });
108 |
109 | theme.exit().remove();
110 |
111 | theme.classed({
112 | "nbp-theme-preview-current": ({key}) => key === current
113 | });
114 |
115 | let _cls = [];
116 |
117 | _cls[`fa-${ICON.defaultTheme}`] = ({key}) => key !== defaultTheme
118 | _cls[`fa-${ICON.defaultThemeActive}`] = ({key}) => key === defaultTheme;
119 |
120 | theme.select(".nbp-default-theme i")
121 | .classed(_cls);
122 |
123 | this.card.update(theme.select(".nbp-theme-preview-card"));
124 |
125 | canned.enter().append("div")
126 | .classed({"nbp-theme-preview-canned": 1})
127 | .on("click", ({value}) => {
128 | let id = uuid.v4();
129 | this.themes.set([id], _.extend({}, value, {id}));
130 | this.current.set(id);
131 | });
132 |
133 | canned.exit().remove();
134 |
135 | this.card.update(canned);
136 |
137 | return this;
138 | }
139 |
140 | currentUpdated(){
141 | let current = this.current.get(),
142 | defaultTheme = this.defaultTheme.get(),
143 | themes = this.themes.get();
144 |
145 | if(current){
146 | if(!defaultTheme || !themes[defaultTheme]){
147 | this.defaultTheme.set(current);
148 | }
149 | }
150 | }
151 |
152 | destroyTheme(theme){
153 | theme = theme || this.current.get();
154 | if(theme){
155 | this.themes.unset([theme]);
156 | }
157 | this.current.set(null);
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/es6/theme/theme/plain.es6:
--------------------------------------------------------------------------------
1 | export const PLAIN_THEMES = {
2 | "dark": {
3 | "backgrounds": {
4 | "dc7afa04-bf90-40b1-82a5-726e3cff5267": {
5 | "background-color": "31af15d2-7e15-44c5-ab5e-e04b16a89eff",
6 | "id": "dc7afa04-bf90-40b1-82a5-726e3cff5267"
7 | }
8 | },
9 | "palette": {
10 | "19cc588f-0593-49c9-9f4b-e4d7cc113b1c": {
11 | "id": "19cc588f-0593-49c9-9f4b-e4d7cc113b1c",
12 | "rgb": [
13 | 252,
14 | 252,
15 | 252
16 | ]
17 | },
18 | "31af15d2-7e15-44c5-ab5e-e04b16a89eff": {
19 | "id": "31af15d2-7e15-44c5-ab5e-e04b16a89eff",
20 | "rgb": [
21 | 68,
22 | 68,
23 | 68
24 | ]
25 | },
26 | "50f92c45-a630-455b-aec3-788680ec7410": {
27 | "id": "50f92c45-a630-455b-aec3-788680ec7410",
28 | "rgb": [
29 | 197,
30 | 226,
31 | 245
32 | ]
33 | },
34 | "c5cc3653-2ee1-402a-aba2-7caae1da4f6c": {
35 | "id": "c5cc3653-2ee1-402a-aba2-7caae1da4f6c",
36 | "rgb": [
37 | 43,
38 | 126,
39 | 184
40 | ]
41 | },
42 | "efa7f048-9acb-414c-8b04-a26811511a21": {
43 | "id": "efa7f048-9acb-414c-8b04-a26811511a21",
44 | "rgb": [
45 | 25.118061674008803,
46 | 73.60176211453744,
47 | 107.4819383259912
48 | ]
49 | }
50 | },
51 | "rules": {
52 | "a": {
53 | "color": "19cc588f-0593-49c9-9f4b-e4d7cc113b1c"
54 | },
55 | "blockquote": {
56 | "color": "50f92c45-a630-455b-aec3-788680ec7410",
57 | "font-size": 3
58 | },
59 | "code": {
60 | "font-family": "Anonymous Pro"
61 | },
62 | "h1": {
63 | "color": "19cc588f-0593-49c9-9f4b-e4d7cc113b1c",
64 | "font-family": "Merriweather",
65 | "font-size": 8
66 | },
67 | "h2": {
68 | "color": "19cc588f-0593-49c9-9f4b-e4d7cc113b1c",
69 | "font-family": "Merriweather",
70 | "font-size": 6
71 | },
72 | "h3": {
73 | "color": "50f92c45-a630-455b-aec3-788680ec7410",
74 | "font-family": "Lato",
75 | "font-size": 5.5
76 | },
77 | "h4": {
78 | "color": "c5cc3653-2ee1-402a-aba2-7caae1da4f6c",
79 | "font-family": "Lato",
80 | "font-size": 5
81 | },
82 | "h5": {
83 | "font-family": "Lato"
84 | },
85 | "h6": {
86 | "font-family": "Lato"
87 | },
88 | "h7": {
89 | "font-family": "Lato"
90 | },
91 | "li": {
92 | "color": "50f92c45-a630-455b-aec3-788680ec7410",
93 | "font-size": 3.25
94 | },
95 | "pre": {
96 | "font-family": "Anonymous Pro",
97 | "font-size": 4
98 | }
99 | },
100 | "text-base": {
101 | "color": "19cc588f-0593-49c9-9f4b-e4d7cc113b1c",
102 | "font-family": "Lato",
103 | "font-size": 4
104 | }
105 | },
106 | "plain": {
107 | "palette": {
108 | "19cc588f-0593-49c9-9f4b-e4d7cc113b1c": {
109 | "id": "19cc588f-0593-49c9-9f4b-e4d7cc113b1c",
110 | "rgb": [
111 | 252,
112 | 252,
113 | 252
114 | ]
115 | },
116 | "31af15d2-7e15-44c5-ab5e-e04b16a89eff": {
117 | "id": "31af15d2-7e15-44c5-ab5e-e04b16a89eff",
118 | "rgb": [
119 | 68,
120 | 68,
121 | 68
122 | ]
123 | },
124 | "50f92c45-a630-455b-aec3-788680ec7410": {
125 | "id": "50f92c45-a630-455b-aec3-788680ec7410",
126 | "rgb": [
127 | 155,
128 | 177,
129 | 192
130 | ]
131 | },
132 | "c5cc3653-2ee1-402a-aba2-7caae1da4f6c": {
133 | "id": "c5cc3653-2ee1-402a-aba2-7caae1da4f6c",
134 | "rgb": [
135 | 43,
136 | 126,
137 | 184
138 | ]
139 | },
140 | "efa7f048-9acb-414c-8b04-a26811511a21": {
141 | "id": "efa7f048-9acb-414c-8b04-a26811511a21",
142 | "rgb": [
143 | 25.118061674008803,
144 | 73.60176211453744,
145 | 107.4819383259912
146 | ]
147 | }
148 | },
149 | "rules": {
150 | "blockquote": {
151 | "color": "50f92c45-a630-455b-aec3-788680ec7410"
152 | },
153 | "code": {
154 | "font-family": "Anonymous Pro"
155 | },
156 | "h1": {
157 | "color": "c5cc3653-2ee1-402a-aba2-7caae1da4f6c",
158 | "font-family": "Lato",
159 | "font-size": 8
160 | },
161 | "h2": {
162 | "color": "c5cc3653-2ee1-402a-aba2-7caae1da4f6c",
163 | "font-family": "Lato",
164 | "font-size": 6
165 | },
166 | "h3": {
167 | "color": "50f92c45-a630-455b-aec3-788680ec7410",
168 | "font-family": "Lato",
169 | "font-size": 5.5
170 | },
171 | "h4": {
172 | "color": "c5cc3653-2ee1-402a-aba2-7caae1da4f6c",
173 | "font-family": "Lato",
174 | "font-size": 5
175 | },
176 | "h5": {
177 | "font-family": "Lato"
178 | },
179 | "h6": {
180 | "font-family": "Lato"
181 | },
182 | "h7": {
183 | "font-family": "Lato"
184 | },
185 | "pre": {
186 | "font-family": "Anonymous Pro",
187 | "font-size": 4
188 | }
189 | },
190 | "text-base": {
191 | "font-family": "Merriweather",
192 | "font-size": 4
193 | }
194 | }
195 | };
196 |
--------------------------------------------------------------------------------
/src/es6/tome/arise.es6:
--------------------------------------------------------------------------------
1 | import {log} from "../logger";
2 | import {PART} from "../parts";
3 | import {BaseTome} from "./base";
4 |
5 | /** import slideshow/RISE presentations */
6 | export class AriseTome extends BaseTome {
7 | icon(){
8 | return "cube";
9 | }
10 |
11 | title(){
12 | return "RISE/reveal.js";
13 | }
14 |
15 | label() {
16 | let slides = this.cells()
17 | .map(({metadata}) => (metadata.slideshow || {}).slide_type)
18 | .filter(String)
19 | .filter((type) => ["slide", "subslide"].indexOf(type) !== -1);
20 | return `${slides.length} Slides`;
21 | }
22 |
23 | relevant() {
24 | return this.cells()
25 | .filter(({metadata}) => metadata.slideshow)
26 | .length;
27 | }
28 |
29 | study(){
30 | let slides = [],
31 | prev;
32 |
33 | this.cells().map((cell, i) => {
34 | prev = slides.slice(-1)[0];
35 |
36 | let slideType = (cell.metadata.slideshow || {}) .slide_type ||
37 | (i ? "-" : "slide");
38 |
39 | switch(slideType){
40 | case "subslide":
41 | case "slide":
42 | slides.push(this.wholeCellSlide(cell, prev ? prev.id : null));
43 | break;
44 | case "fragment":
45 | case "-":
46 | prev && this.fromSlideshowContinuation(cell, prev);
47 | break;
48 | case "notes":
49 | case "skip":
50 | break;
51 | default:
52 | log.debug(slideType, "not implemented yet");
53 | break;
54 | }
55 | });
56 |
57 | return slides;
58 | }
59 |
60 | fromSlideshowContinuation(cell, prev){
61 | let id = this.sorter.nextId(),
62 | cellId = this.sorter.cellId(cell);
63 | prev.regions[id] = {
64 | id,
65 | content: {cell: cellId, part: PART.whole},
66 | attrs: {
67 | x: 0.1,
68 | y: 0.5,
69 | width: 0.8,
70 | height: 0.4
71 | }
72 | };
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/es6/tome/base.es6:
--------------------------------------------------------------------------------
1 | import Jupyter from "base/js/namespace";
2 |
3 | import {PART} from "../parts";
4 |
5 |
6 | /** The base class for automated slide creators */
7 | export class BaseTome {
8 | /** construct a new tome, set up for a slide */
9 | constructor(sorter){
10 | this.sorter = sorter;
11 | }
12 |
13 | relevant(){
14 | return true;
15 | }
16 |
17 | study(){
18 | return [];
19 | }
20 |
21 | execute(){
22 | this.study().map((d)=> this.sorter.slides.set([d.id], d) );
23 | }
24 |
25 | cells() {
26 | return Jupyter.notebook.get_cells();
27 | }
28 |
29 | wholeCellSlide(cell, prev=null){
30 | let slideId = this.sorter.nextId(),
31 | regionId = this.sorter.nextId(),
32 | cellId = this.sorter.cellId(cell),
33 | regions = {};
34 |
35 | regions[regionId] = {
36 | id: regionId,
37 | content: {
38 | cell: cellId,
39 | part: PART.whole
40 | },
41 | attrs: {
42 | x: 0.1,
43 | y: 0.1,
44 | width: 0.8,
45 | height: 0.8
46 | }
47 | };
48 |
49 | return {
50 | regions: regions,
51 | id: slideId,
52 | prev: prev
53 | };
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/es6/tome/basic.es6:
--------------------------------------------------------------------------------
1 | import {BaseTome} from "./base";
2 |
3 |
4 | /** import slideshow/RISE presentations */
5 | export class BasicTome extends BaseTome {
6 | icon(){
7 | return "columns fa-rotate-270";
8 | }
9 |
10 | title(){
11 | return "Basic";
12 | }
13 |
14 | label(){
15 | return `${this.study().length} Slides`;
16 | }
17 |
18 | study(){
19 | let slides = [],
20 | prev;
21 |
22 | this.cells().map((cell) => {
23 | prev = slides.slice(-1)[0];
24 | slides.push(this.wholeCellSlide(cell, prev ? prev.id : null));
25 | });
26 |
27 | return slides;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/es6/toolbar.es6:
--------------------------------------------------------------------------------
1 | import {d3, $, _} from "nbpresent-deps";
2 |
3 | let fn = d3.functor;
4 |
5 | export class Toolbar {
6 | constructor(){
7 | this._btnClass = fn("btn-default");
8 | this._btnGroupClass = fn("btn-group");
9 | this._tipOptions = fn({container: "body"});
10 |
11 | this.update = this.update.bind(this);
12 | }
13 |
14 | btnClass(val){
15 | return arguments.length ?
16 | [this._btnClass = fn(val), this][1] :
17 | this._btnClass;
18 | }
19 |
20 | tipOptions(val){
21 | return arguments.length ?
22 | [this._tipOptions = fn(val), this][1] :
23 | this._tipOptions;
24 | }
25 |
26 | btnGroupClass(val){
27 | return arguments.length ?
28 | [this._btnGroupClass = fn(val), this][1] :
29 | this._btnGroupClass;
30 | }
31 |
32 | update($selection){
33 | let that = this;
34 | $selection.classed({"btn-toolbar nbp-toolbar": 1});
35 |
36 | let $group = $selection.selectAll(".btn-toolbar-group")
37 | .data((d) => d)
38 |
39 | $group.enter()
40 | .append("div")
41 | .classed({"btn-toolbar-group": 1})
42 | .classed(this._btnGroupClass(), 1);
43 |
44 | let $btn = $group.selectAll(".btn")
45 | .data((d) => d);
46 |
47 | $btn.enter()
48 | .append("a")
49 | .classed({btn: 1})
50 | .call(($btn)=>{
51 | $btn.append("i");
52 | $btn.append("label");
53 | });
54 |
55 | $btn.attr({
56 | "class": (d) => `btn ${this._btnClass(d)}`,
57 | title: (d) => d.tip || d.label
58 | })
59 | .each(function(d){
60 | if(!$.fn.tooltip){
61 | return;
62 | }
63 | try {
64 | $(this).tooltip("destroy");
65 | } catch(err) {
66 | // whatever, jquery
67 | }
68 | d.tip && $(this).tooltip(that._tipOptions(d));
69 | })
70 | .on("click", (d) => d.click());
71 |
72 | // regular icons
73 | $btn.select("i")
74 | .filter(({icon}) => _.isString(icon))
75 | .attr({
76 | "class": ({icon}) => `fa fa-fw fa-2x fa-${icon}`
77 | })
78 | .selectAll("i").remove();
79 |
80 | // handle stacked icons
81 | let stacked = $btn.select("i")
82 | .filter(({icon}) => !_.isString(icon))
83 | .attr({"class": "fa-stack"})
84 | .selectAll("i")
85 | .data((d) => d.icon.map(() => d));
86 |
87 | stacked.exit().remove();
88 |
89 | stacked.enter().append("i");
90 |
91 | stacked.attr({"class": ({icon}, i) => `fa fa-${icon[i]}`});
92 |
93 | $btn.select("label")
94 | .text(({label}) => label);
95 |
96 | $btn.filter((d) => d.visible && !d.visible())
97 | .remove();
98 |
99 | $btn.exit()
100 | .remove();
101 |
102 | $group.exit().remove();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/es6/tree.es6:
--------------------------------------------------------------------------------
1 | import {Baobab, d3, $} from "nbpresent-deps";
2 |
3 | export class Tree {
4 | constructor(obj){
5 | this.tree = new Baobab($.extend({
6 | slides: {},
7 | sortedSlides: Baobab.monkey(["slides"], this.sortedSlides),
8 | themes: {}
9 | }, obj));
10 | }
11 |
12 | sortedSlides(slidesMap){
13 | let slides = d3.entries(slidesMap);
14 |
15 | slides.sort(
16 | (a, b) => (a.value.prev === null) || (a.key === b.value.prev) ? -1 : 1
17 | )
18 |
19 | return slides;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/es6/vendor.es6:
--------------------------------------------------------------------------------
1 | /* eslint no-unused-vars: 0 */
2 | import _bind_polyfill from "phantomjs-polyfill";
3 | import d3 from "d3";
4 | import uuid from "uuid";
5 | import Baobab from "baobab";
6 | import $ from "jquery";
7 | import _ from "underscore";
8 | import WebFont from "webfontloader";
9 | import html2canvas from "html2canvas/dist/html2canvas.min";
10 |
11 | export {
12 | _,
13 | $,
14 | Baobab,
15 | d3,
16 | html2canvas,
17 | uuid,
18 | WebFont
19 | };
20 |
--------------------------------------------------------------------------------
/src/js/build.js:
--------------------------------------------------------------------------------
1 | ({
2 | baseUrl: ".",
3 | paths: {
4 | "nbpresent-deps": "../../nbpresent/static/nbpresent/js/nbpresent.deps.min",
5 | "nbpresent-standalone": "../../nbpresent/static/nbpresent/js/nbpresent.standalone.min",
6 | "jquery" : "../../node_modules/jquery/dist/jquery",
7 | "underscore" : "../../node_modules/underscore/underscore"
8 | },
9 | name: "main",
10 | out: "../../nbpresent/static/nbpresent/js/nbpresent.static.min.js",
11 | preserveLicenseComments: true
12 | })
13 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | /* global requirejs define */
2 | define(
3 | ["require", "jquery", "base/js/namespace"],
4 | function(require, $, Jupyter){
5 | // here's your namespace global
6 | var nbpresent = window.nbpresent = {loading: true};
7 |
8 | var initializedResolver,
9 | initializedPromise = new Promise(function(resolve, reject){
10 | initializedResolver = resolve;
11 | });
12 |
13 | var $dlMenu = $("#download_html").parent();
14 |
15 | var formats = [
16 | {key: "nbpresent", label: "Presentation (.html)"},
17 | {key: "nbpresent_pdf", label: "Presentation (.pdf)"}
18 | ];
19 |
20 | function load_ipython_extension() {
21 | initNbpresent();
22 | }
23 |
24 | function initialized(){
25 | return initializedPromise;
26 | }
27 |
28 | function initNbpresent(){
29 | requirejs.config({
30 | paths: {
31 | "nbpresent-deps": require.toUrl("./nbpresent.deps.min.js"),
32 | "nbpresent-notebook": require.toUrl("./nbpresent.notebook.min.js")
33 | }
34 | });
35 |
36 | var modulePath = requirejs.toUrl("nbpresent-notebook").split("?")[0]
37 | .split("/")
38 | .slice(0, -2)
39 | .join("/");
40 |
41 | initStylesheet(modulePath);
42 |
43 |
44 | requirejs(["nbpresent-deps"], function(){
45 | Jupyter.page.show_site();
46 | requirejs(["nbpresent-notebook"], function(mode){
47 | setTimeout(function(){
48 | nbpresent.mode = new mode.NotebookMode(
49 | require.toUrl(".").split("?")[0]
50 | );
51 |
52 | initializedResolver(nbpresent.mode);
53 |
54 | initToolbar();
55 | initMenu();
56 | }, 1000);
57 | });
58 | });
59 | }
60 |
61 | function show(){
62 | nbpresent.mode.show();
63 | }
64 |
65 | function present(){
66 | nbpresent.mode.present();
67 | }
68 |
69 | function nbconvert(key){
70 | Jupyter.menubar._nbconvert(key, true);
71 | }
72 |
73 | function initStylesheet(modulePath){
74 | var id = "nbp-css",
75 | $head = $("head"),
76 | cssPath = modulePath + "/css/nbpresent.min.css";
77 |
78 | $head.find("link#" + id).length || $("", {
79 | id: id,
80 | rel: "stylesheet",
81 | href: cssPath,
82 | }).appendTo($head);
83 | }
84 |
85 | // set up the UI before doing anything else to avoid UI delay
86 | function initToolbar(){
87 | $("#view_menu").append(
88 | $("").append(
89 | $("").text("Toggle Presentation").on("click", show)));
90 |
91 | // TODO: make this one button!
92 | Jupyter.toolbar.add_buttons_group([
93 | {
94 | label: "Edit Presentation",
95 | icon: "fa-gift",
96 | callback: show,
97 | id: "nbp-app-btn"
98 | },
99 | {
100 | label: "Show Presentation",
101 | icon: "fa-youtube-play",
102 | callback: present,
103 | id: "nbp-present-btn"
104 | }
105 | ]);
106 | }
107 |
108 | function initMenu(){
109 | $.get(Jupyter.notebook.base_url + "api/nbconvert", formatsLoaded);
110 | }
111 |
112 | function dlMenuItem(format){
113 | $("")
114 | .append(
115 | $("", {"class": "download_" + format.key})
116 | .text(format.label)
117 | .on("click", function(){ nbconvert(format.key); })
118 | )
119 | .appendTo($dlMenu)
120 | }
121 |
122 | function formatsLoaded(available){
123 | // update the download chrome
124 | formats.map(function(format){
125 | available[format.key] && dlMenuItem(format);
126 | });
127 | }
128 |
129 | return {
130 | load_ipython_extension: load_ipython_extension,
131 | initialized: initialized
132 | };
133 | });
134 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | require(
2 |
3 | ["jquery", "nbpresent-deps", "nbpresent-standalone"],
4 |
5 | function(jquery, deps, mode){
6 | $(function(){
7 | window.nbpresent = new mode.StandaloneMode("./");
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/src/less/app.less:
--------------------------------------------------------------------------------
1 | .nbp-app {
2 | .nbp-ui();
3 |
4 | z-index: @z-app-bar;
5 | position: fixed;
6 | top: 0;
7 | bottom: 0;
8 | right: 0;
9 | width: @app-bar-width;
10 |
11 | background-color: @ui-bg;
12 |
13 | margin-right: -@app-bar-width;
14 | transition: margin-right @tx-dur @tx-fn;
15 |
16 | .btn-toolbar {
17 | margin: 0;
18 | right: 0;
19 | }
20 |
21 | .nbp-deck-toolbar {
22 | position: absolute;
23 | bottom: 0;
24 | }
25 | }
26 |
27 | body.nbp-no-slides {
28 | .nbp-app-bar {
29 | .btn[title="Present"] {
30 | opacity: 0.33;
31 | &:hover {
32 | opacity: 1.0;
33 | }
34 | }
35 | }
36 | }
37 |
38 | body:not(.nbp-app-enabled) {
39 | #site {
40 | transition: width @tx-dur @tx-fn;
41 | }
42 |
43 | #maintoolbar{
44 | padding-right: 0;
45 | transition: padding-right @tx-dur @tx-fn;
46 | }
47 | }
48 |
49 |
50 | body.nbp-app-enabled {
51 | #site {
52 | margin-right: @app-bar-width;
53 | width: auto;
54 | transition: margin-right @tx-dur @tx-fn;
55 | }
56 |
57 | #maintoolbar{
58 | padding-right: @app-bar-width;
59 | transition: padding-right @tx-dur @tx-fn;
60 | }
61 |
62 | #nbp-app-btn{
63 | color: @jpy-brand;
64 | box-shadow: @z-shadow;
65 | background-color: @ui-bg;
66 | transition: margin-right @tx-dur @tx-fn, box-shadow @tx-dur @tx-fn;
67 | }
68 |
69 | .nbp-app {
70 | opacity: 1;
71 | margin-right: 0;
72 | transition: margin-right @tx-dur @tx-fn, opacity @tx-dur @tx-fn;
73 |
74 | box-shadow: @z-shadow;
75 | }
76 |
77 | .tour-nbpresent {
78 | z-index: @z-tour;
79 | }
80 |
81 | &.nbp-presenting{
82 | .nbp-app {
83 | opacity: 0;
84 | transition: opacity @tx-dur @tx-fn;
85 | }
86 | &.nbp-sorting, &.nbp-theming {
87 | .nbp-app {
88 | opacity: 0.5;
89 | transition: opacity @tx-dur @tx-fn;
90 | }
91 | }
92 | .nbp-app:hover {
93 | opacity: 1.0;
94 | transition: opacity @tx-dur @tx-fn;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/less/editor.less:
--------------------------------------------------------------------------------
1 | .nbp-editor {
2 | .nbp-ui();
3 | position: fixed;
4 | bottom: @sorter-height;
5 | right: @app-bar-width;
6 | left: @editor-sidebar-width;
7 | top: 0;
8 | background-color: rgba(50, 50, 50, 0.5);
9 | z-index: @z-editor;
10 | opacity: 0.9;
11 |
12 | .slide_bg {
13 | background-color: #fff;
14 | position: absolute;
15 | }
16 |
17 | .nbp-region {
18 | .nbp-region-bg {
19 | fill: transparent;
20 | opacity: 0.50;
21 | stroke: grey;
22 | stroke-width: 2px;
23 | stroke-dasharray: 5,5;
24 | }
25 |
26 | &.nbp-content-source .nbp-region-bg{
27 | stroke: @part-source-color;
28 | stroke-dasharray: none;
29 | }
30 |
31 | &.nbp-content-outputs .nbp-region-bg{
32 | stroke: @part-outputs-color;
33 | stroke-dasharray: none;
34 | }
35 |
36 | &.nbp-content-widgets .nbp-region-bg{
37 | stroke: @part-widgets-color;
38 | stroke-dasharray: none;
39 | }
40 |
41 | &.nbp-content-whole .nbp-region-bg{
42 | stroke: @part-whole-color;
43 | stroke-dasharray: none;
44 | }
45 |
46 |
47 | &.active .nbp-region-bg{
48 | stroke: @jpy-brand;
49 | opacity: 0.9;
50 | stroke-width: 10px;
51 | stroke-dasharray: none;
52 | }
53 |
54 | }
55 |
56 | }
57 |
58 | // TODO: move this?
59 | .nbp-regiontree {
60 | .nbp-ui();
61 |
62 | z-index: @z-regiontree;
63 | position: absolute;
64 | width: @editor-sidebar-width;
65 | bottom: @sorter-height;
66 | left: 0;
67 | top: 0;
68 | background-color: @ui-bg;
69 | overflow-y: auto;
70 | overflow-x: hidden;
71 | padding-top: @btn-titled-height;
72 | opacity: 0.9;
73 |
74 | .nbp-toolbar {
75 | position: fixed;
76 | top: 0;
77 | z-index: @z-regiontree + 1;
78 | width: @editor-sidebar-width - 10;
79 | background-color: @ui-bg;
80 | box-shadow: @z-shadow;
81 | }
82 |
83 | .region_info {
84 | clear: both;
85 | float: left;
86 | margin: 10px 0;
87 |
88 | .slide {
89 | float: left;
90 | }
91 |
92 | .region_attr {
93 | float: right;
94 | clear: right;
95 | width: 140px;
96 | text-align: right;
97 | background-color: transparent;
98 | border: none;
99 | color: @jpy-brand;
100 | height: 24px;
101 | padding: 2px;
102 |
103 | &.input-group-sm > .input-group-addon.attr_ns {
104 | background-color: transparent;
105 | border: none;
106 | color: @jpy-brand;
107 | padding: 0;
108 | height: 20px;
109 | }
110 |
111 | .attr_name{
112 | padding: 0 5px;
113 | height: 20px;
114 | background-color: @jpy-brand;
115 | color: black;
116 | border-radius: 2px;
117 | user-select: none;
118 | cursor: ew-resize;
119 | border: none;
120 | }
121 |
122 | input {
123 | height: 20px;
124 | text-align: right;
125 | background-color: transparent;
126 | border: none;
127 | color: white;
128 | font-weight: bold;
129 | }
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/less/help.less:
--------------------------------------------------------------------------------
1 | body.nbp-helping {
2 | .nbp-helper{
3 | margin-right: 0;
4 | transition: margin-right @tx-dur @tx-fn;
5 | }
6 | }
7 |
8 | .nbp-helper {
9 | .nbp-ui();
10 |
11 | z-index: @z-help;
12 |
13 | position: fixed;
14 |
15 | right: @app-bar-width;
16 | width: @sorter-slide-width;
17 | top: 0;
18 | bottom: 0;
19 |
20 | margin-right: -@sorter-slide-width;
21 | transition: margin-right @tx-dur @tx-fn;
22 |
23 | background-color: @ui-bg;
24 |
25 | hr {
26 | margin: 0 20px;
27 | padding: 0;
28 | border-top: solid 1px fade(@jpy-brand, 50%);
29 | }
30 |
31 | h1, h2, h3 {
32 | color: @jpy-brand;
33 | text-align: center;
34 | }
35 |
36 | .nbp-version {
37 | font-size: 12px;
38 | }
39 |
40 | h2{
41 | font-size: 18px;
42 | }
43 |
44 | a:not(.btn) {
45 | color: @jpy-brand;
46 | text-decoration: none;
47 | border-bottom: solid 3px fade(@jpy-brand, 20%);
48 | transition: border-bottom @tx-dur @tx-fn;
49 | &:hover {
50 | border-bottom: solid 1px fade(@jpy-brand, 75%);
51 | transition: border-bottom @tx-dur @tx-fn;
52 | }
53 | }
54 |
55 | .nbp-community, .nbp-tour-launcher {
56 | width: 50%;
57 | }
58 |
59 | .nbp-legal {
60 | bottom: 0;
61 | position: absolute;
62 | padding: 10px;
63 | color: @jpy-brand;
64 | font-size: 11px;
65 | text-align: center;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/less/index.less:
--------------------------------------------------------------------------------
1 | @import "mixins";
2 | @import "app";
3 | @import "help";
4 | @import "toolbar";
5 | @import "editor";
6 | @import "sorter";
7 | @import "presenter";
8 | @import "speaker";
9 | @import "mini";
10 | @import "layouts";
11 | @import "tour";
12 | @import "link-overlay";
13 | @import "theme-ui";
14 | @import "theme-card";
15 | @import "variables";
16 |
--------------------------------------------------------------------------------
/src/less/layouts.less:
--------------------------------------------------------------------------------
1 | .nbp-template-library {
2 | .nbp-ui();
3 |
4 | z-index: @z-layouts;
5 | position: fixed;
6 | bottom: @sorter-height;
7 | left: 0;
8 | right: @app-bar-width;
9 | height: @sorter-height;
10 |
11 | background-color: @ui-bg;
12 | opacity: 0.75;
13 |
14 | .npb-template-library-slides {
15 | overflow-x: auto;
16 | overflow-y: hidden;
17 | position: relative;
18 | width: 100%;
19 | height: 170px;
20 | margin-top: 42px;
21 | }
22 |
23 |
24 | .hide_library {
25 | position: absolute;
26 | left: 50%;
27 | bottom: 0;
28 | background-color: @ui-bg;
29 | color: @jpy-brand;
30 | }
31 |
32 | .slide {
33 | opacity: 80%;
34 | position: absolute;
35 | }
36 |
37 | h3 {
38 | color: @jpy-brand;
39 | position: absolute;
40 | margin: 0;
41 | padding: 10px;
42 | &.from_template{
43 | top: 5px;
44 | }
45 | &.from_slide{
46 | bottom: 0;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/less/link-overlay.less:
--------------------------------------------------------------------------------
1 | body.nbp-linking {
2 | .nbp-link-overlay .nbp-part-overlay{
3 | opacity: 1;
4 | transition: opacity @tx-dur @tx-fn;
5 |
6 | &:hover{
7 | opacity: 0.5;
8 | transition: opacity @tx-dur @tx-fn, width @tx-dur @tx-fn;
9 | width: 100%;
10 | }
11 | }
12 |
13 | .cell {
14 | opacity: 0.5;
15 | }
16 | }
17 |
18 | .nbp-link-overlay {
19 | .nbp-ui();
20 |
21 | z-index: @z-link-overlay;
22 | top: 0;
23 | right: 0;
24 | bottom: 0;
25 | left: 0;
26 | position: absolute;
27 |
28 | .nbp-part-overlay {
29 | position: absolute;
30 | width: @sorter-slide-height;
31 |
32 | background-color: @ui-bg;
33 | color: white;
34 | text-align: left;
35 |
36 | opacity: 0;
37 | transition: opacity @tx-dur @tx-fn, width @tx-dur @tx-fn;
38 |
39 | &.nbp-part-overlay-source {
40 | border-right: solid 3px @part-source-color;
41 | }
42 |
43 | &.nbp-part-overlay-outputs {
44 | border-right: solid 3px @part-outputs-color;
45 | }
46 |
47 | &.nbp-part-overlay-widgets {
48 | border-right: solid 3px @part-widgets-color;
49 | }
50 |
51 | &.nbp-part-overlay-whole {
52 | border-left: solid 3px @part-whole-color;
53 | right: 0;
54 | text-align: right;
55 | }
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/less/mini.less:
--------------------------------------------------------------------------------
1 | .slide.nbp-mini {
2 | position: relative;
3 | width: @sorter-slide-width;
4 | height: @sorter-slide-height;
5 | border: solid 1px #eee;
6 | border-radius: 2px;
7 | background-color: #fff;
8 | overflow: hidden;
9 | box-shadow: @z-shadow;
10 |
11 | &.active {
12 | border: solid 3px @jpy-brand;
13 | }
14 |
15 | &.dragging {
16 | box-shadow: @z-shadow;
17 | z-index: 9999;
18 | opacity: 0.9;
19 | }
20 |
21 | .nbp-region {
22 | position: absolute;
23 |
24 | background-color: rgba(255,255,255,0.5);
25 | background-repeat: no-repeat;
26 | border: dashed 3px rgba(128,128,128,0.5);
27 | border-radius: 2px;
28 |
29 | &.nbp-content-source,
30 | &.nbp-content-outputs,
31 | &.nbp-content-widgets,
32 | &.nbp-content-whole {
33 | border: solid 1px black;
34 | }
35 |
36 | &.nbp-content-source {
37 | background-color: fadeOut(@part-source-color, 75%);
38 | }
39 |
40 | &.nbp-content-outputs {
41 | background-color: fadeOut(@part-outputs-color, 75%);
42 | }
43 |
44 | &.nbp-content-widgets {
45 | background-color: fadeOut(@part-widgets-color, 75%);
46 | }
47 |
48 | &.nbp-content-whole {
49 | background-color: fadeOut(@part-whole-color, 75%);
50 | }
51 |
52 | &.active {
53 | border: solid 3px @jpy-brand;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/less/mixins.less:
--------------------------------------------------------------------------------
1 | .nbp-ui {
2 | font-family: Lato, sans-serif;
3 |
4 | .btn {
5 | color: @jpy-brand;
6 | background-color: transparent;
7 | border: none;
8 | font-size: 16px;
9 | opacity: 0.75;
10 |
11 | &:hover {
12 | opacity: 1;
13 | }
14 |
15 | &:active:focus, &:active:hover{
16 | background-color: @jpy-brand;
17 | color: @ui-bg;
18 | }
19 |
20 | label {
21 | font-size: 12px;
22 | text-align: center;
23 | display: block;
24 | break: both;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/less/presenter.less:
--------------------------------------------------------------------------------
1 | body:not(.nbp-presenting) .nbp-presenter{
2 | display: none;
3 | }
4 |
5 | body.nbp-presenting {
6 | overflow: hidden;
7 |
8 | #notebook-container {
9 | position:absolute;
10 | left: -9999px
11 | }
12 |
13 | .nbp-presenter {
14 | .nbp-ui();
15 | z-index: @z-present;
16 | background-color: white;
17 | position: fixed;
18 | top: 0;
19 | left: 0;
20 | bottom: 0;
21 | right: 0;
22 |
23 | .nbp-presenter-background {
24 | position: fixed;
25 | max-width: 100vw;
26 | background-repeat: no-repeat;
27 | }
28 | }
29 |
30 | #notebook .cell .nbp-present .text_cell_render {
31 | border: none;
32 | margin: 0;
33 | padding: 0;
34 | }
35 |
36 |
37 | .cell.nbp-present.selected{
38 | border: none;
39 | background: none;
40 | }
41 |
42 | .cell.nbp-present,
43 | .cell .nbp-present{
44 | z-index: @z-presented;
45 | position: fixed;
46 | display: block;
47 |
48 | .prompt, .ctb_hideshow {
49 | display: none;
50 | }
51 |
52 | .input_area, &.input_area {
53 | height: 100%;
54 |
55 | .CodeMirror {
56 | max-height: 100%;
57 | height: 100%;
58 | }
59 | }
60 |
61 | .output {
62 | height: 100%;
63 | .output_area {
64 | height: 100%;
65 | .output_subarea {
66 | height: 100%;
67 | max-width: 100%;
68 |
69 | &.rendered_html.output_result{
70 | margin: 0;
71 | padding: 0;
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
78 |
79 | .cell div.nbp-unpresent{
80 | display: none;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/less/sorter.less:
--------------------------------------------------------------------------------
1 | body.nbp-sorting {
2 | #notebook_panel {
3 | margin-bottom: @sorter-height;
4 | }
5 |
6 | .nbp-sorter-drawer {
7 | padding-top: 0;
8 | transition: padding-top @tx-dur @tx-fn;
9 | }
10 | }
11 |
12 | body.nbp-presenting{
13 | .nbp-sorter-drawer {
14 | opacity: 0.75;
15 | }
16 | }
17 |
18 | body.nbp-presenting.nbp-sorting{
19 | .nbp-present {
20 | box-shadow: @z-shadow;
21 | transition: box-shadow @tx-dur @tx-fn;
22 | }
23 | }
24 |
25 | body.nbp-presenting:not(.nbp-sorting){
26 | .nbp-present {
27 | box-shadow: none;
28 | transition: box-shadow @tx-dur @tx-fn;
29 | }
30 | }
31 |
32 | .nbp-sorter-drawer {
33 | position: fixed;
34 | left: 0;
35 | bottom: 0;
36 | right: @app-bar-width;
37 | height: @sorter-height;
38 |
39 | z-index: @z-sorter;
40 | padding-top: @sorter-height;
41 | transition: padding-top @tx-dur @tx-fn;
42 |
43 | .nbp-sorter{
44 | .nbp-ui();
45 |
46 | position: absolute;
47 | width: 100%;
48 | height: @sorter-height;
49 |
50 | color: @jpy-brand;
51 | box-shadow: @z-shadow;
52 | background-color: @ui-bg;
53 | opacity: 0.9;
54 |
55 | h2 {
56 | font-size: 18px;
57 | margin: 0;
58 | padding: 10px;
59 | }
60 |
61 | .nbp-sorter-label {
62 | position: absolute;
63 | left: 0;
64 | bottom: 0;
65 | }
66 |
67 | .nbp-sorter-empty {
68 | display: none;
69 |
70 | a, .btn {
71 | color: @jpy-brand;
72 | background-color: transparent;
73 | }
74 |
75 | .btn {
76 | border: solid 1px @jpy-brand;
77 | }
78 | }
79 |
80 | &.empty .nbp-sorter-empty {
81 | display: block;
82 | position: absolute;
83 | top: 0;
84 | text-align: center;
85 | padding: 10px;
86 |
87 | .nbp-empty-intro {
88 | width: @sorter-slide-width * 1.6;
89 | padding: 0px 30px 0 0;
90 | }
91 |
92 | i {
93 | vertical-align: middle;
94 | }
95 |
96 | .nbp-tomes {
97 | position: absolute;
98 | left: @sorter-slide-width * 1.6;
99 | top: 0;
100 | right: 0;
101 | display: flex;
102 |
103 | .nbp-tome {
104 | flex: 1;
105 | text-align: center;
106 | padding: 5px;
107 | }
108 | }
109 | }
110 |
111 | .nbp-region-toolbar{
112 | position: absolute;
113 | display: none;
114 | top: 0;
115 | left: 50%;
116 |
117 | opacity: 0;
118 |
119 | text-align: center;
120 | }
121 |
122 | .nbp-slides-wrap {
123 | overflow-x: auto;
124 | overflow-y: hidden;
125 | position: relative;
126 | width: 100%;
127 | padding-top: @btn-titled-height;
128 | height: @sorter-height;
129 |
130 | .empty {
131 | position: absolute;
132 | }
133 |
134 | .slide.nbp-mini {
135 | position: absolute;
136 | .nbp-region {
137 | background-size: cover;
138 | }
139 | }
140 | }
141 | }
142 | }
143 |
144 | .nbp-slide-theme-picker{
145 | .nbp-ui();
146 |
147 | background-color: @ui-bg;
148 | position: fixed;
149 | bottom: @sorter-height;
150 | right: @app-bar-width;
151 | z-index: @z-sorter;
152 | top: 0;
153 | overflow-y: auto;
154 | padding-left: 5px;
155 | }
156 |
--------------------------------------------------------------------------------
/src/less/speaker.less:
--------------------------------------------------------------------------------
1 | body.nbp-presenting {
2 | .nbpresent_speaker{
3 | .nbp-ui();
4 |
5 | z-index: @z-speaker;
6 | display: block;
7 | position: fixed;
8 | bottom: 10px;
9 | right: @app-bar-width;
10 |
11 | .nbp-presenter-toolbar {
12 | transition: all 0.2s;
13 | float: right;
14 |
15 | &:hover, &.fake_hover{
16 | opacity: 1;
17 | transition: all 0.2s;
18 | }
19 |
20 | a {
21 | color: @jpy-brand;
22 | }
23 | }
24 | }
25 | }
26 |
27 | body:not(.nbp-presenting) .nbpresent_speaker{
28 | display: none;
29 | }
30 |
--------------------------------------------------------------------------------
/src/less/theme-card.less:
--------------------------------------------------------------------------------
1 | .nbp-theme-card {
2 | .nbp-ui();
3 |
4 | width: @theme-card-height;
5 | height: @theme-card-height;
6 | background-color: white;
7 | box-shadow: z-shadow;
8 | border-radius: 2px;
9 | border: solid 1px @ui-bg;
10 | margin: 5px 5px 0 0;
11 | position: relative;
12 |
13 | .nbp-theme-card-backgrounds {
14 | position:absolute;
15 | top: @theme-card-swatch-size;
16 | right: 0;
17 | bottom: 0;
18 |
19 | .nbp-theme-card-background{
20 | width: @theme-card-swatch-size;
21 | background-size: cover;
22 | background-position: center;
23 | }
24 | }
25 |
26 | .nbp-theme-card-fonts {
27 | position: absolute;
28 | top: @theme-card-swatch-size + 2;
29 | bottom: 0;
30 | left: 4px;
31 | right: @theme-card-swatch-size;
32 | overflow-y: auto;
33 | padding-bottom: 4px;
34 | }
35 |
36 | .nbp-theme-card-palette {
37 | position: absolute;
38 | top: 0;
39 | left: 0;
40 | right: 0;
41 | overflow-x: auto;
42 | .nbp-theme-card-color {
43 | width: @theme-card-swatch-size;
44 | height: @theme-card-swatch-size;
45 | float: left;
46 | }
47 | }
48 |
49 | .nbp-theme-card-font {
50 | font-size: 12px;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/less/theme-ui.less:
--------------------------------------------------------------------------------
1 | body:not(.nbp-theming) {
2 | .nbp-theme-manager {
3 | margin-top: -@theme-mgr-height;
4 | transition: margin-top @tx-dur @tx-fn;
5 | }
6 | }
7 |
8 | .nbp-theme-manager {
9 | .nbp-ui();
10 |
11 | position: fixed;
12 | height: @theme-mgr-height;
13 | z-index: @z-theme-mgr;
14 | right: @app-bar-width;
15 | left: 0;
16 | bottom: 0;
17 | top: 0;
18 | background-color: @ui-bg-alpha;
19 |
20 | margin-top: 0;
21 | transition: margin-top @tx-dur @tx-fn;
22 |
23 | .nbp-toolbar{
24 | float: left;
25 | }
26 |
27 | h2 {
28 | margin: 0;
29 | padding: 0;
30 | color: @jpy-brand;
31 | text-align: center;
32 | font-size: 18px;
33 | }
34 |
35 | .nbp-theme-previews {
36 | position: absolute;
37 | left: @app-bar-width;
38 | height: @theme-mgr-height;
39 | right: 250px;
40 | overflow-y: auto;
41 | overflow-x: hidden;
42 |
43 | .nbp-theme-preview {
44 | float: left;
45 | position: relative;
46 |
47 | .nbp-default-theme {
48 | position: absolute;
49 | bottom: 4px;
50 | right: 4px;
51 | }
52 |
53 | &.nbp-theme-preview-current .nbp-theme-preview-card{
54 | border: solid 3px @jpy-brand;
55 | }
56 | }
57 | }
58 |
59 | .nbp-theme-previews-canned{
60 | position: absolute;
61 | right: 0;
62 | height: @theme-mgr-height;
63 | width: 220px;
64 | overflow-y: auto;
65 | overflow-x: hidden;
66 |
67 | .nbp-theme-preview-canned {
68 | float: right;
69 | opacity: 0.5;
70 | &:hover {
71 | opacity: 1.0;
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/less/toolbar.less:
--------------------------------------------------------------------------------
1 | .nbp-toolbar {
2 | .btn-group {
3 | box-shadow: @z-shadow;
4 |
5 | .btn {
6 | &:last-child{
7 | border-right: none;
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/less/tour.less:
--------------------------------------------------------------------------------
1 | .tour-nbpresent {
2 | .nbp-ui();
3 |
4 | font-size: 18px;
5 | padding: 0px;
6 | box-shadow: @z-shadow;
7 | max-width: 200px;
8 | min-width: 400px;
9 | width: 25vw;
10 |
11 | .popover-title {
12 | font-size: @tour-padding;
13 | padding: @tour-padding;
14 | }
15 |
16 | .popover-content {
17 | padding: @tour-padding;
18 | }
19 |
20 | .popover-navigation{
21 | background-color: @ui-bg;
22 | }
23 |
24 | .popover-navigation .btn {
25 | color: @jpy-brand;
26 | font-size: 14px;
27 | background-color: @ui-bg;
28 | border: none;
29 |
30 | &:hover{
31 | color: @ui-bg;
32 | background-color: @jpy-brand;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/less/variables.less:
--------------------------------------------------------------------------------
1 | // z-stack
2 | @z-present: 999;
3 | @z-presented: 1000;
4 |
5 | @z-speaker: 9001;
6 | @z-help: 9001;
7 |
8 | @z-app-bar: 9002;
9 | @z-editor: 9002;
10 | @z-regiontree: 9002;
11 | @z-sorter: 9002;
12 | @z-layouts: 9002;
13 | @z-link-overlay: 9002;
14 |
15 | @z-theme-mgr: 9003;
16 | @z-theme-editor: 9002;
17 |
18 | @z-tour: 9999;
19 |
20 | // colors
21 | @ui-bg: #222;
22 | @ui-bg-alpha: rgba(50, 50, 50, 0.95);
23 | @jpy-brand: #f37626;
24 | @active: #337ab7;
25 | @part-source-color: #303F9F;
26 | @part-outputs-color: #D84315;
27 | @part-widgets-color: average(@part-source-color, @part-outputs-color);
28 | @part-whole-color: @jpy-brand;
29 |
30 |
31 | // misc
32 | @btn-titled-height: 64px;
33 |
34 | // app
35 | @app-bar-width: 78px;
36 |
37 | // theme
38 | @theme-card-height: @sorter-slide-height;
39 | @theme-mgr-height: @theme-card-height + 48;
40 | @theme-swatch-size: 24px;
41 | @theme-card-swatch-size: 15px;
42 |
43 | // sorter
44 | @sorter-slide-width: 160px;
45 | @sorter-slide-height: 90px;
46 | @sorter-height: @sorter-slide-height + 100;
47 |
48 | // editor
49 | @editor-sidebar-width: 320px;
50 |
51 | // pretties
52 | @z-shadow: 0px 0px 12px 1px rgba(87, 87, 87, 0.2);
53 |
54 | // tour
55 | @tour-padding: 24px;
56 |
57 | // transitions
58 | @tx-dur: 0.2s;
59 | @tx-fn: ease;
60 |
--------------------------------------------------------------------------------