Files
bjarneo--cliamp/ui/model/inline_overlays_nav.go
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

230 lines
6.5 KiB
Go

package model
import (
"fmt"
"strings"
"github.com/bjarneo/cliamp/ui"
)
// — provider browser (nav) —
type navViewKind int
const (
navViewMenu navViewKind = iota
navViewArtists
navViewAlbums
navViewTracks
)
// navView collapses the nav browser's mode + screen into the list actually
// shown, so the header, help, and body all agree.
func (m Model) navView() navViewKind {
switch m.navBrowser.mode {
case navBrowseModeByAlbum:
if m.navBrowser.screen == navBrowseScreenTracks {
return navViewTracks
}
return navViewAlbums
case navBrowseModeByArtist:
if m.navBrowser.screen == navBrowseScreenTracks {
return navViewTracks
}
return navViewArtists
case navBrowseModeByArtistAlbum:
switch m.navBrowser.screen {
case navBrowseScreenAlbums:
return navViewAlbums
case navBrowseScreenTracks:
return navViewTracks
default:
return navViewArtists
}
default:
return navViewMenu
}
}
func (m Model) navTrackBreadcrumb() string {
switch m.navBrowser.mode {
case navBrowseModeByArtist:
return "Artist: " + m.navBrowser.selArtist.Name
case navBrowseModeByAlbum:
return "Album: " + m.navBrowser.selAlbum.Name
case navBrowseModeByArtistAlbum:
return m.navBrowser.selArtist.Name + " / " + m.navBrowser.selAlbum.Name
}
return "Tracks"
}
func (m Model) navHeaderLine() string {
if m.navBrowser.searching {
return filterPromptHeader(m.navBrowser.search)
}
switch m.navView() {
case navViewArtists:
return sepHeaderN("Artists", m.navBrowser.cursor+1, len(m.navBrowser.artists))
case navViewAlbums:
label := "Albums"
if m.navBrowser.mode == navBrowseModeByArtistAlbum {
label = "Albums: " + m.navBrowser.selArtist.Name
} else if s := m.navSortLabel(m.navBrowser.sortType); s != "" {
label += " Sort: " + s
}
return sepHeaderN(label, m.navBrowser.cursor+1, len(m.navBrowser.albums))
case navViewTracks:
return sepHeaderN(m.navTrackBreadcrumb(), m.navBrowser.cursor+1, len(m.navBrowser.tracks))
default:
name := "Browse"
if m.navBrowser.prov != nil {
name = m.navBrowser.prov.Name()
}
return sepHeader(name)
}
}
func (m Model) navHelpLine() string {
if m.navBrowser.searching {
return helpKey("Enter", "Confirm ") + helpKey("Esc", "Cancel ") + helpKey("Type", "Filter")
}
switch m.navView() {
case navViewArtists:
return helpKey("←↓↑→", "Navigate ") + helpKey("Enter", "Open ") + helpKey("/", "Search")
case navViewAlbums:
h := helpKey("←↓↑→", "Navigate ") + helpKey("Enter", "Open ")
if m.navBrowser.mode == navBrowseModeByAlbum {
h += helpKey("s", "Sort ")
}
return h + helpKey("/", "Search")
case navViewTracks:
return helpKey("←↓↑→", "Navigate ") + helpKey("Enter", "Play from here ") +
helpKey("q", "Queue ") + helpKey("R", "Replace ") + helpKey("a", "Append ") + helpKey("/", "Search")
default:
return helpKey("↓↑", "Scroll ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
}
}
func (m Model) renderNavBody() string {
budget := m.effectivePlaylistVisible()
switch m.navView() {
case navViewArtists:
if m.navBrowser.loading && len(m.navBrowser.artists) == 0 {
return bodyLines([]string{loadingLine("Loading artists…")}, budget)
}
if len(m.navBrowser.artists) == 0 {
return bodyMessage("No artists found.", budget)
}
items := m.navScrollItems(len(m.navBrowser.artists), func(i int) string {
a := m.navBrowser.artists[i]
return truncate(fmt.Sprintf("%s (%d albums)", a.Name, a.AlbumCount), ui.PanelWidth-6)
})
return strings.Join(items, "\n")
case navViewAlbums:
if m.navBrowser.loading && len(m.navBrowser.albums) == 0 {
return bodyLines([]string{loadingLine("Loading albums…")}, budget)
}
if len(m.navBrowser.albums) == 0 {
return bodyMessage("No albums found.", budget)
}
items := m.navScrollItems(len(m.navBrowser.albums), func(i int) string {
a := m.navBrowser.albums[i]
if a.Year > 0 {
return truncate(fmt.Sprintf("%s — %s (%d)", a.Name, a.Artist, a.Year), ui.PanelWidth-6)
}
return truncate(fmt.Sprintf("%s — %s", a.Name, a.Artist), ui.PanelWidth-6)
})
return strings.Join(items, "\n")
case navViewTracks:
return m.renderNavTrackBody(budget)
default:
items := []string{"By Album", "By Artist", "By Artist / Album"}
return windowList(items, m.navBrowser.cursor, 0, budget)
}
}
func (m Model) renderNavTrackBody(budget int) string {
if m.navBrowser.loading && len(m.navBrowser.tracks) == 0 {
return bodyLines([]string{loadingLine("Loading tracks…")}, budget)
}
if len(m.navBrowser.tracks) == 0 {
return bodyMessage("No tracks found.", budget)
}
if len(m.navBrowser.searchIdx) > 0 || m.navBrowser.search != "" {
items := m.navScrollItems(len(m.navBrowser.tracks), func(i int) string {
t := m.navBrowser.tracks[i]
return formatTrackRow(i+1, t.DisplayName()+trackAlbumSuffix(t, m.showAlbumHeaders), t.DurationSecs)
})
return strings.Join(items, "\n")
}
return m.renderTrackRowsBody(m.navBrowser.tracks, m.navBrowser.cursor, m.navBrowser.scroll, budget)
}
// — file browser —
func (m Model) fbHeaderLine() string {
if m.fileBrowser.searching {
return filterPromptHeader(m.fileBrowser.search)
}
label := "Files: " + m.fileBrowser.dir
if n := len(m.fileBrowser.selected); n > 0 {
label += fmt.Sprintf(" [%d selected]", n)
}
return sepHeader(label)
}
func (m Model) renderFileBrowserBody() string {
budget := m.effectivePlaylistVisible()
if budget <= 0 {
return ""
}
var lines []string
if m.fileBrowser.err != "" {
lines = append(lines, errorStyle.Render(" "+m.fileBrowser.err))
}
count := m.fbCount()
if count == 0 {
if m.fileBrowser.search != "" {
lines = append(lines, dimStyle.Render(" No matches"))
} else {
lines = append(lines, dimStyle.Render(" (empty)"))
}
return bodyLines(lines, budget)
}
scroll := max(m.fileBrowser.scroll, 0)
if scroll > count-1 {
scroll = max(0, count-1)
}
for i := scroll; i < count && len(lines) < budget; i++ {
e := m.fbEntry(i)
check := " "
if m.fileBrowser.selected[e.path] {
check = "✓ "
}
suffix := ""
if e.isAudio {
suffix = " ♫"
}
label := truncate(check+e.name+suffix, max(1, ui.PanelWidth-2))
switch {
case m.fileBrowser.searching:
lines = append(lines, dimStyle.Render(" "+label))
case i == m.fileBrowser.cursor:
lines = append(lines, playlistSelectedStyle.Render("> "+label))
case e.isDir:
lines = append(lines, trackStyle.Render(" "+label))
case e.isAudio:
lines = append(lines, playlistItemStyle.Render(" "+label))
default:
lines = append(lines, dimStyle.Render(" "+label))
}
}
return bodyLines(lines, budget)
}