From c4b2bebe90599b6b39660fcfd2e61877d5fbb790 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 5 Jun 2024 06:56:14 -0400 Subject: [PATCH 01/22] replace voila with solara in cli * currently has some styling issues and does not support hotreloading properly * still needs some updates to testing, docs, etc --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- jdaviz/cli.py | 95 +++++--------------------- jdaviz/core/launcher.py | 25 ++++--- jdaviz/jdaviz_cli.ipynb | 40 ----------- jdaviz/jdaviz_cli_launcher.ipynb | 41 ----------- jdaviz/solara.py | 52 ++++++++++++++ pyproject.toml | 2 +- setup.py | 3 +- 8 files changed, 89 insertions(+), 171 deletions(-) delete mode 100644 jdaviz/jdaviz_cli.ipynb delete mode 100644 jdaviz/jdaviz_cli_launcher.ipynb create mode 100644 jdaviz/solara.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 0a9023cf56..8241faf303 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -93,7 +93,7 @@ body: import ipygoldenlayout; print("ipygoldenlayout", ipygoldenlayout.__version__) import ipypopout; print("ipypopout", ipypopout.__version__) import jinja2; print("Jinja2", jinja2.__version__) - import voila; print("voila", voila.__version__) + import solara; print("solara", solara.__version__) import vispy; print("vispy", vispy.__version__) import sidecar; print("sidecar", sidecar.__version__) import jdaviz; print("Jdaviz", jdaviz.__version__) diff --git a/jdaviz/cli.py b/jdaviz/cli.py index fb7a9a6c2d..cab4eceb6d 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -3,11 +3,6 @@ import inspect import os import pathlib -import sys -import tempfile - -from voila.app import Voila -from voila.configuration import VoilaConfiguration from jdaviz import __version__ from jdaviz.app import _verbosity_levels, ALL_JDAVIZ_CONFIGS @@ -38,7 +33,7 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', browser : str, optional Path to browser executable. theme : {'light', 'dark'} - Theme to use for Voila app or Jupyter Lab. + Theme to use for application. verbosity : {'debug', 'info', 'warning', 'error'} Verbosity of the popup messages in the application. history_verbosity : {'debug', 'info', 'warning', 'error'} @@ -46,13 +41,6 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', hotreload : bool Whether to enable hot-reloading of the UI (for development) """ - import logging # Local import to avoid possibly messing with JWST pipeline logger. - - # Tornado Webserver py3.8 compatibility hotfix for windows - if sys.platform == 'win32': - import asyncio - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - if filepaths: # Convert paths to posix string; windows paths are not JSON compliant file_list = [pathlib.Path(f).absolute().as_posix() for f in filepaths] @@ -63,74 +51,27 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', else: file_list = [] - if layout == '': - if len(file_list) <= 1: - notebook = "jdaviz_cli_launcher.ipynb" - else: - raise ValueError("'layout' argument is required when specifying multiple files") - else: - notebook = "jdaviz_cli.ipynb" - - with open(JDAVIZ_DIR / notebook) as f: - notebook_template = f.read() - - start_dir = os.path.abspath('.') + if layout == '' and len(file_list) > 1: + raise ValueError("'layout' argument is required when specifying multiple files") # Keep track of start directory in environment variable so that it can be # easily accessed e.g. in the file load dialog. - os.environ['JDAVIZ_START_DIR'] = start_dir - - nbdir = tempfile.mkdtemp() - + os.environ['JDAVIZ_START_DIR'] = os.path.abspath('.') + + from solara.__main__ import cli + from jdaviz import solara + solara.config = layout.capitalize() + solara.data_list = file_list + if layout == 'mosviz': + solara.load_data_kwargs = {'instrument': instrument} + solara.jdaviz_verbosity = verbosity + solara.jdaviz_history_verbosity = history_verbosity + args = [] if hotreload: - notebook_template = notebook_template.replace("# PREFIX", "from jdaviz import enable_hot_reloading; enable_hot_reloading()") # noqa: E501 - - with open(os.path.join(nbdir, 'notebook.ipynb'), 'w') as nbf: - nbf.write( - notebook_template - .replace('CONFIG', layout.capitalize()) - .replace('DATA_LIST', str(file_list)) - .replace('JDAVIZ_VERBOSITY', verbosity) - .replace('JDAVIZ_HISTORY_VERBOSITY', history_verbosity) - # Mosviz specific changes - .replace('load_data(data', 'load_data(directory=data' if layout == 'mosviz' else 'load_data(data') # noqa: E501 - .replace(') #ADDITIONAL_LOAD_DATA_ARGS', f', instrument=\'{instrument}\')' if layout == 'mosviz' else ')') # noqa: E501 - .strip() - ) - - os.chdir(nbdir) - - try: - logging.getLogger('tornado.access').disabled = True - Voila.notebook_path = 'notebook.ipynb' - VoilaConfiguration.template = 'jdaviz-default' - VoilaConfiguration.enable_nbextensions = True - VoilaConfiguration.file_whitelist = ['.*'] - VoilaConfiguration.theme = theme - if browser != 'default': - Voila.browser = browser - - voila = Voila.instance() - # monkey patch listen, so we can get a handle on the kernel_manager - # after it is created - previous_listen = voila.listen - - def listen(*args, **kwargs): - # monkey patch remove_kernel, so we can stop the event loop - # when a kernel is removed (which means the browser page was closed) - previous_remove_kernel = voila.kernel_manager.remove_kernel - - def remove_kernel(kernel_id): - previous_remove_kernel(kernel_id) - voila.ioloop.stop() - - voila.kernel_manager.remove_kernel = remove_kernel - return previous_listen(*args, **kwargs) - - voila.listen = listen - sys.exit(voila.launch_instance(argv=[])) - finally: - os.chdir(start_dir) + args += ['--auto-restart'] + else: + args += ['--production'] + cli(['run', 'jdaviz.solara'] + args) def _main(config=None): diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index 23ab7a1ec9..9fcca03170 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -128,9 +128,17 @@ class Launcher(v.VuetifyTemplate): mosviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'mosviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa imviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'imviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa - def __init__(self, main, configs=ALL_JDAVIZ_CONFIGS, filepath='', height=None, *args, **kwargs): + def __init__(self, main=None, configs=ALL_JDAVIZ_CONFIGS, filepath='', + height=None, *args, **kwargs): self.vdocs = 'latest' if 'dev' in __version__ else 'v'+__version__ + if main is None: + main = v.Sheet(class_="mx-25", + attributes={"id": "popout-widget-container"}, + color="#00212C", + height=height, + _metadata={'mount_id': 'content'}) + self.main = main self.configs = configs self.height = f"{height}px" if isinstance(height, int) else height @@ -201,6 +209,11 @@ def vue_launch_config(self, event): self.main.color = 'transparent' self.main.children = [helper.app] + @property + def main_with_launcher(self): + self.main.children = [self] + return self.main + def show_launcher(configs=ALL_JDAVIZ_CONFIGS, filepath='', height='450px'): '''Display an interactive Jdaviz launcher to select your data and compatible configuration @@ -217,11 +230,5 @@ def show_launcher(configs=ALL_JDAVIZ_CONFIGS, filepath='', height='450px'): ''' # Color defined manually due to the custom theme not being defined yet (in main_styles.vue) height = f"{height}px" if isinstance(height, int) else height - main = v.Sheet(class_="mx-25", - attributes={"id": "popout-widget-container"}, - color="#00212C", - height=height, - _metadata={'mount_id': 'content'}) - main.children = [Launcher(main, configs, filepath, height)] - - show_widget(main, loc='inline', title=None) + launcher = Launcher(None, configs, filepath, height) + show_widget(launcher.main_with_launcher, loc='inline', title=None) diff --git a/jdaviz/jdaviz_cli.ipynb b/jdaviz/jdaviz_cli.ipynb deleted file mode 100644 index 05435e519d..0000000000 --- a/jdaviz/jdaviz_cli.ipynb +++ /dev/null @@ -1,40 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# PREFIX\n", - "from jdaviz import CONFIG\n", - "\n", - "viz = CONFIG(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", - "for data in DATA_LIST:\n", - " viz.load_data(data) #ADDITIONAL_LOAD_DATA_ARGS\n", - "viz.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.10" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/jdaviz/jdaviz_cli_launcher.ipynb b/jdaviz/jdaviz_cli_launcher.ipynb deleted file mode 100644 index ac835e73b4..0000000000 --- a/jdaviz/jdaviz_cli_launcher.ipynb +++ /dev/null @@ -1,41 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from jdaviz.core.launcher import show_launcher\n", - "\n", - "show_launcher(height='100%', filepath=(DATA_LIST[0] if len(DATA_LIST) == 1 else ''))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.10" - }, - "vscode": { - "interpreter": { - "hash": "607db476e417971f05b607c2dd14e77ee8262c2c4c20dea422522c60605a222a" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/jdaviz/solara.py b/jdaviz/solara.py new file mode 100644 index 0000000000..30e7a5ddb4 --- /dev/null +++ b/jdaviz/solara.py @@ -0,0 +1,52 @@ +import os +import solara +import ipygoldenlayout +import ipysplitpanes +import ipyvue + +import jdaviz +from jdaviz.app import custom_components + +config = None +data_list = [] +load_data_kwargs = {} +jdaviz_verbosity = 'error' +jdaviz_history_verbosity = 'info' + + +@solara.component +def Page(): + if config is None: + solara.Text("No config defined") + return + + ipysplitpanes.SplitPanes() + ipygoldenlayout.GoldenLayout() + for name, path in custom_components.items(): + ipyvue.register_component_from_file(None, name, + os.path.join(os.path.dirname(jdaviz.__file__), path)) + + ipyvue.register_component_from_file('g-viewer-tab', "container.vue", jdaviz.__file__) + + if not len(data_list): + from jdaviz.core.launcher import Launcher + launcher = Launcher(height='100%', filepath=(data_list[0] if len(data_list) == 1 else '')) + solara.display(launcher.main_with_launcher) + return + + viz = getattr(jdaviz.configs, config)(verbosity=jdaviz_verbosity, + history_verbosity=jdaviz_history_verbosity) + for data in data_list: + if config == 'mosviz': + viz.load_data(directory=data, **load_data_kwargs) + else: + viz.load_data(data, **load_data_kwargs) + + viz.app.template.template = viz.app.template.template.replace( + 'calc(100% - 48px);', '800px' # '80vh !important;' + ) + + height = '800px' + viz.app.layout.height = height + viz.app.state.settings['context']['notebook']['max_height'] = height + solara.display(viz.app) diff --git a/pyproject.toml b/pyproject.toml index 3897a25e7a..4a0a6e4913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "ipysplitpanes>=0.1.0", "ipygoldenlayout>=0.3.0", "ipywidgets>=8.0.6", - "voila>=0.4,<0.5", + "solara>=1.32", "pyyaml>=5.4.1", "specutils>=1.15", "specreduce>=1.4.1", diff --git a/setup.py b/setup.py index e9b5e18ebf..221e340909 100755 --- a/setup.py +++ b/setup.py @@ -116,8 +116,7 @@ def user_dir(): class DevelopCmd(develop): prefix_targets = [ - (os.path.join("nbconvert", "templates"), 'jdaviz-default'), - (os.path.join("voila", "templates"), 'jdaviz-default') + (os.path.join("nbconvert", "templates"), 'jdaviz-default') ] def run(self): From 5930cf6423d708bbc26a4aafd1a08a63fa6fc782 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 5 Jun 2024 09:32:03 -0400 Subject: [PATCH 02/22] remove unneeded voila stuff --- conftest.py | 2 +- jdaviz/conftest.py | 2 +- jdaviz/core/launcher.py | 3 +- .../templates/jdaviz-default/index.html.j2 | 52 ------------------- 4 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 share/jupyter/voila/templates/jdaviz-default/index.html.j2 diff --git a/conftest.py b/conftest.py index c10b503d73..2773682a9e 100644 --- a/conftest.py +++ b/conftest.py @@ -29,7 +29,7 @@ def pytest_configure(config): PYTEST_HEADER_MODULES['ipysplitpanes'] = 'ipysplitpanes' PYTEST_HEADER_MODULES['ipygoldenlayout'] = 'ipygoldenlayout' PYTEST_HEADER_MODULES['ipypopout'] = 'ipypopout' - PYTEST_HEADER_MODULES['voila'] = 'voila' + PYTEST_HEADER_MODULES['solara'] = 'solara' PYTEST_HEADER_MODULES['vispy'] = 'vispy' PYTEST_HEADER_MODULES['gwcs'] = 'gwcs' PYTEST_HEADER_MODULES['asdf'] = 'asdf' diff --git a/jdaviz/conftest.py b/jdaviz/conftest.py index 8a62132da2..fdd4317041 100644 --- a/jdaviz/conftest.py +++ b/jdaviz/conftest.py @@ -402,7 +402,7 @@ def pytest_configure(config): PYTEST_HEADER_MODULES['ipysplitpanes'] = 'ipysplitpanes' PYTEST_HEADER_MODULES['ipygoldenlayout'] = 'ipygoldenlayout' PYTEST_HEADER_MODULES['ipypopout'] = 'ipypopout' - PYTEST_HEADER_MODULES['voila'] = 'voila' + PYTEST_HEADER_MODULES['solara'] = 'solara' PYTEST_HEADER_MODULES['vispy'] = 'vispy' PYTEST_HEADER_MODULES['gwcs'] = 'gwcs' PYTEST_HEADER_MODULES['asdf'] = 'asdf' diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index 9fcca03170..e094d40fa3 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -136,8 +136,7 @@ def __init__(self, main=None, configs=ALL_JDAVIZ_CONFIGS, filepath='', main = v.Sheet(class_="mx-25", attributes={"id": "popout-widget-container"}, color="#00212C", - height=height, - _metadata={'mount_id': 'content'}) + height=height) self.main = main self.configs = configs diff --git a/share/jupyter/voila/templates/jdaviz-default/index.html.j2 b/share/jupyter/voila/templates/jdaviz-default/index.html.j2 deleted file mode 100644 index 9dd28b78da..0000000000 --- a/share/jupyter/voila/templates/jdaviz-default/index.html.j2 +++ /dev/null @@ -1,52 +0,0 @@ -{%- extends 'nbconvert/templates/jdaviz-default/index.html.j2' -%} -{% block notebook_execute %} - -{%- set kernel_id = kernel_start(nb) -%} - -{% endblock notebook_execute %} - -{% block cell_generator %} - - {% for cell in cell_generator(nb, kernel_id) %} - - - {% if cell.cell_type == 'code' %} - {% for output in cell.outputs %} - {% if output.output_type == 'error' %} - - {% endif %} - {% endfor %} - {% endif %} - {% endfor %} -{% endblock cell_generator %} From d1b2de0af9a53bfe1a3d07f2cabfe0e95c6abb40 Mon Sep 17 00:00:00 2001 From: Mario Buikhuizen Date: Wed, 3 Jul 2024 14:26:29 +0200 Subject: [PATCH 03/22] fix: height and spacing (#9) * fix: height and spacing * fix: height of launcher --- jdaviz/core/launcher.py | 2 +- jdaviz/solara.css | 10 ++++++++++ jdaviz/solara.py | 13 +++++-------- pyproject.toml | 1 + 4 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 jdaviz/solara.css diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index e094d40fa3..c59f079488 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -200,7 +200,7 @@ def vue_launch_config(self, event): config = event.get('config') helper = _launch_config_with_data(config, self.loaded_data, filepath=self.filepath, show=False) - if self.height != '100%': + if self.height not in ['100%', '100vh']: # We're in jupyter mode. Set to default height default_height = helper.app.state.settings['context']['notebook']['max_height'] helper.app.layout.height = default_height diff --git a/jdaviz/solara.css b/jdaviz/solara.css new file mode 100644 index 0000000000..4bb8709513 --- /dev/null +++ b/jdaviz/solara.css @@ -0,0 +1,10 @@ +.solara-content-main { + padding-top: 0 !important; +} +.widget-output { + margin: 0; +} +.v-content.jdaviz__content--not-in-notebook { + height: 100vh; + max-height: 100vh; +} diff --git a/jdaviz/solara.py b/jdaviz/solara.py index 30e7a5ddb4..7234459b2e 100644 --- a/jdaviz/solara.py +++ b/jdaviz/solara.py @@ -1,4 +1,6 @@ import os +from pathlib import Path + import solara import ipygoldenlayout import ipysplitpanes @@ -28,9 +30,11 @@ def Page(): ipyvue.register_component_from_file('g-viewer-tab', "container.vue", jdaviz.__file__) + solara.Style(Path(__file__).parent / "solara.css") + if not len(data_list): from jdaviz.core.launcher import Launcher - launcher = Launcher(height='100%', filepath=(data_list[0] if len(data_list) == 1 else '')) + launcher = Launcher(height='100vh', filepath=(data_list[0] if len(data_list) == 1 else '')) solara.display(launcher.main_with_launcher) return @@ -42,11 +46,4 @@ def Page(): else: viz.load_data(data, **load_data_kwargs) - viz.app.template.template = viz.app.template.template.replace( - 'calc(100% - 48px);', '800px' # '80vh !important;' - ) - - height = '800px' - viz.app.layout.height = height - viz.app.state.settings['context']['notebook']['max_height'] = height solara.display(viz.app) diff --git a/pyproject.toml b/pyproject.toml index 4a0a6e4913..9d7ac695b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,7 @@ jdaviz = [ "data/*", "data/*/*", "*.vue", + "*.css", "components/*.vue", "configs/*/*/*/*.vue", "configs/*/*.yaml", From 1b0ce77884adb5bb378fbd7f09be9dc2654e8903 Mon Sep 17 00:00:00 2001 From: Mario Buikhuizen Date: Tue, 23 Jul 2024 19:15:53 +0200 Subject: [PATCH 04/22] fix: make the app full height in popout window (#12) Use the new class that is also available in a Solara popout window. Keep the styling of #popout-widget-container for backward compatibility with ipypopout<1.3.0. --- jdaviz/main_styles.vue | 8 ++++++-- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/jdaviz/main_styles.vue b/jdaviz/main_styles.vue index 21e0253186..95ca6fb19d 100644 --- a/jdaviz/main_styles.vue +++ b/jdaviz/main_styles.vue @@ -239,12 +239,16 @@ a:active { max-height: calc(100% - 48px); } -#popout-widget-container .v-application.jdaviz { +/* #popout-widget-container line can be removed once users use ipypopout >= 1.3.0 */ +#popout-widget-container .v-application.jdaviz, +.jupyter-widgets-popout-container .v-application.jdaviz { min-height: 100vh; max-height: 100vh; } -#popout-widget-container .jdaviz__content--not-in-notebook { +/* #popout-widget-container line can be removed once users use ipypopout >= 1.3.0 */ +#popout-widget-container .jdaviz__content--not-in-notebook, +.jupyter-widgets-popout-container .jdaviz__content--not-in-notebook { max-height: 100%; } diff --git a/pyproject.toml b/pyproject.toml index 9d7ac695b6..d0c76229c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "ipysplitpanes>=0.1.0", "ipygoldenlayout>=0.3.0", "ipywidgets>=8.0.6", - "solara>=1.32", + "solara>=1.36", "pyyaml>=5.4.1", "specutils>=1.15", "specreduce>=1.4.1", From 73a7b9d93c36e116dfd36f3b840de6a522aae391 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Tue, 30 Jul 2024 16:32:33 +0200 Subject: [PATCH 05/22] feat: use solara for pyinstaller (#11) * feat: use solara for pyinstaller * rich.logging hidden import * make test run * close when tab is closed --- .github/workflows/standalone.yml | 4 ++-- jdaviz/solara.py | 15 +++++++++++++++ standalone/hooks/hook-ipyreact.py | 5 +++++ standalone/hooks/hook-ipyvuetify.py | 5 +++++ standalone/hooks/hook-solara.py | 5 +++++ standalone/hooks/hook-solara_server.py | 5 +++++ standalone/hooks/hook-solara_ui.py | 5 +++++ standalone/jdaviz-cli-entrypoint.py | 21 +++------------------ standalone/jdaviz.spec | 2 +- standalone/test_standalone.py | 4 +--- 10 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 standalone/hooks/hook-ipyreact.py create mode 100644 standalone/hooks/hook-ipyvuetify.py create mode 100644 standalone/hooks/hook-solara.py create mode 100644 standalone/hooks/hook-solara_server.py create mode 100644 standalone/hooks/hook-solara_ui.py diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index e081f486a8..bf573e3a5c 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -18,7 +18,7 @@ defaults: jobs: build_binary_not_osx: runs-on: ${{ matrix.os }}-latest - if: (github.repository == 'spacetelescope/jdaviz' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build standalone'))) + # if: (github.repository == 'spacetelescope/jdaviz' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build standalone'))) strategy: matrix: os: [ubuntu, windows] @@ -54,7 +54,7 @@ jobs: - name: Wait for Voila to get online uses: ifaxity/wait-on-action@a7d13170ec542bdca4ef8ac4b15e9c6aa00a6866 # v1.2.1 with: - resource: tcp:8866 + resource: tcp:8765 timeout: 60000 - name: Test standalone diff --git a/jdaviz/solara.py b/jdaviz/solara.py index 7234459b2e..906fd0b861 100644 --- a/jdaviz/solara.py +++ b/jdaviz/solara.py @@ -1,7 +1,9 @@ import os from pathlib import Path +import signal import solara +import solara.lab import ipygoldenlayout import ipysplitpanes import ipyvue @@ -16,6 +18,19 @@ jdaviz_history_verbosity = 'info' +@solara.lab.on_kernel_start +def on_kernel_start(): + # at import time, solara runs with a dummy kernel + # we simply ignore that + if "dummy" in solara.get_kernel_id(): + return + def on_kernel_close(): + # for some reason, sys.exit(0) does not work here + # see https://github.com/encode/uvicorn/discussions/1103 + signal.raise_signal(signal.SIGINT) + return on_kernel_close + + @solara.component def Page(): if config is None: diff --git a/standalone/hooks/hook-ipyreact.py b/standalone/hooks/hook-ipyreact.py new file mode 100644 index 0000000000..8a2acbaaeb --- /dev/null +++ b/standalone/hooks/hook-ipyreact.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata, collect_submodules + +hiddenimports = collect_submodules("ipyreact") +datas = collect_data_files("ipyreact") # codespell:ignore datas +datas += copy_metadata("ipyreact") # codespell:ignore datas diff --git a/standalone/hooks/hook-ipyvuetify.py b/standalone/hooks/hook-ipyvuetify.py new file mode 100644 index 0000000000..d3bee8c400 --- /dev/null +++ b/standalone/hooks/hook-ipyvuetify.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata, collect_submodules + +hiddenimports = collect_submodules("ipyvuetify") +datas = collect_data_files('ipyvuetify') +datas += copy_metadata('ipyvuetify') diff --git a/standalone/hooks/hook-solara.py b/standalone/hooks/hook-solara.py new file mode 100644 index 0000000000..0a51665040 --- /dev/null +++ b/standalone/hooks/hook-solara.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata, collect_submodules + +hiddenimports = collect_submodules("solara") +datas = collect_data_files('solara') +datas += collect_data_files('solara-ui') diff --git a/standalone/hooks/hook-solara_server.py b/standalone/hooks/hook-solara_server.py new file mode 100644 index 0000000000..227108ff6a --- /dev/null +++ b/standalone/hooks/hook-solara_server.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata, collect_submodules + +hiddenimports = collect_submodules("solara-server") +datas = collect_data_files('solara-server') +datas += copy_metadata('solara-server') diff --git a/standalone/hooks/hook-solara_ui.py b/standalone/hooks/hook-solara_ui.py new file mode 100644 index 0000000000..3747e9a718 --- /dev/null +++ b/standalone/hooks/hook-solara_ui.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata, collect_submodules + +hiddenimports = collect_submodules("solara-ui") +datas = collect_data_files('solara-ui') +datas += copy_metadata('solara-ui') diff --git a/standalone/jdaviz-cli-entrypoint.py b/standalone/jdaviz-cli-entrypoint.py index 25649db2d7..5fbcbf7f59 100644 --- a/standalone/jdaviz-cli-entrypoint.py +++ b/standalone/jdaviz-cli-entrypoint.py @@ -7,24 +7,9 @@ import matplotlib_inline import matplotlib_inline.backend_inline -def start_as_kernel(): - # similar to https://github.com/astrofrog/voila-qt-app/blob/master/voila_demo.py - import sys - - from ipykernel import kernelapp as app - app.launch_new_instance() - sys.argv = [app.__file__, sys.argv[3:]] +import jdaviz.cli if __name__ == "__main__": - # When voila starts a kernel under pyinstaller, it will use sys.executable - # (which is this entry point again) - # if called like [sys.argv[0], "-m", "ipykernel_launcher", ...] - if len(sys.argv) >= 3 and sys.argv[1] == "-m" and sys.argv[2] == "ipykernel_launcher": - # it is important that we do not import jdaviz top level - # as that would cause it to import ipywidgets before the kernel is started - start_as_kernel() - else: - import jdaviz.cli - # should change this to _main, but now it doesn't need arguments - jdaviz.cli.main(layout="") + # should change this to _main, but now it doesn't need arguments + jdaviz.cli.main(layout="") diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index 31e9f2f11b..11ddf9a610 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -26,7 +26,7 @@ a = Analysis( pathex=[], binaries=[], datas=datas, - hiddenimports=[], + hiddenimports=["rich.logging"], hookspath=["hooks"], hooksconfig={}, runtime_hooks=[], diff --git a/standalone/test_standalone.py b/standalone/test_standalone.py index 2864d6362e..916d9b328c 100644 --- a/standalone/test_standalone.py +++ b/standalone/test_standalone.py @@ -4,9 +4,7 @@ def test_voila_basics(page: Page): - page.goto("http://localhost:8866/") + page.goto("http://localhost:8765/") - # basic voila is loaded - page.locator("body.theme-light").wait_for() # when jdaviz is loaded (button at the top left) page.locator("text=Welcome to Jdaviz").wait_for() From b29b87213be6838c04a1034d403462ff62eddc8e Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Tue, 30 Jul 2024 10:33:37 -0400 Subject: [PATCH 06/22] re-enable standalone build check --- .github/workflows/standalone.yml | 2 +- jdaviz/solara.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index bf573e3a5c..3f05297a96 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -18,7 +18,7 @@ defaults: jobs: build_binary_not_osx: runs-on: ${{ matrix.os }}-latest - # if: (github.repository == 'spacetelescope/jdaviz' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build standalone'))) + if: (github.repository == 'spacetelescope/jdaviz' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build standalone'))) strategy: matrix: os: [ubuntu, windows] diff --git a/jdaviz/solara.py b/jdaviz/solara.py index 906fd0b861..0b81a876eb 100644 --- a/jdaviz/solara.py +++ b/jdaviz/solara.py @@ -24,6 +24,7 @@ def on_kernel_start(): # we simply ignore that if "dummy" in solara.get_kernel_id(): return + def on_kernel_close(): # for some reason, sys.exit(0) does not work here # see https://github.com/encode/uvicorn/discussions/1103 From 5b160f6a0ba7f0c228dd70d4309db7692b794e23 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 8 Aug 2024 10:13:54 -0400 Subject: [PATCH 07/22] remove no longer needed share/jupyter/nbconvert directories --- .../templates/jdaviz-default/ansi.js | 270 ------------------ .../templates/jdaviz-default/app.html | 92 ------ .../templates/jdaviz-default/conf.json | 1 - .../templates/jdaviz-default/index.html.j2 | 81 ------ .../templates/jdaviz-default/util.js | 188 ------------ 5 files changed, 632 deletions(-) delete mode 100644 share/jupyter/nbconvert/templates/jdaviz-default/ansi.js delete mode 100755 share/jupyter/nbconvert/templates/jdaviz-default/app.html delete mode 100644 share/jupyter/nbconvert/templates/jdaviz-default/conf.json delete mode 100755 share/jupyter/nbconvert/templates/jdaviz-default/index.html.j2 delete mode 100755 share/jupyter/nbconvert/templates/jdaviz-default/util.js diff --git a/share/jupyter/nbconvert/templates/jdaviz-default/ansi.js b/share/jupyter/nbconvert/templates/jdaviz-default/ansi.js deleted file mode 100644 index 18c5782efe..0000000000 --- a/share/jupyter/nbconvert/templates/jdaviz-default/ansi.js +++ /dev/null @@ -1,270 +0,0 @@ -/* Copied from: https://github.com/jupyterlab/jupyterlab/blob/9284892500c9338852532d79d6c5b467c61d797b/packages/rendermime/src/renderers.ts#L881 */ -const ANSI_COLORS = [ - 'ansi-black', - 'ansi-red', - 'ansi-green', - 'ansi-yellow', - 'ansi-blue', - 'ansi-magenta', - 'ansi-cyan', - 'ansi-white', - 'ansi-black-intense', - 'ansi-red-intense', - 'ansi-green-intense', - 'ansi-yellow-intense', - 'ansi-blue-intense', - 'ansi-magenta-intense', - 'ansi-cyan-intense', - 'ansi-white-intense' -]; - -/** - * Create HTML tags for a string with given foreground, background etc. and - * add them to the `out` array. - */ -function pushColoredChunk( - chunk, - fg, - bg, - bold, - underline, - inverse, - out -) { - if (chunk) { - const classes = []; - const styles = []; - - if (bold && typeof fg === 'number' && 0 <= fg && fg < 8) { - fg += 8; // Bold text uses "intense" colors - } - if (inverse) { - [fg, bg] = [bg, fg]; - } - - if (typeof fg === 'number') { - classes.push(ANSI_COLORS[fg] + '-fg'); - } else if (fg.length) { - styles.push(`color: rgb(${fg})`); - } else if (inverse) { - classes.push('ansi-default-inverse-fg'); - } - - if (typeof bg === 'number') { - classes.push(ANSI_COLORS[bg] + '-bg'); - } else if (bg.length) { - styles.push(`background-color: rgb(${bg})`); - } else if (inverse) { - classes.push('ansi-default-inverse-bg'); - } - - if (bold) { - classes.push('ansi-bold'); - } - - if (underline) { - classes.push('ansi-underline'); - } - - if (classes.length || styles.length) { - out.push(''); - out.push(chunk); - out.push(''); - } else { - out.push(chunk); - } - } -} - -/** - * Convert ANSI extended colors to R/G/B triple. - */ -function getExtendedColors(numbers) { - let r; - let g; - let b; - const n = numbers.shift(); - if (n === 2 && numbers.length >= 3) { - // 24-bit RGB - r = numbers.shift(); - g = numbers.shift(); - b = numbers.shift(); - if ([r, g, b].some(c => c < 0 || 255 < c)) { - throw new RangeError('Invalid range for RGB colors'); - } - } else if (n === 5 && numbers.length >= 1) { - // 256 colors - const idx = numbers.shift(); - if (idx < 0) { - throw new RangeError('Color index must be >= 0'); - } else if (idx < 16) { - // 16 default terminal colors - return idx; - } else if (idx < 232) { - // 6x6x6 color cube, see https://stackoverflow.com/a/27165165/500098 - r = Math.floor((idx - 16) / 36); - r = r > 0 ? 55 + r * 40 : 0; - g = Math.floor(((idx - 16) % 36) / 6); - g = g > 0 ? 55 + g * 40 : 0; - b = (idx - 16) % 6; - b = b > 0 ? 55 + b * 40 : 0; - } else if (idx < 256) { - // grayscale, see https://stackoverflow.com/a/27165165/500098 - r = g = b = (idx - 232) * 10 + 8; - } else { - throw new RangeError('Color index must be < 256'); - } - } else { - throw new RangeError('Invalid extended color specification'); - } - return [r, g, b]; -} - -/** - * Transform ANSI color escape codes into HTML tags with CSS - * classes such as "ansi-green-intense-fg". - * The actual colors used are set in the CSS file. - * This also removes non-color escape sequences. - * This is supposed to have the same behavior as nbconvert.filters.ansi2html() - */ -function ansiSpan(str) { - const ansiRe = /\x1b\[(.*?)([@-~])/g; // eslint-disable-line no-control-regex - let fg = []; - let bg = []; - let bold = false; - let underline = false; - let inverse = false; - let match; - const out = []; - const numbers = []; - let start = 0; - - //str = _.escape(str); - - str += '\x1b[m'; // Ensure markup for trailing text - // tslint:disable-next-line - while ((match = ansiRe.exec(str))) { - if (match[2] === 'm') { - const items = match[1].split(';'); - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (item === '') { - numbers.push(0); - } else if (item.search(/^\d+$/) !== -1) { - numbers.push(parseInt(item, 10)); - } else { - // Ignored: Invalid color specification - numbers.length = 0; - break; - } - } - } else { - // Ignored: Not a color code - } - const chunk = str.substring(start, match.index); - pushColoredChunk(chunk, fg, bg, bold, underline, inverse, out); - start = ansiRe.lastIndex; - - while (numbers.length) { - const n = numbers.shift(); - switch (n) { - case 0: - fg = bg = []; - bold = false; - underline = false; - inverse = false; - break; - case 1: - case 5: - bold = true; - break; - case 4: - underline = true; - break; - case 7: - inverse = true; - break; - case 21: - case 22: - bold = false; - break; - case 24: - underline = false; - break; - case 27: - inverse = false; - break; - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - fg = n - 30; - break; - case 38: - try { - fg = getExtendedColors(numbers); - } catch (e) { - numbers.length = 0; - } - break; - case 39: - fg = []; - break; - case 40: - case 41: - case 42: - case 43: - case 44: - case 45: - case 46: - case 47: - bg = n - 40; - break; - case 48: - try { - bg = getExtendedColors(numbers); - } catch (e) { - numbers.length = 0; - } - break; - case 49: - bg = []; - break; - case 90: - case 91: - case 92: - case 93: - case 94: - case 95: - case 96: - case 97: - fg = n - 90 + 8; - break; - case 100: - case 101: - case 102: - case 103: - case 104: - case 105: - case 106: - case 107: - bg = n - 100 + 8; - break; - default: - // Unknown codes are ignored - } - } - } - return out.join(''); -} diff --git a/share/jupyter/nbconvert/templates/jdaviz-default/app.html b/share/jupyter/nbconvert/templates/jdaviz-default/app.html deleted file mode 100755 index eaa51874b0..0000000000 --- a/share/jupyter/nbconvert/templates/jdaviz-default/app.html +++ /dev/null @@ -1,92 +0,0 @@ -{% raw -%} - - - -{% endraw -%} diff --git a/share/jupyter/nbconvert/templates/jdaviz-default/conf.json b/share/jupyter/nbconvert/templates/jdaviz-default/conf.json deleted file mode 100644 index 2eaa987d0f..0000000000 --- a/share/jupyter/nbconvert/templates/jdaviz-default/conf.json +++ /dev/null @@ -1 +0,0 @@ -{"base_template": "lab"} diff --git a/share/jupyter/nbconvert/templates/jdaviz-default/index.html.j2 b/share/jupyter/nbconvert/templates/jdaviz-default/index.html.j2 deleted file mode 100755 index b90006095e..0000000000 --- a/share/jupyter/nbconvert/templates/jdaviz-default/index.html.j2 +++ /dev/null @@ -1,81 +0,0 @@ - - - - Jdaviz - - - - - - - {% if resources.theme == 'dark' %} - - {% else %} - - {% endif-%} - - {% block stylesheets %} - {% endblock %} - - - - - - {%- block body_header -%} - {% if resources.theme == 'dark' %} - - {% else %} - - {% endif-%} - {%- endblock body_header -%} - - - - {% include "app.html" %} - - {% block notebook_execute %} - {% endblock notebook_execute %} - {% set cell_count = nb.cells|length %} - - {% block cell_generator %} - - {% endblock cell_generator %} - - - diff --git a/share/jupyter/nbconvert/templates/jdaviz-default/util.js b/share/jupyter/nbconvert/templates/jdaviz-default/util.js deleted file mode 100755 index 3927105c8e..0000000000 --- a/share/jupyter/nbconvert/templates/jdaviz-default/util.js +++ /dev/null @@ -1,188 +0,0 @@ -Vue.use(Vuetify); - -Vue.component('jupyter-widget-mount-point', { - data() { - return { - renderFn: undefined, - elem: undefined, - } - }, - props: ['mount-id'], - created() { - requestWidget(this.mountId); - }, - mounted() { - requestWidget(this.mountId) - .then(widgetView => { - if (['VuetifyView', 'VuetifyTemplateView'].includes(widgetView.model.get('_view_name'))) { - this.renderFn = createElement => widgetView.vueRender(createElement); - } else { - while (this.$el.firstChild) { - this.$el.removeChild(this.$el.firstChild); - } - - requirejs(['@jupyter-widgets/base'], widgets => - widgets.JupyterPhosphorWidget.attach(widgetView.pWidget, this.$el) - ); - } - } - ); - }, - render(createElement) { - if (this.renderFn) { - /* workaround for v-menu click */ - if (!this.elem) { - this.elem = this.renderFn(createElement); - } - return this.elem; - } - } -}); - -const widgetResolveFns = {}; -const widgetPromises = {}; - -function provideWidget(mountId, widgetView) { - if (widgetResolveFns[mountId]) { - widgetResolveFns[mountId](widgetView); - } else { - widgetPromises[mountId] = Promise.resolve(widgetView); - } -} - -function requestWidget(mountId) { - if (!widgetPromises[mountId]) { - widgetPromises[mountId] = new Promise(resolve => widgetResolveFns[mountId] = resolve); - } - return widgetPromises[mountId]; -} - -function getWidgetManager(voila, kernel) { - try { - /* voila < 0.1.8 */ - return new voila.WidgetManager(kernel); - } catch (e) { - if (e instanceof TypeError) { - /* voila >= 0.1.8 */ - const context = { - session: { - kernel, - kernelChanged: { - connect: () => {} - }, - statusChanged: { - connect: () => {} - }, - }, - saveState: { - connect: () => {} - }, - /* voila >= 0.2.8 */ - sessionContext: { - session: { - kernel - }, - kernelChanged: { - connect: () => { - } - }, - statusChanged: { - connect: () => { - } - }, - connectionStatusChanged: { - connect: () => { - } - }, - }, - }; - - const settings = { - saveState: false - }; - - const rendermime = new voila.RenderMimeRegistry({ - initialFactories: voila.standardRendererFactories - }); - - return new voila.WidgetManager(context, rendermime, settings); - } else { - throw e; - } - } -} - -function injectDebugMessageInterceptor(kernel) { - const _original_handle_message = kernel._handleMessage.bind(kernel) - kernel._handleMessage = ((msg) => { - if (msg.msg_type === 'error') { - app.$data.voilaDebugMessages.push({ - cell: '_', - traceback: msg.content.traceback.map(line => ansiSpan(_.escape(line))) - }); - } else if(msg.msg_type === 'stream' && (msg.content['name'] === 'stdout' || msg.content['name'] === 'stderr')) { - app.$data.voilaDebugMessages.push({ - cell: '_', - name: msg.content.name, - text: msg.content.text - }); - } - return _original_handle_message(msg); - }) -} - -window.init = async (voila) => { - define("vue", [], () => Vue); - - const kernel = await voila.connectKernel(); - injectDebugMessageInterceptor(kernel); - window.addEventListener('beforeunload', () => kernel.shutdown()); - - const widgetManager = getWidgetManager(voila, kernel); - - if (!window.enable_nbextensions) { - const originalLoader = widgetManager.loader; - widgetManager.loader = (moduleName, moduleVersion) => { - console.log(moduleName); - if (moduleName === 'jupyter-vuetify' || moduleName === 'jupyter-vue') { - requirejs.config({ - paths: { - [moduleName]: [`${moduleName}/nodeps`, `https://unpkg.com/${moduleName}@${moduleVersion}/dist/nodeps`] - } - }); - } - return originalLoader(moduleName, moduleVersion); - }; - } - - await widgetManager.build_widgets(); - - Object.values(widgetManager._models) - .forEach(async (modelPromise) => { - const model = await modelPromise; - const meta = model.get('_metadata'); - const mountId = meta && meta.mount_id; - if (mountId && model.get('_view_name')) { - const view = await widgetManager.create_view(model); - provideWidget(mountId, view); - } - }); - - const urlParams = new URLSearchParams(window.location.search); - app.$data.debug = urlParams.has('debug') - if (window['voilaDebugMessages']) { - app.$data.voilaDebugMessages = window['voilaDebugMessages']; - } - app.$data.loading = false; - removeInterferingStyleTags(); -}; - -function removeInterferingStyleTags() { - document.querySelectorAll("style:not(#vuetify-theme-stylesheet)") - .forEach((styleTag) => { - if (styleTag.textContent.includes("/* Override Blueprint's _reset.scss styles */")) { - document.head.removeChild(styleTag); - } - }); -} - From 784f81f51ad3ff83ff3e44884605b00e5b0ea685 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 09:15:32 -0400 Subject: [PATCH 08/22] hide solara tagline --- jdaviz/solara.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jdaviz/solara.css b/jdaviz/solara.css index 4bb8709513..0b25805a09 100644 --- a/jdaviz/solara.css +++ b/jdaviz/solara.css @@ -8,3 +8,6 @@ height: 100vh; max-height: 100vh; } +.v-application--wrap > div:nth-child(2) > div:nth-child(2){ + display: none !important; +} From fa13344107238fbb6bb56663867cc033906e4529 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 12:04:32 -0400 Subject: [PATCH 09/22] set page title to "Jdaviz" could eventually dynamically change this as configs are selected, data loaded, etc. --- jdaviz/solara.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jdaviz/solara.py b/jdaviz/solara.py index 0b81a876eb..44bf992eee 100644 --- a/jdaviz/solara.py +++ b/jdaviz/solara.py @@ -34,6 +34,8 @@ def on_kernel_close(): @solara.component def Page(): + solara.Title("Jdaviz") + if config is None: solara.Text("No config defined") return From fc3c051b012b54ce23be2e2314384087ee00d78f Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 12:16:48 -0400 Subject: [PATCH 10/22] override favicon (currently with cubeviz icon) --- assets/favicon.svg | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 assets/favicon.svg diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000000..72e2e2c0f5 --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 82ddb49f8eb8bc51eb06ad95bd2c3de1d69d5982 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 12:17:04 -0400 Subject: [PATCH 11/22] cli: remove unneeded logic for python<3.10 --- jdaviz/cli.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/jdaviz/cli.py b/jdaviz/cli.py index cab4eceb6d..a8f20bf7db 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -98,13 +98,9 @@ def _main(config=None): help='Verbosity of the application for popup snackbars.') parser.add_argument('--history-verbosity', choices=_verbosity_levels, default='info', help='Verbosity of the logger history.') - if sys.version_info >= (3, 9): - # Also enables --no-hotreload - parser.add_argument('--hotreload', action=argparse.BooleanOptionalAction, default=False, - help='Whether to enable hot-reloading of the UI (for development).') - else: - parser.add_argument('--hotreload', action='store_true', default=False, - help='Enable hot-reloading of the UI (for development).') + # Also enables --no-hotreload + parser.add_argument('--hotreload', action=argparse.BooleanOptionalAction, default=False, + help='Whether to enable hot-reloading of the UI (for development).') parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}') args = parser.parse_args() From 90429377578d5a510a8fd10bedbeb61ca7d79c1a Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 12:20:22 -0400 Subject: [PATCH 12/22] changelog entry --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cfd40b3949..4cf88b0a5e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,8 @@ New Features - Plugins can now expose in-UI API hints. [#3137] +- The standalone version of jdaviz now uses solara instead of voila, resulting in faster load times. [#2909] + Cubeviz ^^^^^^^ From edf628c00066c5e068989888fe22077894e03c7f Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 14:04:32 -0400 Subject: [PATCH 13/22] fix handling passing layout and/or filepaths --- jdaviz/solara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/solara.py b/jdaviz/solara.py index 44bf992eee..713e6d9494 100644 --- a/jdaviz/solara.py +++ b/jdaviz/solara.py @@ -50,7 +50,7 @@ def Page(): solara.Style(Path(__file__).parent / "solara.css") - if not len(data_list): + if config is None or not hasattr(jdaviz.configs, config): from jdaviz.core.launcher import Launcher launcher = Launcher(height='100vh', filepath=(data_list[0] if len(data_list) == 1 else '')) solara.display(launcher.main_with_launcher) From 8b327db76a7d783f9d584e0bdc2c2da92f159a31 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Aug 2024 14:08:17 -0400 Subject: [PATCH 14/22] update/remove other mentions of voila across codebase --- .github/workflows/standalone.yml | 4 ++-- docs/dev/infrastructure.rst | 2 +- docs/dev/win_dev.rst | 13 +------------ docs/installation.rst | 1 - jdaviz/main_styles.vue | 2 +- standalone/test_standalone.py | 2 +- tox.ini | 2 -- 7 files changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 3f05297a96..f2cc442255 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -51,7 +51,7 @@ jobs: - name: Install pytest run: pip install pytest-playwright - - name: Wait for Voila to get online + - name: Wait for Solara to get online uses: ifaxity/wait-on-action@a7d13170ec542bdca4ef8ac4b15e9c6aa00a6866 # v1.2.1 with: resource: tcp:8765 @@ -174,7 +174,7 @@ jobs: - name: Install pytest run: pip install pytest-playwright - - name: Wait for Voila to get online + - name: Wait for Solara to get online uses: ifaxity/wait-on-action@a7d13170ec542bdca4ef8ac4b15e9c6aa00a6866 # v1.2.1 with: resource: tcp:8866 diff --git a/docs/dev/infrastructure.rst b/docs/dev/infrastructure.rst index 58032ef4f4..d8381845d2 100644 --- a/docs/dev/infrastructure.rst +++ b/docs/dev/infrastructure.rst @@ -49,7 +49,7 @@ The target interfaces are: * **Desktop**: This interface is meant to behave like a more traditional "desktop app", i.e., a window with a fixed set of functionality and a particular layout for a specific set of scientific use cases. This interface is accessed via a - `VoilĂ  `_ wrapper that loads the same machinery as the + `Solara `_ wrapper that loads the same machinery as the other interfaces but presents the outputs of notebook "cells" as the only view. This trades the flexibility of the notebook interface for a consistent and reproducible layout and simpler interface without the distraction of the notebook diff --git a/docs/dev/win_dev.rst b/docs/dev/win_dev.rst index 27e095c522..b347443e25 100644 --- a/docs/dev/win_dev.rst +++ b/docs/dev/win_dev.rst @@ -9,15 +9,4 @@ creates a copy of the data files instead of using symbolic links. As a result, if you are changing the contents in ``share`` folder under the source checkout's root directory, you will need to rebuild the package even in editable install mode. Otherwise, this should not -affect your development experience. - -WSL2 and voila --------------- - -``voila`` is unable to display when WSL2 cannot start up the -Windows-side browser executable. Unfortunately, unlike Jupyter -notebook, ``voila`` does not have a ``--no-browser`` option -with a tokenized URL you can copy-and-paste manually on the -Windows side (see https://github.com/voila-dashboards/voila/issues/773). -Therefore, you might need to install Jdaviz natively on Windows -to test its standalone application functionality. +affect your development experience. \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst index 092dcd9dfa..032ac85033 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -71,7 +71,6 @@ instead of ``pip``: conda install bottleneck conda install -c conda-forge notebook conda install -c conda-forge jupyterlab - conda install -c conda-forge voila You might also want to enable the ``ipywidgets`` notebook extension, as follows: diff --git a/jdaviz/main_styles.vue b/jdaviz/main_styles.vue index 95ca6fb19d..3aa60462d4 100644 --- a/jdaviz/main_styles.vue +++ b/jdaviz/main_styles.vue @@ -37,7 +37,7 @@ export default {