TUISTREAM/internal/tui/styles.go
28allday 959e507a83 Initial commit: TUISTREAM — headless Jellyfin TUI for Omarchy/Arch
Three-tab Bubble Tea TUI (Setup / Manage / Monitor) for setting up and
running a headless Jellyfin server over SSH:

- Setup: install/uninstall Jellyfin, add media drives (single or btrfs
  RAID pool), import an existing detached btrfs pool non-destructively,
  firewall, move Jellyfin storage onto a media drive
- Boot-drive-safe drive classifier (walks LUKS/LVM/RAID to the physical
  disk; never offers a disk hosting /, /boot or swap)
- Keep-existing-filesystem path previews on-disk content, skips starter
  folders when content exists, and grants Jellyfin recursive read access
- Manage: copy from external drives, mount/eject, rename/delete
- Monitor: per-core CPU, load/mem/uptime, btrfs error counters, SMART

Single static binary; installs to /usr/local/bin so sudo can find it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:47:18 +01:00

114 lines
3.8 KiB
Go

package tui
import (
"github.com/charmbracelet/lipgloss"
"tuistream/internal/theme"
)
// centered horizontally centres every line of a block within width w. Each
// line is centred independently.
func centered(s string, w int) string {
return lipgloss.NewStyle().Width(w).Align(lipgloss.Center).Render(s)
}
// centeredFrame renders content inside a bordered card with every line centred
// within the card interior.
func centeredFrame(content string) string {
return frameStyle.Align(lipgloss.Center).Render(content)
}
// centeredCard is centeredFrame's equivalent for the padded modal card style
// (confirms, pickers, progress splashes).
func centeredCard(content string) string {
return cardStyle.Align(lipgloss.Center).Render(content)
}
// Colours and styles, (re)built from the active Omarchy theme by applyTheme.
// Mirrors omarchy-send's style block so the two sibling apps look related.
var (
accent lipgloss.Color
text lipgloss.Color
dim lipgloss.Color
muted lipgloss.Color
good lipgloss.Color
bad lipgloss.Color
titleBarStyle lipgloss.Style
tabActiveStyle lipgloss.Style
tabInactiveStyle lipgloss.Style
frameStyle lipgloss.Style
cardStyle lipgloss.Style
footerStyle lipgloss.Style
titleStyle lipgloss.Style
headerStyle lipgloss.Style
labelStyle lipgloss.Style
valueStyle lipgloss.Style
roleSystemStyle lipgloss.Style // boot/OS disk — off-limits
roleAvailableStyle lipgloss.Style // ready to be picked
roleInUseStyle lipgloss.Style // already mounted somewhere
devNameStyle lipgloss.Style // /dev/... in inventory rows
)
func init() { applyTheme(theme.Default()) }
// cardWidth picks a consistent width for every card/frame the TUI draws.
// The cap (88) keeps content as a comfortably-centred column with balanced
// margins instead of filling a wide terminal edge-to-edge (which reads as
// left-bunched once the lines inside are left-aligned). The floor (40) keeps
// narrow terminals readable rather than broken. The -8 budget leaves at least
// a small margin on each side so the block always looks centred, never flush.
func cardWidth(termWidth int) int {
w := termWidth - 8
if w > 88 {
w = 88
}
if w < 40 {
w = 40
}
return w
}
// applyWidth rebuilds the width-sensitive style values for the current
// terminal width. Called once per render so all cards / frames have the
// same width, regardless of which renderer drew them.
func applyWidth(termWidth int) {
w := cardWidth(termWidth)
cardStyle = cardStyle.Width(w)
frameStyle = frameStyle.Width(w)
}
func applyTheme(t theme.Theme) {
accent = lipgloss.Color(t.Accent)
text = lipgloss.Color(t.Fg)
bg := lipgloss.Color(t.Bg)
dim = lipgloss.Color(t.Dim)
muted = lipgloss.Color(t.Muted)
good = lipgloss.Color(t.Good)
bad = lipgloss.Color(t.Bad)
titleBarStyle = lipgloss.NewStyle().Bold(true).Foreground(bg).Background(accent)
tabActiveStyle = lipgloss.NewStyle().Bold(true).Foreground(bg).Background(accent).Padding(0, 2)
tabInactiveStyle = lipgloss.NewStyle().Foreground(dim).Padding(0, 2)
frameStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(accent).
Padding(0, 1)
cardStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(accent).
Padding(1, 2)
footerStyle = lipgloss.NewStyle().Foreground(muted).Padding(0, 1)
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(accent)
headerStyle = lipgloss.NewStyle().Foreground(dim)
labelStyle = lipgloss.NewStyle().Foreground(dim).Width(14)
valueStyle = lipgloss.NewStyle().Foreground(text)
roleSystemStyle = lipgloss.NewStyle().Foreground(bad).Bold(true)
roleAvailableStyle = lipgloss.NewStyle().Foreground(good).Bold(true)
roleInUseStyle = lipgloss.NewStyle().Foreground(muted)
devNameStyle = lipgloss.NewStyle().Foreground(accent)
}