diff --git a/qe.ipynb b/qe.ipynb index a4153e027..b916c5ea4 100644 --- a/qe.ipynb +++ b/qe.ipynb @@ -107,7 +107,7 @@ "metadata": {}, "outputs": [], "source": [ - "controller.enable_toggles()" + "controller.enable_controls()" ] } ], diff --git a/src/aiidalab_qe/app/wrapper.py b/src/aiidalab_qe/app/wrapper.py index 3c171bd6e..a80bfcf7c 100644 --- a/src/aiidalab_qe/app/wrapper.py +++ b/src/aiidalab_qe/app/wrapper.py @@ -2,7 +2,7 @@ import ipywidgets as ipw import traitlets as tl -from IPython.display import display +from IPython.display import Javascript, display from aiidalab_qe.common.guide_manager import guide_manager from aiidalab_qe.common.widgets import LoadingWidget @@ -48,10 +48,10 @@ def __init__( self._view = view self._set_event_handlers() - def enable_toggles(self) -> None: - """Enable the toggle buttons.""" - self._view.guide_toggle.disabled = False - self._view.about_toggle.disabled = False + def enable_controls(self) -> None: + """Enable the control buttons at the top of the app.""" + for control in self._view.controls.children: + control.disabled = False @without_triggering("about_toggle") def _on_guide_toggle(self, change: dict): @@ -82,29 +82,35 @@ def _on_about_toggle(self, change: dict): self._view.info_container.children = [] self._view.info_container.layout.display = "none" - def _on_guide_category_select(self, change: dict): - self._view.guide_selection.options = guide_manager.get_guides(change["new"]) - self._update_active_guide() + def _on_calculation_history_click(self, _): + self._open_external_notebook("./calculation_history.ipynb") - def _on_guide_select(self, _): - self._update_active_guide() + def _on_guide_category_selection_change(self, change): + self._model.guide_options = guide_manager.get_guides(change["new"]) - def _update_active_guide(self): - """Sets the current active guide.""" + def _on_guide_selection_change(self, _): category = self._view.guide_category_selection.value guide = self._view.guide_selection.value - active_guide = f"{category}/{guide}" if category != "none" else category - guide_manager.active_guide = active_guide + self._model.update_active_guide(category, guide) - def _set_guide_category_options(self, _): - """Fetch the available guides.""" - self._view.guide_category_selection.options = [ - "none", - *guide_manager.get_guide_categories(), - ] + def _open_external_notebook(self, url): + """Open an external notebook in a new tab.""" + display(Javascript(f"window.open('{url}', '_blank')")) def _set_event_handlers(self) -> None: """Set up event handlers.""" + self._model.observe( + self._on_guide_category_selection_change, + "selected_guide_category", + ) + self._model.observe( + self._on_guide_selection_change, + [ + "selected_guide_category", + "selected_guide", + ], + ) + self._view.guide_toggle.observe( self._on_guide_toggle, "value", @@ -113,22 +119,38 @@ def _set_event_handlers(self) -> None: self._on_about_toggle, "value", ) - self._view.guide_category_selection.observe( - self._on_guide_category_select, - "value", + self._view.calculation_history_link.on_click(self._on_calculation_history_click) + + ipw.dlink( + (self._model, "guide_category_options"), + (self._view.guide_category_selection, "options"), ) - self._view.guide_selection.observe( - self._on_guide_select, - "value", + ipw.link( + (self._model, "selected_guide_category"), + (self._view.guide_category_selection, "value"), + ) + ipw.dlink( + (self._model, "guide_options"), + (self._view.guide_selection, "options"), + ) + ipw.link( + (self._model, "selected_guide"), + (self._view.guide_selection, "value"), ) - self._view.on_displayed(self._set_guide_category_options) class AppWrapperModel(tl.HasTraits): """An MVC model for `AppWrapper`.""" - def __init__(self): - """`AppWrapperModel` constructor.""" + guide_category_options = tl.List(["none", *guide_manager.get_guide_categories()]) + selected_guide_category = tl.Unicode("none") + guide_options = tl.List(tl.Unicode()) + selected_guide = tl.Unicode(None, allow_none=True) + + def update_active_guide(self, category, guide): + """Sets the current active guide.""" + active_guide = f"{category}/{guide}" if category != "none" else category + guide_manager.active_guide = active_guide class AppWrapperView(ipw.VBox): @@ -184,24 +206,23 @@ def __init__(self) -> None: disabled=True, ) - self.calculation_history_button = ipw.Button( + self.calculation_history_link = ipw.Button( layout=ipw.Layout(width="auto"), button_style="", icon="list", description="Calculation history", tooltip="View all calculations run with this app", + disabled=True, ) - self.calculation_history_button.on_click(self._open_calculation_history) - - info_toggles = ipw.HBox( + self.controls = ipw.HBox( children=[ self.guide_toggle, self.about_toggle, - self.calculation_history_button, + self.calculation_history_link, ] ) - info_toggles.add_class("info-toggles") + self.controls.add_class("info-toggles") env = Environment() guide_template = files(templates).joinpath("guide.jinja").read_text() @@ -211,9 +232,7 @@ def __init__(self) -> None: self.about = ipw.HTML(env.from_string(about_template).render()) self.guide_category_selection = ipw.RadioButtons( - options=["none"], description="Guides:", - value="none", layout=ipw.Layout(width="max-content"), ) self.guide_selection = ipw.RadioButtons(layout=ipw.Layout(margin="2px 20px")) @@ -224,7 +243,7 @@ def __init__(self) -> None: children=[ logo, subtitle, - info_toggles, + self.controls, self.info_container, ], ) @@ -249,9 +268,3 @@ def __init__(self) -> None: footer, ], ) - - def _open_calculation_history(self, _): - from IPython.display import Javascript - - url = "./calculation_history.ipynb" - display(Javascript(f"window.open('{url}', '_blank')")) diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index e4eed6a1c..8173cfdc8 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py @@ -2,19 +2,19 @@ class TestWrapper: - def test_enable_toggles(self): - """Test enable_toggles method.""" + def test_enable_controls(self): + """Test enable_controls method.""" self._instansiate_mvc_components() assert self.view.guide_toggle.disabled is True assert self.view.about_toggle.disabled is True - self.controller.enable_toggles() + self.controller.enable_controls() assert self.view.guide_toggle.disabled is False assert self.view.about_toggle.disabled is False def test_guide_toggle(self): """Test guide_toggle method.""" self._instansiate_mvc_components() - self.controller.enable_toggles() + self.controller.enable_controls() self.controller._on_guide_toggle({"new": True}) self._assert_guide_is_on() self.controller._on_guide_toggle({"new": False}) @@ -23,7 +23,7 @@ def test_guide_toggle(self): def test_about_toggle(self): """Test about_toggle method.""" self._instansiate_mvc_components() - self.controller.enable_toggles() + self.controller.enable_controls() self.controller._on_about_toggle({"new": True}) self._assert_about_is_on() self.controller._on_about_toggle({"new": False}) @@ -32,7 +32,7 @@ def test_about_toggle(self): def test_toggle_switch(self): """Test toggle_switch method.""" self._instansiate_mvc_components() - self.controller.enable_toggles() + self.controller.enable_controls() self._assert_no_info() self.controller._on_guide_toggle({"new": True}) self._assert_guide_is_on()