Library: status and property filters follow the focused list
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@ const helpText = `[yellow]Global[-]
|
||||
[yellow]Library[-]
|
||||
Tab switch between the movies and series lists
|
||||
f cycle filter (all, missing, downloaded, monitored, unmonitored);
|
||||
the status and property filters apply to both lists
|
||||
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
|
||||
|
||||
+48
-44
@@ -184,6 +184,9 @@ type libPane struct {
|
||||
err error
|
||||
gen int // invalidates stale loads
|
||||
text string // text filter, set with / while this pane is the current one
|
||||
// Each list has its own filters.
|
||||
filter int // status preset, index into libFilters
|
||||
attr attrFilter // file properties
|
||||
}
|
||||
|
||||
// libraryView shows the movies of Radarr above the series of Sonarr, with the
|
||||
@@ -200,9 +203,7 @@ type libraryView struct {
|
||||
panes [2]*libPane
|
||||
|
||||
// UI-goroutine state.
|
||||
cur libKind // the pane that has (or last had) focus
|
||||
filter int
|
||||
attr attrFilter
|
||||
cur libKind // the pane that has (or last had) focus
|
||||
|
||||
// File properties of series, fetched on demand and cached. An entry is
|
||||
// reused while the series' file count and size stay the same.
|
||||
@@ -276,7 +277,8 @@ func (v *libraryView) handleKey(ev *tcell.EventKey) *tcell.EventKey {
|
||||
case '/':
|
||||
v.app.app.SetFocus(v.input)
|
||||
case 'f':
|
||||
v.filter = (v.filter + 1) % len(libFilters)
|
||||
p := v.pane()
|
||||
p.filter = (p.filter + 1) % len(libFilters)
|
||||
v.render()
|
||||
case 'F':
|
||||
v.openFilterDialog()
|
||||
@@ -368,17 +370,7 @@ func (v *libraryView) render() {
|
||||
for _, p := range v.panes {
|
||||
v.renderPane(p)
|
||||
}
|
||||
filters := "[yellow]" + libFilters[v.filter].name + "[-]"
|
||||
if v.attr.active() {
|
||||
filters += " + [yellow]" + v.attr.String() + "[-]"
|
||||
if v.filePending > 0 {
|
||||
filters += fmt.Sprintf(" [gray](reading %d series…)[-]", v.filePending)
|
||||
}
|
||||
}
|
||||
v.header.SetText(fmt.Sprintf("[::b]Library[::-] · %s %d/%d · %s %d/%d · filter: %s",
|
||||
kindMovies, len(v.panes[kindMovies].shown), len(v.panes[kindMovies].all),
|
||||
kindSeries, len(v.panes[kindSeries].shown), len(v.panes[kindSeries].all),
|
||||
filters))
|
||||
v.header.SetText("[::b]Library[::-] · Tab switches list · each list has its own filters")
|
||||
v.renderDetails()
|
||||
}
|
||||
|
||||
@@ -387,7 +379,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, p.text, v.attr)
|
||||
p.shown = filterItems(p.all, p.filter, p.text, p.attr)
|
||||
|
||||
p.table.Clear()
|
||||
header(p.table, 0, "Title", "Status", "Size")
|
||||
@@ -413,11 +405,20 @@ func (v *libraryView) renderPane(p *libPane) {
|
||||
if selRow == 0 {
|
||||
p.table.ScrollToBeginning()
|
||||
}
|
||||
title := fmt.Sprintf(" %s ", p.kind)
|
||||
if p.err != nil && p.loaded {
|
||||
title = fmt.Sprintf(" %s [red](outdated)[-] ", p.kind)
|
||||
title := fmt.Sprintf(" %s %d/%d", p.kind, len(p.shown), len(p.all))
|
||||
if p.filter > 0 {
|
||||
title += " · [yellow]" + libFilters[p.filter].name + "[-]"
|
||||
}
|
||||
p.table.SetTitle(title)
|
||||
if p.attr.active() {
|
||||
title += " · [yellow]" + p.attr.String() + "[-]"
|
||||
if p.kind == kindSeries && v.filePending > 0 {
|
||||
title += fmt.Sprintf(" [gray](reading %d…)[-]", v.filePending)
|
||||
}
|
||||
}
|
||||
if p.err != nil && p.loaded {
|
||||
title += " [red](outdated)[-]"
|
||||
}
|
||||
p.table.SetTitle(title + " ")
|
||||
}
|
||||
|
||||
func (v *libraryView) renderDetails() {
|
||||
@@ -499,32 +500,35 @@ func (v *libraryView) run(doing, done string, fn func(ctx context.Context) error
|
||||
}()
|
||||
}
|
||||
|
||||
func (v *libraryView) setAttrFilter(f attrFilter) {
|
||||
v.attr = f
|
||||
v.render()
|
||||
v.loadSeriesFiles(false)
|
||||
}
|
||||
|
||||
// openFilterDialog opens the property filter. The files of all series are read
|
||||
// so the dialog can leave out options that would match nothing.
|
||||
// openFilterDialog opens the property filter for the focused list. For series
|
||||
// the files of all series are read so the dialog can leave out options that
|
||||
// would match nothing.
|
||||
func (v *libraryView) openFilterDialog() {
|
||||
v.refreshDialog = v.app.openAttrFilter(v.attr, v.filterPool, v.setAttrFilter, func() { v.refreshDialog = nil })
|
||||
v.loadSeriesFiles(true)
|
||||
}
|
||||
|
||||
// filterPool lists the files of the items that pass the status and text
|
||||
// filters, for the filter dialog.
|
||||
func (v *libraryView) filterPool() filterPool {
|
||||
var p filterPool
|
||||
for _, pane := range v.panes {
|
||||
for _, it := range filterItems(pane.all, v.filter, pane.text, attrFilter{}) {
|
||||
if !it.attrsKnown {
|
||||
p.unknown = true
|
||||
}
|
||||
p.files = append(p.files, it.attrs)
|
||||
p := v.pane()
|
||||
set := func(f attrFilter) {
|
||||
p.attr = f
|
||||
v.render()
|
||||
if p.kind == kindSeries {
|
||||
v.loadSeriesFiles(false)
|
||||
}
|
||||
}
|
||||
return p
|
||||
v.refreshDialog = v.app.openAttrFilter(p.attr, func() filterPool { return v.filterPool(p) }, set, func() { v.refreshDialog = nil })
|
||||
if p.kind == kindSeries {
|
||||
v.loadSeriesFiles(true)
|
||||
}
|
||||
}
|
||||
|
||||
// filterPool lists the files of the items of one list that pass its status
|
||||
// and text filters, for the filter dialog.
|
||||
func (v *libraryView) filterPool(p *libPane) filterPool {
|
||||
var pool filterPool
|
||||
for _, it := range filterItems(p.all, p.filter, p.text, attrFilter{}) {
|
||||
if !it.attrsKnown {
|
||||
pool.unknown = true
|
||||
}
|
||||
pool.files = append(pool.files, it.attrs)
|
||||
}
|
||||
return pool
|
||||
}
|
||||
|
||||
// withCachedFiles fills in the file properties of a series from the cache.
|
||||
@@ -543,7 +547,7 @@ func (v *libraryView) withCachedFiles(it libItem) libItem {
|
||||
// but only while a property filter is active or the filter dialog is open
|
||||
// (it costs one request per series).
|
||||
func (v *libraryView) loadSeriesFiles(force bool) {
|
||||
if !force && !v.attr.active() {
|
||||
if !force && !v.panes[kindSeries].attr.active() {
|
||||
return
|
||||
}
|
||||
p := v.panes[kindSeries]
|
||||
|
||||
Reference in New Issue
Block a user