81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
|
|
"""Commands page — read-only browser of `commands.yaml` + plugin packs."""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import flet as ft
|
|||
|
|
import yaml
|
|||
|
|
|
|||
|
|
|
|||
|
|
_HERE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _load_commands() -> dict[str, dict]:
|
|||
|
|
try:
|
|||
|
|
with open(os.path.join(_HERE, "commands.yaml"), "rt", encoding="utf8") as f:
|
|||
|
|
return yaml.safe_load(f) or {}
|
|||
|
|
except (OSError, yaml.YAMLError):
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build(page: ft.Page) -> ft.Control: # pylint: disable=unused-argument
|
|||
|
|
cmds = _load_commands()
|
|||
|
|
search = ft.TextField(label="Фильтр", value="", autofocus=False, expand=True)
|
|||
|
|
list_view = ft.ListView(expand=True, spacing=6, padding=0)
|
|||
|
|
|
|||
|
|
def render(query: str = ""):
|
|||
|
|
q = (query or "").strip().lower()
|
|||
|
|
list_view.controls.clear()
|
|||
|
|
shown = 0
|
|||
|
|
for cmd_id, entry in sorted(cmds.items()):
|
|||
|
|
phrases = entry.get("phrases") or []
|
|||
|
|
action = entry.get("action") or {}
|
|||
|
|
joined = " ".join(phrases).lower()
|
|||
|
|
if q and q not in cmd_id.lower() and q not in joined:
|
|||
|
|
continue
|
|||
|
|
shown += 1
|
|||
|
|
list_view.controls.append(
|
|||
|
|
ft.Container(
|
|||
|
|
content=ft.Column([
|
|||
|
|
ft.Row([
|
|||
|
|
ft.Text(cmd_id, weight=ft.FontWeight.W_600, size=13),
|
|||
|
|
ft.Container(
|
|||
|
|
content=ft.Text(action.get("type", "?"), size=10),
|
|||
|
|
bgcolor="#1e3a44",
|
|||
|
|
padding=ft.padding.symmetric(horizontal=8, vertical=2),
|
|||
|
|
border_radius=10,
|
|||
|
|
),
|
|||
|
|
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
|||
|
|
ft.Text(
|
|||
|
|
" · ".join(phrases[:6]) + ("…" if len(phrases) > 6 else ""),
|
|||
|
|
size=11,
|
|||
|
|
color="#8aa0a8",
|
|||
|
|
),
|
|||
|
|
], spacing=4),
|
|||
|
|
bgcolor="#15252b",
|
|||
|
|
border_radius=8,
|
|||
|
|
padding=10,
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
# The header refers to the closure-captured `header` Text via the
|
|||
|
|
# outer-scope binding declared after this function.
|
|||
|
|
header.value = f"Команд показано: {shown} из {len(cmds)}"
|
|||
|
|
page.update()
|
|||
|
|
|
|||
|
|
search.on_change = lambda e: render(e.control.value)
|
|||
|
|
|
|||
|
|
header = ft.Text(f"Всего команд: {len(cmds)}", size=12, color="#8aa0a8")
|
|||
|
|
|
|||
|
|
render()
|
|||
|
|
|
|||
|
|
return ft.Column(
|
|||
|
|
controls=[
|
|||
|
|
ft.Row([
|
|||
|
|
ft.Text("Команды", size=24, weight=ft.FontWeight.W_700),
|
|||
|
|
header,
|
|||
|
|
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
|||
|
|
search,
|
|||
|
|
ft.Container(content=list_view, expand=True),
|
|||
|
|
],
|
|||
|
|
spacing=10,
|
|||
|
|
expand=True,
|
|||
|
|
)
|