From 91f1e2906b67c8e59128a73d444a7bedd17ddfd4 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 6 Jan 2022 17:29:43 -0500 Subject: [PATCH] Add a "Download Source" entry to dropdown menu. When no source link is provided, display a message informing viewer of their right to the source code under the AGPLv3 license. --- .../scss/vegafusion-embed.scss | 9 ++++ python/vegafusion-jupyter/src/widget.ts | 49 +++++++++++++++---- .../vegafusion_jupyter/__init__.py | 18 +++---- .../vegafusion_jupyter/widget.py | 1 + 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/python/vegafusion-jupyter/scss/vegafusion-embed.scss b/python/vegafusion-jupyter/scss/vegafusion-embed.scss index 32cffeca8..289dc7326 100644 --- a/python/vegafusion-jupyter/scss/vegafusion-embed.scss +++ b/python/vegafusion-jupyter/scss/vegafusion-embed.scss @@ -84,6 +84,15 @@ opacity: 50%; } + .source-msg { + padding: 4px 16px; + font-family: sans-serif; + font-size: 10px; + font-weight: 400; + max-width: 180px; + color: #CD5C5C; + } + a { padding: 4px 16px; font-family: sans-serif; diff --git a/python/vegafusion-jupyter/src/widget.ts b/python/vegafusion-jupyter/src/widget.ts index d1a4bc7a7..8666b04a8 100644 --- a/python/vegafusion-jupyter/src/widget.ts +++ b/python/vegafusion-jupyter/src/widget.ts @@ -60,6 +60,7 @@ export class VegaFusionModel extends DOMWidgetModel { verbose: null, debounce_wait: 30, debounce_max_wait: 60, + download_source_link: null, }; } @@ -80,6 +81,7 @@ export class VegaFusionView extends DOMWidgetView { vegafusion_handle: import("vegafusion-wasm").MsgReceiver; viewElement = document.createElement("div"); containerElement = document.createElement("div"); + menuElement = document.createElement("div"); render_vegafusion: typeof import("vegafusion-wasm").render_vegafusion; vegalite_compile: typeof import("vega-lite").compile; @@ -135,21 +137,45 @@ export class VegaFusionView extends DOMWidgetView { // Add About const aboutLink = document.createElement('a'); + const about_href = ''; aboutLink.text = "About VegaFusion"; - aboutLink.href = ''; + aboutLink.href = about_href; aboutLink.target = '_blank'; - aboutLink.title = ''; + aboutLink.title = about_href; ctrl.append(aboutLink); // Add License const licenseLink = document.createElement('a'); - licenseLink.text = "AGPL License"; - licenseLink.href = 'https://www.gnu.org/licenses/agpl-3.0.en.html'; + const licence_href = 'https://www.gnu.org/licenses/agpl-3.0.en.html'; + licenseLink.text = "AGPLv3 License"; + licenseLink.href = licence_href; licenseLink.target = '_blank'; - licenseLink.title = ''; + licenseLink.title = licence_href; ctrl.append(licenseLink); - return details + // Add source message + const download_source_link: string = this.model.get( + 'download_source_link' + ); + if (download_source_link) { + const sourceItem = document.createElement('a'); + sourceItem.text = 'Download Source'; + sourceItem.href = download_source_link; + sourceItem.target = '_blank'; + sourceItem.title = download_source_link; + ctrl.append(sourceItem); + } else { + const sourceItem = document.createElement('p'); + sourceItem.classList.add('source-msg'); + sourceItem.textContent = + "VegaFusion's AGPLv3 license requires " + + "the author to provide this application's " + + 'source code upon request'; + sourceItem.title = ''; + ctrl.append(sourceItem); + } + + return details; } async render() { @@ -158,13 +184,11 @@ export class VegaFusionView extends DOMWidgetView { const { compile } = await import("vega-lite"); this.vegalite_compile = compile; - - let menu = this.generate_menu(); this.containerElement.appendChild(this.viewElement); this.containerElement.classList.add(CHART_WRAPPER_CLASS); this.el.appendChild(this.containerElement); - this.el.appendChild(menu); + this.el.appendChild(this.menuElement); this.el.classList.add("vegafusion-embed"); this.el.classList.add("has-actions"); @@ -173,6 +197,7 @@ export class VegaFusionView extends DOMWidgetView { this.model.on('change:verbose', this.value_changed, this); this.model.on('change:debounce_wait', this.value_changed, this); this.model.on('change:debounce_max_wait', this.value_changed, this); + this.model.on('change:download_source_link', this.value_changed, this); this.model.on("msg:custom", (ev: any, buffers: [DataView]) => { if (this.model.get("verbose")) { @@ -185,6 +210,12 @@ export class VegaFusionView extends DOMWidgetView { } value_changed() { + // Update menu + while (this.menuElement.lastChild) { + this.menuElement.removeChild(this.menuElement.lastChild); + } + this.menuElement.appendChild(this.generate_menu()); + let spec = this.model.get('spec'); if (spec !== null) { let parsed = JSON.parse(spec); diff --git a/python/vegafusion-jupyter/vegafusion_jupyter/__init__.py b/python/vegafusion-jupyter/vegafusion_jupyter/__init__.py index 9e2ecd11a..a7a03ca0a 100644 --- a/python/vegafusion-jupyter/vegafusion_jupyter/__init__.py +++ b/python/vegafusion-jupyter/vegafusion_jupyter/__init__.py @@ -22,25 +22,23 @@ from .runtime import runtime -def enable(debounce_wait=30, debounce_max_wait=60, data_dir="_vegafusion_data"): +def enable( + download_source_link=None, + debounce_wait=30, + debounce_max_wait=60, + data_dir="_vegafusion_data" +): """ Enable the VegaFusion data transformer and renderer so that all Charts are displayed using VegaFusion. - Equivalent to - - ```python - import altair as alt - alt.renderers.enable('vegafusion') - alt.data_transformers.enable('vegafusion-feather') - ``` - This isn't necessary in order to use the VegaFusionWidget directly """ alt.renderers.enable( 'vegafusion', debounce_wait=debounce_wait, - debounce_max_wait=debounce_max_wait + debounce_max_wait=debounce_max_wait, + download_source_link=download_source_link, ) alt.data_transformers.enable( 'vegafusion-feather', data_dir=data_dir diff --git a/python/vegafusion-jupyter/vegafusion_jupyter/widget.py b/python/vegafusion-jupyter/vegafusion_jupyter/widget.py index b28a3af7d..67e00da88 100644 --- a/python/vegafusion-jupyter/vegafusion_jupyter/widget.py +++ b/python/vegafusion-jupyter/vegafusion_jupyter/widget.py @@ -45,6 +45,7 @@ class VegaFusionWidget(DOMWidget): verbose = Bool(False).tag(sync=True) debounce_wait = Float(30, allow_none=False).tag(sync=True) debounce_max_wait = Float(60, allow_none=True).tag(sync=True) + download_source_link = Unicode(None, allow_none=True).tag(sync=True) def __init__(self, *args, **kwargs):