mirror of
https://github.com/anotherhadi/proton-vpn-tui.git
synced 2026-10-05 11:18:27 +02:00
122 lines
3.5 KiB
Go
122 lines
3.5 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
"github.com/charmbracelet/x/ansi"
|
|
zone "github.com/lrstanley/bubblezone/v2"
|
|
|
|
"github.com/anotherhadi/ilovetui/style"
|
|
|
|
"github.com/anotherhadi/proton-vpn-tui/internal/icons"
|
|
)
|
|
|
|
func (m Model) renderHeader() string {
|
|
title := headerStyle.Render("ProtonVPN")
|
|
if style.S.NerdFonts {
|
|
title = icons.I.Vpn + title
|
|
}
|
|
lines := []string{
|
|
title,
|
|
"",
|
|
m.renderStatusLine(),
|
|
m.renderModesLine(),
|
|
}
|
|
if searchLine := m.renderSearchLine(); searchLine != "" {
|
|
lines = append(lines, searchLine)
|
|
}
|
|
lines = append(lines, "", m.renderColumnHeader())
|
|
for i, l := range lines {
|
|
lines[i] = clampToWidth(l, m.width)
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func (m Model) renderColumnHeader() string {
|
|
indent, expandIcon := "", " "
|
|
prefixWidth := lipgloss.Width(indent) + lipgloss.Width(expandIcon) + 1
|
|
infoW, gaps := infoColumnLayout(m.showServersColumn())
|
|
nameWidth := max(m.width-boxOverhead-prefixWidth-infoW-loadColWidth-gaps, minNameWidth)
|
|
|
|
hdrStyle := lipgloss.NewStyle().Foreground(style.S.Subtle).Bold(true)
|
|
|
|
line := " " + indent + expandIcon + " " + hdrStyle.Render(fixedWidth("Name", nameWidth))
|
|
if m.showServersColumn() {
|
|
line += " " + hdrStyle.Render(fixedWidth("Servers", infoColWidth))
|
|
}
|
|
return line + " " + hdrStyle.Render(fixedWidth("Load", loadColWidth))
|
|
}
|
|
|
|
func oneLine(s string) string {
|
|
return strings.Join(strings.Fields(s), " ")
|
|
}
|
|
|
|
func clampToWidth(s string, width int) string {
|
|
if width <= 0 {
|
|
return s
|
|
}
|
|
return ansi.Truncate(s, width, "…")
|
|
}
|
|
|
|
func (m Model) renderStatusLine() string {
|
|
label := infoLabelStyle.Render("Status")
|
|
|
|
if m.connecting {
|
|
return label + primaryStyle.Render(icons.I.Connecting+" connecting…")
|
|
}
|
|
if m.disconnecting {
|
|
return label + mutedStyle.Render(icons.I.Loading+" loading…")
|
|
}
|
|
if m.statusErr != nil {
|
|
return label + warnStyle.Render(oneLine(m.statusErr.Error()))
|
|
}
|
|
if !m.statusLoaded {
|
|
if m.quickConnected {
|
|
return label + mutedStyle.Render(icons.I.Connected+" connected, "+icons.I.Loading+" loading…")
|
|
}
|
|
return label + mutedStyle.Render(icons.I.Loading+" loading…")
|
|
}
|
|
if !m.status.Connected {
|
|
return label + mutedStyle.Render(icons.I.Disconnected+" disconnected")
|
|
}
|
|
|
|
loc := m.status.Server
|
|
if m.status.Location != "" {
|
|
loc = fmt.Sprintf("%s (%s)", m.status.Server, m.status.Location)
|
|
}
|
|
text := fmt.Sprintf("%s connected: %s", icons.I.Connected, loc)
|
|
if m.status.Load > 0 {
|
|
text += fmt.Sprintf(" %d%% load", m.status.Load)
|
|
}
|
|
return label + connectedStyle.Render(text)
|
|
}
|
|
|
|
func (m Model) renderSearchLine() string {
|
|
if !m.searching && strings.TrimSpace(m.searchInput.Value()) == "" {
|
|
return ""
|
|
}
|
|
return infoLabelStyle.Render("Search") + m.searchInput.View()
|
|
}
|
|
|
|
func (m Model) renderModesLine() string {
|
|
label := infoLabelStyle.Render("Mode")
|
|
parts := []string{
|
|
zone.Mark(zoneBadgeFastest, modeBadge(icons.I.Fastest+" fastest", m.prefer == "fastest")),
|
|
zone.Mark(zoneBadgeRandom, modeBadge(icons.I.Random+" random", m.prefer == "random")),
|
|
zone.Mark(zoneBadgeSecureCore, modeBadge(icons.I.SecureCore+" secure core", m.filters.SecureCore)),
|
|
zone.Mark(zoneBadgeTor, modeBadge(icons.I.Tor+" tor", m.filters.Tor)),
|
|
zone.Mark(zoneBadgeP2P, modeBadge(icons.I.P2p+" p2p", m.filters.P2P)),
|
|
zone.Mark(zoneBadgeFree, modeBadge(icons.I.Free+" free", m.filters.Free)),
|
|
}
|
|
return label + strings.Join(parts, mutedStyle.Render(" "))
|
|
}
|
|
|
|
func modeBadge(text string, active bool) string {
|
|
if active {
|
|
return primaryStyle.Bold(true).Render(text)
|
|
}
|
|
return mutedStyle.Render(text)
|
|
}
|