Create chat/ package with Message type, Room (NATS pub/sub + buffer), DB persistence helpers, and a unified templ component parameterized by Config (CSS prefix, post URL, color function, key propagation). Both c4game and snakegame now use chat.Room for message management and chatcomponents.Chat for rendering, eliminating the duplicated ChatMessage types, chat templ components, chatAutoScroll scripts, color functions, and inline buffer management.
78 lines
2.6 KiB
Plaintext
78 lines
2.6 KiB
Plaintext
package pages
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/ryanhamamura/c4/chat"
|
|
chatcomponents "github.com/ryanhamamura/c4/chat/components"
|
|
"github.com/ryanhamamura/c4/features/common/components"
|
|
"github.com/ryanhamamura/c4/features/common/layouts"
|
|
snakecomponents "github.com/ryanhamamura/c4/features/snakegame/components"
|
|
"github.com/ryanhamamura/c4/snake"
|
|
"github.com/starfederation/datastar-go/datastar"
|
|
)
|
|
|
|
// keydownScript builds the inline JS for a single data-on:keydown handler
|
|
// that dispatches WASD/arrow keys to direction POST endpoints.
|
|
func keydownScript(gameID string) string {
|
|
return fmt.Sprintf(
|
|
"const k=evt.key;"+
|
|
"if(k==='w'||k==='ArrowUp'){evt.preventDefault();%s}"+
|
|
"else if(k==='s'||k==='ArrowDown'){evt.preventDefault();%s}"+
|
|
"else if(k==='a'||k==='ArrowLeft'){evt.preventDefault();%s}"+
|
|
"else if(k==='d'||k==='ArrowRight'){evt.preventDefault();%s}",
|
|
datastar.PostSSE("/snake/%s/dir?d=0", gameID),
|
|
datastar.PostSSE("/snake/%s/dir?d=1", gameID),
|
|
datastar.PostSSE("/snake/%s/dir?d=2", gameID),
|
|
datastar.PostSSE("/snake/%s/dir?d=3", gameID),
|
|
)
|
|
}
|
|
|
|
templ GamePage(sg *snake.SnakeGame, mySlot int, messages []chat.Message, chatCfg chatcomponents.Config, gameID string) {
|
|
@layouts.Base("Snake") {
|
|
<main
|
|
class="snake-wrapper flex flex-col items-center gap-4 p-4"
|
|
data-signals={ `{"chatMsg":""}` }
|
|
data-init={ datastar.GetSSE("/snake/%s/events", gameID) }
|
|
data-on:keydown.throttle_100ms={ keydownScript(gameID) }
|
|
tabindex="0"
|
|
>
|
|
@components.BackToLobby()
|
|
<h1 class="text-3xl font-bold">~~~~</h1>
|
|
@snakecomponents.PlayerList(sg, mySlot)
|
|
@snakecomponents.StatusBanner(sg, mySlot, gameID)
|
|
if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished {
|
|
if sg.Mode == snake.ModeMultiplayer {
|
|
<div class="snake-game-area">
|
|
@snakecomponents.Board(sg)
|
|
@chatcomponents.Chat(messages, chatCfg)
|
|
</div>
|
|
} else {
|
|
@snakecomponents.Board(sg)
|
|
}
|
|
} else if sg.Mode == snake.ModeMultiplayer {
|
|
@chatcomponents.Chat(messages, chatCfg)
|
|
}
|
|
if sg.Mode == snake.ModeMultiplayer && (sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown) {
|
|
@snakecomponents.InviteLink(gameID)
|
|
}
|
|
</main>
|
|
}
|
|
}
|
|
|
|
templ JoinPage(gameID string) {
|
|
@layouts.Base("Snake - Join") {
|
|
@components.GameJoinPrompt(
|
|
fmt.Sprintf("/login?return_url=/snake/%s", gameID),
|
|
fmt.Sprintf("/register?return_url=/snake/%s", gameID),
|
|
fmt.Sprintf("/snake/%s", gameID),
|
|
)
|
|
}
|
|
}
|
|
|
|
templ NicknamePage(gameID string) {
|
|
@layouts.Base("Snake - Join") {
|
|
@components.NicknamePrompt(fmt.Sprintf("/snake/%s/join", gameID))
|
|
}
|
|
}
|