diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go index 8c37d01..7b153d9 100644 --- a/features/c4game/handlers.go +++ b/features/c4game/handlers.go @@ -16,6 +16,7 @@ import ( "github.com/ryanhamamura/games/connect4" "github.com/ryanhamamura/games/db/repository" "github.com/ryanhamamura/games/features/c4game/pages" + sharedcomponents "github.com/ryanhamamura/games/features/common/components" "github.com/ryanhamamura/games/sessions" ) @@ -119,7 +120,7 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag } sendPing := func() error { - return sse.PatchSignals([]byte(fmt.Sprintf(`{"lastPing":%d}`, time.Now().UnixMilli()))) + return sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli())) } // Send initial render and ping diff --git a/features/c4game/pages/game.templ b/features/c4game/pages/game.templ index 16204cf..455c91d 100644 --- a/features/c4game/pages/game.templ +++ b/features/c4game/pages/game.templ @@ -15,10 +15,10 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c @layouts.Base("Connect 4") {
- @sharedcomponents.ConnectionIndicator() + @sharedcomponents.ConnectionIndicator(0) @GameContent(g, myColor, messages, chatCfg)
} diff --git a/features/common/components/shared.templ b/features/common/components/shared.templ index 76d67b6..de24752 100644 --- a/features/common/components/shared.templ +++ b/features/common/components/shared.templ @@ -1,6 +1,10 @@ package components -import "github.com/starfederation/datastar-go/datastar" +import ( + "fmt" + + "github.com/starfederation/datastar-go/datastar" +) templ BackToLobby() { ← Back @@ -45,18 +49,29 @@ templ NicknamePrompt(returnPath string) { } // ConnectionIndicator shows a small dot indicating SSE connection status. -// It requires a `lastPing` signal (unix ms timestamp) to be set by the server. -templ ConnectionIndicator() { +// Server patches this with a timestamp; client JS detects staleness. +templ ConnectionIndicator(lastPing int64) {
- +
+ @connectionWatcher() +} + +script connectionWatcher() { + setInterval(function() { + var el = document.getElementById('connection-indicator'); + var dot = document.getElementById('connection-dot'); + if (!el || !dot) return; + var lastPing = parseInt(el.dataset.lastPing, 10) || 0; + var stale = Date.now() - lastPing > 20000; + dot.classList.toggle('bg-green-500', !stale); + dot.classList.toggle('bg-red-500', stale); + }, 1000); } templ GameJoinPrompt(loginURL string, registerURL string, gamePath string) { diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go index d5aac7b..4448beb 100644 --- a/features/snakegame/handlers.go +++ b/features/snakegame/handlers.go @@ -13,6 +13,7 @@ import ( "github.com/ryanhamamura/games/chat" chatcomponents "github.com/ryanhamamura/games/chat/components" + sharedcomponents "github.com/ryanhamamura/games/features/common/components" "github.com/ryanhamamura/games/features/snakegame/pages" "github.com/ryanhamamura/games/sessions" "github.com/ryanhamamura/games/snake" @@ -125,7 +126,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sm *scs.Sess } sendPing := func() error { - return sse.PatchSignals([]byte(fmt.Sprintf(`{"lastPing":%d}`, time.Now().UnixMilli()))) + return sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli())) } // Send initial render and ping diff --git a/features/snakegame/pages/game.templ b/features/snakegame/pages/game.templ index f8fe244..a1b2cd4 100644 --- a/features/snakegame/pages/game.templ +++ b/features/snakegame/pages/game.templ @@ -32,12 +32,12 @@ templ GamePage(sg *snake.SnakeGame, mySlot int, messages []chat.Message, chatCfg @layouts.Base("Snake") {
- @components.ConnectionIndicator() + @components.ConnectionIndicator(0) @GameContent(sg, mySlot, messages, chatCfg, gameID)
}