Files
Completed/cmd/tui/main.go

151 lines
3.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// Styles
var (
titleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
Padding(0, 1)
itemStyle = lipgloss.NewStyle().
PaddingLeft(2)
scoreStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFD700")) // Gold color
errorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF5555")).
Bold(true)
)
type Game struct {
ID int64 `json:"id"`
Name string `json:"name"`
PlatformID int64 `json:"platform_id"`
Score int32 `json:"score"`
ReleaseYear string `json:"release_year"`
Finished string `json:"finished,omitempty"`
}
type GamesResponse struct {
Body []Game `json:"body"`
}
type Model struct {
spinner spinner.Model
loading bool
err error
games []Game
quitting bool
}
func InitialModel() Model {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return Model{spinner: s, loading: true}
}
type loadedMsg []Game
type errMsg error
func fetchGames() tea.Msg {
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get("http://localhost:8080/games")
if err != nil {
return errMsg(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errMsg(fmt.Errorf("status code: %d", resp.StatusCode))
}
var data GamesResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return errMsg(err)
}
return loadedMsg(data.Body)
}
func (m Model) Init() tea.Cmd {
return tea.Batch(m.spinner.Tick, fetchGames)
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "q" || msg.String() == "ctrl+c" {
m.quitting = true
return m, tea.Quit
}
case spinner.TickMsg:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
case loadedMsg:
m.loading = false
m.games = msg
return m, nil
case errMsg:
m.loading = false
m.err = msg
return m, nil
}
return m, nil
}
func (m Model) View() string {
if m.quitting {
return "Bye!\n"
}
s := titleStyle.Render("My Game Collection") + "\n\n"
if m.loading {
s += fmt.Sprintf("%s Loading games...", m.spinner.View())
} else if m.err != nil {
s += errorStyle.Render(fmt.Sprintf("Error: %v", m.err))
s += "\nMake sure the API is running on localhost:8080"
} else {
if len(m.games) == 0 {
s += "No games found."
} else {
for _, g := range m.games {
score := scoreStyle.Render(fmt.Sprintf("★ %d", g.Score))
line := fmt.Sprintf("• %s (%s) - %s", g.Name, g.ReleaseYear, score)
if g.Finished != "" {
line += " [✓ Finished]"
}
s += itemStyle.Render(line) + "\n"
}
}
}
s += strings.Repeat("\n", 2) + lipgloss.NewStyle().Foreground(lipgloss.Color("#555")).Render("Press 'q' to quit")
return s
}
func main() {
p := tea.NewProgram(InitialModel())
if _, err := p.Run(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}