Terminal Jellyfin client, watch-side companion to TUISTREAM. - LAN auto-discovery (broadcast + unicast subnet sweep) - mpv-window video playback over JSON IPC (pause/seek/progress/resume) - headless audio playback with in-terminal now-playing screen - volume control with remembered level, mute - audio auto-advance + n/p track skip through the album queue - Omarchy-themed UI with Nerd Font (nf-md) icons - cliamp Jellyfin provider auto-config on login - pacman dependency-installing installer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2 KiB
Go
67 lines
2 KiB
Go
// Shared screen chrome: a header rule (app name + server) and a footer rule
|
|
// (status + help) used by the browse and playing screens so they feel like one
|
|
// app rather than three separate views.
|
|
package tui
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// hostLabel extracts a friendly host from a server URL — no scheme or port, and
|
|
// a trailing ".local" trimmed (so "http://omabox.local:8096" → "omabox").
|
|
func hostLabel(raw string) string {
|
|
u, err := url.Parse(raw)
|
|
if err != nil || u.Host == "" {
|
|
return raw
|
|
}
|
|
return strings.TrimSuffix(u.Hostname(), ".local")
|
|
}
|
|
|
|
// serverLabel prefers a discovered server's advertised name (matched by URL),
|
|
// otherwise falls back to the host portion of the configured URL.
|
|
func (m Model) serverLabel() string {
|
|
for _, s := range m.discovered {
|
|
if s.Address == m.cfg.ServerURL && s.Name != "" {
|
|
return s.Name
|
|
}
|
|
}
|
|
return hostLabel(m.cfg.ServerURL)
|
|
}
|
|
|
|
// frameWidth returns the usable width, with a sane fallback before the first
|
|
// WindowSizeMsg arrives.
|
|
func (m Model) frameWidth() int {
|
|
if m.width <= 0 {
|
|
return 80
|
|
}
|
|
return m.width
|
|
}
|
|
|
|
// headerBar renders the top rule: " tsplay ───────────────── server ".
|
|
func (m Model) headerBar() string {
|
|
title := m.st.title.Render(" tsplay ")
|
|
srv := ""
|
|
if s := m.serverLabel(); s != "" {
|
|
srv = m.st.muted.Render(" " + s + " ")
|
|
}
|
|
dash := m.frameWidth() - lipgloss.Width(title) - lipgloss.Width(srv)
|
|
if dash < 1 {
|
|
dash = 1
|
|
}
|
|
return title + m.st.barEmpty.Render(strings.Repeat("─", dash)) + srv
|
|
}
|
|
|
|
// footerBar renders a thin rule, an optional accent status line, then the help
|
|
// keys — the consistent bottom of every framed screen.
|
|
func (m Model) footerBar(help, status string) string {
|
|
rule := m.st.barEmpty.Render(strings.Repeat("─", m.frameWidth()))
|
|
lines := []string{rule}
|
|
if status != "" {
|
|
lines = append(lines, " "+m.st.accent.Render(status))
|
|
}
|
|
lines = append(lines, " "+m.st.help.Render(help))
|
|
return strings.Join(lines, "\n")
|
|
}
|