├── ipyvuetify ├── py.typed ├── extra │ ├── __init__.py │ ├── file_input.vue │ └── file_input.py ├── _version.py ├── Html.py ├── VuetifyTemplate.py ├── __init__.py └── Themes.py ├── requirements.txt ├── js ├── src │ ├── nodeps.js │ ├── vue_alias.js │ ├── notebook.js │ ├── plugins │ │ ├── vuetify.js │ │ └── nodepsVuetify.js │ ├── nodepsEmbed.js │ ├── public-path.js │ ├── embed.js │ ├── extension.js │ ├── index.js │ ├── Html.js │ ├── labplugin.js │ ├── VuetifyTemplate.js │ ├── jupyterEnvironment.js │ ├── styles.css │ ├── nodepsVuetifyView.js │ ├── VuetifyView.js │ └── Themes.js ├── .babelrc ├── README.md ├── webpack.config.lab3.js ├── postcss.config.js ├── .eslintrc.js ├── package.json └── webpack.config.js ├── docs ├── images │ ├── dark-demo.gif │ ├── light-demo.gif │ ├── responsive-laptop.png │ ├── responsive-mobile.png │ ├── template-hot-reload.gif │ └── msd-logo.svg ├── installation.rst ├── _static │ └── custom.css ├── fruit-selector.vue ├── conf.py ├── introduction.rst ├── index.rst ├── advanced_usage.rst ├── showcase.py ├── template_usage.rst └── usage.rst ├── prefix └── etc │ └── nbconfig │ └── notebook.d │ └── jupyter-vuetify.json ├── mypy.ini ├── MANIFEST.in ├── RELEASE.md ├── tests └── ui │ ├── snapshots │ └── tests │ │ └── ui │ │ └── button_test.py │ │ ├── test_button-solara-chromium-linux-reference.png │ │ ├── test_button-voila-chromium-linux-reference.png │ │ ├── test_button-jupyter_lab-chromium-linux-reference.png │ │ └── test_button-jupyter_notebook-chromium-linux-reference.png │ └── button_test.py ├── generate_source ├── package.json ├── patch_vuetify_build.py ├── generate_api.sh ├── base.json ├── es6-template.njk ├── python.njk ├── generate_source.py └── generate_schema.py ├── release.sh ├── .bumpversion.cfg ├── .readthedocs.yaml ├── .gitignore ├── noxfile.py ├── .pre-commit-config.yaml ├── LICENSE ├── examples ├── extra │ ├── FileInput.ipynb │ └── FileInput example random access.ipynb ├── Examples template.ipynb └── Examples.ipynb ├── setup.py ├── pyproject.toml ├── README.md └── .github └── workflows └── test.yml /ipyvuetify/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | -------------------------------------------------------------------------------- /ipyvuetify/extra/__init__.py: -------------------------------------------------------------------------------- 1 | from .file_input import FileInput 2 | -------------------------------------------------------------------------------- /ipyvuetify/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.11.3" 2 | semver = "^" + __version__ 3 | -------------------------------------------------------------------------------- /js/src/nodeps.js: -------------------------------------------------------------------------------- 1 | import "./public-path"; 2 | 3 | export * from "./nodepsEmbed"; 4 | -------------------------------------------------------------------------------- /js/src/vue_alias.js: -------------------------------------------------------------------------------- 1 | import { Vue } from "jupyter-vue"; 2 | 3 | export default Vue; 4 | -------------------------------------------------------------------------------- /docs/images/dark-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/docs/images/dark-demo.gif -------------------------------------------------------------------------------- /docs/images/light-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/docs/images/light-demo.gif -------------------------------------------------------------------------------- /docs/images/responsive-laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/docs/images/responsive-laptop.png -------------------------------------------------------------------------------- /docs/images/responsive-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/docs/images/responsive-mobile.png -------------------------------------------------------------------------------- /docs/images/template-hot-reload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/docs/images/template-hot-reload.gif -------------------------------------------------------------------------------- /prefix/etc/nbconfig/notebook.d/jupyter-vuetify.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "jupyter-vuetify/extension": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | check_untyped_defs = True 3 | ignore_missing_imports = True 4 | no_implicit_optional = False 5 | allow_empty_bodies = True 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | recursive-include prefix *.* 3 | recursive-include ipyvuetify/extra *.vue 4 | recursive-include ipyvuetify/generated *.* 5 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Fully automated 2 | 3 | $ ./release.sh 4 | 5 | ## Making an alpha release 6 | 7 | $ ./release.sh patch --new-version 1.29.1a1 8 | -------------------------------------------------------------------------------- /js/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": 3, 8 | "modules": false 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /js/README.md: -------------------------------------------------------------------------------- 1 | Jupyter widgets based on vuetify UI components 2 | 3 | ## Package Install 4 | 5 | **Prerequisites** 6 | 7 | - [node](http://nodejs.org/) 8 | 9 | ```bash 10 | npm install --save jupyter-vuetify 11 | ``` 12 | -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/button_test.py/test_button-solara-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/tests/ui/snapshots/tests/ui/button_test.py/test_button-solara-chromium-linux-reference.png -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/button_test.py/test_button-voila-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/tests/ui/snapshots/tests/ui/button_test.py/test_button-voila-chromium-linux-reference.png -------------------------------------------------------------------------------- /generate_source/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "_", 3 | "repository": "_", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "widget-gen": "git+https://github.com/mariobuikhuizen/widget-gen.git#workarounds" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/button_test.py/test_button-jupyter_lab-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/tests/ui/snapshots/tests/ui/button_test.py/test_button-jupyter_lab-chromium-linux-reference.png -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/button_test.py/test_button-jupyter_notebook-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyvuetify/master/tests/ui/snapshots/tests/ui/button_test.py/test_button-jupyter_notebook-chromium-linux-reference.png -------------------------------------------------------------------------------- /js/src/notebook.js: -------------------------------------------------------------------------------- 1 | /** 2 | * public-path is necessary for the loading of fonts in jupyter notebook. This import does not 3 | * work in jupyer lab so it is imported in this separate file. 4 | */ 5 | import "./public-path"; 6 | 7 | export * from "./index"; 8 | -------------------------------------------------------------------------------- /js/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; // eslint-disable-line import/no-extraneous-dependencies 2 | import Vuetify from "@mariobuikhuizen/vuetify"; 3 | import "@mariobuikhuizen/vuetify/dist/vuetify.min.css"; 4 | 5 | Vue.use(Vuetify); 6 | 7 | export default new Vuetify(); 8 | export { Vue, Vuetify }; 9 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -o pipefail 3 | # usage: ./release minor -n 4 | version=$(bump2version --dry-run --list $* | grep new_version | sed -r s,"^.*=",,) 5 | echo Version tag v$version 6 | bumpversion $* --verbose 7 | (cd js && npm install) 8 | git add js/package-lock.json && git commit --amend --no-edit 9 | git push upstream master v$version 10 | -------------------------------------------------------------------------------- /js/webpack.config.lab3.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | const rules = [ 4 | { 5 | test: /\.css$/, 6 | use: ["postcss-loader"], 7 | }, 8 | ]; 9 | 10 | module.exports = { 11 | resolve: { 12 | alias: { 13 | vue$: path.resolve(__dirname, "lib", "vue_alias"), 14 | }, 15 | }, 16 | module: { 17 | rules: rules, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /generate_source/patch_vuetify_build.py: -------------------------------------------------------------------------------- 1 | # upgrading these packages fails when using yarn cli 2 | for path in ["vuetify/packages/vuetify/package.json", "vuetify/packages/docs/package.json"]: 3 | with open(path, "r") as f: 4 | file_contents = f.read() 5 | 6 | with open(path, "w") as f: 7 | f.write(file_contents.replace('"fibers": "^4.0.1"', '"fibers": "^5"')) 8 | -------------------------------------------------------------------------------- /js/src/nodepsEmbed.js: -------------------------------------------------------------------------------- 1 | import "./styles.css"; 2 | 3 | export { VuetifyView } from "./VuetifyView"; 4 | export * from "./generated"; 5 | export { HtmlModel } from "./Html"; 6 | export { VuetifyTemplateModel } from "./VuetifyTemplate"; 7 | export { ThemeModel, ThemeColorsModel } from "./Themes"; 8 | 9 | export const { version } = require("../package.json"); // eslint-disable-line global-require 10 | -------------------------------------------------------------------------------- /js/src/plugins/nodepsVuetify.js: -------------------------------------------------------------------------------- 1 | // import vuetify from 'vuetify'; 2 | 3 | /* The import above would break voila-vuetify and voila-embed users, so use the workaround below 4 | * until all users have upgraded to Voila-vuetify > 0.2.2 and voila-embed > 2020-02-11, or 5 | * ipyvuetify bumps a major version */ 6 | const vuetify = 7 | window.app && window.app.$vuetify && window.app.$options.vuetify; 8 | 9 | export default vuetify; 10 | -------------------------------------------------------------------------------- /js/src/public-path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Setup notebook base URL 3 | * 4 | * Some static assets may be required by the custom widget javascript. The base 5 | * url for the notebook is not known at build time and is therefore computed 6 | * dynamically. 7 | */ 8 | 9 | const baseUrl = document.querySelector("body").getAttribute("data-base-url"); 10 | __webpack_public_path__ = `${baseUrl}nbextensions/jupyter-vuetify/`; // eslint-disable-line no-undef 11 | -------------------------------------------------------------------------------- /js/src/embed.js: -------------------------------------------------------------------------------- 1 | // Entry point for the unpkg bundle containing custom model definitions. 2 | // 3 | // It differs from the notebook bundle in that it does not need to define a 4 | // dynamic baseURL for the static assets and may load some css that would 5 | // already be loaded by the notebook otherwise. 6 | 7 | // Export widget models and views, and the npm package version number. 8 | // module.exports = require('./example.js'); 9 | // module.exports['version'] = require('../package.json').version; 10 | export * from "./index"; 11 | -------------------------------------------------------------------------------- /generate_source/generate_api.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Note!: this will only work on node <=14 4 | 5 | # use script directory as working directory 6 | cd "${0%/*}" 7 | 8 | rm -rf vuetify 9 | git clone --branch v2.2.26 --depth 1 https://github.com/vuetifyjs/vuetify.git 10 | npm install -g yarn 11 | python patch_vuetify_build.py 12 | (cd vuetify && yarn --ignore-optional && yarn build) 13 | 14 | node -e 'console.log(JSON.stringify(require("./vuetify/packages/api-generator/dist/api.js"), null, 2))' > vuetify_api.json 15 | pre-commit run --files vuetify_api.json 16 | -------------------------------------------------------------------------------- /js/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require("autoprefixer"); 2 | const scopify = require("postcss-scopify"); 3 | 4 | module.exports = (ctx) => { 5 | const plugins = [ 6 | autoprefixer({ 7 | remove: false, 8 | }), 9 | ]; 10 | // There are styles that should be global in styles.css, so we skip it 11 | if (!ctx.file.endsWith("ipyvuetify/js/lib/styles.css")) { 12 | plugins.push( 13 | scopify({ 14 | scope: ":where(.vuetify-styles)", 15 | }) 16 | ); 17 | } 18 | 19 | return { 20 | plugins, 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.11.3 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)(\.(?P\d+))(\.(?P\d+))((?P.)(?P\d+))? 6 | serialize = 7 | {major}.{minor}.{patch}{release}{build} 8 | {major}.{minor}.{patch} 9 | 10 | [bumpversion:part:release] 11 | optional_value = g 12 | first_value = g 13 | values = 14 | a 15 | b 16 | g 17 | 18 | [bumpversion:file:ipyvuetify/_version.py] 19 | 20 | [bumpversion:file:js/package.json] 21 | 22 | [bumpversion:file:docs/conf.py] 23 | 24 | [bumpversion:file:pyproject.toml] 25 | -------------------------------------------------------------------------------- /js/src/extension.js: -------------------------------------------------------------------------------- 1 | // This file contains the javascript that is run when the notebook is loaded. 2 | // It contains some requirejs configuration and the `load_ipython_extension` 3 | // which is required for any notebook extension. 4 | 5 | // Configure requirejs 6 | if (window.require) { 7 | window.require.config({ 8 | map: { 9 | "*": { 10 | "jupyter-vuetify": "nbextensions/jupyter-vuetify/index", 11 | }, 12 | }, 13 | }); 14 | } 15 | 16 | // Export the required load_ipython_extension 17 | module.exports = { 18 | load_ipython_extension() {}, 19 | }; 20 | -------------------------------------------------------------------------------- /js/src/index.js: -------------------------------------------------------------------------------- 1 | import "typeface-roboto"; 2 | import "material-design-icons-iconfont/dist/material-design-icons.css"; 3 | import "@mdi/font/css/materialdesignicons.css"; 4 | import "./styles.css"; 5 | 6 | export { VuetifyView } from "./VuetifyView"; 7 | export * from "./generated"; 8 | export { HtmlModel } from "./Html"; 9 | export { VuetifyTemplateModel } from "./VuetifyTemplate"; 10 | export { ThemeModel, ThemeColorsModel } from "./Themes"; 11 | export { Vue, Vuetify } from "./plugins/vuetify"; 12 | 13 | export const { version } = require("../package.json"); // eslint-disable-line global-require 14 | -------------------------------------------------------------------------------- /js/src/Html.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: off */ 2 | import { HtmlModel as VueHtmlModel } from "jupyter-vue"; 3 | 4 | export class HtmlModel extends VueHtmlModel { 5 | defaults() { 6 | return { 7 | ...super.defaults(), 8 | ...{ 9 | _model_name: "HtmlModel", 10 | _view_name: "VuetifyView", 11 | _view_module: "jupyter-vuetify", 12 | _model_module: "jupyter-vuetify", 13 | _view_module_version: "0.1.11", 14 | _model_module_version: "0.1.11", 15 | }, 16 | }; 17 | } 18 | } 19 | 20 | HtmlModel.serializers = { 21 | ...VueHtmlModel.serializers, 22 | }; 23 | -------------------------------------------------------------------------------- /js/src/labplugin.js: -------------------------------------------------------------------------------- 1 | const base = require("@jupyter-widgets/base"); 2 | const apputils = require("@jupyterlab/apputils"); 3 | 4 | const jupyterVuetify = require("./index"); 5 | 6 | module.exports = { 7 | id: "jupyter-vuetify", 8 | requires: [base.IJupyterWidgetRegistry], 9 | optional: [apputils.IThemeManager], 10 | activate(app, widgets, themeManager) { 11 | jupyterVuetify.ThemeModel.themeManager = themeManager; 12 | widgets.registerWidget({ 13 | name: "jupyter-vuetify", 14 | version: jupyterVuetify.version, 15 | exports: jupyterVuetify, 16 | }); 17 | }, 18 | autoStart: true, 19 | }; 20 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # build with latest available ubuntu version 8 | build: 9 | os: ubuntu-20.04 10 | tools: 11 | python: "3.10" 12 | nodejs: "16" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # Optionally set the version of Python and requirements required to build your docs 19 | python: 20 | install: 21 | - method: pip 22 | path: . 23 | extra_requirements: 24 | - doc 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | *.egg-info/ 8 | .eggs/ 9 | dist/ 10 | build/ 11 | 12 | # generated code 13 | generate_source/build 14 | generate_source/vuetify 15 | ipyvuetify/generated 16 | js/src/generated 17 | 18 | # Compiled javascript 19 | prefix/share 20 | js/lib 21 | 22 | # Unit test / coverage reports 23 | .nox 24 | 25 | # Sphinx documentation 26 | docs/_build/ 27 | 28 | # Jupyter Notebook 29 | .ipynb_checkpoints 30 | 31 | # Unit test / coverage reports 32 | .ruff_cache 33 | 34 | # node installed dependencies 35 | **/node_modules/ 36 | -------------------------------------------------------------------------------- /js/src/VuetifyTemplate.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: off */ 2 | import { VueTemplateModel } from "jupyter-vue"; 3 | 4 | export class VuetifyTemplateModel extends VueTemplateModel { 5 | defaults() { 6 | return { 7 | ...super.defaults(), 8 | ...{ 9 | _model_name: "VuetifyTemplateModel", 10 | _view_name: "VuetifyView", 11 | _view_module: "jupyter-vuetify", 12 | _model_module: "jupyter-vuetify", 13 | _view_module_version: "0.1.0", 14 | _model_module_version: "0.1.0", 15 | }, 16 | }; 17 | } 18 | } 19 | 20 | VuetifyTemplateModel.serializers = { 21 | ...VueTemplateModel.serializers, 22 | }; 23 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | """ 2 | All the process that can be run using nox. 3 | 4 | The nox run are build in isolated environment that will be stored in .nox. to force the venv update, remove the .nox/xxx folder. 5 | """ 6 | 7 | import nox 8 | 9 | 10 | @nox.session(reuse_venv=True) 11 | def lint(session): 12 | """Apply the pre-commits.""" 13 | session.install("pre-commit") 14 | session.run("pre-commit", "run", "-a", *session.posargs) 15 | 16 | 17 | @nox.session(reuse_venv=True) 18 | def docs(session): 19 | """Build the documentation.""" 20 | session.install(".[doc]") 21 | session.run("sphinx-build", "-v", "-b", "html", "docs", "docs/_build/html") 22 | -------------------------------------------------------------------------------- /ipyvuetify/Html.py: -------------------------------------------------------------------------------- 1 | # This subclass is needed to use VuetifyView instead of VueView so CSS workarounds for Vuetify are 2 | # added in the frontend. 3 | 4 | from ipyvue import Html as VueHtml 5 | from traitlets import Unicode 6 | 7 | from ._version import semver 8 | 9 | 10 | class Html(VueHtml): 11 | 12 | _model_name = Unicode("HtmlModel").tag(sync=True) 13 | 14 | _view_name = Unicode("VuetifyView").tag(sync=True) 15 | 16 | _view_module = Unicode("jupyter-vuetify").tag(sync=True) 17 | 18 | _model_module = Unicode("jupyter-vuetify").tag(sync=True) 19 | 20 | _view_module_version = Unicode(semver).tag(sync=True) 21 | 22 | _model_module_version = Unicode(semver).tag(sync=True) 23 | 24 | 25 | __all__ = ["Html"] 26 | -------------------------------------------------------------------------------- /generate_source/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "widgets": { 3 | "VuetifyWidget": { 4 | "inherits": ["VueWidget"], 5 | "properties": { 6 | "_view_name": "VuetifyView", 7 | "_view_module": "jupyter-vuetify", 8 | "_model_module": "jupyter-vuetify", 9 | "_view_module_version": "^1.8.5", 10 | "_model_module_version": "^1.8.5", 11 | 12 | "_metadata": { 13 | "type": "object", 14 | "allowNull": true, 15 | "default": null 16 | } 17 | } 18 | }, 19 | 20 | "Text": { 21 | "inherits": ["VuetifyWidget"], 22 | "properties": { 23 | "value": { 24 | "type": "string", 25 | "default": "" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ipyvuetify/VuetifyTemplate.py: -------------------------------------------------------------------------------- 1 | # This subclass is needed to use VuetifyView instead of VueView so CSS workarounds for Vuetify are 2 | # added in the frontend. 3 | 4 | from ipyvue import VueTemplate 5 | from traitlets import Unicode 6 | 7 | from ._version import semver 8 | 9 | 10 | class VuetifyTemplate(VueTemplate): 11 | 12 | _model_name = Unicode("VuetifyTemplateModel").tag(sync=True) 13 | 14 | _view_name = Unicode("VuetifyView").tag(sync=True) 15 | 16 | _view_module = Unicode("jupyter-vuetify").tag(sync=True) 17 | 18 | _model_module = Unicode("jupyter-vuetify").tag(sync=True) 19 | 20 | _view_module_version = Unicode(semver).tag(sync=True) 21 | 22 | _model_module_version = Unicode(semver).tag(sync=True) 23 | 24 | 25 | __all__ = ["VuetifyTemplate"] 26 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: "https://github.com/psf/black" 3 | rev: "22.3.0" 4 | hooks: 5 | - id: black 6 | stages: [commit] 7 | - repo: "https://github.com/commitizen-tools/commitizen" 8 | rev: "v2.18.0" 9 | hooks: 10 | - id: commitizen 11 | stages: [commit-msg] 12 | - repo: "https://github.com/kynan/nbstripout" 13 | rev: "0.5.0" 14 | hooks: 15 | - id: nbstripout 16 | stages: [commit] 17 | - repo: "https://github.com/pre-commit/mirrors-prettier" 18 | rev: "v2.7.1" 19 | hooks: 20 | - id: prettier 21 | stages: [commit] 22 | 23 | - repo: https://github.com/charliermarsh/ruff-pre-commit 24 | rev: "v0.0.213" 25 | hooks: 26 | - id: ruff 27 | stages: [commit] 28 | -------------------------------------------------------------------------------- /js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: "airbnb-base", 7 | globals: { 8 | Atomics: "readonly", 9 | SharedArrayBuffer: "readonly", 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: "module", 14 | }, 15 | plugins: ["vue"], 16 | rules: { 17 | indent: ["error", 4, { SwitchCase: 1 }], 18 | "import/prefer-default-export": "off", 19 | camelcase: [ 20 | "error", 21 | { 22 | allow: [ 23 | "__webpack_public_path__", 24 | "load_ipython_extension", 25 | "_model_name", 26 | ], 27 | }, 28 | ], 29 | "no-underscore-dangle": "off", 30 | "class-methods-use-this": "off", 31 | "no-use-before-define": "off", 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Using pip 5 | --------- 6 | 7 | .. code-block:: bash 8 | 9 | $ pip install ipyvuetify 10 | 11 | Using conda 12 | ----------- 13 | 14 | .. code-block:: bash 15 | 16 | (myenv) $ conda install -c conda-forge ipyvuetify 17 | 18 | Jupyter Lab 19 | ----------- 20 | 21 | If you're using only the classic notebook, you're done. If you're using Jupyter Lab, the extension has to be installed 22 | in Jupyter Lab: 23 | 24 | .. code-block:: bash 25 | 26 | $ jupyter labextension install jupyter-vuetify 27 | 28 | .. note:: 29 | ipyvuetify depends on ipywidgets being installed in Jupyter Lab, see the `ipywidgets documentation 30 | `_ on how to do 31 | that. 32 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | .vuetify-styles .v-text-field input { 2 | box-shadow: unset; 3 | } 4 | 5 | div.jupyter_container.docutils { 6 | padding: 0.4em; 7 | } 8 | 9 | /* Hero from Homepage */ 10 | #hero { 11 | display: flex; 12 | flex-direction: row; 13 | min-height: min(calc(80vh), 1100px); 14 | } 15 | 16 | #hero img { 17 | border: 1px solid var(--pst-color-border); 18 | border-radius: 0.25em; 19 | } 20 | 21 | #hero-left { 22 | max-width: 476px; 23 | width: 40%; 24 | margin: auto 1em; 25 | } 26 | 27 | #hero-right { 28 | min-width: 476px; 29 | width: 60%; 30 | margin: auto 1em; 31 | } 32 | 33 | /* customize primary color to align with the vuetify website */ 34 | html[data-theme="light"] { 35 | --pst-color-primary: #1867c0; 36 | } 37 | 38 | html[data-theme="dark"] { 39 | --pst-color-primary: #1697f6; 40 | } 41 | -------------------------------------------------------------------------------- /js/src/jupyterEnvironment.js: -------------------------------------------------------------------------------- 1 | function scriptExists(fingerprint) { 2 | return [].slice 3 | .call(document.getElementsByTagName("script")) 4 | .map((e) => e.src) 5 | .find((e) => e.includes(fingerprint)); 6 | } 7 | 8 | export function getLabContainer() { 9 | return ( 10 | scriptExists("/lab/static/main.") && 11 | document.querySelector("div#main.jp-ApplicationShell") 12 | ); 13 | } 14 | 15 | export function getNotebookContainer() { 16 | return ( 17 | scriptExists("/static/notebook/js/main.min.js") && 18 | document.querySelector("body.notebook_app div#site") 19 | ); 20 | } 21 | 22 | export function getVoilaContainer() { 23 | return ( 24 | scriptExists("/voila/static/main.js") && 25 | document.querySelector("div#notebook") 26 | ); 27 | } 28 | 29 | export function getContainer() { 30 | return getLabContainer() || getNotebookContainer() || getVoilaContainer(); 31 | } 32 | -------------------------------------------------------------------------------- /js/src/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | /* font sizes in vuetify are in rem units. To get the correct font size, the html tag has to have a font size of 3 | * 16px */ 4 | font-size: 16px; 5 | } 6 | 7 | :where(.vuetify-styles) div.v-application--wrap { 8 | /* disable min-height: 100vh set by vuetify */ 9 | min-height: unset; 10 | } 11 | 12 | .vuetify-styles { 13 | /* Rules set on the html element in the vuetify styles are no longer applied after prefixing all selectors, so copy 14 | * them here. */ 15 | overflow-x: hidden; 16 | text-rendering: optimizeLegibility; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 20 | 21 | /* From _reset.scss */ 22 | box-sizing: border-box; 23 | } 24 | 25 | /* fix for override from jp-ThemedContainer button */ 26 | button.v-btn--rounded { 27 | border-radius: 28px; 28 | } 29 | -------------------------------------------------------------------------------- /ipyvuetify/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | from .generated import * # noqa: F403 3 | from .Html import Html 4 | from .Themes import theme 5 | from .VuetifyTemplate import VuetifyTemplate 6 | 7 | 8 | def _prefix(): 9 | import sys 10 | from pathlib import Path 11 | 12 | prefix = sys.prefix 13 | here = Path(__file__).parent 14 | # for when in dev mode 15 | if (here.parent / "prefix").exists(): 16 | prefix = str(here.parent) 17 | return prefix 18 | 19 | 20 | def _jupyter_labextension_paths(): 21 | return [ 22 | { 23 | "src": f"{_prefix()}/prefix/share/jupyter/labextensions/jupyter-vuetify/", 24 | "dest": "jupyter-vuetify", 25 | } 26 | ] 27 | 28 | 29 | def _jupyter_nbextension_paths(): 30 | return [ 31 | { 32 | "section": "notebook", 33 | "src": f"{_prefix()}/prefix/share/jupyter/nbextensions/jupyter-vuetify/", 34 | "dest": "jupyter-vuetify", 35 | "require": "jupyter-vuetify/extension", 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /tests/ui/button_test.py: -------------------------------------------------------------------------------- 1 | import playwright.sync_api 2 | from IPython.display import display 3 | 4 | 5 | def test_button(ipywidgets_runner, page_session: playwright.sync_api.Page, assert_solara_snapshot): 6 | def kernel_code(): 7 | import ipyvuetify as v 8 | 9 | button = v.Btn( 10 | children=[v.Icon(children=["mdi-check"]), "Click Me!"], 11 | class_="snapshot-button", 12 | ) 13 | container = v.Container( 14 | children=[button], 15 | class_="ma-2 snapshot-container", 16 | ) 17 | 18 | def change_description(*ignore): 19 | button.children = ["Tested event"] 20 | 21 | button.on_event("click", change_description) 22 | display(container) 23 | 24 | ipywidgets_runner(kernel_code) 25 | button_sel = page_session.locator("button >> text=Click Me!") 26 | button_sel.wait_for() 27 | assert_solara_snapshot(page_session.locator(".snapshot-button").screenshot()) 28 | button_sel.click() 29 | page_session.locator("button >> text=Tested event").wait_for() 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mario Buikhuizen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/images/msd-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /js/src/nodepsVuetifyView.js: -------------------------------------------------------------------------------- 1 | import { DOMWidgetView } from "@jupyter-widgets/base"; 2 | import { vueRender, createViewContext } from "jupyter-vue"; 3 | import vuetify from "./plugins/nodepsVuetify"; 4 | 5 | export class VuetifyView extends DOMWidgetView { 6 | remove() { 7 | if (this.vueApp) { 8 | this.vueApp.$destroy(); 9 | } 10 | return super.remove(); 11 | } 12 | 13 | render() { 14 | super.render(); 15 | this.displayed.then(() => { 16 | const vueEl = document.createElement("div"); 17 | this.el.appendChild(vueEl); 18 | const view = this; 19 | 20 | this.vueApp = new Vue({ 21 | vuetify, 22 | el: vueEl, 23 | provide: { 24 | viewCtx: createViewContext(this), 25 | }, 26 | render(createElement) { 27 | // see VuetifyView.js 28 | if (!view.ipyvuetifyApp) { 29 | view.ipyvuetifyApp = createElement("v-app", [ 30 | vueRender(createElement, view.model, view), 31 | ]); 32 | } 33 | return view.ipyvuetifyApp; 34 | }, 35 | }); 36 | }); 37 | } 38 | 39 | vueRender(createElement) { 40 | return createElement({ 41 | provide: { 42 | viewCtx: createViewContext(this), 43 | }, 44 | render: () => { 45 | return vueRender(createElement, this.model, this); 46 | }, 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /generate_source/es6-template.njk: -------------------------------------------------------------------------------- 1 | {% block header %} 2 | {% endblock %} 3 | {% block imports %} 4 | {% block globalimports %} 5 | {% endblock %} 6 | {% block localimports %} 7 | {% endblock %} 8 | {% endblock %} 9 | {% block widgets %} 10 | {% for widget in widgets %} 11 | {% block widget %} 12 | {% if widget.name === "VuetifyWidget" %} 13 | import { VueModel as VueWidgetModel } from 'jupyter-vue'; 14 | {% else %} 15 | {% if outputMultiple %} 16 | {% for ref in widgets[0].localDependencies if ref !== "VuetifyWidgetModel" %} 17 | import { {{ ref }}Model } from './{{ ref }}'; 18 | {% endfor %} 19 | {% endif %} 20 | {% endif %} 21 | 22 | export class {{ widget.name }}Model extends {{ widget.inherits[0] }}Model { 23 | {% block widgetbody %} 24 | defaults() { 25 | return { 26 | ...super.defaults(), 27 | ...{ 28 | _model_name: '{{ widget.name }}Model', 29 | {% if widget.properties %} 30 | {% for propName, prop in widget.properties %} 31 | {{ propName }}: {{ prop.default }}, 32 | {% endfor %} 33 | {% endif %} 34 | }, 35 | }; 36 | } 37 | {% if widget.name !== "VuetifyWidget" %} 38 | 39 | getVueTag() { // eslint-disable-line class-methods-use-this 40 | return 'v{{ widget.name | replace(r/([A-Z][a-z]+)/g, "-$1") | lower}}'; 41 | } 42 | {% endif %} 43 | {% endblock %} 44 | } 45 | 46 | {{ widget.name }}Model.serializers = { 47 | ...{{ widget.inherits[0] }}Model.serializers, 48 | {% for key, serializer in widget.serializers %} 49 | {{ key }}: {{ serializer }}, 50 | {% endfor %} 51 | }; 52 | {% endblock %} 53 | {% endfor %} 54 | {% endblock %} -------------------------------------------------------------------------------- /generate_source/python.njk: -------------------------------------------------------------------------------- 1 | {% block header %} 2 | {% endblock %} 3 | {% block imports %} 4 | {% block globalimports %} 5 | from traitlets import ( 6 | Unicode, Enum, Instance, Union, Float, Int, List, Tuple, Dict, 7 | Undefined, Bool, Any 8 | ) 9 | {% endblock %} 10 | {% block localimports %} 11 | {% if outputMultiple %} 12 | 13 | {% for ref in widgets[0].localDependencies %} 14 | from .{{ ref }} import {{ ref }} 15 | {% endfor %} 16 | {% endif %} 17 | {% endblock %} 18 | {% endblock %} 19 | {% block widgets %} 20 | {% for widget in widgets %} 21 | {% block widget %} 22 | {% if widget.name === "VuetifyWidget" %} 23 | from ipyvue import VueWidget 24 | from ipywidgets.widgets.widget import widget_serialization 25 | {% endif %} 26 | 27 | 28 | class {{ widget.name }}({{ widget.inherits | join(", ") }}): 29 | 30 | {% block widgetbody %} 31 | _model_name = Unicode('{{ widget.name }}Model').tag(sync=True) 32 | 33 | {% if widget.properties %} 34 | {% for propName, prop in widget.properties %} 35 | {{ propName }} = {{ prop.traitDef }} 36 | 37 | {% endfor %} 38 | {% else %} 39 | pass 40 | {% endif %} 41 | {# 42 | # Bug in traitlets, it doesn't set it, which triggers the bug fixed here: 43 | # https://github.com/jupyter-widgets/ipywidgets/pull/1675 44 | # which is not released yet (7.0.2 should have it) 45 | #} 46 | {% if widget.properties %} 47 | {% for propName, prop in widget.properties %} 48 | {% if 'List(None' in prop.traitDef %} 49 | {{ widget.name }}.{{ propName }}.default_value=None 50 | {% endif %} 51 | {% endfor %} 52 | {% endif %} 53 | {% endblock %} 54 | {% endblock %} 55 | {% endfor %} 56 | {% endblock %} 57 | {% if outputMultiple %} 58 | __all__ = ['{{ widgets[0].name }}'] 59 | {% else %} 60 | __all__ = ['{{ modules | join("', '") }}'] 61 | {% endif %} -------------------------------------------------------------------------------- /generate_source/generate_source.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import subprocess 3 | from pathlib import Path 4 | 5 | from pynpm import NPMPackage 6 | 7 | from .generate_schema import generate_schema 8 | 9 | here = Path(__file__).parent 10 | 11 | vuetify_api = here / "vuetify_api.json" 12 | base_schema = here / "base.json" 13 | build_dir = here / "build" 14 | widget_gen_schema = build_dir / "widget_gen_schema.json" 15 | 16 | widgetgen = here / "node_modules" / ".bin" / "widgetgen" 17 | 18 | es6_template = here / "es6-template.njk" 19 | python_template = here / "python.njk" 20 | 21 | project_dir = here.parent 22 | destination_js = project_dir / "js" / "src" / "generated" 23 | destination_python = project_dir / "ipyvuetify" / "generated" 24 | 25 | 26 | def reset_dir(name: Path): 27 | shutil.rmtree(name, ignore_errors=True) 28 | name.mkdir(exist_ok=True) 29 | 30 | 31 | def generate(): 32 | 33 | build_dir.mkdir(exist_ok=True) 34 | 35 | generate_schema(vuetify_api, base_schema, widget_gen_schema) 36 | 37 | NPMPackage(here / "package.json")._run_npm("ci") 38 | 39 | reset_dir(destination_js) 40 | 41 | subprocess.check_call( 42 | f"{widgetgen} -p json -o {destination_js} -t {es6_template} {widget_gen_schema} es6", 43 | shell=True, 44 | ) 45 | with open(destination_js / ".eslintrc.js", "w") as f: 46 | f.write( 47 | """ 48 | module.exports = { 49 | rules: { 50 | camelcase: 'off', 51 | quotes: 'off' 52 | }, 53 | }; 54 | """ 55 | ) 56 | 57 | reset_dir(destination_python) 58 | subprocess.check_call( 59 | f"{widgetgen} -p json -o {destination_python} -t {python_template} " 60 | f"{widget_gen_schema} python", 61 | shell=True, 62 | ) 63 | -------------------------------------------------------------------------------- /docs/fruit-selector.vue: -------------------------------------------------------------------------------- 1 | 32 | 44 | 69 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | """Configuration file for the Sphinx documentation builder. 2 | 3 | This file only contains a selection of the most common options. For a full 4 | list see the documentation: 5 | https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | """ 7 | from datetime import datetime 8 | 9 | # -- Project information ----------------------------------------------------- 10 | 11 | project = "ipyvuetify" 12 | copyright = f"2019-{datetime.now().year}, Mario Buikhuizen" 13 | author = "Mario Buikhuizen" 14 | release = "1.11.3" 15 | 16 | # -- General configuration --------------------------------------------------- 17 | 18 | extensions = ["jupyter_sphinx", "sphinx_rtd_theme", "sphinx_design"] 19 | templates_path = ["_templates"] 20 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 21 | 22 | # -- Options for HTML output ------------------------------------------------- 23 | 24 | html_theme = "pydata_sphinx_theme" 25 | html_static_path = ["_static"] 26 | html_css_files = ["custom.css"] 27 | 28 | # -- Theme configuration ----------------------------------------------------- 29 | 30 | html_theme_options = { 31 | "use_edit_page_button": True, 32 | "show_prev_next": True, 33 | "navbar_start": ["navbar-logo"], 34 | "secondary_sidebar_items": [ 35 | "page-toc.html", 36 | "searchbox.html", 37 | "edit-this-page.html", 38 | "sourcelink.html", 39 | ], 40 | "icon_links": [ 41 | { 42 | "name": "GitHub", 43 | "url": "https://github.com/widgetti/ipyvuetify", 44 | "icon": "fa-brands fa-github", 45 | }, 46 | { 47 | "name": "Pypi", 48 | "url": "https://pypi.org/project/ipyvuetify/", 49 | "icon": "fa-brands fa-python", 50 | }, 51 | ], 52 | "logo": { 53 | "text": "ipyvuetify", 54 | }, 55 | } 56 | html_context = { 57 | "github_user": "widgetti", 58 | "github_repo": "ipyvuetify", 59 | "github_version": "master", 60 | "doc_path": "docs", 61 | } 62 | -------------------------------------------------------------------------------- /ipyvuetify/Themes.py: -------------------------------------------------------------------------------- 1 | from ipywidgets import Widget 2 | from traitlets import Bool, Unicode 3 | 4 | from ._version import semver 5 | 6 | 7 | class Themes: 8 | def __init__(self): 9 | self.light = ThemeColors( 10 | _theme_name="light", 11 | primary="#1976D2", 12 | secondary="#424242", 13 | accent="#82B1FF", 14 | error="#FF5252", 15 | info="#2196F3", 16 | success="#4CAF50", 17 | warning="#FB8C00", 18 | ) 19 | 20 | self.dark = ThemeColors( 21 | _theme_name="dark", 22 | primary="#2196F3", 23 | secondary="#424242", 24 | accent="#FF4081", 25 | error="#FF5252", 26 | info="#2196F3", 27 | success="#4CAF50", 28 | warning="#FB8C00", 29 | ) 30 | 31 | 32 | class Theme(Widget): 33 | _model_name = Unicode("ThemeModel").tag(sync=True) 34 | 35 | _model_module = Unicode("jupyter-vuetify").tag(sync=True) 36 | 37 | _view_module_version = Unicode(semver).tag(sync=True) 38 | 39 | _model_module_version = Unicode(semver).tag(sync=True) 40 | 41 | dark = Bool(None, allow_none=True).tag(sync=True) 42 | 43 | dark_effective = Bool(None, allow_none=True).tag(sync=True) 44 | 45 | def __init__(self): 46 | super().__init__() 47 | 48 | self.themes = Themes() 49 | 50 | 51 | class ThemeColors(Widget): 52 | 53 | _model_name = Unicode("ThemeColorsModel").tag(sync=True) 54 | 55 | _model_module = Unicode("jupyter-vuetify").tag(sync=True) 56 | 57 | _view_module_version = Unicode(semver).tag(sync=True) 58 | 59 | _model_module_version = Unicode(semver).tag(sync=True) 60 | 61 | _theme_name = Unicode().tag(sync=True) 62 | 63 | primary = Unicode().tag(sync=True) 64 | secondary = Unicode().tag(sync=True) 65 | accent = Unicode().tag(sync=True) 66 | error = Unicode().tag(sync=True) 67 | info = Unicode().tag(sync=True) 68 | success = Unicode().tag(sync=True) 69 | warning = Unicode().tag(sync=True) 70 | anchor = Unicode(None, allow_none=True).tag(sync=True) 71 | 72 | 73 | theme = Theme() 74 | 75 | __all__ = ["theme"] 76 | -------------------------------------------------------------------------------- /ipyvuetify/extra/file_input.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 81 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Ipyvuetify is a widget library for making modern looking GUI's in Jupyter notebooks (classic and lab) and dashboards 5 | (`Voilà `_). It's based on the `Google material design philosophy 6 | `_ best known from the Android user interface. 7 | 8 | A large set of widgets is provided with many widgets having multiple variants. a few of which are displayed below. 9 | 10 | .. TODO: fix CSS collisions with rtd_theme 11 | 12 | .. jupyter-execute:: showcase.py 13 | :hide-code: 14 | 15 | There is support for responsive layouts, which are not that useful in a notebook because of the fixed width, but come in 16 | handy when making dashboards with Voilà, making your dashboard usable on tablets and phones. 17 | 18 | When comparing ipyvuetify to `ipywidgets `_, 19 | the standard widget library of Jupyter, ipyvuetify has a lot more widgets which are also more customizable and 20 | composable at the expense of a bit more verbosity in the source code. 21 | 22 | Ipyvuetify uses the machinery of `ipywidgets `_ 23 | as a base, but has different conventions for the API. This is mainly due to the fact the Python API is generated from 24 | the JavaScript library it is based on: `Vuetify `_. This exposes the full power of Vuetify and 25 | allows us to rely on the extensive documentation and examples of it. Generating code and relying on documentation from 26 | the underlying library allowed us to expose a lot of widgets to Jupyter in a relatively short amount of time. 27 | 28 | In :doc:`usage` all concepts and how they differ from ipywidgets will be explained and supported by examples. 29 | 30 | To explore which widgets are available and how to use them we defer to the 31 | `Vuetify documentation `_. You can browse examples on the left-hand 32 | side and see the source code by clicking on '< >' on the top right-hand side of the example. By reading :doc:`usage` you 33 | will be able to translate the examples to ipyvuetify. 34 | -------------------------------------------------------------------------------- /examples/extra/FileInput.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import ipywidgets as widgets\n", 10 | "from ipyvuetify.extra import FileInput\n", 11 | "import hashlib" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "file_input = FileInput()\n", 21 | "file_input" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "# first select some files in the FileInput above\n", 31 | "\n", 32 | "myfiles = file_input.get_files()\n", 33 | "myfiles[0]" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "file_input.disabled = True\n", 43 | "\n", 44 | "def md5_sum_file(file):\n", 45 | " file['file_obj'].seek(0)\n", 46 | " data = file['file_obj'].read()\n", 47 | " md5_sum = hashlib.md5(data).hexdigest()\n", 48 | " return f'{file[\"name\"]}, md5: {md5_sum}'\n", 49 | "\n", 50 | "sums = [md5_sum_file(file) for file in myfiles]\n", 51 | "file_input.disabled = False\n", 52 | "\n", 53 | "sums" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "file_input.clear()" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [] 71 | } 72 | ], 73 | "metadata": { 74 | "kernelspec": { 75 | "display_name": "Python 3", 76 | "language": "python", 77 | "name": "python3" 78 | }, 79 | "language_info": { 80 | "codemirror_mode": { 81 | "name": "ipython", 82 | "version": 3 83 | }, 84 | "file_extension": ".py", 85 | "mimetype": "text/x-python", 86 | "name": "python", 87 | "nbconvert_exporter": "python", 88 | "pygments_lexer": "ipython3", 89 | "version": "3.7.8" 90 | } 91 | }, 92 | "nbformat": 4, 93 | "nbformat_minor": 4 94 | } 95 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :html_theme.sidebar_secondary.remove: 2 | 3 | .. raw:: html 4 | 5 | 6 | 18 | 19 | ipyvuetify: Jupyter widgets based on Vuetify UI components 20 | ========================================================== 21 | 22 | .. raw:: html 23 | 24 |
25 |
26 | 27 | IpyVuetify 28 | ---------- 29 | 30 | Jupyter widgets based on Vuetify UI components 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | 33 | **Ipyvuetify** is a widget library for making modern-looking GUIs in Jupyter notebooks (`classic `__, `lab `__, `lite `__) and dashboards (`Voilà `__, `Voici `__). Based on the `Vuetify UI `__ library, it extends the standard Jupyter widget library with additional widgets that are more customizable and composable. 34 | 35 | .. raw:: html 36 | 37 | 38 | 47 | 48 | .. raw:: html 49 | 50 |
51 |
52 | 53 | .. image:: images/light-demo.gif 54 | :class: only-light 55 | 56 | .. image:: images/dark-demo.gif 57 | :class: only-dark 58 | 59 | .. raw:: html 60 | 61 |
62 |
63 | 64 | .. toctree:: 65 | :hidden: 66 | 67 | introduction 68 | installation 69 | usage 70 | advanced_usage 71 | template_usage 72 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | 4 | from setuptools import Command, setup 5 | from setuptools.command.egg_info import egg_info 6 | 7 | ROOT = Path(__file__).parent 8 | sys.path.append(str(ROOT)) 9 | 10 | 11 | def update_package_data(distribution) -> None: 12 | """Update package_data to catch changes during setup""" 13 | distribution.data_files = get_data_files() 14 | distribution.get_command_obj("build_py").finalize_options() 15 | 16 | 17 | def rel(f: Path) -> str: 18 | """give the relative path of f with respect to ROOT""" 19 | # make sure the relative links are ok even when build in the isolated directory 20 | return str(f.relative_to(ROOT)) 21 | 22 | 23 | def js_prerelease(command: Command) -> None: 24 | """Decorator for building minified js/css prior to another command""" 25 | 26 | class DecoratedCommand(command): 27 | """Decorated command to install jsdeps first.""" 28 | 29 | def run(self): 30 | """Run the command""" 31 | if not (ROOT / "prefix/share/jupyter/nbextensions/jupyter-vuetify/index.js").exists(): 32 | from pynpm import NPMPackage 33 | 34 | from generate_source import generate_source 35 | 36 | npm = NPMPackage(ROOT / "js" / "package.json") 37 | npm._run_npm("ci") 38 | generate_source.generate() 39 | npm.run_script("build") 40 | 41 | self.distribution.data_files = get_data_files() 42 | command.run(self) 43 | 44 | return DecoratedCommand 45 | 46 | 47 | def get_data_files(): 48 | """files that need to be installed in specific locations upon installation.""" 49 | nbext = [rel(f) for f in ROOT.glob("prefix/share/jupyter/nbextensions/jupyter-vuetify/*")] 50 | package = [ 51 | rel(f) for f in ROOT.glob("prefix/share/jupyter/labextensions/jupyter-vuetify/package.json") 52 | ] 53 | labext = [ 54 | rel(f) for f in ROOT.glob("prefix/share/jupyter/labextensions/jupyter-vuetify/static/*") 55 | ] 56 | nbconfig = [rel(f) for f in ROOT.glob("prefix/etc/nbconfig/notebook.d/jupyter-vuetify.json")] 57 | 58 | return [ 59 | ("share/jupyter/nbextensions/jupyter-vuetify", nbext), 60 | ("share/jupyter/labextensions/jupyter-vuetify", package), 61 | ("share/jupyter/labextensions/jupyter-vuetify/static", labext), 62 | ("etc/jupyter/nbconfig/notebook.d", nbconfig), 63 | ] 64 | 65 | 66 | setup(cmdclass={"egg_info": js_prerelease(egg_info)}) 67 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ "jupyterlab~=4.0", "setuptools>=40.8.0", "wheel", "pynpm"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "ipyvuetify" 7 | version = "1.11.3" 8 | description="Jupyter widgets based on vuetify UI components" 9 | requires-python = ">=3.6" 10 | dependencies = ["ipyvue>=1.7,<2"] 11 | keywords=["ipython", "jupyter", "widgets"] 12 | authors = [ 13 | {name = "Mario Buikhuizen", email = "mbuikhuizen@gmail.com"}, 14 | {name = "Maarten Breddels", email = "maartenbreddels@gmail.com"}, 15 | ] 16 | classifiers=[ 17 | "Development Status :: 5 - Production/Stable", 18 | "Framework :: IPython", 19 | "Intended Audience :: Developers", 20 | "Intended Audience :: Science/Research", 21 | "Topic :: Multimedia :: Graphics", 22 | "License :: OSI Approved :: MIT License", 23 | "Programming Language :: Python :: 3.6", 24 | "Programming Language :: Python :: 3.7", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | ] 30 | 31 | [project.urls] 32 | repository = "https://github.com/widgetti/ipyvuetify" 33 | 34 | [project.license] 35 | text = "MIT" 36 | 37 | [project.readme] 38 | file = "README.md" 39 | content-type = "text/markdown" 40 | 41 | [project.optional-dependencies] 42 | dev = ["nox", "pre-commit", "mypy"] 43 | test = ["pytest", "pytest-playwright<0.6", "nbformat<5.10", "jupyterlab<4", "solara[pytest]"] 44 | doc = [ 45 | "sphinx<7", 46 | "jupyter-sphinx", 47 | "ipykernel", 48 | "pydata-sphinx-theme", 49 | "sphinx-design", 50 | "sphinx_rtd_theme", 51 | ] 52 | 53 | [tool.setuptools] 54 | include-package-data = true 55 | license-files = ["LICENSE"] 56 | 57 | [tool.setuptools.packages.find] 58 | include = ["ipyvuetify*"] 59 | exclude = ["docs*", "tests*"] 60 | 61 | [tool.distutils.bdist_wheel] 62 | universal = true 63 | 64 | [tool.ruff] 65 | ignore-init-module-imports = true 66 | fix = true 67 | exclude = [ 68 | '.git', 69 | 'dist', 70 | '.eggs', 71 | '**/node_modules', 72 | '**/generated', 73 | ] 74 | ignore = [ 75 | "E501", # line too long | Black take care of it 76 | #"W503", # line break before binary operator | Black takes care of it 77 | #"E203", # whitespace before ':' | Black take care of it 78 | ] 79 | select = ["E", "W", "F", "Q", "I"] 80 | 81 | [tool.black] 82 | line-length = 100 83 | 84 | [tool.ruff.per-file-ignores] 85 | "__init__.py" = ["F401"] 86 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyter-vuetify", 3 | "version": "1.11.3", 4 | "description": "Jupyter widgets based on vuetify UI components", 5 | "license": "MIT", 6 | "author": "Mario Buikhuizen, Maarten Breddels", 7 | "main": "lib/index.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/widgetti/ipyvuetify.git" 11 | }, 12 | "keywords": [ 13 | "jupyter", 14 | "widgets", 15 | "ipython", 16 | "ipywidgets", 17 | "jupyterlab-extension" 18 | ], 19 | "files": [ 20 | "src/**/*.js", 21 | "lib/**/*.js", 22 | "dist/", 23 | "styles.css" 24 | ], 25 | "browserslist": ">0.8%, not ie 11, not op_mini all, not dead", 26 | "scripts": { 27 | "build:babel": "babel src --out-dir lib --copy-files", 28 | "watch:babel": "babel src --watch --out-dir lib --copy-files --verbose", 29 | "build:labextension": "jupyter labextension build .", 30 | "watch:labextension": "jupyter labextension watch .", 31 | "build:webpack": "webpack", 32 | "watch:webpack": "webpack --mode development --watch", 33 | "watch": "run-p watch:*", 34 | "clean": "rimraf lib/ dist/", 35 | "build": "run-s build:*", 36 | "test": "echo \"Error: no test specified\" && exit 1" 37 | }, 38 | "devDependencies": { 39 | "@babel/cli": "^7.5.0", 40 | "@babel/core": "^7.4.4", 41 | "@babel/preset-env": "^7.4.4", 42 | "@jupyterlab/builder": "^3 || ^4", 43 | "ajv": "^6.10.0", 44 | "autoprefixer": "^10.4.20", 45 | "css-loader": "^7.1.2", 46 | "eslint": "^5.16.0", 47 | "eslint-config-airbnb-base": "^13.1.0", 48 | "eslint-plugin-import": "^2.17.2", 49 | "eslint-plugin-vue": "^5.2.2", 50 | "npm-run-all": "^4.1.5", 51 | "postcss-loader": "^8.1.1", 52 | "postcss-scopify": "^1.0.0", 53 | "rimraf": "^2.6.3", 54 | "style-loader": "^4.0.0", 55 | "webpack": "^5.97.1", 56 | "webpack-cli": "^6.0.1" 57 | }, 58 | "dependencies": { 59 | "@jupyter-widgets/base": "^1 || ^2 || ^3 || ^4 || ^6", 60 | "@jupyterlab/apputils": "^2 || ^3 || ^4", 61 | "@mariobuikhuizen/vuetify": "2.2.26-rc.1", 62 | "@mdi/font": "^4.9.95", 63 | "core-js": "^3.0.1", 64 | "jupyter-vue": "^1.11.2", 65 | "lodash": "^4.17.11", 66 | "material-design-icons-iconfont": "^5.0.1", 67 | "typeface-roboto": "^1.1.13" 68 | }, 69 | "jupyterlab": { 70 | "extension": "lib/labplugin", 71 | "outputDir": "../prefix/share/jupyter/labextensions/jupyter-vuetify", 72 | "webpackConfig": "webpack.config.lab3.js", 73 | "sharedPackages": { 74 | "@jupyter-widgets/base": { 75 | "bundled": false, 76 | "singleton": true 77 | }, 78 | "@jupyterlab/apputils": { 79 | "bundled": false, 80 | "singleton": true 81 | }, 82 | "jupyter-vue": { 83 | "bundled": false, 84 | "singleton": true 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/extra/FileInput example random access.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import ipywidgets as widgets\n", 10 | "from ipyvuetify.extra import FileInput\n", 11 | "from IPython.display import Image\n", 12 | "import zipfile\n", 13 | "import random" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "# Download http://images.cocodataset.org/zips/val2017.zip and choose it in the FileInput below\n", 23 | "\n", 24 | "file_input = FileInput(show_progress=False,multiple=False)\n", 25 | "file_input" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "myfile = file_input.get_files()[0]\n", 35 | "myfile" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "archive = zipfile.ZipFile(myfile['file_obj'], 'r')\n", 45 | "listing = archive.namelist()\n", 46 | "listing" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "jpgs = [x for x in listing if x.endswith('jpg')]" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "image1 = archive.read(jpgs[random.randint(0, len(jpgs)-1)])\n", 65 | "Image(image1)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "image = widgets.Image(format='jpg',)\n", 75 | "\n", 76 | "def load_rnd_image(*ignored):\n", 77 | " show_progress=True\n", 78 | " image.value = archive.read(jpgs[random.randint(0, len(jpgs)-1)])\n", 79 | " show_progress=False\n", 80 | " \n", 81 | "btn = widgets.Button(description='show random image')\n", 82 | "btn.on_click(load_rnd_image)\n", 83 | "\n", 84 | "widgets.VBox([widgets.Box([image]), btn])" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "print(f'Percentage of data read: {file_input.stats[0]/myfile[\"size\"]}%')" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [] 102 | } 103 | ], 104 | "metadata": { 105 | "kernelspec": { 106 | "display_name": "Python 3", 107 | "language": "python", 108 | "name": "python3" 109 | }, 110 | "language_info": { 111 | "codemirror_mode": { 112 | "name": "ipython", 113 | "version": 3 114 | }, 115 | "file_extension": ".py", 116 | "mimetype": "text/x-python", 117 | "name": "python", 118 | "nbconvert_exporter": "python", 119 | "pygments_lexer": "ipython3", 120 | "version": "3.7.8" 121 | } 122 | }, 123 | "nbformat": 4, 124 | "nbformat_minor": 4 125 | } 126 | -------------------------------------------------------------------------------- /js/src/VuetifyView.js: -------------------------------------------------------------------------------- 1 | import { DOMWidgetView } from "@jupyter-widgets/base"; 2 | import Vue from "vue"; // eslint-disable-line import/no-extraneous-dependencies 3 | import { vueRender, createViewContext } from "jupyter-vue"; 4 | import { getContainer } from "./jupyterEnvironment"; 5 | import vuetify from "./plugins/vuetify"; 6 | 7 | export class VuetifyView extends DOMWidgetView { 8 | remove() { 9 | this.vueApp.$destroy(); 10 | return super.remove(); 11 | } 12 | 13 | createDivs(elem) { 14 | /* Scope vuetify styles for overlays to this element */ 15 | const vuetifyStyles = document.createElement("DIV"); 16 | vuetifyStyles.classList.add("vuetify-styles"); 17 | vuetifyStyles.setAttribute("id", "vuetify-styles"); 18 | elem.insertBefore(vuetifyStyles, elem.children[0]); 19 | 20 | /* Overlays wil be rendered here (e.g. v-menu, v-tooltip and dialog). */ 21 | const overlay = document.createElement("DIV"); 22 | overlay.setAttribute("vuetify-overlay", true); 23 | overlay.classList.add("v-application"); 24 | overlay.classList.add("v-application--is-ltr"); 25 | overlay.classList.add("theme--light"); 26 | 27 | /* Prevent the notebook from capturing keyboard events in overlay components (e.g. Menu and 28 | * Dialog). The captured events were executed as commands, messing up the notebook and 29 | * prevented the events from reaching the designated component within the overlay. */ 30 | 31 | overlay.addEventListener("keydown", (event) => { 32 | event.stopPropagation(); 33 | }); 34 | 35 | vuetifyStyles.appendChild(overlay); 36 | 37 | /* Set the Vuetify data-app attribute. Needed for Slider and closing overlays 38 | * (click-outside mixin) */ 39 | elem.setAttribute("data-app", true); 40 | } 41 | 42 | render() { 43 | super.render(); 44 | this.displayed.then(() => { 45 | if (!document.getElementById("vuetify-styles")) { 46 | this.createDivs(getContainer() || document.body); 47 | } 48 | 49 | const vueEl = document.createElement("div"); 50 | this.el.appendChild(vueEl); 51 | const view = this; 52 | 53 | this.vueApp = new Vue({ 54 | vuetify, 55 | el: vueEl, 56 | provide: { 57 | viewCtx: createViewContext(this), 58 | }, 59 | created() { 60 | const original_$forceUpdate = this.$forceUpdate.bind(this); 61 | this.$forceUpdate = () => { 62 | view.ipyvuetifyApp = undefined; 63 | original_$forceUpdate(); 64 | }; 65 | }, 66 | render(createElement) { 67 | // TODO: Don't use v-app in embedded mode 68 | /* Prevent re-rendering of toplevel component. This happens on button-click in 69 | * v-menu */ 70 | if (!view.ipyvuetifyApp) { 71 | view.ipyvuetifyApp = createElement( 72 | "div", 73 | { class: "vuetify-styles" }, 74 | [ 75 | createElement("v-app", [ 76 | vueRender(createElement, view.model, view), 77 | ]), 78 | ] 79 | ); 80 | } 81 | return view.ipyvuetifyApp; 82 | }, 83 | }); 84 | }); 85 | } 86 | 87 | vueRender(createElement) { 88 | return createElement({ 89 | provide: { 90 | viewCtx: createViewContext(this), 91 | }, 92 | render: () => { 93 | return vueRender(createElement, this.model, this); 94 | }, 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /js/src/Themes.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: off */ 2 | import { WidgetModel } from "@jupyter-widgets/base"; 3 | import Vue from "vue"; 4 | import colors from "@mariobuikhuizen/vuetify/lib/util/colors"; 5 | import vuetify from "./plugins/vuetify"; 6 | 7 | export class ThemeModel extends WidgetModel { 8 | defaults() { 9 | return { 10 | ...super.defaults(), 11 | ...{ 12 | _model_name: "ThemeModel", 13 | _model_module: "jupyter-vuetify", 14 | _view_module_version: "0.1.11", 15 | _model_module_version: "0.1.11", 16 | dark: null, 17 | dark_effective: null, 18 | }, 19 | }; 20 | } 21 | 22 | constructor(...args) { 23 | super(...args); 24 | 25 | if (!vuetify) { 26 | return; 27 | } 28 | 29 | if (ThemeModel.themeManager) { 30 | ThemeModel.themeManager.themeChanged.connect(() => { 31 | if (this.get("dark") === null) { 32 | this.setTheme(); 33 | } 34 | }, this); 35 | } 36 | this.setTheme(); 37 | 38 | this.on("change:dark", () => { 39 | this.setTheme(); 40 | }); 41 | 42 | new Vue({ 43 | vuetify, 44 | watch: { 45 | "$vuetify.theme.dark": (newValue) => { 46 | this.vuetifyThemeChange(); 47 | }, 48 | }, 49 | }); 50 | } 51 | 52 | setTheme() { 53 | if (this.get("dark") !== null) { 54 | vuetify.framework.theme.dark = this.get("dark"); 55 | } else if (document.body.dataset.jpThemeLight) { 56 | vuetify.framework.theme.dark = 57 | document.body.dataset.jpThemeLight === "false"; 58 | } else if (document.body.classList.contains("theme-dark")) { 59 | vuetify.framework.theme.dark = true; 60 | } else if (document.body.classList.contains("theme-light")) { 61 | vuetify.framework.theme.dark = false; 62 | } else if (window.Jupyter) { 63 | // Special case for Jupyter Notebook 64 | vuetify.framework.theme.dark = false; 65 | } else if (document.body.dataset.vscodeThemeKind === "vscode-dark") { 66 | // Special case for VS Code 67 | vuetify.framework.theme.dark = true; 68 | } else if (document.body.dataset.vscodeThemeKind === "vscode-light") { 69 | vuetify.framework.theme.dark = false; 70 | } else if (document.documentElement.matches("[theme=dark]")) { 71 | // Special case for Colab 72 | vuetify.framework.theme.dark = true; 73 | } else if (document.documentElement.matches("[theme=light]")) { 74 | vuetify.framework.theme.dark = false; 75 | } 76 | this.set("dark_effective", vuetify.framework.theme.dark); 77 | this.save_changes(); 78 | } 79 | 80 | vuetifyThemeChange() { 81 | if ( 82 | this.get("dark") !== null && 83 | this.get("dark") !== vuetify.framework.theme.dark 84 | ) { 85 | this.set("dark", vuetify.framework.theme.dark); 86 | } 87 | this.set("dark_effective", vuetify.framework.theme.dark); 88 | this.save_changes(); 89 | } 90 | } 91 | 92 | ThemeModel.serializers = { 93 | ...WidgetModel.serializers, 94 | }; 95 | 96 | export class ThemeColorsModel extends WidgetModel { 97 | defaults() { 98 | return { 99 | ...super.defaults(), 100 | ...{ 101 | _model_name: "ThemeColorsModel", 102 | _model_module: "jupyter-vuetify", 103 | _view_module_version: "0.1.11", 104 | _model_module_version: "0.1.11", 105 | _theme_name: null, 106 | primary: null, 107 | secondary: null, 108 | accent: null, 109 | error: null, 110 | info: null, 111 | success: null, 112 | warning: null, 113 | anchor: null, 114 | }, 115 | }; 116 | } 117 | 118 | constructor(...args) { 119 | super(...args); 120 | 121 | if (!vuetify) { 122 | return; 123 | } 124 | 125 | const themeName = this.get("_theme_name"); 126 | 127 | this.keys() 128 | .filter((prop) => !prop.startsWith("_")) 129 | .forEach((prop) => { 130 | vuetify.framework.theme.themes[themeName][prop] = convertColor( 131 | this.get(prop) 132 | ); 133 | this.on(`change:${prop}`, () => { 134 | vuetify.framework.theme.themes[themeName][prop] = convertColor( 135 | this.get(prop) 136 | ); 137 | }); 138 | }); 139 | } 140 | } 141 | 142 | ThemeColorsModel.serializers = { 143 | ...WidgetModel.serializers, 144 | }; 145 | 146 | function convertColor(colorStr) { 147 | if (colorStr == null) { 148 | return null; 149 | } 150 | 151 | if (colorStr.startsWith("colors")) { 152 | const parts = colorStr.split(".").slice(1); 153 | let result = colors; 154 | 155 | parts.forEach((part) => { 156 | result = result[part]; 157 | }); 158 | 159 | return result; 160 | } 161 | 162 | return colorStr; 163 | } 164 | -------------------------------------------------------------------------------- /generate_source/generate_schema.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from itertools import chain 4 | from pathlib import Path 5 | 6 | sizes = ["xs", "sm", "md", "lg", "xl"] 7 | 8 | keywords = ["for", "open", "close"] 9 | 10 | boolean_type = {"type": "boolean", "allowNull": True, "default": None} 11 | 12 | d_type_props = [ 13 | (f"d_{display}", boolean_type) 14 | for display in [ 15 | "inline", 16 | "block", 17 | "contents", 18 | "flex", 19 | "grid", 20 | "inline_block", 21 | "inline_flex", 22 | "inline_grid", 23 | "inline_table", 24 | "list_item", 25 | "run_in", 26 | "table", 27 | "table_caption", 28 | "table_column_group", 29 | "table_header_group", 30 | "table_footer_group", 31 | "table_row_group", 32 | "table_cell", 33 | "table_column", 34 | "table_row", 35 | "none", 36 | "initial", 37 | "inherit", 38 | ] 39 | ] 40 | 41 | grid_list_props = [(f"grid_list_{size}", boolean_type) for size in sizes] 42 | 43 | spacing_props = [ 44 | (f"{type_}{direction}_{size}", boolean_type) 45 | for type_ in ["m", "p"] 46 | for direction in ["t", "b", "l", "r", "x", "y", "a"] 47 | for size in ["auto"] + [str(s) for s in range(0, 6)] 48 | ] 49 | 50 | 51 | def identity(x): 52 | return x 53 | 54 | 55 | def kebab_to_camel(name): 56 | return "".join(map(lambda x: x.capitalize(), name.split("-")[1:])) 57 | 58 | 59 | def property_to_snake_case(name): 60 | return re.sub("(?!^)([A-Z]+)", r"_\1", name).lower() 61 | 62 | 63 | def make_grid_props(prefix, start): 64 | return [ 65 | (f"{prefix}{size}{columns}", boolean_type) for size in sizes for columns in range(start, 13) 66 | ] 67 | 68 | 69 | def make_type(api_type): 70 | if type(api_type) is str: 71 | # Vuetify api schema contains casing errors 72 | api_type = api_type.casefold() 73 | 74 | if api_type == "number": 75 | api_type = "float" 76 | 77 | if api_type == "dataoptions": 78 | api_type = "object" 79 | 80 | if api_type in ["boolean", "string", "object", "any", "float"]: 81 | return {"type": api_type} 82 | 83 | # Type info of arrays is not included in the vuetify api json file, use any for now. 84 | # TODO: Retrieve type info for arrays 85 | if api_type in ["array", "tableheader[]"]: 86 | return {"type": "array", "items": {"type": "any"}} 87 | 88 | if type(api_type) is list: 89 | return { 90 | "type": "union", 91 | "oneOf": list(filter(identity, map(make_type, api_type))), 92 | } 93 | 94 | # Not supported 95 | if api_type == "function": 96 | return None 97 | 98 | print(f"Unknown type: {api_type}") 99 | return None 100 | 101 | 102 | def expand_property(api_name): 103 | if api_name == "d-{type}": 104 | return d_type_props 105 | 106 | if api_name == "grid-list-{xs through xl}": 107 | return grid_list_props 108 | 109 | if api_name == "(size)(1-12)": 110 | return make_grid_props("", 1) 111 | 112 | if api_name == "offset-(size)(0-12)": 113 | return make_grid_props("offset_", 0) 114 | 115 | if api_name == "order-(size)(1-12)": 116 | return make_grid_props("order_", 1) 117 | 118 | print(f"Unknown compressed property: {api_name}") 119 | return None 120 | 121 | 122 | def make_properties(data): 123 | if "type" not in data.keys(): 124 | return None 125 | 126 | api_name = data["name"] 127 | 128 | # compressed properties like: (size)(1-12) and d-{type} 129 | if "(" in api_name or "{" in api_name: 130 | return expand_property(api_name) 131 | 132 | schema_name = property_to_snake_case(api_name) 133 | 134 | if schema_name in keywords: 135 | schema_name += "_" 136 | 137 | schema_type = make_type(data["type"]) 138 | 139 | if schema_type is None: 140 | return None 141 | 142 | schema_type["allowNull"] = True 143 | schema_type["default"] = None 144 | 145 | return [(schema_name, schema_type)] 146 | 147 | 148 | def make_widget(data): 149 | name, attributes = data 150 | widget_name = kebab_to_camel(name) 151 | 152 | # Widgets without props are directives, internationalization or $vuetify 153 | if "props" not in attributes.keys(): 154 | return None 155 | 156 | properties = chain(*filter(identity, map(make_properties, attributes["props"]))) 157 | 158 | if widget_name in ["Container", "Content", "Flex", "Layout"]: 159 | properties = chain(properties, spacing_props) 160 | 161 | return ( 162 | widget_name, 163 | {"inherits": ["VuetifyWidget"], "properties": dict(properties)}, 164 | ) 165 | 166 | 167 | def generate_schema( 168 | vuetify_api_file_name: Path, 169 | base_schema_file_name: Path, 170 | schema_output_file_name: Path, 171 | ): 172 | api_data = json.loads(vuetify_api_file_name.read_text()) 173 | base_schema = json.loads(base_schema_file_name.read_text()) 174 | 175 | widgets = filter(identity, map(make_widget, api_data.items())) 176 | 177 | schema = {"widgets": {**base_schema["widgets"], **dict(widgets)}} 178 | 179 | with schema_output_file_name.open("w") as outfile: 180 | json.dump(schema, outfile) 181 | -------------------------------------------------------------------------------- /js/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var version = require("./package.json").version; 3 | 4 | // Custom webpack rules are generally the same for all webpack bundles, hence 5 | // stored in a separate local variable. 6 | var rules = [ 7 | { test: /\.css$/, use: ["style-loader", "css-loader", "postcss-loader"] }, 8 | { 9 | test: /\.(woff|woff2|eot|ttf|otf)$/, 10 | type: "asset/resource", 11 | }, 12 | ]; 13 | 14 | module.exports = [ 15 | { 16 | // Notebook extension 17 | // 18 | // This bundle only contains the part of the JavaScript that is run on 19 | // load of the notebook. This section generally only performs 20 | // some configuration for requirejs, and provides the legacy 21 | // "load_ipython_extension" function which is required for any notebook 22 | // extension. 23 | // 24 | entry: "./lib/extension.js", 25 | output: { 26 | filename: "extension.js", 27 | path: path.resolve( 28 | __dirname, 29 | "..", 30 | "prefix", 31 | "share", 32 | "jupyter", 33 | "nbextensions", 34 | "jupyter-vuetify" 35 | ), 36 | libraryTarget: "amd", 37 | }, 38 | mode: "production", 39 | }, 40 | { 41 | // Bundle for the notebook containing the custom widget views and models 42 | // 43 | // This bundle contains the implementation for the custom widget views and 44 | // custom widget. 45 | // It must be an amd module 46 | // 47 | entry: "./lib/notebook.js", 48 | output: { 49 | filename: "index.js", 50 | path: path.resolve( 51 | __dirname, 52 | "..", 53 | "prefix", 54 | "share", 55 | "jupyter", 56 | "nbextensions", 57 | "jupyter-vuetify" 58 | ), 59 | libraryTarget: "amd", 60 | }, 61 | devtool: "source-map", 62 | module: { 63 | rules: rules, 64 | }, 65 | externals: ["@jupyter-widgets/base", "@jupyterlab/apputils", "jupyter-vue"], 66 | resolve: { 67 | alias: { 68 | vue$: path.resolve(__dirname, "lib", "vue_alias"), 69 | }, 70 | }, 71 | mode: "production", 72 | performance: { 73 | maxEntrypointSize: 1400000, 74 | maxAssetSize: 1400000, 75 | }, 76 | }, 77 | { 78 | entry: "./lib/nodeps.js", 79 | output: { 80 | filename: "nodeps.js", 81 | path: path.resolve( 82 | __dirname, 83 | "..", 84 | "prefix", 85 | "share", 86 | "jupyter", 87 | "nbextensions", 88 | "jupyter-vuetify" 89 | ), 90 | libraryTarget: "amd", 91 | }, 92 | devtool: "source-map", 93 | module: { 94 | rules: rules, 95 | }, 96 | externals: [ 97 | "@jupyter-widgets/base", 98 | "@jupyterlab/apputils", 99 | "jupyter-vue", 100 | "@mariobuikhuizen/vuetify/dist/vuetify.min.css", 101 | "material-design-icons-iconfont", 102 | "typeface-roboto", 103 | "@mdi/font", 104 | "vuetify", 105 | ], 106 | mode: "production", 107 | resolve: { 108 | alias: { 109 | "./VuetifyView$": path.resolve(__dirname, "src/nodepsVuetifyView.js"), 110 | "./plugins/vuetify$": path.resolve( 111 | __dirname, 112 | "src/plugins/nodepsVuetify.js" 113 | ), 114 | }, 115 | }, 116 | }, 117 | { 118 | entry: "./lib/nodepsEmbed.js", 119 | output: { 120 | filename: "nodeps.js", 121 | path: path.resolve(__dirname, "dist"), 122 | libraryTarget: "amd", 123 | publicPath: "https://unpkg.com/jupyter-vuetify@" + version + "/dist/", 124 | }, 125 | devtool: "source-map", 126 | module: { 127 | rules: rules, 128 | }, 129 | externals: [ 130 | "@jupyter-widgets/base", 131 | "@jupyterlab/apputils", 132 | "jupyter-vue", 133 | "@mariobuikhuizen/vuetify/dist/vuetify.min.css", 134 | "material-design-icons-iconfont", 135 | "typeface-roboto", 136 | "@mdi/font", 137 | "vuetify", 138 | ], 139 | mode: "production", 140 | resolve: { 141 | alias: { 142 | "./VuetifyView$": path.resolve(__dirname, "src/nodepsVuetifyView.js"), 143 | "./plugins/vuetify$": path.resolve( 144 | __dirname, 145 | "src/plugins/nodepsVuetify.js" 146 | ), 147 | }, 148 | }, 149 | }, 150 | { 151 | // Embeddable jupyter-vuetify bundle 152 | // 153 | // This bundle is generally almost identical to the notebook bundle 154 | // containing the custom widget views and models. 155 | // 156 | // The only difference is in the configuration of the webpack public path 157 | // for the static assets. 158 | // 159 | // It will be automatically distributed by unpkg to work with the static 160 | // widget embedder. 161 | // 162 | // The target bundle is always `dist/index.js`, which is the path required 163 | // by the custom widget embedder. 164 | // 165 | entry: "./lib/embed.js", 166 | output: { 167 | filename: "index.js", 168 | path: path.resolve(__dirname, "dist"), 169 | libraryTarget: "amd", 170 | publicPath: "https://unpkg.com/jupyter-vuetify@" + version + "/dist/", 171 | }, 172 | devtool: "source-map", 173 | module: { 174 | rules, 175 | }, 176 | externals: ["@jupyter-widgets/base", "@jupyterlab/apputils", "jupyter-vue"], 177 | resolve: { 178 | alias: { 179 | vue$: path.resolve(__dirname, "lib", "vue_alias"), 180 | }, 181 | }, 182 | mode: "production", 183 | performance: { 184 | maxEntrypointSize: 1400000, 185 | maxAssetSize: 1400000, 186 | }, 187 | }, 188 | ]; 189 | -------------------------------------------------------------------------------- /docs/advanced_usage.rst: -------------------------------------------------------------------------------- 1 | Advanced Usage 2 | ============== 3 | 4 | (Scoped) Slots 5 | -------------- 6 | 7 | Slots are used to add content at a certain location in a widget. You can find out what slots a widget supports by using 8 | the Vuetify documentation. If you want to know what slots :code:`Select` has, search for :code:`v-select` on the 9 | `Vuetify API explorer `_ or for this example use the `direct link 10 | `_. On the left-hand side of list of attributes you will find a tab 11 | 'slots'. 12 | 13 | An example for using the slot 'no-data', which changes what the Select widget shows when it has no items: 14 | 15 | Vuetify: 16 | 17 | .. code-block:: html 18 | 19 | 20 | 27 | 28 | 29 | ipyvuetify: 30 | 31 | .. jupyter-execute:: 32 | :hide-output: 33 | :hide-code: 34 | 35 | import ipyvuetify as v 36 | 37 | .. jupyter-execute:: 38 | 39 | v.Select(v_slots=[{ 40 | 'name': 'no-data', 41 | 'children': [ 42 | v.ListItem(children=[ 43 | v.ListItemTitle(children=['My custom no data message'])])] 44 | }]) 45 | 46 | Scoped slots are used if the parent widget needs to share its scope with the content. In the example below the events 47 | of the parent widget are used in the slot content. 48 | 49 | Vuetify: 50 | 51 | .. code-block:: html 52 | 53 | 54 | 59 | Insert tooltip text here 60 | 61 | 62 | ipyvuetify: 63 | 64 | .. jupyter-execute:: 65 | 66 | v.Container(children=[ 67 | v.Tooltip(bottom=True, v_slots=[{ 68 | 'name': 'activator', 69 | 'variable': 'tooltip', 70 | 'children': v.Btn(v_on='tooltip.on', color='primary', children=[ 71 | 'button with tooltip' 72 | ]), 73 | }], children=['Insert tooltip text here']) 74 | ]) 75 | 76 | In the Vuetify examples you will actually see: 77 | 78 | .. code-block:: html 79 | 80 | ... 81 |