Skip to content

Implement consistent step blocker system #1171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 18 additions & 37 deletions src/aiidalab_qe/app/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from __future__ import annotations

import ipywidgets as ipw
import traitlets as tl

from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
from aiidalab_qe.app.utils import get_entry_items
Expand All @@ -15,7 +14,7 @@
ConfigurationSettingsModel,
ConfigurationSettingsPanel,
)
from aiidalab_qe.common.widgets import QeDependentWizardStep
from aiidalab_qe.common.wizard import QeConfirmableDependentWizardStep

from .advanced import (
AdvancedConfigurationSettingsModel,
Expand All @@ -27,14 +26,18 @@
DEFAULT: dict = DEFAULT_PARAMETERS # type: ignore


class ConfigureQeAppWorkChainStep(QeDependentWizardStep[ConfigurationStepModel]):
class ConfigureQeAppWorkChainStep(
QeConfirmableDependentWizardStep[ConfigurationStepModel]
):
missing_information_warning = "Missing input structure. Please set it first."

def __init__(self, model: ConfigurationStepModel, **kwargs):
super().__init__(model=model, **kwargs)
self._model.observe(
self._on_confirmation_change,
"confirmed",
super().__init__(
model=model,
confirm_kwargs={
"tooltip": "Confirm the currently selected settings and go to the next step",
},
**kwargs,
)
self._model.observe(
self._on_input_structure_change,
Expand Down Expand Up @@ -69,6 +72,8 @@ def __init__(self, model: ConfigurationStepModel, **kwargs):
self._fetch_plugin_calculation_settings()

def _render(self):
super()._render()

# RelaxType: degrees of freedom in geometry optimization
self.relax_type_help = ipw.HTML()
ipw.dlink(
Expand Down Expand Up @@ -115,22 +120,7 @@ def _render(self):
self.sub_steps.set_title(0, "Step 2.1: Select which properties to calculate")
self.sub_steps.set_title(1, "Step 2.2: Customize calculation parameters")

self.confirm_button = ipw.Button(
description="Confirm",
tooltip="Confirm the currently selected settings and go to the next step.",
button_style="success",
icon="check-circle",
disabled=True,
layout=ipw.Layout(width="auto"),
)
ipw.dlink(
(self, "state"),
(self.confirm_button, "disabled"),
lambda state: state != self.State.CONFIGURED,
)
self.confirm_button.on_click(self.confirm)

self.children = [
self.content.children = [
InAppGuide(identifier="configuration-step"),
ipw.HTML("""
<div style="padding-top: 0px; padding-bottom: 0px">
Expand All @@ -140,28 +130,22 @@ def _render(self):
self.relax_type_help,
self.relax_type,
self.sub_steps,
self.confirm_button,
]

self.children = [
self.content,
self.confirm_box,
]

def _post_render(self):
self._update_tabs()

def is_saved(self):
return self._model.confirmed

def confirm(self, _=None):
self._model.confirm()

def reset(self):
self._model.reset()
if self.rendered:
self.sub_steps.selected_index = None
self.tabs.selected_index = 0

@tl.observe("previous_step_state")
def _on_previous_step_state_change(self, _):
self._update_state()

def _on_tab_change(self, change):
if (tab_index := change["new"]) is None:
return
Expand All @@ -173,9 +157,6 @@ def _on_input_structure_change(self, _):
self._model.update()
self.reset()

def _on_confirmation_change(self, _):
self._update_state()

def _update_tabs(self):
children = []
titles = []
Expand Down
6 changes: 2 additions & 4 deletions src/aiidalab_qe/app/configuration/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,21 @@
from aiida_quantumespresso.common.types import RelaxType
from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
from aiidalab_qe.common.mixins import (
Confirmable,
HasInputStructure,
HasModels,
)
from aiidalab_qe.common.panel import ConfigurationSettingsModel
from aiidalab_qe.common.widgets import QeWizardStepModel
from aiidalab_qe.common.wizard import QeConfirmableWizardStepModel

DEFAULT: dict = DEFAULT_PARAMETERS # type: ignore

NO_RELAXATION_OPTION = ("Structure as is", "none")


class ConfigurationStepModel(
QeWizardStepModel,
QeConfirmableWizardStepModel,
HasModels[ConfigurationSettingsModel],
HasInputStructure,
Confirmable,
):
identifier = "configuration"

Expand Down
6 changes: 3 additions & 3 deletions src/aiidalab_qe/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ class QeApp:
def __init__(
self,
process=None,
qe_auto_setup=True,
auto_setup=True,
bug_report_url=DEFAULT_BUG_REPORT_URL,
show_log=False,
):
"""Initialize the AiiDAlab QE application with the necessary setup."""

self.process = process
self.qe_auto_setup = qe_auto_setup
self.auto_setup = auto_setup
self.log_widget = None

self._load_styles()
Expand Down Expand Up @@ -83,7 +83,7 @@ def _load_styles(self):
def load(self):
"""Initialize the WizardApp and integrate the app into the main view."""
self.app = WizardApp(
qe_auto_setup=self.qe_auto_setup,
auto_setup=self.auto_setup,
log_widget=self.log_widget,
)
self.view.main.children = [self.app]
Expand Down
3 changes: 2 additions & 1 deletion src/aiidalab_qe/app/result/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from aiida.engine import ProcessState
from aiidalab_qe.common.infobox import InAppGuide
from aiidalab_qe.common.widgets import LoadingWidget, QeDependentWizardStep
from aiidalab_qe.common.widgets import LoadingWidget
from aiidalab_qe.common.wizard import QeDependentWizardStep
from aiidalab_widgets_base import ProcessMonitor, WizardAppWidgetStep

from .components import ResultsComponent
Expand Down
2 changes: 1 addition & 1 deletion src/aiidalab_qe/app/result/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from aiida import orm
from aiida.engine.processes import control
from aiidalab_qe.common.mixins import HasModels, HasProcess
from aiidalab_qe.common.widgets import QeWizardStepModel
from aiidalab_qe.common.wizard import QeWizardStepModel

from .components import ResultsComponentModel

Expand Down
5 changes: 5 additions & 0 deletions src/aiidalab_qe/app/static/styles/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,8 @@ footer {
padding: 0 5px;
margin: -1px;
}

.blocker-messages .alert {
margin-bottom: 0;
padding: 10px;
}
102 changes: 63 additions & 39 deletions src/aiidalab_qe/app/structure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@
ShakeNBreakEditor,
)
from aiidalab_qe.common.infobox import InAppGuide
from aiidalab_qe.common.widgets import CategorizedStructureExamplesWidget, QeWizardStep
from aiidalab_qe.common.widgets import CategorizedStructureExamplesWidget
from aiidalab_qe.common.wizard import QeConfirmableWizardStep
from aiidalab_widgets_base import (
BasicCellEditor,
BasicStructureEditor,
StructureManagerWidget,
StructureUploadWidget,
)

# The Examples list of (name, file) tuple curretly passed to
# The Examples list of (name, file) tuple currently passed to
# StructureExamplesWidget.
file_path = pathlib.Path(__file__).parent
Examples = [
Expand All @@ -39,33 +40,46 @@
]


class StructureSelectionStep(QeWizardStep[StructureStepModel]):
class StructureSelectionStep(QeConfirmableWizardStep[StructureStepModel]):
"""Integrated widget for the selection and edition of structure.
The widget includes a structure manager that allows to select a structure
from different sources. It also includes the structure editor. Both the
structure importers and the structure editors can be extended by plugins.
"""

def __init__(self, model: StructureStepModel, **kwargs):
super().__init__(model=model, **kwargs)
def __init__(self, model: StructureStepModel, auto_setup=True, **kwargs):
super().__init__(
model=model,
confirm_kwargs={
"tooltip": "Confirm the currently selected structure and go to the next step",
},
**kwargs,
)
self._model.observe(
self._on_installation_change,
["installing_sssp", "sssp_installed"],
)
self._model.observe(
self._on_confirmation_change,
"confirmed",
self._on_sssp_installed,
"sssp_installed",
)
self._model.observe(
self._on_input_structure_change,
"input_structure",
)
self._install_sssp(auto_setup)

def _render(self):
super()._render()

examples_by_category = {"Simple crystals": Examples}
plugin_structure_examples = {
item["title"]: item["structures"]
for item in get_entry_items(
"aiidalab_qe.properties", "structure_examples"
).values()
}
examples_by_category.update(plugin_structure_examples)
examples_by_category |= plugin_structure_examples

importers = [
StructureUploadWidget(title="Upload file"),
Expand Down Expand Up @@ -129,27 +143,7 @@ def _render(self):
(self.structure_name_text, "value"),
)

self.confirm_button = ipw.Button(
description="Confirm",
tooltip="Confirm the currently selected structure and go to the next step.",
button_style="success",
icon="check-circle",
layout=ipw.Layout(width="auto"),
)
ipw.dlink(
(self, "state"),
(self.confirm_button, "disabled"),
lambda state: state != self.State.CONFIGURED,
)
self.confirm_button.on_click(self.confirm)

self.message_area = ipw.HTML()
ipw.dlink(
(self._model, "message_area"),
(self.message_area, "value"),
)

self.children = [
self.content.children = [
InAppGuide(identifier="structure-step"),
ipw.HTML("""
<p>
Expand All @@ -161,33 +155,63 @@ def _render(self):
"""),
self.manager,
self.structure_name_text,
self.message_area,
self.confirm_button,
]
# after rendering the widget, nglview needs to be resized

self.confirm_box.children += (self.sssp_installation,)

self.children = [
self.content,
self.confirm_box,
]

def _post_render(self):
# After rendering the widget, nglview needs to be resized
# to properly display the structure
self.manager.viewer._viewer.handle_resize()

def is_saved(self):
return self._model.confirmed

def confirm(self, _=None):
self.manager.store_structure()
self._model.message_area = ""
self._model.confirm()
super().confirm()

def can_reset(self):
return self._model.confirmed

def reset(self):
self._model.reset()

def _on_installation_change(self, _):
self._model.update_blockers()

def _on_sssp_installed(self, _):
self._toggle_sssp_installation_widget()

def _on_input_structure_change(self, _):
self._model.update_widget_text()
self._update_state()

def _on_confirmation_change(self, _):
self._update_state()
def _install_sssp(self, auto_setup):
from aiidalab_qe.common.setup_pseudos import PseudosInstallWidget

self.sssp_installation = PseudosInstallWidget(auto_start=False)
ipw.dlink(
(self.sssp_installation, "busy"),
(self._model, "installing_sssp"),
)
ipw.dlink(
(self.sssp_installation, "installed"),
(self._model, "installing_sssp"),
lambda installed: not installed,
)
ipw.dlink(
(self.sssp_installation, "installed"),
(self._model, "sssp_installed"),
)
if auto_setup:
self.sssp_installation.refresh()

def _toggle_sssp_installation_widget(self):
sssp_installation_display = "none" if self._model.sssp_installed else "block"
self.sssp_installation.layout.display = sssp_installation_display

def _update_state(self):
if self._model.confirmed:
Expand Down
Loading
Loading