phase 0.3
This commit is contained in:
commit
4a94bfc4e7
52 changed files with 11653 additions and 0 deletions
125
internal/ui/airport_view.go
Normal file
125
internal/ui/airport_view.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"skyrama-tui/internal/model"
|
||||
"skyrama-tui/internal/style"
|
||||
)
|
||||
|
||||
// AirportView renders the full airport overview panel.
|
||||
// Uses a single panel with two sections to avoid nested-panel line-count overhead.
|
||||
func AirportView(airport *model.Airport, country string, width int) string {
|
||||
if airport == nil {
|
||||
return lipgloss.JoinVertical(lipgloss.Left,
|
||||
style.PanelHeader.Render("🛫 Airport"),
|
||||
"",
|
||||
style.Muted.Render("No airport found for your account."),
|
||||
style.Muted.Render("Ask an admin to create one, or use the db_manager tool."),
|
||||
)
|
||||
}
|
||||
|
||||
barW := 22
|
||||
|
||||
fuelBar := style.ResourceBar(
|
||||
int(airport.CurrentFuel), atLeast1(int(airport.MaxFuelCapacity)), barW, style.ColorFuel)
|
||||
fuelLine := row(
|
||||
style.FuelChip.Width(14).Render("⛽ Fuel"),
|
||||
fuelBar,
|
||||
style.Muted.Render(fmt.Sprintf(" %d / %d", airport.CurrentFuel, airport.MaxFuelCapacity)),
|
||||
style.Muted.Render(fmt.Sprintf(" +%d/min", airport.FuelGenerationRate)),
|
||||
)
|
||||
|
||||
paxBar := style.ResourceBar(
|
||||
int(airport.CurrentPassengers), atLeast1(int(airport.MaxPassengerCapacity)), barW, style.ColorPassenger)
|
||||
paxLine := row(
|
||||
style.PassengerChip.Width(14).Render("👤 Passengers"),
|
||||
paxBar,
|
||||
style.Muted.Render(fmt.Sprintf(" %d / %d", airport.CurrentPassengers, airport.MaxPassengerCapacity)),
|
||||
style.Muted.Render(fmt.Sprintf(" +%d/min", airport.PassengerGenerationRate)),
|
||||
)
|
||||
|
||||
cashLine := style.CashChip.Render(fmt.Sprintf("💰 Cash $%s", formatCash(airport.Cash)))
|
||||
|
||||
xpNeeded := model.XPToNextLevel(airport.PlayerLevel)
|
||||
xpCurrent := model.XPIntoLevel(airport.PlayerLevel, airport.ExperiencePoints)
|
||||
xpBar := style.ResourceBar(xpCurrent, atLeast1(xpNeeded), barW, style.ColorGreen)
|
||||
xpLine := row(
|
||||
style.Bold.Width(14).Render("✨ XP"),
|
||||
xpBar,
|
||||
style.Muted.Render(fmt.Sprintf(" %d / %d", xpCurrent, xpNeeded)),
|
||||
)
|
||||
|
||||
avail := style.StatusIdle.Render("Open ✓")
|
||||
if !airport.IsAvailable {
|
||||
avail = style.StatusMaint.Render("Closed ✗")
|
||||
}
|
||||
|
||||
sinceSec := time.Since(airport.ResourcesLastUpdatedAt)
|
||||
syncStr := "just now"
|
||||
if sinceSec >= time.Second {
|
||||
syncStr = sinceSec.Round(time.Second).String() + " ago"
|
||||
}
|
||||
|
||||
// Single panel containing both sections — avoids double-border line overhead.
|
||||
panel := style.Panel.Width(width).Render(lipgloss.JoinVertical(lipgloss.Left,
|
||||
style.PanelHeader.Render("Resources"),
|
||||
fuelLine,
|
||||
paxLine,
|
||||
cashLine,
|
||||
xpLine,
|
||||
"",
|
||||
style.PanelHeader.Render("Airport Info"),
|
||||
kv("Name", airport.Name),
|
||||
kv("Country", country),
|
||||
kv("Level", fmt.Sprintf("Lv.%d", airport.PlayerLevel)),
|
||||
kv("Status", avail),
|
||||
kv("Last sync", syncStr),
|
||||
))
|
||||
|
||||
// 1-line header to keep total view height minimal.
|
||||
header := lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
style.Bold.Foreground(style.ColorSky).Width(width/2).Render("🛫 "+airport.Name),
|
||||
style.Muted.Width(width/2).Align(lipgloss.Right).
|
||||
Render(fmt.Sprintf("Lv.%d 📍 %s", airport.PlayerLevel, country)),
|
||||
)
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, header, panel)
|
||||
}
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func row(parts ...string) string {
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, parts...)
|
||||
}
|
||||
|
||||
func kv(key, val string) string {
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
style.Muted.Width(14).Render(key+":"),
|
||||
val,
|
||||
)
|
||||
}
|
||||
|
||||
func formatCash(v uint64) string {
|
||||
s := fmt.Sprintf("%d", v)
|
||||
var b strings.Builder
|
||||
for i, ch := range s {
|
||||
if i > 0 && (len(s)-i)%3 == 0 {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
b.WriteRune(ch)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// atLeast1 prevents division-by-zero in progress bar calculations.
|
||||
func atLeast1(n int) int {
|
||||
if n <= 0 {
|
||||
return 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue