mirror of
https://github.com/anotherhadi/proton-vpn-tui.git
synced 2026-10-05 11:18:27 +02:00
181 lines
4.2 KiB
Go
181 lines
4.2 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"io"
|
|
"strings"
|
|
|
|
"charm.land/bubbles/v2/list"
|
|
tea "charm.land/bubbletea/v2"
|
|
"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/backend"
|
|
"github.com/anotherhadi/proton-vpn-tui/internal/config"
|
|
"github.com/anotherhadi/proton-vpn-tui/internal/icons"
|
|
)
|
|
|
|
const (
|
|
infoColWidth = 14
|
|
loadColWidth = 8
|
|
minNameWidth = 6
|
|
|
|
boxOverhead = 4
|
|
|
|
serversColumnMinWidth = 45
|
|
)
|
|
|
|
type rowDelegate struct {
|
|
showInfo bool
|
|
}
|
|
|
|
func (d rowDelegate) Height() int { return 3 }
|
|
|
|
func (d rowDelegate) Spacing() int { return 1 }
|
|
func (d rowDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
|
|
|
|
func (d rowDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
|
r, ok := item.(row)
|
|
if !ok {
|
|
return
|
|
}
|
|
selected := index == m.Index()
|
|
|
|
indent := strings.Repeat(" ", int(r.kind))
|
|
|
|
expandIcon := " "
|
|
if r.kind == rowCountry || r.kind == rowCity {
|
|
if r.expanded {
|
|
expandIcon = icons.I.Expanded
|
|
} else {
|
|
expandIcon = icons.I.Collapsed
|
|
}
|
|
}
|
|
|
|
label, info, load := d.columns(r)
|
|
|
|
nameStyle := lipgloss.NewStyle().Foreground(style.S.Text)
|
|
metaStyle := lipgloss.NewStyle().Foreground(style.S.Muted)
|
|
if selected {
|
|
nameStyle = nameStyle.Foreground(style.S.Primary).Bold(true)
|
|
metaStyle = metaStyle.Foreground(style.S.Primary)
|
|
}
|
|
|
|
prefixWidth := lipgloss.Width(indent) + lipgloss.Width(expandIcon) + 1
|
|
infoW, gaps := infoColumnLayout(d.showInfo)
|
|
nameWidth := max(m.Width()-boxOverhead-prefixWidth-infoW-loadColWidth-gaps, minNameWidth)
|
|
|
|
nameCol := nameStyle.Render(fixedWidth(label, nameWidth))
|
|
loadCol := metaStyle.Render(fixedWidth(load, loadColWidth))
|
|
|
|
borderType := style.S.BorderType
|
|
if !selected && r.kind != rowCountry {
|
|
borderType = lipgloss.HiddenBorder()
|
|
}
|
|
|
|
boxStyle := lipgloss.NewStyle().
|
|
Border(borderType).
|
|
BorderForeground(rowBorderColor(r, selected)).
|
|
Padding(0, 1)
|
|
|
|
line := indent + expandIcon + " " + nameCol
|
|
if d.showInfo {
|
|
line += " " + metaStyle.Render(fixedWidth(info, infoColWidth))
|
|
}
|
|
line += " " + loadCol
|
|
rendered := boxStyle.Render(clampToWidth(line, m.Width()-boxOverhead))
|
|
fmt.Fprint(w, zone.Mark(rowZoneID(index), rendered))
|
|
}
|
|
|
|
func infoColumnLayout(showInfo bool) (width, gaps int) {
|
|
if !showInfo {
|
|
return 0, 1
|
|
}
|
|
return infoColWidth, 2
|
|
}
|
|
|
|
func fixedWidth(s string, width int) string {
|
|
if width <= 0 {
|
|
return ""
|
|
}
|
|
return lipgloss.NewStyle().Width(width).MaxWidth(width).Inline(true).
|
|
Render(ansi.Truncate(s, width, "…"))
|
|
}
|
|
|
|
func rowBorderColor(r row, selected bool) color.Color {
|
|
if selected {
|
|
return style.S.Primary
|
|
}
|
|
if r.kind == rowCountry {
|
|
return style.S.Subtle
|
|
}
|
|
return style.S.Background
|
|
}
|
|
|
|
func (d rowDelegate) columns(r row) (name, info, load string) {
|
|
switch r.kind {
|
|
case rowCountry:
|
|
countryName := backend.CountryName(r.country)
|
|
mark := ""
|
|
switch {
|
|
case config.Global.App.ShowFlags:
|
|
mark = backend.FlagEmoji(r.country) + " "
|
|
case icons.I.Country != "":
|
|
mark = icons.I.Country + " "
|
|
}
|
|
fav := ""
|
|
if r.favorite {
|
|
fav = icons.I.Favorite + " "
|
|
}
|
|
name = fav + mark + countryName
|
|
info = fmt.Sprintf("%d servers", r.serverCount)
|
|
load = fmt.Sprintf("%s %d%%", icons.I.Load, r.avgLoad)
|
|
|
|
case rowCity:
|
|
cityName := r.city
|
|
if cityName == "" {
|
|
cityName = "Other"
|
|
}
|
|
cityIcon := ""
|
|
if icons.I.City != "" {
|
|
cityIcon = icons.I.City + " "
|
|
}
|
|
fav := ""
|
|
if r.favorite {
|
|
fav = icons.I.Favorite + " "
|
|
}
|
|
name = fav + cityIcon + cityName
|
|
info = fmt.Sprintf("%d servers", r.serverCount)
|
|
load = fmt.Sprintf("%s %d%%", icons.I.Load, r.avgLoad)
|
|
|
|
default:
|
|
serverIcon := ""
|
|
if icons.I.Server != "" {
|
|
serverIcon = icons.I.Server + " "
|
|
}
|
|
fav := ""
|
|
if r.favorite {
|
|
fav = icons.I.Favorite + " "
|
|
}
|
|
name = fav + serverIcon + r.server.Name
|
|
|
|
var badges []string
|
|
if r.server.SecureCore {
|
|
badges = append(badges, icons.I.SecureCore)
|
|
}
|
|
if r.server.Tor {
|
|
badges = append(badges, icons.I.Tor)
|
|
}
|
|
if r.server.P2P {
|
|
badges = append(badges, icons.I.P2p)
|
|
}
|
|
info = strings.Join(badges, " ")
|
|
load = fmt.Sprintf("%s %d%%", icons.I.Load, r.server.Load)
|
|
}
|
|
return
|
|
}
|