aboutsummaryrefslogtreecommitdiff
path: root/app/ui
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-26 17:46:48 -0400
committerhistoria <historiavg@proton.me>2026-08-26 17:46:48 -0400
commit6ccb6d443d2fb871b43d96ea61a95bc3e6a92355 (patch)
treed692ddccd6ec212cf4ebabb25a85e83af479d0fe /app/ui
parentc147087c9d4707bffaeee58d390653637a21cce8 (diff)
downloadtts-audiobook-generator-6ccb6d443d2fb871b43d96ea61a95bc3e6a92355.tar.gz
feat: combined install/configure tui screens into one menu, removed extraneous wizard screens
Diffstat (limited to 'app/ui')
-rw-r--r--app/ui/hub.py9
-rw-r--r--app/ui/runview.py5
-rw-r--r--app/ui/taskview.py9
-rw-r--r--app/ui/tui.py27
4 files changed, 34 insertions, 16 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py
index 1c0cafa..b41b369 100644
--- a/app/ui/hub.py
+++ b/app/ui/hub.py
@@ -403,7 +403,10 @@ class _Hub:
self.stdscr.timeout(-1)
except Exception:
pass
- invalidate_detect_cache()
+ # The run may have autostarted a server or changed on-disk
+ # state; every exit path (stop-and-exit, key press, cancel,
+ # crash) must drop the cached statuses.
+ invalidate_detect_cache()
return False
# -- settings -------------------------------------------------------
@@ -1406,8 +1409,8 @@ def _read_port(values: dict, key: str) -> int:
"""Parse a port field value, raising ValueError on a bad number."""
try:
number = int(values[key].strip())
- except (KeyError, ValueError):
- raise ValueError(f"Enter a valid port for {key}")
+ except (KeyError, ValueError) as exc:
+ raise ValueError(f"Enter a valid port for {key}") from exc
if not 1 <= number <= 65535:
raise ValueError("Port must be between 1 and 65535")
return number
diff --git a/app/ui/runview.py b/app/ui/runview.py
index a954499..1da8690 100644
--- a/app/ui/runview.py
+++ b/app/ui/runview.py
@@ -112,10 +112,7 @@ class RunView(ScreenView):
self.chunk_total = 0
self.book_results: List[tuple] = [] # (name, ok)
self.error_message = ""
- self.cancelled = False
- self.cancelling = False
- self.started_server = False
- self.finished_at: Optional[float] = None
+ self.started_server = False # cancelled/cancelling/finished_at: base
self.boot_started: Optional[float] = None
self.convert_started: Optional[float] = None
self.stop_started: Optional[float] = None
diff --git a/app/ui/taskview.py b/app/ui/taskview.py
index 0d3b445..c5c2ff7 100644
--- a/app/ui/taskview.py
+++ b/app/ui/taskview.py
@@ -52,15 +52,9 @@ from ui.viewkit import (TERMINAL_PHASES as _TERMINAL,
ScreenView, _box, _fit, _format_elapsed, _sep,
_text)
-# Redraw cadence for the timed getch (milliseconds).
-_DRAW_TIMEOUT_MS = 250
-
# How many recent output lines the log tail keeps.
_LOG_TAIL = 10
-# Terminal state: the run is over and the screen waits for a key.
-_TERMINAL = ("done", "error", "cancelled")
-
# Progress-line matchers, in order of precedence.
_PROGRESS_BYTES = re.compile(r"AUDIOCPP_PROGRESS downloaded=(\d+) total=(\d+)")
_PROGRESS_PERCENT = re.compile(r"(\d{1,3})%")
@@ -592,7 +586,6 @@ class _LaneState:
self.finished = False
-
class _GetchModes:
"""The blocking/non-blocking getch switching shared by all views."""
@@ -836,7 +829,7 @@ class LanesView(_GetchModes):
rects = [(1, 1, width - 2, top_h),
(1, 2 + top_h, width - 2, inner_h - top_h - 1)]
- for lane, (x, y, w, h) in zip(self._lanes, rects):
+ for lane, (x, y, w, h) in zip(self._lanes, rects, strict=False):
self._draw_pane(curses, theme, x, y, w, h, lane, terminal)
if self.phase == "done":
diff --git a/app/ui/tui.py b/app/ui/tui.py
index 11f5653..cc21c0b 100644
--- a/app/ui/tui.py
+++ b/app/ui/tui.py
@@ -883,13 +883,20 @@ def form(scr, title: str, fields: Sequence[dict],
"validate": lambda s: None if s.isdigit() else "digits only"}
{"key": "combine", "label": "Combine chapters",
"kind": "bool", "value": False}
+ {"key": "voices", "label": "Voices directory",
+ "kind": "dir", "value": Path("./voices")}
Fields render as a two-column table: each label is padded to the
widest label so every value starts in the same column. KINDS:
``choice`` opens a single choice menu (its ``choices`` may be a
callable of the field list, resolved when the menu opens); ``text``
opens a line editor (reusing its VALIDATE); ``bool`` shows Yes/No and
- toggles in place on Enter or Space.
+ toggles in place on Enter or Space; ``dir`` opens the DOS-style
+ directory browser (browse_directory) on Enter — its VALUE is a Path
+ (or str path, used as the browse start), an empty value starts at
+ the working directory, and backing out of the browser keeps the old
+ value. Accepting a directory also moves focus to the first button,
+ so Enter right after picking continues to the next screen.
A field may set ``visible`` to a bool or a callable of the field
list; hidden fields are not drawn, are skipped by the cursor, and
@@ -945,6 +952,9 @@ def form(scr, title: str, fields: Sequence[dict],
def display_value(field: dict) -> str:
if field.get("kind") == "bool":
return "Yes" if field["value"] else "No"
+ if field.get("kind") == "dir":
+ value = field["value"]
+ return str(value) if value is not None else ""
return str(field["value"])
while True:
@@ -1047,6 +1057,21 @@ def form(scr, title: str, fields: Sequence[dict],
if chosen is not edit_cancel:
field["value"] = chosen
run_on_change(field)
+ elif field.get("kind") == "dir":
+ start = field["value"]
+ start = Path(start) if start else Path.cwd()
+ picked = browse_directory(
+ scr, field["label"], start=start,
+ validate=field.get("validate"),
+ back_value=edit_cancel)
+ if picked is not edit_cancel:
+ field["value"] = picked
+ run_on_change(field)
+ # Picking a directory is a completed choice:
+ # hand focus straight to the accept button so
+ # Enter continues, with no extra Tab hunting.
+ on_buttons = True
+ btn_index = 0
else:
edited = line_edit(scr, field["label"],
field["value"],