Add the calendar view (M10)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 22:39:39 +02:00
co-authored by Claude Sonnet 5.5
parent 7582504268
commit cd5786ba78
13 changed files with 756 additions and 30 deletions
+41 -7
View File
@@ -41,6 +41,8 @@ type App struct {
overlays []overlay // dialogs and full-screen views stacked above the pages
services *tview.TextView
topBar *tview.Flex // tab bar and connection indicators
narrow bool // terminal too narrow for full service names
state map[string]serviceState // keyed by service name
probes []probe
@@ -79,7 +81,7 @@ type overlay struct {
page string
focus tview.Primitive
hints string
full bool // full-screen view (can be left with F1-F5), as opposed to a dialog
full bool // full-screen view (can be left with F1-F6), as opposed to a dialog
close func() // optional cleanup when the overlay is removed
}
@@ -117,6 +119,7 @@ func New(cfg *config.Config) *App {
}},
{"SABnzbd", sabClient.Version},
}
calendar := newCalendarView(a, radarrClient, sonarrClient)
library := newLibraryView(a, radarrClient, sonarrClient)
downloads := newDownloadsView(a, sabClient,
[]arrSource{
@@ -129,16 +132,18 @@ func New(cfg *config.Config) *App {
{key: '2', name: "Series", body: newSearchView(a, sonarrBackend{sonarrClient}, cfg.Sonarr).root, hints: searchHints},
{key: '3', name: "Library", body: library.root, onShow: library.activate, hints: libraryHints},
{key: '4', name: "Downloads", body: downloads.root, onShow: downloads.activate, onHide: downloads.deactivate, hints: downloads.hints},
{key: '5', name: "Log", body: a.logs.root, onShow: a.logs.onShow, hints: logHints},
{key: '5', name: "Calendar", body: calendar.root, onShow: calendar.activate, hints: calendarHints},
{key: '6', name: "Log", body: a.logs.root, onShow: a.logs.onShow, hints: logHints},
}
for i, v := range a.views {
a.pages.AddPage(v.name, v.body, true, i == 0)
}
a.topBar = tview.NewFlex().
AddItem(a.tabs, 0, 1, false).
AddItem(a.services, servicesWidth(false), 0, false)
layout := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(tview.NewFlex().
AddItem(a.tabs, 0, 1, false).
AddItem(a.services, 25, 0, false), 1, 0, false).
AddItem(a.topBar, 1, 0, false).
AddItem(a.pages, 0, 1, true).
AddItem(a.status, 1, 0, false)
@@ -171,10 +176,35 @@ func (a *App) SetService(name, version string, err error) {
a.renderServices()
}
// servicesWidth is the width of the connection indicators.
func servicesWidth(narrow bool) int {
if narrow {
return 14
}
return 25
}
// setNarrow switches the indicators between full names and initials; called
// from the draw handler when the terminal width crosses the threshold.
func (a *App) setNarrow(narrow bool) {
if narrow == a.narrow {
return
}
a.narrow = narrow
a.topBar.ResizeItem(a.services, servicesWidth(narrow), 0)
a.renderServices()
}
func (a *App) renderServices() {
var s string
for _, n := range serviceNames {
short := strings.TrimSuffix(n, "nzbd") // SABnzbd -> SAB, keeps the bar narrow
if a.narrow {
short = n[:1]
if n == "SABnzbd" {
short = "SAB"
}
}
st := a.state[n]
switch {
case !st.known:
@@ -254,7 +284,7 @@ func (a *App) renderStatus() {
if h := a.views[a.active].hints; h != nil {
hints = h() + " · "
}
hints += "? help · F1-F5 · q quit"
hints += "? help · F1-F6 · q quit"
}
a.status.SetText("[gray]" + hints)
}
@@ -381,7 +411,7 @@ func (a *App) handleKey(ev *tcell.EventKey) *tcell.EventKey {
return ev
}
// viewShortcut returns the view index for F1-F5, or -1.
// viewShortcut returns the view index for F1-F6, or -1.
func (a *App) viewShortcut(ev *tcell.EventKey) int {
if ev.Key() >= tcell.KeyF1 && ev.Key() <= tcell.KeyF12 {
return int(ev.Key() - tcell.KeyF1)
@@ -422,4 +452,8 @@ func libraryHints() string {
return "Tab list · f status · F props · / text · t monitor · s search · d remove"
}
func calendarHints() string {
return "[ ] week · t today · u unmonitored · Enter releases · s search · r reload"
}
func logHints() string { return "↑↓ scroll · G follow · c clear" }