From 4f1ee11fa33c90249aabfbb70cca52fe7b172dc2 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:06:01 -1000 Subject: [PATCH 1/2] fix: add Enter key handlers to all auth and nickname inputs Pressing Enter on the username field in login/register or the nickname field in the join-game prompt now submits the form, matching user expectations. Also add *.templ to the gitignore allowlist. --- .gitignore | 1 + features/auth/pages/login.templ | 48 ++++++++++++++++ features/auth/pages/login_templ.go | 23 ++++++-- features/auth/pages/register.templ | 57 +++++++++++++++++++ features/auth/pages/register_templ.go | 36 ++++++++++-- features/common/components/shared.templ | 60 ++++++++++++++++++++ features/common/components/shared_templ.go | 65 +++++++++++++--------- 7 files changed, 254 insertions(+), 36 deletions(-) create mode 100644 features/auth/pages/login.templ create mode 100644 features/auth/pages/register.templ create mode 100644 features/common/components/shared.templ diff --git a/.gitignore b/.gitignore index 9dca485..6a7aed8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ !.gitignore !*.go +!*.templ !*.sql !go.sum !go.mod diff --git a/features/auth/pages/login.templ b/features/auth/pages/login.templ new file mode 100644 index 0000000..3a4bcae --- /dev/null +++ b/features/auth/pages/login.templ @@ -0,0 +1,48 @@ +package pages + +import ( + "github.com/ryanhamamura/c4/features/common/layouts" + "github.com/starfederation/datastar-go/datastar" +) + +templ LoginPage() { + @layouts.Base("Login") { +
+

Login

+

Sign in to your account

+
+
+
+ + + + +
+ +
+

+ Don't have an account? Register +

+
+ } +} diff --git a/features/auth/pages/login_templ.go b/features/auth/pages/login_templ.go index 5d0cec4..55ae981 100644 --- a/features/auth/pages/login_templ.go +++ b/features/auth/pages/login_templ.go @@ -46,33 +46,46 @@ func LoginPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Login

Sign in to your account

Login

Sign in to your account

Don't have an account? Register

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">

Don't have an account? Register

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/auth/pages/register.templ b/features/auth/pages/register.templ new file mode 100644 index 0000000..00ef50a --- /dev/null +++ b/features/auth/pages/register.templ @@ -0,0 +1,57 @@ +package pages + +import ( + "github.com/ryanhamamura/c4/features/common/layouts" + "github.com/starfederation/datastar-go/datastar" +) + +templ RegisterPage() { + @layouts.Base("Register") { +
+

Register

+

Create a new account

+
+
+
+ + + + + + +
+ +
+

+ Already have an account? Login +

+
+ } +} diff --git a/features/auth/pages/register_templ.go b/features/auth/pages/register_templ.go index 7486adb..c7961d5 100644 --- a/features/auth/pages/register_templ.go +++ b/features/auth/pages/register_templ.go @@ -46,33 +46,59 @@ func RegisterPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Register

Create a new account

Register

Create a new account

Already have an account? Login

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">

