Filter dialog: Language right after Quality

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 12:27:12 +01:00
co-authored by Claude Opus 5.5
parent 0fcc5fb7fa
commit 1015135a4b
4 changed files with 9 additions and 7 deletions
+2
View File
@@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- **Filter dialog** (Library and release list): Language comes right after Quality, so the two
most used filters are at the top.
- **Downloading episodes in a series' seasons and episodes:** like the Library, the drill-down - **Downloading episodes in a series' seasons and episodes:** like the Library, the drill-down
marks episodes in Sonarr's download queue with a blue dot and "downloading 45%" as their status marks episodes in Sonarr's download queue with a blue dot and "downloading 45%" as their status
(details: "Downloading 45% · 0:01:40 left"), and seasons with such episodes with a blue dot. (details: "Downloading 45% · 0:01:40 left"), and seasons with such episodes with a blue dot.
+2 -2
View File
@@ -57,7 +57,7 @@ Notes:
- **Config:** load URLs + API keys from a config file; fail with a clear message if missing or unreachable. - **Config:** load URLs + API keys from a config file; fail with a clear message if missing or unreachable.
- **Search:** a Movies view (Radarr) and a Series view (Sonarr), each with a search box. Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio. - **Search:** a Movies view (Radarr) and a Series view (Sonarr), each with a search box. Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio.
- **Add:** from a result, open a small form: quality profile, root folder, monitored (yes/no), "search now" (yes/no); for series also monitor mode (all / future / first season / none). Submit calls `POST /movie` or `POST /series` with `addOptions.searchForMovie` / `searchForMissingEpisodes`. - **Add:** from a result, open a small form: quality profile, root folder, monitored (yes/no), "search now" (yes/no); for series also monitor mode (all / future / first season / none). Submit calls `POST /movie` or `POST /series` with `addOptions.searchForMovie` / `searchForMissingEpisodes`.
- **Interactive release search:** for a movie (or a series' season/episode) in the library, list releases from indexers (`GET /release`) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (`POST /release`). Rejected releases can be grabbed only after an explicit confirmation. The list can be filtered by quality, codec (x265/x264/AV1), HDR/DV/SDR, audio format and language; releases carry no media info, so codec and HDR are read from the title, quality and custom formats (a release that does not name its codec never matches a codec filter). - **Interactive release search:** for a movie (or a series' season/episode) in the library, list releases from indexers (`GET /release`) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (`POST /release`). Rejected releases can be grabbed only after an explicit confirmation. The list can be filtered by quality, language, codec (x265/x264/AV1), HDR/DV/SDR and audio format; releases carry no media info, so codec and HDR are read from the title, quality and custom formats (a release that does not name its codec never matches a codec filter).
- **Downloads view:** all downloads and queues in one place, auto-refreshing every few seconds: - **Downloads view:** all downloads and queues in one place, auto-refreshing every few seconds:
- SABnzbd queue (name, size, progress, ETA, speed) and history (completed / failed, with retry). - SABnzbd queue (name, size, progress, ETA, speed) and history (completed / failed, with retry).
- Radarr and Sonarr queues (status, warnings, import errors), so stuck imports are visible. - Radarr and Sonarr queues (status, warnings, import errors), so stuck imports are visible.
@@ -124,7 +124,7 @@ Keybindings (the status bar shows the ones that apply to the current view):
| `/` | Movies, Series, Library: focus the search / text filter | | `/` | Movies, Series, Library: focus the search / text filter |
| `Enter` | Movies, Series: add the selected result (dialog: ↑↓ move, ←→ change dropdowns) | | `Enter` | Movies, Series: add the selected result (dialog: ↑↓ move, ←→ change dropdowns) |
| `i` | Movies, Series, Library: interactive release search (series: seasons and episodes) | | `i` | Movies, Series, Library: interactive release search (series: seasons and episodes) |
| `F` | Library and release list: filter by properties (quality, codec, HDR/DV, audio, language) | | `F` | Library and release list: filter by properties (quality, language, codec, HDR/DV, audio; in this order in the dialog) |
| `f` | Library: cycle the status filter; release list: hide rejected releases | | `f` | Library: cycle the status filter; release list: hide rejected releases |
| `[` `]` `t` `u` | Calendar: week back / forward, today, include unmonitored | | `[` `]` `t` `u` | Calendar: week back / forward, today, include unmonitored |
| `d` | Library: remove the item from the library (asks first) | | `d` | Library: remove the item from the library (asks first) |
+2 -2
View File
@@ -35,7 +35,7 @@ var helpSections = []helpSection{
{title: "Library", note: "The dot before a title: green when monitored, grey hollow when not, blue while it is in the Radarr/Sonarr download queue (the details say how far).", items: []helpItem{ {title: "Library", note: "The dot before a title: green when monitored, grey hollow when not, blue while it is in the Radarr/Sonarr download queue (the details say how far).", items: []helpItem{
{"Tab", "switch between the movies and series lists"}, {"Tab", "switch between the movies and series lists"},
{"f", "cycle the status filter (all, missing, downloaded, monitored, unmonitored); every list has its own filters"}, {"f", "cycle the status filter (all, missing, downloaded, monitored, unmonitored); every list has its own filters"},
{"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 files are read on demand."}, {"F", "filter by file properties: quality, language, codec (x265, x264, AV1), HDR / DV / SDR, audio (Atmos, TrueHD, DTS, DD+ ...). 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 files are read on demand."},
{"/", "text filter for the focused list only; each list keeps its own text (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"}, {"t", "toggle monitored"},
{"d", "remove from the library (files are kept unless you tick \"Delete files\")"}, {"d", "remove from the library (files are kept unless you tick \"Delete files\")"},
@@ -59,7 +59,7 @@ var helpSections = []helpSection{
{"Enter, g", "grab the selected release"}, {"Enter, g", "grab the selected release"},
{"s", "cycle the sort (best, size, age)"}, {"s", "cycle the sort (best, size, age)"},
{"f", "hide rejected releases"}, {"f", "hide rejected releases"},
{"F", "filter by quality, codec, HDR / DV, audio, language (read from the release title, quality and custom formats)"}, {"F", "filter by quality, language, codec, HDR / DV, audio (read from the release title, quality and custom formats)"},
}}, }},
{title: "Downloads", items: []helpItem{ {title: "Downloads", items: []helpItem{
{"p", "pause / resume the SABnzbd queue"}, {"p", "pause / resume the SABnzbd queue"},
+3 -3
View File
@@ -129,10 +129,10 @@ func staticOptions(o []string) func(filterPool) []string {
var facetDefs = []facet{ var facetDefs = []facet{
{"Quality", staticOptions(resOptions), func(f attrFilter) string { return f.res }, func(f attrFilter, v string) attrFilter { f.res = v; return f }, nil, nil}, {"Quality", staticOptions(resOptions), func(f attrFilter) string { return f.res }, func(f attrFilter, v string) attrFilter { f.res = v; return f }, nil, nil},
{"Language", filterPool.languages, func(f attrFilter) string { return f.lang }, func(f attrFilter, v string) attrFilter { f.lang = v; return f }, nil, nil},
{"Codec", staticOptions(codecOptions), func(f attrFilter) string { return f.codec }, func(f attrFilter, v string) attrFilter { f.codec = v; return f }, nil, nil}, {"Codec", staticOptions(codecOptions), func(f attrFilter) string { return f.codec }, func(f attrFilter, v string) attrFilter { f.codec = v; return f }, nil, nil},
{"HDR", staticOptions(hdrOptions), func(f attrFilter) string { return f.hdr }, func(f attrFilter, v string) attrFilter { f.hdr = v; return f }, nil, nil}, {"HDR", staticOptions(hdrOptions), func(f attrFilter) string { return f.hdr }, func(f attrFilter, v string) attrFilter { f.hdr = v; return f }, nil, nil},
{"Audio", staticOptions(audioOptions), func(f attrFilter) string { return f.audio }, func(f attrFilter, v string) attrFilter { f.audio = v; return f }, nil, nil}, {"Audio", staticOptions(audioOptions), func(f attrFilter) string { return f.audio }, func(f attrFilter, v string) attrFilter { f.audio = v; return f }, nil, nil},
{"Language", filterPool.languages, func(f attrFilter) string { return f.lang }, func(f attrFilter, v string) attrFilter { f.lang = v; return f }, nil, nil},
} }
// listedValues returns the options a facet offers: those that, together with // listedValues returns the options a facet offers: those that, together with
@@ -153,8 +153,8 @@ func listedValues(f facet, state attrFilter, pool filterPool) []string {
return vals return vals
} }
// openAttrFilter shows the dialog to pick quality, codec, HDR, audio and // openAttrFilter shows the dialog to pick quality, language, codec, HDR and
// language. Every change is passed to set immediately so the list behind the // audio (the most used first). Every change is passed to set immediately so the list behind the
// dialog follows along, also while moving through an open list; Cancel and Esc // dialog follows along, also while moving through an open list; Cancel and Esc
// restore cur. Entries that would leave no results are not offered. The // restore cur. Entries that would leave no results are not offered. The
// returned function re-evaluates them when the pool has changed; onClose runs // returned function re-evaluates them when the pool has changed; onClose runs