With an empty search box, Movies and Series show one of Trakt's public lists: trending now, most watched this week, anticipated, popular, and for movies the US box office. [ ] switch lists, r reloads. Each entry is looked up in Radarr (tmdb:) or Sonarr (tvdb:), so it behaves like a search result: details, poster, tick when in the library, add, releases. Needs a Trakt client ID: new optional trakt.client_id setting (Config view section "Trakt", ARR_TUI_TRAKT_CLIENT_ID), masked like the API keys and checked by Test. ARR_TUI_TRAKT_URL points at another server for development. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
152 lines
6.2 KiB
Go
152 lines
6.2 KiB
Go
package ui
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/rivo/tview"
|
|
)
|
|
|
|
// helpItem is one line of the key reference: the keys and what they do.
|
|
type helpItem struct{ keys, desc string }
|
|
|
|
// helpSection is a titled group of items, with an optional note above them.
|
|
type helpSection struct {
|
|
title string
|
|
note string
|
|
items []helpItem
|
|
}
|
|
|
|
var helpSections = []helpSection{
|
|
{title: "Global", items: []helpItem{
|
|
{"F1-F7", "switch view (also from the release and episode lists)"},
|
|
{"Tab, Shift+Tab", "switch panel within a view"},
|
|
{"F9", "this help (works everywhere, also while typing)"},
|
|
{"Ctrl+C", "quit"},
|
|
{"Esc", "close a dialog / go back"},
|
|
}},
|
|
{title: "Movies and Series", items: []helpItem{
|
|
{"/", "focus the search box"},
|
|
{"Enter, a", "add the selected title (dialog: ↑↓ move, ←→ change a dropdown, Space toggles, Enter/Space opens a list)"},
|
|
{"i", "interactive release search (library titles)"},
|
|
{"[ ]", "with an empty search box: previous / next Trakt list (trending now, most watched this week, anticipated, popular; movies also the US box office); needs a Trakt client ID (Config view)"},
|
|
{"r", "reload the Trakt list"},
|
|
}},
|
|
{title: "Library", items: []helpItem{
|
|
{"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", "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."},
|
|
{"/", "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"},
|
|
}},
|
|
{title: "Calendar (F5)", items: []helpItem{
|
|
{"[ ]", "move the two-week window back / forward by a week"},
|
|
{"t", "back to today"},
|
|
{"u", "include unmonitored items"},
|
|
{"Enter, i", "release search for the selected movie / episode"},
|
|
{"s", "automatic search"},
|
|
{"r", "reload"},
|
|
}},
|
|
{title: "Seasons and episodes", items: []helpItem{
|
|
{"Enter", "season: expand / collapse; episode: release search"},
|
|
{"i, s, t", "release search, automatic search, toggle monitored (on a season they apply to the whole season)"},
|
|
}},
|
|
{title: "Release list", items: []helpItem{
|
|
{"Enter, g", "grab the selected release"},
|
|
{"s", "cycle the sort (best, size, age)"},
|
|
{"f", "hide rejected releases"},
|
|
{"F", "filter by quality, codec, HDR / DV, audio, language (read from the release title, quality and custom formats)"},
|
|
}},
|
|
{title: "Downloads", items: []helpItem{
|
|
{"p", "pause / resume the SABnzbd queue"},
|
|
{"d", "delete the selected job or entry"},
|
|
{"b", "remove from the Radarr/Sonarr queue and blocklist"},
|
|
{"t", "retry a failed download (history)"},
|
|
{"r", "refresh now"},
|
|
}},
|
|
{title: "Config (F6)", note: "Edit the settings of the config file: service addresses and API keys, add-form defaults, refresh interval, posters, theme, notifications.", items: []helpItem{
|
|
{"↑↓", "move through all fields, one below the other, in the sections Radarr, Sonarr, SABnzbd, Trakt and Interface; ↓ after the last field reaches Save and Revert"},
|
|
{"Tab", "jump between the fields and Save / Revert"},
|
|
{"Space", "toggle a checkbox; press a button"},
|
|
{"←→, Enter", "the default quality profile and root folder: pick from the lists the server has (loaded with the address and key in the fields)"},
|
|
{"Test", "(button below the service settings) checks the services and Trakt with the values typed in"},
|
|
{"Save, Revert", "(at the bottom) Save writes the file and applies everything at once; Revert discards edits"},
|
|
}},
|
|
{title: "Log (F7)", note: "Popups fade away by themselves; everything they said is kept here.", items: []helpItem{
|
|
{"↑↓, PgUp, PgDn", "scroll"},
|
|
{"G", "follow new entries"},
|
|
{"c", "clear the log"},
|
|
}},
|
|
}
|
|
|
|
const helpKeyWidth = 16 // columns for the keys, before the descriptions
|
|
|
|
// renderHelp lays the key reference out for a text area of the given width:
|
|
// descriptions that do not fit wrap under themselves, not under the keys.
|
|
func renderHelp(width int) string {
|
|
var b strings.Builder
|
|
descWidth := max(width-2-helpKeyWidth, 20)
|
|
for i, sec := range helpSections {
|
|
if i > 0 {
|
|
b.WriteByte('\n')
|
|
}
|
|
b.WriteString("[yellow]" + sec.title + "[-]\n")
|
|
if sec.note != "" {
|
|
for _, l := range flowWrap(tview.Escape(sec.note), width-2, width-2, 0) {
|
|
b.WriteString(" " + l + "\n")
|
|
}
|
|
}
|
|
for _, it := range sec.items {
|
|
keys := tview.Escape(it.keys)
|
|
pad := strings.Repeat(" ", max(helpKeyWidth-tview.TaggedStringWidth(keys), 1))
|
|
lines := flowWrap(tview.Escape(it.desc), descWidth, descWidth, 0)
|
|
for j, l := range lines {
|
|
if j == 0 {
|
|
b.WriteString(" " + keys + pad + l + "\n")
|
|
} else {
|
|
b.WriteString(strings.Repeat(" ", 2+helpKeyWidth) + l + "\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// showHelp opens the key reference together with the connection state.
|
|
func (a *App) showHelp() {
|
|
screenW, screenH := 100, 40
|
|
if a.screen != nil {
|
|
screenW, screenH = a.screen.Size()
|
|
}
|
|
width := min(100, screenW-4)
|
|
textWidth := width - 3 // border and a column of air
|
|
text := renderHelp(textWidth) + "\n[yellow]Connections[-]\n"
|
|
for _, l := range strings.Split(strings.TrimRight(a.serviceSummary(), "\n"), "\n") {
|
|
for j, w := range flowWrap(l, textWidth-2, textWidth-2, 0) {
|
|
if j == 0 {
|
|
text += " " + w + "\n"
|
|
} else {
|
|
text += " " + w + "\n"
|
|
}
|
|
}
|
|
}
|
|
|
|
tv := tview.NewTextView().SetDynamicColors(true).SetScrollable(true).SetWrap(false)
|
|
tv.SetBorder(true).SetTitle(" Help — Esc to close ")
|
|
tv.SetBorderPadding(0, 0, 1, 0)
|
|
tv.SetText(text)
|
|
tv.SetInputCapture(func(ev *tcell.EventKey) *tcell.EventKey {
|
|
if ev.Key() == tcell.KeyEsc {
|
|
a.closeModal()
|
|
return nil
|
|
}
|
|
return ev
|
|
})
|
|
height := min(screenH-2, strings.Count(text, "\n")+2)
|
|
a.openModalHints(tv, width, height, "↑↓ PgUp PgDn scroll · Esc close")
|
|
}
|