Library: keep the place after removing a title

The list is reloaded after a removal and the selection follows the
selected title by id; the removed title is gone, so it fell back to the
top. Now a title that has left the library hands its row to the one that
moved up into it (or the new last one). A title that is only filtered out
still starts the list from the top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
2026-10-21 12:35:49 +02:00
co-authored by Claude Opus 5.5
parent b49bb1ef31
commit 12e5d5aa33
3 changed files with 68 additions and 2 deletions
+2
View File
@@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- **Library:** after removing a title the selection stays at its place (on the title that moved
up into it, or the new last one) instead of jumping to the top of the list.
- **Config (F6):** the default quality profile and root folder are chosen from the lists Radarr - **Config (F6):** the default quality profile and root folder are chosen from the lists Radarr
and Sonarr have, loaded with the address and API key in the fields (when the view opens, and and Sonarr have, loaded with the address and API key in the fields (when the view opens, and
again shortly after either is changed, before saving). A configured value the server does not again shortly after either is changed, before saving). A configured value the server does not
+18 -2
View File
@@ -391,9 +391,9 @@ func (v *libraryView) render() {
} }
func (v *libraryView) renderPane(p *libPane) { func (v *libraryView) renderPane(p *libPane) {
selID := -1 selID, prevRow := -1, 0
if r, _ := p.table.GetSelection(); r >= 1 && r <= len(p.shown) { if r, _ := p.table.GetSelection(); r >= 1 && r <= len(p.shown) {
selID = p.shown[r-1].id selID, prevRow = p.shown[r-1].id, r
} }
p.shown = filterItems(p.all, p.filter, p.text, p.attr) p.shown = filterItems(p.all, p.filter, p.text, p.attr)
@@ -408,6 +408,13 @@ func (v *libraryView) renderPane(p *libPane) {
selRow = i + 1 selRow = i + 1
} }
} }
if selRow == 0 && selID != -1 && !containsItem(p.all, selID) {
// The selected title is gone from the library (removed): stay at its
// place, on the title that moved up into it (or the new last one),
// instead of jumping to the top. A title that is only filtered out
// still starts the list from the top.
selRow = min(prevRow, len(p.shown))
}
placeholder := "Nothing matches the filter" placeholder := "Nothing matches the filter"
switch { switch {
case p.err != nil && !p.loaded: case p.err != nil && !p.loaded:
@@ -437,6 +444,15 @@ func (v *libraryView) renderPane(p *libPane) {
p.table.SetTitle(title + " ") p.table.SetTitle(title + " ")
} }
func containsItem(items []libItem, id int) bool {
for _, it := range items {
if it.id == id {
return true
}
}
return false
}
func (v *libraryView) renderDetails() { func (v *libraryView) renderDetails() {
if it, ok := v.selected(); ok { if it, ok := v.selected(); ok {
v.details.Show(it.details, it.cover) v.details.Show(it.details, it.cover)
+48
View File
@@ -209,3 +209,51 @@ func TestListedValues(t *testing.T) {
t.Errorf("empty pool: %v", got) t.Errorf("empty pool: %v", got)
} }
} }
func TestRemovingATitleKeepsThePlace(t *testing.T) {
a := newTestApp(t)
v := newLibraryView(a, a.radarr, a.sonarr)
p := v.panes[0]
items := func(ids ...int) []libItem {
var out []libItem
for _, id := range ids {
out = append(out, libItem{id: id, title: string(rune('A' + id)), label: string(rune('A' + id))})
}
return out
}
selectedID := func() int {
r, _ := p.table.GetSelection()
return p.shown[r-1].id
}
p.all, p.loaded = items(1, 2, 3, 4, 5), true
v.renderPane(p)
// Remove the third title: the fourth moves up into its place and is selected.
p.table.Select(3, 0)
p.all = items(1, 2, 4, 5)
v.renderPane(p)
if r, _ := p.table.GetSelection(); r != 3 || selectedID() != 4 {
t.Errorf("after removing the third: row %d, title %d", r, selectedID())
}
// Remove the last title: the new last one is selected.
p.table.Select(4, 0)
p.all = items(1, 2, 4)
v.renderPane(p)
if r, _ := p.table.GetSelection(); r != 3 || selectedID() != 4 {
t.Errorf("after removing the last: row %d, title %d", r, selectedID())
}
// A reload that keeps the title keeps it selected, wherever it is now.
p.table.Select(2, 0) // title 2
p.all = items(0, 1, 2, 4)
v.renderPane(p)
if selectedID() != 2 {
t.Errorf("kept title not followed: %d", selectedID())
}
// A title that is only filtered out still starts the list from the top.
p.table.Select(4, 0) // title 4
p.text = "B" // title 1 only: 4 is filtered out, not removed
v.renderPane(p)
if r, _ := p.table.GetSelection(); r != 1 {
t.Errorf("filtered out: row %d, want the top", r)
}
}