From bd8cc1a1dd49db2d0a37a6fbc0a0ce8ab2be3bc1 Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Mon, 9 Dec 2024 19:28:35 +0000 Subject: [PATCH] Generalize and simplify --- src/aiidalab_qe/app/configuration/__init__.py | 52 +------- .../app/configuration/advanced/advanced.py | 14 +-- .../app/configuration/basic/workflow.py | 22 +--- src/aiidalab_qe/app/main.py | 12 +- src/aiidalab_qe/app/structure/__init__.py | 52 +------- src/aiidalab_qe/app/submission/__init__.py | 20 +-- src/aiidalab_qe/app/wrapper.py | 10 +- src/aiidalab_qe/common/guide_manager.py | 50 ++++++++ src/aiidalab_qe/common/infobox.py | 43 +++---- src/aiidalab_qe/guides/basic.html | 119 ++++++++++++++++++ src/aiidalab_qe/plugins/bands/__init__.py | 3 + .../plugins/bands/guides/bands.html | 119 ++++++++++++++++++ src/aiidalab_qe/plugins/bands/setting.py | 14 +-- src/aiidalab_qe/plugins/pdos/setting.py | 14 +-- tests/test_infobox.py | 19 ++- 15 files changed, 343 insertions(+), 220 deletions(-) create mode 100644 src/aiidalab_qe/common/guide_manager.py create mode 100644 src/aiidalab_qe/guides/basic.html create mode 100644 src/aiidalab_qe/plugins/bands/guides/bands.html diff --git a/src/aiidalab_qe/app/configuration/__init__.py b/src/aiidalab_qe/app/configuration/__init__.py index 2adb18c3c..00e549f46 100644 --- a/src/aiidalab_qe/app/configuration/__init__.py +++ b/src/aiidalab_qe/app/configuration/__init__.py @@ -115,36 +115,13 @@ def render(self): children=[ ipw.VBox( children=[ - InAppGuide( - children=[ - ipw.HTML(""" -
- Here we select the properties to calculate. -
- Select Electronic band structure and - Projected density of states (PDOS) -
-
- """) - ], - ), + InAppGuide(identifier="properties-selection"), *self.property_children, ] ), ipw.VBox( children=[ - InAppGuide( - children=[ - ipw.HTML(""" -
- Here we can customize the calculation parameters. -
- Click on each tab to customize its settings. -
-
- """) - ], - ), + InAppGuide(identifier="calculation-settings"), self.tabs, ], ), @@ -171,30 +148,7 @@ def render(self): self.confirm_button.on_click(self.confirm) self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -
- In this step, we define the workflow tasks, including structure - relaxation and which properties to compute, and select the - parameters of the calculations. -
-

Tasks

-
    -
  1. Select Full geometry relaxation
  2. -
  3. Open Step 2.1 to select properties
  4. -
  5. Open Step 2.2 to customize parameters
  6. -
  7. Click Confirm to proceed
  8. -
-
-
- Note: Changes after confirmation will unconfirm this - step and reset the following steps. -
-
- """) - ], - ), + InAppGuide(identifier="configuration-step"), self.structure_set_message, ipw.HTML("""
diff --git a/src/aiidalab_qe/app/configuration/advanced/advanced.py b/src/aiidalab_qe/app/configuration/advanced/advanced.py index e2b7e8a96..c675b4d83 100644 --- a/src/aiidalab_qe/app/configuration/advanced/advanced.py +++ b/src/aiidalab_qe/app/configuration/advanced/advanced.py @@ -275,19 +275,7 @@ def render(self): self.pseudos.render() self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -
- The Advanced settings allow you to finely tune the calculation. -
- In this walkthrough, we will not modify advanced settings - and proceed with the defaults. -
-
- """) - ], - ), + InAppGuide(identifier="advanced-settings"), ipw.HBox( children=[ self.clean_workdir, diff --git a/src/aiidalab_qe/app/configuration/basic/workflow.py b/src/aiidalab_qe/app/configuration/basic/workflow.py index 75fdce849..c58084957 100644 --- a/src/aiidalab_qe/app/configuration/basic/workflow.py +++ b/src/aiidalab_qe/app/configuration/basic/workflow.py @@ -61,27 +61,7 @@ def render(self): ) self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -
- The basic settings panel provides top-level calculation - settings including the electronic and magnetic properties of - the material. It also provides a choice of three protocols - that pre-configure many calculation settings, balancing - speed with accuracy. -
- Select the fast protocol -
-
- Note: Due to the limited resources provided for the - demo server, we select the fast protocol to - reduce the cost of the calculation. -
-
- """) - ], - ), + InAppGuide(identifier="basic-settings"), ipw.HTML("""
Below you can indicate both if the material should be treated as an diff --git a/src/aiidalab_qe/app/main.py b/src/aiidalab_qe/app/main.py index 2cc5fc12c..b1aad16c8 100644 --- a/src/aiidalab_qe/app/main.py +++ b/src/aiidalab_qe/app/main.py @@ -110,17 +110,7 @@ def __init__(self, qe_auto_setup=True): super().__init__( children=[ - InAppGuide( - children=[ - ipw.HTML(""" -
- You've activated an in-app guide. Follow along below to learn - how to use the Quantum ESPRESSO app. -
- """) - ], - classes=["guide-warning"], - ), + InAppGuide(identifier="guide-warning", classes=["guide-warning"]), self.new_workchain_button, self._process_loading_message, self._wizard_app_widget, diff --git a/src/aiidalab_qe/app/structure/__init__.py b/src/aiidalab_qe/app/structure/__init__.py index ebc5bfd19..e32e47ecc 100644 --- a/src/aiidalab_qe/app/structure/__init__.py +++ b/src/aiidalab_qe/app/structure/__init__.py @@ -152,57 +152,7 @@ def render(self): ) self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -
- In this step, you can select a structure as follows: -
    -
  • - Upload file: - upload a structure file from your computer. -
  • -
  • - OPTIMADE: - search for structures in the OPTIMADE database. -
  • -
  • - AiiDA database: - search for structures in your AiiDA database. -
  • -
  • - From Examples: - select a structure from a list of example structures. -
  • -
- Once selected, you may inspect the structure. You can also edit - the structure using the available structure editors. When done, - you can choose to modify the structure label and/or provide a - description. These will be attached to the input structure node - in your AiiDA database. When you are ready, click "Confirm" to - proceed to the next step. -
-
-

Tasks

-
    -
  1. Click on the From examples tab
  2. -
  3. Select Gold from the dropdown list
  4. -
  5. Click the Confirm button to proceed.
  6. -
-
-
- Warning: If the confirmed structure is not yet stored - in the AiiDA database, it will be stored automatically when - you proceed to the next step. -
-
- Warning: Changes after confirmation will unconfirm - this step and reset the following steps. -
-
- """), - ], - ), + InAppGuide(identifier="structure-step"), ipw.HTML("""

Select a structure from one of the following sources and then diff --git a/src/aiidalab_qe/app/submission/__init__.py b/src/aiidalab_qe/app/submission/__init__.py index cb9e8420e..e4f745abe 100644 --- a/src/aiidalab_qe/app/submission/__init__.py +++ b/src/aiidalab_qe/app/submission/__init__.py @@ -159,25 +159,7 @@ def render(self): ) self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -

- In this step, we define the resources to be used in the - calculation. The global resources are used to define resources - across all workflow calculations. Optionally, you can override - the resource settings for specific calculations. -
-

Tasks

-
    -
  1. Select resources
  2. -
  3. Click Submit to proceed
  4. -
-
-
- """) - ], - ), + InAppGuide(identifier="submission-step"), ipw.HTML("""

Codes

diff --git a/src/aiidalab_qe/app/wrapper.py b/src/aiidalab_qe/app/wrapper.py index 14ce07d74..59cc530a1 100644 --- a/src/aiidalab_qe/app/wrapper.py +++ b/src/aiidalab_qe/app/wrapper.py @@ -4,6 +4,7 @@ import traitlets as tl from IPython.display import display +from aiidalab_qe.common.guide_manager import guide_manager from aiidalab_qe.common.widgets import LoadingWidget @@ -95,16 +96,19 @@ def _on_job_history_toggle(self, change: dict): def _on_guide_select(self, change: dict): """Sets the current active guide.""" - from aiidalab_qe.common.infobox import guide_manager - guide_manager.active_guide = change["new"] + def _set_guide_options(self, _): + """Fetch the available guides.""" + self._view.guide_selection.options = ["none", *guide_manager.get_guides()] + def _set_event_handlers(self) -> None: """Set up event handlers.""" self._view.guide_toggle.observe(self._on_guide_toggle, "value") self._view.about_toggle.observe(self._on_about_toggle, "value") self._view.job_history_toggle.observe(self._on_job_history_toggle, "value") self._view.guide_selection.observe(self._on_guide_select, "value") + self._view.on_displayed(self._set_guide_options) class AppWrapperModel(tl.HasTraits): @@ -196,7 +200,7 @@ def __init__(self) -> None: self.about = ipw.HTML(env.from_string(about_template).render()) self.guide_selection = ipw.RadioButtons( - options=["none", "basic"], + options=["none"], description="Guides:", value="none", ) diff --git a/src/aiidalab_qe/common/guide_manager.py b/src/aiidalab_qe/common/guide_manager.py new file mode 100644 index 000000000..9e4e8a755 --- /dev/null +++ b/src/aiidalab_qe/common/guide_manager.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from pathlib import Path + +import traitlets as tl +from bs4 import BeautifulSoup + +import aiidalab_qe +from aiidalab_qe.app.utils import get_entry_items + + +class GuideManager(tl.HasTraits): + active_guide = tl.Unicode("none", allow_none=True) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + guides = Path(aiidalab_qe.__file__).parent.joinpath("guides").glob("*") + self._guides = {guide.stem: guide.absolute() for guide in guides} + self._fetch_plugin_guides() + + self.content = BeautifulSoup() + + self.observe( + self._on_active_guide_change, + "active_guide", + ) + + @property + def has_guide(self): + return self.active_guide != "none" + + def get_guides(self): + return [*self._guides.keys()] + + def get_guide_section_by_id(self, content_id: str): + return self.content.find(attrs={"id": content_id}) + + def _on_active_guide_change(self, _): + guide_path = self._guides.get(self.active_guide) + html = Path(guide_path).read_text() if guide_path else "" + self.content = BeautifulSoup(html, "html.parser") + + def _fetch_plugin_guides(self): + entries: dict[str, Path] = get_entry_items("aiidalab_qe.properties", "guides") + for guides in entries.values(): + for guide in guides.glob("*"): + self._guides[guide.stem] = guide.absolute() + + +guide_manager = GuideManager() diff --git a/src/aiidalab_qe/common/infobox.py b/src/aiidalab_qe/common/infobox.py index a665991f3..841dc7622 100644 --- a/src/aiidalab_qe/common/infobox.py +++ b/src/aiidalab_qe/common/infobox.py @@ -1,7 +1,6 @@ from __future__ import annotations import ipywidgets as ipw -import traitlets as tl class InfoBox(ipw.VBox): @@ -23,19 +22,13 @@ def __init__(self, classes: list[str] | None = None, **kwargs): self.add_class(custom_class) -class GuideManager(tl.HasTraits): - active_guide = tl.Unicode("none") - - -guide_manager = GuideManager() - - class InAppGuide(InfoBox): - """The `InfoAppGuide` is used to set up in-app guides that may be toggle in unison.""" + """The `InfoAppGuide` is used to set up toggleable in-app guides.""" def __init__( self, - guide_class: str = "qe-app", + children: list | None = None, + identifier: str = "", classes: list[str] | None = None, **kwargs, ): @@ -43,14 +36,16 @@ def __init__( Parameters ---------- - `guide_class` : `str`, optional - The identifier used to toggle the guide. - The default `qe-app` identifies built-in guide sections. + children : `list`, optional + The children of the guide. + `identifier` : `str` + The identifier used to load the guide file. `classes` : `list[str]`, optional One or more CSS classes. """ + from aiidalab_qe.common.guide_manager import guide_manager - self.guide_class = guide_class + self.manager = guide_manager super().__init__( classes=[ @@ -60,7 +55,15 @@ def __init__( **kwargs, ) - guide_manager.observe( + if children: + self.children = children + elif identifier: + self.children = [] + self.identifier = identifier + else: + raise ValueError("No content or path identifier provided") + + self.manager.observe( self._on_active_guide_change, "active_guide", ) @@ -74,9 +77,7 @@ def _on_active_guide_change(self, _): self._toggle_guide() def _toggle_guide(self): - active_guide = guide_manager.active_guide - not_generic = self.guide_class != "qe-app" - if active_guide == "none" or (not_generic and active_guide != self.guide_class): - self.layout.display = "none" - else: - self.layout.display = "flex" + if hasattr(self, "identifier"): + html = self.manager.get_guide_section_by_id(self.identifier) + self.children = [ipw.HTML(str(html))] if html else [] + self.layout.display = "flex" if self.manager.has_guide else "none" diff --git a/src/aiidalab_qe/guides/basic.html b/src/aiidalab_qe/guides/basic.html new file mode 100644 index 000000000..bd28dd655 --- /dev/null +++ b/src/aiidalab_qe/guides/basic.html @@ -0,0 +1,119 @@ +
+ You've activated an in-app guide. Follow along below to learn how to use the + Quantum ESPRESSO app. +
+ +
+ In this step, you can select a structure as follows: +
    +
  • Upload file: upload a structure file from your computer.
  • +
  • OPTIMADE: search for structures in the OPTIMADE database.
  • +
  • + AiiDA database: search for structures in your AiiDA database. +
  • +
  • + From Examples: select a structure from a list of example + structures. +
  • +
+ Once selected, you may inspect the structure. You can also edit the structure + using the available structure editors. When done, you can choose to modify the + structure label and/or provide a description. These will be attached to the + input structure node in your AiiDA database. When you are ready, click + "Confirm" to proceed to the next step. +
+
+

Tasks

+
    +
  1. Click on the From examples tab
  2. +
  3. Select Gold from the dropdown list
  4. +
  5. Click the Confirm button to proceed.
  6. +
+
+
+ Warning: If the confirmed structure is not yet stored in the AiiDA + database, it will be stored automatically when you proceed to the next step. +
+
+ Warning: Changes after confirmation will unconfirm this step and + reset the following steps. +
+
+ +
+ In this step, we define the workflow tasks, including structure relaxation and + which properties to compute, and select the parameters of the calculations. +
+

Tasks

+
    +
  1. Select Full geometry relaxation
  2. +
  3. Open Step 2.1 to select properties
  4. +
  5. Open Step 2.2 to customize parameters
  6. +
  7. Click Confirm to proceed
  8. +
+
+
+ Note: Changes after confirmation will unconfirm this step and reset + the following steps. +
+
+ +
+ Here we select the properties to calculate. +
+ Select Electronic band structure and + Projected density of states (PDOS) +
+
+ +
+ Here we can customize the calculation parameters. +
+ Click on each tab to customize its settings. +
+
+ +
+ The basic settings panel provides top-level calculation settings including the + electronic and magnetic properties of the material. It also provides a choice + of three protocols that pre-configure many calculation settings, balancing + speed with accuracy. +
Select the fast protocol
+
+ Note: Due to the limited resources provided for the demo server, we select + the fast protocol to reduce the cost of the calculation. +
+
+ +
+ The Advanced settings allow you to finely tune the calculation. +
+ In this walkthrough, we will not modify advanced settings and proceed with + the defaults. +
+
+ +
+ Here we configure the settings for computing the band structure. +
Check Fat bands calculation
+
+ +
+ Here we configure the settings for computing the projected density of states, + or PDOS. +
???
+
+ +
+ In this step, we define the resources to be used in the calculation. The + global resources are used to define resources across all workflow + calculations. Optionally, you can override the resource settings for specific + calculations. +
+

Tasks

+
    +
  1. Select resources
  2. +
  3. Click Submit to proceed
  4. +
+
+
diff --git a/src/aiidalab_qe/plugins/bands/__init__.py b/src/aiidalab_qe/plugins/bands/__init__.py index 2cc72a868..3c2a97aca 100644 --- a/src/aiidalab_qe/plugins/bands/__init__.py +++ b/src/aiidalab_qe/plugins/bands/__init__.py @@ -1,4 +1,6 @@ # from aiidalab_qe.bands.result import Result +from pathlib import Path + from aiidalab_qe.common.panel import PluginOutline from .model import BandsConfigurationSettingsModel @@ -27,4 +29,5 @@ class BandsPluginOutline(PluginOutline): "model": BandsResultsModel, }, "workchain": workchain_and_builder, + "guides": Path(__file__).parent / "guides", } diff --git a/src/aiidalab_qe/plugins/bands/guides/bands.html b/src/aiidalab_qe/plugins/bands/guides/bands.html new file mode 100644 index 000000000..bd28dd655 --- /dev/null +++ b/src/aiidalab_qe/plugins/bands/guides/bands.html @@ -0,0 +1,119 @@ +
+ You've activated an in-app guide. Follow along below to learn how to use the + Quantum ESPRESSO app. +
+ +
+ In this step, you can select a structure as follows: +
    +
  • Upload file: upload a structure file from your computer.
  • +
  • OPTIMADE: search for structures in the OPTIMADE database.
  • +
  • + AiiDA database: search for structures in your AiiDA database. +
  • +
  • + From Examples: select a structure from a list of example + structures. +
  • +
+ Once selected, you may inspect the structure. You can also edit the structure + using the available structure editors. When done, you can choose to modify the + structure label and/or provide a description. These will be attached to the + input structure node in your AiiDA database. When you are ready, click + "Confirm" to proceed to the next step. +
+
+

Tasks

+
    +
  1. Click on the From examples tab
  2. +
  3. Select Gold from the dropdown list
  4. +
  5. Click the Confirm button to proceed.
  6. +
+
+
+ Warning: If the confirmed structure is not yet stored in the AiiDA + database, it will be stored automatically when you proceed to the next step. +
+
+ Warning: Changes after confirmation will unconfirm this step and + reset the following steps. +
+
+ +
+ In this step, we define the workflow tasks, including structure relaxation and + which properties to compute, and select the parameters of the calculations. +
+

Tasks

+
    +
  1. Select Full geometry relaxation
  2. +
  3. Open Step 2.1 to select properties
  4. +
  5. Open Step 2.2 to customize parameters
  6. +
  7. Click Confirm to proceed
  8. +
+
+
+ Note: Changes after confirmation will unconfirm this step and reset + the following steps. +
+
+ +
+ Here we select the properties to calculate. +
+ Select Electronic band structure and + Projected density of states (PDOS) +
+
+ +
+ Here we can customize the calculation parameters. +
+ Click on each tab to customize its settings. +
+
+ +
+ The basic settings panel provides top-level calculation settings including the + electronic and magnetic properties of the material. It also provides a choice + of three protocols that pre-configure many calculation settings, balancing + speed with accuracy. +
Select the fast protocol
+
+ Note: Due to the limited resources provided for the demo server, we select + the fast protocol to reduce the cost of the calculation. +
+
+ +
+ The Advanced settings allow you to finely tune the calculation. +
+ In this walkthrough, we will not modify advanced settings and proceed with + the defaults. +
+
+ +
+ Here we configure the settings for computing the band structure. +
Check Fat bands calculation
+
+ +
+ Here we configure the settings for computing the projected density of states, + or PDOS. +
???
+
+ +
+ In this step, we define the resources to be used in the calculation. The + global resources are used to define resources across all workflow + calculations. Optionally, you can override the resource settings for specific + calculations. +
+

Tasks

+
    +
  1. Select resources
  2. +
  3. Click Submit to proceed
  4. +
+
+
diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index 4b70cdff3..225aac3ef 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -27,19 +27,7 @@ def render(self): ) self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -
- Here we configure the settings for computing the band - structure. -
- Check Fat bands calculation -
-
- """) - ], - ), + InAppGuide(identifier="bands-settings"), ipw.HTML("""

Settings

diff --git a/src/aiidalab_qe/plugins/pdos/setting.py b/src/aiidalab_qe/plugins/pdos/setting.py index e08c361a1..c4cf0f301 100644 --- a/src/aiidalab_qe/plugins/pdos/setting.py +++ b/src/aiidalab_qe/plugins/pdos/setting.py @@ -108,19 +108,7 @@ def render(self): ) self.children = [ - InAppGuide( - children=[ - ipw.HTML(""" -
- Here we configure the settings for computing the projected - density of states, or PDOS. -
- ??? -
-
- """) - ], - ), + InAppGuide(identifier="pdos-settings"), ipw.HTML("""

Settings

diff --git a/tests/test_infobox.py b/tests/test_infobox.py index 314d7928d..871232db4 100644 --- a/tests/test_infobox.py +++ b/tests/test_infobox.py @@ -1,8 +1,7 @@ -from aiidalab_qe.common.infobox import InAppGuide, InfoBox - - def test_infobox_classes(): """Test `InfoBox` classes.""" + from aiidalab_qe.common.infobox import InfoBox + custom_classes = ["custom-1", "custom-2 custom-3"] infobox = InfoBox(classes=custom_classes) assert all( @@ -18,13 +17,21 @@ def test_infobox_classes(): def test_in_app_guide(): """Test `InAppGuide` class.""" - guide_class = "some_guide" - in_app_guide = InAppGuide(guide_class=guide_class) + import ipywidgets as ipw + + from aiidalab_qe.common.guide_manager import guide_manager + from aiidalab_qe.common.infobox import InAppGuide + + in_app_guide = InAppGuide(children=[ipw.HTML("Hello, World!")]) assert all( css_class in in_app_guide._dom_classes for css_class in ( "info-box", "in-app-guide", - f"{guide_class}-guide-identifier", ) ) + assert in_app_guide.children[0].value == "Hello, World!" + + guide_manager.active_guide = "basic" + in_app_guide = InAppGuide(identifier="guide-warning") + assert "You've activated an in-app guide" in in_app_guide.children[0].value