Previously patchAll() re-rendered the full GameContent on every chat message, which was inefficient and could cause UI glitches. Now we append just the new ChatMessage to the chat history element.
79 lines
2.3 KiB
Plaintext
79 lines
2.3 KiB
Plaintext
package components
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/ryanhamamura/games/chat"
|
|
"github.com/starfederation/datastar-go/datastar"
|
|
)
|
|
|
|
// ColorFunc resolves a player slot to a CSS color string.
|
|
type ColorFunc func(slot int) string
|
|
|
|
// Config holds the game-specific settings for rendering a chat component.
|
|
type Config struct {
|
|
// CSSPrefix is used for element IDs and CSS classes (e.g. "c4" or "snake").
|
|
CSSPrefix string
|
|
// PostURL is the URL to POST chat messages to (e.g. "/games/{id}/chat").
|
|
PostURL string
|
|
// Color resolves a player slot to a CSS color string.
|
|
Color ColorFunc
|
|
// StopKeyPropagation adds data-on:keydown.stop="" to the input to prevent
|
|
// key events from propagating (needed for snake to avoid steering while typing).
|
|
StopKeyPropagation bool
|
|
}
|
|
|
|
// ChatMessage renders a single chat message. Used for appending new messages via SSE.
|
|
templ ChatMessage(m chat.Message, cfg Config) {
|
|
<div class={ cfg.CSSPrefix + "-chat-msg" }>
|
|
<span style={ fmt.Sprintf("color:%s;font-weight:bold;", cfg.Color(m.Slot)) }>
|
|
{ m.Nickname + ": " }
|
|
</span>
|
|
<span>{ m.Message }</span>
|
|
</div>
|
|
}
|
|
|
|
templ Chat(messages []chat.Message, cfg Config) {
|
|
<div id={ cfg.CSSPrefix + "-chat" } class={ cfg.CSSPrefix + "-chat" }>
|
|
<div id={ cfg.CSSPrefix + "-chat-history" } class={ cfg.CSSPrefix + "-chat-history" }>
|
|
for _, m := range messages {
|
|
@ChatMessage(m, cfg)
|
|
}
|
|
</div>
|
|
<div class={ cfg.CSSPrefix + "-chat-input" } data-morph-ignore>
|
|
if cfg.StopKeyPropagation {
|
|
<input
|
|
type="text"
|
|
placeholder="Chat..."
|
|
autocomplete="off"
|
|
data-bind="chatMsg"
|
|
data-on:keydown__stop={ "evt.key === 'Enter' && " + datastar.PostSSE("%s", cfg.PostURL) }
|
|
/>
|
|
} else {
|
|
<input
|
|
type="text"
|
|
placeholder="Chat..."
|
|
autocomplete="off"
|
|
data-bind="chatMsg"
|
|
data-on:keydown={ "evt.key === 'Enter' && " + datastar.PostSSE("%s", cfg.PostURL) }
|
|
/>
|
|
}
|
|
<button
|
|
type="button"
|
|
data-on:click={ datastar.PostSSE("%s", cfg.PostURL) }
|
|
>
|
|
Send
|
|
</button>
|
|
</div>
|
|
@chatAutoScroll(cfg.CSSPrefix)
|
|
</div>
|
|
}
|
|
|
|
script chatAutoScroll(cssPrefix string) {
|
|
var el = document.querySelector('.' + cssPrefix + '-chat-history');
|
|
if (!el) return;
|
|
el.scrollTop = el.scrollHeight;
|
|
new MutationObserver(function(){ el.scrollTop = el.scrollHeight; })
|
|
.observe(el, {childList:true, subtree:true});
|
|
}
|