diff --git a/examples/demo_portable.yaml b/examples/demo_portable.yaml index ebe08895..d5425a8e 100644 --- a/examples/demo_portable.yaml +++ b/examples/demo_portable.yaml @@ -11,3 +11,5 @@ pages: some_number: 42 - ExampleAssets: picture_path: ./example_banner.png + css_path: ./example_stylesheet.css + js_path: ./example_javascript.js diff --git a/examples/example_javascript.js b/examples/example_javascript.js new file mode 100644 index 00000000..8b8ed2fa --- /dev/null +++ b/examples/example_javascript.js @@ -0,0 +1 @@ +alert("This loaded JavaScript file comes from a plugin."); diff --git a/examples/example_stylesheet.css b/examples/example_stylesheet.css new file mode 100644 index 00000000..e1b2552f --- /dev/null +++ b/examples/example_stylesheet.css @@ -0,0 +1,3 @@ +body { + background-color: green; +} diff --git a/webviz_config/plugins/_example_assets.py b/webviz_config/plugins/_example_assets.py index e9ae1cf4..824e3959 100644 --- a/webviz_config/plugins/_example_assets.py +++ b/webviz_config/plugins/_example_assets.py @@ -7,11 +7,17 @@ class ExampleAssets(WebvizPluginABC): - def __init__(self, picture_path: Path): + def __init__(self, picture_path: Path, css_path: Path = None, js_path: Path = None): super().__init__() self.asset_url = WEBVIZ_ASSETS.add(picture_path) + if css_path is not None: + WEBVIZ_ASSETS.add(css_path) + + if js_path is not None: + WEBVIZ_ASSETS.add(js_path) + @property def layout(self) -> html.Img: return html.Img(src=self.asset_url) diff --git a/webviz_config/templates/webviz_template.py.jinja2 b/webviz_config/templates/webviz_template.py.jinja2 index f3b1c3a2..20d23a01 100644 --- a/webviz_config/templates/webviz_template.py.jinja2 +++ b/webviz_config/templates/webviz_template.py.jinja2 @@ -63,7 +63,6 @@ WEBVIZ_STORAGE.storage_folder = path.join( ) WEBVIZ_ASSETS.portable = {{ portable }} -{{ "WEBVIZ_ASSETS.register_app(app)" if not portable else ""}} if {{ not portable }} and not webviz_config.is_reload_process(): # When Dash/Flask is started on localhost with hot module reload activated, @@ -106,6 +105,8 @@ else: {% endfor %}], ) +{{ "WEBVIZ_ASSETS.directly_host_assets(app)" if not portable else ""}} + if __name__ == "__main__": # This part is ignored when the webviz app is started # using Docker container and uwsgi (e.g. when hosted on Azure). diff --git a/webviz_config/webviz_assets.py b/webviz_config/webviz_assets.py index 9f305d2f..712ea38d 100644 --- a/webviz_config/webviz_assets.py +++ b/webviz_config/webviz_assets.py @@ -50,6 +50,19 @@ def _base_folder(self) -> str: return "assets" if self.portable else "temp" def add(self, filename: pathlib.Path) -> str: + """Calling this function makes the filename given as input + available as a hosted asset when the application is running. + The returned string is a URI which the plugin optionally + can use internally (e.g. as "src" in image elements). + + Calling this function with the same input path + multiple times will return the same URI. + + Filenames added to WebvizAssets that ends with .css or .js + are loaded automatically in the browser by Dash, + both in non-portable and portable mode. + """ + path = pathlib.Path(filename) if filename not in self._assets.values(): @@ -60,13 +73,19 @@ def add(self, filename: pathlib.Path) -> str: return os.path.normcase(os.path.join(self._base_folder(), assigned_id)) - def register_app(self, app: Dash) -> None: + def directly_host_assets(self, app: Dash) -> None: """In non-portable mode, this function can be called by the application. It routes the Dash application to the added assets on disk, making hot reloading and more interactive development of the application possible. """ + if self._portable: + raise RuntimeError( + "The function WebvizAssets.directly_host_assets() " + "method is only meaningful in a non-portable settings." + ) + @app.server.route(f"/{self._base_folder()}/") def _send_file(asset_id: str) -> Optional[flask.wrappers.Response]: if asset_id in self._assets: # Only serve white listed resources @@ -74,6 +93,17 @@ def _send_file(asset_id: str) -> Optional[flask.wrappers.Response]: return flask.send_from_directory(path.parent, path.name) return None + # Add .css and .js files to auto-loaded Dash assets + for asset_id, asset_path in self._assets.items(): + if asset_path.suffix == ".css": + app.config.external_stylesheets.append( + f"./{self._base_folder()}/{asset_id}" + ) + elif asset_path.suffix == ".js": + app.config.external_scripts.append( + f"./{self._base_folder()}/{asset_id}" + ) + def make_portable(self, asset_folder: str) -> None: """Copy over all added assets to the given folder (asset_folder). """