mirror of
https://github.com/anotherhadi/proton-vpn-tui.git
synced 2026-10-05 19:28:28 +02:00
init
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user