Generated _templ.go files are deterministic output from .templ sources, same as output.css from input.css. Remove them from version control to reduce diff noise and merge conflicts. Add build:templ and live:templ tasks to the Taskfile so generation happens as part of the build.
110 lines
2.4 KiB
Plaintext
110 lines
2.4 KiB
Plaintext
package components
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/ryanhamamura/c4/game"
|
|
"github.com/starfederation/datastar-go/datastar"
|
|
)
|
|
|
|
templ GameList(games []GameListItem) {
|
|
if len(games) > 0 {
|
|
<div class="mt-8 text-left">
|
|
<h3 class="mb-4 text-center text-lg font-bold">Your Games</h3>
|
|
<div class="flex flex-col gap-2">
|
|
for _, g := range games {
|
|
@gameListEntry(g)
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
templ gameListEntry(g GameListItem) {
|
|
<div class="flex items-center gap-2 p-2 bg-base-200 rounded-lg transition-colors hover:bg-base-300">
|
|
<a
|
|
href={ templ.SafeURL("/games/" + g.ID) }
|
|
class="flex-1 flex justify-between items-center px-2 py-1 no-underline text-base-content"
|
|
>
|
|
<div class="flex flex-col gap-1">
|
|
<span class="font-bold">{ opponentDisplay(g) }</span>
|
|
<span class={ statusClass(g) }>{ statusText(g) }</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-xs opacity-60">{ formatTimeAgo(g.LastPlayed) }</span>
|
|
</div>
|
|
</a>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm btn-square hover:btn-error"
|
|
data-on:click={ datastar.DeleteSSE("/games/%s", g.ID) }
|
|
>
|
|
×
|
|
</button>
|
|
</div>
|
|
}
|
|
|
|
func statusText(g GameListItem) string {
|
|
switch game.GameStatus(g.Status) {
|
|
case game.StatusWaitingForPlayer:
|
|
return "Waiting for opponent"
|
|
case game.StatusInProgress:
|
|
if g.IsMyTurn {
|
|
return "Your turn!"
|
|
}
|
|
return "Opponent's turn"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func statusClass(g GameListItem) string {
|
|
switch game.GameStatus(g.Status) {
|
|
case game.StatusWaitingForPlayer:
|
|
return "text-sm opacity-60"
|
|
case game.StatusInProgress:
|
|
if g.IsMyTurn {
|
|
return "text-sm text-success font-bold"
|
|
}
|
|
return "text-sm"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func opponentDisplay(g GameListItem) string {
|
|
if g.OpponentName == "" {
|
|
return "Waiting for opponent..."
|
|
}
|
|
return "vs " + g.OpponentName
|
|
}
|
|
|
|
func formatTimeAgo(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
duration := time.Since(t)
|
|
|
|
if duration < time.Minute {
|
|
return "just now"
|
|
}
|
|
if duration < time.Hour {
|
|
mins := int(duration.Minutes())
|
|
if mins == 1 {
|
|
return "1 minute ago"
|
|
}
|
|
return fmt.Sprintf("%d minutes ago", mins)
|
|
}
|
|
if duration < 24*time.Hour {
|
|
hours := int(duration.Hours())
|
|
if hours == 1 {
|
|
return "1 hour ago"
|
|
}
|
|
return fmt.Sprintf("%d hours ago", hours)
|
|
}
|
|
days := int(duration.Hours() / 24)
|
|
if days == 1 {
|
|
return "yesterday"
|
|
}
|
|
return fmt.Sprintf("%d days ago", days)
|
|
}
|