Source code for pjkm.tui.screens.progress

"""Progress screen: shows live task execution."""

from __future__ import annotations

from textual.app import ComposeResult
from textual.containers import Vertical, VerticalScroll
from textual.screen import Screen
from textual.widgets import Label, ProgressBar, RichLog, Static
from textual.worker import Worker, WorkerState

from pjkm.core.engine.project_engine import ProjectEngine, ProjectResult
from pjkm.core.models.project import ProjectConfig
from pjkm.core.models.task import (
    PhaseCompleted,
    PhaseStarted,
    TaskCompleted,
    TaskEvent,
    TaskStarted,
)
from pjkm.core.tasks.defaults import create_default_registry


[docs] class ProgressScreen(Screen): """Displays live progress of project generation.""" def __init__(self, config: ProjectConfig, extra: dict | None = None) -> None: super().__init__() self._config = config self._extra = extra or {} self._total_tasks = 0 self._completed_tasks = 0
[docs] def compose(self) -> ComposeResult: with Vertical(id="wizard-container"): yield Static("Building Project...", classes="title") yield ProgressBar(id="progress-bar", total=100) yield Label("", id="status-label") yield Label("") with VerticalScroll(id="log-scroll"): yield RichLog(id="task-log", highlight=True, markup=True)
[docs] def on_mount(self) -> None: self._run_build()
@property def _log(self) -> RichLog: return self.query_one("#task-log", RichLog) @property def _progress(self) -> ProgressBar: return self.query_one("#progress-bar", ProgressBar) @property def _status(self) -> Label: return self.query_one("#status-label", Label) def _run_build(self) -> None: self.run_worker(self._execute, exclusive=True, thread=True) def _execute(self) -> ProjectResult: registry = create_default_registry() engine = ProjectEngine(task_registry=registry) def on_event(event: TaskEvent) -> None: self.call_from_thread(self._handle_event, event) return engine.execute(self._config, on_event=on_event, extra=self._extra)
[docs] def on_worker_state_changed(self, event: Worker.StateChanged) -> None: if event.state == WorkerState.SUCCESS: result = event.worker.result if isinstance(result, ProjectResult): self.app.show_done( project_dir=self._config.project_dir, success=result.success, )
def _handle_event(self, event: TaskEvent) -> None: match event: case PhaseStarted(phase=phase): self._log.write(f"[bold blue]>>> {phase.name}[/bold blue]") case TaskStarted(task_id=tid, description=desc): self._status.update(f"Running: {desc or tid}") case TaskCompleted(task_id=tid, result=result): self._completed_tasks += 1 pct = min(99, int(self._completed_tasks / max(self._total_tasks, 7) * 100)) self._progress.update(progress=pct) if result.skipped: self._log.write(f" [yellow]Skipped:[/yellow] {tid}") elif result.success: self._log.write(f" [green]Done:[/green] {tid}") else: self._log.write(f" [red]Failed:[/red] {tid}{result.message}") case PhaseCompleted(): pass