trainer_settings_view.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # -*- coding: utf-8 -*-
  2. """
  3. Trainer SubView: Settings (F2).
  4. Schema-basiertes Formular fuer Trainings-Einstellungen.
  5. Felder werden dynamisch aus dem Settings-Schema des Trainers generiert.
  6. """
  7. from __future__ import annotations
  8. from textual.app import ComposeResult
  9. from textual.containers import Vertical
  10. from textual.widgets import Static, Button, Input, Switch
  11. from trixy_core.tui.views.base import MainView
  12. class TrainerSettingsView(MainView):
  13. """F2 im Trainer-Kontext: Trainings-Einstellungen."""
  14. TITLE = "Einstellungen"
  15. VIEW_ID = "trainer_settings"
  16. def __init__(self, **kwargs) -> None:
  17. super().__init__(**kwargs)
  18. self._trainer_data: dict = {}
  19. self._detail_data: object | None = None
  20. self._field_ids: list[str] = []
  21. def set_trainer(self, trainer_data: dict) -> None:
  22. """Setzt die Trainer-Basisdaten."""
  23. self._trainer_data = trainer_data
  24. self._detail_data = None
  25. self._field_ids = []
  26. def compose(self) -> ComposeResult:
  27. with Vertical(classes="trainer-section"):
  28. yield Static("[bold]Einstellungen[/bold]", classes="trainer-section-header")
  29. yield Vertical(id="trainer-settings-form", classes="trainer-form-container")
  30. yield Button("Speichern", id="trainer-settings-save", variant="success")
  31. yield Static("", id="trainer-settings-status")
  32. async def on_activate(self) -> None:
  33. """Laedt Schema und aktuelle Werte."""
  34. await self._load_detail()
  35. async def _load_detail(self) -> None:
  36. """Laedt Detail-Daten und baut das Formular auf."""
  37. if not self.connection or not self._trainer_data:
  38. return
  39. trainer_id = self._trainer_data.get("trainer_id", "")
  40. if not trainer_id:
  41. return
  42. try:
  43. detail = await self.connection.request_trainer_detail(trainer_id)
  44. except Exception:
  45. return
  46. if detail is None or detail.error:
  47. return
  48. self._detail_data = detail
  49. await self._build_form(detail)
  50. async def _build_form(self, detail: object) -> None:
  51. """Baut das Formular aus dem Schema dynamisch auf."""
  52. container = self.query_one("#trainer-settings-form", Vertical)
  53. # Alte Felder entfernen
  54. for child in list(container.children):
  55. await child.remove()
  56. self._field_ids = []
  57. schema = getattr(detail, "settings_schema", {})
  58. fields = schema.get("fields", []) if isinstance(schema, dict) else []
  59. settings = getattr(detail, "settings", {})
  60. for field_def in fields:
  61. name = field_def.get("name", "") or field_def.get("key", "")
  62. label = field_def.get("label", name)
  63. field_type = field_def.get("field_type", "") or field_def.get("type", "text")
  64. help_text = field_def.get("help_text", "") or field_def.get("help", "")
  65. default = field_def.get("default")
  66. current_value = settings.get(name, default)
  67. field_id = f"trainer-set-{name}"
  68. self._field_ids.append(field_id)
  69. # Label
  70. label_widget = Static(label, classes="trainer-field-label")
  71. await container.mount(label_widget)
  72. # Widget je nach Typ
  73. if field_type == "checkbox":
  74. widget = Switch(id=field_id, value=bool(current_value))
  75. elif field_type == "number":
  76. is_float = field_def.get("is_float", False)
  77. input_type = "number" if is_float else "integer"
  78. val_str = str(current_value) if current_value is not None else ""
  79. widget = Input(
  80. id=field_id,
  81. value=val_str,
  82. placeholder=str(default) if default is not None else "",
  83. type=input_type,
  84. )
  85. else:
  86. widget = Input(
  87. id=field_id,
  88. value=str(current_value) if current_value is not None else "",
  89. placeholder=field_def.get("placeholder", ""),
  90. )
  91. await container.mount(widget)
  92. if help_text:
  93. help_widget = Static(f"[dim]{help_text}[/dim]", classes="trainer-field-help")
  94. await container.mount(help_widget)
  95. def collect_settings(self) -> dict:
  96. """Sammelt die Einstellungswerte aus dem Formular."""
  97. if not self._detail_data:
  98. return {}
  99. schema = getattr(self._detail_data, "settings_schema", {})
  100. fields = schema.get("fields", []) if isinstance(schema, dict) else []
  101. result = {}
  102. for field_def in fields:
  103. name = field_def.get("name", "") or field_def.get("key", "")
  104. field_type = field_def.get("field_type", "") or field_def.get("type", "text")
  105. field_id = f"trainer-set-{name}"
  106. try:
  107. if field_type == "checkbox":
  108. widget = self.query_one(f"#{field_id}", Switch)
  109. result[name] = widget.value
  110. elif field_type == "number":
  111. widget = self.query_one(f"#{field_id}", Input)
  112. is_float = field_def.get("is_float", False)
  113. if is_float:
  114. result[name] = float(widget.value) if widget.value else 0.0
  115. else:
  116. result[name] = int(widget.value) if widget.value else 0
  117. else:
  118. widget = self.query_one(f"#{field_id}", Input)
  119. result[name] = widget.value
  120. except Exception:
  121. pass
  122. return result
  123. async def on_button_pressed(self, event: Button.Pressed) -> None:
  124. """Speichern-Button."""
  125. if event.button.id != "trainer-settings-save":
  126. return
  127. status = self.query_one("#trainer-settings-status", Static)
  128. if not self.connection:
  129. status.update("[red]Keine Verbindung[/red]")
  130. return
  131. trainer_id = self._trainer_data.get("trainer_id", "")
  132. settings = self.collect_settings()
  133. try:
  134. status.update("[yellow]Speichern...[/yellow]")
  135. result = await self.connection.write_trainer_settings(trainer_id, settings)
  136. if result and result.success:
  137. status.update("[green]Einstellungen gespeichert[/green]")
  138. else:
  139. error = result.error if result else "Unbekannter Fehler"
  140. status.update(f"[red]Fehler: {error}[/red]")
  141. except Exception as e:
  142. status.update(f"[red]Fehler: {e}[/red]")