151 lines
3.2 KiB
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)
|
|
}
|
|
}
|