mirror of
https://github.com/anotherhadi/proton-vpn-tui.git
synced 2026-10-05 11:18:27 +02:00
init
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package settings
|
||||
|
||||
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"
|
||||
|
||||
"github.com/anotherhadi/ilovetui/style"
|
||||
|
||||
"github.com/anotherhadi/proton-vpn-tui/internal/backend"
|
||||
"github.com/anotherhadi/proton-vpn-tui/internal/icons"
|
||||
)
|
||||
|
||||
func clampToWidth(s string, width int) string {
|
||||
if width <= 0 {
|
||||
return s
|
||||
}
|
||||
return ansi.Truncate(s, width, "…")
|
||||
}
|
||||
|
||||
type delegate struct{}
|
||||
|
||||
func (d delegate) Height() int { return 2 }
|
||||
func (d delegate) Spacing() int { return 1 }
|
||||
func (d delegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
|
||||
|
||||
func (d delegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
it, ok := listItem.(item)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
selected := index == m.Index()
|
||||
|
||||
var titleStyle, descStyle, valueStyle lipgloss.Style
|
||||
if selected {
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||
BorderForeground(style.S.Primary).
|
||||
Foreground(style.S.Primary).
|
||||
Bold(true).
|
||||
PaddingLeft(1)
|
||||
descStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||
BorderForeground(style.S.Primary).
|
||||
Foreground(style.S.Muted).
|
||||
PaddingLeft(1)
|
||||
valueStyle = lipgloss.NewStyle().Foreground(style.S.Success).Bold(true)
|
||||
} else {
|
||||
titleStyle = lipgloss.NewStyle().Foreground(style.S.Text).PaddingLeft(2)
|
||||
descStyle = lipgloss.NewStyle().Foreground(style.S.Muted).PaddingLeft(2)
|
||||
valueStyle = lipgloss.NewStyle().Foreground(style.S.Success)
|
||||
}
|
||||
|
||||
value := renderCycleValue(valueStyle.Render(humanizeKebab(it.setting.Value)))
|
||||
if isOnOffSetting(it.setting) {
|
||||
value = renderToggle(it.setting.Value == "on")
|
||||
}
|
||||
|
||||
width := m.Width()
|
||||
title := titleStyle.Render(humanizeKebab(it.key))
|
||||
line1 := padLine(justifyBetween(title, value, width), width)
|
||||
line2 := padLine(descStyle.Render(it.setting.Description), width)
|
||||
fmt.Fprintf(w, "%s\n%s", line1, line2)
|
||||
}
|
||||
|
||||
func justifyBetween(left, right string, width int) string {
|
||||
gap := width - lipgloss.Width(left) - lipgloss.Width(right)
|
||||
if gap < 1 {
|
||||
gap = 1
|
||||
}
|
||||
return left + strings.Repeat(" ", gap) + right
|
||||
}
|
||||
|
||||
func padLine(s string, width int) string {
|
||||
s = clampToWidth(s, width)
|
||||
if pad := width - lipgloss.Width(s); pad > 0 {
|
||||
s += strings.Repeat(" ", pad)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func isOnOffSetting(s backend.Setting) bool {
|
||||
if len(s.Options) != 2 {
|
||||
return false
|
||||
}
|
||||
var hasOn, hasOff bool
|
||||
for _, o := range s.Options {
|
||||
hasOn = hasOn || o.ID == "on"
|
||||
hasOff = hasOff || o.ID == "off"
|
||||
}
|
||||
return hasOn && hasOff
|
||||
}
|
||||
|
||||
func renderCycleValue(value string) string {
|
||||
arrow := lipgloss.NewStyle().Foreground(style.S.Muted)
|
||||
return arrow.Render("< ") + value + arrow.Render(" >")
|
||||
}
|
||||
|
||||
func renderToggle(on bool) string {
|
||||
if on {
|
||||
return renderBadge(" ●", style.S.Success)
|
||||
}
|
||||
return renderBadge("● ", style.S.Subtle)
|
||||
}
|
||||
|
||||
func renderBadge(label string, fill color.Color) string {
|
||||
body := lipgloss.NewStyle().
|
||||
Background(fill).
|
||||
Foreground(style.S.Background).
|
||||
Bold(true).
|
||||
Render(label)
|
||||
|
||||
if icons.I.PillLeft == "" && icons.I.PillRight == "" {
|
||||
return body
|
||||
}
|
||||
|
||||
cap := lipgloss.NewStyle().Foreground(fill).Background(style.S.Background)
|
||||
return cap.Render(icons.I.PillLeft) + body + cap.Render(icons.I.PillRight)
|
||||
}
|
||||
|
||||
var settingWordOverrides = map[string]string{
|
||||
"nat": "NAT",
|
||||
"dns": "DNS",
|
||||
"vpn": "VPN",
|
||||
"ip": "IP",
|
||||
"ipv6": "IPv6",
|
||||
"netshield": "NetShield",
|
||||
}
|
||||
|
||||
func humanizeKebab(s string) string {
|
||||
words := strings.Split(s, "-")
|
||||
for i, w := range words {
|
||||
if override, ok := settingWordOverrides[strings.ToLower(w)]; ok {
|
||||
words[i] = override
|
||||
continue
|
||||
}
|
||||
if w == "" {
|
||||
continue
|
||||
}
|
||||
words[i] = strings.ToUpper(w[:1]) + w[1:]
|
||||
}
|
||||
return strings.Join(words, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user