Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement app skeleton
Browse files Browse the repository at this point in the history
edan-bainglass committed Jan 16, 2025
1 parent 8df45a1 commit bd00c13
Showing 29 changed files with 473 additions and 2 deletions.
47 changes: 47 additions & 0 deletions app.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiida import load_profile\n",
"\n",
"load_profile();"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_qe.solara.main import Page\n",
"\n",
"Page()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ output-format = "full"
target-version = "py39"

[tool.ruff.lint]
ignore = ["E501", "E402", "TRY003", "RUF012", "N806"]
ignore = ["E501", "E402", "TRY003", "RUF012", "N806", "N802", "N816"]
select = [
"A", # flake8-builtins
"ARG", # flake8-unused-arguments
@@ -42,7 +42,7 @@ select = [
"PLC", # pylint convention rules
"RUF", # ruff-specific rules
"TRY", # Tryceratops
"UP" # pyupgrade
"UP" # pyupgrade
]

[tool.ruff.lint.isort]
Empty file.
9 changes: 9 additions & 0 deletions src/aiidalab_qe/solara/assets/styles/css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css");

#site {
overflow-y: scroll !important;
}

.output_subarea {
max-width: unset !important;
}
Empty file.
29 changes: 29 additions & 0 deletions src/aiidalab_qe/solara/common/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from enum import Enum


class State(Enum):
FAIL = -1
INIT = 0
CONFIGURED = 1
READY = 2
ACTIVE = 3
SUCCESS = 4


STATE_ICONS = {
State.INIT: "\u25cb",
State.READY: "\u25ce",
State.CONFIGURED: "\u25cf",
State.ACTIVE: "\u231b",
State.SUCCESS: "\u2713",
State.FAIL: "\u00d7",
}

BG_COLORS = {
State.INIT: "#eee",
State.READY: "#fcf8e3",
State.CONFIGURED: "#fcf8e3",
State.ACTIVE: "#d9edf7",
State.SUCCESS: "#dff0d8",
State.FAIL: "#f8d7da",
}
15 changes: 15 additions & 0 deletions src/aiidalab_qe/solara/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .app import WizardApp
from .parameters import ParametersStep
from .resources import ResourcesStep
from .results import ResultsStep
from .structure import StructureStep
from .submission import SubmissionStep

__all__ = [
"ParametersStep",
"ResourcesStep",
"ResultsStep",
"StructureStep",
"SubmissionStep",
"WizardApp",
]
6 changes: 6 additions & 0 deletions src/aiidalab_qe/solara/components/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .app import App, WizardApp

__all__ = [
"App",
"WizardApp",
]
34 changes: 34 additions & 0 deletions src/aiidalab_qe/solara/components/app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

import solara
from solara.alias import rv

from ..header import Header, LogoProps
from ..navbar import NavBar, NavItemProps
from ..wizard import StepProps, Wizard


@solara.component
def App(
title: str,
subtitle: str = "",
logo: LogoProps | None = None,
nav_items: list[NavItemProps] | None = None,
children: list[solara.Element] | None = None,
):
with rv.Container(class_="text-center"):
Header(title, subtitle, logo)
if nav_items:
NavBar(nav_items)
rv.Container(children=children or [])


@solara.component
def WizardApp(
title: str,
subtitle: str = "",
logo: LogoProps | None = None,
nav_items: list[NavItemProps] | None = None,
steps: list[StepProps] | None = None,
):
App(title, subtitle, logo, nav_items, children=[Wizard(steps)])
8 changes: 8 additions & 0 deletions src/aiidalab_qe/solara/components/header/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .header import Header
from .logo import Logo, LogoProps

__all__ = [
"Header",
"Logo",
"LogoProps",
]
24 changes: 24 additions & 0 deletions src/aiidalab_qe/solara/components/header/header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

import solara
from solara.alias import rv

from .logo import Logo, LogoProps


