From b0a2c3b4fd3ece53e9699a8aa7723b270e31e2b9 Mon Sep 17 00:00:00 2001 From: Nils Stinnesbeck Date: Tue, 20 Oct 2026 21:00:56 +0200 Subject: [PATCH] Library: the text filter only acts on the focused list Co-Authored-By: Claude Sonnet 5.5 --- SPECS.md | 2 +- internal/ui/help.go | 5 +++-- internal/ui/library.go | 16 +++++++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/SPECS.md b/SPECS.md index c39094a..476c3eb 100644 --- a/SPECS.md +++ b/SPECS.md @@ -67,7 +67,7 @@ Notes: ### 4.2 v0.2 (done, M8) -- **Library view:** movies (top) and series (below) with the details of the selected item on the right; Tab switches between the two lists. Filters (all, missing, downloaded, monitored, unmonitored), a file-property filter (`F`: quality 2160p/1080p/720p/SD, codec x265/x264/AV1, HDR/DV/SDR, audio format Atmos/TrueHD/DTS:X/DTS-HD/DTS/DD+/DD/AAC/FLAC/Opus, audio language; changes apply live to the list behind the dialog, including while moving through an open dropdown, and Esc restores the previous filter; each dropdown only lists options that, together with the other chosen criteria, still match at least one item (series files are read when the dialog opens so this is accurate)) and a text filter apply to both lists. Movie properties come from the movie list; series properties are read per series from `GET /episodefile?seriesId=` only while a property filter is active, cached while a series' file count and size stay the same, and a series matches when any of its files does. `t` toggles monitoring, `s` starts an automatic search, `i`/Enter opens the release search (movies) or the drill-down (series). +- **Library view:** movies (top) and series (below) with the details of the selected item on the right; Tab switches between the two lists. Filters (all, missing, downloaded, monitored, unmonitored), a file-property filter (`F`: quality 2160p/1080p/720p/SD, codec x265/x264/AV1, HDR/DV/SDR, audio format Atmos/TrueHD/DTS:X/DTS-HD/DTS/DD+/DD/AAC/FLAC/Opus, audio language; changes apply live to the list behind the dialog, including while moving through an open dropdown, and Esc restores the previous filter; each dropdown only lists options that, together with the other chosen criteria, still match at least one item (series files are read when the dialog opens so this is accurate)) apply to both lists; the text filter (`/`) only acts on the focused list, and each list keeps its own text. Movie properties come from the movie list; series properties are read per series from `GET /episodefile?seriesId=` only while a property filter is active, cached while a series' file count and size stay the same, and a series matches when any of its files does. `t` toggles monitoring, `s` starts an automatic search, `i`/Enter opens the release search (movies) or the drill-down (series). - **Series drill-down:** seasons and episodes, toggle monitoring per season/episode, trigger season/episode search and release search. ### 4.3 Later / ideas diff --git a/internal/ui/help.go b/internal/ui/help.go index 0ece690..d52a59b 100644 --- a/internal/ui/help.go +++ b/internal/ui/help.go @@ -21,14 +21,15 @@ const helpText = `[yellow]Global[-] [yellow]Library[-] Tab switch between the movies and series lists f cycle filter (all, missing, downloaded, monitored, unmonitored); - filters apply to both lists + the status and property filters apply to both lists F filter by file properties: quality, codec (x265, x264, AV1), HDR / DV / SDR, audio (Atmos, TrueHD, DTS, DD+ ...), language. Changes apply live behind the dialog, also while you move through an open list; Esc undoes them. Options that would leave no results are not offered. Series are read on demand. - / text filter (Enter or Esc returns to the list) + / text filter for the focused list only; each list keeps its + own text (Enter or Esc returns to the list) t toggle monitored s automatic search (series: all missing, asks first) i, Enter movie: release search; series: seasons and episodes diff --git a/internal/ui/library.go b/internal/ui/library.go index a6ab239..64e4ac4 100644 --- a/internal/ui/library.go +++ b/internal/ui/library.go @@ -182,7 +182,8 @@ type libPane struct { shown []libItem loaded bool err error - gen int // invalidates stale loads + gen int // invalidates stale loads + text string // text filter, set with / while this pane is the current one } // libraryView shows the movies of Radarr above the series of Sonarr, with the @@ -222,7 +223,7 @@ func newLibraryView(app *App, r *radarr.Client, s *sonarr.Client) *libraryView { v := &libraryView{ app: app, radarr: r, sonarr: s, header: tview.NewTextView().SetDynamicColors(true), - input: tview.NewInputField().SetLabel("Filter: ").SetFieldWidth(0), + input: tview.NewInputField().SetLabel("Filter Movies: ").SetFieldWidth(0), details: tview.NewTextView().SetDynamicColors(true).SetWordWrap(true), seriesFiles: map[int]seriesFiles{}, @@ -238,6 +239,8 @@ func newLibraryView(app *App, r *radarr.Client, s *sonarr.Client) *libraryView { p.table.SetInputCapture(v.handleKey) p.table.SetFocusFunc(func() { v.cur = kind + v.input.SetLabel("Filter " + kind.String() + ": ") + v.input.SetText(p.text) p.table.SetBorderColor(tcell.ColorYellow) v.renderDetails() }) @@ -252,7 +255,10 @@ func newLibraryView(app *App, r *radarr.Client, s *sonarr.Client) *libraryView { AddItem(lists, 0, 3, true). AddItem(v.details, 0, 2, false), 0, 1, true) - v.input.SetChangedFunc(func(string) { v.render() }) + v.input.SetChangedFunc(func(text string) { + v.pane().text = text // the text filter only acts on the current pane + v.render() + }) v.input.SetDoneFunc(func(tcell.Key) { v.app.app.SetFocus(v.pane().table) }) v.render() return v @@ -381,7 +387,7 @@ func (v *libraryView) renderPane(p *libPane) { if r, _ := p.table.GetSelection(); r >= 1 && r <= len(p.shown) { selID = p.shown[r-1].id } - p.shown = filterItems(p.all, v.filter, v.input.GetText(), v.attr) + p.shown = filterItems(p.all, v.filter, p.text, v.attr) p.table.Clear() header(p.table, 0, "Title", "Status", "Size") @@ -511,7 +517,7 @@ func (v *libraryView) openFilterDialog() { func (v *libraryView) filterPool() filterPool { var p filterPool for _, pane := range v.panes { - for _, it := range filterItems(pane.all, v.filter, v.input.GetText(), attrFilter{}) { + for _, it := range filterItems(pane.all, v.filter, pane.text, attrFilter{}) { if !it.attrsKnown { p.unknown = true }