Already have an account? Login

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/common/components/shared.templ b/features/common/components/shared.templ new file mode 100644 index 0000000..7d0b631 --- /dev/null +++ b/features/common/components/shared.templ @@ -0,0 +1,60 @@ +package components + +import "github.com/starfederation/datastar-go/datastar" + +templ BackToLobby() { + ← Back +} + +templ StealthTitle(class string) { + + + + + + +} + +templ NicknamePrompt(returnPath string) { +
+

Join Game

+

Enter your nickname to join the game.

+
+
+ + +
+ +
+
+} + +templ GameJoinPrompt(loginURL string, registerURL string, gamePath string) { +
+

Join Game

+

Log in to track your game history, or continue as a guest.

+
+ Login + Continue as Guest +
+

+ Don't have an account? + Register +

+
+} diff --git a/features/common/components/shared_templ.go b/features/common/components/shared_templ.go index 9c74d6c..7341d8c 100644 --- a/features/common/components/shared_templ.go +++ b/features/common/components/shared_templ.go @@ -107,20 +107,33 @@ func NicknamePrompt(returnPath string) templ.Component { templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

Join Game

Enter your nickname to join the game.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" required autofocus>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -144,51 +157,51 @@ func GameJoinPrompt(loginURL string, registerURL string, gamePath string) templ. }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent + templ_7745c5c3_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Join Game

Log in to track your game history, or continue as a guest.

Login

Join Game

Log in to track your game history, or continue as a guest.

Don't have an account? Login Register

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">Continue as Guest

Don't have an account? Register

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } From 2bea5bb48986bff2f8b90e927c4bd704272654b6 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:27:38 -1000 Subject: [PATCH 2/2] chore: gitignore generated _templ.go files, track .templ sources 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. --- .gitignore | 3 +- Taskfile.yml | 19 +- features/auth/pages/login_templ.go | 102 ---- features/auth/pages/register_templ.go | 115 ----- features/c4game/components/board.templ | 65 +++ features/c4game/components/board_templ.go | 199 -------- features/c4game/components/chat.templ | 69 +++ features/c4game/components/chat_templ.go | 173 ------- features/c4game/components/status.templ | 151 ++++++ features/c4game/components/status_templ.go | 352 ------------- features/c4game/pages/game.templ | 47 ++ features/c4game/pages/game_templ.go | 219 -------- features/common/components/shared_templ.go | 212 -------- features/common/layouts/base.templ | 21 + features/common/layouts/base_templ.go | 69 --- features/lobby/components/gamelist.templ | 109 ++++ features/lobby/components/gamelist_templ.go | 239 --------- features/lobby/pages/lobby.templ | 171 +++++++ features/lobby/pages/lobby_templ.go | 339 ------------- features/snakegame/components/board.templ | 113 +++++ features/snakegame/components/board_templ.go | 295 ----------- features/snakegame/components/chat.templ | 66 +++ features/snakegame/components/chat_templ.go | 173 ------- features/snakegame/components/status.templ | 137 +++++ features/snakegame/components/status_templ.go | 470 ------------------ features/snakegame/pages/game.templ | 75 +++ features/snakegame/pages/game_templ.go | 277 ----------- 27 files changed, 1044 insertions(+), 3236 deletions(-) delete mode 100644 features/auth/pages/login_templ.go delete mode 100644 features/auth/pages/register_templ.go create mode 100644 features/c4game/components/board.templ delete mode 100644 features/c4game/components/board_templ.go create mode 100644 features/c4game/components/chat.templ delete mode 100644 features/c4game/components/chat_templ.go create mode 100644 features/c4game/components/status.templ delete mode 100644 features/c4game/components/status_templ.go create mode 100644 features/c4game/pages/game.templ delete mode 100644 features/c4game/pages/game_templ.go delete mode 100644 features/common/components/shared_templ.go create mode 100644 features/common/layouts/base.templ delete mode 100644 features/common/layouts/base_templ.go create mode 100644 features/lobby/components/gamelist.templ delete mode 100644 features/lobby/components/gamelist_templ.go create mode 100644 features/lobby/pages/lobby.templ delete mode 100644 features/lobby/pages/lobby_templ.go create mode 100644 features/snakegame/components/board.templ delete mode 100644 features/snakegame/components/board_templ.go create mode 100644 features/snakegame/components/chat.templ delete mode 100644 features/snakegame/components/chat_templ.go create mode 100644 features/snakegame/components/status.templ delete mode 100644 features/snakegame/components/status_templ.go create mode 100644 features/snakegame/pages/game.templ delete mode 100644 features/snakegame/pages/game_templ.go diff --git a/.gitignore b/.gitignore index 6a7aed8..90c8dd5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,8 @@ !assets/**/* -# Generated CSS stays out of version control +# Generated files stay out of version control +*_templ.go assets/css/output.css # Deploy scripts and configs diff --git a/Taskfile.yml b/Taskfile.yml index 7e8f9b6..e0df8ef 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -6,12 +6,22 @@ tasks: cmds: - go run cmd/downloader/main.go + build:templ: + desc: Compile .templ files to Go + cmds: + - go tool templ generate + sources: + - "**/*.templ" + generates: + - "**/*_templ.go" + build:styles: desc: Build TailwindCSS styles cmds: - go tool gotailwind -i assets/css/input.css -o assets/css/output.css --minify sources: - "assets/css/input.css" + - "**/*.templ" - "**/*.go" generates: - "assets/css/output.css" @@ -21,8 +31,14 @@ tasks: cmds: - go build -o bin/c4 . deps: + - build:templ - build:styles + live:templ: + desc: Watch and recompile .templ files + cmds: + - go tool templ generate -watch + live:styles: desc: Watch and rebuild TailwindCSS styles cmds: @@ -36,12 +52,13 @@ tasks: -build.cmd "go build -tags=dev -o tmp/bin/c4 ." \ -build.bin "tmp/bin/c4" \ -build.exclude_dir "data,bin,tmp,deploy" \ - -build.include_ext "go" \ + -build.include_ext "go,templ" \ -misc.clean_on_exit "true" live: desc: Dev mode with hot-reload deps: + - live:templ - live:styles - live:server diff --git a/features/auth/pages/login_templ.go b/features/auth/pages/login_templ.go deleted file mode 100644 index 55ae981..0000000 --- a/features/auth/pages/login_templ.go +++ /dev/null @@ -1,102 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package pages - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "github.com/ryanhamamura/c4/features/common/layouts" - "github.com/starfederation/datastar-go/datastar" -) - -func LoginPage() templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Login

Sign in to your account

Don't have an account? Register

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Login").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/auth/pages/register_templ.go b/features/auth/pages/register_templ.go deleted file mode 100644 index c7961d5..0000000 --- a/features/auth/pages/register_templ.go +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package pages - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "github.com/ryanhamamura/c4/features/common/layouts" - "github.com/starfederation/datastar-go/datastar" -) - -func RegisterPage() templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Register

Create a new account