@solara.component
def Header(title: str, subtitle: str = "", logo: LogoProps | None = None):
if logo:
Logo(**logo)
with rv.Container(class_="text-center"):
rv.Html(
tag="h1",
class_="display-5 fw-bold",
children=[title],
)
if subtitle:
rv.Html(
tag="h2",
class_="lead mx-auto py-2 text-center",
children=[subtitle],
)
16 changes: 16 additions & 0 deletions src/aiidalab_qe/solara/components/header/logo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

import solara
from solara.alias import rv

LogoProps = dict[str, str]


@solara.component
def Logo(src: str, alt: str = ""):
rv.Img(
class_="d-block mx-auto",
src=src,
alt=alt,
width=100,
)
9 changes: 9 additions & 0 deletions src/aiidalab_qe/solara/components/navbar/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .bar import NavBar
from .item import LinkNavItem, NavItem, NavItemProps

__all__ = [
"LinkNavItem",
"NavBar",
"NavItem",
"NavItemProps",
]
13 changes: 13 additions & 0 deletions src/aiidalab_qe/solara/components/navbar/bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

import solara
from solara.alias import rv

from .item import LinkNavItem, NavItem, NavItemProps


@solara.component
def NavBar(items: list[NavItemProps]):
with rv.Container(class_="d-grid d-sm-block mb-3 p-0 justify-content-center"):
for item in items:
LinkNavItem(**item) if "href" in item else NavItem(**item)
21 changes: 21 additions & 0 deletions src/aiidalab_qe/solara/components/navbar/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import annotations

import solara

NavItemProps = dict[str, str]


@solara.component
def NavItem(label: str = "", icon: str = "", **kwargs):
solara.Button(
class_="btn btn-primary btn-lg m-1 justify-content-start",
icon_name=f"mdi-{icon}",
outlined=True,
label=label,
**kwargs,
)


@solara.component
def LinkNavItem(label: str = "", icon: str = "", href: str = "", **kwargs):
NavItem(label, icon, link=True, href=href, target="_blank", **kwargs)
5 changes: 5 additions & 0 deletions src/aiidalab_qe/solara/components/parameters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .step import ParametersStep

__all__ = [
"ParametersStep",
]
10 changes: 10 additions & 0 deletions src/aiidalab_qe/solara/components/parameters/step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

import solara

from ..wizard.step import onStateChange


@solara.component
def ParametersStep(on_state_change: onStateChange):
pass
5 changes: 5 additions & 0 deletions src/aiidalab_qe/solara/components/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .step import ResourcesStep

__all__ = [
"ResourcesStep",
]
10 changes: 10 additions & 0 deletions src/aiidalab_qe/solara/components/resources/step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

import solara

from ..wizard.step import onStateChange


@solara.component
def ResourcesStep(on_state_change: onStateChange):
pass
5 changes: 5 additions & 0 deletions src/aiidalab_qe/solara/components/results/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .step import ResultsStep

__all__ = [
"ResultsStep",
]
10 changes: 10 additions & 0 deletions src/aiidalab_qe/solara/components/results/step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

import solara

from ..wizard.step import onStateChange


@solara.component
def ResultsStep(on_state_change: onStateChange):
pass
5 changes: 5 additions & 0 deletions src/aiidalab_qe/solara/components/structure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .step import StructureStep

__all__ = [
"StructureStep",
]
10 changes: 10 additions & 0 deletions src/aiidalab_qe/solara/components/structure/step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

import solara

from ..wizard.step import onStateChange


@solara.component
def StructureStep(on_state_change: onStateChange):
pass
5 changes: 5 additions & 0 deletions src/aiidalab_qe/solara/components/submission/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .step import SubmissionStep

__all__ = [
"SubmissionStep",
]
10 changes: 10 additions & 0 deletions src/aiidalab_qe/solara/components/submission/step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

import solara

from ..wizard.step import onStateChange


@solara.component
def SubmissionStep(on_state_change: onStateChange):
pass
8 changes: 8 additions & 0 deletions src/aiidalab_qe/solara/components/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .step import StepProps, WizardStep
from .wizard import Wizard

