This commit is contained in:
2026-01-11 03:47:25 -08:00
parent df2bb76390
commit 722eedc3c3
4 changed files with 64 additions and 29 deletions

View File

@@ -306,7 +306,7 @@ class OpenLibrary(Provider):
"key": "quality", "key": "quality",
"label": "Image Quality", "label": "Image Quality",
"default": "medium", "default": "medium",
"placeholder": "high, medium, low" "choices": ["high", "medium", "low"]
} }
] ]

View File

@@ -152,7 +152,8 @@ class Provider(ABC):
"label": "API Key", "label": "API Key",
"default": "", "default": "",
"required": True, "required": True,
"secret": True "secret": True,
"choices": ["Option 1", "Option 2"]
} }
""" """
return [] return []

View File

@@ -21,7 +21,8 @@ class Store(ABC):
"key": "PATH", "key": "PATH",
"label": "Store Location", "label": "Store Location",
"default": "", "default": "",
"required": True "required": True,
"choices": ["/mnt/media", "/srv/data"]
} }
""" """
return [] return []

View File

@@ -1,7 +1,7 @@
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.screen import ModalScreen from textual.screen import ModalScreen
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
from textual.widgets import Static, Button, Input, Label, ListView, ListItem, Rule, OptionList, Footer from textual.widgets import Static, Button, Input, Label, ListView, ListItem, Rule, OptionList, Footer, Select
from textual import on from textual import on
from textual.message import Message from textual.message import Message
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
@@ -84,6 +84,11 @@ class ConfigModal(ModalScreen):
content-align: left middle; content-align: left middle;
} }
.item-row Button {
width: 15;
margin-left: 1;
}
Button { Button {
margin: 0 1; margin: 0 1;
} }
@@ -279,6 +284,7 @@ class ConfigModal(ModalScreen):
# Determine display props from schema # Determine display props from schema
label_text = k label_text = k
is_secret = False is_secret = False
choices = None
schema = provider_schema_map.get(k_upper) schema = provider_schema_map.get(k_upper)
if schema: if schema:
label_text = schema.get("label") or k label_text = schema.get("label") or k
@@ -286,10 +292,23 @@ class ConfigModal(ModalScreen):
label_text += " *" label_text += " *"
if schema.get("secret"): if schema.get("secret"):
is_secret = True is_secret = True
choices = schema.get("choices")
container.mount(Label(label_text)) container.mount(Label(label_text))
inp_id = f"item-{idx}" inp_id = f"item-{idx}"
self._input_id_map[inp_id] = k self._input_id_map[inp_id] = k
if choices:
# Select takes a list of (label, value) tuples
select_options = [(str(c), str(c)) for c in choices]
# If current value not in choices, add it or stay blank
current_val = str(v)
if current_val not in [str(c) for c in choices]:
select_options.insert(0, (current_val, current_val))
sel = Select(select_options, value=current_val, id=inp_id)
container.mount(sel)
else:
inp = Input(value=str(v), id=inp_id, classes="config-input") inp = Input(value=str(v), id=inp_id, classes="config-input")
if is_secret: if is_secret:
inp.password = True inp.password = True
@@ -306,17 +325,24 @@ class ConfigModal(ModalScreen):
label_text += " *" label_text += " *"
default_val = str(field_def.get("default") or "") default_val = str(field_def.get("default") or "")
choices = field_def.get("choices")
container.mount(Label(label_text))
inp_id = f"item-{idx}" inp_id = f"item-{idx}"
self._input_id_map[inp_id] = key self._input_id_map[inp_id] = key
if choices:
select_options = [(str(c), str(c)) for c in choices]
sel = Select(select_options, value=default_val, id=inp_id)
container.mount(sel)
else:
inp = Input(value=default_val, id=inp_id, classes="config-input") inp = Input(value=default_val, id=inp_id, classes="config-input")
idx += 1
if field_def.get("secret"): if field_def.get("secret"):
inp.password = True inp.password = True
if field_def.get("placeholder"): if field_def.get("placeholder"):
inp.placeholder = field_def.get("placeholder") inp.placeholder = field_def.get("placeholder")
container.mount(Label(label_text))
container.mount(inp) container.mount(inp)
idx += 1
# If it's a store, we might have required keys (legacy check fallback) # If it's a store, we might have required keys (legacy check fallback)
if item_type.startswith("store-"): if item_type.startswith("store-"):
@@ -495,20 +521,15 @@ class ConfigModal(ModalScreen):
self.editing_item_name = ptype self.editing_item_name = ptype
self.refresh_view() self.refresh_view()
@on(Input.Changed) def _update_config_value(self, widget_id: str, value: Any) -> None:
def on_input_changed(self, event: Input.Changed) -> None: if widget_id not in self._input_id_map:
if not event.input.id:
return return
bid = event.input.id key = self._input_id_map[widget_id]
if bid not in self._input_id_map:
return
key = self._input_id_map[bid] if widget_id.startswith("global-"):
self.config_data[key] = value
if bid.startswith("global-"): elif widget_id.startswith("item-") and self.editing_item_name:
self.config_data[key] = event.value
elif bid.startswith("item-") and self.editing_item_name:
it = str(self.editing_item_type or "") it = str(self.editing_item_type or "")
inm = str(self.editing_item_name or "") inm = str(self.editing_item_name or "")
@@ -521,14 +542,26 @@ class ConfigModal(ModalScreen):
self.config_data["store"][stype] = {} self.config_data["store"][stype] = {}
if inm not in self.config_data["store"][stype]: if inm not in self.config_data["store"][stype]:
self.config_data["store"][stype][inm] = {} self.config_data["store"][stype][inm] = {}
self.config_data["store"][stype][inm][key] = event.value self.config_data["store"][stype][inm][key] = value
else: else:
# Provider or other top-level sections # Provider or other top-level sections
if it not in self.config_data: if it not in self.config_data:
self.config_data[it] = {} self.config_data[it] = {}
if inm not in self.config_data[it]: if inm not in self.config_data[it]:
self.config_data[it][inm] = {} self.config_data[it][inm] = {}
self.config_data[it][inm][key] = event.value self.config_data[it][inm][key] = value
@on(Input.Changed)
def on_input_changed(self, event: Input.Changed) -> None:
if event.input.id:
self._update_config_value(event.input.id, event.value)
@on(Select.Changed)
def on_select_changed(self, event: Select.Changed) -> None:
if event.select.id:
# Select value can be the 'Select.BLANK' sentinel
if event.value != Select.BLANK:
self._update_config_value(event.select.id, event.value)
def save_all(self) -> None: def save_all(self) -> None:
save_config(self.config_data, config_dir=self.workspace_root) save_config(self.config_data, config_dir=self.workspace_root)