diff --git a/src/aiidalab_qe/app/wrapper.py b/src/aiidalab_qe/app/wrapper.py index 59cc530a1..8ecedb45a 100644 --- a/src/aiidalab_qe/app/wrapper.py +++ b/src/aiidalab_qe/app/wrapper.py @@ -60,7 +60,13 @@ def _on_guide_toggle(self, change: dict): if change["new"]: self._view.info_container.children = [ self._view.guide, - self._view.guide_selection, + ipw.HBox( + children=[ + self._view.guide_category_selection, + self._view.guide_selection, + ], + layout=ipw.Layout(align_items="baseline"), + ), ] self._view.info_container.layout.display = "flex" self._view.job_history_toggle.value = False @@ -94,21 +100,50 @@ def _on_job_history_toggle(self, change: dict): else: self._view.main.children = self._old_view - def _on_guide_select(self, change: dict): + 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_guide_select(self, _): + self._update_active_guide() + + def _update_active_guide(self): """Sets the current active guide.""" - guide_manager.active_guide = change["new"] + 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 - def _set_guide_options(self, _): + def _set_guide_category_options(self, _): """Fetch the available guides.""" - self._view.guide_selection.options = ["none", *guide_manager.get_guides()] + self._view.guide_category_selection.options = [ + "none", + *guide_manager.get_guide_categories(), + ] 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) + 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_category_selection.observe( + self._on_guide_category_select, + "value", + ) + self._view.guide_selection.observe( + self._on_guide_select, + "value", + ) + self._view.on_displayed(self._set_guide_category_options) class AppWrapperModel(tl.HasTraits): @@ -199,11 +234,13 @@ def __init__(self) -> None: self.guide = ipw.HTML(env.from_string(guide_template).render()) self.about = ipw.HTML(env.from_string(about_template).render()) - self.guide_selection = ipw.RadioButtons( + 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")) self.job_history = QueryInterface() diff --git a/src/aiidalab_qe/common/guide_manager.py b/src/aiidalab_qe/common/guide_manager.py index 2bcc6e72a..15e313693 100644 --- a/src/aiidalab_qe/common/guide_manager.py +++ b/src/aiidalab_qe/common/guide_manager.py @@ -19,7 +19,13 @@ 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._guides = { + "general": { + guide.stem.split("_", maxsplit=1)[1]: guide.absolute() + for guide in sorted(guides, key=lambda x: x.stem.split("_")[0]) + } + } + self._fetch_plugin_guides() self.content = BeautifulSoup() @@ -33,7 +39,7 @@ def __init__(self, *args, **kwargs): def has_guide(self) -> bool: return self.active_guide != "none" - def get_guides(self) -> list[str]: + def get_guide_categories(self) -> list[str]: """Return a list of available guides. Returns @@ -43,6 +49,16 @@ def get_guides(self) -> list[str]: """ return [*self._guides.keys()] + def get_guides(self, identifier: str) -> list[str]: + """Return a list of available sub-guides. + + Returns + ------- + `list[str]` + A list of the names of available sub-guides. + """ + return [*self._guides[identifier].keys()] if identifier != "none" else [] + def get_guide_section_by_id(self, content_id: str) -> PageElement | None: """Return a guide section by its HTML `id` attribute. @@ -60,16 +76,23 @@ def get_guide_section_by_id(self, content_id: str) -> PageElement | None: def _on_active_guide_change(self, _): """Load the contents of the active guide.""" - guide_path = self._guides.get(self.active_guide) + if self.active_guide == "none": + self.content = BeautifulSoup() + return + category, guide = self.active_guide.split("/") + guide_path = self._guides[category][guide] html = Path(guide_path).read_text() if guide_path else "" self.content = BeautifulSoup(html, "html.parser") def _fetch_plugin_guides(self): """Fetch guides from plugins.""" 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() + for identifier, guides in entries.items(): + if identifier not in self._guides: + self._guides[identifier] = {} + for guide in sorted(guides.glob("*"), key=lambda x: x.stem.split("_")[0]): + stem = guide.stem.split("_", maxsplit=1)[1] + self._guides[identifier][stem] = guide.absolute() guide_manager = GuideManager() diff --git a/src/aiidalab_qe/guides/basic.html b/src/aiidalab_qe/guides/0_basic.html similarity index 94% rename from src/aiidalab_qe/guides/basic.html rename to src/aiidalab_qe/guides/0_basic.html index bd28dd655..e51f8763d 100644 --- a/src/aiidalab_qe/guides/basic.html +++ b/src/aiidalab_qe/guides/0_basic.html @@ -1,6 +1,6 @@
- You've activated an in-app guide. Follow along below to learn how to use the - Quantum ESPRESSO app. + You've activated the basic in-app guide. Follow along below to learn the basic + features of the Quantum ESPRESSO app.
@@ -101,7 +101,10 @@

Tasks

Here we configure the settings for computing the projected density of states, or PDOS. -
???
+
+ In this walkthrough, we will not modify pdos settings and proceed with the + defaults. +
diff --git a/src/aiidalab_qe/guides/1_advanced.html b/src/aiidalab_qe/guides/1_advanced.html new file mode 100644 index 000000000..90c25128d --- /dev/null +++ b/src/aiidalab_qe/guides/1_advanced.html @@ -0,0 +1,10 @@ +
+ You've activated the advanced in-app guide. Follow along below to learn how to + use the more advanced features of the Quantum ESPRESSO app. +
+ +
Advanced example
+ +
Advanced example
+ +
Advanced example
diff --git a/src/aiidalab_qe/plugins/bands/guides/0_basic.html b/src/aiidalab_qe/plugins/bands/guides/0_basic.html new file mode 100644 index 000000000..9af66d3e0 --- /dev/null +++ b/src/aiidalab_qe/plugins/bands/guides/0_basic.html @@ -0,0 +1,10 @@ +
+ You've activated the basic bands in-app guide. Follow along below to learn how + to use the Quantum ESPRESSO app to run a simple band structure calculation. +
+ +
Bands example
+ +
Bands example
+ +
Bands example
diff --git a/src/aiidalab_qe/plugins/bands/guides/bands.html b/src/aiidalab_qe/plugins/bands/guides/bands.html deleted file mode 100644 index bd28dd655..000000000 --- a/src/aiidalab_qe/plugins/bands/guides/bands.html +++ /dev/null @@ -1,119 +0,0 @@ -
- 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/tests/test_infobox.py b/tests/test_infobox.py index 871232db4..c45ab503e 100644 --- a/tests/test_infobox.py +++ b/tests/test_infobox.py @@ -32,6 +32,6 @@ def test_in_app_guide(): ) assert in_app_guide.children[0].value == "Hello, World!" - guide_manager.active_guide = "basic" + guide_manager.active_guide = "general/basic" in_app_guide = InAppGuide(identifier="guide-warning") assert "You've activated an in-app guide" in in_app_guide.children[0].value