__all__ = [
"StepProps",
"Wizard",
"WizardStep",
]
27 changes: 27 additions & 0 deletions src/aiidalab_qe/solara/components/wizard/step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import annotations

import typing as t

import solara

from aiidalab_qe.solara.common.state import State

onStateChange = t.Callable[[State], None]
QeAppWizardStep = t.Callable[[onStateChange], solara.Element]
StepProps = tuple[str, QeAppWizardStep]


@solara.component
def WizardStep(
step: QeAppWizardStep,
on_state_change: onStateChange,
confirmable: bool = True,
):
step(on_state_change)
if confirmable:
solara.Button(
label="Confirm",
color="success",
icon_name="check",
on_click=lambda: on_state_change(State.SUCCESS),
)
54 changes: 54 additions & 0 deletions src/aiidalab_qe/solara/components/wizard/wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from __future__ import annotations

import solara
from solara.alias import rv

from aiidalab_qe.solara.common.state import BG_COLORS, STATE_ICONS, State

from .step import StepProps, WizardStep


@solara.component
def Wizard(steps: list[StepProps]):
selected_index, set_selected_index = solara.use_state(None)
states, set_states = solara.use_state([State.INIT for _ in steps])

def on_selection(index: int):
set_selected_index(index)

def on_state_change(i: int, new_state: State):
set_states([*states[:i], new_state, *states[i + 1 :]])
if selected_index < len(steps) - 1:
set_selected_index(selected_index + 1)

with rv.ExpansionPanels(
class_="accordion",
accordion=True,
v_model=selected_index,
on_v_model=on_selection,
hover=True,
):
for i, (title, step) in enumerate(steps):
with rv.ExpansionPanel(
class_="accordion-item",
):
with rv.ExpansionPanelHeader(
class_="accordion-header align-items-center justify-content-start",
style_=f"background-color: {BG_COLORS[states[i]]}",
):
with rv.Container(class_="d-flex p-0"):
rv.Icon(
class_="me-3",
style_="margin-bottom: 1px",
children=[STATE_ICONS[states[i]]],
)
rv.Text(
class_="align-self-end",
children=[f"Step {i + 1}: {title}"],
)
with rv.ExpansionPanelContent(class_="accordion-collapse"):
WizardStep(
step=step,
on_state_change=lambda state, i=i: on_state_change(i, state),
confirmable=i < len(steps) - 1,
)
76 changes: 76 additions & 0 deletions src/aiidalab_qe/solara/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

from pathlib import Path

import solara

from .components import (
ParametersStep,
ResourcesStep,
ResultsStep,
StructureStep,
SubmissionStep,
WizardApp,
)


@solara.component
def Page():
with solara.Head():
solara.Title("AiiDAlab QE app")
solara.Style(Path(__file__).parent / "assets/styles/css/main.css")
WizardApp(
title="The AiiDAlab Quantum ESPRESSO app",
subtitle="🎉 Happy computing 🎉",
logo={
"src": "docs/source/_static/images/aiidalab_qe_logo.png",
"alt": "AiiDAlab Quantum ESPRESSO app logo",
},
nav_items=[
{
"label": "Getting started",
"icon": "rocket",
},
{
"label": "About",
"icon": "information",
},
{
"label": "Calculation history",
"icon": "format-list-bulleted-square",
"href": "./calculation_history.ipynb",
},
{
"label": "Setup resources",
"icon": "database",
"href": "../home/code_setup.ipynb",
},
{
"label": "New calculation",
"icon": "plus-circle",
"href": "./test.ipynb",
},
],
steps=[
(
"Select structure",
StructureStep,
),
(
"Configure the workflow",
ParametersStep,
),
(
"Choose computational resources",
ResourcesStep,
),
(
"Submit the workflow",
SubmissionStep,
),
(
"Status & results",
ResultsStep,
),
],
)

0 comments on commit bd00c13

Please sign in to comment.