Source code for exlab_wizard.ui.pages.welcome
"""Welcome card page (Frontend Spec §3.1.3).
Modal card shown exactly once on the first launch of the app on a
workstation. Three bullets describing the app, a *5-minute* setup
estimate, an autostart toggle defaulted on, and two buttons:
* ``[Get started]`` -- applies autostart and opens Settings in
setup-incomplete mode (§7.14).
* ``Skip for now`` (text link) -- applies autostart and closes the card.
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from exlab_wizard.logging import get_logger
_log = get_logger(__name__)
WELCOME_HEADLINE = "Welcome to ExLab-Wizard"
WELCOME_BULLETS: tuple[str, ...] = (
"Creates standardized run / project directories on disk and NAS.",
"Integrates with your LIMS for project tracking.",
"Validates outputs and gates NAS sync on hard-tier findings.",
)
WELCOME_TIME_ESTIMATE = "Setup takes about 5 minutes."
WELCOME_AUTOSTART_LABEL = "Start ExLab-Wizard automatically when I log in."
WELCOME_AUTOSTART_HELPER = (
"Recommended on lab workstations dedicated to acquisition. "
"You can change this later in Settings -> Application."
)
[docs]
@dataclass
class WelcomeCardSpec:
"""Render spec captured for unit-test assertions."""
headline: str
bullets: tuple[str, ...]
time_estimate: str
autostart_default_on: bool
autostart_label: str
autostart_helper: str
primary_label: str
secondary_label: str
[docs]
def welcome_card_spec() -> WelcomeCardSpec:
"""Return the immutable spec used to render the welcome card."""
return WelcomeCardSpec(
headline=WELCOME_HEADLINE,
bullets=WELCOME_BULLETS,
time_estimate=WELCOME_TIME_ESTIMATE,
autostart_default_on=True,
autostart_label=WELCOME_AUTOSTART_LABEL,
autostart_helper=WELCOME_AUTOSTART_HELPER,
primary_label="Get started",
secondary_label="Skip for now",
)
[docs]
def render_welcome_page(
*,
on_get_started: Callable[[bool], None],
on_skip: Callable[[bool], None],
) -> Any:
"""Render the welcome card.
``on_get_started`` and ``on_skip`` are invoked with the autostart
toggle's final value when the operator clicks the corresponding
affordance.
"""
spec = welcome_card_spec()
try:
from nicegui import ui
except Exception:
return spec
autostart_value = {"on": spec.autostart_default_on}
card = (
ui.card()
.props('data-testid="welcome-card"')
.style(
"max-width: 480px; "
"padding: var(--sp-8); "
"background: var(--color-surface); "
"border-radius: var(--radius-lg); "
"box-shadow: var(--shadow-lg);" # modal card -- shadow-lg permitted
)
)
with card:
ui.label(spec.headline).props('data-testid="welcome-headline"').style(
"font-family: var(--font-display); "
"font-size: var(--text-2xl); "
"color: var(--color-heading); "
"font-weight: 600;"
)
for bullet in spec.bullets:
ui.label(f"-- {bullet}").style(
"font-family: var(--font-body); "
"font-size: var(--text-sm); "
"color: var(--color-body);"
)
ui.label(spec.time_estimate).style(
"font-family: var(--font-body); font-size: var(--text-xs); color: var(--color-muted);"
)
def _on_toggle(evt: Any) -> None:
autostart_value["on"] = bool(evt.value)
ui.checkbox(
spec.autostart_label,
value=spec.autostart_default_on,
on_change=_on_toggle,
).props('data-testid="welcome-autostart-toggle"')
ui.label(spec.autostart_helper).style(
"font-family: var(--font-body); font-size: var(--text-xs); color: var(--color-muted);"
)
with ui.row().classes("items-center w-full justify-end").style("gap: var(--sp-3);"):
ui.button(
spec.secondary_label,
on_click=lambda _evt: on_skip(autostart_value["on"]),
).props('flat data-testid="welcome-skip-for-now"')
ui.button(
spec.primary_label,
on_click=lambda _evt: on_get_started(autostart_value["on"]),
).props('color=primary data-testid="welcome-get-started"')
return card