Already have an account? Login

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/components/board.templ b/features/c4game/components/board.templ new file mode 100644 index 0000000..ee4cb72 --- /dev/null +++ b/features/c4game/components/board.templ @@ -0,0 +1,65 @@ +package components + +import ( + "fmt" + + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +templ Board(g *game.Game, myColor int) { +
+ for col := 0; col < 7; col++ { + @column(g, col, myColor) + } +
+} + +templ column(g *game.Game, colIdx int, myColor int) { + if g.Status == game.StatusInProgress && myColor == g.CurrentTurn { +
+ for row := 0; row < 6; row++ { + @cell(g, row, colIdx) + } +
+ } else { +
+ for row := 0; row < 6; row++ { + @cell(g, row, colIdx) + } +
+ } +} + +templ cell(g *game.Game, row int, col int) { +
+} + +func cellClass(g *game.Game, row, col int) string { + color := g.Board[row][col] + activeTurn := 0 + if g.Status == game.StatusInProgress { + activeTurn = g.CurrentTurn + } + + class := "cell" + switch color { + case 1: + class += " red" + case 2: + class += " yellow" + } + if g.IsWinningCell(row, col) { + class += " winning" + } + if color != 0 && color == activeTurn { + class += " active-turn" + } + return class +} + +// suppress unused import +var _ = fmt.Sprintf diff --git a/features/c4game/components/board_templ.go b/features/c4game/components/board_templ.go deleted file mode 100644 index 7c638be..0000000 --- a/features/c4game/components/board_templ.go +++ /dev/null @@ -1,199 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - - "github.com/ryanhamamura/c4/game" - "github.com/starfederation/datastar-go/datastar" -) - -func Board(g *game.Game, myColor int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for col := 0; col < 7; col++ { - templ_7745c5c3_Err = column(g, col, myColor).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func column(g *game.Game, colIdx int, myColor int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var2 := templ.GetChildren(ctx) - if templ_7745c5c3_Var2 == nil { - templ_7745c5c3_Var2 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - if g.Status == game.StatusInProgress && myColor == g.CurrentTurn { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for row := 0; row < 6; row++ { - templ_7745c5c3_Err = cell(g, row, colIdx).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for row := 0; row < 6; row++ { - templ_7745c5c3_Err = cell(g, row, colIdx).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func cell(g *game.Game, row int, col int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var4 := templ.GetChildren(ctx) - if templ_7745c5c3_Var4 == nil { - templ_7745c5c3_Var4 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var5 = []any{cellClass(g, row, col)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func cellClass(g *game.Game, row, col int) string { - color := g.Board[row][col] - activeTurn := 0 - if g.Status == game.StatusInProgress { - activeTurn = g.CurrentTurn - } - - class := "cell" - switch color { - case 1: - class += " red" - case 2: - class += " yellow" - } - if g.IsWinningCell(row, col) { - class += " winning" - } - if color != 0 && color == activeTurn { - class += " active-turn" - } - return class -} - -// suppress unused import -var _ = fmt.Sprintf - -var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/components/chat.templ b/features/c4game/components/chat.templ new file mode 100644 index 0000000..c1e6c07 --- /dev/null +++ b/features/c4game/components/chat.templ @@ -0,0 +1,69 @@ +package components + +import ( + "fmt" + + "github.com/starfederation/datastar-go/datastar" +) + +type ChatMessage struct { + Nickname string `json:"nickname"` + Color int `json:"color"` + Message string `json:"message"` + Time int64 `json:"time"` +} + +var chatColors = map[int]string{ + 1: "#4a2a3a", + 2: "#2a4545", +} + +templ Chat(messages []ChatMessage, gameID string) { +
+
+ for _, m := range messages { +
+ + { m.Nickname }:  + + { m.Message } +
+ } + @chatAutoScroll() +
+
+ + +
+
+} + +templ chatAutoScroll() { + +} + +func chatColor(color int) string { + if c, ok := chatColors[color]; ok { + return c + } + return "#666" +} diff --git a/features/c4game/components/chat_templ.go b/features/c4game/components/chat_templ.go deleted file mode 100644 index b70da12..0000000 --- a/features/c4game/components/chat_templ.go +++ /dev/null @@ -1,173 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - - "github.com/starfederation/datastar-go/datastar" -) - -type ChatMessage struct { - Nickname string `json:"nickname"` - Color int `json:"color"` - Message string `json:"message"` - Time int64 `json:"time"` -} - -var chatColors = map[int]string{ - 1: "#4a2a3a", - 2: "#2a4545", -} - -func Chat(messages []ChatMessage, gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, m := range messages { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(m.Nickname) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 27, Col: 18} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, ":  ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(m.Message) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 29, Col: 22} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = chatAutoScroll().Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func chatAutoScroll() templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func chatColor(color int) string { - if c, ok := chatColors[color]; ok { - return c - } - return "#666" -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/components/status.templ b/features/c4game/components/status.templ new file mode 100644 index 0000000..d4f4e71 --- /dev/null +++ b/features/c4game/components/status.templ @@ -0,0 +1,151 @@ +package components + +import ( + "github.com/ryanhamamura/c4/config" + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +templ StatusBanner(g *game.Game, myColor int) { +
+ { statusMessage(g, myColor) } + if g.IsFinished() { + if g.RematchGameID != nil { + + Join Rematch + + } else { + + } + } +
+} + +templ PlayerInfo(g *game.Game, myColor int) { +
+ for _, info := range playerInfoPairs(g, myColor) { +
+ + { info.Label } +
+ } +
+} + +templ InviteLink(gameID string) { +
+

Share this link with your opponent:

+
+ { config.Global.AppURL + "/games/" + gameID } +
+ +
+} + +script copyToClipboard(url string) { + navigator.clipboard.writeText(url) +} + +func statusClass(g *game.Game, myColor int) string { + switch g.Status { + case game.StatusWaitingForPlayer: + return "alert bg-base-200 text-xl font-bold" + case game.StatusInProgress: + if g.CurrentTurn == myColor { + return "alert alert-success text-xl font-bold" + } + return "alert bg-base-200 text-xl font-bold" + case game.StatusWon: + if g.Winner != nil && g.Winner.Color == myColor { + return "alert alert-success text-xl font-bold" + } + return "alert alert-error text-xl font-bold" + case game.StatusDraw: + return "alert alert-warning text-xl font-bold" + } + return "alert bg-base-200 text-xl font-bold" +} + +func statusMessage(g *game.Game, myColor int) string { + switch g.Status { + case game.StatusWaitingForPlayer: + return "Waiting for opponent..." + case game.StatusInProgress: + if g.CurrentTurn == myColor { + return "Your turn!" + } + return opponentName(g, myColor) + "'s turn" + case game.StatusWon: + if g.Winner != nil && g.Winner.Color == myColor { + return "You win!" + } + if g.Winner != nil { + return g.Winner.Nickname + " wins!" + } + return "Game over" + case game.StatusDraw: + return "It's a draw!" + } + return "" +} + +func opponentName(g *game.Game, myColor int) string { + for _, p := range g.Players { + if p != nil && p.Color != myColor { + return p.Nickname + } + } + return "Opponent" +} + +type playerInfoData struct { + ColorClass string + Label string +} + +func playerInfoPairs(g *game.Game, myColor int) []playerInfoData { + var result []playerInfoData + + var myName, oppName string + var myClass, oppClass string + + for _, p := range g.Players { + if p == nil { + continue + } + colorClass := "yellow" + if p.Color == 1 { + colorClass = "red" + } + if p.Color == myColor { + myName = p.Nickname + myClass = colorClass + } else { + oppName = p.Nickname + oppClass = colorClass + } + } + + if oppName == "" { + oppName = "Waiting..." + } + + result = append(result, playerInfoData{ColorClass: myClass, Label: myName + " (You)"}) + result = append(result, playerInfoData{ColorClass: oppClass, Label: oppName}) + return result +} diff --git a/features/c4game/components/status_templ.go b/features/c4game/components/status_templ.go deleted file mode 100644 index c87c799..0000000 --- a/features/c4game/components/status_templ.go +++ /dev/null @@ -1,352 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "github.com/ryanhamamura/c4/config" - "github.com/ryanhamamura/c4/game" - "github.com/starfederation/datastar-go/datastar" -) - -func StatusBanner(g *game.Game, myColor int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var2 = []any{statusClass(g, myColor)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(statusMessage(g, myColor)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 11, Col: 29} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if g.IsFinished() { - if g.RematchGameID != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Join Rematch") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func PlayerInfo(g *game.Game, myColor int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, info := range playerInfoPairs(g, myColor) { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var8 = []any{"player-chip " + info.ColorClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(info.Label) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 38, Col: 22} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func InviteLink(gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var11 := templ.GetChildren(ctx) - if templ_7745c5c3_Var11 == nil { - templ_7745c5c3_Var11 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

Share this link with your opponent:

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(config.Global.AppURL + "/games/" + gameID) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 48, Col: 46} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(config.Global.AppURL+"/games/"+gameID)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func copyToClipboard(url string) templ.ComponentScript { - return templ.ComponentScript{ - Name: `__templ_copyToClipboard_1463`, - Function: `function __templ_copyToClipboard_1463(url){navigator.clipboard.writeText(url) -}`, - Call: templ.SafeScript(`__templ_copyToClipboard_1463`, url), - CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_1463`, url), - } -} - -func statusClass(g *game.Game, myColor int) string { - switch g.Status { - case game.StatusWaitingForPlayer: - return "alert bg-base-200 text-xl font-bold" - case game.StatusInProgress: - if g.CurrentTurn == myColor { - return "alert alert-success text-xl font-bold" - } - return "alert bg-base-200 text-xl font-bold" - case game.StatusWon: - if g.Winner != nil && g.Winner.Color == myColor { - return "alert alert-success text-xl font-bold" - } - return "alert alert-error text-xl font-bold" - case game.StatusDraw: - return "alert alert-warning text-xl font-bold" - } - return "alert bg-base-200 text-xl font-bold" -} - -func statusMessage(g *game.Game, myColor int) string { - switch g.Status { - case game.StatusWaitingForPlayer: - return "Waiting for opponent..." - case game.StatusInProgress: - if g.CurrentTurn == myColor { - return "Your turn!" - } - return opponentName(g, myColor) + "'s turn" - case game.StatusWon: - if g.Winner != nil && g.Winner.Color == myColor { - return "You win!" - } - if g.Winner != nil { - return g.Winner.Nickname + " wins!" - } - return "Game over" - case game.StatusDraw: - return "It's a draw!" - } - return "" -} - -func opponentName(g *game.Game, myColor int) string { - for _, p := range g.Players { - if p != nil && p.Color != myColor { - return p.Nickname - } - } - return "Opponent" -} - -type playerInfoData struct { - ColorClass string - Label string -} - -func playerInfoPairs(g *game.Game, myColor int) []playerInfoData { - var result []playerInfoData - - var myName, oppName string - var myClass, oppClass string - - for _, p := range g.Players { - if p == nil { - continue - } - colorClass := "yellow" - if p.Color == 1 { - colorClass = "red" - } - if p.Color == myColor { - myName = p.Nickname - myClass = colorClass - } else { - oppName = p.Nickname - oppClass = colorClass - } - } - - if oppName == "" { - oppName = "Waiting..." - } - - result = append(result, playerInfoData{ColorClass: myClass, Label: myName + " (You)"}) - result = append(result, playerInfoData{ColorClass: oppClass, Label: oppName}) - return result -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/pages/game.templ b/features/c4game/pages/game.templ new file mode 100644 index 0000000..eb328ad --- /dev/null +++ b/features/c4game/pages/game.templ @@ -0,0 +1,47 @@ +package pages + +import ( + "github.com/ryanhamamura/c4/features/c4game/components" + sharedcomponents "github.com/ryanhamamura/c4/features/common/components" + "github.com/ryanhamamura/c4/features/common/layouts" + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +templ GamePage(g *game.Game, myColor int, messages []components.ChatMessage) { + @layouts.Base("Connect 4") { +
+ @sharedcomponents.BackToLobby() + @sharedcomponents.StealthTitle("text-3xl font-bold") + @components.PlayerInfo(g, myColor) + @components.StatusBanner(g, myColor) +
+ @components.Board(g, myColor) + @components.Chat(messages, g.ID) +
+ if g.Status == game.StatusWaitingForPlayer { + @components.InviteLink(g.ID) + } +
+ } +} + +templ JoinPage(gameID string) { + @layouts.Base("Connect 4 - Join") { + @sharedcomponents.GameJoinPrompt( + "/login?return_url=/games/"+gameID, + "/register?return_url=/games/"+gameID, + "/games/"+gameID, + ) + } +} + +templ NicknamePage(gameID string) { + @layouts.Base("Connect 4 - Join") { + @sharedcomponents.NicknamePrompt("/games/" + gameID + "/join") + } +} diff --git a/features/c4game/pages/game_templ.go b/features/c4game/pages/game_templ.go deleted file mode 100644 index 903e9a3..0000000 --- a/features/c4game/pages/game_templ.go +++ /dev/null @@ -1,219 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package pages - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "github.com/ryanhamamura/c4/features/c4game/components" - sharedcomponents "github.com/ryanhamamura/c4/features/common/components" - "github.com/ryanhamamura/c4/features/common/layouts" - "github.com/ryanhamamura/c4/game" - "github.com/starfederation/datastar-go/datastar" -) - -func GamePage(g *game.Game, myColor int, messages []components.ChatMessage) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = sharedcomponents.BackToLobby().Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = sharedcomponents.StealthTitle("text-3xl font-bold").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.PlayerInfo(g, myColor).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.StatusBanner(g, myColor).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.Board(g, myColor).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.Chat(messages, g.ID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if g.Status == game.StatusWaitingForPlayer { - templ_7745c5c3_Err = components.InviteLink(g.ID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Connect 4").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func JoinPage(gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var4 := templ.GetChildren(ctx) - if templ_7745c5c3_Var4 == nil { - templ_7745c5c3_Var4 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = sharedcomponents.GameJoinPrompt( - "/login?return_url=/games/"+gameID, - "/register?return_url=/games/"+gameID, - "/games/"+gameID, - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Connect 4 - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func NicknamePage(gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var7 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = sharedcomponents.NicknamePrompt("/games/"+gameID+"/join").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Connect 4 - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/common/components/shared_templ.go b/features/common/components/shared_templ.go deleted file mode 100644 index 7341d8c..0000000 --- a/features/common/components/shared_templ.go +++ /dev/null @@ -1,212 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import "github.com/starfederation/datastar-go/datastar" - -func BackToLobby() templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "← Back") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func StealthTitle(class string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var2 := templ.GetChildren(ctx) - if templ_7745c5c3_Var2 == nil { - templ_7745c5c3_Var2 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var3 = []any{class} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func NicknamePrompt(returnPath string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var5 := templ.GetChildren(ctx) - if templ_7745c5c3_Var5 == nil { - templ_7745c5c3_Var5 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

Join Game

Enter your nickname to join the game.

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func GameJoinPrompt(loginURL string, registerURL string, gamePath string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Join Game

Log in to track your game history, or continue as a guest.

Login Continue as Guest

Don't have an account? Register

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/common/layouts/base.templ b/features/common/layouts/base.templ new file mode 100644 index 0000000..ffd9585 --- /dev/null +++ b/features/common/layouts/base.templ @@ -0,0 +1,21 @@ +package layouts + +import "github.com/ryanhamamura/c4/config" + +templ Base(title string) { + + + + { title } + + + + + + if config.Global.Environment == config.Dev { +
+ } + { children... } + + +} diff --git a/features/common/layouts/base_templ.go b/features/common/layouts/base_templ.go deleted file mode 100644 index 083a510..0000000 --- a/features/common/layouts/base_templ.go +++ /dev/null @@ -1,69 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package layouts - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import "github.com/ryanhamamura/c4/config" - -func Base(title string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/common/layouts/base.templ`, Line: 9, Col: 17} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if config.Global.Environment == config.Dev { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/lobby/components/gamelist.templ b/features/lobby/components/gamelist.templ new file mode 100644 index 0000000..24563e5 --- /dev/null +++ b/features/lobby/components/gamelist.templ @@ -0,0 +1,109 @@ +package components + +import ( + "fmt" + "time" + + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +templ GameList(games []GameListItem) { + if len(games) > 0 { +
+

Your Games

+
+ for _, g := range games { + @gameListEntry(g) + } +
+
+ } +} + +templ gameListEntry(g GameListItem) { +
+ +
+ { opponentDisplay(g) } + { statusText(g) } +
+
+ { formatTimeAgo(g.LastPlayed) } +
+
+ +
+} + +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) +} diff --git a/features/lobby/components/gamelist_templ.go b/features/lobby/components/gamelist_templ.go deleted file mode 100644 index 4157630..0000000 --- a/features/lobby/components/gamelist_templ.go +++ /dev/null @@ -1,239 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - "time" - - "github.com/ryanhamamura/c4/game" - "github.com/starfederation/datastar-go/datastar" -) - -func GameList(games []GameListItem) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - if len(games) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Your Games

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, g := range games { - templ_7745c5c3_Err = gameListEntry(g).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func gameListEntry(g GameListItem) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var2 := templ.GetChildren(ctx) - if templ_7745c5c3_Var2 == nil { - templ_7745c5c3_Var2 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(opponentDisplay(g)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 31, Col: 48} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 = []any{statusClass(g)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(statusText(g)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 32, Col: 50} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(formatTimeAgo(g.LastPlayed)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 35, Col: 66} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -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) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/lobby/pages/lobby.templ b/features/lobby/pages/lobby.templ new file mode 100644 index 0000000..cff0080 --- /dev/null +++ b/features/lobby/pages/lobby.templ @@ -0,0 +1,171 @@ +package pages + +import ( + "fmt" + + "github.com/ryanhamamura/c4/features/common/components" + "github.com/ryanhamamura/c4/features/common/layouts" + lobbycomponents "github.com/ryanhamamura/c4/features/lobby/components" + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +templ LobbyPage(data LobbyData) { + @layouts.Base("Game Lobby") { +
+ // Auth header + if data.IsLoggedIn { +
+ Logged in as { data.Username } + +
+ } else { +
+ Playing as guest. + Login + or + Register + to save your games. +
+ } + // Title +

+ @components.StealthTitle("") +

+ // Tab buttons +
+ + +
+ // Connect4 tab +
+

Start a new session

+
+
+ + +
+ +
+ @lobbycomponents.GameList(data.UserGames) +
+ // Snake tab +
+ // Nickname +
+
+ + +
+
+ // Speed selector +
+ +
+ for i, preset := range snake.SpeedPresets { + + } +
+
+ // Solo play +
+

Play Solo

+
+ for i, preset := range snake.GridPresets { + + } +
+
+ // Multiplayer +
+

Create Multiplayer Game

+
+ for i, preset := range snake.GridPresets { + + } +
+
+ // Active snake games + if len(data.ActiveSnakeGames) > 0 { + + } +
+
+ } +} diff --git a/features/lobby/pages/lobby_templ.go b/features/lobby/pages/lobby_templ.go deleted file mode 100644 index 9e11bd7..0000000 --- a/features/lobby/pages/lobby_templ.go +++ /dev/null @@ -1,339 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package pages - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - - "github.com/ryanhamamura/c4/features/common/components" - "github.com/ryanhamamura/c4/features/common/layouts" - lobbycomponents "github.com/ryanhamamura/c4/features/lobby/components" - "github.com/ryanhamamura/c4/snake" - "github.com/starfederation/datastar-go/datastar" -) - -func LobbyPage(data LobbyData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.IsLoggedIn { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Logged in as ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Username) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 22, Col: 47} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
Playing as guest. Login or Register to save your games.
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.StealthTitle("").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Start a new session

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = lobbycomponents.GameList(data.UserGames).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for i, preset := range snake.SpeedPresets { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Play Solo

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for i, preset := range snake.GridPresets { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

Create Multiplayer Game

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for i, preset := range snake.GridPresets { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(data.ActiveSnakeGames) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

Join a Game

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, g := range data.ActiveSnakeGames { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d\u00d7%d \u2014 %d/8 players", g.Width, g.Height, g.PlayerCount)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 161, Col: 96} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(g.StatusLabel) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 162, Col: 57} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Game Lobby").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/components/board.templ b/features/snakegame/components/board.templ new file mode 100644 index 0000000..6083935 --- /dev/null +++ b/features/snakegame/components/board.templ @@ -0,0 +1,113 @@ +package components + +import ( + "fmt" + + "github.com/ryanhamamura/c4/snake" +) + +func cellSizeForGrid(width, height int) int { + maxDim := width + if height > maxDim { + maxDim = height + } + switch { + case maxDim <= 15: + return 28 + case maxDim <= 20: + return 24 + case maxDim <= 30: + return 20 + case maxDim <= 40: + return 16 + default: + return 14 + } +} + +type cellInfo struct { + snakeIdx int // -1 = empty, -2 = food + isHead bool +} + +templ Board(sg *snake.SnakeGame) { +
+ if sg.State != nil && (sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished) { + @boardCells(sg) + } +
+} + +templ boardCells(sg *snake.SnakeGame) { + {{ state := sg.State }} + {{ grid := buildGrid(state) }} + {{ cellSize := cellSizeForGrid(state.Width, state.Height) }} + for y := 0; y < state.Height; y++ { +
+ for x := 0; x < state.Width; x++ { + {{ ci := grid[y][x] }} + if ci.snakeIdx == -2 { +
+ } else if ci.snakeIdx >= 0 { + {{ s := state.Snakes[ci.snakeIdx] }} + {{ bg := snakeColor(ci.snakeIdx) }} + if ci.isHead { + if s.Alive { +
+ } else { +
+ } + } else { + if s.Alive { +
+ } else { +
+ } + } + } else { +
+ } + } +
+ } +} + +func buildGrid(state *snake.GameState) [][]cellInfo { + grid := make([][]cellInfo, state.Height) + for y := 0; y < state.Height; y++ { + grid[y] = make([]cellInfo, state.Width) + for x := 0; x < state.Width; x++ { + grid[y][x] = cellInfo{snakeIdx: -1} + } + } + for fi := range state.Food { + f := state.Food[fi] + if f.X >= 0 && f.X < state.Width && f.Y >= 0 && f.Y < state.Height { + grid[f.Y][f.X] = cellInfo{snakeIdx: -2} + } + } + for si, s := range state.Snakes { + if s == nil { + continue + } + for bi, bp := range s.Body { + if bp.X >= 0 && bp.X < state.Width && bp.Y >= 0 && bp.Y < state.Height { + grid[bp.Y][bp.X] = cellInfo{snakeIdx: si, isHead: bi == 0} + } + } + } + return grid +} + +func snakeColor(idx int) string { + if idx >= 0 && idx < len(snake.SnakeColors) { + return snake.SnakeColors[idx] + } + return "#666" +} diff --git a/features/snakegame/components/board_templ.go b/features/snakegame/components/board_templ.go deleted file mode 100644 index cbe1237..0000000 --- a/features/snakegame/components/board_templ.go +++ /dev/null @@ -1,295 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - - "github.com/ryanhamamura/c4/snake" -) - -func cellSizeForGrid(width, height int) int { - maxDim := width - if height > maxDim { - maxDim = height - } - switch { - case maxDim <= 15: - return 28 - case maxDim <= 20: - return 24 - case maxDim <= 30: - return 20 - case maxDim <= 40: - return 16 - default: - return 14 - } -} - -type cellInfo struct { - snakeIdx int // -1 = empty, -2 = food - isHead bool -} - -func Board(sg *snake.SnakeGame) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if sg.State != nil && (sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished) { - templ_7745c5c3_Err = boardCells(sg).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func boardCells(sg *snake.SnakeGame) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var3 := templ.GetChildren(ctx) - if templ_7745c5c3_Var3 == nil { - templ_7745c5c3_Var3 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - state := sg.State - grid := buildGrid(state) - cellSize := cellSizeForGrid(state.Width, state.Height) - for y := 0; y < state.Height; y++ { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for x := 0; x < state.Width; x++ { - ci := grid[y][x] - if ci.snakeIdx == -2 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else if ci.snakeIdx >= 0 { - s := state.Snakes[ci.snakeIdx] - bg := snakeColor(ci.snakeIdx) - if ci.isHead { - if s.Alive { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } else { - if s.Alive { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func buildGrid(state *snake.GameState) [][]cellInfo { - grid := make([][]cellInfo, state.Height) - for y := 0; y < state.Height; y++ { - grid[y] = make([]cellInfo, state.Width) - for x := 0; x < state.Width; x++ { - grid[y][x] = cellInfo{snakeIdx: -1} - } - } - for fi := range state.Food { - f := state.Food[fi] - if f.X >= 0 && f.X < state.Width && f.Y >= 0 && f.Y < state.Height { - grid[f.Y][f.X] = cellInfo{snakeIdx: -2} - } - } - for si, s := range state.Snakes { - if s == nil { - continue - } - for bi, bp := range s.Body { - if bp.X >= 0 && bp.X < state.Width && bp.Y >= 0 && bp.Y < state.Height { - grid[bp.Y][bp.X] = cellInfo{snakeIdx: si, isHead: bi == 0} - } - } - } - return grid -} - -func snakeColor(idx int) string { - if idx >= 0 && idx < len(snake.SnakeColors) { - return snake.SnakeColors[idx] - } - return "#666" -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/components/chat.templ b/features/snakegame/components/chat.templ new file mode 100644 index 0000000..58c137b --- /dev/null +++ b/features/snakegame/components/chat.templ @@ -0,0 +1,66 @@ +package components + +import ( + "fmt" + + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +type ChatMessage struct { + Nickname string `json:"nickname"` + Slot int `json:"slot"` + Message string `json:"message"` + Time int64 `json:"time"` +} + +templ Chat(messages []ChatMessage, gameID string) { +
+
+ for _, m := range messages { +
+ + { m.Nickname + ": " } + + { m.Message } +
+ } +
+
+ + +
+ @chatAutoScroll() +
+} + +templ chatAutoScroll() { + +} + +func chatColor(slot int) string { + if slot >= 0 && slot < len(snake.SnakeColors) { + return snake.SnakeColors[slot] + } + return "#666" +} diff --git a/features/snakegame/components/chat_templ.go b/features/snakegame/components/chat_templ.go deleted file mode 100644 index db1946e..0000000 --- a/features/snakegame/components/chat_templ.go +++ /dev/null @@ -1,173 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - - "github.com/ryanhamamura/c4/snake" - "github.com/starfederation/datastar-go/datastar" -) - -type ChatMessage struct { - Nickname string `json:"nickname"` - Slot int `json:"slot"` - Message string `json:"message"` - Time int64 `json:"time"` -} - -func Chat(messages []ChatMessage, gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, m := range messages { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(m.Nickname + ": ") - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 23, Col: 25} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(m.Message) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 25, Col: 22} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = chatAutoScroll().Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func chatAutoScroll() templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func chatColor(slot int) string { - if slot >= 0 && slot < len(snake.SnakeColors) { - return snake.SnakeColors[slot] - } - return "#666" -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/components/status.templ b/features/snakegame/components/status.templ new file mode 100644 index 0000000..b09613d --- /dev/null +++ b/features/snakegame/components/status.templ @@ -0,0 +1,137 @@ +package components + +import ( + "fmt" + "math" + "time" + + "github.com/ryanhamamura/c4/config" + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +templ StatusBanner(sg *snake.SnakeGame, mySlot int, gameID string) { +
+ switch sg.Status { + case snake.StatusWaitingForPlayers: + if sg.Mode == snake.ModeSinglePlayer { +
Ready?
+ } else { +
Waiting for players...
+ } + case snake.StatusCountdown: + {{ remaining := time.Until(sg.CountdownEnd) }} + {{ secs := int(math.Ceil(remaining.Seconds())) }} + if secs < 0 { + {{ secs = 0 }} + } +
+ { fmt.Sprintf("Starting in %d...", secs) } +
+ case snake.StatusInProgress: + if sg.State != nil && mySlot >= 0 && mySlot < len(sg.State.Snakes) && sg.State.Snakes[mySlot] != nil && !sg.State.Snakes[mySlot].Alive { +
You're out!
+ } else if sg.Mode == snake.ModeSinglePlayer { +
+ { fmt.Sprintf("Score: %d", sg.Score) } +
+ } else { +
Go!
+ } + case snake.StatusFinished: + @finishedBanner(sg, mySlot, gameID) + } +
+} + +templ finishedBanner(sg *snake.SnakeGame, mySlot int, gameID string) { + if sg.Mode == snake.ModeSinglePlayer { +
+ { fmt.Sprintf("Game Over! Score: %d", sg.Score) } + @rematchOrJoin(sg, gameID) +
+ } else if sg.Winner != nil { + if sg.Winner.Slot == mySlot { +
+ You win! + @rematchOrJoin(sg, gameID) +
+ } else { +
+ { sg.Winner.Nickname + " wins!" } + @rematchOrJoin(sg, gameID) +
+ } + } else { +
+ It's a draw! + @rematchOrJoin(sg, gameID) +
+ } +} + +templ rematchOrJoin(sg *snake.SnakeGame, gameID string) { + if sg.RematchGameID != nil { + + Join Rematch + + } else { + + } +} + +templ PlayerList(sg *snake.SnakeGame, mySlot int) { +
+ for i, p := range sg.Players { + if p != nil { +
+ + + { p.Nickname } + if i == mySlot { + { " (You)" } + } + + if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { + if sg.State != nil && i < len(sg.State.Snakes) && sg.State.Snakes[i] != nil { + if sg.State.Snakes[i].Alive { + + { fmt.Sprintf("(%d)", len(sg.State.Snakes[i].Body)) } + + } else { + (dead) + } + } + } +
+ } + } +
+} + +templ InviteLink(gameID string) { + {{ fullURL := config.Global.AppURL + "/snake/" + gameID }} +
+

Share this link to invite players:

+
+ { fullURL } +
+ +
+} + +script copyToClipboard(url string) { + navigator.clipboard.writeText(url) +} diff --git a/features/snakegame/components/status_templ.go b/features/snakegame/components/status_templ.go deleted file mode 100644 index 57416eb..0000000 --- a/features/snakegame/components/status_templ.go +++ /dev/null @@ -1,470 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package components - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - "math" - "time" - - "github.com/ryanhamamura/c4/config" - "github.com/ryanhamamura/c4/snake" - "github.com/starfederation/datastar-go/datastar" -) - -func StatusBanner(sg *snake.SnakeGame, mySlot int, gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - switch sg.Status { - case snake.StatusWaitingForPlayers: - if sg.Mode == snake.ModeSinglePlayer { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Ready?
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
Waiting for players...
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - case snake.StatusCountdown: - remaining := time.Until(sg.CountdownEnd) - secs := int(math.Ceil(remaining.Seconds())) - if secs < 0 { - secs = 0 - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Starting in %d...", secs)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 29, Col: 45} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case snake.StatusInProgress: - if sg.State != nil && mySlot >= 0 && mySlot < len(sg.State.Snakes) && sg.State.Snakes[mySlot] != nil && !sg.State.Snakes[mySlot].Alive { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
You're out!
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else if sg.Mode == snake.ModeSinglePlayer { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Score: %d", sg.Score)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 36, Col: 42} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
Go!
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - case snake.StatusFinished: - templ_7745c5c3_Err = finishedBanner(sg, mySlot, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func finishedBanner(sg *snake.SnakeGame, mySlot int, gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var4 := templ.GetChildren(ctx) - if templ_7745c5c3_Var4 == nil { - templ_7745c5c3_Var4 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - if sg.Mode == snake.ModeSinglePlayer { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Game Over! Score: %d", sg.Score)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 50, Col: 50} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = rematchOrJoin(sg, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else if sg.Winner != nil { - if sg.Winner.Slot == mySlot { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
You win!") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = rematchOrJoin(sg, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(sg.Winner.Nickname + " wins!") - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 61, Col: 35} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = rematchOrJoin(sg, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
It's a draw!") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = rematchOrJoin(sg, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func rematchOrJoin(sg *snake.SnakeGame, gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - if sg.RematchGameID != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Join Rematch") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func PlayerList(sg *snake.SnakeGame, mySlot int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var10 := templ.GetChildren(ctx) - if templ_7745c5c3_Var10 == nil { - templ_7745c5c3_Var10 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for i, p := range sg.Players { - if p != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(p.Nickname) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 96, Col: 18} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if i == mySlot { - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(" (You)") - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 98, Col: 17} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { - if sg.State != nil && i < len(sg.State.Snakes) && sg.State.Snakes[i] != nil { - if sg.State.Snakes[i].Alive { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("(%d)", len(sg.State.Snakes[i].Body))) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 105, Col: 60} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "(dead)") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func InviteLink(gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var15 := templ.GetChildren(ctx) - if templ_7745c5c3_Var15 == nil { - templ_7745c5c3_Var15 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - fullURL := config.Global.AppURL + "/snake/" + gameID - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

Share this link to invite players:

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fullURL) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 123, Col: 12} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(fullURL)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func copyToClipboard(url string) templ.ComponentScript { - return templ.ComponentScript{ - Name: `__templ_copyToClipboard_1463`, - Function: `function __templ_copyToClipboard_1463(url){navigator.clipboard.writeText(url) -}`, - Call: templ.SafeScript(`__templ_copyToClipboard_1463`, url), - CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_1463`, url), - } -} - -var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/pages/game.templ b/features/snakegame/pages/game.templ new file mode 100644 index 0000000..49b90de --- /dev/null +++ b/features/snakegame/pages/game.templ @@ -0,0 +1,75 @@ +package pages + +import ( + "fmt" + + "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 []snakecomponents.ChatMessage, gameID string) { + @layouts.Base("Snake") { +
+ @components.BackToLobby() +

~~~~

+ @snakecomponents.PlayerList(sg, mySlot) + @snakecomponents.StatusBanner(sg, mySlot, gameID) + if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { + if sg.Mode == snake.ModeMultiplayer { +
+ @snakecomponents.Board(sg) + @snakecomponents.Chat(messages, gameID) +
+ } else { + @snakecomponents.Board(sg) + } + } else if sg.Mode == snake.ModeMultiplayer { + @snakecomponents.Chat(messages, gameID) + } + if sg.Mode == snake.ModeMultiplayer && (sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown) { + @snakecomponents.InviteLink(gameID) + } +
+ } +} + +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)) + } +} diff --git a/features/snakegame/pages/game_templ.go b/features/snakegame/pages/game_templ.go deleted file mode 100644 index 123aaab..0000000 --- a/features/snakegame/pages/game_templ.go +++ /dev/null @@ -1,277 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.1001 -package pages - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - - "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), - ) -} - -func GamePage(sg *snake.SnakeGame, mySlot int, messages []snakecomponents.ChatMessage, gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.BackToLobby().Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

~~~~

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = snakecomponents.PlayerList(sg, mySlot).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = snakecomponents.StatusBanner(sg, mySlot, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { - if sg.Mode == snake.ModeMultiplayer { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = snakecomponents.Board(sg).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = snakecomponents.Chat(messages, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = snakecomponents.Board(sg).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } else if sg.Mode == snake.ModeMultiplayer { - templ_7745c5c3_Err = snakecomponents.Chat(messages, gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if sg.Mode == snake.ModeMultiplayer && (sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown) { - templ_7745c5c3_Err = snakecomponents.InviteLink(gameID).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Snake").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func JoinPage(gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var7 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = components.GameJoinPrompt( - fmt.Sprintf("/login?return_url=/snake/%s", gameID), - fmt.Sprintf("/register?return_url=/snake/%s", gameID), - fmt.Sprintf("/snake/%s", gameID), - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Snake - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func NicknamePage(gameID string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var9 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = components.NicknamePrompt(fmt.Sprintf("/snake/%s/join", gameID)).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = layouts.Base("Snake - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate