Remove titles from the library with a confirmation (M10)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 22:36:19 +02:00
co-authored by Claude Sonnet 5.5
parent ebbb3cd178
commit 7582504268
11 changed files with 174 additions and 5 deletions
+1 -1
View File
@@ -419,7 +419,7 @@ func searchHints() string {
}
func libraryHints() string {
return "Tab list · f status · F file props · / text · t monitor · s search"
return "Tab list · f status · F props · / text · t monitor · s search · d remove"
}
func logHints() string { return "↑↓ scroll · G follow · c clear" }
+2
View File
@@ -31,6 +31,8 @@ const helpText = `[yellow]Global[-]
/ text filter for the focused list only; each list keeps its
own text (Enter or Esc returns to the list)
t toggle monitored
d remove from the library (files are kept unless you tick
"Delete files")
s automatic search (series: all missing, asks first)
i, Enter movie: release search; series: seasons and episodes
r reload
+66
View File
@@ -0,0 +1,66 @@
package ui
import (
"context"
"github.com/rivo/tview"
)
// confirmDelete asks before removing a title from the library. Deleting the
// files is a separate, unticked choice: by default only the library entry goes.
func (a *App) confirmDelete(label, summary string, onDelete func(deleteFiles, exclude bool)) {
box, _ := a.newDeleteDialog(label, summary, onDelete)
a.openModal(box, 66, 14)
}
func (a *App) newDeleteDialog(label, summary string, onDelete func(deleteFiles, exclude bool)) (*tview.Flex, *tview.Form) {
const (
lFiles = "Delete files from disk"
lExclude = "Don't let import lists re-add it"
)
text := tview.NewTextView().SetDynamicColors(true).SetWordWrap(true).
SetText("[::b]" + label + "[::-]\n" + summary)
form := tview.NewForm()
form.AddCheckbox(lFiles, false, nil)
form.AddCheckbox(lExclude, false, nil)
files := form.GetFormItemByLabel(lFiles).(*tview.Checkbox)
exclude := form.GetFormItemByLabel(lExclude).(*tview.Checkbox)
// Cancel first so a stray Enter is safe.
form.AddButton("Cancel", a.closeModal)
form.AddButton("Delete", func() {
a.closeModal()
onDelete(files.IsChecked(), exclude.IsChecked())
})
form.SetFocus(form.GetFormItemCount()) // the Cancel button
arrowNavigation(form, a.closeModal, nil)
box := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(text, 4, 0, false).
AddItem(form, 0, 1, true)
box.SetBorder(true).SetTitle(" Remove from library ")
return box, form
}
// remove asks to delete the selected library item.
func (v *libraryView) remove() {
it, ok := v.selected()
if !ok {
return
}
summary := "[gray]Untick “Delete files” to keep the files on disk.[-]"
if it.have {
summary = "[yellow]" + it.size + " on disk.[-] Files are kept unless you tick “Delete files”."
}
v.app.confirmDelete(it.label, summary, func(deleteFiles, exclude bool) {
what := "Removed " + it.label
if deleteFiles {
what = "Removed " + it.label + " and its files"
}
v.run("Removing "+it.label, what, func(ctx context.Context) error {
if it.movie != nil {
return v.radarr.DeleteMovie(ctx, it.id, deleteFiles, exclude)
}
return v.sonarr.DeleteSeries(ctx, it.id, deleteFiles, exclude)
}, v.reload)
})
}
+46
View File
@@ -0,0 +1,46 @@
package ui
import (
"testing"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func TestDeleteDialogDefaultsAreSafe(t *testing.T) {
a := newTestApp(t)
var calls [][2]bool
_, form := a.newDeleteDialog("Heat (1995)", "3 GB on disk", func(files, exclude bool) { calls = append(calls, [2]bool{files, exclude}) })
var focus func(p tview.Primitive)
focus = func(p tview.Primitive) { p.Focus(focus) }
form.Focus(focus)
press := func(k tcell.Key) { form.InputHandler()(tcell.NewEventKey(k, 0, 0), focus) }
space := func() { form.InputHandler()(tcell.NewEventKey(tcell.KeyRune, ' ', 0), focus) }
// Both boxes start unticked and the focus starts on Cancel: a stray Enter does nothing.
press(tcell.KeyEnter)
if len(calls) != 0 {
t.Fatalf("Enter on the initial focus deleted: %v", calls)
}
// Moving to Delete and confirming removes the entry only.
form.SetFocus(form.GetFormItemCount())
press(tcell.KeyRight)
press(tcell.KeyEnter)
if len(calls) != 1 || calls[0] != [2]bool{false, false} {
t.Fatalf("calls = %v, want one call without files and exclusion", calls)
}
// Ticking the boxes is passed on.
form.SetFocus(0)
space() // delete files
press(tcell.KeyDown)
space() // exclusion
press(tcell.KeyDown)
press(tcell.KeyDown) // Delete button
press(tcell.KeyEnter)
if len(calls) != 2 || calls[1] != [2]bool{true, true} {
t.Errorf("calls = %v, want the second with both ticked", calls)
}
}
+2
View File
@@ -293,6 +293,8 @@ func (v *libraryView) handleKey(ev *tcell.EventKey) *tcell.EventKey {
v.search()
case 't':
v.toggleMonitored()
case 'd':
v.remove()
default:
return ev
}