diff --git a/qe.ipynb b/qe.ipynb
index b916c5ea4..fd5b46928 100644
--- a/qe.ipynb
+++ b/qe.ipynb
@@ -24,17 +24,6 @@
"load_profile();"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from aiidalab_widgets_base.utils.loaders import load_css\n",
- "\n",
- "load_css(css_path=\"src/aiidalab_qe/app/static/styles\")"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -50,43 +39,6 @@
" pass"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from aiidalab_qe.app.wrapper import AppWrapperContoller, AppWrapperModel, AppWrapperView\n",
- "\n",
- "model = AppWrapperModel()\n",
- "view = AppWrapperView()\n",
- "controller = AppWrapperContoller(model, view)\n",
- "\n",
- "display(view)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from aiidalab_qe.app.main import App\n",
- "from aiidalab_widgets_base.bug_report import (\n",
- " install_create_github_issue_exception_handler,\n",
- ")\n",
- "\n",
- "install_create_github_issue_exception_handler(\n",
- " view.output,\n",
- " url=\"https://github.com/aiidalab/aiidalab-qe/issues/new\",\n",
- " labels=(\"bug\", \"automated-report\"),\n",
- ")\n",
- "\n",
- "app = App(qe_auto_setup=True)\n",
- "\n",
- "view.main.children = [app]"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -95,10 +47,15 @@
"source": [
"import urllib.parse as urlparse\n",
"\n",
+ "from aiidalab_qe.app.main import QeApp\n",
+ "\n",
"url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n",
"query = urlparse.parse_qs(url.query)\n",
- "if \"pk\" in query:\n",
- " app.process = query[\"pk\"][0]"
+ "pk = query[\"pk\"][0] if \"pk\" in query else None\n",
+ "\n",
+ "app = QeApp(\n",
+ " process=pk, bug_report_url=\"https://github.com/aiidalab/aiidalab-qe/issues/new\"\n",
+ ")"
]
},
{
@@ -107,13 +64,13 @@
"metadata": {},
"outputs": [],
"source": [
- "controller.enable_controls()"
+ "app.load()"
]
}
],
"metadata": {
"kernelspec": {
- "display_name": "base",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
diff --git a/src/aiidalab_qe/app/main.py b/src/aiidalab_qe/app/main.py
index 8b849c243..8e403ae97 100644
--- a/src/aiidalab_qe/app/main.py
+++ b/src/aiidalab_qe/app/main.py
@@ -1,210 +1,52 @@
-"""The main widget that shows the application in the Jupyter notebook.
+"""The main app that shows the application in the Jupyter notebook.
Authors: AiiDAlab team
"""
-import ipywidgets as ipw
-import traitlets as tl
-from IPython.display import Javascript, display
-
-from aiida.orm import load_node
-from aiida.orm.utils.serialize import deserialize_unsafe
-from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep
-from aiidalab_qe.app.configuration.model import ConfigurationStepModel
-from aiidalab_qe.app.result import ViewQeAppWorkChainStatusAndResultsStep
-from aiidalab_qe.app.result.model import ResultsStepModel
-from aiidalab_qe.app.structure import StructureSelectionStep
-from aiidalab_qe.app.structure.model import StructureStepModel
-from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep
-from aiidalab_qe.app.submission.model import SubmissionStepModel
-from aiidalab_qe.common.infobox import InAppGuide
-from aiidalab_qe.common.widgets import LoadingWidget, QeWizardStep
-from aiidalab_widgets_base import WizardAppWidget
-
-
-class App(ipw.VBox):
- """The main widget that combines all the application steps together."""
-
- # The PK or UUID of the work chain node.
- process = tl.Union([tl.Unicode(), tl.Int()], allow_none=True)
-
- def __init__(self, qe_auto_setup=True):
- # Initialize the models
- self.structure_model = StructureStepModel()
- self.configure_model = ConfigurationStepModel()
- self.submit_model = SubmissionStepModel()
- self.results_model = ResultsStepModel()
-
- # Create the application steps
- self.structure_step = StructureSelectionStep(
- model=self.structure_model,
- auto_advance=True,
- )
- self.configure_step = ConfigureQeAppWorkChainStep(
- model=self.configure_model,
- auto_advance=True,
- )
- self.submit_step = SubmitQeAppWorkChainStep(
- model=self.submit_model,
- auto_advance=True,
- qe_auto_setup=qe_auto_setup,
- )
- self.results_step = ViewQeAppWorkChainStatusAndResultsStep(
- model=self.results_model,
- )
-
- # Wizard step observations
- ipw.dlink(
- (self.structure_step, "state"),
- (self.configure_step, "previous_step_state"),
- )
- self.structure_model.observe(
- self._on_structure_confirmation_change,
- "confirmed",
- )
- ipw.dlink(
- (self.configure_step, "state"),
- (self.submit_step, "previous_step_state"),
- )
- self.configure_model.observe(
- self._on_configuration_confirmation_change,
- "confirmed",
- )
- ipw.dlink(
- (self.submit_step, "state"),
- (self.results_step, "previous_step_state"),
- )
- self.submit_model.observe(
- self._on_submission,
- "confirmed",
- )
-
- # Add the application steps to the application
- self._wizard_app_widget = WizardAppWidget(
- steps=[
- ("Select structure", self.structure_step),
- ("Configure workflow", self.configure_step),
- ("Choose computational resources", self.submit_step),
- ("Status & results", self.results_step),
- ]
- )
- self._wizard_app_widget.observe(
- self._on_step_change,
- "selected_index",
- )
-
- # Hide the header
- self._wizard_app_widget.children[0].layout.display = "none" # type: ignore
-
- self._process_loading_message = LoadingWidget(
- message="Loading process",
- layout=ipw.Layout(display="none"),
- )
-
- super().__init__(
- children=[
- InAppGuide(identifier="guide-header"),
- self._process_loading_message,
- self._wizard_app_widget,
- InAppGuide(identifier="post-guide"),
- ]
- )
-
- self._wizard_app_widget.selected_index = None
-
- self.structure_step.state = QeWizardStep.State.READY
-
- self._update_blockers()
-
- @property
- def steps(self):
- return self._wizard_app_widget.steps
-
- @tl.observe("process")
- def _on_process_change(self, change):
- self._update_from_process(change["new"])
-
- def _on_new_workchain_button_click(self, _):
- display(Javascript("window.open('./qe.ipynb', '_blank')"))
-
- def _on_step_change(self, change):
- if (step_index := change["new"]) is not None:
- self._render_step(step_index)
-
- def _on_structure_confirmation_change(self, _):
- self._update_configuration_step()
- self._update_blockers()
-
- def _on_configuration_confirmation_change(self, _):
- self._update_submission_step()
- self._update_blockers()
-
- def _on_submission(self, _):
- self._update_results_step()
- self._lock_app()
-
- def _render_step(self, step_index):
- step: QeWizardStep = self.steps[step_index][1]
- step.render()
-
- def _update_configuration_step(self):
- if self.structure_model.confirmed:
- self.configure_model.input_structure = self.structure_model.input_structure
- else:
- self.configure_model.input_structure = None
-
- def _update_submission_step(self):
- if self.configure_model.confirmed:
- self.submit_model.input_structure = self.structure_model.input_structure
- self.submit_model.input_parameters = self.configure_model.get_model_state()
- else:
- self.submit_model.input_structure = None
- self.submit_model.input_parameters = {}
-
- def _update_results_step(self):
- ipw.dlink(
- (self.submit_model, "process_node"),
- (self.results_model, "process_uuid"),
- lambda node: node.uuid if node is not None else None,
- )
-
- def _lock_app(self):
- for model in (
- self.structure_model,
- self.configure_model,
- self.submit_model,
- ):
- model.unobserve_all("confirmed")
-
- def _update_blockers(self):
- self.submit_model.external_submission_blockers = [
- f"Unsaved changes in the {title} step. Please confirm the changes before submitting."
- for title, step in self.steps[:2]
- if not step.is_saved()
- ]
-
- def _update_from_process(self, pk):
- if pk is None:
- self._wizard_app_widget.reset()
- self._wizard_app_widget.selected_index = 0
- else:
- self._show_process_loading_message()
- process_node = load_node(pk)
- self.structure_model.input_structure = process_node.inputs.structure
- self.structure_model.confirm()
- parameters = process_node.base.extras.get("ui_parameters", {})
- if parameters and isinstance(parameters, str):
- parameters = deserialize_unsafe(parameters)
- self.configure_model.set_model_state(parameters)
- self.configure_model.confirm()
- self.submit_model.process_node = process_node
- self.submit_model.set_model_state(parameters)
- self.submit_model.confirm()
- self._wizard_app_widget.selected_index = 3
- self._hide_process_loading_message()
-
- def _show_process_loading_message(self):
- self._process_loading_message.layout.display = "flex"
-
- def _hide_process_loading_message(self):
- self._process_loading_message.layout.display = "none"
+from pathlib import Path
+
+from IPython.display import display
+
+from aiidalab_qe.app.static import styles
+from aiidalab_qe.app.wizard_app import WizardApp
+from aiidalab_qe.app.wrapper import AppWrapperContoller, AppWrapperModel, AppWrapperView
+from aiidalab_widgets_base.bug_report import (
+ install_create_github_issue_exception_handler,
+)
+from aiidalab_widgets_base.utils.loaders import load_css
+
+
+class QeApp:
+ def __init__(self, process=None, bug_report_url=None):
+ """Initialize the AiiDAlab QE application with the necessary setup."""
+ self._load_styles()
+
+ # Initialize MVC components
+ self.model = AppWrapperModel()
+ self.view = AppWrapperView()
+ display(self.view)
+
+ # Set up bug report handling (if a URL is provided)
+ if bug_report_url:
+ install_create_github_issue_exception_handler(
+ self.view.output,
+ url=bug_report_url,
+ labels=("bug", "automated-report"),
+ )
+
+ # setup UI controls
+ self.controller = AppWrapperContoller(self.model, self.view)
+ self.controller.enable_controls()
+ self.process = process
+
+ def _load_styles(self):
+ """Load CSS styles from the static directory."""
+ load_css(css_path=Path(styles.__file__).parent)
+
+ def load(self):
+ """Initialize the WizardApp and integrate the app into the main view."""
+ self.app = WizardApp(qe_auto_setup=True)
+ self.view.main.children = [self.app]
+ # load a previous calculation if it is provided
+ if self.process:
+ self.app.process = self.process
diff --git a/src/aiidalab_qe/app/wizard_app.py b/src/aiidalab_qe/app/wizard_app.py
new file mode 100644
index 000000000..c20efe943
--- /dev/null
+++ b/src/aiidalab_qe/app/wizard_app.py
@@ -0,0 +1,205 @@
+import ipywidgets as ipw
+import traitlets as tl
+from IPython.display import Javascript, display
+
+from aiida.orm import load_node
+from aiida.orm.utils.serialize import deserialize_unsafe
+from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep
+from aiidalab_qe.app.configuration.model import ConfigurationStepModel
+from aiidalab_qe.app.result import ViewQeAppWorkChainStatusAndResultsStep
+from aiidalab_qe.app.result.model import ResultsStepModel
+from aiidalab_qe.app.structure import StructureSelectionStep
+from aiidalab_qe.app.structure.model import StructureStepModel
+from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep
+from aiidalab_qe.app.submission.model import SubmissionStepModel
+from aiidalab_qe.common.infobox import InAppGuide
+from aiidalab_qe.common.widgets import LoadingWidget, QeWizardStep
+from aiidalab_widgets_base import WizardAppWidget
+
+
+class WizardApp(ipw.VBox):
+ """The main widget that combines all the application steps together."""
+
+ # The PK or UUID of the work chain node.
+ process = tl.Union([tl.Unicode(), tl.Int()], allow_none=True)
+
+ def __init__(self, qe_auto_setup=True):
+ # Initialize the models
+ self.structure_model = StructureStepModel()
+ self.configure_model = ConfigurationStepModel()
+ self.submit_model = SubmissionStepModel()
+ self.results_model = ResultsStepModel()
+
+ # Create the application steps
+ self.structure_step = StructureSelectionStep(
+ model=self.structure_model,
+ auto_advance=True,
+ )
+ self.configure_step = ConfigureQeAppWorkChainStep(
+ model=self.configure_model,
+ auto_advance=True,
+ )
+ self.submit_step = SubmitQeAppWorkChainStep(
+ model=self.submit_model,
+ auto_advance=True,
+ qe_auto_setup=qe_auto_setup,
+ )
+ self.results_step = ViewQeAppWorkChainStatusAndResultsStep(
+ model=self.results_model,
+ )
+
+ # Wizard step observations
+ ipw.dlink(
+ (self.structure_step, "state"),
+ (self.configure_step, "previous_step_state"),
+ )
+ self.structure_model.observe(
+ self._on_structure_confirmation_change,
+ "confirmed",
+ )
+ ipw.dlink(
+ (self.configure_step, "state"),
+ (self.submit_step, "previous_step_state"),
+ )
+ self.configure_model.observe(
+ self._on_configuration_confirmation_change,
+ "confirmed",
+ )
+ ipw.dlink(
+ (self.submit_step, "state"),
+ (self.results_step, "previous_step_state"),
+ )
+ self.submit_model.observe(
+ self._on_submission,
+ "confirmed",
+ )
+
+ # Add the application steps to the application
+ self._wizard_app_widget = WizardAppWidget(
+ steps=[
+ ("Select structure", self.structure_step),
+ ("Configure workflow", self.configure_step),
+ ("Choose computational resources", self.submit_step),
+ ("Status & results", self.results_step),
+ ]
+ )
+ self._wizard_app_widget.observe(
+ self._on_step_change,
+ "selected_index",
+ )
+
+ # Hide the header
+ self._wizard_app_widget.children[0].layout.display = "none" # type: ignore
+
+ self._process_loading_message = LoadingWidget(
+ message="Loading process",
+ layout=ipw.Layout(display="none"),
+ )
+
+ super().__init__(
+ children=[
+ InAppGuide(identifier="guide-header"),
+ self._process_loading_message,
+ self._wizard_app_widget,
+ InAppGuide(identifier="post-guide"),
+ ]
+ )
+
+ self._wizard_app_widget.selected_index = None
+
+ self.structure_step.state = QeWizardStep.State.READY
+
+ self._update_blockers()
+
+ @property
+ def steps(self):
+ return self._wizard_app_widget.steps
+
+ @tl.observe("process")
+ def _on_process_change(self, change):
+ self._update_from_process(change["new"])
+
+ def _on_new_workchain_button_click(self, _):
+ display(Javascript("window.open('./qe.ipynb', '_blank')"))
+
+ def _on_step_change(self, change):
+ if (step_index := change["new"]) is not None:
+ self._render_step(step_index)
+
+ def _on_structure_confirmation_change(self, _):
+ self._update_configuration_step()
+ self._update_blockers()
+
+ def _on_configuration_confirmation_change(self, _):
+ self._update_submission_step()
+ self._update_blockers()
+
+ def _on_submission(self, _):
+ self._update_results_step()
+ self._lock_app()
+
+ def _render_step(self, step_index):
+ step: QeWizardStep = self.steps[step_index][1]
+ step.render()
+
+ def _update_configuration_step(self):
+ if self.structure_model.confirmed:
+ self.configure_model.input_structure = self.structure_model.input_structure
+ else:
+ self.configure_model.input_structure = None
+
+ def _update_submission_step(self):
+ if self.configure_model.confirmed:
+ self.submit_model.input_structure = self.structure_model.input_structure
+ self.submit_model.input_parameters = self.configure_model.get_model_state()
+ else:
+ self.submit_model.input_structure = None
+ self.submit_model.input_parameters = {}
+
+ def _update_results_step(self):
+ ipw.dlink(
+ (self.submit_model, "process_node"),
+ (self.results_model, "process_uuid"),
+ lambda node: node.uuid if node is not None else None,
+ )
+
+ def _lock_app(self):
+ for model in (
+ self.structure_model,
+ self.configure_model,
+ self.submit_model,
+ ):
+ model.unobserve_all("confirmed")
+
+ def _update_blockers(self):
+ self.submit_model.external_submission_blockers = [
+ f"Unsaved changes in the {title} step. Please confirm the changes before submitting."
+ for title, step in self.steps[:2]
+ if not step.is_saved()
+ ]
+
+ def _update_from_process(self, pk):
+ if pk is None:
+ self._wizard_app_widget.reset()
+ self._wizard_app_widget.selected_index = 0
+ else:
+ self._show_process_loading_message()
+ process_node = load_node(pk)
+ self.structure_model.input_structure = process_node.inputs.structure
+ self.structure_model.confirm()
+ parameters = process_node.base.extras.get("ui_parameters", {})
+ if parameters and isinstance(parameters, str):
+ parameters = deserialize_unsafe(parameters)
+ self.configure_model.set_model_state(parameters)
+ self.configure_model.confirm()
+ self.submit_model.process_node = process_node
+ self.submit_model.set_model_state(parameters)
+ self.submit_model.confirm()
+ self._wizard_app_widget.selected_index = 3
+ self._hide_process_loading_message()
+
+ def _show_process_loading_message(self):
+ self._process_loading_message.layout.display = "flex"
+
+ def _hide_process_loading_message(self):
+ self._process_loading_message.layout.display = "none"