Skip to content

Commit

Permalink
Add in-app guide mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
edan-bainglass committed Sep 20, 2024
1 parent 95e8f4b commit d6ae1d4
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 9 deletions.
3 changes: 3 additions & 0 deletions src/aiidalab_qe/app/static/styles/infobox.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
.info-box p {
line-height: 24px;
}
.info-box.in-app-guide.show {
display: flex !important;
}
3 changes: 3 additions & 0 deletions src/aiidalab_qe/app/static/templates/guide.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@
For a more in-depth dive into the app's features, please refer to the
<a href="https://aiidalab-qe.readthedocs.io/howto/index.html" target="_blank">how-to guides</a>.
</p>

<p>
Alternatively, you can select one of our in-app guides below to walk through an example use-case.
</p>
</div>
47 changes: 45 additions & 2 deletions src/aiidalab_qe/app/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ipywidgets as ipw
import traitlets
from IPython.display import Javascript, display


def without_triggering(toggle: str):
Expand Down Expand Up @@ -52,7 +53,14 @@ def enable_toggles(self) -> None:
@without_triggering("about_toggle")
def _on_guide_toggle(self, change: dict):
"""Toggle the guide section."""
self._view.info_container.children = [self._view.guide] if change["new"] else []
self._view.info_container.children = (
[
self._view.guide,
self._view.guide_selection,
]
if change["new"]
else []
)
self._view.info_container.layout.display = "flex" if change["new"] else "none"

@without_triggering("guide_toggle")
Expand All @@ -61,10 +69,35 @@ def _on_about_toggle(self, change: dict):
self._view.info_container.children = [self._view.about] if change["new"] else []
self._view.info_container.layout.display = "flex" if change["new"] else "none"

def _on_guide_select(self, change: dict):
"""Toggle the guide section."""
display(
Javascript(f"""
document.querySelectorAll('.{change["old"]}').forEach((guide) => {'{'}
guide.classList.remove('show');
{'}'});
""")
)
if (guide_class := change["new"]) != "none":
display(
Javascript(f"""
document.querySelectorAll('.{guide_class}').forEach((guide) => {'{'}
guide.classList.add('show');
{'}'});
""")
)

def _on_close_first_time_info(self, _=None):
"""Close the first time info box."""
self._view.first_time_users_infobox.layout.display = "none"
with open("first-time-user", "w") as file:
file.write("existing user")

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.guide_selection.observe(self._on_guide_select, "value")


class AppWrapperModel(traitlets.HasTraits):
Expand All @@ -85,7 +118,7 @@ def __init__(self) -> None:
from datetime import datetime

from importlib_resources import files
from IPython.display import Image, display
from IPython.display import Image
from jinja2 import Environment

from aiidalab_qe.app.static import templates
Expand Down Expand Up @@ -140,6 +173,16 @@ 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(
options=[
"none",
"relaxation",
"bands",
],
description="Guides:",
value="none",
)

self.info_container = InfoBox()

header = ipw.VBox(
Expand Down
17 changes: 17 additions & 0 deletions src/aiidalab_qe/common/infobox.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,20 @@ def __init__(self, classes: list[str] | None = None, **kwargs):
for custom_class in custom_classes.split(" "):
if custom_class:
self.add_class(custom_class)


class InAppGuide(InfoBox):
"""The `InfoAppGuide` is used to set up in-app guides that may be toggle in unison."""

def __init__(self, guide_id: str, classes: list[str] | None = None, **kwargs):
"""`InAppGuide` constructor.
Parameters
----------
`guide_id` : `str`
The unique identifier for the guide.
`classes` : `list[str]`, optional
One or more CSS classes.
"""
classes = ["in-app-guide", *(classes or []), guide_id]
super().__init__(classes=classes, **kwargs)
16 changes: 15 additions & 1 deletion tests/test_infobox.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from aiidalab_qe.common.infobox import InfoBox
from aiidalab_qe.common.infobox import InAppGuide, InfoBox


def test_infobox_classes():
Expand All @@ -14,3 +14,17 @@ def test_infobox_classes():
"custom-3",
)
)


def test_in_app_guide():
"""Test `InAppGuide` class."""
guide_id = "some_guide"
in_app_guide = InAppGuide(guide_id=guide_id)
assert all(
css_class in in_app_guide._dom_classes
for css_class in (
"info-box",
"in-app-guide",
guide_id,
)
)
12 changes: 6 additions & 6 deletions tests/test_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_guide_toggle(self):
self.controller._on_guide_toggle({"new": True})
self._assert_guide_is_on()
self.controller._on_guide_toggle({"new": False})
self._assert_no_guide_info()
self._assert_no_info()

def test_about_toggle(self):
"""Test about_toggle method."""
Expand All @@ -27,33 +27,33 @@ def test_about_toggle(self):
self.controller._on_about_toggle({"new": True})
self._assert_about_is_on()
self.controller._on_about_toggle({"new": False})
self._assert_no_guide_info()
self._assert_no_info()

def test_toggle_switch(self):
"""Test toggle_switch method."""
self._instansiate_mvc_components()
self.controller.enable_toggles()
self._assert_no_guide_info()
self._assert_no_info()
self.controller._on_guide_toggle({"new": True})
self._assert_guide_is_on()
self.controller._on_about_toggle({"new": True})
self._assert_about_is_on()
self.controller._on_guide_toggle({"new": True})
self._assert_guide_is_on()
self.controller._on_guide_toggle({"new": False})
self._assert_no_guide_info()
self._assert_no_info()

def _assert_guide_is_on(self):
"""Assert guide is on."""
assert len(self.view.info_container.children) == 1
assert len(self.view.info_container.children) == 2
assert self.view.guide in self.view.info_container.children

def _assert_about_is_on(self):
"""Assert about is on."""
assert len(self.view.info_container.children) == 1
assert self.view.about in self.view.info_container.children

def _assert_no_guide_info(self):
def _assert_no_info(self):
"""Assert no info is shown."""
assert len(self.view.info_container.children) == 0

Expand Down

0 comments on commit d6ae1d4

Please sign in to comment.