Skip to content

Commit

Permalink
Clean up app wrapper MVC (aiidalab#1009)
Browse files Browse the repository at this point in the history
This PR cleans up recent developments in the app wrapper classes to better fit with the overall MVC design of the app.
  • Loading branch information
edan-bainglass authored Dec 20, 2024
1 parent 5f57e25 commit eac7167
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 51 deletions.
2 changes: 1 addition & 1 deletion qe.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"metadata": {},
"outputs": [],
"source": [
"controller.enable_toggles()"
"controller.enable_controls()"
]
}
],
Expand Down
101 changes: 57 additions & 44 deletions src/aiidalab_qe/app/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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",
Expand All @@ -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):
Expand Down Expand Up @@ -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()
Expand All @@ -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"))
Expand All @@ -224,7 +243,7 @@ def __init__(self) -> None:
children=[
logo,
subtitle,
info_toggles,
self.controls,
self.info_container,
],
)
Expand All @@ -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')"))
12 changes: 6 additions & 6 deletions tests/test_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand All @@ -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})
Expand All @@ -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()
Expand Down

0 comments on commit eac7167

Please sign in to comment.