1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
package ui
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/humdrum-tiv/sportsball/internal/model"
)
// scheduleView renders one team's full season schedule: a scrollable list of
// past results and upcoming fixtures, with an "upcoming" divider between them.
func (a App) scheduleView() string {
t := a.schedule.Team
l, _ := model.LeagueByID(a.scheduleLeague)
name := t.FullName
if name == "" {
name = t.Name
}
title := styleTitle.Render(" SCHEDULE ") + " " +
lipgloss.NewStyle().Foreground(teamColor(t.Color)).Bold(true).Render(l.Icon+" "+name)
if a.schedule.Season != "" {
title += " " + styleSubtle.Render(a.schedule.Season)
}
footer := styleHelp.Render(strings.Join([]string{
keys.Up.Help().Key + "/" + keys.Down.Help().Key + " scroll",
keys.Refresh.Help().Key + " refresh",
keys.Back.Help().Key + " back",
}, styleFaint.Render(" · ")))
frame := func(body string) string {
return styleApp.Render(strings.Join([]string{title, "", body, "", footer}, "\n"))
}
switch {
case a.scheduleErr != nil:
return frame(styleSubtle.Render("schedule unavailable — " + a.scheduleErr.Error()))
case len(a.schedule.Games) == 0:
return frame(styleSubtle.Render(a.spinner.View() + " loading schedule…"))
}
lines, _ := a.scheduleLines()
avail := a.scheduleAvail()
off := a.scheduleScroll
if off > len(lines)-avail {
off = len(lines) - avail
}
if off < 0 {
off = 0
}
end := off + avail
if end > len(lines) {
end = len(lines)
}
return frame(strings.Join(lines[off:end], "\n"))
}
func (a App) scheduleAvail() int {
avail := a.height - 5 // title, blank, blank, footer (+1 slack)
if avail < 3 {
avail = 3
}
return avail
}
func (a App) scheduleScrollMax() int {
lines, _ := a.scheduleLines()
if m := len(lines) - a.scheduleAvail(); m > 0 {
return m
}
return 0
}
// scheduleLines renders each game to a line and reports the line index of the
// first upcoming game, so the view can open at "now" rather than the top.
func (a App) scheduleLines() (lines []string, upcomingTop int) {
t := a.schedule.Team
dividerDone := false
for _, g := range a.schedule.Games {
if !dividerDone && g.State == model.StatePre {
upcomingTop = len(lines)
lines = append(lines, styleFaint.Render(" ── upcoming ──"))
dividerDone = true
}
lines = append(lines, a.scheduleRow(g, t))
}
return lines, upcomingTop
}
// scheduleRow formats one game from the perspective of team t: date, home/away
// marker, opponent, and the result (W/L + score for finals, time for upcoming).
func (a App) scheduleRow(g model.Game, t model.Team) string {
home := g.Home.ID == t.ID
opp := g.Home
if home {
opp = g.Away
}
date := styleSubtle.Render(fmt.Sprintf("%-10s", g.Start.Format("Mon Jan 2")))
loc := "vs"
if !home {
loc = "@ "
}
oppAbbr := lipgloss.NewStyle().Foreground(teamColor(opp.Color)).Bold(true).
Render(fmt.Sprintf("%-3s", opp.Abbr))
oppName := truncate(opp.Name, 16)
oppName += strings.Repeat(" ", max(0, 16-lipgloss.Width(oppName)))
var result string
switch g.State {
case model.StateLive:
us, them := teamScores(g, home)
result = lipgloss.NewStyle().Foreground(colLive).Bold(true).
Render(fmt.Sprintf("LIVE %d-%d", us, them))
case model.StateFinal:
us, them := teamScores(g, home)
won := (home && g.Home.Winner) || (!home && g.Away.Winner)
mark, style := "L", styleFinal
switch {
case won:
mark, style = "W", styleWin
case us == them:
mark = "T"
}
result = style.Render(fmt.Sprintf("%s %d-%d", mark, us, them))
default:
result = styleSubtle.Render(startLabel(g))
}
return fmt.Sprintf("%s %s %s %s %s", date, styleSubtle.Render(loc), oppAbbr, oppName, result)
}
// teamScores returns (team's score, opponent's score) given whether the team is
// home in g.
func teamScores(g model.Game, home bool) (us, them int) {
if home {
return g.Home.Score, g.Away.Score
}
return g.Away.Score, g.Home.Score
}
|