Files
Medios-Macina/TUI/modalscreen/matrix_room_picker.py

239 lines
8.3 KiB
Python
Raw Normal View History

2026-01-26 02:29:56 -08:00
from __future__ import annotations
from typing import Any, Dict, List, Optional
from textual.app import ComposeResult
2026-01-30 16:24:08 -08:00
from textual.containers import Container, Horizontal, ScrollableContainer
2026-01-26 02:29:56 -08:00
from textual.screen import ModalScreen
2026-01-30 16:24:08 -08:00
from textual.widgets import Static, Button, Checkbox, ListView, ListItem
2026-01-26 02:29:56 -08:00
from textual import work
2026-01-27 14:56:01 -08:00
from rich.text import Text
2026-01-26 02:29:56 -08:00
class MatrixRoomPicker(ModalScreen[List[str]]):
"""Modal that lists Matrix rooms and returns selected defaults."""
CSS = """
MatrixRoomPicker {
align: center middle;
background: rgba(0, 0, 0, 0.45);
}
#matrix-room-picker {
width: 70%;
height: 70%;
background: $panel;
border: thick $primary;
padding: 1;
}
#matrix-room-picker-hint {
text-style: italic;
color: $text-muted;
margin-bottom: 1;
}
#matrix-room-scroll {
height: 1fr;
border: solid $surface;
padding: 1;
margin-bottom: 1;
}
2026-01-30 16:24:08 -08:00
#matrix-room-list {
2026-01-26 02:29:56 -08:00
padding: 0;
}
.matrix-room-row {
border-bottom: solid $surface;
2026-01-30 16:24:08 -08:00
padding: 1 0;
2026-01-26 02:29:56 -08:00
align: left middle;
}
.matrix-room-meta {
2026-01-27 14:56:01 -08:00
padding-left: 1;
2026-01-26 02:29:56 -08:00
content-align: left middle;
}
#matrix-room-actions {
height: 3;
align: right middle;
}
#matrix-room-status {
height: 3;
text-style: bold;
}
"""
def __init__(
self,
config: Dict[str, Any],
*,
existing: Optional[List[str]] = None,
rooms: Optional[List[Dict[str, Any]]] = None,
) -> None:
super().__init__()
self.config = config
self._prefetched_rooms = rooms
self._existing_ids = {str(r).strip() for r in (existing or []) if str(r).strip()}
self._checkbox_map: Dict[str, str] = {}
self._rooms: List[Dict[str, Any]] = []
self._status_widget: Optional[Static] = None
self._checklist: Optional[Vertical] = None
self._save_button: Optional[Button] = None
def compose(self) -> ComposeResult:
with Container(id="matrix-room-picker"):
yield Static("Matrix Default Rooms", classes="section-title")
yield Static(
"Choose rooms to keep in the sharing defaults and autocomplete.",
id="matrix-room-picker-hint",
)
with ScrollableContainer(id="matrix-room-scroll"):
2026-01-30 16:24:08 -08:00
yield ListView(id="matrix-room-list")
2026-01-26 02:29:56 -08:00
with Horizontal(id="matrix-room-actions"):
yield Button("Cancel", variant="error", id="matrix-room-cancel")
2026-01-30 16:24:08 -08:00
yield Button("Select All", id="matrix-room-select-all")
yield Button("Clear All", id="matrix-room-clear")
2026-01-26 02:29:56 -08:00
yield Button("Save defaults", variant="success", id="matrix-room-save")
yield Static("Loading rooms...", id="matrix-room-status")
def on_mount(self) -> None:
self._status_widget = self.query_one("#matrix-room-status", Static)
2026-01-30 16:24:08 -08:00
self._checklist = self.query_one("#matrix-room-list", ListView)
2026-01-26 02:29:56 -08:00
self._save_button = self.query_one("#matrix-room-save", Button)
if self._save_button:
self._save_button.disabled = True
if self._prefetched_rooms is not None:
self._apply_room_results(self._prefetched_rooms, None)
else:
if self._status_widget:
self._set_status("Loading rooms...")
self._load_rooms_background()
2026-01-30 16:24:08 -08:00
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Intercept ListView.Selected events and prevent them from bubbling up
to parent components which may react (e.g., the main config modal).
Selecting an item should not implicitly close the picker or change the
outer editor state."""
try:
# Stop propagation so parent handlers (ConfigModal) don't react.
event.stop()
except Exception:
pass
2026-01-26 02:29:56 -08:00
def _set_status(self, text: str) -> None:
if self._status_widget:
self._status_widget.update(text)
2026-01-30 16:24:08 -08:00
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
# Enable Save only when at least one checkbox is selected
any_selected = False
for checkbox_id in self._checkbox_map.keys():
try:
cb = self.query_one(f"#{checkbox_id}", Checkbox)
if cb.value:
any_selected = True
break
except Exception:
continue
if self._save_button:
self._save_button.disabled = not any_selected
2026-01-26 02:29:56 -08:00
def _render_rooms(self, rooms: List[Dict[str, Any]]) -> None:
if not self._checklist:
return
for child in list(self._checklist.children):
child.remove()
self._checkbox_map.clear()
self._rooms = rooms
if not rooms:
self._set_status("Matrix returned no rooms.")
if self._save_button:
self._save_button.disabled = True
return
for idx, room in enumerate(rooms):
room_id = str(room.get("room_id") or "").strip()
name = str(room.get("name") or "").strip()
checkbox_id = f"matrix-room-{idx}"
2026-01-30 16:24:08 -08:00
# Prefer display name; otherwise fall back to room id
label_text = name or room_id or "Matrix Room"
2026-01-26 02:29:56 -08:00
checkbox = Checkbox(
2026-01-30 16:24:08 -08:00
label_text,
2026-01-26 02:29:56 -08:00
id=checkbox_id,
value=bool(room_id and room_id in self._existing_ids),
)
self._checkbox_map[checkbox_id] = room_id
2026-01-27 14:56:01 -08:00
2026-01-30 16:24:08 -08:00
list_item = ListItem(checkbox, classes="matrix-room-row")
self._checklist.mount(list_item)
2026-01-27 14:56:01 -08:00
2026-01-26 02:29:56 -08:00
self._set_status("Loaded rooms. Select one or more and save.")
if self._save_button:
self._save_button.disabled = False
@work(thread=True)
def _load_rooms_background(self) -> None:
try:
from Provider.matrix import Matrix
provider = Matrix(self.config)
rooms = provider.list_rooms()
self.app.call_from_thread(self._apply_room_results, rooms, None)
except Exception as exc:
self.app.call_from_thread(self._apply_room_results, [], str(exc))
def _apply_room_results(self, rooms: List[Dict[str, Any]], error: Optional[str]) -> None:
if error:
self._set_status(f"Failed to load Matrix rooms: {error}")
if self._save_button:
self._save_button.disabled = True
return
self._render_rooms(rooms)
2026-01-30 16:24:08 -08:00
# Ensure save button is enabled only if at least one checkbox is selected
any_selected = False
for cbid in self._checkbox_map.keys():
try:
cb = self.query_one(f"#{cbid}", Checkbox)
if cb.value:
any_selected = True
break
except Exception:
continue
if self._save_button:
self._save_button.disabled = not any_selected
2026-01-26 02:29:56 -08:00
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "matrix-room-cancel":
self.dismiss([])
2026-01-30 16:24:08 -08:00
elif event.button.id == "matrix-room-select-all":
for checkbox_id in self._checkbox_map.keys():
try:
cb = self.query_one(f"#{checkbox_id}", Checkbox)
cb.value = True
except Exception:
pass
if self._save_button:
self._save_button.disabled = False
elif event.button.id == "matrix-room-clear":
for checkbox_id in self._checkbox_map.keys():
try:
cb = self.query_one(f"#{checkbox_id}", Checkbox)
cb.value = False
except Exception:
pass
if self._save_button:
self._save_button.disabled = True
2026-01-26 02:29:56 -08:00
elif event.button.id == "matrix-room-save":
selected: List[str] = []
for checkbox_id, room_id in self._checkbox_map.items():
try:
cb = self.query_one(f"#{checkbox_id}", Checkbox)
if cb.value and room_id:
selected.append(room_id)
except Exception:
pass
self.dismiss(selected)