mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-31 18:02:05 +00:00
Merge branch 'overstory/builder-sortfilter/SeedPass-de11' into beta
This commit is contained in:
commit
842f0ca9c7
3 files changed
+201
-1
No files matched your search
@@ -930,6 +930,10 @@ class SeedPassTuiV3(App[None]):
|
||||
Binding("g", "show_qr", "QR", show=False),
|
||||
Binding("a", "toggle_archive", "Archive", show=False),
|
||||
Binding("c", "copy_selected", "Copy", show=False),
|
||||
Binding("n", "sort_label", "Sort:Name", show=False),
|
||||
Binding("r", "sort_recent", "Sort:Recent", show=False),
|
||||
Binding("k", "sort_kind", "Sort:Kind", show=False),
|
||||
Binding("f", "clear_filter", "Clear Filter", show=False),
|
||||
]
|
||||
|
||||
# Shared Reactive State
|
||||
@@ -1299,6 +1303,26 @@ class SeedPassTuiV3(App[None]):
|
||||
self.notify(f"Applied filter: {self.filter_kind}")
|
||||
self.action_refresh()
|
||||
|
||||
def action_sort_label(self) -> None:
|
||||
"""Keyboard shortcut: sort grid by label (A→Z)."""
|
||||
self.action_set_search_sort("label_asc")
|
||||
|
||||
def action_sort_recent(self) -> None:
|
||||
"""Keyboard shortcut: sort grid by most-recently modified."""
|
||||
self.action_set_search_sort("modified_desc")
|
||||
|
||||
def action_sort_kind(self) -> None:
|
||||
"""Keyboard shortcut: sort grid by entry kind."""
|
||||
self.action_set_search_sort("kind")
|
||||
|
||||
def action_clear_filter(self) -> None:
|
||||
"""Clear active filter, search query, and reset sort to relevance."""
|
||||
self.filter_kind = "all"
|
||||
self.search_query = ""
|
||||
self.search_sort = "relevance"
|
||||
self.notify("Filter and sort cleared")
|
||||
self.action_refresh()
|
||||
|
||||
def action_open_palette(self) -> None:
|
||||
"""Toggle the command palette."""
|
||||
try:
|
||||
|
||||
@@ -79,6 +79,11 @@ class GridToolbar(Horizontal):
|
||||
margin-right: 1;
|
||||
height: 1;
|
||||
}
|
||||
GridToolbar Button.active {
|
||||
background: #007700;
|
||||
color: #ffffff;
|
||||
text-style: bold;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
@@ -93,11 +98,57 @@ class GridToolbar(Horizontal):
|
||||
yield Button("Semantic", id="grid-mode-semantic")
|
||||
yield Button("Relevance", id="grid-sort-relevance")
|
||||
yield Button("Recent", id="grid-sort-modified_desc")
|
||||
yield Button("Label", id="grid-sort-label_asc")
|
||||
yield Button("Label ↑", id="grid-sort-label_asc")
|
||||
yield Button("Kind", id="grid-sort-kind")
|
||||
yield Button("Linked", id="grid-sort-most_linked")
|
||||
yield Button("[X] Clear", id="grid-clear-filter")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.watch(self.app, "filter_kind", self._sync_active_states)
|
||||
self.watch(self.app, "search_mode", self._sync_active_states)
|
||||
self.watch(self.app, "search_sort", self._sync_active_states)
|
||||
self._sync_active_states()
|
||||
|
||||
def _sync_active_states(self, *_args: Any) -> None:
|
||||
"""Update button active CSS classes to reflect current app state."""
|
||||
try:
|
||||
app = self.app
|
||||
filter_kind = app.filter_kind
|
||||
search_mode = app.search_mode
|
||||
search_sort = app.search_sort
|
||||
|
||||
filter_map = {
|
||||
"grid-filter-all": filter_kind == "all",
|
||||
"grid-filter-secrets": filter_kind == "secrets",
|
||||
"grid-filter-docs": filter_kind == "docs",
|
||||
"grid-filter-keys": filter_kind == "keys",
|
||||
"grid-filter-2fa": filter_kind == "2fa",
|
||||
"grid-mode-keyword": search_mode == "keyword",
|
||||
"grid-mode-hybrid": search_mode == "hybrid",
|
||||
"grid-mode-semantic": search_mode == "semantic",
|
||||
"grid-sort-relevance": search_sort == "relevance",
|
||||
"grid-sort-modified_desc": search_sort == "modified_desc",
|
||||
"grid-sort-label_asc": search_sort == "label_asc",
|
||||
"grid-sort-kind": search_sort == "kind",
|
||||
"grid-sort-most_linked": search_sort == "most_linked",
|
||||
}
|
||||
for btn_id, is_active in filter_map.items():
|
||||
try:
|
||||
btn = self.query_one(f"#{btn_id}", Button)
|
||||
if is_active:
|
||||
btn.add_class("active")
|
||||
else:
|
||||
btn.remove_class("active")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
button_id = event.button.id or ""
|
||||
if button_id == "grid-clear-filter":
|
||||
self.app.action_clear_filter()
|
||||
return
|
||||
if button_id.startswith("grid-filter-"):
|
||||
self.app.action_set_kind_filter(button_id.removeprefix("grid-filter-"))
|
||||
return
|
||||
|
||||
@@ -1095,3 +1095,128 @@ async def test_tui3_inspector_utility_hints_update_by_entry_kind() -> None:
|
||||
await pilot.pause()
|
||||
hints_text = str(hints_bar.render())
|
||||
assert "Actions" not in hints_text
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tui3_keyboard_sort_shortcuts_change_sort() -> None:
|
||||
app, _entry, _vault, search = _build_app()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause()
|
||||
# Default sort is relevance
|
||||
assert app.search_sort == "relevance"
|
||||
|
||||
# n → sort by label_asc
|
||||
await pilot.press("n")
|
||||
await pilot.pause()
|
||||
assert app.search_sort == "label_asc"
|
||||
assert search.calls and search.calls[-1]["sort"] == "label_asc"
|
||||
|
||||
# r → sort by modified_desc
|
||||
await pilot.press("r")
|
||||
await pilot.pause()
|
||||
assert app.search_sort == "modified_desc"
|
||||
assert search.calls[-1]["sort"] == "modified_desc"
|
||||
|
||||
# k → sort by kind
|
||||
await pilot.press("k")
|
||||
await pilot.pause()
|
||||
assert app.search_sort == "kind"
|
||||
assert search.calls[-1]["sort"] == "kind"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tui3_clear_filter_button_resets_state() -> None:
|
||||
app, _entry, _vault, search = _build_app()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause()
|
||||
|
||||
# Set non-default filter/sort/query state
|
||||
app.action_set_kind_filter("docs")
|
||||
app.action_set_search_sort("modified_desc")
|
||||
app.action_search("Email")
|
||||
await pilot.pause()
|
||||
assert app.filter_kind == "docs"
|
||||
assert app.search_sort == "modified_desc"
|
||||
assert app.search_query == "Email"
|
||||
|
||||
# Press Clear button
|
||||
app.screen.query_one("#grid-clear-filter", Button).press()
|
||||
await pilot.pause()
|
||||
assert app.filter_kind == "all"
|
||||
assert app.search_sort == "relevance"
|
||||
assert app.search_query == ""
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tui3_clear_filter_keyboard_shortcut_resets_state() -> None:
|
||||
app, _entry, _vault, _search = _build_app()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause()
|
||||
app.action_set_kind_filter("keys")
|
||||
app.action_set_search_sort("label_asc")
|
||||
await pilot.pause()
|
||||
assert app.filter_kind == "keys"
|
||||
|
||||
await pilot.press("f")
|
||||
await pilot.pause()
|
||||
assert app.filter_kind == "all"
|
||||
assert app.search_sort == "relevance"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tui3_filter_sort_state_persists_across_screen_transitions() -> None:
|
||||
"""Filter and sort state must survive pushing/popping overlay screens."""
|
||||
app, _entry, _vault, search = _build_app()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause()
|
||||
|
||||
# Establish non-default state
|
||||
app.action_set_kind_filter("secrets")
|
||||
app.action_set_search_sort("modified_desc")
|
||||
await pilot.pause()
|
||||
assert app.filter_kind == "secrets"
|
||||
assert app.search_sort == "modified_desc"
|
||||
|
||||
# Push a screen (atlas wayfinder)
|
||||
app.action_open_atlas_wayfinder()
|
||||
await pilot.pause()
|
||||
assert isinstance(app.screen, AtlasWayfinderScreen)
|
||||
|
||||
# Pop back to main
|
||||
app.pop_screen()
|
||||
await pilot.pause()
|
||||
# State must be unchanged
|
||||
assert app.filter_kind == "secrets"
|
||||
assert app.search_sort == "modified_desc"
|
||||
|
||||
# Grid refresh triggered after pop should use persisted sort
|
||||
table = app.screen.query_one("#entry-data-table", DataTable)
|
||||
table._refresh_data()
|
||||
await pilot.pause()
|
||||
assert search.calls and search.calls[-1]["sort"] == "modified_desc"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tui3_toolbar_active_class_reflects_app_state() -> None:
|
||||
"""GridToolbar buttons must gain/lose the 'active' CSS class as state changes."""
|
||||
app, _entry, _vault, _search = _build_app()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause()
|
||||
|
||||
# Default: filter=all, mode=keyword, sort=relevance
|
||||
assert app.screen.query_one("#grid-filter-all", Button).has_class("active")
|
||||
assert app.screen.query_one("#grid-mode-keyword", Button).has_class("active")
|
||||
assert app.screen.query_one("#grid-sort-relevance", Button).has_class("active")
|
||||
assert not app.screen.query_one("#grid-filter-docs", Button).has_class("active")
|
||||
|
||||
# Change filter to docs
|
||||
app.action_set_kind_filter("docs")
|
||||
await pilot.pause()
|
||||
assert app.screen.query_one("#grid-filter-docs", Button).has_class("active")
|
||||
assert not app.screen.query_one("#grid-filter-all", Button).has_class("active")
|
||||
|
||||
# Change sort to label_asc
|
||||
app.action_set_search_sort("label_asc")
|
||||
await pilot.pause()
|
||||
assert app.screen.query_one("#grid-sort-label_asc", Button).has_class("active")
|
||||
assert not app.screen.query_one("#grid-sort-relevance", Button).has_class("active")
|
||||
Reference in new issue
Block a user